diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml new file mode 100644 index 000000000..496970357 --- /dev/null +++ b/.github/workflows/linux-build.yml @@ -0,0 +1,79 @@ +# Linux build, unit tests, and the IPC open-forwarding regression gate. +# +# The existing workflows build Windows and generate documentation; nothing +# builds QElectroTech on Linux or runs tests/ in CI. This does both. +# +# It runs inside an ubuntu:26.04 container rather than directly on the runner +# because the IPC gate below needs Qt 6.10.2. The ubuntu-latest image ships +# Qt 6.4, and whether the crash that gate covers reproduces on 6.4 has never +# been checked -- a gate that cannot go red is worse than no gate. +name: Linux build and tests + +on: + push: + branches: [master] + pull_request: + workflow_dispatch: + +jobs: + linux: + name: Build and test (Qt 6, Debug) + runs-on: ubuntu-latest + container: ubuntu:26.04 + + steps: + - name: Install build and test dependencies + run: | + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + ca-certificates build-essential cmake ninja-build git pkg-config \ + qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \ + libqt6svg6-dev libqt6sql6-sqlite libsqlite3-dev libcups2-dev \ + libxkbcommon-x11-0 \ + xvfb openbox xdotool x11-utils + # extra-cmake-modules and the KF6 libraries are installed rather than + # left to FetchContent: without them CMake builds extra-cmake-modules + # from source, and its Qt Help documentation target then demands + # Qt6{Core,Gui,Widgets,DBus}Tools, failing the configure with an error + # that appears to be about QElectroTech and is not. + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + extra-cmake-modules libkf6coreaddons-dev libkf6widgetsaddons-dev \ + qt6-svg-plugins + + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Configure + # Debug, not Release. Whether a use-after-free faults depends on what + # the allocator does with the freed block: the commit before #868 + # crashes every time built Debug and survives every time built + # -O3 -DNDEBUG, so a Release job would never catch a regression here. + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + cmake -B build -G Ninja \ + -DCMAKE_BUILD_TYPE=Debug \ + -DQT_VERSION_MAJOR=6 + + - name: Build + run: cmake --build build --parallel $(nproc) + + - name: Unit tests + run: xvfb-run -a ctest --test-dir build --output-on-failure + + - name: IPC open-forwarding regression (#868) + # Fails the job only on an actual crash. An inconclusive run means the + # crash path was not exercised -- it proves nothing, which is not the + # same as a regression, and failing on it would make this job flaky + # rather than informative. + run: | + set +e + tests/ipc-regression/run.sh --binary build/qelectrotech + status=$? + set -e + case $status in + 0) echo "::notice::IPC gate passed" ;; + 2) echo "::warning::IPC gate inconclusive -- the crash path was not exercised, nothing was proven" ;; + *) echo "::error::IPC gate failed -- forwarded files are being opened inside the socket handler again" + exit 1 ;; + esac diff --git a/.github/workflows/windows-msi.yml b/.github/workflows/windows-msi.yml index 73d57e7ab..03428963a 100644 --- a/.github/workflows/windows-msi.yml +++ b/.github/workflows/windows-msi.yml @@ -42,6 +42,9 @@ jobs: pages: write id-token: write # Required by SignPath + env: + SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }} + outputs: qt6_msi: ${{ steps.export.outputs.msi_name_qt6 }} @@ -290,7 +293,8 @@ jobs: # PR/run must never attempt a signing request. # (cf. DieterMayerOSS:fix/msi-signing-fork-guard, d3f60c88) - name: Sign MSI via SignPath - if: github.repository == 'qelectrotech/qelectrotech-source-mirror' + id: sign + if: github.repository == 'qelectrotech/qelectrotech-source-mirror' && env.SIGNPATH_API_TOKEN != '' uses: signpath/github-action-submit-signing-request@v2 with: api-token: ${{ secrets.SIGNPATH_API_TOKEN }} @@ -345,7 +349,7 @@ jobs: run: | Write-Host "=== MSI build summary (${{ matrix.flavor }}) ===" Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}" - Write-Host "Signed : true" + Write-Host "Signed : ${{ steps.sign.outcome == 'success' }}" # --------------------------------------------------------------------------- # Job 2 : Génère et déploie la page GitHub Pages une fois le MSI publié, diff --git a/CMakeLists.txt b/CMakeLists.txt index 4fc0fffd4..60ea552e4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,7 @@ if(WIN32) ${QET_SRC_FILES} ${QM_FILES} ${QET_DIR}/qelectrotech.qrc + ${QET_DIR}/ico/icon-themes.qrc ) else() add_executable( @@ -147,6 +148,7 @@ else() ${QET_SRC_FILES} ${QM_FILES} ${QET_DIR}/qelectrotech.qrc + ${QET_DIR}/ico/icon-themes.qrc ) endif() diff --git a/ico/icon-themes.qrc b/ico/icon-themes.qrc new file mode 100644 index 000000000..1a3635da5 --- /dev/null +++ b/ico/icon-themes.qrc @@ -0,0 +1,285 @@ + + + + + themes/qet/index.theme + 16x16/application-exit.png + 16x16/arrow-left-double.png + 16x16/arrow-left.png + 16x16/arrow-right-double.png + 16x16/arrow-right.png + 16x16/circle.png + 16x16/conductor-edit.png + 16x16/conductor-reset.png + 16x16/conductor.png + 16x16/configure-toolbars.png + 16x16/configure.png + 16x16/diagram.png + 16x16/dialog-cancel.png + 16x16/dialog-ok.png + 16x16/document-close.png + 16x16/document-export.png + 16x16/document-import.png + 16x16/document-new.png + 16x16/document-open-recent.png + 16x16/document-open.png + 16x16/document-print-frame.png + 16x16/document-print.png + 16x16/document-save-all.png + 16x16/document-save-as.png + 16x16/document-save.png + 16x16/east.png + 16x16/edit-clear-locationbar-ltr.png + 16x16/edit-clear-locationbar-rtl.png + 16x16/edit-clear.png + 16x16/edit-copy.png + 16x16/edit-cut.png + 16x16/edit-delete.png + 16x16/edit-download.png + 16x16/edit-paste.png + 16x16/edit-redo.png + 16x16/edit-rename.png + 16x16/edit-select-all.png + 16x16/edit-select-invert.png + 16x16/edit-select-none.png + 16x16/edit-table-cell-merge.png + 16x16/edit-table-cell-split.png + 16x16/edit-table-delete-column.png + 16x16/edit-table-delete-row.png + 16x16/edit-table-insert-column-left.png + 16x16/edit-table-insert-column-right.png + 16x16/edit-table-insert-row-above.png + 16x16/edit-table-insert-row-under.png + 16x16/edit-undo.png + 16x16/element-delete.png + 16x16/element-edit.png + 16x16/element-master.png + 16x16/element-new.png + 16x16/element-slave.png + 16x16/element.png + 16x16/endline-circle.png + 16x16/endline-diamond.png + 16x16/endline-none.png + 16x16/endline-simple.png + 16x16/endline-triangle.png + 16x16/export-csv.png + 16x16/flip.png + 16x16/folder-delete.png + 16x16/folder-edit.png + 16x16/folder-new.png + 16x16/folder-only-this.png + 16x16/folder-open.png + 16x16/folder-properties.png + 16x16/folder-show-all.png + 16x16/folder.png + 16x16/folio-delete.png + 16x16/folio-new.png + 16x16/folio-properties.png + 16x16/folio-ref-coming.png + 16x16/go-bottom.png + 16x16/go-company.png + 16x16/go-down-double.png + 16x16/go-down.png + 16x16/go-home.png + 16x16/go-top.png + 16x16/go-up-double.png + 16x16/go-up.png + 16x16/grid.png + 16x16/ground.png + 16x16/help-contents.png + 16x16/help-donate.png + 16x16/item-cancel.png + 16x16/item-copy.png + 16x16/item-move.png + 16x16/kdenlive-show-video.png + 16x16/label.png + 16x16/list-add.png + 16x16/list-remove.png + 16x16/masquer.png + 16x16/mirror.png + 16x16/move.png + 16x16/neutral.png + 16x16/north.png + 16x16/object-group.png + 16x16/object-rotate-right.png + 16x16/orientations.png + 16x16/phase.png + 16x16/preferences-desktop-user.png + 16x16/project-close.png + 16x16/project-delete.png + 16x16/project-new.png + 16x16/project-properties.png + 16x16/project.png + 16x16/qet.png + 16x16/qt.png + 16x16/run-dxf.png + 16x16/select.png + 16x16/south.png + 16x16/table-of-content.png + 16x16/terminal.png + 16x16/terminalstrip.png + 16x16/text-xml.png + 16x16/transform-rotate.png + 16x16/user-busy.png + 16x16/user-online.png + 16x16/view-fullscreen.png + 16x16/view-refresh.png + 16x16/view-restore.png + 16x16/west.png + 16x16/window-new.png + 16x16/zoom-draw.png + 16x16/zoom-fit-best.png + 16x16/zoom-in.png + 16x16/zoom-original.png + 16x16/zoom-out.png + 22x22/Actions-player-time-icon.png + 22x22/all_pages.png + 22x22/application-exit.png + 22x22/applications-development-translation.png + 22x22/arc.png + 22x22/arrow-left-double.png + 22x22/arrow-left.png + 22x22/arrow-right-double.png + 22x22/arrow-right.png + 22x22/autoconnect.png + 22x22/bring_forward.png + 22x22/conductor2.png + 22x22/conductor2.png + 22x22/configure-toolbars.png + 22x22/configure.png + 22x22/diagram.png + 22x22/diagram_add.png + 22x22/diagram_add.png + 22x22/diagram_bg.png + 22x22/diagram_del.png + 22x22/diagram_del.png + 22x22/dialog-cancel.png + 22x22/dialog-information.png + 22x22/dialog-information.png + 22x22/dialog-ok.png + 22x22/document-close.png + 22x22/document-export.png + 22x22/document-import.png + 22x22/document-new.png + 22x22/document-open-recent.png + 22x22/document-open.png + 22x22/document-print-frame.png + 22x22/document-print.png + 22x22/document-save-all.png + 22x22/document-save-as.png + 22x22/document-save.png + 22x22/edit-clear-locationbar-ltr.png + 22x22/edit-clear-locationbar-rtl.png + 22x22/edit-clear.png + 22x22/edit-copy.png + 22x22/edit-cut.png + 22x22/edit-delete.png + 22x22/edit-paste.png + 22x22/edit-redo.png + 22x22/edit-rename.png + 22x22/edit-select-all.png + 22x22/edit-table-cell-merge.png + 22x22/edit-table-cell-split.png + 22x22/edit-table-delete-column.png + 22x22/edit-table-delete-row.png + 22x22/edit-table-insert-column-left.png + 22x22/edit-table-insert-column-right.png + 22x22/edit-table-insert-row-above.png + 22x22/edit-table-insert-row-under.png + 22x22/edit-undo.png + 22x22/element-delete.png + 22x22/element-edit.png + 22x22/element-new.png + 22x22/ellipse.png + 22x22/export-csv.png + 22x22/folder-delete.png + 22x22/folder-edit.png + 22x22/folder-new.png + 22x22/folder-open.png + 22x22/format-text-subscript.png + 22x22/format-text-superscript.png + 22x22/go-company.png + 22x22/go-down.png + 22x22/go-home.png + 22x22/go-up.png + 22x22/grid.png + 22x22/guides.png + 22x22/hotspot.png + 22x22/insert-image.png + 22x22/label.png + 22x22/landscape.png + 22x22/line.png + 22x22/list-add.png + 22x22/list-remove.png + 22x22/lower.png + 22x22/move.png + 22x22/names.png + 22x22/object-locked.png + 22x22/object-rotate-right.png + 22x22/object-unlocked.png + 22x22/pdf-import.png + 22x22/polygon.png + 22x22/portrait.png + 22x22/preferences-desktop-user.png + 22x22/project-close.png + 22x22/project-delete.png + 22x22/project-new.png + 22x22/project.png + 22x22/raise.png + 22x22/rectangle.png + 22x22/restaurer.png + 22x22/select.png + 22x22/send_backward.png + 22x22/single_page.png + 22x22/start.png + 22x22/terminal.png + 22x22/terminalstrip.png + 22x22/text-xml.png + 22x22/text.png + 22x22/textfield.png + 22x22/titleblock-bottom.png + 22x22/titleblock-right.png + 22x22/transform-scale.png + 22x22/two_pages.png + 22x22/view-fit-window.png + 22x22/view-fullscreen.png + 22x22/view-refresh.png + 22x22/view-restore.png + 22x22/view_fit_width.png + 22x22/window-new.png + 22x22/zoom-draw.png + 22x22/zoom-fit-best.png + 22x22/zoom-in.png + 22x22/zoom-original.png + 22x22/zoom-out.png + 32x32/application-pdf.png + 32x32/format-text-bold.png + 32x32/format-text-color.png + 32x32/format-text-italic.png + 32x32/format-text-underline.png + 32x32/image-x-eps.png + 32x32/insert-link.png + 32x32/printer.png + 32x32/qt.png + 32x32/simplifyrichtext.png + 32x32/text-xml.png + 48x48/document-print-frame.png + 48x48/user-away-extended.png + 48x48/user-away.png + 48x48/view-pim-journal.png + 128x128/diagram.png + 128x128/document-export.png + 128x128/plasmagik.png + 128x128/printer.png + 128x128/project.png + 128x128/settings.png + 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-vertical-symbolic.svg + breeze-icons/scalable/apps/hidef/draw-bezier-curves.svg + breeze-icons/scalable/apps/hidef/transform-crop.svg + generated/ellipse-to-bezier.svg + generated/rect-to-bezier.svg + generated/rect-to-polyline.svg + + diff --git a/ico/themes/qet/index.theme b/ico/themes/qet/index.theme new file mode 100644 index 000000000..74c1b904b --- /dev/null +++ b/ico/themes/qet/index.theme @@ -0,0 +1,30 @@ +[Icon Theme] +Name=qet +Comment=QElectroTech icons +Directories=16x16,22x22,32x32,48x48,128x128,scalable + +[16x16] +Size=16 +Type=Fixed + +[22x22] +Size=22 +Type=Fixed + +[32x32] +Size=32 +Type=Fixed + +[48x48] +Size=48 +Type=Fixed + +[128x128] +Size=128 +Type=Fixed + +[scalable] +Size=22 +Type=Scalable +MinSize=8 +MaxSize=256 diff --git a/misc/make_icon_themes.py b/misc/make_icon_themes.py new file mode 100644 index 000000000..864798b1a --- /dev/null +++ b/misc/make_icon_themes.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# Copyright 2006-2026 The QElectroTech Team +# This file is part of QElectroTech. +# +# QElectroTech is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# QElectroTech is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with QElectroTech. If not, see . +"""Build QET's icon theme "qet" from the PNG and SVG sources in ico/. + +Run from the repository root: + + python3 misc/make_icon_themes.py + +It writes: + + ico/themes/qet/index.theme the theme description + ico/icon-themes.qrc resource file listing the theme + +The theme does not copy any file. The .qrc aliases the existing +ico//.png files into the theme layout Qt's icon loader +expects (themes/qet//.png), so QIcon::fromTheme("name") +finds them. Idempotent: running it twice changes nothing. +""" + +import os +import re +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +ICO = ROOT / "ico" +THEMES = ICO / "themes" +QRC = ICO / "icon-themes.qrc" + +# Fixed-size folders that hold toolbar, menu and dialog icons. The 24x16 +# flags, the 22x22/color swatches and the application icons are not theme +# icons and stay on their resource paths. +SIZES = ["16x16", "22x22", "32x32", "48x48", "128x128"] + +# 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 +# the 22 pixel file is exposed under the 16 pixel name as well. +ALIASES = { + "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. +SVGS = [ + "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-vertical-symbolic.svg", + "breeze-icons/scalable/apps/hidef/draw-bezier-curves.svg", + "breeze-icons/scalable/apps/hidef/transform-crop.svg", + "generated/ellipse-to-bezier.svg", + "generated/rect-to-bezier.svg", + "generated/rect-to-polyline.svg", +] + + +def write_if_changed(path, data): + """Write text only when the content differs, so git stays quiet.""" + if path.exists() and path.read_text(encoding="utf-8") == data: + return False + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(data, encoding="utf-8") + return True + + +def index_theme(name, comment, dirs, inherits=None): + lines = ["[Icon Theme]", f"Name={name}", f"Comment={comment}"] + if inherits: + lines.append(f"Inherits={inherits}") + lines.append("Directories=" + ",".join(dirs)) + lines.append("") + for d in dirs: + lines.append(f"[{d}]") + if d == "scalable": + lines += ["Size=22", "Type=Scalable", "MinSize=8", "MaxSize=256"] + else: + lines += [f"Size={d.split('x')[0]}", "Type=Fixed"] + lines.append("") + return "\n".join(lines) + + +def main(): + entries = [] # (alias, source) pairs, paths relative to ico/ + changed = 0 + + for size in SIZES: + for png in sorted((ICO / size).glob("*.png")): + rel = f"{size}/{png.name}" + names = [png.stem] + if rel in ALIASES: + names.append(ALIASES[rel]) + for name in names: + entries.append((f"themes/qet/{size}/{name}.png", rel)) + + for rel in SVGS: + name = re.sub(r"-symbolic(?=\.svg$)", "", Path(rel).name) + entries.append((f"themes/qet/scalable/{name}", rel)) + + dirs = SIZES + ["scalable"] + changed += write_if_changed(THEMES / "qet" / "index.theme", + index_theme("qet", "QElectroTech icons", dirs)) + + qrc = ["", "", + '', ' ', + " themes/qet/index.theme"] + for alias, source in entries: + qrc.append(f' {source}') + qrc += [" ", "", ""] + changed += write_if_changed(QRC, "\n".join(qrc)) + + print(f"{len(entries)} theme entries; {changed} files written") + + +if __name__ == "__main__": + os.chdir(ROOT) + main() diff --git a/sources/ElementsCollection/fileelementcollectionitem.cpp b/sources/ElementsCollection/fileelementcollectionitem.cpp index 15a6cdb83..2183507ab 100644 --- a/sources/ElementsCollection/fileelementcollectionitem.cpp +++ b/sources/ElementsCollection/fileelementcollectionitem.cpp @@ -456,13 +456,13 @@ void FileElementCollectionItem::setUpIcon() if (macrosPath.endsWith("/")) macrosPath.remove(macrosPath.length() - 1, 1); if (m_path == QETApp::commonElementsDirN()) - setIcon(QIcon(":/ico/16x16/qet.png")); + setIcon(QIcon::fromTheme("qet")); else if (m_path == QETApp::companyElementsDirN()) - setIcon(QIcon(":/ico/16x16/go-company.png")); + setIcon(QIcon::fromTheme("go-company")); else if (m_path == macrosPath) - setIcon(QIcon(":/ico/16x16/go-home.png")); + setIcon(QIcon::fromTheme("go-home")); else - setIcon(QIcon(":/ico/16x16/go-home.png")); + setIcon(QIcon::fromTheme("go-home")); } else { diff --git a/sources/NameList/ui/namelistwidget.ui b/sources/NameList/ui/namelistwidget.ui index f021a77a3..a38dd046d 100644 --- a/sources/NameList/ui/namelistwidget.ui +++ b/sources/NameList/ui/namelistwidget.ui @@ -36,7 +36,7 @@ Ajouter une ligne - + :/ico/16x16/list-add.png:/ico/16x16/list-add.png @@ -48,7 +48,7 @@ Copier dans le presse papier - + :/ico/16x16/edit-paste.png:/ico/16x16/edit-paste.png diff --git a/sources/SearchAndReplace/ui/replaceconductordialog.ui b/sources/SearchAndReplace/ui/replaceconductordialog.ui index 4be6a94d3..f5cefc78c 100644 --- a/sources/SearchAndReplace/ui/replaceconductordialog.ui +++ b/sources/SearchAndReplace/ui/replaceconductordialog.ui @@ -370,7 +370,7 @@ phase - + :/ico/16x16/phase.png:/ico/16x16/phase.png @@ -397,7 +397,7 @@ neutre - + :/ico/16x16/neutral.png:/ico/16x16/neutral.png @@ -433,7 +433,7 @@ terre - + :/ico/16x16/ground.png:/ico/16x16/ground.png diff --git a/sources/SearchAndReplace/ui/replacefoliowidget.ui b/sources/SearchAndReplace/ui/replacefoliowidget.ui index b5ee7e0df..b438180b9 100644 --- a/sources/SearchAndReplace/ui/replacefoliowidget.ui +++ b/sources/SearchAndReplace/ui/replacefoliowidget.ui @@ -180,7 +180,7 @@ - + :/ico/22x22/start.png:/ico/22x22/start.png diff --git a/sources/SearchAndReplace/ui/searchandreplacewidget.ui b/sources/SearchAndReplace/ui/searchandreplacewidget.ui index fcc280960..597e1f439 100644 --- a/sources/SearchAndReplace/ui/searchandreplacewidget.ui +++ b/sources/SearchAndReplace/ui/searchandreplacewidget.ui @@ -106,7 +106,7 @@ - + :/ico/16x16/go-bottom.png:/ico/16x16/go-bottom.png @@ -123,7 +123,7 @@ - + :/ico/16x16/go-top.png:/ico/16x16/go-top.png @@ -140,7 +140,7 @@ - + :/ico/16x16/view-refresh.png:/ico/16x16/view-refresh.png @@ -157,7 +157,7 @@ - + :/ico/16x16/configure-toolbars.png:/ico/16x16/configure-toolbars.png diff --git a/sources/TerminalStrip/ui/freeterminaleditor.ui b/sources/TerminalStrip/ui/freeterminaleditor.ui index f464b3e1d..4f318c60e 100644 --- a/sources/TerminalStrip/ui/freeterminaleditor.ui +++ b/sources/TerminalStrip/ui/freeterminaleditor.ui @@ -84,7 +84,7 @@ - + :/ico/22x22/dialog-ok.png:/ico/22x22/dialog-ok.png diff --git a/sources/TerminalStrip/ui/terminalstripeditor.ui b/sources/TerminalStrip/ui/terminalstripeditor.ui index 29f6854e3..ef65cdf9c 100644 --- a/sources/TerminalStrip/ui/terminalstripeditor.ui +++ b/sources/TerminalStrip/ui/terminalstripeditor.ui @@ -63,7 +63,7 @@ - + :/ico/16x16/dialog-ok.png:/ico/16x16/dialog-ok.png diff --git a/sources/TerminalStrip/ui/terminalstripeditorwindow.ui b/sources/TerminalStrip/ui/terminalstripeditorwindow.ui index f390f33cf..53b413b3b 100644 --- a/sources/TerminalStrip/ui/terminalstripeditorwindow.ui +++ b/sources/TerminalStrip/ui/terminalstripeditorwindow.ui @@ -54,7 +54,7 @@ - + :/ico/16x16/list-add.png:/ico/16x16/list-add.png @@ -66,7 +66,7 @@ - + :/ico/16x16/list-remove.png:/ico/16x16/list-remove.png @@ -78,7 +78,7 @@ - + :/ico/16x16/view-refresh.png:/ico/16x16/view-refresh.png diff --git a/sources/autoNum/ui/autonumberingdockwidget.ui b/sources/autoNum/ui/autonumberingdockwidget.ui index 4fcd61901..b0bcc6e61 100644 --- a/sources/autoNum/ui/autonumberingdockwidget.ui +++ b/sources/autoNum/ui/autonumberingdockwidget.ui @@ -73,7 +73,7 @@ - + :/ico/16x16/view-refresh.png:/ico/16x16/view-refresh.png @@ -161,7 +161,7 @@ - + :/ico/16x16/view-refresh.png:/ico/16x16/view-refresh.png @@ -238,7 +238,7 @@ - + :/ico/16x16/view-refresh.png:/ico/16x16/view-refresh.png @@ -319,7 +319,7 @@ Configurer - + :/ico/16x16/configure.png:/ico/16x16/configure.png diff --git a/sources/autoNum/ui/selectautonumw.ui b/sources/autoNum/ui/selectautonumw.ui index 3c4d953af..8da8d2e70 100644 --- a/sources/autoNum/ui/selectautonumw.ui +++ b/sources/autoNum/ui/selectautonumw.ui @@ -63,7 +63,7 @@ - + :/ico/16x16/edit-delete.png:/ico/16x16/edit-delete.png @@ -136,7 +136,7 @@ - + :/ico/22x22/list-remove.png:/ico/22x22/list-remove.png @@ -153,7 +153,7 @@ - + :/ico/22x22/list-add.png:/ico/22x22/list-add.png @@ -170,7 +170,7 @@ - + :/ico/16x16/arrow-left.png:/ico/16x16/arrow-left.png @@ -184,7 +184,7 @@ - + :/ico/16x16/arrow-right.png:/ico/16x16/arrow-right.png diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index bd0f0069d..20d2c150d 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -28,6 +28,7 @@ #include "../qetproject.h" #include +#include #include #include @@ -132,11 +133,88 @@ QETProject *projectDataBase::project() const } /** - @brief projectDataBase::newQuery - @return a QSqlquery with query as query - and the internal database of this class as database to use. + @brief projectDataBase::isReadOnlySelect + Every query that reaches newQuery() goes through this check first -- + including one loaded from a saved nomenclature/summary table's + element (ProjectDBModel::fromXml()), which makes this a defense against + a crafted project file, not just a careless custom-SQL edit + (qelectrotech-source-mirror#886 asked for read-only enforcement on the + custom SQL reports feature; this covers every path into newQuery(), not + just that one dialog). + + Deliberately simple rather than a real SQL parser: reject more than one + statement (blocks stacking a write after a leading SELECT with `;`), and + require the query to start with SELECT or WITH. A determined attacker + with arbitrary SQL access to a local SQLite connection can still find + tricks a simple prefix check won't catch; this is meant to stop the + ordinary mistake and the obvious payload, not to be a security boundary + against a hostile file assumed to already run in some other trust + context. + @param query the raw SQL text to check + @param error set to a human-readable reason when this returns false + @return true if @p query looks like a single read-only SELECT/WITH */ -QSqlQuery projectDataBase::newQuery(const QString &query) { +bool projectDataBase::isReadOnlySelect(const QString &query, QString *error) +{ + if (error) { + error->clear(); + } + + QString trimmed = query.trimmed(); + if (trimmed.endsWith(QLatin1Char(';'))) { + trimmed.chop(1); + trimmed = trimmed.trimmed(); + } + + if (trimmed.isEmpty()) { + if (error) { + *error = projectDataBase::tr("La requête est vide."); + } + return false; + } + + if (trimmed.contains(QLatin1Char(';'))) { + if (error) { + *error = projectDataBase::tr("Une seule requête SELECT est autorisée" + " (le caractère ';' ne peut apparaître" + " qu'à la toute fin)."); + } + return false; + } + + const int first_space = trimmed.indexOf(QRegularExpression(QStringLiteral("\\s"))); + const QString first_word = (first_space == -1 ? trimmed : trimmed.left(first_space)).toUpper(); + if (first_word != QLatin1String("SELECT") && first_word != QLatin1String("WITH")) { + if (error) { + *error = projectDataBase::tr("Seules les requêtes en lecture seule" + " (SELECT ou WITH ... SELECT) sont" + " autorisées."); + } + return false; + } + + return true; +} + +/** + @brief projectDataBase::newQuery + @param query the SQL text to run -- must be a single read-only + SELECT/WITH statement, see isReadOnlySelect() + @param error set to a human-readable reason when the query was rejected + before ever reaching the database + @return a QSqlQuery with query as query and the internal database of + this class as database to use, or an unexecuted, harmless QSqlQuery if + the query was rejected +*/ +QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) { + QString reason; + if (!isReadOnlySelect(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); } diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 2ce9266bb..abd5e966c 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -59,7 +59,8 @@ class projectDataBase : public QObject */ void setUpdateBlocked(bool blocked); QETProject *project() const; - QSqlQuery newQuery(const QString &query = QString()); + QSqlQuery newQuery(const QString &query = QString(), QString *error = nullptr); + static bool isReadOnlySelect(const QString &query, QString *error = nullptr); QSqlDatabase database() const {return m_data_base;} int excludedConductorCount() const; diff --git a/sources/dataBase/ui/elementquerywidget.cpp b/sources/dataBase/ui/elementquerywidget.cpp index aafdeb32d..f5a42beb7 100644 --- a/sources/dataBase/ui/elementquerywidget.cpp +++ b/sources/dataBase/ui/elementquerywidget.cpp @@ -20,8 +20,15 @@ #include "../../properties/elementdata.h" #include "../../qetapp.h" #include "../../qetinformation.h" +#include "../projectdatabase.h" #include "ui_elementquerywidget.h" +#include +#include +#include +#include +#include + #include /** @@ -94,6 +101,9 @@ ElementQueryWidget::ElementQueryWidget(QWidget *parent) : setUpItems(); fillSavedQuery(); + + connect(ui->m_sql_query, &QLineEdit::textChanged, this, &ElementQueryWidget::checkQueryValidity); + checkQueryValidity(); } /** @@ -599,6 +609,8 @@ void ElementQueryWidget::on_m_edit_sql_query_cb_clicked() m_custom_query = ui->m_sql_query->text(); updateQueryLine(); } + + checkQueryValidity(); } /** @@ -672,6 +684,10 @@ void ElementQueryWidget::on_m_load_pb_clicked() */ void ElementQueryWidget::on_m_save_current_conf_pb_clicked() { + if (!projectDataBase::isReadOnlySelect(queryStr())) { + return; + } + QFile file_(QETApp::configDir() % "/nomenclature.json"); if (file_.open(QFile::ReadWrite)) @@ -698,7 +714,147 @@ void ElementQueryWidget::on_m_save_current_conf_pb_clicked() } void ElementQueryWidget::on_m_save_name_le_textChanged(const QString &arg1) { - ui->m_save_current_conf_pb->setDisabled(arg1.isEmpty()); + Q_UNUSED(arg1) + checkQueryValidity(); +} + +/** + @brief ElementQueryWidget::checkQueryValidity + Live feedback for the custom-SQL box: only the free-text path can carry + anything other than a SELECT (the column/filter builder always produces + one), so this only actually restricts something once "Requête SQL + personnalisée" is checked. Same rule projectDataBase::isReadOnlySelect() + enforces at execution time -- this just tells the user *before* they hit + Preview/Export/OK instead of after (qelectrotech-source-mirror#886). +*/ +void ElementQueryWidget::checkQueryValidity() +{ + if (!ui->m_edit_sql_query_cb->isChecked()) { + ui->m_query_warning_label->clear(); + ui->m_save_current_conf_pb->setEnabled(!ui->m_save_name_le->text().isEmpty()); + return; + } + + QString reason; + const bool valid = projectDataBase::isReadOnlySelect(ui->m_sql_query->text(), &reason); + ui->m_query_warning_label->setText(valid ? QString() : reason); + ui->m_save_current_conf_pb->setEnabled(valid && !ui->m_save_name_le->text().isEmpty()); +} + +/** + @brief ElementQueryWidget::on_m_export_reports_pb_clicked + Write every saved report in nomenclature.json to a file the user picks, + so it can be handed to a colleague or another install + (qelectrotech-source-mirror#886's "import and export report definitions + for sharing between users or company installations"). +*/ +void ElementQueryWidget::on_m_export_reports_pb_clicked() +{ + QFile source(QETApp::configDir() % "/nomenclature.json"); + if (!source.open(QFile::ReadOnly)) { + QMessageBox::information(this, tr("Exporter"), tr("Aucun rapport enregistré à exporter.")); + return; + } + const auto content = source.readAll(); + source.close(); + + if (QJsonDocument::fromJson(content).object().isEmpty()) { + QMessageBox::information(this, tr("Exporter"), tr("Aucun rapport enregistré à exporter.")); + return; + } + + const QString file_path = QFileDialog::getSaveFileName( + this, tr("Exporter les rapports"), QStringLiteral("rapports_qet.json"), + tr("Fichiers JSON (*.json)")); + if (file_path.isEmpty()) { + return; + } + + QFile dest(file_path); + if (!dest.open(QFile::WriteOnly) || dest.write(content) == -1) { + QMessageBox::critical(this, tr("Erreur"), tr("Impossible d'écrire dans %1.").arg(file_path)); + } +} + +/** + @brief ElementQueryWidget::on_m_import_reports_pb_clicked + Merge a previously-exported reports file into this install's + nomenclature.json. A name already used locally is not overwritten + silently -- the user is asked, per report, whether to replace it. +*/ +void ElementQueryWidget::on_m_import_reports_pb_clicked() +{ + const QString file_path = QFileDialog::getOpenFileName( + this, tr("Importer des rapports"), QString(), tr("Fichiers JSON (*.json)")); + if (file_path.isEmpty()) { + return; + } + + QFile source(file_path); + if (!source.open(QFile::ReadOnly)) { + QMessageBox::critical(this, tr("Erreur"), tr("Impossible de lire %1.").arg(file_path)); + return; + } + + QJsonParseError parse_error; + const auto incoming_doc = QJsonDocument::fromJson(source.readAll(), &parse_error); + source.close(); + + if (parse_error.error != QJsonParseError::NoError || !incoming_doc.isObject()) { + QMessageBox::critical( + this, tr("Erreur"), + tr("%1 ne contient pas des rapports QElectroTech valides.").arg(file_path)); + return; + } + + const auto incoming = incoming_doc.object(); + if (incoming.isEmpty()) { + QMessageBox::information(this, tr("Importer"), tr("Ce fichier ne contient aucun rapport.")); + return; + } + + QFile dest_file(QETApp::configDir() % "/nomenclature.json"); + QJsonObject existing; + if (dest_file.open(QFile::ReadOnly)) { + existing = QJsonDocument::fromJson(dest_file.readAll()).object(); + dest_file.close(); + } + + int imported = 0, skipped = 0; + for (auto it = incoming.begin(); it != incoming.end(); ++it) + { + if (existing.contains(it.key())) + { + const auto answer = QMessageBox::question( + this, tr("Rapport déjà existant"), + tr("Un rapport nommé « %1 » existe déjà. Le remplacer ?").arg(it.key()), + QMessageBox::Yes | QMessageBox::No); + if (answer != QMessageBox::Yes) { + ++skipped; + continue; + } + } + existing[it.key()] = it.value(); + ++imported; + } + + if (dest_file.open(QFile::WriteOnly | QFile::Truncate)) + { + dest_file.write(QJsonDocument(existing).toJson()); + dest_file.close(); + } + else + { + QMessageBox::critical(this, tr("Erreur"), tr("Impossible d'écrire la configuration locale.")); + return; + } + + ui->m_conf_cb->clear(); + fillSavedQuery(); + + QMessageBox::information( + this, tr("Importer"), + tr("%1 rapport(s) importé(s), %2 ignoré(s).").arg(imported).arg(skipped)); } void ElementQueryWidget::on_m_choosen_list_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) diff --git a/sources/dataBase/ui/elementquerywidget.h b/sources/dataBase/ui/elementquerywidget.h index b44377e12..f89883793 100644 --- a/sources/dataBase/ui/elementquerywidget.h +++ b/sources/dataBase/ui/elementquerywidget.h @@ -59,6 +59,9 @@ class ElementQueryWidget : public QWidget void on_m_load_pb_clicked(); void on_m_save_current_conf_pb_clicked(); void on_m_save_name_le_textChanged(const QString &arg1); + void on_m_import_reports_pb_clicked(); + void on_m_export_reports_pb_clicked(); + void checkQueryValidity(); void on_m_choosen_list_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_m_var_list_itemDoubleClicked(QListWidgetItem *item); void on_m_choosen_list_itemDoubleClicked(QListWidgetItem *item); diff --git a/sources/dataBase/ui/elementquerywidget.ui b/sources/dataBase/ui/elementquerywidget.ui index 7f3ae30fb..8300b2387 100644 --- a/sources/dataBase/ui/elementquerywidget.ui +++ b/sources/dataBase/ui/elementquerywidget.ui @@ -88,7 +88,7 @@ - + :/ico/16x16/go-up.png:/ico/16x16/go-up.png @@ -105,7 +105,7 @@ - + :/ico/16x16/list-add.png:/ico/16x16/list-add.png @@ -119,7 +119,7 @@ - + :/ico/16x16/list-remove.png:/ico/16x16/list-remove.png @@ -133,7 +133,7 @@ - + :/ico/16x16/go-down.png:/ico/16x16/go-down.png @@ -353,7 +353,7 @@ - + :/ico/16x16/folder-open.png:/ico/16x16/folder-open.png @@ -380,7 +380,7 @@ - + :/ico/16x16/document-save.png:/ico/16x16/document-save.png @@ -388,6 +388,26 @@ + + + + Importer des rapports depuis un fichier + + + Importer... + + + + + + + Exporter tous les rapports enregistrés vers un fichier + + + Exporter... + + + @@ -429,6 +449,19 @@ + + + + color: red; + + + + + + true + + + diff --git a/sources/dataBase/ui/summaryquerywidget.ui b/sources/dataBase/ui/summaryquerywidget.ui index d6eb50e10..de98cd343 100644 --- a/sources/dataBase/ui/summaryquerywidget.ui +++ b/sources/dataBase/ui/summaryquerywidget.ui @@ -76,7 +76,7 @@ - + :/ico/16x16/go-up.png:/ico/16x16/go-up.png @@ -87,7 +87,7 @@ - + :/ico/16x16/list-add.png:/ico/16x16/list-add.png @@ -98,7 +98,7 @@ - + :/ico/16x16/list-remove.png:/ico/16x16/list-remove.png @@ -109,7 +109,7 @@ - + :/ico/16x16/go-down.png:/ico/16x16/go-down.png diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 591f2b885..aa8e92399 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -801,6 +801,11 @@ void Diagram::clearEventInterface() } } +bool Diagram::eventInterfaceIsRunning() const +{ + return m_event_interface && m_event_interface->isRunning(); +} + /** @brief Diagram::conductorsAutonumName @return the name of autonum to use. diff --git a/sources/diagram.h b/sources/diagram.h index d2e0f0e12..824b6dc38 100644 --- a/sources/diagram.h +++ b/sources/diagram.h @@ -216,6 +216,7 @@ class Diagram : public QGraphicsScene // methods related to graphics items addition/removal on the diagram virtual void addItem (QGraphicsItem *item); virtual void removeItem (QGraphicsItem *item); + bool eventInterfaceIsRunning() const; // methods related to graphics options ExportProperties applyProperties(const ExportProperties &); diff --git a/sources/diagramcommands.cpp b/sources/diagramcommands.cpp index 3bf1be437..8c19636f6 100644 --- a/sources/diagramcommands.cpp +++ b/sources/diagramcommands.cpp @@ -19,6 +19,7 @@ #include "diagram.h" #include "qetgraphicsitem/conductortextitem.h" +#include "qetgraphicsitem/dynamicelementtextitem.h" #include "qetgraphicsitem/element.h" #include "qetgraphicsitem/elementtextitemgroup.h" #include "qetinformation.h" @@ -90,6 +91,62 @@ void PasteDiagramCommand::redo() //make new uuid, because old uuid are the uuid of the copied element e -> newUuid(); + // PLC slaves carry master-specific data (type, address, + // function, cross-ref, etc.) in their elementInformations. + // Always clear those on paste so the duplicate starts clean, + // regardless of the user's erase-label-on-copy preference. + const bool is_slave = (e->linkType() == Element::Slave); + if (is_slave) { + DiagramContext dc = e->elementInformations(); + dc.remove(QETInformation::ELMT_PLC_TYPE); + dc.remove(QETInformation::ELMT_PLC_ADDRESS); + dc.remove(QETInformation::ELMT_PLC_FUNCTION); + dc.remove(QETInformation::ELMT_PLC_COMMENT); + dc.remove(QETInformation::ELMT_PLC_CROSSREF); + dc.remove(QETInformation::ELMT_LABEL); + dc.remove(QETInformation::ELMT_PLC_TC); + dc.remove(QETInformation::ELMT_PLC_T1); + dc.remove(QETInformation::ELMT_PLC_T2); + dc.remove(QETInformation::ELMT_PLC_T3); + dc.remove(QETInformation::ELMT_PLC_T4); + dc.remove(QStringLiteral("xref")); + + // Block alignment before setElementInformations so + // that elementInfoChanged() resolves texts without + // finishAlignment() shifting right/center-aligned items. + for (DynamicElementTextItem *deti : e->dynamicTextItems()) + deti->m_block_alignment = true; + for (auto *group : e->textGroups()) + group->blockAlignmentUpdate(true); + + e->setElementInformations(dc); + + for (DynamicElementTextItem *deti : e->dynamicTextItems()) + deti->m_block_alignment = false; + for (auto *group : e->textGroups()) + group->blockAlignmentUpdate(false); + + // After setElementInformations, elementInfoChanged() + // resolves composite text with cleaned dc. Clear + // all non-UserText items directly as a safety net. + for (DynamicElementTextItem *deti : e->dynamicTextItems()) { + if (deti->textFrom() != DynamicElementTextItem::UserText) { + deti->m_block_alignment = true; + deti->setPlainText(QString()); + deti->m_block_alignment = false; + } + } + for (auto *group : e->textGroups()) { + for (DynamicElementTextItem *deti : group->texts()) { + if (deti->textFrom() != DynamicElementTextItem::UserText) { + deti->m_block_alignment = true; + deti->setPlainText(QString()); + deti->m_block_alignment = false; + } + } + } + } + if (settings.value("diagramcommands/erase-label-on-copy", true).toBool()) { //Reset the information about the label, the comment and location @@ -99,27 +156,21 @@ void PasteDiagramCommand::redo() dc.addValue("comment", ""); dc.addValue("location", ""); - // PLC slaves store master data (type, address, comment, - // cross-ref, etc.) in their own elementInformations. - // Remove them the same way MasterElement::unlinkElement() - // does, so pasted PLC slaves start clean like regular - // slaves. - if (e->linkType() == Element::Slave) { - dc.remove(QETInformation::ELMT_PLC_TYPE); - dc.remove(QETInformation::ELMT_PLC_ADDRESS); - dc.remove(QETInformation::ELMT_PLC_FUNCTION); - dc.remove(QETInformation::ELMT_PLC_COMMENT); - dc.remove(QETInformation::ELMT_PLC_CROSSREF); - dc.remove(QETInformation::ELMT_LABEL); - dc.remove(QETInformation::ELMT_PLC_TC); - dc.remove(QETInformation::ELMT_PLC_T1); - dc.remove(QETInformation::ELMT_PLC_T2); - dc.remove(QETInformation::ELMT_PLC_T3); - dc.remove(QETInformation::ELMT_PLC_T4); - dc.remove(QStringLiteral("xref")); + // Block alignment during setElementInformations + // for non-slaves, same as Element::fromXml() (line 890-896). + if (!is_slave) { + for (DynamicElementTextItem *deti : e->dynamicTextItems()) + deti->m_block_alignment = true; + for (auto *group : e->textGroups()) + group->blockAlignmentUpdate(true); } e->setElementInformations(dc); + + for (DynamicElementTextItem *deti : e->dynamicTextItems()) + deti->m_block_alignment = false; + for (auto *group : e->textGroups()) + group->blockAlignmentUpdate(false); //Reset the text of conductors, the same way the label/comment/ //location above are reset to "" rather than to some other diff --git a/sources/diagramevent/diagrameventaddpaste.cpp b/sources/diagramevent/diagrameventaddpaste.cpp index b08406b68..ac29b522a 100644 --- a/sources/diagramevent/diagrameventaddpaste.cpp +++ b/sources/diagramevent/diagrameventaddpaste.cpp @@ -23,6 +23,8 @@ #include "../qetdiagrameditor.h" #include "../qetgraphicsitem/conductor.h" +#include + #include #include #include @@ -35,7 +37,7 @@ @param start_pos : where the pasted items first appear, in scene coordinates -- normally the cursor */ -DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos) : + DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &start_pos) : DiagramEventInterface(diagram) { //DiagramEventInterface::init() is called by Diagram::setEventInterface @@ -49,20 +51,45 @@ DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &star QDomDocument document_xml; if (!document_xml.setContent(clipboard_text)) return; - m_diagram->fromXml(document_xml, Diagram::snapToGrid(start_pos), false, &m_content); + //Load items at their original XML coordinates. + m_diagram->fromXml(document_xml, QPointF(), false, &m_content); if (!m_content.count()) return; - //Remember where each item sits relative to the group's top left, so a - //move is one assignment per item rather than an accumulated delta. - QRectF group_rect; const QList movable = m_content.items(MovableItems); + if (movable.isEmpty()) return; + + //Compute the top-left of all items' positions (not bounding + //rects) and snap to grid — used only for the initial cursor + //warp. Items stay at their original XML positions; + //moveTo() handles grid-snapped movement via deltas. + QPointF top_left; + bool first = true; for (auto *item : movable) { - group_rect = group_rect.united(item->mapToScene(item->boundingRect()).boundingRect()); + const QPointF p = item->pos(); + if (first) { + top_left = p; + first = false; + } else { + if (p.x() < top_left.x()) top_left.setX(p.x()); + if (p.y() < top_left.y()) top_left.setY(p.y()); + } } - const QPointF top_left = group_rect.topLeft(); + QSettings settings; + const int xGrid = settings.value(QStringLiteral("diagrameditor/Xgrid"), + Diagram::xGrid).toInt(); + const int yGrid = settings.value(QStringLiteral("diagrameditor/Ygrid"), + Diagram::yGrid).toInt(); + const QPointF grid_origin( + qRound(top_left.x() / xGrid) * xGrid, + qRound(top_left.y() / yGrid) * yGrid); + + //Store each item's position. moveTo() applies a grid-snapped + //delta from the baseline, so items preserve their layout and + //move in whole grid steps. for (auto *item : movable) { - m_relative_pos.insert(item, item->pos() - top_left); + m_relative_pos.insert(item, item->pos()); } + m_group_origin = grid_origin; m_diagram->clearSelection(); for (auto *item : movable) { @@ -70,8 +97,15 @@ DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &star } if (!m_diagram->views().isEmpty()) { - if (const auto qde = QETApp::diagramEditorAncestorOf(m_diagram->views().at(0))) { - m_status_bar = qde->statusBar(); + if (auto *view = m_diagram->views().at(0)) { + if (const auto qde = QETApp::diagramEditorAncestorOf(view)) { + m_status_bar = qde->statusBar(); + } + //Warp the cursor close to the group origin so the + //first mouseMoveEvent captures the correct baseline. + const QPoint view_pos = view->mapFromScene(m_group_origin); + const QPoint global_pos = view->viewport()->mapToGlobal(view_pos); + QCursor::setPos(global_pos); } } showHint(); @@ -88,7 +122,9 @@ DiagramEventAddPaste::DiagramEventAddPaste(Diagram *diagram, const QPointF &star DiagramEventAddPaste::~DiagramEventAddPaste() { if (!m_finished && m_diagram) { - cancel(); + removeItems(); + m_finished = true; + m_running = false; } if (m_status_bar) { m_status_bar->clearMessage(); @@ -129,16 +165,49 @@ void DiagramEventAddPaste::showHint() /** @brief DiagramEventAddPaste::moveTo - Put the group's top left corner at @a scene_pos, snapped to the grid. + Compute a grid-snapped delta from the initial cursor position and + apply it to every item's grid-shifted position. This keeps all + items exactly on grid points regardless of modifier keys or + sub-pixel cursor-warp rounding. */ void DiagramEventAddPaste::moveTo(const QPointF &scene_pos) { - const QPointF anchor = Diagram::snapToGrid(scene_pos); + QSettings settings; + const int xGrid = settings.value(QStringLiteral("diagrameditor/Xgrid"), + Diagram::xGrid).toInt(); + const int yGrid = settings.value(QStringLiteral("diagrameditor/Ygrid"), + Diagram::yGrid).toInt(); + + const auto snapGrid = [xGrid, yGrid](const QPointF &p) -> QPointF { + return QPointF( + qRound(p.x() / xGrid) * xGrid, + qRound(p.y() / yGrid) * yGrid); + }; + + //On the very first call, record the actual grid-snapped + //cursor position as baseline. The cursor warp in the + //constructor goes through integer rounding (mapFromScene → + //QPoint) so the real position may differ slightly from + //m_initial_cursor. Using the actual scene position avoids + //a one-grid-unit jump on the first mouse movement. + if (m_initial_cursor.isNull()) { + m_initial_cursor = snapGrid(scene_pos); + return; + } + + const QPointF delta = snapGrid(scene_pos) - m_initial_cursor; + for (auto it = m_relative_pos.constBegin() ; it != m_relative_pos.constEnd() ; ++it) { if (it.key()) { - it.key()->setPos(anchor + it.value()); + it.key()->setPos(it.value() + delta); } } + + //Update conductor paths so they follow the moved terminals. + const QList conductors = m_content.conductors(DiagramContent::AnyConductor); + for (auto *conductor : conductors) { + conductor->updatePath(); + } } void DiagramEventAddPaste::mouseMoveEvent(QGraphicsSceneMouseEvent *event) @@ -160,13 +229,12 @@ void DiagramEventAddPaste::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (!m_running) return; + event->setAccepted(true); if (event->button() == Qt::LeftButton) { - moveTo(event->scenePos()); commit(); } else if (event->button() == Qt::RightButton) { cancel(); } - event->setAccepted(true); } void DiagramEventAddPaste::keyPressEvent(QKeyEvent *event) @@ -175,15 +243,15 @@ void DiagramEventAddPaste::keyPressEvent(QKeyEvent *event) switch (event->key()) { case Qt::Key_Escape: - cancel(); event->setAccepted(true); + cancel(); break; //Return and Enter drop the paste where it stands, so the whole //operation can be completed without a mouse. case Qt::Key_Return: case Qt::Key_Enter: - commit(); event->setAccepted(true); + commit(); break; default: break; @@ -216,13 +284,14 @@ void DiagramEventAddPaste::commit() void DiagramEventAddPaste::cancel() { if (m_finished || !m_diagram) return; + removeItems(); m_finished = true; m_running = false; + emit finish(); // only the user-driven path signals +} - //Conductors first: they hold pointers to the terminals of the - //elements below, so removing an element out from under one would - //leave it pointing at freed memory for as long as it is still in the - //scene. +void DiagramEventAddPaste::removeItems() +{ const QList conductors = m_content.conductors(DiagramContent::AnyConductor); for (auto *conductor : conductors) { m_diagram->removeItem(conductor); @@ -237,5 +306,4 @@ void DiagramEventAddPaste::cancel() m_content.clear(); m_relative_pos.clear(); - emit finish(); } diff --git a/sources/diagramevent/diagrameventaddpaste.h b/sources/diagramevent/diagrameventaddpaste.h index a200ca04d..28ae3854b 100644 --- a/sources/diagramevent/diagrameventaddpaste.h +++ b/sources/diagramevent/diagrameventaddpaste.h @@ -73,13 +73,22 @@ class DiagramEventAddPaste : public DiagramEventInterface void commit(); void cancel(); void showHint(); + void removeItems(); - DiagramContent m_content; - ///Each movable item's position relative to the group's top left, - ///taken once so repeated moves cannot accumulate rounding drift. - QHash m_relative_pos; - QPointer m_status_bar; - bool m_finished{false}; + DiagramContent m_content; + ///Each movable item's position relative to the group's top left, + ///taken once so repeated moves cannot accumulate rounding drift. + QHash m_relative_pos; + ///Top-left corner of the bounding rect of all movable items, + ///in scene coordinates, captured when the paste starts. + QPointF m_group_origin; + ///Cursor position (scene coords) at the moment the paste starts, + ///so delta-based movement can compute offsets from the initial point. + QPointF m_initial_cursor; + ///Set to true once the first moveTo() captures the real cursor position. + bool m_baseline_captured{false}; + QPointer m_status_bar; + bool m_finished{false}; }; #endif // DIAGRAMEVENTADDPASTE_H diff --git a/sources/diagramevent/diagrameventaddshape.cpp b/sources/diagramevent/diagrameventaddshape.cpp index d19669e76..9795d86f8 100644 --- a/sources/diagramevent/diagrameventaddshape.cpp +++ b/sources/diagramevent/diagrameventaddshape.cpp @@ -317,10 +317,16 @@ void DiagramEventAddShape::mouseMoveEvent(QGraphicsSceneMouseEvent *event) did anything. Re-running the last known mouse position through applyPosition() here makes the key press or release itself the trigger, giving immediate feedback instead of waiting on chance. + Any other key falls back to standard handling, e.g. pressing ESC aborts the operation. */ void DiagramEventAddShape::keyPressEvent(QKeyEvent *event) { - reapplyLastPosition(event); + if (event->key() == Qt::Key_Shift || event->key() == Qt::Key_Control) { + reapplyLastPosition(event); + event->setAccepted(true); + } else { + DiagramEventInterface::keyPressEvent(event); + } } void DiagramEventAddShape::keyReleaseEvent(QKeyEvent *event) diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index e16a02840..9e89be6cb 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -733,6 +733,10 @@ void DiagramView::keyPressEvent(QKeyEvent *e) //way off the canvas for someone working without a mouse. //Escape steps back out: first it drops the selection, then it //hands focus to the next widget. + if (m_diagram && m_diagram->eventInterfaceIsRunning()) { + QGraphicsView::keyPressEvent(e); // let the active tool see it + return; + } if (m_diagram && !m_diagram->selectedItems().isEmpty()) { m_diagram->clearSelection(); } else { diff --git a/sources/editor/ui/lineeditor.cpp b/sources/editor/ui/lineeditor.cpp index b75dfcf39..18ec7d0be 100644 --- a/sources/editor/ui/lineeditor.cpp +++ b/sources/editor/ui/lineeditor.cpp @@ -156,6 +156,16 @@ bool LineEditor::setParts(QList parts) return false; } + // setPart() no-ops when parts.first() is already m_part (its own + // identity guard), which skips updateForm(). That is fine when this + // editor widget stayed on screen the whole time, but since PR #675 + // keeps the same editor instance installed across selection changes + // instead of tearing it down, an already-shown single line can also be + // parts.first() of a later multi-selection -- and without this call + // the geometry spinboxes keep showing whatever was in them before, + // not this selection's actual first line. + updateForm(); + return m_style->setParts(parts); } diff --git a/sources/editor/ui/polygoneditor.ui b/sources/editor/ui/polygoneditor.ui index 98612f832..c34d26888 100644 --- a/sources/editor/ui/polygoneditor.ui +++ b/sources/editor/ui/polygoneditor.ui @@ -48,7 +48,7 @@ - + :/ico/16x16/list-add.png:/ico/16x16/list-add.png @@ -57,7 +57,7 @@ - + :/ico/16x16/list-remove.png:/ico/16x16/list-remove.png diff --git a/sources/editor/ui/qetelementeditor.cpp b/sources/editor/ui/qetelementeditor.cpp index 9ae36def3..424cabccf 100644 --- a/sources/editor/ui/qetelementeditor.cpp +++ b/sources/editor/ui/qetelementeditor.cpp @@ -576,9 +576,11 @@ void QETElementEditor::updateInformations() || selection_xml_name == "ellipse" || selection_xml_name == "arc") { - clearToolsDock(); //We add the editor widget ElementItemEditor *editor = static_cast(m_editors[selection_xml_name]); + if (m_tools_dock_stack -> widget(1) != editor) { + clearToolsDock(); + } #if TODO_LIST #pragma message("@TODO Check if it takes longer than setting the parts again to the editor.") @@ -611,11 +613,15 @@ void QETElementEditor::updateInformations() success = editor -> setParts(cep_list); } if (success) { - m_tools_dock_stack -> insertWidget(1, editor); + if (m_tools_dock_stack -> widget(1) != editor) { + m_tools_dock_stack -> insertWidget(1, editor); + } m_tools_dock_stack -> setCurrentIndex(1); } else { qDebug() << "Editor refused part."; + clearToolsDock(); + m_tools_dock_stack->setCurrentIndex(0); } } return; @@ -629,8 +635,10 @@ void QETElementEditor::updateInformations() // multi edit for polygons makes no sense // TODO: maybe allowing multipart edit when number of points is the same? //We add the editor widget - clearToolsDock(); ElementItemEditor *editor = static_cast(m_editors[selection_xml_name]); + if (m_tools_dock_stack -> widget(1) != editor) { + clearToolsDock(); + } CustomElementPart* part = editor -> currentPart(); bool equal = part == cep_list.first(); @@ -640,11 +648,15 @@ void QETElementEditor::updateInformations() success = editor -> setPart(cep_list.first()); } if (success) { - m_tools_dock_stack -> insertWidget(1, editor); + if (m_tools_dock_stack -> widget(1) != editor) { + m_tools_dock_stack -> insertWidget(1, editor); + } m_tools_dock_stack -> setCurrentIndex(1); } else { qDebug() << "Editor refused part."; + clearToolsDock(); + m_tools_dock_stack->setCurrentIndex(0); } } return; @@ -658,15 +670,21 @@ void QETElementEditor::updateInformations() //There's several parts selecteds and all can be edited by style editor. if (style_editable) { - clearToolsDock(); ElementItemEditor *selection_editor = m_editors["style"]; + if (m_tools_dock_stack -> widget(1) != selection_editor) { + clearToolsDock(); + } if (selection_editor) { if (selection_editor -> setParts(cep_list)) { - m_tools_dock_stack -> insertWidget(1, selection_editor); + if (m_tools_dock_stack -> widget(1) != selection_editor) { + m_tools_dock_stack -> insertWidget(1, selection_editor); + } m_tools_dock_stack -> setCurrentIndex(1); } else { qDebug() << "Editor refused part."; + clearToolsDock(); + m_tools_dock_stack->setCurrentIndex(0); } } } diff --git a/sources/editor/ui/qetelementeditor.ui b/sources/editor/ui/qetelementeditor.ui index 65a55aa8b..0ec31adc3 100644 --- a/sources/editor/ui/qetelementeditor.ui +++ b/sources/editor/ui/qetelementeditor.ui @@ -14,7 +14,7 @@ QElectroTech - Éditeur d'élément - + :/ico/16x16/qet.png:/ico/16x16/qet.png @@ -55,7 +55,7 @@ Coller depuis... - + :/ico/16x16/edit-paste.png:/ico/16x16/edit-paste.png @@ -202,7 +202,7 @@ - + :/ico/16x16/edit-select-all.png:/ico/16x16/edit-select-all.png @@ -211,7 +211,7 @@ - + :/ico/22x22/document-new.png:/ico/22x22/document-new.png @@ -220,7 +220,7 @@ - + :/ico/22x22/folder-open.png:/ico/22x22/folder-open.png @@ -229,7 +229,7 @@ - + :/ico/22x22/folder-open.png:/ico/22x22/folder-open.png @@ -238,7 +238,7 @@ - + :/ico/22x22/document-save.png:/ico/22x22/document-save.png @@ -247,7 +247,7 @@ - + :/ico/22x22/document-save-as.png:/ico/22x22/document-save-as.png @@ -256,7 +256,7 @@ - + :/ico/22x22/document-save.png:/ico/22x22/document-save.png @@ -265,7 +265,7 @@ - + :/ico/22x22/document-export.png:/ico/22x22/document-export.png @@ -274,7 +274,7 @@ - + :/ico/22x22/view-refresh.png:/ico/22x22/view-refresh.png @@ -283,7 +283,7 @@ - + :/ico/16x16/application-exit.png:/ico/16x16/application-exit.png @@ -295,7 +295,7 @@ - + :/ico/16x16/edit-select-none.png:/ico/16x16/edit-select-none.png @@ -304,7 +304,7 @@ - + :/ico/16x16/edit-cut.png:/ico/16x16/edit-cut.png @@ -313,7 +313,7 @@ - + :/ico/16x16/edit-copy.png:/ico/16x16/edit-copy.png @@ -322,7 +322,7 @@ - + :/ico/16x16/edit-paste.png:/ico/16x16/edit-paste.png @@ -331,7 +331,7 @@ - + :/ico/16x16/edit-paste.png:/ico/16x16/edit-paste.png @@ -340,7 +340,7 @@ - + :/ico/16x16/text-xml.png:/ico/16x16/text-xml.png @@ -349,7 +349,7 @@ - + :/ico/16x16/element.png:/ico/16x16/element.png @@ -358,7 +358,7 @@ - + :/ico/16x16/edit-select-invert.png:/ico/16x16/edit-select-invert.png @@ -367,7 +367,7 @@ - + :/ico/22x22/edit-delete.png:/ico/22x22/edit-delete.png @@ -376,7 +376,7 @@ - + :/ico/22x22/names.png:/ico/22x22/names.png @@ -385,7 +385,7 @@ - + :/ico/16x16/preferences-desktop-user.png:/ico/16x16/preferences-desktop-user.png @@ -394,7 +394,7 @@ - + :/ico/22x22/element-edit.png:/ico/22x22/element-edit.png @@ -403,7 +403,7 @@ - + :/ico/22x22/zoom-in.png:/ico/22x22/zoom-in.png @@ -412,7 +412,7 @@ - + :/ico/22x22/zoom-out.png:/ico/22x22/zoom-out.png @@ -421,7 +421,7 @@ - + :/ico/22x22/zoom-fit-best.png:/ico/22x22/zoom-fit-best.png @@ -430,7 +430,7 @@ - + :/ico/22x22/zoom-original.png:/ico/22x22/zoom-original.png @@ -439,7 +439,7 @@ - + :/ico/16x16/qet.png:/ico/16x16/qet.png @@ -451,7 +451,7 @@ - + :/ico/16x16/help-contents.png:/ico/16x16/help-contents.png @@ -463,7 +463,7 @@ - + :/ico/16x16/kdenlive-show-video.png:/ico/16x16/kdenlive-show-video.png @@ -475,7 +475,7 @@ - + :/ico/16x16/help-donate.png:/ico/16x16/help-donate.png @@ -487,7 +487,7 @@ - + :/ico/16x16/qt.png:/ico/16x16/qt.png @@ -499,7 +499,7 @@ - + :/ico/16x16/orientations.png:/ico/16x16/orientations.png @@ -508,7 +508,7 @@ - + :/ico/16x16/orientations.png:/ico/16x16/orientations.png @@ -517,7 +517,7 @@ - + :/ico/16x16/mirror.png:/ico/16x16/mirror.png @@ -526,7 +526,7 @@ - + :/ico/16x16/flip.png:/ico/16x16/flip.png @@ -535,7 +535,7 @@ - + :/ico/16x16/run-dxf.png:/ico/16x16/run-dxf.png @@ -544,7 +544,7 @@ - + :/ico/22x22/folder-open.png:/ico/22x22/folder-open.png diff --git a/sources/elementspanelwidget.cpp b/sources/elementspanelwidget.cpp index 018064da5..4aa61b7b1 100644 --- a/sources/elementspanelwidget.cpp +++ b/sources/elementspanelwidget.cpp @@ -28,6 +28,8 @@ #include #include #include "qetgraphicsitem/element.h" +#include "qetgraphicsitem/dynamicelementtextitem.h" +#include "qetinformation.h" /* When the ENABLE_PANEL_WIDGET_DND_CHECKS flag is set, the panel @@ -668,6 +670,10 @@ void ElementsPanelWidget::duplicateDiagram() new_diagram->fromXml(diagram_elmt, QPointF(0, 0), false, nullptr); + QSettings settings; + bool erase_labels = settings.value( + "diagramcommands/erase-label-on-copy", true).toBool(); + for (QGraphicsItem *item : new_diagram->items()) { if (Element *elmt = dynamic_cast(item)) { // The XML round-trip kept the source elements' uuids. Give the @@ -688,6 +694,90 @@ void ElementsPanelWidget::duplicateDiagram() { new_diagram->restoreText(elmt); } + + // Clear pending links so copies don't link back to + // the source elements via stale UUIDs. + elmt->clearPendingLinks(); + + // Clean up copied element data: + // 1. Slaves always lose label/formula/comment/location + // and PLC master data — their text comes from a + // master element not available on the copy. + // 2. Non-slaves: honour "erase-label-on-copy". + + DiagramContext dc = elmt->elementInformations(); + bool changed = false; + + // Slaves always lose their label/BMK and PLC data — + // their text comes from the master, which is not + // available on the copied element. + if (elmt->linkType() == Element::Slave) { + dc.addValue("formula", ""); + dc.addValue("label", ""); + dc.addValue("comment", ""); + dc.addValue("location", ""); + changed = true; + + for (const QString &key : { + QETInformation::ELMT_PLC_TYPE, + QETInformation::ELMT_PLC_ADDRESS, + QETInformation::ELMT_PLC_FUNCTION, + QETInformation::ELMT_PLC_COMMENT, + QETInformation::ELMT_PLC_CROSSREF, + QETInformation::ELMT_PLC_TC, + QETInformation::ELMT_PLC_T1, + QETInformation::ELMT_PLC_T2, + QETInformation::ELMT_PLC_T3, + QETInformation::ELMT_PLC_T4, + QStringLiteral("xref")}) { + if (dc.contains(key)) { + dc.remove(key); + changed = true; + } + } + } + // Non-slaves: honour the "erase-label-on-copy" preference. + else if (erase_labels) { + dc.addValue("formula", ""); + dc.addValue("label", ""); + dc.addValue("comment", ""); + dc.addValue("location", ""); + changed = true; + } + + if (changed) { + const bool is_slave = (elmt->linkType() == Element::Slave); + + // Block alignment during setElementInformations + // for non-slaves, same as Element::fromXml() (line 890-896). + // For PLC slaves, don't block — elementInfoChanged() + // fires (elementUseForInfo returns self) and needs + // finishAlignment() to adjust positions for the + // new (empty) text. + if (!is_slave) { + for (auto deti : elmt->dynamicTextItems()) + deti->m_block_alignment = true; + } + elmt->setElementInformations(dc); + for (auto deti : elmt->dynamicTextItems()) + deti->m_block_alignment = false; + + // For non-PLC slaves, elementInfoChanged() doesn't + // fire (no linked master), so the text items keep + // their original text. Clear them directly. + // For PLC slaves, elementInfoChanged() already + // cleared the text above. Skip UserText items + // (free text typed by the user) in both cases. + if (is_slave) { + for (auto deti : elmt->dynamicTextItems()) { + if (deti->textFrom() != DynamicElementTextItem::UserText) { + deti->m_block_alignment = true; + deti->setPlainText(QString()); + deti->m_block_alignment = false; + } + } + } + } } else if (Conductor *cond = dynamic_cast(item)) { // Same reasoning for conductors: conductor.uuid is the PRIMARY @@ -695,6 +785,16 @@ void ElementsPanelWidget::duplicateDiagram() // so a duplicated uuid fails and the wire silently disappears // from the wiring list and the per-element wire count. cond->newUuid(); + + // Reset conductor labels when "erase-label-on-copy" is + // active, matching PasteDiagramCommand::redo() — + // "erase on copy" means erase, not "replace with the + // project's default new-conductor text" (see issue #413). + if (erase_labels) { + ConductorProperties cp = cond->properties(); + cp.text = ""; + cond->setProperties(cp); + } } } } diff --git a/sources/print/projectprintwindow.cpp b/sources/print/projectprintwindow.cpp index 93de03871..e6b86efa5 100644 --- a/sources/print/projectprintwindow.cpp +++ b/sources/print/projectprintwindow.cpp @@ -56,7 +56,6 @@ void ProjectPrintWindow::launchDialog(QETProject *project, QPrinter::OutputFormat format, QWidget *parent) { auto printer_ = new QPrinter(); - QPrinter printer(QPrinter::HighResolution); printer_->setDocName(ProjectPrintWindow::docName(project)); printer_->setPageOrientation(QPageLayout::Landscape); @@ -150,6 +149,11 @@ ProjectPrintWindow::ProjectPrintWindow(QETProject *project, QPrinter *printer, Q ui->m_draw_terminal_names_cb->setChecked(exp.draw_terminal_names); ui->m_keep_conductor_color_cb->setChecked(exp.draw_colored_conductors); + QSettings settings; + ui->m_component_info_cb->setChecked(settings.value("print/default/componentinfo", false).toBool()); + ui->m_fit_in_page_cb->setChecked(settings.value("print/default/fitinpage", true).toBool()); + ui->m_use_full_page_cb->setChecked(settings.value("print/default/fullpage", false).toBool()); + ui->m_date_cb->blockSignals(true); ui->m_date_cb->setDate(QDate::currentDate()); ui->m_date_cb->blockSignals(false); @@ -657,6 +661,16 @@ void ProjectPrintWindow::loadPageSetupForCurrentPrinter() settings.endGroup(); } +void ProjectPrintWindow::savePrintProperties() +{ + QSettings settings; + exportProperties().toSettings(settings, "print/default"); + settings.setValue("print/default/componentinfo", ui->m_component_info_cb->isChecked()); + settings.setValue("print/default/fitinpage", ui->m_fit_in_page_cb->isChecked()); + settings.setValue("print/default/fullpage", ui->m_use_full_page_cb->isChecked()); + settings.sync(); +} + void ProjectPrintWindow::savePageSetupForCurrentPrinter() { QSettings settings; @@ -842,6 +856,7 @@ void ProjectPrintWindow::print() // is created/destroyed inside that call savePageSetupForCurrentPrinter(); + savePrintProperties(); if (isPdf && !pdfFile.isEmpty()) { // Defer post-processing and window close to the next event-loop diff --git a/sources/print/projectprintwindow.h b/sources/print/projectprintwindow.h index 3d16fce91..c023e0080 100644 --- a/sources/print/projectprintwindow.h +++ b/sources/print/projectprintwindow.h @@ -91,6 +91,7 @@ class ProjectPrintWindow : public QMainWindow void setUpDiagramList(); QString settingsSectionName(const QPrinter *printer); void loadPageSetupForCurrentPrinter(); + void savePrintProperties(); void savePageSetupForCurrentPrinter(); void saveReloadDiagramParameters(Diagram *diagram, const ExportProperties &options, bool save); QList selectedDiagram() const; diff --git a/sources/print/projectprintwindow.ui b/sources/print/projectprintwindow.ui index a1db8e411..c13399fb0 100644 --- a/sources/print/projectprintwindow.ui +++ b/sources/print/projectprintwindow.ui @@ -134,7 +134,7 @@ - + :/ico/16x16/dialog-ok.png:/ico/16x16/dialog-ok.png @@ -305,7 +305,7 @@ - + :/ico/22x22/view_fit_width.png:/ico/22x22/view_fit_width.png @@ -314,7 +314,7 @@ - + :/ico/22x22/view-fit-window.png:/ico/22x22/view-fit-window.png @@ -323,7 +323,7 @@ - + :/ico/16x16/zoom-out.png:/ico/16x16/zoom-out.png @@ -332,7 +332,7 @@ - + :/ico/16x16/zoom-in.png:/ico/16x16/zoom-in.png @@ -341,7 +341,7 @@ - + :/ico/22x22/landscape.png:/ico/22x22/landscape.png @@ -350,7 +350,7 @@ - + :/ico/22x22/portrait.png:/ico/22x22/portrait.png @@ -359,7 +359,7 @@ - + :/ico/16x16/arrow-left-double.png:/ico/16x16/arrow-left-double.png @@ -368,7 +368,7 @@ - + :/ico/16x16/arrow-left.png:/ico/16x16/arrow-left.png @@ -377,7 +377,7 @@ - + :/ico/16x16/arrow-right.png:/ico/16x16/arrow-right.png @@ -386,7 +386,7 @@ - + :/ico/22x22/arrow-right-double.png:/ico/22x22/arrow-right-double.png @@ -395,7 +395,7 @@ - + :/ico/22x22/single_page.png:/ico/22x22/single_page.png @@ -404,7 +404,7 @@ - + :/ico/22x22/two_pages.png:/ico/22x22/two_pages.png @@ -413,7 +413,7 @@ - + :/ico/22x22/all_pages.png:/ico/22x22/all_pages.png @@ -422,7 +422,7 @@ - + :/ico/48x48/document-print-frame.png:/ico/48x48/document-print-frame.png diff --git a/sources/qetapp.cpp b/sources/qetapp.cpp index d8e625d4d..76c1203fc 100644 --- a/sources/qetapp.cpp +++ b/sources/qetapp.cpp @@ -120,6 +120,7 @@ QETApp::QETApp() : } initConfiguration(); initLanguage(); + initIconTheme(); QET::Icons::initIcons(); initFonts(); initStyle(); @@ -2330,6 +2331,22 @@ void QETApp::initFonts() } } +/** + @brief QETApp::initIconTheme + Register QET's icon theme "qet" (see misc/make_icon_themes.py and + ico/icon-themes.qrc) and make it the current theme, so + QIcon::fromTheme("name") resolves to QET's own icons on every + platform. Must run before QET::Icons::initIcons(), which looks icons + up by name. +*/ +void QETApp::initIconTheme() +{ + QStringList paths = QIcon::themeSearchPaths(); + paths.prepend(QStringLiteral(":/ico/themes")); + QIcon::setThemeSearchPaths(paths); + QIcon::setThemeName(QStringLiteral("qet")); +} + /** @brief QETApp::initStyle Setup the gui style @@ -2660,14 +2677,28 @@ void QETApp::checkBackupFiles() } } - if (stale_files.isEmpty()) { - // Only offer an unretrieved crash dump when there's no project - // to recover this run -- discussion #644 step 5 is explicit - // that the two prompts must never both show at once. - checkCrashDump(); - return; + if (!stale_files.isEmpty()) { + offerBackupFiles(stale_files); } + // Discussion #644 step 5 asks that the recovery prompt and the crash + // report never show at the same time -- not that the report be dropped + // whenever there is something to recover. Offering it here, once the + // recovery prompt has been answered, keeps the two sequential without + // losing the report after the most common crash there is: one with a + // project open, which always leaves a stale file behind, so the report + // was unreachable in exactly the case it is most wanted (issue #901). + checkCrashDump(); +} + +/** + @brief QETApp::offerBackupFiles + Ask whether to reopen the recovery files left by a previous run, and + open or discard them accordingly. + @param stale_files : the recovery files to offer +*/ +void QETApp::offerBackupFiles(const QList &stale_files) +{ QString text; if(stale_files.size() == 1) { text.append(tr("Le fichier de restauration suivant a été trouvé,
" diff --git a/sources/qetapp.h b/sources/qetapp.h index ee793ecb6..8242abf71 100644 --- a/sources/qetapp.h +++ b/sources/qetapp.h @@ -31,6 +31,7 @@ class QSplashScreen; class QMenu; class QAction; class QMainWindow; +class KAutoSaveFile; #define QETAPP_COMMON_TBT_PROTOCOL "commontbt" #define QETAPP_COMPANY_TBT_PROTOCOL "companytbt" @@ -285,11 +286,13 @@ class QETApp : public QObject void setSplashScreenStep(const QString & = QString()); void initLanguage(); void initFonts(); + void initIconTheme(); void initStyle(); void initConfiguration(); void initSystemTray(); void buildSystemTrayMenu(); void checkBackupFiles(); + void offerBackupFiles(const QList &stale_files); void checkCrashDump(); void fetchWindowStats( const QList &, diff --git a/sources/qetdiagrameditor.cpp b/sources/qetdiagrameditor.cpp index c9f4c868e..d1a4be91b 100644 --- a/sources/qetdiagrameditor.cpp +++ b/sources/qetdiagrameditor.cpp @@ -430,7 +430,7 @@ void QETDiagramEditor::setUpActions() }); // Draw or not the custom guides - m_draw_guides = new QAction ( QIcon(":/ico/22x22/guides.png"), tr("Afficher les guides"), this); + m_draw_guides = new QAction ( QIcon::fromTheme("guides"), tr("Afficher les guides"), this); m_draw_guides->setStatusTip(tr("Affiche ou masque les guides")); m_draw_guides->setCheckable(true); m_draw_guides->setChecked(settings.value("diagrameditor/guides_display_startup", false).toBool()); @@ -1754,6 +1754,10 @@ void QETDiagramEditor::addItemGroupTriggered(QAction *action) // here makes the button's appearance match its actual state // regardless of whether Qt's own change notification fired // correctly. + + if (DiagramView *dv = currentDiagramView()) + dv->setFocus(); // so the view (and the active tool) actually receives Escape etc + if (QWidget *button = m_add_item_tool_bar->widgetForAction(action)) button->update(); } diff --git a/sources/qetgraphicsitem/ViewItem/ui/graphicstablepropertieseditor.ui b/sources/qetgraphicsitem/ViewItem/ui/graphicstablepropertieseditor.ui index 439512405..28edea330 100644 --- a/sources/qetgraphicsitem/ViewItem/ui/graphicstablepropertieseditor.ui +++ b/sources/qetgraphicsitem/ViewItem/ui/graphicstablepropertieseditor.ui @@ -62,7 +62,7 @@
- + :/ico/16x16/arrow-right.png:/ico/16x16/arrow-right.png @@ -104,7 +104,7 @@ - + :/ico/22x22/zoom-fit-best.png:/ico/22x22/zoom-fit-best.png @@ -179,7 +179,7 @@
- + :/ico/16x16/arrow-left.png:/ico/16x16/arrow-left.png @@ -203,7 +203,7 @@ - + :/ico/22x22/all_pages.png:/ico/22x22/all_pages.png diff --git a/sources/qetgraphicsitem/ViewItem/ui/projectdbmodelpropertieswidget.ui b/sources/qetgraphicsitem/ViewItem/ui/projectdbmodelpropertieswidget.ui index 3ce470886..649e756c7 100644 --- a/sources/qetgraphicsitem/ViewItem/ui/projectdbmodelpropertieswidget.ui +++ b/sources/qetgraphicsitem/ViewItem/ui/projectdbmodelpropertieswidget.ui @@ -20,7 +20,7 @@ Requête - + :/ico/16x16/edit-rename.png:/ico/16x16/edit-rename.png @@ -31,7 +31,7 @@ Recharger - + :/ico/16x16/view-refresh.png:/ico/16x16/view-refresh.png diff --git a/sources/qetgraphicsitem/element.h b/sources/qetgraphicsitem/element.h index 1b9ee9409..a189e632b 100644 --- a/sources/qetgraphicsitem/element.h +++ b/sources/qetgraphicsitem/element.h @@ -120,6 +120,11 @@ class Element : public QetGraphicsItem QList conductors() const; QList> AlignedFreeTerminals() const; + void clearPendingLinks() { + tmp_uuids_link.clear(); + m_group_index_map.clear(); + } + //METHODS related to information DiagramContext elementInformations()const {return m_data.m_informations;} diff --git a/sources/qeticons.cpp b/sources/qeticons.cpp index 309ef423c..39030275c 100644 --- a/sources/qeticons.cpp +++ b/sources/qeticons.cpp @@ -392,471 +392,401 @@ void QET::Icons::initIcons() bool rtl = QApplication::isRightToLeft(); QTransform reverse = QTransform().scale(-1, 1); - Add .addFile(":/ico/16x16/list-add.png"); - Add .addFile(":/ico/22x22/list-add.png"); - AddFolio .addFile(":/ico/16x16/folio-new.png"); - Allowed .addFile(":/ico/16x16/user-online.png"); - ApplicationExit .addFile(":/ico/16x16/application-exit.png"); - ApplicationExit .addFile(":/ico/22x22/application-exit.png"); - ArrowLeft .addFile(":/ico/16x16/arrow-left.png"); - ArrowLeft .addFile(":/ico/22x22/arrow-left.png"); - ArrowLeftDouble .addFile(":/ico/16x16/arrow-left-double.png"); - ArrowLeftDouble .addFile(":/ico/22x22/arrow-left-double.png"); - ArrowRight .addFile(":/ico/16x16/arrow-right.png"); - ArrowRight .addFile(":/ico/22x22/arrow-right.png"); - ArrowRightDouble .addFile(":/ico/16x16/arrow-right-double.png"); - ArrowRightDouble .addFile(":/ico/22x22/arrow-right-double.png"); - Autoconnect .addFile(":/ico/22x22/autoconnect.png"); - BringForward .addFile(":/ico/22x22/bring_forward.png"); - Cancel .addFile(":/ico/16x16/item-cancel.png"); - Company .addFile(":/ico/16x16/go-company.png"); - Company .addFile(":/ico/22x22/go-company.png"); - Conductor .addFile(":/ico/16x16/conductor.png"); - ConductorEdit .addFile(":/ico/16x16/conductor-edit.png"); - ConductorSettings .addFile(":/ico/16x16/conductor-reset.png"); - ConductorSettings .addFile(":/ico/22x22/conductor2.png"); - Configure .addFile(":/ico/16x16/configure.png"); - Configure .addFile(":/ico/22x22/configure.png"); - ConfigureToolbars .addFile(":/ico/16x16/configure-toolbars.png"); - ConfigureToolbars .addFile(":/ico/22x22/configure-toolbars.png"); - IC_CopyFile .addFile(":/ico/16x16/item-copy.png"); - DiagramAdd .addFile(":/ico/16x16/folio-new.png"); - DiagramAdd .addFile(":/ico/22x22/diagram_add.png"); - Diagram .addFile(":/ico/16x16/diagram.png"); - Diagram .addFile(":/ico/diagram.png"); - DiagramBg .addFile(":/ico/22x22/diagram_bg.png"); - DiagramDelete .addFile(":/ico/16x16/folio-delete.png"); - DiagramDelete .addFile(":/ico/22x22/diagram_del.png"); - DialogCancel .addFile(":/ico/16x16/dialog-cancel.png"); - DialogCancel .addFile(":/ico/22x22/dialog-cancel.png"); - DialogInformation .addFile(":/ico/16x16/folio-properties.png"); - DialogInformation .addFile(":/ico/22x22/dialog-information.png"); - DialogOk .addFile(":/ico/16x16/dialog-ok.png"); - DialogOk .addFile(":/ico/22x22/dialog-ok.png"); - DocumentClose .addFile(":/ico/16x16/document-close.png"); - DocumentClose .addFile(":/ico/22x22/document-close.png"); - DocumentExport .addFile(":/ico/16x16/document-export.png"); - DocumentExport .addFile(":/ico/22x22/document-export.png"); - DocumentSpreadsheet .addFile(":/ico/16x16/export-csv.png"); - DocumentExport .addFile(":/ico/128x128/document-export.png"); - DocumentImport .addFile(":/ico/16x16/document-import.png"); - DocumentImport .addFile(":/ico/22x22/document-import.png"); - DocumentNew .addFile(":/ico/16x16/document-new.png"); - DocumentNew .addFile(":/ico/22x22/document-new.png"); - DocumentOpen .addFile(":/ico/16x16/document-open.png"); - DocumentOpen .addFile(":/ico/22x22/document-open.png"); - DocumentOpenRecent .addFile(":/ico/16x16/document-open-recent.png"); - DocumentOpenRecent .addFile(":/ico/22x22/document-open-recent.png"); - DocumentPrint .addFile(":/ico/16x16/document-print.png"); - DocumentPrint .addFile(":/ico/22x22/document-print.png"); - DocumentPrintFrame .addFile(":/ico/16x16/document-print-frame.png"); - DocumentPrintFrame .addFile(":/ico/48x48/document-print-frame.png"); - DocumentSave .addFile(":/ico/16x16/document-save.png"); - DocumentSave .addFile(":/ico/22x22/document-save.png"); - DocumentSaveAll .addFile(":/ico/16x16/document-save-all.png"); - DocumentSaveAll .addFile(":/ico/22x22/document-save-all.png"); - DocumentSaveAs .addFile(":/ico/16x16/document-save-as.png"); - DocumentSaveAs .addFile(":/ico/22x22/document-save-as.png"); - East .addFile(":/ico/16x16/east.png"); - EditClear .addFile(":/ico/16x16/edit-clear.png"); - EditClear .addFile(":/ico/22x22/edit-clear.png"); - EditText .addFile(":/ico/22x22/names.png"); - adding_image .addFile(":/ico/22x22/insert-image.png"); - adding_pdf .addFile(":/ico/22x22/pdf-import.png"); + Add = QIcon::fromTheme("list-add"); + AddFolio = QIcon::fromTheme("folio-new"); + Allowed = QIcon::fromTheme("user-online"); + ApplicationExit = QIcon::fromTheme("application-exit"); + ArrowLeft = QIcon::fromTheme("arrow-left"); + ArrowLeftDouble = QIcon::fromTheme("arrow-left-double"); + ArrowRight = QIcon::fromTheme("arrow-right"); + ArrowRightDouble = QIcon::fromTheme("arrow-right-double"); + Autoconnect = QIcon::fromTheme("autoconnect"); + BringForward = QIcon::fromTheme("bring_forward"); + Cancel = QIcon::fromTheme("item-cancel"); + Company = QIcon::fromTheme("go-company"); + Conductor = QIcon::fromTheme("conductor"); + ConductorEdit = QIcon::fromTheme("conductor-edit"); + ConductorSettings = QIcon::fromTheme("conductor-reset"); + Configure = QIcon::fromTheme("configure"); + ConfigureToolbars = QIcon::fromTheme("configure-toolbars"); + IC_CopyFile = QIcon::fromTheme("item-copy"); + DiagramAdd = QIcon::fromTheme("folio-new"); + Diagram.addFile(":/ico/16x16/diagram.png"); + Diagram.addFile(":/ico/diagram.png"); + DiagramBg = QIcon::fromTheme("diagram_bg"); + DiagramDelete = QIcon::fromTheme("folio-delete"); + DialogCancel = QIcon::fromTheme("dialog-cancel"); + DialogInformation = QIcon::fromTheme("folio-properties"); + DialogOk = QIcon::fromTheme("dialog-ok"); + DocumentClose = QIcon::fromTheme("document-close"); + DocumentExport = QIcon::fromTheme("document-export"); + DocumentSpreadsheet = QIcon::fromTheme("export-csv"); + DocumentExport = QIcon::fromTheme("document-export"); + DocumentImport = QIcon::fromTheme("document-import"); + DocumentNew = QIcon::fromTheme("document-new"); + DocumentOpen = QIcon::fromTheme("document-open"); + DocumentOpenRecent = QIcon::fromTheme("document-open-recent"); + DocumentPrint = QIcon::fromTheme("document-print"); + DocumentPrintFrame = QIcon::fromTheme("document-print-frame"); + DocumentSave = QIcon::fromTheme("document-save"); + DocumentSaveAll = QIcon::fromTheme("document-save-all"); + DocumentSaveAs = QIcon::fromTheme("document-save-as"); + East = QIcon::fromTheme("east"); + EditClear = QIcon::fromTheme("edit-clear"); + EditText = QIcon::fromTheme("names"); + adding_image = QIcon::fromTheme("insert-image"); + adding_pdf = QIcon::fromTheme("pdf-import"); if (rtl) { - EditClearLocationBar.addPixmap(QPixmap(":/ico/16x16/edit-clear-locationbar-ltr.png").transformed(reverse)); - EditClearLocationBar.addPixmap(QPixmap(":/ico/22x22/edit-clear-locationbar-ltr.png").transformed(reverse)); + EditClearLocationBar.addPixmap(QIcon::fromTheme("edit-clear-locationbar-ltr").pixmap(16).transformed(reverse)); + EditClearLocationBar.addPixmap(QIcon::fromTheme("edit-clear-locationbar-ltr").pixmap(22).transformed(reverse)); } else { - EditClearLocationBar.addFile(":/ico/16x16/edit-clear-locationbar-ltr.png"); - EditClearLocationBar.addFile(":/ico/22x22/edit-clear-locationbar-ltr.png"); + EditClearLocationBar= QIcon::fromTheme("edit-clear-locationbar-ltr"); } - EditCopy .addFile(":/ico/16x16/edit-copy.png"); - EditCopy .addFile(":/ico/22x22/edit-copy.png"); - EditCut .addFile(":/ico/16x16/edit-cut.png"); - EditCut .addFile(":/ico/22x22/edit-cut.png"); - EditDelete .addFile(":/ico/16x16/edit-delete.png"); - EditDelete .addFile(":/ico/22x22/edit-delete.png"); - EditOpacity .addFile(":/ico/breeze-icons/scalable/apps/hidef/edit-opacity.svg"); - EditPaste .addFile(":/ico/22x22/edit-paste.png"); - EditPaste .addFile(":/ico/16x16/edit-paste.png"); + EditCopy = QIcon::fromTheme("edit-copy"); + EditCut = QIcon::fromTheme("edit-cut"); + EditDelete = QIcon::fromTheme("edit-delete"); + EditOpacity = QIcon::fromTheme("edit-opacity"); + EditPaste = QIcon::fromTheme("edit-paste"); if (rtl) { - EditRedo.addPixmap(QPixmap(":/ico/16x16/edit-redo.png").transformed(reverse)); - EditRedo.addPixmap(QPixmap(":/ico/22x22/edit-redo.png").transformed(reverse)); + EditRedo.addPixmap(QIcon::fromTheme("edit-redo").pixmap(16).transformed(reverse)); + EditRedo.addPixmap(QIcon::fromTheme("edit-redo").pixmap(22).transformed(reverse)); } else { - EditRedo .addFile(":/ico/16x16/edit-redo.png"); - EditRedo .addFile(":/ico/22x22/edit-redo.png"); + EditRedo = QIcon::fromTheme("edit-redo"); } - EditRename .addFile(":/ico/16x16/edit-rename.png"); - EditRename .addFile(":/ico/22x22/edit-rename.png"); - EditSelectAll .addFile(":/ico/16x16/edit-select-all.png"); - EditSelectAll .addFile(":/ico/22x22/edit-select-all.png"); - EditSelectInvert .addFile(":/ico/16x16/edit-select-invert.png"); - EditSelectNone .addFile(":/ico/16x16/edit-select-none.png"); - EditTableCellMerge .addFile(":/ico/16x16/edit-table-cell-merge.png"); - EditTableCellMerge .addFile(":/ico/22x22/edit-table-cell-merge.png"); - EditTableCellSplit .addFile(":/ico/16x16/edit-table-cell-split.png"); - EditTableCellSplit .addFile(":/ico/22x22/edit-table-cell-split.png"); - EditTableDeleteColumn .addFile(":/ico/16x16/edit-table-delete-column.png"); - EditTableDeleteColumn .addFile(":/ico/22x22/edit-table-delete-column.png"); - EditTableDeleteRow .addFile(":/ico/16x16/edit-table-delete-row.png"); - EditTableDeleteRow .addFile(":/ico/22x22/edit-table-delete-row.png"); - EditTableInsertColumnLeft .addFile(":/ico/16x16/edit-table-insert-column-left.png"); - EditTableInsertColumnLeft .addFile(":/ico/22x22/edit-table-insert-column-left.png"); - EditTableInsertColumnRight.addFile(":/ico/16x16/edit-table-insert-column-right.png"); - EditTableInsertColumnRight.addFile(":/ico/22x22/edit-table-insert-column-right.png"); - EditTableInsertRowAbove .addFile(":/ico/16x16/edit-table-insert-row-above.png"); - EditTableInsertRowAbove .addFile(":/ico/22x22/edit-table-insert-row-above.png"); - EditTableInsertRowUnder .addFile(":/ico/16x16/edit-table-insert-row-under.png"); - EditTableInsertRowUnder .addFile(":/ico/22x22/edit-table-insert-row-under.png"); + EditRename = QIcon::fromTheme("edit-rename"); + EditSelectAll = QIcon::fromTheme("edit-select-all"); + EditSelectInvert = QIcon::fromTheme("edit-select-invert"); + EditSelectNone = QIcon::fromTheme("edit-select-none"); + EditTableCellMerge = QIcon::fromTheme("edit-table-cell-merge"); + EditTableCellSplit = QIcon::fromTheme("edit-table-cell-split"); + EditTableDeleteColumn= QIcon::fromTheme("edit-table-delete-column"); + EditTableDeleteRow = QIcon::fromTheme("edit-table-delete-row"); + EditTableInsertColumnLeft= QIcon::fromTheme("edit-table-insert-column-left"); + EditTableInsertColumnRight= QIcon::fromTheme("edit-table-insert-column-right"); + EditTableInsertRowAbove= QIcon::fromTheme("edit-table-insert-row-above"); + EditTableInsertRowUnder= QIcon::fromTheme("edit-table-insert-row-under"); if (rtl) { - EditUndo.addPixmap(QPixmap(":/ico/16x16/edit-undo.png").transformed(reverse)); - EditUndo.addPixmap(QPixmap(":/ico/22x22/edit-undo.png").transformed(reverse)); + EditUndo.addPixmap(QIcon::fromTheme("edit-undo").pixmap(16).transformed(reverse)); + EditUndo.addPixmap(QIcon::fromTheme("edit-undo").pixmap(22).transformed(reverse)); } else { - EditUndo .addFile(":/ico/16x16/edit-undo.png"); - EditUndo .addFile(":/ico/22x22/edit-undo.png"); + EditUndo = QIcon::fromTheme("edit-undo"); } - Element .addFile(":/ico/breeze-icons/16x16/mimetypes/application-x-qet-element.png"); - Element .addFile(":/ico/breeze-icons/22x22/mimetypes/application-x-qet-element.png"); - Element .addFile(":/ico/breeze-icons/32x32/mimetypes/application-x-qet-element.png"); - ElementDelete .addFile(":/ico/16x16/element-delete.png"); - ElementDelete .addFile(":/ico/22x22/element-delete.png"); - ElementEdit .addFile(":/ico/16x16/element-edit.png"); - ElementEdit .addFile(":/ico/22x22/element-edit.png"); - ElementNew .addFile(":/ico/16x16/element-new.png"); - ElementNew .addFile(":/ico/22x22/element-new.png"); - ElementSmall .addFile(":/ico/16x16/element.png"); - EllipseToBezier .addFile(":/ico/generated/ellipse-to-bezier.svg"); - EndLineCircle .addFile(":/ico/16x16/endline-circle.png"); - EndLineDiamond .addFile(":/ico/16x16/endline-diamond.png"); - EndLineNone .addFile(":/ico/16x16/endline-none.png"); - EndLineSimple .addFile(":/ico/16x16/endline-simple.png"); - EndLineTriangle .addFile(":/ico/16x16/endline-triangle.png"); - EPS .addFile(":/ico/32x32/image-x-eps.png"); - Flip .addFile(":/ico/16x16/flip.png"); - Folder .addFile(":/ico/16x16/folder.png"); - FolderDelete .addFile(":/ico/16x16/folder-delete.png"); - FolderDelete .addFile(":/ico/22x22/folder-delete.png"); - FolderEdit .addFile(":/ico/16x16/folder-edit.png"); - FolderEdit .addFile(":/ico/22x22/folder-edit.png"); - FolderNew .addFile(":/ico/16x16/folder-new.png"); - FolderNew .addFile(":/ico/22x22/folder-new.png"); - FolderOnlyThis .addFile(":/ico/16x16/folder-only-this.png"); - FolderOpen .addFile(":/ico/16x16/folder-open.png"); - FolderOpen .addFile(":/ico/22x22/folder-open.png"); - FolderProperties .addFile(":/ico/16x16/folder-properties.png"); - FolderShowAll .addFile(":/ico/16x16/folder-show-all.png"); - Forbidden .addFile(":/ico/16x16/user-busy.png"); - FullScreenEnter .addFile(":/ico/16x16/view-fullscreen.png"); - FullScreenEnter .addFile(":/ico/22x22/view-fullscreen.png"); - FullScreenExit .addFile(":/ico/16x16/view-restore.png"); - FullScreenExit .addFile(":/ico/22x22/view-restore.png"); - GoBottom .addFile(":/ico/16x16/go-bottom.png"); - GoDown .addFile(":/ico/16x16/go-down.png"); - GoDown .addFile(":/ico/22x22/go-down.png"); - GoDownDouble .addFile(":/ico/16x16/go-down-double.png"); - GoTop .addFile(":/ico/16x16/go-top.png"); - GoUp .addFile(":/ico/16x16/go-up.png"); - GoUp .addFile(":/ico/22x22/go-up.png"); - GoUpDouble .addFile(":/ico/16x16/go-up-double.png"); - Ground .addFile(":/ico/16x16/ground.png"); - Grid .addFile(":/ico/16x16/grid.png"); - Grid .addFile(":/ico/22x22/grid.png"); - Hide .addFile(":/ico/16x16/masquer.png"); - Home .addFile(":/ico/16x16/go-home.png"); - Home .addFile(":/ico/22x22/go-home.png"); - HotSpot .addFile(":/ico/22x22/hotspot.png"); - ImageFlipHorizontal .addFile(":/ico/breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg"); - ImageFlipVertical .addFile(":/ico/breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg"); - InsertImage .addFile(":/ico/22x22/insert-image.png"); - Lower .addFile(":/ico/22x22/lower.png"); - IC_MoveFile .addFile(":/ico/16x16/item-move.png"); - Mirror .addFile(":/ico/16x16/mirror.png"); - Names .addFile(":/ico/22x22/names.png"); - Neutral .addFile(":/ico/16x16/neutral.png"); - NewDiagram .addFile(":/ico/128x128/diagram.png"); - North .addFile(":/ico/16x16/north.png"); - ObjectLocked .addFile(":/ico/22x22/object-locked.png"); - ObjectRotateRight .addFile(":/ico/16x16/object-rotate-right.png"); - ObjectRotateRight .addFile(":/ico/22x22/object-rotate-right.png"); - ObjectUnlocked .addFile(":/ico/22x22/object-unlocked.png"); - Orientations .addFile(":/ico/16x16/orientations.png"); - PartArc .addFile(":/ico/22x22/arc.png"); - PartBezier .addFile(":/ico/breeze-icons/scalable/apps/hidef/draw-bezier-curves.svg"); - PartCircle .addFile(":/ico/16x16/circle.png"); - PartEllipse .addFile(":/ico/22x22/ellipse.png"); - PartLine .addFile(":/ico/22x22/line.png"); - PartPolygon .addFile(":/ico/22x22/polygon.png"); - PartRectangle .addFile(":/ico/22x22/rectangle.png"); - PartSelect .addFile(":/ico/16x16/select.png"); - PartSelect .addFile(":/ico/22x22/select.png"); - PartText .addFile(":/ico/22x22/text.png"); - PartTextField .addFile(":/ico/22x22/textfield.png"); - PDF .addFile(":/ico/32x32/application-pdf.png"); - Phase .addFile(":/ico/16x16/phase.png"); - PrintAllPages .addFile(":/ico/22x22/all_pages.png"); - Printer .addFile(":/ico/32x32/printer.png"); - Printer .addFile(":/ico/128x128/printer.png"); - PrintLandscape .addFile(":/ico/22x22/landscape.png"); - PrintPortrait .addFile(":/ico/22x22/portrait.png"); - PrintTwoPages .addFile(":/ico/22x22/two_pages.png"); - Projects .addFile(":/ico/128x128/project.png"); - ProjectClose .addFile(":/ico/16x16/project-close.png"); - ProjectClose .addFile(":/ico/22x22/project-close.png"); - ProjectNew .addFile(":/ico/16x16/project-new.png"); - ProjectNew .addFile(":/ico/22x22/project-new.png"); - ProjectFile .addFile(":/ico/breeze-icons/16x16/mimetypes/application-x-qet-project.png"); - ProjectFile .addFile(":/ico/breeze-icons/22x22/mimetypes/application-x-qet-project.png"); - ProjectFile .addFile(":/ico/breeze-icons/32x32/mimetypes/application-x-qet-project.png"); - ProjectProperties .addFile(":/ico/16x16/project-properties.png"); - QETDonate .addFile(":/ico/16x16/help-donate.png"); - QETDownload .addFile(":/ico/16x16/edit-download.png"); - QETIcon .addFile(":/ico/256x256/qelectrotech.png"); - ProjectFileGP .addFile(":/ico/16x16/project.png"); - QETLogo .addFile(":/ico/16x16/qet.png"); - QETManual .addFile(":/ico/16x16/help-contents.png"); - QETLogo .addFile(":/ico/256x256/qet.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/128x128/apps/qelectrotech.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/16x16/apps/qelectrotech.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/22x22/apps/qelectrotech.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/256x256/apps/qelectrotech.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/32x32/apps/qelectrotech.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/48x48/apps/qelectrotech.png"); - QETOxygenLogo .addFile(":/ico/breeze-icons/64x64/apps/qelectrotech.png"); - QETVideo .addFile(":/ico/16x16/kdenlive-show-video.png"); - QtLogo .addFile(":/ico/16x16/qt.png"); - Raise .addFile(":/ico/22x22/raise.png"); - RectToBezier .addFile(":/ico/generated/rect-to-bezier.svg"); - RectToPolyline .addFile(":/ico/generated/rect-to-polyline.svg"); - Remove .addFile(":/ico/16x16/list-remove.png"); - Remove .addFile(":/ico/22x22/list-remove.png"); - resize_image .addFile(":/ico/22x22/transform-scale.png"); - Restore .addFile(":/ico/22x22/restaurer.png"); - RunDxf .addFile(":/ico/16x16/run-dxf.png"); - SendBackward .addFile(":/ico/22x22/send_backward.png"); - Settings .addFile(":/ico/128x128/settings.png"); - SinglePage .addFile(":/ico/22x22/single_page.png"); - South .addFile(":/ico/16x16/south.png"); - Start .addFile(":/ico/22x22/start.png"); - TableOfContent .addFile(":/ico/16x16/table-of-content.png"); - Terminal .addFile(":/ico/22x22/terminal.png"); - TerminalStrip .addFile(":/ico/16x16/terminalstrip.png"); - TerminalStrip .addFile(":/ico/22x22/terminalstrip.png"); - TitleBlock .addFile(":/ico/16x16/label.png"); - TitleBlock .addFile(":/ico/22x22/label.png"); - TitleBlockBottom .addFile(":/ico/22x22/titleblock-bottom.png"); - TitleBlockRight .addFile(":/ico/22x22/titleblock-right.png"); - TransformCrop .addFile(":/ico/breeze-icons/scalable/apps/hidef/transform-crop.svg"); - TransformRotate .addFile(":/ico/16x16/transform-rotate.png"); - UserInformations .addFile(":/ico/16x16/preferences-desktop-user.png"); - UserInformations .addFile(":/ico/22x22/preferences-desktop-user.png"); - ViewFitWidth .addFile(":/ico/22x22/view_fit_width.png"); - ViewFitWindow .addFile(":/ico/22x22/view-fit-window.png"); - ViewMove .addFile(":/ico/16x16/move.png"); - ViewMove .addFile(":/ico/22x22/move.png"); + Element.addFile(":/ico/breeze-icons/16x16/mimetypes/application-x-qet-element.png"); + Element.addFile(":/ico/breeze-icons/22x22/mimetypes/application-x-qet-element.png"); + Element.addFile(":/ico/breeze-icons/32x32/mimetypes/application-x-qet-element.png"); + ElementDelete = QIcon::fromTheme("element-delete"); + ElementEdit = QIcon::fromTheme("element-edit"); + ElementNew = QIcon::fromTheme("element-new"); + ElementSmall = QIcon::fromTheme("element"); + EllipseToBezier = QIcon::fromTheme("ellipse-to-bezier"); + EndLineCircle = QIcon::fromTheme("endline-circle"); + EndLineDiamond = QIcon::fromTheme("endline-diamond"); + EndLineNone = QIcon::fromTheme("endline-none"); + EndLineSimple = QIcon::fromTheme("endline-simple"); + EndLineTriangle = QIcon::fromTheme("endline-triangle"); + EPS = QIcon::fromTheme("image-x-eps"); + Flip = QIcon::fromTheme("flip"); + Folder = QIcon::fromTheme("folder"); + FolderDelete = QIcon::fromTheme("folder-delete"); + FolderEdit = QIcon::fromTheme("folder-edit"); + FolderNew = QIcon::fromTheme("folder-new"); + FolderOnlyThis = QIcon::fromTheme("folder-only-this"); + FolderOpen = QIcon::fromTheme("folder-open"); + FolderProperties = QIcon::fromTheme("folder-properties"); + FolderShowAll = QIcon::fromTheme("folder-show-all"); + Forbidden = QIcon::fromTheme("user-busy"); + FullScreenEnter = QIcon::fromTheme("view-fullscreen"); + FullScreenExit = QIcon::fromTheme("view-restore"); + GoBottom = QIcon::fromTheme("go-bottom"); + GoDown = QIcon::fromTheme("go-down"); + GoDownDouble = QIcon::fromTheme("go-down-double"); + GoTop = QIcon::fromTheme("go-top"); + GoUp = QIcon::fromTheme("go-up"); + GoUpDouble = QIcon::fromTheme("go-up-double"); + Ground = QIcon::fromTheme("ground"); + Grid = QIcon::fromTheme("grid"); + Hide = QIcon::fromTheme("masquer"); + Home = QIcon::fromTheme("go-home"); + HotSpot = QIcon::fromTheme("hotspot"); + ImageFlipHorizontal = QIcon::fromTheme("image-flip-horizontal"); + ImageFlipVertical = QIcon::fromTheme("image-flip-vertical"); + InsertImage = QIcon::fromTheme("insert-image"); + Lower = QIcon::fromTheme("lower"); + IC_MoveFile = QIcon::fromTheme("item-move"); + Mirror = QIcon::fromTheme("mirror"); + Names = QIcon::fromTheme("names"); + Neutral = QIcon::fromTheme("neutral"); + NewDiagram = QIcon::fromTheme("diagram"); + North = QIcon::fromTheme("north"); + ObjectLocked = QIcon::fromTheme("object-locked"); + ObjectRotateRight = QIcon::fromTheme("object-rotate-right"); + ObjectUnlocked = QIcon::fromTheme("object-unlocked"); + Orientations = QIcon::fromTheme("orientations"); + PartArc = QIcon::fromTheme("arc"); + PartBezier = QIcon::fromTheme("draw-bezier-curves"); + PartCircle = QIcon::fromTheme("circle"); + PartEllipse = QIcon::fromTheme("ellipse"); + PartLine = QIcon::fromTheme("line"); + PartPolygon = QIcon::fromTheme("polygon"); + PartRectangle = QIcon::fromTheme("rectangle"); + PartSelect = QIcon::fromTheme("select"); + PartText = QIcon::fromTheme("text"); + PartTextField = QIcon::fromTheme("textfield"); + PDF = QIcon::fromTheme("application-pdf"); + Phase = QIcon::fromTheme("phase"); + PrintAllPages = QIcon::fromTheme("all_pages"); + Printer = QIcon::fromTheme("printer"); + PrintLandscape = QIcon::fromTheme("landscape"); + PrintPortrait = QIcon::fromTheme("portrait"); + PrintTwoPages = QIcon::fromTheme("two_pages"); + Projects = QIcon::fromTheme("project"); + ProjectClose = QIcon::fromTheme("project-close"); + ProjectNew = QIcon::fromTheme("project-new"); + ProjectFile.addFile(":/ico/breeze-icons/16x16/mimetypes/application-x-qet-project.png"); + ProjectFile.addFile(":/ico/breeze-icons/22x22/mimetypes/application-x-qet-project.png"); + ProjectFile.addFile(":/ico/breeze-icons/32x32/mimetypes/application-x-qet-project.png"); + ProjectProperties = QIcon::fromTheme("project-properties"); + QETDonate = QIcon::fromTheme("help-donate"); + QETDownload = QIcon::fromTheme("edit-download"); + QETIcon.addFile(":/ico/256x256/qelectrotech.png"); + // The elements panel shows the project root with this icon in a 50 px + // slot meant for element previews. The theme name "project" also + // carries the 128 px file used by the configuration dialog, and on a + // 2x display Qt's theme loader would pick it and fill the slot. Load + // the two small files directly so the item stays at 16/22 px. + ProjectFileGP.addFile(":/ico/themes/qet/16x16/project.png"); + ProjectFileGP.addFile(":/ico/themes/qet/22x22/project.png"); + QETLogo = QIcon::fromTheme("qet"); + QETManual = QIcon::fromTheme("help-contents"); + QETLogo.addFile(":/ico/256x256/qet.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/128x128/apps/qelectrotech.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/16x16/apps/qelectrotech.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/22x22/apps/qelectrotech.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/256x256/apps/qelectrotech.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/32x32/apps/qelectrotech.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/48x48/apps/qelectrotech.png"); + QETOxygenLogo.addFile(":/ico/breeze-icons/64x64/apps/qelectrotech.png"); + QETVideo = QIcon::fromTheme("kdenlive-show-video"); + QtLogo = QIcon::fromTheme("qt"); + Raise = QIcon::fromTheme("raise"); + RectToBezier = QIcon::fromTheme("rect-to-bezier"); + RectToPolyline = QIcon::fromTheme("rect-to-polyline"); + Remove = QIcon::fromTheme("list-remove"); + resize_image = QIcon::fromTheme("transform-scale"); + Restore = QIcon::fromTheme("restaurer"); + RunDxf = QIcon::fromTheme("run-dxf"); + SendBackward = QIcon::fromTheme("send_backward"); + Settings = QIcon::fromTheme("settings"); + SinglePage = QIcon::fromTheme("single_page"); + South = QIcon::fromTheme("south"); + Start = QIcon::fromTheme("start"); + TableOfContent = QIcon::fromTheme("table-of-content"); + Terminal = QIcon::fromTheme("terminal"); + TerminalStrip = QIcon::fromTheme("terminalstrip"); + TitleBlock = QIcon::fromTheme("label"); + TitleBlockBottom = QIcon::fromTheme("titleblock-bottom"); + TitleBlockRight = QIcon::fromTheme("titleblock-right"); + TransformCrop = QIcon::fromTheme("transform-crop"); + TransformRotate = QIcon::fromTheme("transform-rotate"); + UserInformations = QIcon::fromTheme("preferences-desktop-user"); + ViewFitWidth = QIcon::fromTheme("view_fit_width"); + ViewFitWindow = QIcon::fromTheme("view-fit-window"); + ViewMove = QIcon::fromTheme("move"); if (rtl) { - ViewRefresh.addPixmap(QPixmap(":/ico/16x16/view-refresh.png").transformed(reverse)); - ViewRefresh.addPixmap(QPixmap(":/ico/22x22/view-refresh.png").transformed(reverse)); + ViewRefresh.addPixmap(QIcon::fromTheme("view-refresh").pixmap(16).transformed(reverse)); + ViewRefresh.addPixmap(QIcon::fromTheme("view-refresh").pixmap(22).transformed(reverse)); } else { - ViewRefresh .addFile(":/ico/16x16/view-refresh.png"); - ViewRefresh .addFile(":/ico/22x22/view-refresh.png"); + ViewRefresh = QIcon::fromTheme("view-refresh"); } - West .addFile(":/ico/16x16/west.png"); - WindowNew .addFile(":/ico/16x16/window-new.png"); - WindowNew .addFile(":/ico/22x22/window-new.png"); - XmlTextFile .addFile(":/ico/16x16/text-xml.png"); - XmlTextFile .addFile(":/ico/22x22/text-xml.png"); - XmlTextFile .addFile(":/ico/32x32/text-xml.png"); - ZoomDraw .addFile(":/ico/16x16/zoom-draw.png"); - ZoomDraw .addFile(":/ico/22x22/zoom-draw.png"); - ZoomFitBest .addFile(":/ico/16x16/zoom-fit-best.png"); - ZoomFitBest .addFile(":/ico/22x22/zoom-fit-best.png"); - ZoomIn .addFile(":/ico/16x16/zoom-in.png"); - ZoomIn .addFile(":/ico/22x22/zoom-in.png"); - ZoomOriginal .addFile(":/ico/16x16/zoom-original.png"); - ZoomOriginal .addFile(":/ico/22x22/zoom-original.png"); - ZoomOut .addFile(":/ico/16x16/zoom-out.png"); - ZoomOut .addFile(":/ico/22x22/zoom-out.png"); - sa .addFile(":/ico/24x16/sa.png"); - br .addFile(":/ico/24x16/br.png"); - catalonia .addFile(":/ico/24x16/catalonia.png"); - cs .addFile(":/ico/24x16/cs.png"); - da .addFile(":/ico/24x16/dk.png"); - de .addFile(":/ico/24x16/de.png"); - gr .addFile(":/ico/24x16/gr.png"); - en .addFile(":/ico/24x16/en.png"); - es .addFile(":/ico/24x16/es.png"); - fr .addFile(":/ico/24x16/fr.png"); - hr .addFile(":/ico/24x16/hr.png"); - hu .addFile(":/ico/24x16/hu.png"); - it .addFile(":/ico/24x16/it.png"); - jp .addFile(":/ico/24x16/jp.png"); - ko .addFile(":/ico/24x16/kr.png"); - pl .addFile(":/ico/24x16/pl.png"); - pt .addFile(":/ico/24x16/pt.png"); - ro .addFile(":/ico/24x16/ro.png"); - ru .addFile(":/ico/24x16/ru.png"); - sl .addFile(":/ico/24x16/sl.png"); - nl .addFile(":/ico/24x16/nl.png"); - no .addFile(":/ico/24x16/no.png"); - nl_BE .addFile(":/ico/24x16/nl_BE.png"); - tr .addFile(":/ico/24x16/tr.png"); - uk .addFile(":/ico/24x16/ua.png"); - mn .addFile(":/ico/24x16/mn.png"); - zh .addFile(":/ico/24x16/zh.png"); - se .addFile(":/ico/24x16/se.png"); - translation .addFile(":/ico/22x22/applications-development-translation.png"); - listDrawings .addFile(":/ico/48x48/view-pim-journal.png"); - AutoNum .addFile(":/ico/128x128/plasmagik.png"); - sub .addFile(":/ico/22x22/format-text-subscript.png"); - super .addFile(":/ico/22x22/format-text-superscript.png"); - textGroup .addFile(":/ico/16x16/object-group.png"); - ElementMaster .addFile(":/ico/16x16/element-master.png"); - ElementSlave .addFile(":/ico/16x16/element-slave.png"); - FolioXrefComing .addFile(":/ico/16x16/folio-ref-coming.png"); - ElementTerminal .addFile(":/ico/16x16/terminal.png"); - ColorBrown .addFile(":/ico/22x22/color/color-brown.png"); - ColorGray .addFile(":/ico/22x22/color/color-gray.png"); - ColorOrange .addFile(":/ico/22x22/color/color-orange.png"); - ColorPurple .addFile(":/ico/22x22/color/color-purple.png"); - ColorHTMLPinkPink .addFile(":/ico/22x22/color/color-01-pink-01-pink.png"); - ColorHTMLPinkLightPink .addFile(":/ico/22x22/color/color-01-pink-02-lightpink.png"); - ColorHTMLPinkHotPink .addFile(":/ico/22x22/color/color-01-pink-03-hotpink.png"); - ColorHTMLPinkDeepPink .addFile(":/ico/22x22/color/color-01-pink-04-deeppink.png"); - ColorHTMLPinkPaleVioletRed .addFile(":/ico/22x22/color/color-01-pink-05-palevioletred.png"); - ColorHTMLPinkMediumVioletRed .addFile(":/ico/22x22/color/color-01-pink-06-mediumvioletred.png"); - ColorHTMLRedLightSalmon .addFile(":/ico/22x22/color/color-02-red-01-lightsalmon.png"); - ColorHTMLRedSalmon .addFile(":/ico/22x22/color/color-02-red-02-salmon.png"); - ColorHTMLRedDarkSalmon .addFile(":/ico/22x22/color/color-02-red-03-darksalmon.png"); - ColorHTMLRedLightCoral .addFile(":/ico/22x22/color/color-02-red-04-lightcoral.png"); - ColorHTMLRedIndianRed .addFile(":/ico/22x22/color/color-02-red-05-indianred.png"); - ColorHTMLRedCrimson .addFile(":/ico/22x22/color/color-02-red-06-crimson.png"); - ColorHTMLRedFirebrick .addFile(":/ico/22x22/color/color-02-red-07-firebrick.png"); - ColorHTMLRedDarkRed .addFile(":/ico/22x22/color/color-02-red-08-darkred.png"); - ColorHTMLRedRed .addFile(":/ico/22x22/color/color-02-red-09-red.png"); - ColorHTMLOrangeOrangeRed .addFile(":/ico/22x22/color/color-03-orange-01-orangered.png"); - ColorHTMLOrangeTomato .addFile(":/ico/22x22/color/color-03-orange-02-tomato.png"); - ColorHTMLOrangeCoral .addFile(":/ico/22x22/color/color-03-orange-03-coral.png"); - ColorHTMLOrangeDarkOrange .addFile(":/ico/22x22/color/color-03-orange-04-darkorange.png"); - ColorHTMLOrangeOrange .addFile(":/ico/22x22/color/color-03-orange-05-orange.png"); - ColorHTMLYellowYellow .addFile(":/ico/22x22/color/color-04-yellow-01-yellow.png"); - ColorHTMLYellowLightYellow .addFile(":/ico/22x22/color/color-04-yellow-02-lightyellow.png"); - ColorHTMLYellowLemonChiffon .addFile(":/ico/22x22/color/color-04-yellow-03-lemonchiffon.png"); + West = QIcon::fromTheme("west"); + WindowNew = QIcon::fromTheme("window-new"); + XmlTextFile = QIcon::fromTheme("text-xml"); + ZoomDraw = QIcon::fromTheme("zoom-draw"); + ZoomFitBest = QIcon::fromTheme("zoom-fit-best"); + ZoomIn = QIcon::fromTheme("zoom-in"); + ZoomOriginal = QIcon::fromTheme("zoom-original"); + ZoomOut = QIcon::fromTheme("zoom-out"); + sa.addFile(":/ico/24x16/sa.png"); + br.addFile(":/ico/24x16/br.png"); + catalonia.addFile(":/ico/24x16/catalonia.png"); + cs.addFile(":/ico/24x16/cs.png"); + da.addFile(":/ico/24x16/dk.png"); + de.addFile(":/ico/24x16/de.png"); + gr.addFile(":/ico/24x16/gr.png"); + en.addFile(":/ico/24x16/en.png"); + es.addFile(":/ico/24x16/es.png"); + fr.addFile(":/ico/24x16/fr.png"); + hr.addFile(":/ico/24x16/hr.png"); + hu.addFile(":/ico/24x16/hu.png"); + it.addFile(":/ico/24x16/it.png"); + jp.addFile(":/ico/24x16/jp.png"); + ko.addFile(":/ico/24x16/kr.png"); + pl.addFile(":/ico/24x16/pl.png"); + pt.addFile(":/ico/24x16/pt.png"); + ro.addFile(":/ico/24x16/ro.png"); + ru.addFile(":/ico/24x16/ru.png"); + sl.addFile(":/ico/24x16/sl.png"); + nl.addFile(":/ico/24x16/nl.png"); + no.addFile(":/ico/24x16/no.png"); + nl_BE.addFile(":/ico/24x16/nl_BE.png"); + tr.addFile(":/ico/24x16/tr.png"); + uk.addFile(":/ico/24x16/ua.png"); + mn.addFile(":/ico/24x16/mn.png"); + zh.addFile(":/ico/24x16/zh.png"); + se.addFile(":/ico/24x16/se.png"); + translation = QIcon::fromTheme("applications-development-translation"); + listDrawings = QIcon::fromTheme("view-pim-journal"); + AutoNum = QIcon::fromTheme("plasmagik"); + sub = QIcon::fromTheme("format-text-subscript"); + super = QIcon::fromTheme("format-text-superscript"); + textGroup = QIcon::fromTheme("object-group"); + ElementMaster = QIcon::fromTheme("element-master"); + ElementSlave = QIcon::fromTheme("element-slave"); + FolioXrefComing = QIcon::fromTheme("folio-ref-coming"); + ElementTerminal = QIcon::fromTheme("terminal"); + ColorBrown.addFile(":/ico/22x22/color/color-brown.png"); + ColorGray.addFile(":/ico/22x22/color/color-gray.png"); + ColorOrange.addFile(":/ico/22x22/color/color-orange.png"); + ColorPurple.addFile(":/ico/22x22/color/color-purple.png"); + ColorHTMLPinkPink.addFile(":/ico/22x22/color/color-01-pink-01-pink.png"); + ColorHTMLPinkLightPink.addFile(":/ico/22x22/color/color-01-pink-02-lightpink.png"); + ColorHTMLPinkHotPink.addFile(":/ico/22x22/color/color-01-pink-03-hotpink.png"); + ColorHTMLPinkDeepPink.addFile(":/ico/22x22/color/color-01-pink-04-deeppink.png"); + ColorHTMLPinkPaleVioletRed.addFile(":/ico/22x22/color/color-01-pink-05-palevioletred.png"); + ColorHTMLPinkMediumVioletRed.addFile(":/ico/22x22/color/color-01-pink-06-mediumvioletred.png"); + ColorHTMLRedLightSalmon.addFile(":/ico/22x22/color/color-02-red-01-lightsalmon.png"); + ColorHTMLRedSalmon.addFile(":/ico/22x22/color/color-02-red-02-salmon.png"); + ColorHTMLRedDarkSalmon.addFile(":/ico/22x22/color/color-02-red-03-darksalmon.png"); + ColorHTMLRedLightCoral.addFile(":/ico/22x22/color/color-02-red-04-lightcoral.png"); + ColorHTMLRedIndianRed.addFile(":/ico/22x22/color/color-02-red-05-indianred.png"); + ColorHTMLRedCrimson.addFile(":/ico/22x22/color/color-02-red-06-crimson.png"); + ColorHTMLRedFirebrick.addFile(":/ico/22x22/color/color-02-red-07-firebrick.png"); + ColorHTMLRedDarkRed.addFile(":/ico/22x22/color/color-02-red-08-darkred.png"); + ColorHTMLRedRed.addFile(":/ico/22x22/color/color-02-red-09-red.png"); + ColorHTMLOrangeOrangeRed.addFile(":/ico/22x22/color/color-03-orange-01-orangered.png"); + ColorHTMLOrangeTomato.addFile(":/ico/22x22/color/color-03-orange-02-tomato.png"); + ColorHTMLOrangeCoral.addFile(":/ico/22x22/color/color-03-orange-03-coral.png"); + ColorHTMLOrangeDarkOrange.addFile(":/ico/22x22/color/color-03-orange-04-darkorange.png"); + ColorHTMLOrangeOrange.addFile(":/ico/22x22/color/color-03-orange-05-orange.png"); + ColorHTMLYellowYellow.addFile(":/ico/22x22/color/color-04-yellow-01-yellow.png"); + ColorHTMLYellowLightYellow.addFile(":/ico/22x22/color/color-04-yellow-02-lightyellow.png"); + ColorHTMLYellowLemonChiffon.addFile(":/ico/22x22/color/color-04-yellow-03-lemonchiffon.png"); ColorHTMLYellowLightGoldenrodYellow.addFile(":/ico/22x22/color/color-04-yellow-04-lightgoldenrodyellow.png"); - ColorHTMLYellowPapayaWhip .addFile(":/ico/22x22/color/color-04-yellow-05-papayawhip.png"); - ColorHTMLYellowMoccasin .addFile(":/ico/22x22/color/color-04-yellow-06-moccasin.png"); - ColorHTMLYellowPeachPuff .addFile(":/ico/22x22/color/color-04-yellow-07-peachpuff.png"); - ColorHTMLYellowPaleGoldenrod .addFile(":/ico/22x22/color/color-04-yellow-08-palegoldenrod.png"); - ColorHTMLYellowKhaki .addFile(":/ico/22x22/color/color-04-yellow-09-khaki.png"); - ColorHTMLYellowDarkKhaki .addFile(":/ico/22x22/color/color-04-yellow-10-darkkhaki.png"); - ColorHTMLYellowGold .addFile(":/ico/22x22/color/color-04-yellow-11-gold.png"); - ColorHTMLBrownCornsilk .addFile(":/ico/22x22/color/color-05-brown-01-cornsilk.png"); - ColorHTMLBrownBlanchedAlmond .addFile(":/ico/22x22/color/color-05-brown-02-blanchedalmond.png"); - ColorHTMLBrownBisque .addFile(":/ico/22x22/color/color-05-brown-03-bisque.png"); - ColorHTMLBrownNavajoWhite .addFile(":/ico/22x22/color/color-05-brown-04-navajowhite.png"); - ColorHTMLBrownWheat .addFile(":/ico/22x22/color/color-05-brown-05-wheat.png"); - ColorHTMLBrownBurlywood .addFile(":/ico/22x22/color/color-05-brown-06-burlywood.png"); - ColorHTMLBrownTan .addFile(":/ico/22x22/color/color-05-brown-07-tan.png"); - ColorHTMLBrownRosyBrown .addFile(":/ico/22x22/color/color-05-brown-08-rosybrown.png"); - ColorHTMLBrownSandyBrown .addFile(":/ico/22x22/color/color-05-brown-09-sandybrown.png"); - ColorHTMLBrownGoldenrod .addFile(":/ico/22x22/color/color-05-brown-10-goldenrod.png"); - ColorHTMLBrownDarkGoldenrod .addFile(":/ico/22x22/color/color-05-brown-11-darkgoldenrod.png"); - ColorHTMLBrownPeru .addFile(":/ico/22x22/color/color-05-brown-12-peru.png"); - ColorHTMLBrownChocolate .addFile(":/ico/22x22/color/color-05-brown-13-chocolate.png"); - ColorHTMLBrownSaddleBrown .addFile(":/ico/22x22/color/color-05-brown-14-saddlebrown.png"); - ColorHTMLBrownSienna .addFile(":/ico/22x22/color/color-05-brown-15-sienna.png"); - ColorHTMLBrownBrown .addFile(":/ico/22x22/color/color-05-brown-16-brown.png"); - ColorHTMLBrownMaroon .addFile(":/ico/22x22/color/color-05-brown-17-maroon.png"); - ColorHTMLGreenDarkOliveGreen .addFile(":/ico/22x22/color/color-06-green-01-darkolivegreen.png"); - ColorHTMLGreenOlive .addFile(":/ico/22x22/color/color-06-green-02-olive.png"); - ColorHTMLGreenOliveDrab .addFile(":/ico/22x22/color/color-06-green-03-olivedrab.png"); - ColorHTMLGreenYellowGreen .addFile(":/ico/22x22/color/color-06-green-04-yellowgreen.png"); - ColorHTMLGreenLimeGreen .addFile(":/ico/22x22/color/color-06-green-05-limegreen.png"); - ColorHTMLGreenLime .addFile(":/ico/22x22/color/color-06-green-06-lime.png"); - ColorHTMLGreenLawnGreen .addFile(":/ico/22x22/color/color-06-green-07-lawngreen.png"); - ColorHTMLGreenChartreuse .addFile(":/ico/22x22/color/color-06-green-08-chartreuse.png"); - ColorHTMLGreenGreenYellow .addFile(":/ico/22x22/color/color-06-green-09-greenyellow.png"); - ColorHTMLGreenSpringGreen .addFile(":/ico/22x22/color/color-06-green-10-springgreen.png"); - ColorHTMLGreenMediumSpringGreen .addFile(":/ico/22x22/color/color-06-green-11-mediumspringgreen.png"); - ColorHTMLGreenLightGreen .addFile(":/ico/22x22/color/color-06-green-12-lightgreen.png"); - ColorHTMLGreenPaleGreen .addFile(":/ico/22x22/color/color-06-green-13-palegreen.png"); - ColorHTMLGreenDarkSeaGreen .addFile(":/ico/22x22/color/color-06-green-14-darkseagreen.png"); - ColorHTMLGreenMediumAquamarine .addFile(":/ico/22x22/color/color-06-green-15-mediumaquamarine.png"); - ColorHTMLGreenMediumSeaGreen .addFile(":/ico/22x22/color/color-06-green-16-mediumseagreen.png"); - ColorHTMLGreenSeaGreen .addFile(":/ico/22x22/color/color-06-green-17-seagreen.png"); - ColorHTMLGreenForestGreen .addFile(":/ico/22x22/color/color-06-green-18-forestgreen.png"); - ColorHTMLGreenGreen .addFile(":/ico/22x22/color/color-06-green-19-green.png"); - ColorHTMLGreenDarkGreen .addFile(":/ico/22x22/color/color-06-green-20-darkgreen.png"); - ColorHTMLCyanAqua .addFile(":/ico/22x22/color/color-07-cyan-01-aqua.png"); - ColorHTMLCyanCyan .addFile(":/ico/22x22/color/color-07-cyan-02-cyan.png"); - ColorHTMLCyanLightCyan .addFile(":/ico/22x22/color/color-07-cyan-03-lightcyan.png"); - ColorHTMLCyanPaleTurquoise .addFile(":/ico/22x22/color/color-07-cyan-04-paleturquoise.png"); - ColorHTMLCyanAquamarine .addFile(":/ico/22x22/color/color-07-cyan-05-aquamarine.png"); - ColorHTMLCyanTurquoise .addFile(":/ico/22x22/color/color-07-cyan-06-turquoise.png"); - ColorHTMLCyanMediumTurquoise .addFile(":/ico/22x22/color/color-07-cyan-07-mediumturquoise.png"); - ColorHTMLCyanDarkTurquoise .addFile(":/ico/22x22/color/color-07-cyan-08-darkturquoise.png"); - ColorHTMLCyanLightSeaGreen .addFile(":/ico/22x22/color/color-07-cyan-09-lightseagreen.png"); - ColorHTMLCyanCadetBlue .addFile(":/ico/22x22/color/color-07-cyan-10-cadetblue.png"); - ColorHTMLCyanDarkCyan .addFile(":/ico/22x22/color/color-07-cyan-11-darkcyan.png"); - ColorHTMLCyanTeal .addFile(":/ico/22x22/color/color-07-cyan-12-teal.png"); - ColorHTMLBlueLightSteelBlue .addFile(":/ico/22x22/color/color-08-blue-01-lightsteelblue.png"); - ColorHTMLBluePowderBlue .addFile(":/ico/22x22/color/color-08-blue-02-powderblue.png"); - ColorHTMLBlueLightBlue .addFile(":/ico/22x22/color/color-08-blue-03-lightblue.png"); - ColorHTMLBlueSkyBlue .addFile(":/ico/22x22/color/color-08-blue-04-skyblue.png"); - ColorHTMLBlueLightSkyBlue .addFile(":/ico/22x22/color/color-08-blue-05-lightskyblue.png"); - ColorHTMLBlueDeepSkyBlue .addFile(":/ico/22x22/color/color-08-blue-06-deepskyblue.png"); - ColorHTMLBlueDodgerBlue .addFile(":/ico/22x22/color/color-08-blue-07-dodgerblue.png"); - ColorHTMLBlueCornflowerBlue .addFile(":/ico/22x22/color/color-08-blue-08-cornflowerblue.png"); - ColorHTMLBlueSteelBlue .addFile(":/ico/22x22/color/color-08-blue-09-steelblue.png"); - ColorHTMLBlueRoyalBlue .addFile(":/ico/22x22/color/color-08-blue-10-royalblue.png"); - ColorHTMLBlueBlue .addFile(":/ico/22x22/color/color-08-blue-11-blue.png"); - ColorHTMLBlueMediumBlue .addFile(":/ico/22x22/color/color-08-blue-12-mediumblue.png"); - ColorHTMLBlueDarkBlue .addFile(":/ico/22x22/color/color-08-blue-13-darkblue.png"); - ColorHTMLBlueNavy .addFile(":/ico/22x22/color/color-08-blue-14-navy.png"); - ColorHTMLBlueMidnightBlue .addFile(":/ico/22x22/color/color-08-blue-15-midnightblue.png"); - ColorHTMLPurpleLavender .addFile(":/ico/22x22/color/color-09-purple-01-lavender.png"); - ColorHTMLPurpleThistle .addFile(":/ico/22x22/color/color-09-purple-02-thistle.png"); - ColorHTMLPurplePlum .addFile(":/ico/22x22/color/color-09-purple-03-plum.png"); - ColorHTMLPurpleViolet .addFile(":/ico/22x22/color/color-09-purple-04-violet.png"); - ColorHTMLPurpleOrchid .addFile(":/ico/22x22/color/color-09-purple-05-orchid.png"); - ColorHTMLPurpleFuchsia .addFile(":/ico/22x22/color/color-09-purple-06-fuchsia.png"); - ColorHTMLPurpleMagenta .addFile(":/ico/22x22/color/color-09-purple-07-magenta.png"); - ColorHTMLPurpleMediumOrchid .addFile(":/ico/22x22/color/color-09-purple-08-mediumorchid.png"); - ColorHTMLPurpleMediumPurple .addFile(":/ico/22x22/color/color-09-purple-09-mediumpurple.png"); - ColorHTMLPurpleBlueViolet .addFile(":/ico/22x22/color/color-09-purple-10-blueviolet.png"); - ColorHTMLPurpleDarkViolet .addFile(":/ico/22x22/color/color-09-purple-11-darkviolet.png"); - ColorHTMLPurpleDarkOrchid .addFile(":/ico/22x22/color/color-09-purple-12-darkorchid.png"); - ColorHTMLPurpleDarkMagenta .addFile(":/ico/22x22/color/color-09-purple-13-darkmagenta.png"); - ColorHTMLPurplePurple .addFile(":/ico/22x22/color/color-09-purple-14-purple.png"); - ColorHTMLPurpleIndigo .addFile(":/ico/22x22/color/color-09-purple-15-indigo.png"); - ColorHTMLPurpleDarkSlateBlue .addFile(":/ico/22x22/color/color-09-purple-16-darkslateblue.png"); - ColorHTMLPurpleSlateBlue .addFile(":/ico/22x22/color/color-09-purple-17-slateblue.png"); - ColorHTMLPurpleMediumSlateBlue .addFile(":/ico/22x22/color/color-09-purple-18-mediumslateblue.png"); - ColorHTMLWhiteWhite .addFile(":/ico/22x22/color/color-10-white-01-white.png"); - ColorHTMLWhiteSnow .addFile(":/ico/22x22/color/color-10-white-02-snow.png"); - ColorHTMLWhiteHoneydew .addFile(":/ico/22x22/color/color-10-white-03-honeydew.png"); - ColorHTMLWhiteMintCream .addFile(":/ico/22x22/color/color-10-white-04-mintcream.png"); - ColorHTMLWhiteAzure .addFile(":/ico/22x22/color/color-10-white-05-azure.png"); - ColorHTMLWhiteAliceBlue .addFile(":/ico/22x22/color/color-10-white-06-aliceblue.png"); - ColorHTMLWhiteGhostWhite .addFile(":/ico/22x22/color/color-10-white-07-ghostwhite.png"); - ColorHTMLWhiteWhiteSmoke .addFile(":/ico/22x22/color/color-10-white-08-whitesmoke.png"); - ColorHTMLWhiteSeashell .addFile(":/ico/22x22/color/color-10-white-09-seashell.png"); - ColorHTMLWhiteBeige .addFile(":/ico/22x22/color/color-10-white-10-beige.png"); - ColorHTMLWhiteOldLace .addFile(":/ico/22x22/color/color-10-white-11-oldlace.png"); - ColorHTMLWhiteFloralWhite .addFile(":/ico/22x22/color/color-10-white-12-floralwhite.png"); - ColorHTMLWhiteIvory .addFile(":/ico/22x22/color/color-10-white-13-ivory.png"); - ColorHTMLWhiteAntiqueWhite .addFile(":/ico/22x22/color/color-10-white-14-antiquewhite.png"); - ColorHTMLWhiteLinen .addFile(":/ico/22x22/color/color-10-white-15-linen.png"); - ColorHTMLWhiteLavenderBlush .addFile(":/ico/22x22/color/color-10-white-16-lavenderblush.png"); - ColorHTMLWhiteMistyRose .addFile(":/ico/22x22/color/color-10-white-17-mistyrose.png"); - ColorHTMLGrayGainsboro .addFile(":/ico/22x22/color/color-11-gray-01-gainsboro.png"); - ColorHTMLGrayLightGray .addFile(":/ico/22x22/color/color-11-gray-02-lightgray.png"); - ColorHTMLGraySilver .addFile(":/ico/22x22/color/color-11-gray-03-silver.png"); - ColorHTMLGrayDarkGray .addFile(":/ico/22x22/color/color-11-gray-04-darkgray.png"); - ColorHTMLGrayGray .addFile(":/ico/22x22/color/color-11-gray-05-gray.png"); - ColorHTMLGrayDimGray .addFile(":/ico/22x22/color/color-11-gray-06-dimgray.png"); - ColorHTMLGrayLightSlateGray .addFile(":/ico/22x22/color/color-11-gray-07-lightslategray.png"); - ColorHTMLGraySlateGray .addFile(":/ico/22x22/color/color-11-gray-08-slategray.png"); - ColorHTMLGrayDarkSlateGray .addFile(":/ico/22x22/color/color-11-gray-09-darkslategray.png"); - ColorHTMLGrayBlack .addFile(":/ico/22x22/color/color-11-gray-10-black.png"); + ColorHTMLYellowPapayaWhip.addFile(":/ico/22x22/color/color-04-yellow-05-papayawhip.png"); + ColorHTMLYellowMoccasin.addFile(":/ico/22x22/color/color-04-yellow-06-moccasin.png"); + ColorHTMLYellowPeachPuff.addFile(":/ico/22x22/color/color-04-yellow-07-peachpuff.png"); + ColorHTMLYellowPaleGoldenrod.addFile(":/ico/22x22/color/color-04-yellow-08-palegoldenrod.png"); + ColorHTMLYellowKhaki.addFile(":/ico/22x22/color/color-04-yellow-09-khaki.png"); + ColorHTMLYellowDarkKhaki.addFile(":/ico/22x22/color/color-04-yellow-10-darkkhaki.png"); + ColorHTMLYellowGold.addFile(":/ico/22x22/color/color-04-yellow-11-gold.png"); + ColorHTMLBrownCornsilk.addFile(":/ico/22x22/color/color-05-brown-01-cornsilk.png"); + ColorHTMLBrownBlanchedAlmond.addFile(":/ico/22x22/color/color-05-brown-02-blanchedalmond.png"); + ColorHTMLBrownBisque.addFile(":/ico/22x22/color/color-05-brown-03-bisque.png"); + ColorHTMLBrownNavajoWhite.addFile(":/ico/22x22/color/color-05-brown-04-navajowhite.png"); + ColorHTMLBrownWheat.addFile(":/ico/22x22/color/color-05-brown-05-wheat.png"); + ColorHTMLBrownBurlywood.addFile(":/ico/22x22/color/color-05-brown-06-burlywood.png"); + ColorHTMLBrownTan.addFile(":/ico/22x22/color/color-05-brown-07-tan.png"); + ColorHTMLBrownRosyBrown.addFile(":/ico/22x22/color/color-05-brown-08-rosybrown.png"); + ColorHTMLBrownSandyBrown.addFile(":/ico/22x22/color/color-05-brown-09-sandybrown.png"); + ColorHTMLBrownGoldenrod.addFile(":/ico/22x22/color/color-05-brown-10-goldenrod.png"); + ColorHTMLBrownDarkGoldenrod.addFile(":/ico/22x22/color/color-05-brown-11-darkgoldenrod.png"); + ColorHTMLBrownPeru.addFile(":/ico/22x22/color/color-05-brown-12-peru.png"); + ColorHTMLBrownChocolate.addFile(":/ico/22x22/color/color-05-brown-13-chocolate.png"); + ColorHTMLBrownSaddleBrown.addFile(":/ico/22x22/color/color-05-brown-14-saddlebrown.png"); + ColorHTMLBrownSienna.addFile(":/ico/22x22/color/color-05-brown-15-sienna.png"); + ColorHTMLBrownBrown.addFile(":/ico/22x22/color/color-05-brown-16-brown.png"); + ColorHTMLBrownMaroon.addFile(":/ico/22x22/color/color-05-brown-17-maroon.png"); + ColorHTMLGreenDarkOliveGreen.addFile(":/ico/22x22/color/color-06-green-01-darkolivegreen.png"); + ColorHTMLGreenOlive.addFile(":/ico/22x22/color/color-06-green-02-olive.png"); + ColorHTMLGreenOliveDrab.addFile(":/ico/22x22/color/color-06-green-03-olivedrab.png"); + ColorHTMLGreenYellowGreen.addFile(":/ico/22x22/color/color-06-green-04-yellowgreen.png"); + ColorHTMLGreenLimeGreen.addFile(":/ico/22x22/color/color-06-green-05-limegreen.png"); + ColorHTMLGreenLime.addFile(":/ico/22x22/color/color-06-green-06-lime.png"); + ColorHTMLGreenLawnGreen.addFile(":/ico/22x22/color/color-06-green-07-lawngreen.png"); + ColorHTMLGreenChartreuse.addFile(":/ico/22x22/color/color-06-green-08-chartreuse.png"); + ColorHTMLGreenGreenYellow.addFile(":/ico/22x22/color/color-06-green-09-greenyellow.png"); + ColorHTMLGreenSpringGreen.addFile(":/ico/22x22/color/color-06-green-10-springgreen.png"); + ColorHTMLGreenMediumSpringGreen.addFile(":/ico/22x22/color/color-06-green-11-mediumspringgreen.png"); + ColorHTMLGreenLightGreen.addFile(":/ico/22x22/color/color-06-green-12-lightgreen.png"); + ColorHTMLGreenPaleGreen.addFile(":/ico/22x22/color/color-06-green-13-palegreen.png"); + ColorHTMLGreenDarkSeaGreen.addFile(":/ico/22x22/color/color-06-green-14-darkseagreen.png"); + ColorHTMLGreenMediumAquamarine.addFile(":/ico/22x22/color/color-06-green-15-mediumaquamarine.png"); + ColorHTMLGreenMediumSeaGreen.addFile(":/ico/22x22/color/color-06-green-16-mediumseagreen.png"); + ColorHTMLGreenSeaGreen.addFile(":/ico/22x22/color/color-06-green-17-seagreen.png"); + ColorHTMLGreenForestGreen.addFile(":/ico/22x22/color/color-06-green-18-forestgreen.png"); + ColorHTMLGreenGreen.addFile(":/ico/22x22/color/color-06-green-19-green.png"); + ColorHTMLGreenDarkGreen.addFile(":/ico/22x22/color/color-06-green-20-darkgreen.png"); + ColorHTMLCyanAqua.addFile(":/ico/22x22/color/color-07-cyan-01-aqua.png"); + ColorHTMLCyanCyan.addFile(":/ico/22x22/color/color-07-cyan-02-cyan.png"); + ColorHTMLCyanLightCyan.addFile(":/ico/22x22/color/color-07-cyan-03-lightcyan.png"); + ColorHTMLCyanPaleTurquoise.addFile(":/ico/22x22/color/color-07-cyan-04-paleturquoise.png"); + ColorHTMLCyanAquamarine.addFile(":/ico/22x22/color/color-07-cyan-05-aquamarine.png"); + ColorHTMLCyanTurquoise.addFile(":/ico/22x22/color/color-07-cyan-06-turquoise.png"); + ColorHTMLCyanMediumTurquoise.addFile(":/ico/22x22/color/color-07-cyan-07-mediumturquoise.png"); + ColorHTMLCyanDarkTurquoise.addFile(":/ico/22x22/color/color-07-cyan-08-darkturquoise.png"); + ColorHTMLCyanLightSeaGreen.addFile(":/ico/22x22/color/color-07-cyan-09-lightseagreen.png"); + ColorHTMLCyanCadetBlue.addFile(":/ico/22x22/color/color-07-cyan-10-cadetblue.png"); + ColorHTMLCyanDarkCyan.addFile(":/ico/22x22/color/color-07-cyan-11-darkcyan.png"); + ColorHTMLCyanTeal.addFile(":/ico/22x22/color/color-07-cyan-12-teal.png"); + ColorHTMLBlueLightSteelBlue.addFile(":/ico/22x22/color/color-08-blue-01-lightsteelblue.png"); + ColorHTMLBluePowderBlue.addFile(":/ico/22x22/color/color-08-blue-02-powderblue.png"); + ColorHTMLBlueLightBlue.addFile(":/ico/22x22/color/color-08-blue-03-lightblue.png"); + ColorHTMLBlueSkyBlue.addFile(":/ico/22x22/color/color-08-blue-04-skyblue.png"); + ColorHTMLBlueLightSkyBlue.addFile(":/ico/22x22/color/color-08-blue-05-lightskyblue.png"); + ColorHTMLBlueDeepSkyBlue.addFile(":/ico/22x22/color/color-08-blue-06-deepskyblue.png"); + ColorHTMLBlueDodgerBlue.addFile(":/ico/22x22/color/color-08-blue-07-dodgerblue.png"); + ColorHTMLBlueCornflowerBlue.addFile(":/ico/22x22/color/color-08-blue-08-cornflowerblue.png"); + ColorHTMLBlueSteelBlue.addFile(":/ico/22x22/color/color-08-blue-09-steelblue.png"); + ColorHTMLBlueRoyalBlue.addFile(":/ico/22x22/color/color-08-blue-10-royalblue.png"); + ColorHTMLBlueBlue.addFile(":/ico/22x22/color/color-08-blue-11-blue.png"); + ColorHTMLBlueMediumBlue.addFile(":/ico/22x22/color/color-08-blue-12-mediumblue.png"); + ColorHTMLBlueDarkBlue.addFile(":/ico/22x22/color/color-08-blue-13-darkblue.png"); + ColorHTMLBlueNavy.addFile(":/ico/22x22/color/color-08-blue-14-navy.png"); + ColorHTMLBlueMidnightBlue.addFile(":/ico/22x22/color/color-08-blue-15-midnightblue.png"); + ColorHTMLPurpleLavender.addFile(":/ico/22x22/color/color-09-purple-01-lavender.png"); + ColorHTMLPurpleThistle.addFile(":/ico/22x22/color/color-09-purple-02-thistle.png"); + ColorHTMLPurplePlum.addFile(":/ico/22x22/color/color-09-purple-03-plum.png"); + ColorHTMLPurpleViolet.addFile(":/ico/22x22/color/color-09-purple-04-violet.png"); + ColorHTMLPurpleOrchid.addFile(":/ico/22x22/color/color-09-purple-05-orchid.png"); + ColorHTMLPurpleFuchsia.addFile(":/ico/22x22/color/color-09-purple-06-fuchsia.png"); + ColorHTMLPurpleMagenta.addFile(":/ico/22x22/color/color-09-purple-07-magenta.png"); + ColorHTMLPurpleMediumOrchid.addFile(":/ico/22x22/color/color-09-purple-08-mediumorchid.png"); + ColorHTMLPurpleMediumPurple.addFile(":/ico/22x22/color/color-09-purple-09-mediumpurple.png"); + ColorHTMLPurpleBlueViolet.addFile(":/ico/22x22/color/color-09-purple-10-blueviolet.png"); + ColorHTMLPurpleDarkViolet.addFile(":/ico/22x22/color/color-09-purple-11-darkviolet.png"); + ColorHTMLPurpleDarkOrchid.addFile(":/ico/22x22/color/color-09-purple-12-darkorchid.png"); + ColorHTMLPurpleDarkMagenta.addFile(":/ico/22x22/color/color-09-purple-13-darkmagenta.png"); + ColorHTMLPurplePurple.addFile(":/ico/22x22/color/color-09-purple-14-purple.png"); + ColorHTMLPurpleIndigo.addFile(":/ico/22x22/color/color-09-purple-15-indigo.png"); + ColorHTMLPurpleDarkSlateBlue.addFile(":/ico/22x22/color/color-09-purple-16-darkslateblue.png"); + ColorHTMLPurpleSlateBlue.addFile(":/ico/22x22/color/color-09-purple-17-slateblue.png"); + ColorHTMLPurpleMediumSlateBlue.addFile(":/ico/22x22/color/color-09-purple-18-mediumslateblue.png"); + ColorHTMLWhiteWhite.addFile(":/ico/22x22/color/color-10-white-01-white.png"); + ColorHTMLWhiteSnow.addFile(":/ico/22x22/color/color-10-white-02-snow.png"); + ColorHTMLWhiteHoneydew.addFile(":/ico/22x22/color/color-10-white-03-honeydew.png"); + ColorHTMLWhiteMintCream.addFile(":/ico/22x22/color/color-10-white-04-mintcream.png"); + ColorHTMLWhiteAzure.addFile(":/ico/22x22/color/color-10-white-05-azure.png"); + ColorHTMLWhiteAliceBlue.addFile(":/ico/22x22/color/color-10-white-06-aliceblue.png"); + ColorHTMLWhiteGhostWhite.addFile(":/ico/22x22/color/color-10-white-07-ghostwhite.png"); + ColorHTMLWhiteWhiteSmoke.addFile(":/ico/22x22/color/color-10-white-08-whitesmoke.png"); + ColorHTMLWhiteSeashell.addFile(":/ico/22x22/color/color-10-white-09-seashell.png"); + ColorHTMLWhiteBeige.addFile(":/ico/22x22/color/color-10-white-10-beige.png"); + ColorHTMLWhiteOldLace.addFile(":/ico/22x22/color/color-10-white-11-oldlace.png"); + ColorHTMLWhiteFloralWhite.addFile(":/ico/22x22/color/color-10-white-12-floralwhite.png"); + ColorHTMLWhiteIvory.addFile(":/ico/22x22/color/color-10-white-13-ivory.png"); + ColorHTMLWhiteAntiqueWhite.addFile(":/ico/22x22/color/color-10-white-14-antiquewhite.png"); + ColorHTMLWhiteLinen.addFile(":/ico/22x22/color/color-10-white-15-linen.png"); + ColorHTMLWhiteLavenderBlush.addFile(":/ico/22x22/color/color-10-white-16-lavenderblush.png"); + ColorHTMLWhiteMistyRose.addFile(":/ico/22x22/color/color-10-white-17-mistyrose.png"); + ColorHTMLGrayGainsboro.addFile(":/ico/22x22/color/color-11-gray-01-gainsboro.png"); + ColorHTMLGrayLightGray.addFile(":/ico/22x22/color/color-11-gray-02-lightgray.png"); + ColorHTMLGraySilver.addFile(":/ico/22x22/color/color-11-gray-03-silver.png"); + ColorHTMLGrayDarkGray.addFile(":/ico/22x22/color/color-11-gray-04-darkgray.png"); + ColorHTMLGrayGray.addFile(":/ico/22x22/color/color-11-gray-05-gray.png"); + ColorHTMLGrayDimGray.addFile(":/ico/22x22/color/color-11-gray-06-dimgray.png"); + ColorHTMLGrayLightSlateGray.addFile(":/ico/22x22/color/color-11-gray-07-lightslategray.png"); + ColorHTMLGraySlateGray.addFile(":/ico/22x22/color/color-11-gray-08-slategray.png"); + ColorHTMLGrayDarkSlateGray.addFile(":/ico/22x22/color/color-11-gray-09-darkslategray.png"); + ColorHTMLGrayBlack.addFile(":/ico/22x22/color/color-11-gray-10-black.png"); } diff --git a/sources/richtext/richtexteditor.cpp b/sources/richtext/richtexteditor.cpp index 489d3391a..9c8b9b0a6 100644 --- a/sources/richtext/richtexteditor.cpp +++ b/sources/richtext/richtexteditor.cpp @@ -514,19 +514,19 @@ RichTextEditorToolBar::RichTextEditorToolBar(RichTextEditor *editor, // Bold, italic and underline buttons m_bold_action = createCheckableAction( - QIcon(":/ico/32x32/format-text-bold.png"), + QIcon::fromTheme("format-text-bold"), tr("Texte en gras"), editor, &RichTextEditor::setFontBold, this); ShortcutManager::instance().registerAction(m_bold_action, "richtext.bold", tr("Éditeur de texte"), Qt::CTRL | Qt::Key_B); addAction(m_bold_action); m_italic_action = createCheckableAction( - QIcon(":/ico/32x32/format-text-italic.png"), + QIcon::fromTheme("format-text-italic"), tr("Texte en italique"), editor, &RichTextEditor::setFontItalic, this); ShortcutManager::instance().registerAction(m_italic_action, "richtext.italic", tr("Éditeur de texte"), Qt::CTRL | Qt::Key_I); addAction(m_italic_action); m_underline_action = createCheckableAction( - QIcon(":/ico/32x32/format-text-underline.png"), + QIcon::fromTheme("format-text-underline"), tr("Texte souligé"), editor, &RichTextEditor::setFontUnderline, this); ShortcutManager::instance().registerAction(m_underline_action, "richtext.underline", tr("Éditeur de texte"), Qt::CTRL | Qt::Key_U); addAction(m_underline_action); @@ -567,13 +567,13 @@ RichTextEditorToolBar::RichTextEditorToolBar(RichTextEditor *editor, // Superscript and subscript buttons m_valign_sup_action = createCheckableAction( - QIcon(":/ico/22x22/format-text-superscript.png"), + QIcon::fromTheme("format-text-superscript"), tr("Superscript"), this, &RichTextEditorToolBar::setVAlignSuper, this); addAction(m_valign_sup_action); m_valign_sub_action = createCheckableAction( - QIcon(":/ico/22x22/format-text-subscript.png"), + QIcon::fromTheme("format-text-subscript"), tr("Subscript"), this, &RichTextEditorToolBar::setVAlignSub, this); addAction(m_valign_sub_action); @@ -601,7 +601,7 @@ RichTextEditorToolBar::RichTextEditorToolBar(RichTextEditor *editor, // Simplify rich text m_simplify_richtext_action = createCheckableAction( - QIcon(":/ico/32x32/simplifyrichtext.png"), + QIcon::fromTheme("simplifyrichtext"), tr("Simplify Rich Text"), editor, &RichTextEditor::setSimplifyRichText, this); m_simplify_richtext_action->setChecked(editor->simplifyRichText()); connect(m_editor, &RichTextEditor::simplifyRichTextChanged, m_simplify_richtext_action, &QAction::setChecked); diff --git a/sources/ui/bomexportdialog.cpp b/sources/ui/bomexportdialog.cpp index e2ea6ce77..da2be53ca 100644 --- a/sources/ui/bomexportdialog.cpp +++ b/sources/ui/bomexportdialog.cpp @@ -26,6 +26,7 @@ #include #include +#include #include /** @@ -44,6 +45,9 @@ BOMExportDialog::BOMExportDialog(QETProject *project, QWidget *parent) : ui->m_main_layout->insertWidget(0, m_query_widget); m_query_widget->setQuery(BomExport::defaultQuery()); on_m_format_as_bom_clicked(false); + + m_preview_model = new QSqlQueryModel(this); + ui->m_preview_table->setModel(m_preview_model); } /** @@ -89,7 +93,15 @@ QByteArray BOMExportDialog::getBom(QString *error) error->clear(); } m_project->dataBase()->updateDB(); - auto query_ = m_project->dataBase()->newQuery(m_query_widget->queryStr()); + QString rejection; + auto query_ = m_project->dataBase()->newQuery(m_query_widget->queryStr(), &rejection); + + if (!rejection.isEmpty()) { + if (error) { + *error = rejection; + } + return {}; + } if (!query_.exec()) { qDebug() << "BOMExportDialog::getBom : query errir : " << query_.lastError(); @@ -130,3 +142,39 @@ void BOMExportDialog::on_m_format_as_bom_clicked(bool checked) { m_query_widget->setGroupBy("designation", checked); m_query_widget->setCount("COUNT(*) AS designation_qty", checked); } + +/** + @brief BOMExportDialog::on_m_preview_pb_clicked + Run the current query and show its result live, without going through + the CSV round-trip -- lets a report be checked and adjusted before + committing to a file (qelectrotech-source-mirror#886). +*/ +void BOMExportDialog::on_m_preview_pb_clicked() +{ + m_project->dataBase()->updateDB(); + QString rejection; + auto query_ = m_project->dataBase()->newQuery(m_query_widget->queryStr(), &rejection); + + if (!rejection.isEmpty()) { + QMessageBox::warning(this, tr("Requête refusée"), rejection); + return; + } + + if (!query_.exec()) { + QMessageBox::warning( + this, tr("Erreur"), + tr("Erreur dans la requête :\n%1").arg(query_.lastError().text())); + return; + } + + m_preview_model->setQuery(std::move(query_)); + for (int i = 0; i < m_preview_model->columnCount(); ++i) + { + const auto field_name = m_preview_model->record().fieldName(i); + const auto translated = QETInformation::translatedInfoKey(field_name); + if (!translated.isEmpty()) { + m_preview_model->setHeaderData(i, Qt::Horizontal, translated); + } + } + ui->m_preview_table->resizeColumnsToContents(); +} diff --git a/sources/ui/bomexportdialog.h b/sources/ui/bomexportdialog.h index 36fa40d15..0c5a0743e 100644 --- a/sources/ui/bomexportdialog.h +++ b/sources/ui/bomexportdialog.h @@ -23,6 +23,7 @@ class QETProject; class ElementQueryWidget; +class QSqlQueryModel; namespace Ui { class BOMExportDialog; @@ -44,11 +45,13 @@ class BOMExportDialog : public QDialog private slots: void on_m_format_as_bom_clicked(bool checked); + void on_m_preview_pb_clicked(); private: Ui::BOMExportDialog *ui; ElementQueryWidget *m_query_widget = nullptr; QETProject *m_project = nullptr; + QSqlQueryModel *m_preview_model = nullptr; }; #endif // BOMEXPORTDIALOG_H diff --git a/sources/ui/bomexportdialog.ui b/sources/ui/bomexportdialog.ui index b9e60b329..04b673f46 100644 --- a/sources/ui/bomexportdialog.ui +++ b/sources/ui/bomexportdialog.ui @@ -7,7 +7,7 @@ 0 0 610 - 232 + 480 @@ -56,6 +56,37 @@ + + + + + + Aperçu + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::NoEditTriggers + + + diff --git a/sources/ui/conductorpropertieswidget.ui b/sources/ui/conductorpropertieswidget.ui index 48ea44ac8..8a31d43a3 100644 --- a/sources/ui/conductorpropertieswidget.ui +++ b/sources/ui/conductorpropertieswidget.ui @@ -233,7 +233,7 @@ - + :/ico/16x16/configure.png:/ico/16x16/configure.png @@ -363,7 +363,7 @@ neutre - + :/ico/16x16/neutral.png:/ico/16x16/neutral.png @@ -377,7 +377,7 @@ terre - + :/ico/16x16/ground.png:/ico/16x16/ground.png @@ -391,7 +391,7 @@ phase - + :/ico/16x16/phase.png:/ico/16x16/phase.png diff --git a/sources/ui/configsaveloaderwidget.ui b/sources/ui/configsaveloaderwidget.ui index f0b69605b..aaa8aa130 100644 --- a/sources/ui/configsaveloaderwidget.ui +++ b/sources/ui/configsaveloaderwidget.ui @@ -29,7 +29,7 @@ - + :/ico/16x16/folder-open.png:/ico/16x16/folder-open.png @@ -40,7 +40,7 @@ - + :/ico/16x16/document-save.png:/ico/16x16/document-save.png diff --git a/sources/ui/dynamicelementtextitemeditor.ui b/sources/ui/dynamicelementtextitemeditor.ui index 66a326d4f..273637c2e 100644 --- a/sources/ui/dynamicelementtextitemeditor.ui +++ b/sources/ui/dynamicelementtextitemeditor.ui @@ -25,7 +25,7 @@ ... - + :/ico/16x16/document-save.png:/ico/16x16/document-save.png @@ -39,7 +39,7 @@ ... - + :/ico/16x16/folder-open.png:/ico/16x16/folder-open.png @@ -66,7 +66,7 @@ ... - + :/ico/22x22/textfield.png:/ico/22x22/textfield.png @@ -80,7 +80,7 @@ ... - + :/ico/16x16/object-group.png:/ico/16x16/object-group.png @@ -94,7 +94,7 @@ ... - + :/ico/16x16/list-remove.png:/ico/16x16/list-remove.png diff --git a/sources/ui/linksingleelementwidget.cpp b/sources/ui/linksingleelementwidget.cpp index e2c3c8b38..322e955bf 100644 --- a/sources/ui/linksingleelementwidget.cpp +++ b/sources/ui/linksingleelementwidget.cpp @@ -25,10 +25,14 @@ #include "../undocommand/linkelementcommand.h" #include "../qetinformation.h" #include "../qetproject.h" +#include "../qetgraphicsitem/masterelement.h" #include "../ui_linksingleelementwidget.h" #include #include +#include +#include +#include /** @@ -52,6 +56,22 @@ LinkSingleElementWidget::LinkSingleElementWidget(Element *elmt, m_show_qtwi = new QAction(tr("Montrer l'élément"), this); m_show_element = new QAction(tr("Montrer l'élément esclave"), this); m_save_header_state = new QAction(tr("Enregistrer la disposition"), this); + + // Hide the full-masters checkbox for non-slave elements + const bool is_slave = (elmt && elmt->elementData().m_type == ElementData::Slave); + ui->m_hide_full_masters_cb->setVisible(is_slave); + + // Restore persisted state before connecting to avoid toggled firing buildTree() + // while m_element is still null. + { + const QSignalBlocker blocker(ui->m_hide_full_masters_cb); + QSettings settings; + ui->m_hide_full_masters_cb->setChecked( + settings.value(QStringLiteral("link-element-widget/hideFullMasters"), false).toBool()); + } + + connect(ui->m_hide_full_masters_cb, &QCheckBox::toggled, + this, &LinkSingleElementWidget::hideFullMastersToggled); connect(m_show_qtwi, &QAction::triggered, this, [=]() { @@ -249,6 +269,7 @@ void LinkSingleElementWidget::buildTree() if (m_element->elementData().m_type == ElementData::Slave) { + m_full_masters.clear(); for(const auto &elmt : elmt_vector) { @@ -288,6 +309,16 @@ void LinkSingleElementWidget::buildTree() QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list); m_qtwi_elmt_hash.insert(qtwi, elmt); m_qtwi_strl_hash.insert(qtwi, search_list); + + // Check if this master is full + if (elmt->linkType() == Element::Master) { + MasterElement *me = qobject_cast(elmt); + if (me && me->isFull()) { + m_full_masters.insert(qtwi); + if (ui->m_hide_full_masters_cb->isChecked()) + qtwi->setHidden(true); + } + } } @@ -455,6 +486,8 @@ void LinkSingleElementWidget::setUpCompleter() */ void LinkSingleElementWidget::clearTreeWidget() { + m_pending_qtwi = nullptr; + while(ui->m_tree_widget->topLevelItemCount()) { QTreeWidgetItem *qtwi = ui->m_tree_widget->takeTopLevelItem(0); @@ -467,6 +500,7 @@ void LinkSingleElementWidget::clearTreeWidget() m_qtwi_elmt_hash.clear(); m_qtwi_strl_hash.clear(); + m_full_masters.clear(); } void LinkSingleElementWidget::setUpHeaderLabels() @@ -675,6 +709,7 @@ void LinkSingleElementWidget::hideButtons() ui->m_show_linked_pb->hide(); ui->m_show_this_pb->hide(); ui->m_search_field->show(); + ui->m_hide_full_masters_cb->show(); } /** @@ -688,6 +723,7 @@ void LinkSingleElementWidget::showButtons() ui->m_show_linked_pb->show(); ui->m_show_this_pb->show(); ui->m_search_field->hide(); + ui->m_hide_full_masters_cb->hide(); } void LinkSingleElementWidget::headerCustomContextMenuRequested( @@ -783,27 +819,35 @@ void LinkSingleElementWidget::on_m_show_this_pb_clicked() If arg1 is empty, show all items. @param arg1 */ -void LinkSingleElementWidget::on_m_search_field_textEdited(const QString &arg1) +void LinkSingleElementWidget::on_m_search_field_textEdited(const QString &) { - //Show all items if arg1 is empty, if not hide all items - foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys()) - qtwi->setHidden(!arg1.isEmpty()); - - QList qtwi_list; - - foreach(QTreeWidgetItem *qtwi, m_qtwi_strl_hash.keys()) + updateItemsVisibility(); +} + +void LinkSingleElementWidget::hideFullMastersToggled(bool checked) +{ + QSettings settings; + settings.setValue(QStringLiteral("link-element-widget/hideFullMasters"), checked); + updateItemsVisibility(); +} + +void LinkSingleElementWidget::updateItemsVisibility() +{ + const QString text = ui->m_search_field->text(); + const bool hide_full = ui->m_hide_full_masters_cb->isChecked(); + + for (auto it = m_qtwi_strl_hash.cbegin(); it != m_qtwi_strl_hash.cend(); ++it) { - foreach(QString str, m_qtwi_strl_hash.value(qtwi)) - { - if(str.contains(arg1, Qt::CaseInsensitive)) - { - qtwi_list << qtwi; - continue; + bool match = text.isEmpty(); + if (!match) { + for (const QString &str : it.value()) { + if (str.contains(text, Qt::CaseInsensitive)) { + match = true; + break; + } } } + it.key()->setHidden(!match + || (hide_full && m_full_masters.contains(it.key()))); } - - //Show items which match with arg1 - foreach(QTreeWidgetItem *qtwi, qtwi_list) - qtwi->setHidden(false); } diff --git a/sources/ui/linksingleelementwidget.h b/sources/ui/linksingleelementwidget.h index d477d107b..b41acd211 100644 --- a/sources/ui/linksingleelementwidget.h +++ b/sources/ui/linksingleelementwidget.h @@ -26,6 +26,7 @@ #include class QTreeWidgetItem; +class QCheckBox; class Element; class QMenu; @@ -71,6 +72,7 @@ class LinkSingleElementWidget : public AbstractElementPropertiesEditorWidget void setUpCompleter(); void clearTreeWidget(); void setUpHeaderLabels(); + void updateItemsVisibility(); private slots: void diagramWasRemovedFromProject(); @@ -87,6 +89,7 @@ class LinkSingleElementWidget : public AbstractElementPropertiesEditorWidget void on_m_show_this_pb_clicked(); void on_m_search_field_textEdited(const QString &arg1); + void hideFullMastersToggled(bool checked); private: Ui::LinkSingleElementWidget *ui; @@ -103,6 +106,8 @@ class LinkSingleElementWidget : public AbstractElementPropertiesEditorWidget Element *m_showed_element = nullptr, *m_element_to_link = nullptr; + QSet m_full_masters; + int m_pending_group_index = -1; QMenu *m_context_menu{nullptr}; diff --git a/sources/ui/linksingleelementwidget.ui b/sources/ui/linksingleelementwidget.ui index b31f7c10d..0488626eb 100644 --- a/sources/ui/linksingleelementwidget.ui +++ b/sources/ui/linksingleelementwidget.ui @@ -21,7 +21,7 @@ - + 5 @@ -57,30 +57,20 @@ - + Recherche - - - + + - Remarque : les éléments maîtres ayant atteint leur nombre maximal d'esclaves sont masqués. - - - true - - - - true - + Masquer les éléments maîtres pleins - diff --git a/sources/ui/masterpropertieswidget.ui b/sources/ui/masterpropertieswidget.ui index 71eb57dd8..910ac27c0 100644 --- a/sources/ui/masterpropertieswidget.ui +++ b/sources/ui/masterpropertieswidget.ui @@ -136,7 +136,7 @@ - + :/ico/16x16/go-up.png:/ico/16x16/go-up.png @@ -150,7 +150,7 @@ - + :/ico/16x16/go-down.png:/ico/16x16/go-down.png diff --git a/sources/ui/thirdpartybinaryinstalldialog.ui b/sources/ui/thirdpartybinaryinstalldialog.ui index 899666d2c..cbd94b056 100644 --- a/sources/ui/thirdpartybinaryinstalldialog.ui +++ b/sources/ui/thirdpartybinaryinstalldialog.ui @@ -33,7 +33,7 @@ Télechargement - + :/ico/16x16/edit-download.png:/ico/16x16/edit-download.png @@ -44,7 +44,7 @@ Dossier installation - + :/ico/16x16/folder-open.png:/ico/16x16/folder-open.png diff --git a/sources/ui/titleblockpropertieswidget.ui b/sources/ui/titleblockpropertieswidget.ui index 2389eabcf..0be2a3623 100644 --- a/sources/ui/titleblockpropertieswidget.ui +++ b/sources/ui/titleblockpropertieswidget.ui @@ -94,7 +94,7 @@ - + :/ico/22x22/titleblock-bottom.png:/ico/22x22/titleblock-bottom.png @@ -103,7 +103,7 @@ - + :/ico/22x22/titleblock-right.png:/ico/22x22/titleblock-right.png @@ -121,7 +121,7 @@ - + :/ico/22x22/edit-rename.png:/ico/22x22/edit-rename.png @@ -210,7 +210,7 @@ - + :/ico/16x16/configure.png:/ico/16x16/configure.png @@ -305,7 +305,7 @@ - + :/ico/22x22/start.png:/ico/22x22/start.png diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 855a9acd2..638d3961a 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -82,12 +82,28 @@ target_link_libraries(tst_contactusage PRIVATE Qt::Test) add_executable( tst_qetpalette tst_qetpalette.cpp + inkcontrast.h ${QET_DIR}/sources/qetpalette.cpp ${QET_DIR}/sources/qetpalette.h) add_test(NAME tst_qetpalette COMMAND tst_qetpalette) target_include_directories(tst_qetpalette PRIVATE ${QET_DIR}/sources) target_link_libraries(tst_qetpalette PRIVATE Qt::Test Qt::Widgets) +# qeticons.cpp is compiled in so the test can check the table entries +# themselves (icon sizes in the element panel), not only the theme files. +add_executable( + tst_qeticons + tst_qeticons.cpp + inkcontrast.h + ${QET_DIR}/sources/qeticons.cpp + ${QET_DIR}/sources/qeticons.h + ${QET_DIR}/sources/qetpalette.cpp + ${QET_DIR}/sources/qetpalette.h + ${QET_DIR}/ico/icon-themes.qrc) +add_test(NAME tst_qeticons COMMAND tst_qeticons) +target_include_directories(tst_qeticons PRIVATE ${QET_DIR}/sources) +target_link_libraries(tst_qeticons PRIVATE Qt::Test Qt::Widgets Qt::Svg) + add_executable( tst_smart_device tst_smart_device.cpp diff --git a/tests/qttest/inkcontrast.h b/tests/qttest/inkcontrast.h new file mode 100644 index 000000000..282a4db2e --- /dev/null +++ b/tests/qttest/inkcontrast.h @@ -0,0 +1,77 @@ +/* + 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 . +*/ +#ifndef QET_TEST_INKCONTRAST_H +#define QET_TEST_INKCONTRAST_H + +#include +#include +#include +#include +#include +#include + +#include "qetpalette.h" + +/** + Shared helpers for the rendering tests: paint a widget on the offscreen + platform and measure the ink against the background. +*/ +namespace QET { + namespace Test { + /** + Grab a widget for measuring. With QET_TEST_DUMP_DIR set, the + image is also written there as -.png so a failure + can be looked at. + */ + inline QImage grab(QWidget *widget, const char *name) + { + const QImage image = widget->grab().toImage(); + const QByteArray dir = qgetenv("QET_TEST_DUMP_DIR"); + if (!dir.isEmpty()) + image.save(QString("%1/%2-%3.png").arg(QString::fromLocal8Bit(dir), + QTest::currentDataTag(), name)); + return image; + } + + /** + Contrast between the background of a rendered widget (its most + frequent color) and the ink drawn on it (the pixel whose + luminance differs most from the background), inside rect. + */ + inline double inkContrast(const QImage &image, const QRect &rect) + { + QHash histogram; + for (int y = rect.top(); y <= rect.bottom(); ++y) + for (int x = rect.left(); x <= rect.right(); ++x) + ++histogram[image.pixel(x, y)]; + + QRgb background = 0; + int best = -1; + for (auto it = histogram.cbegin(); it != histogram.cend(); ++it) + if (it.value() > best) { best = it.value(); background = it.key(); } + + double contrast = 1.0; + for (auto it = histogram.cbegin(); it != histogram.cend(); ++it) + contrast = qMax(contrast, QET::Palette::contrastRatio(QColor(background), + QColor(it.key()))); + return contrast; + } + } +} + +#endif diff --git a/tests/qttest/tst_qeticons.cpp b/tests/qttest/tst_qeticons.cpp new file mode 100644 index 000000000..af5966d0d --- /dev/null +++ b/tests/qttest/tst_qeticons.cpp @@ -0,0 +1,166 @@ +/* + 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 . +*/ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "inkcontrast.h" +#include "qeticons.h" +#include "qetpalette.h" + +/** + Checks on QET's icon theme "qet", generated by misc/make_icon_themes.py + into ico/icon-themes.qrc, and on the icon table that uses it. + + Every icon name must resolve. A toolbar button painted with Fusion must + show its icon at 3:1 (WCAG 1.4.11), with the disabled state reading + weaker than the enabled one. And the icons the elements panel draws in + its 50 px slots must stay small (GitHub #870). +*/ +class tst_qeticons : public QObject +{ + Q_OBJECT + + private slots: + void initTestCase(); + void everyIconResolves(); + void toolbarIconIsReadable_data(); + void toolbarIconIsReadable(); + void panelProjectIconStaysSmall(); +}; + +namespace { + const double kIconRatio = 3.0; + + QStringList iconNames(const QString &theme) + { + QStringList names; + for (const QString &size : {"16x16", "22x22", "32x32", "48x48", "128x128", "scalable"}) + { + QDir dir(QString(":/ico/themes/%1/%2").arg(theme, size)); + for (const QString &file : dir.entryList(QDir::Files)) + names << file.section('.', 0, -2); + } + names.removeDuplicates(); + names.sort(); + return names; + } +} + +void tst_qeticons::initTestCase() +{ + QStringList paths = QIcon::themeSearchPaths(); + paths.prepend(QStringLiteral(":/ico/themes")); + QIcon::setThemeSearchPaths(paths); + QIcon::setThemeName(QStringLiteral("qet")); + QVERIFY2(!iconNames("qet").isEmpty(), "theme has no icons: is ico/icon-themes.qrc compiled in?"); +} + +void tst_qeticons::everyIconResolves() +{ + QVERIFY(QIcon::hasThemeIcon("list-add")); + for (const QString &name : iconNames("qet")) + QVERIFY2(!QIcon::fromTheme(name).isNull(), qPrintable(QString("%1 missing").arg(name))); + // Names the icon table relies on that come from the alias list in + // misc/make_icon_themes.py, not from a file of that name at 22 pixels. + for (const QString &name : {"folio-new", "folio-delete", "folio-properties", "conductor-reset"}) + QVERIFY2(!QIcon::fromTheme(name).pixmap(22).isNull(), qPrintable(name)); +} + +void tst_qeticons::toolbarIconIsReadable_data() +{ + QTest::addColumn("palette"); + QTest::newRow("light") << QET::Palette::fusionLight(); +} + +/** + A line-art icon on a Fusion tool button, as in the diagram editor + toolbar. Enabled must reach 3:1; disabled must be weaker than enabled. +*/ +void tst_qeticons::toolbarIconIsReadable() +{ + QFETCH(QPalette, palette); + QApplication::setStyle(QStyleFactory::create("Fusion")); + QApplication::setPalette(palette); + + QMainWindow window; + QToolBar *toolbar = window.addToolBar("view"); + auto make = [&](const char *name, bool enabled) { + auto *button = new QToolButton; + button->setIcon(QIcon::fromTheme("zoom-fit-best")); + button->setAutoRaise(true); + button->setEnabled(enabled); + button->setObjectName(name); + toolbar->addWidget(button); + return button; + }; + QToolButton *enabled = make("enabled", true); + QToolButton *disabled = make("disabled", false); + window.resize(300, 100); + window.show(); + QVERIFY(QTest::qWaitForWindowExposed(&window)); + + const double enabled_contrast = QET::Test::inkContrast(QET::Test::grab(enabled, "enabled"), enabled->rect()); + const double disabled_contrast = QET::Test::inkContrast(QET::Test::grab(disabled, "disabled"), disabled->rect()); + QVERIFY2(enabled_contrast >= kIconRatio, + qPrintable(QString("enabled icon: %1").arg(enabled_contrast))); + QVERIFY2(disabled_contrast < enabled_contrast, + qPrintable(QString("disabled %1 reads better than enabled %2") + .arg(disabled_contrast).arg(enabled_contrast))); +} + +/** + The elements panel sets a 50 px icon size so element previews are + large. Its project root item uses ProjectFileGP, whose theme name also + has a 128 px file for the configuration dialog. On a 2x display the + theme loader would pick that file for a 50 px request and fill the + slot, so the entry must not resolve through the theme name. +*/ +void tst_qeticons::panelProjectIconStaysSmall() +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QSKIP("QIcon::pixmap(size, devicePixelRatio) needs Qt 6"); +#else + QET::Icons::initIcons(); + + const QPixmap panel = QET::Icons::ProjectFileGP.pixmap(QSize(50, 50), 2.0); + QVERIFY(!panel.isNull()); + const int logical = qRound(panel.width() / panel.devicePixelRatio()); + QVERIFY2(logical <= 22, qPrintable(QString("project root icon is %1 px in a 50 px slot").arg(logical))); + + // The configuration dialog's page list still gets the large file. + QCOMPARE(QET::Icons::Projects.pixmap(QSize(128, 128), 1.0).width(), 128); +#endif +} + +int main(int argc, char **argv) +{ + if (qEnvironmentVariableIsEmpty("QT_QPA_PLATFORM")) + qputenv("QT_QPA_PLATFORM", "offscreen"); + QApplication app(argc, argv); + tst_qeticons test; + QTEST_SET_MAIN_SOURCE_PATH + return QTest::qExec(&test, argc, argv); +} + +#include "tst_qeticons.moc" diff --git a/tests/qttest/tst_qetpalette.cpp b/tests/qttest/tst_qetpalette.cpp index 7cc589bdd..ccbe07f15 100644 --- a/tests/qttest/tst_qetpalette.cpp +++ b/tests/qttest/tst_qetpalette.cpp @@ -28,9 +28,12 @@ #include #include +#include "inkcontrast.h" #include "qetpalette.h" using QET::Palette::contrastRatio; +using QET::Test::grab; +using QET::Test::inkContrast; /** Contrast checks for the palettes QET installs under the Fusion style. @@ -94,44 +97,6 @@ namespace { {"Link/Window", QPalette::Link, QPalette::Window}, }; - /** - Grab a widget for measuring. With QET_TEST_DUMP_DIR set, the image - is also written there as -.png so a failure can be - looked at. - */ - QImage grab(QWidget *widget, const char *name) - { - const QImage image = widget->grab().toImage(); - const QByteArray dir = qgetenv("QET_TEST_DUMP_DIR"); - if (!dir.isEmpty()) - image.save(QString("%1/%2-%3.png").arg(QString::fromLocal8Bit(dir), - QTest::currentDataTag(), name)); - return image; - } - - /** - Contrast between the background of a rendered widget (its most - frequent color) and the ink drawn on it (the pixel whose luminance - differs most from the background), inside rect. - */ - double inkContrast(const QImage &image, const QRect &rect) - { - QHash histogram; - for (int y = rect.top(); y <= rect.bottom(); ++y) - for (int x = rect.left(); x <= rect.right(); ++x) - ++histogram[image.pixel(x, y)]; - - QRgb background = 0; - int best = -1; - for (auto it = histogram.cbegin(); it != histogram.cend(); ++it) - if (it.value() > best) { best = it.value(); background = it.key(); } - - double contrast = 1.0; - for (auto it = histogram.cbegin(); it != histogram.cend(); ++it) - contrast = qMax(contrast, contrastRatio(QColor(background), - QColor(it.key()))); - return contrast; - } } void tst_qetpalette::addPaletteRows()