Compare commits

...

185 Commits

Author SHA1 Message Date
Laurent Trinques c256e2dd1a Merge pull request #958 from bhangart/fix/pin-fetchcontent-dependencies
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m37s
Fix/pin fetchcontent dependencies
2026-09-20 20:13:37 +02:00
Laurent Trinques 8e3a7a6ab6 Merge pull request #927 from ispyisail/fix/923-snap-element-text
Snap a device's text to the grid when it is dragged (#923)
2026-09-20 20:10:13 +02:00
Laurent Trinques 740271c993 Merge pull request #938 from arummler/fix-translation-syntax
Fix various singular/plural cosntructions
2026-09-20 20:08:53 +02:00
Laurent Trinques 1bec2700bd Merge pull request #924 from Kellermorph/auto-numbering-new-project
Add global auto-numbering rules to QElectroTech settings
2026-09-20 20:07:16 +02:00
Laurent Trinques 268e9c7bd9 Merge pull request #959 from jp2images/fix-dark-canvas-item-updates
Keep the dark canvas on QGraphicsView's own update path
2026-09-20 20:02:50 +02:00
Laurent Trinques 264a0f677f Merge pull request #964 from jp2images/remove-stray-sbom-files
Remove two files that slipped into #944
2026-09-20 20:02:27 +02:00
Laurent Trinques 9ccfb5e116 Merge pull request #963 from jp2images/fix-hover-tint-light-face
Darken the hover ink on a light button face
2026-09-20 19:45:28 +02:00
Jeff Patterson 04463f0954 Darken the hover ink on a light button face
QETStyle::hoverColor() lightened the highlight color until it read at
3.5:1 against the Light role. On a dark face that is the way to go; on a
light face lightening only fades the ink, so with a pale platform accent
that QET keeps (macOS's green selection color, black selection text) the
loop ran to white and every hovered line-art icon vanished. The ink now
moves away from the face, darker on a light face, lighter on a dark one,
and falls back to the button text color if twenty steps are not enough.

The hover test gets a row with that accent on each palette, and a new
test sweeps accents across hues and lightness on both palettes and
requires the hover ink to read at 3:1 on the face.

Fixes #962
2026-09-20 12:45:07 -05:00
Laurent Trinques 3245919c19 Merge pull request #961 from jp2images/fix-small-page-icons
Give every configuration page an icon at page size
2026-09-20 19:44:00 +02:00
Jeff Patterson 55ad2eccbf Remove two files that slipped into #944
sources/qetsbom.cpp and sources/qetsbom.h were untracked local files
that a directory-wide add swept into the rebuilt #944 commit. Nothing
references them; the build does not compile them.
2026-09-20 12:43:57 -05:00
Jeff Patterson 8b2548d601 Keep the dark canvas on QGraphicsView's own update path
Laurent found that moving an element on a #954 build left its terminals'
help lines behind at every step, on both palettes. The view listened to
QGraphicsScene::changed() so that render() would keep the scene's updates
flowing, and any receiver on that signal puts the scene on its Qt 4.4
compatibility path, which erases a moved item's own old rect only:
children bigger than their parent stay on screen. Master hides that with
FullViewportUpdate, which repaints the whole viewport on every change.

Paint the inverted folio through QGraphicsView::paintEvent() instead, with
IndirectPainting set for that call and the draw hooks painting into a
viewport-sized image, and hand the scene the viewport when the items are
drawn so it records where each item was painted, including a child whose
geometry is set while its parent paints. The listener and the
full-viewport update go. Two tests move a parent with a sheet-wide child,
read the backing store, and require the repaint to be a partial one.
2026-09-20 12:43:50 -05:00
Laurent Trinques c3aeb0bf83 Merge pull request #944 from jp2images/fix-stylesheet-palette-switch
Refresh style-sheet widgets after a live palette switch
2026-09-20 19:36:26 +02:00
Jeff Patterson 16d89f64fd Refresh style-sheet widgets after a live palette switch
Switching the system between light and dark while QET runs changed
the palette of every plain widget but left widgets that carry a style
sheet in the colors they were created with: the folio tab bar stayed
light in dark mode, and after a dark-to-light switch its Add folio and
chevron buttons hovered as a near-black box with the icon lost inside
it. QApplication::setPalette() does not reach a widget with a style
sheet; QStyleSheetStyle resolved its palette once, when the sheet was
applied, and keeps it. Seventeen call sites set a sheet on a widget and
five .ui files carry one, so any of them could show the stale palette.

QET::Palette::refreshStyleSheets() re-applies each such widget's own
sheet, which makes QStyleSheetStyle resolve it against the palette now
in force. QETApp::useSystemPalette() calls it after installing the
palette, so both the OS color scheme change and the "use system
colors" setting are covered.

tests/qttest/tst_qetpalette: a tab widget with the folio tab bar's
sheet is still drawn in the old colors after setPalette(), which is the
defect, and follows the palette after refreshStyleSheets(), in both
directions.

Fixes #943.
2026-09-20 12:20:01 -05:00
Jeff Patterson ac78c51b70 Give every configuration page an icon at page size
The settings and project dialogs list their pages with 64 or 128 pixel
icons, and two pages had theirs at 22 pixels only: the terminal-strip
page and the shortcuts page, which borrowed configure-toolbars. Both get
a 128 pixel icon drawn in the style of the other page icons, the
shortcuts page under its own name, configure-shortcuts. The SVG sources
sit beside the PNGs.

On a dark palette the Printing and Export pages were small too: their
128 pixel icons exist in the light theme only, and Qt inherits by name,
not by size, so the dark theme's small copies were scaled up instead.
make_icon_themes.py now aliases the light files of the sizes a dark name
lacks, when they read on the dark window at 3:1.

A test asks the theme for every page icon at 128 pixels, on both
palettes.

Fixes #960
2026-09-20 10:44:09 -05:00
Beat Hangartner 2e27f77b28 Added more verbose description of the difference between lightweight and annotated tags as comment. 2026-09-20 14:21:36 +02:00
Laurent Trinques 83fb525e27 Merge pull request #939 from elevatormind/backtrace-windows-disable
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m39s
Fix issue #937
2026-09-20 14:03:43 +02:00
Laurent Trinques d1808c7a89 Fix Draw the folio with inverted lightness on a dark palette- #954 2026-09-20 13:07:57 +02:00
Laurent Trinques 4e4f029d2d Merge pull request #957 from qelectrotech/revert-955-revert-954-feature-dark-canvas
Revert "Revert "Draw the folio with inverted lightness on a dark palette""
2026-09-20 13:05:38 +02:00
Laurent Trinques 242f134e0f Revert "Revert "Draw the folio with inverted lightness on a dark palette"" 2026-09-20 13:05:13 +02:00
Magnus Hellströmer 2dc88df29c feat(build): Enable backtrace detection on Windows 2026-09-20 12:48:10 +02:00
Magnus Hellströmer fd8135264f fix(build): Remove stale Windows backtrace comments 2026-09-20 12:48:10 +02:00
Magnus Hellströmer 827cd2a91e fix(build): support backtrace in MSYS2 2026-09-20 12:48:10 +02:00
Magnus Hellströmer cdd25189b5 fix(build): guard backtrace detection on Windows 2026-09-20 12:48:04 +02:00
Laurent Trinques f1313f5895 Merge pull request #955 from qelectrotech/revert-954-feature-dark-canvas
Revert "Draw the folio with inverted lightness on a dark palette"
2026-09-20 07:20:03 +02:00
Laurent Trinques 75fafd5acc Revert "Draw the folio with inverted lightness on a dark palette" 2026-09-20 06:57:56 +02:00
Laurent Trinques 3394c1246c Merge pull request #954 from jp2images/feature-dark-canvas
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m12s
Draw the folio with inverted lightness on a dark palette
2026-09-20 06:44:09 +02:00
Jeff Patterson 3e34a6d55f Fill the dark canvas buffer and keep one scene connection
QGraphicsView::render() paints only what the scene draws, so a scene
without a background brush left the off-screen buffer uninitialized
and the inversion turned that memory into noise. The buffer is now
filled white first, which the inversion turns into the Base color.

listenToScene() connected a new receiver on every setScene() call and
never dropped the previous scene's. It now keeps a single connection
and replaces it.

Test in tst_qetpalette: paletteViewFillsWhatTheSceneLeavesBlank.
2026-09-19 18:27:00 -05:00
Jeff Patterson 50792ba1ad Repaint the whole dark canvas when the application palette changes
After a live light/dark switch only the sheet changed colors; the
viewport around it kept the previous palette. Qt repaints a widget on
an application palette change only when the widget's own palette
changed with it, and under the folio tab widget's style sheet it does
not, so the only repaints came from the scene and covered the scene
rectangle alone. PaletteGraphicsView now watches the application
object for ApplicationPaletteChange, the one receiver Qt always
notifies, and repaints its whole viewport.

The test paletteViewFollowsTheApplicationUnderAStyleSheet now also
requires a full-viewport repaint after each switch, before anything
asks the view for a rendering.
2026-09-19 18:27:00 -05:00
Jeff Patterson 6404612014 Follow the application palette, not the view's, on the dark canvas
The folio tab widget carries a style sheet, and QStyleSheetStyle pins
the palette of every widget under it to the application palette in
force when the sheet was applied. After a live light/dark switch the
view's own palette() is therefore stale: the folio kept its dark sheet
after a switch to light, and kept its white sheet after a switch to
dark. PaletteGraphicsView now reads the application palette both for
the decision to invert and for the sheet and ink colors.

Test in tst_qetpalette: paletteViewFollowsTheApplicationUnderAStyleSheet
puts the view in a tab widget with a style sheet and switches the
application palette to dark and back.
2026-09-19 18:27:00 -05:00
Jeff Patterson eaf15faaa3 Move the dark canvas into PaletteGraphicsView and test it directly
The inverted painting, the rubber band replay and the changed()
receiver lived in DiagramView, which the unit tests cannot link, so the
update-flag regression was only covered through a stand-in view. They
now live in PaletteGraphicsView, a QGraphicsView subclass with no
other dependency, and DiagramView derives from it. The view tells a
subclass through paintingInverted(bool) when it renders for an
inverted display; DiagramView forwards that to the diagram. The grid
dot rule moves out of Diagram::drawBackground into
QET::Palette::gridDotColor().

tst_qetpalette now links the real class: gridDotColorSoftensInvertedDots,
paletteViewFollowsThePalette (light sheet, dark sheet at text contrast
with a red box still red and the paintingInverted calls in order, back
to light), paletteViewKeepsSceneUpdatesFlowing (three whole-scene
updates and a selection each repaint, scene set after construction),
paletteViewDrawsTheRubberBand.
2026-09-19 18:27:00 -05:00
Jeff Patterson b8c9e670c4 Keep scene updates flowing on the dark canvas and soften its grid
On a dark palette the folio view paints through QGraphicsView::render()
instead of onto its viewport. In that case QGraphicsView never clears
the scene's "update everything" flag, and while the flag is set every
further QGraphicsScene::update() and item update is dropped: from the
second Diagram::update() on, the grid toggle, the white/gray toggle and
even a selection waited for an unrelated repaint. With a receiver on
QGraphicsScene::changed() the scene clears the flag before it emits, so
DiagramView now connects an empty receiver in its constructor.

While the view paints for inversion, Diagram draws the grid dots a
third of the way from the sheet color to black, so they come out as a
soft gray on the dark sheet instead of as bright as the ink. Printing
and export never take that path.

Test in tst_qetpalette: sceneUpdatesReachARenderedView.
2026-09-19 18:26:45 -05:00
Jeff Patterson 85dc638a42 Draw the folio with inverted lightness on a dark palette
On a dark palette the folio stayed a white sheet with black ink, and
the white/gray toggle only darkened the sheet while the ink stayed
black. DiagramView now renders each repaint into an image and inverts
its lightness before blitting it: white becomes the palette's Base,
black becomes its Text, and colored conductors and elements keep their
hue. The document, printing and export are untouched; only the screen
rendering changes, and only while the palette is dark.

QET::Palette::invertLightness does the inversion in one integer pass
(adding 255 - max - min to the three channels inverts the HSL lightness
and keeps hue and saturation), then stretches the result between the
sheet and ink colors through three lookup tables. A 4K viewport costs
about 9 ms in a release build. QGraphicsView::render() skips the
selection rubber band, so the view draws it again after the inversion.

Tests in tst_qetpalette: invertLightnessMapsSheetAndInk,
invertedViewReadsOnDarkSheet, invertLightnessSpeed.
2026-09-19 18:26:45 -05:00
Laurent Trinques dfe56cddbd Merge pull request #928 from ispyisail/fix/903-escape-cancels-text-tool
Let Escape cancel the text tool, like every other placement (#903)
2026-09-19 23:43:31 +02:00
Beat Hangartner 3f397f5f78 Explain why fetching by git tag is a supply chain risk
The pinning comment stated that a tag is mutable but not what an attacker
does with that, so the trade-off was hard to judge for anyone reviewing or
later undoing the pins. Spell out the mechanism: a tag is a name pointing
at a commit, anyone with push access upstream can force-push it elsewhere,
and FetchContent resolves it at build time, so a stolen maintainer account
or CI token makes every fresh build compile the attacker's code while
nothing changes here and the tag name still reads correctly. A commit hash
is derived from the content and cannot be moved that way.

Name the two cases where this was actually exploited: tj-actions/changed-
files in March 2025 (CVE-2025-30066), where tags v1 through v45.0.7 were
retargeted to a commit leaking CI secrets into build logs across more than
23,000 repositories, and aquasecurity/trivy-action in March 2026
(CVE-2026-33634), where 76 of 77 version tags were force-pushed to a
credential stealer for about twelve hours. Both were GitHub Actions rather
than CMake dependencies, which the comment says, because the point is the
shared mechanism of resolving a tag at build time.

Also document how to upgrade a pin, including that git ls-remote reports
the tag object for an annotated tag and the commit on the "^{}" line.

The note lives in fetch_pugixml.cmake, which fetch_kdeaddons.cmake and
fetch_singleapplication.cmake already refer to. Comments only; no build
behaviour changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 23:36:20 +02:00
Beat Hangartner 7ec13cbc1a Pin fetched dependencies to commit hashes instead of git tags
CMake fetches pugixml, SingleApplication and the three KDE Frameworks
modules by git tag. A tag is a mutable pointer that its owner can move,
so two builds of the same QElectroTech commit can silently get different
third-party sources, and a compromised upstream account can change what
every builder downloads without anything changing in this repository.
Pinning each dependency to the commit its tag currently points at closes
that, while keeping the tag name in a trailing comment so the intended
version stays readable.

No versions change. Every pinned commit is the one its tag resolves to,
checked with git ls-remote and confirmed by fetching each one and
verifying that git describe reports exactly the tag. The three KDE
modules live in separate repositories and therefore need separate
commits, so the single KF_GIT_TAG variable becomes three per-module
variables; passing -DKF_GIT_TAG=<ref> still selects one ref for all
three, unpinned, exactly as before, and KF_GIT_TAG stays defined so the
build summary in define_definitions.cmake is unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-19 23:05:39 +02:00
Laurent Trinques ef1fbd79b4 Merge pull request #942 from elevatormind/gitignore-ide-addition
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m1s
chore: ignore IDE settings and cache files
2026-09-19 20:25:12 +02:00
Laurent Trinques eb7abdfbc6 Merge pull request #946 from jp2images/feature-panels-follow-palette
Let the Collections and Projects panels follow the palette
2026-09-19 20:24:05 +02:00
Laurent Trinques f574d28340 Merge pull request #947 from jp2images/fix-dark-theme-light-icons
Redraw the Add PDF and folio icons as SVGs, stop inverting page-shaped icons in the dark theme
2026-09-19 20:22:29 +02:00
Laurent Trinques 31f90e2c46 Merge pull request #949 from jp2images/fix-menubar-test-macos
Make the menu bar keyboard test pass on macOS
2026-09-19 20:20:58 +02:00
Laurent Trinques 45bd8256b3 Merge pull request #950 from jp2images/feature-icon-hover
Give icons a hover state through a proxy style
2026-09-19 20:18:20 +02:00
Jeff Patterson 4e43cde063 Give icons a hover state through a proxy style
Hovering a tool button changed only its frame, and on the dark palette
Fusion's hover frame is too faint to notice, so nothing told the user
which button was under the pointer (GitHub #870, PR 3 of the plan).

QETStyle wraps the running style. For QIcon::Active it returns line-art
icons tinted in the palette's highlight color, lightened until the tint
reads at 3:1 on the Light role, which is the top of Fusion's hover
gradient. Colored icons keep their colors, using the same line-art rule
as misc/make_icon_themes.py. Every other mode goes to the base style.

Fusion also asks for QIcon::Active for the icon of a highlighted menu
item and paints it on the highlight bar, where the tint would vanish.
The generated pixmap is cached per icon and cannot tell a menu from a
toolbar, so drawControl(CE_MenuItem) hands the base style an icon whose
Active pixmap is in the HighlightedText color instead.

QETApp::initStyle() installs the proxy on every platform, keeping the
base style's object name so the Fusion checks still match.

tests/qttest/tst_qeticons: hovering changes the icon ink to the tint and
stays at 3:1 on both palettes, on a raised button and on a checked one
drawn sunken, where the frame gives no hover cue at all; a colored icon is unchanged on hover; a
highlighted menu item's icon reads at 3:1 on the highlight bar.
inkcontrast.h gains background() and ink() helpers for those checks.
2026-09-19 12:26:06 -05:00
Jeff Patterson aef56fe41d Make the menu bar keyboard test pass on macOS
tst_menubarkeyboard's control case presses Alt+F and expects the File
menu to open. On macOS it never did, for two reasons unrelated to the
F10 shortcut it guards: a QMenuBar is native there, so its menus live
in the system menu bar where QTest key events do not reach them, and Qt
does not turn "&File" into an Alt+F mnemonic on macOS at all. The test
has failed on every Mac build since bd6bed8d6 added it.

On macOS the test now uses an in-window menu bar and switches auto
mnemonics on for its own process, which runs the same QMenuBar code the
other platforms exercise. What it still does not prove on macOS is the
native bar: QETMainWindow::activateMenuBar() calls setActiveAction() on
a bar the system draws, and only F10 in the running application can
say what that does there. Other platforms are unchanged.

Fixes #948.
2026-09-19 12:24:57 -05:00
Jeff Patterson 0395f90f75 Let the Collections and Projects panels follow the palette
Both panels forced a light palette on themselves (white rows, black
text, their own selection blue) so that element previews, which are
black line art drawn for the white sheet, would stay visible on a dark
desktop (bugtracker 335). On a dark palette the two docks were the
only white windows left.

The forced palettes are gone. Element previews are now kept as drawn,
on a transparent background, and adapted where they are shown:
ElementPreviewDelegate, installed on the collection tree, hands the
view a copy with its lightness inverted when the palette is dark
(QET::Palette::forPalette), so black ink becomes the palette's light
gray while colored icons such as folders stay as they are; the drag
pixmap is adapted the same way. A light palette shows the previews
untouched. This fixes bugtracker 335 on every dark desktop rather than
masking it with a white panel.

The preview cache stored the old white-sheet pictures; it records the
format now and drops a cache written before this change once. The
amber "show this directory" highlight sets black text so it reads on
both palettes. The Projects panel only shows icons from the icon
theme, which has a dark variant, so nothing else changes there.

tests/qttest/tst_qetpalette: the line-art rule tells ink from color;
inversion keeps hue and alpha; a preview reads at 3:1 on the Base
color of both palettes; in a tree on the dark palette the delegate
inverts a line-art icon and leaves a colored one alone.

Fixes #945.
2026-09-19 12:22:24 -05:00
Jeff Patterson a4d5b7a12a Draw the folio icons as SVGs
The folio icons (Add, Remove, Properties, New folio, Title block
template) were anti-aliased gray page drawings, and the previous commit
left them untouched on the dark palette, where their soft gray fills
read blurry next to the line-art icons. They are now pixel-grid SVGs in
ico/scalable/ in the style of the Add PDF icon: a landscape sheet with
a title block line, a plus or minus badge in the corner, text lines for
properties, a filled title block for the template. Same 24 pixel canvas
as pdf-import.svg, same currentColor recoloring for the dark theme.

Only the 22 pixel PNGs go: five files leave ico/22x22 and both .qrc
files, and the alias list in misc/make_icon_themes.py that exposed
three of them under a second name is down to conductor2.png. The 16
pixel files stay, so menus and the projects panel keep their icons at
that size, and the 128 pixel diagram.png stays for the configuration
page list.

tests/qttest/tst_qeticons: every file in ico/scalable/ resolves in both
themes at 22, 24, 32 and 64 pixels, dark ink on light and light ink on
dark, with no 22 pixel PNG left beside it; the light-art check reads
the folio family at 16 pixels, where the page art remains.
2026-09-19 12:02:55 -05:00
Jeff Patterson ba29dc45a0 Draw the Add PDF icon as an SVG
The "Add PDF" action used ico/22x22/pdf-import.png, a white page with a
red PDF mark that sat apart from its neighbors "Add text" and "Add
image", both gray line art in a square frame with a plus. It is now
ico/scalable/pdf-import.svg, the same pixel design as insert-image.png:
a frame, the letters PDF, a plus in the corner. One file serves every
slot and stays sharp on high-DPI screens; the PNG is gone from both
.qrc files.

The canvas is 24 pixels with the art offset by one, like the Breeze
SVGs already in the theme. Fusion's toolbar slot is 24 pixels: a 22
pixel PNG is drawn unscaled inside it, but a scalable icon is rendered
at the slot size, and a 22 pixel grid stretched to 24 puts every one
pixel line between pixels and reads blurry.

The file uses currentColor like the Breeze SVGs already in the theme,
so misc/make_icon_themes.py produces the dark copy the same way. A new
ico/scalable/ folder holds QET's own vector icons; the direction
arummler asked for in #690.

tests/qttest/tst_qeticons: the icon resolves in both themes at 16, 22,
32 and 64 pixels, dark ink on the light theme and light ink on the dark
one, and no 22 pixel PNG remains.
2026-09-19 12:02:54 -05:00
Jeff Patterson d06c7be606 Leave light icons out of the dark theme
misc/make_icon_themes.py sorted icons by saturation alone, so a white
page with a small red mark counted as line art and its dark copy turned
the page black: the PDF import icon read black on black (#919,
Kellermorph), and the folio, diagram and label icons came out as dark
pages with a light border.

An icon whose visible pixels are at least 30% near white is now "light
art" and inherits from the qet theme untouched; it already reads on a
dark toolbar. The generator also removes dark files it no longer
produces, so a reclassified icon falls back to the light theme instead
of keeping a stale copy. Thirteen files leave ico/themes/qet-dark.

tests/qttest/tst_qeticons: every dark theme file, taken as its mean
visible color, reaches 3:1 on the dark palette's window color; the
lightest-pixel check it replaces let a black page with a light border
through. Asking the dark theme for pdf-import, diagram, label, the
folio icons and diagram_bg returns the light art.
2026-09-19 12:02:54 -05:00
Magnus Hellströmer 56cbd6e23d chore: ignore IDE settings and cache files 2026-09-19 18:10:37 +02:00
Laurent Trinques 8bbc2da5c7 Merge pull request #940 from Kellermorph/german-translation
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m17s
Update German translation
2026-09-19 14:11:15 +02:00
Kellermorph 7fa1ff35b8 Add global auto-numbering rules to QElectroTech settings
Add a 'Numérotation auto' tab to the global settings page (Settings >
Nouveau projet) where users can define default auto-numbering rules
for Conducteurs, Eléments, and Folios. These rules are automatically
transferred to every new project created.

Changes:
- Add NumerotationContext::saveToSettings()/loadFromSettings() static
  helpers for persisting named numerotation contexts via QSettings
- Add 'Numérotation auto' tab to NewDiagramPage with three sub-tabs
  using SelectAutonumW widgets (same UI as project properties)
- Add save/remove/persist slots for conductor, element, and folio
  contexts with immediate QSettings persistence on every change
- NewDiagramPage::applyConf() saves autonum settings when editing
  global defaults (no project)
- QETProject constructor loads global autonum settings from QSettings
  for new empty projects
2026-09-19 13:47:28 +02:00
Kellermorph 4b8db6be0e Update German translation 2026-09-19 13:25:39 +02:00
Laurent Trinques a6b4c3c673 Merge pull request #930 from ispyisail/feat/923-forum-auto-conductor-shortcut
List auto conductor creation in the Shortcuts page
2026-09-19 13:04:44 +02:00
Laurent Trinques 8f0ee06a4f Merge pull request #929 from ispyisail/feat/461-conductor-color-quick-access
Add a one-click conductor colour to the toolbar (#461)
2026-09-19 13:00:14 +02:00
Andre Rummler 7e3420b1a5 Avoid constructing sentences. 2026-09-19 10:39:54 +02:00
Andre Rummler 183035d5ac Replace enumerator list. 2026-09-19 09:15:28 +02:00
Andre Rummler eb4109bb11 Replace manual enumerator sentence with qt trasnlator automatism. 2026-09-19 09:08:25 +02:00
Andre Rummler 717bf57677 Improve plural marking from previous commit. 2026-09-19 09:08:00 +02:00
Andre Rummler 861e5de25e Use qt own enumeration translation function. Fix plural forms. 2026-09-19 08:31:34 +02:00
ispyisail 70599c4ae1 List auto conductor creation in the Shortcuts page
"Est il possible dans les raccourcis d'ajouter un pour création
automatique de conducteur ? Je n'utilise pas par défaut, mais
ponctuellement c'est très pratique." -- oc67, an electrician, on the
forum (viewtopic.php?pid=23296).

The action itself has existed for a long time: m_auto_conductor is a
checkable QAction in the Schéma toolbar and the Project menu. It was
simply never handed to ShortcutManager, so it did not appear in
Configuration > Raccourcis and there was no way to reach it from the
keyboard. This registers it.

No default sequence is set. That is the request read literally -- he
asked for it to be *in* the shortcuts list so he can bind it himself --
and it avoids spending one of the few free keys on a setting many people
never touch. The Shortcuts page already treats "no shortcut" as a normal
state: it renders an empty field and its quick filter can list actions
with and without a binding separately.

Verified on a virtual display. With shortcuts/diagrameditor.auto_conductor
set to Ctrl+Alt+A:

  Configuration > Raccourcis, filtered on "conducteur", lists
  "Création automatique de conducteur(s)" under "Éditeur de schémas"
  showing that binding.

  Mouse parked away from the toolbar, pressing it twice: the toolbar
  button changes on each press and returns to its starting appearance
  after the second, so the key toggles the setting exactly as clicking
  the button does.

Two things that misled the first run, recorded so the next person does
not repeat them: F7 is already registered to panel.move_diagram_downx100
in the elements panel, and a screenshot taken with the pointer resting on
the button shows its hover state, not its checked state.

ctest 12/12, Qt 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CaKympWT3owLotCpEN2CFj
2026-09-19 09:18:04 +12:00
ispyisail e42ebdc861 Add a conductor colour button to the Schéma toolbar (#461)
An electrician on the forum draws 400 V and 24 V circuits in the same
folio and wants the colour to be one click away
(qelectrotech.org/forum, viewtopic pid=23296). Today the quickest route
is F2, which opens a colour dialog and refuses to act unless exactly one
conductor is selected, so colouring a run means one conductor, one
dialog, at a time.

This adds a swatch button beside the auto-conductor actions. Picking a
colour does two things:

  - recolours every conductor currently selected, as ONE undo step;
  - becomes the colour of the next conductor drawn, through the
    LastUsedStyle mechanism #888 already added and Conductor's
    constructor already reads.

Either half is useful alone: with nothing selected it just sets the pen
for what comes next.

The menu lists the colours the trade names -- the three phases, neutral,
earth, and the ones used for control and extra-low-voltage circuits --
then any custom colours picked this session, then the full colour
dialog. A colour already in the standard list is not repeated under
"recently used".

Nothing is written to the project or to QSettings. That is deliberate:
it is the same session-scoped "what did I just use" idea as
LastUsedStyle, so it adds no persisted state and no file-format change.
Named presets stored per project -- what #461 actually asks for -- are a
larger feature that needs a maintainer decision first; the question is
still open on that issue since 21 June.

Verified on a virtual display against examples/Habitat-Schemas_developpes.qet,
reading colours back from the saved project rather than the screen:

  select all on folio 1, pick Rouge
      21 conductors {none:1, #ff5500:2, #ff0000:6, #00aa00:5, #0000ff:7}
      -> all 21 #ff0000
  one Ctrl+Z
      -> back to the original five-colour mix, exactly
  pick Marron with nothing selected, then draw a conductor
      -> the new conductor is #7b3f00

ctest 12/12, Qt 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CaKympWT3owLotCpEN2CFj
2026-09-19 09:02:58 +12:00
ispyisail 3c1fb2fe72 Let Escape cancel the text tool, like every other placement (#903)
Issue #903 reported that Escape stopped cancelling an in-progress
placement. PR #899 fixed that by letting Escape through to the active
tool whenever Diagram::eventInterfaceIsRunning(), and 863ac5f0a tightened
it to isRunning() so a second Escape cannot retrigger an abort already in
flight. That covers seven of the eight tools. It cannot cover the eighth.

eventInterfaceIsRunning() is m_event_interface && isRunning(), and
isRunning() returns m_running. DiagramEventAddText is the only class
under sources/diagramevent/ that never sets m_running, so the guard reads
false for the whole time the tool is armed and DiagramView::keyPressEvent
keeps swallowing Escape for its own selection/focus handling.

It is also the only one of the eight with no RightButton branch --
right-clicking the folio with the text tool armed opens the folio context
menu. So this tool currently has no way to cancel at all: the only way
out is to pick a different tool, and any stray click drops a text field
the user did not want.

The tool is armed from the moment it is attached, so m_running is set in
the constructor and cleared where the text is placed, before finish().

Measured on a virtual display against a fixture holding one free text
field, counting diagram-level text fields in the saved project:

  arm the text tool, click            2 fields   places, as it should
  arm it, press Escape, then click    2 fields   before  -- not cancelled
                                      1 field    after   -- cancelled

The same run on master's rectangle tool cancels correctly, which is what
made the text tool look fixed when it was not.

ctest 12/12, Qt 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 08:32:58 +12:00
ispyisail 95d0e523fe Snap a device text to the grid when it is dragged (#923)
Dragging an element's text -- its label, article number, any of its
information fields -- moved it in free one-unit steps while everything
else in the editor snapped to the grid. Reported by pki791 in #923 for
labels moved with Shift.

QET moves a text with the mouse along five paths. Four snap and let Ctrl
place freely:

  DiagramTextItem::mouseMoveEvent        an independent text
  ElementTextItemGroup::mouseMoveEvent   a group of element texts
  ElementTextsMover::continueMovement    every OTHER selected element text
  QetGraphicsItem::setPos                elements, images, shapes

DynamicElementTextItem::mouseMoveEvent, the text actually under the
cursor, ended "setPos(new_pos)" with no grid and no modifier check. It is
otherwise the same function as the group's, which is why this reads as an
omission rather than a decision: the line this adds is that function's,
character for character.

The inconsistency was visible in one gesture. With two element texts
selected and one of them dragged, ElementTextsMover skips the driver item
and snaps the rest, so the text under the cursor was the only one on the
folio that did not land on the grid.

Verified on a virtual display (Xvfb + openbox) against a two-lamp fixture,
grid 10, reading the saved positions rather than the screen:

  Shift+drag the label      before (32.95, -11.55) -> (7.95, 23.45) off-grid
                            after                  -> (10, 20)      on-grid
  the co-selected label     (10, -10) -> (50, 20) on-grid, before and after
  Shift to grab, then Ctrl  -> (7.95, 23.45) off-grid, free placement kept

The last line matters: moving an element text needs Shift at press, and
the modifier is read at move time, so Ctrl still places freely -- press
with Shift, hold Ctrl to drag. Holding both from the press is a different
gesture, reserved by DiagramView::isCtrlShifting() for the view's mode
switch, and does not move the text at all. Nothing that was possible
before is lost.

Worth knowing when reviewing: 470 of the 492 element texts in the 24
example projects (95.5 %) sit off the grid today, because element
definitions place their default text at fractional offsets. The first
drag of almost any existing label will pull it onto the grid, by at most
half a grid step.

ctest 12/12, Qt 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 08:06:45 +12:00
Laurent Trinques 2da516667e Merge pull request #918 from Kellermorph/fix-colour-selection
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m41s
Persist QColorDialog custom colors across application restarts
2026-09-18 19:36:25 +02:00
Laurent Trinques b62b118d45 Merge pull request #919 from jp2images/feature-icon-themes
Add a "qet-dark" icon theme for dark palettes
2026-09-18 19:00:47 +02:00
Laurent Trinques b0b7814015 Merge pull request #925 from arummler/fix-undo-stack
Fix issues with undo stack
2026-09-18 18:54:11 +02:00
Laurent Trinques c9a63b5bce Merge pull request #921 from arummler/master-fix-group-rotation
Fix: "group rotate"
2026-09-18 18:52:04 +02:00
Andre Rummler 059d179d9b Fix:
a) Project stayed in modified state even after undoing all changes.
b) Saving the project deleted the undo stack.
2026-09-18 18:28:48 +02:00
Kellermorph fa52aab149 Persist QColorDialog custom colors across application restarts
Qt's QColorDialog loads custom colors from QSettings on startup but
never writes them back, so user-defined colors in the color picker
are lost when the application exits.

Save and load the 16 custom color slots explicitly via QSettings
in QETApp's constructor (after initStyle) and destructor (before
other settings are flushed). This covers every QColorDialog usage
in the application transparently.

Also fixes a QColorDialog memory leak in DiagramView.
2026-09-18 13:53:14 +02:00
Andre Rummler 54e61d482d Fix: "group rotate" of elements detached the conductors as those were not rotated. 2026-09-18 12:35:08 +02:00
Jeff Patterson 800ddfbcbe Add a "qet-dark" icon theme for dark palettes
189 of QET's 266 fixed-size icons are black line art with no dark
variant, so on a dark palette they were black on a dark toolbar, and
Fusion's disabled rendering lightened them into something more readable
than the enabled state (GitHub #466, #870; bugtracker 335 for the
element panels, which keep their own fix).

The theme "qet-dark" holds light-ink copies of the line-art icons in
ico/themes/qet-dark, generated by misc/make_icon_themes.py. Colored
icons are not copied; the theme inherits them from "qet". An icon counts
as line art when fewer than 20% of its visible pixels are saturated. The
copies keep hue and alpha and invert lightness, scaled so each icon's
darkest ink becomes (220,220,220), the dark palette's text color. The
eight SVG icons get their color replaced the same way.

QETApp::applyIconTheme() picks "qet-dark" for a dark palette and "qet"
otherwise. It runs from initIconTheme(), again from initStyle() once the
palette is final, and on the OS color scheme switch. Icons created with
QIcon::fromTheme() re-resolve on their next paint, so nothing else
changes.

With light-ink files, Fusion's own disabled rendering comes out dimmer
than enabled with no extra code.

tests/qttest/tst_qeticons: every name resolves in both themes, every
dark file has light ink, and a Fusion tool button shows its icon at 3:1
in both themes with disabled weaker than enabled.
2026-09-18 05:06:51 -05:00
Laurent Trinques 0258d1a74f Merge pull request #917 from ispyisail/fix/913-paste-under-cursor
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m43s
Paste under the cursor, and restore the folio context menu after a paste (#913)
2026-09-18 10:26:00 +02:00
ispyisail dd0c194a3c Paste under the cursor, and give the context menu back (#913)
Two fixes in the placement tool Ctrl+V starts.

Paste appeared on top of the original, not under the cursor. 55c2c0df9
added the placement tool precisely so the copy would not land invisibly
on top of what was copied, but 53a0f07ca then warped the pointer to the
group's grid-snapped origin -- which is the original's position -- so the
copy reappeared exactly over the original until the mouse was moved. The
start_pos the caller computes from the cursor was left unread: three
mentions in the file, all declaration or comment. Reported on #913,
where it reads as Ctrl+V pasting in place.

Move the group to the cursor instead of the cursor to the group. Both put
the copy under the pointer; only one of them takes the pointer away from
where the user put it. start_pos is honoured, the items are translated
once at construction, their conductors re-routed before anything is
drawn, and the movement baseline is set there too rather than waiting for
the first mouse move.

That made the baseline sentinel matter, so it is now the
m_baseline_captured flag the header already declared and nothing used,
rather than m_initial_cursor.isNull() -- which cannot tell "not set yet"
from a baseline that is legitimately scene (0,0).

Separately: one Ctrl+V killed the folio's right-click menu for the rest
of the session. init() sets Qt::NoContextMenu so a right click cancels
the placement instead of opening a menu over it, and nothing ever set it
back, taking "Coller ici", "Collage multiple", the folio properties and
add/remove column/row with it. Every other DiagramEvent* class restores
the policy in its destructor; this one did not. Confirmed against an
unmodified master build: the menu opens before a paste and not after one.
It matters here because "use the right-click menu instead" is the answer
people are given when Ctrl+V does not place where they wanted.

Verified on a built binary driven through a virtual X display, against
examples/741.qet and convertisseur.qet:

- the pointer does not move across Ctrl+V (1300,870 before and after;
  on master it jumps to the original at 798,455), and the copy is under it
- a multi-element selection keeps its layout and its conductors re-route
- Escape and right-click both cancel, leaving nothing behind and nothing
  on the undo stack; click and Return both commit; one undo removes the
  whole paste and redo restores it
- with the pointer outside the viewport the copy lands at the viewport
  centre, visible, and follows correctly once the mouse enters
- pasting into a different folio from the one copied from works
- the context menu opens before a paste, after a cancelled one, and after
  a committed one, with "Coller ici" present

ctest 9/9.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 19:57:59 +12:00
Laurent Trinques 3ba7de3284 CI: fix skip SignPath signing until the API token is configured 2026-09-18 08:33:45 +02:00
Laurent Trinques 0067ba1dca Merge pull request #905 from ispyisail/harden/crash-reporter
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m1s
Harden the crash reporter: keep every dump, and say what crashed
2026-09-18 04:59:45 +02:00
ispyisail 16220af012 Merge remote-tracking branch 'upstream/master' into harden/crash-reporter 2026-09-18 14:31:54 +12:00
ispyisail f8c1b5206a Address review on #905: offered-list semantics, dump ordering, FreeBSD, legacy dumps
Five things raised in review, plus tests for the parts that were only
described in prose.

clearPendingCrashDump() did not do what its comment said. It called
pendingCrashDumpFiles() again at clear time, so it deleted whatever was
in the directory then, not what had been offered. The offer sits inside a
modal dialog that stays open as long as the user reads it, and
SingleApplication keys its socket on the binary path, so a second
QElectroTech build running alongside is a separate process that can crash
and write a dump in that window. Re-listing deleted that dump unseen --
the exact failure this change exists to fix. The list is now taken once
in QETApp::checkCrashDump() and passed to both
pendingCrashDumpContents() and clearPendingCrashDump().

The ring is now written before the backtrace. backtrace() unwinds through
libgcc, which calls dl_iterate_phdr and takes the loader lock; warming it
in install() removes the allocation but not the lock. Crashing inside
dlopen() (Qt plugin loading), or on a corrupted stack, could therefore
hang or re-fault the handler at the backtrace and lose the ring with it.
Order is now header, signal, ring, backtrace, so the cheapest and most
valuable part is already on disk before anything that can block. The
class comment claimed the handler takes no locks; that was not strictly
true and now says so.

QET_CRASH_BACKTRACE comes from find_package(Backtrace) rather than
__has_include(<execinfo.h>). The header exists on FreeBSD but backtrace()
lives in libexecinfo there, so the probe compiled and the link failed.

A crash_dump.log left by a pre-#905 version is migrated into crashes/ at
startup, named from its own mtime. Otherwise upgrading stranded it: the
new code never looks at that path, so the dump from the crash that
prompted the upgrade would sit there unoffered forever.

Also from the review: dumps are capped at the 10 newest, so a crash loop
cannot fill the log directory before any dialog is shown; crashDumpDir()
no longer creates the directory as a side effect of a const getter
(ensureCrashDumpDir() does that for the callers that write); and redact()
now masks an AppImage's per-run /tmp/.mount_XXXXXX prefix, which
backtrace_symbols_fd() writes into every frame.

Two test executables, both of which were checked to fail against the
behaviour they replace:

- tst_crashhandler covers CrashHandler::formatInt(), which had no
  coverage at all despite running only inside a signal handler, where
  nothing can assert: zero, negatives, INT_MIN (negated through unsigned,
  since -INT_MIN is UB), INT_MAX, truncation and a zero-sized buffer,
  each checked against a sentinel-filled buffer so a write past the
  reported length fails.
- tst_crashdumps covers the bookkeeping: ordering, empty dumps, the
  exclusion of this run's own path, the cap, concatenation of every
  offered dump, that clearing deletes only what was offered, and what
  redact() masks. qetlogger.cpp needs exactly one symbol from the
  application, QETApp::dataDir(), which the test supplies itself.

Not addressed here: the timestamp in crash_<timestamp>_<pid> is the
launch time, not the crash time -- correct as observed, and the commit
message that implied otherwise was the thing that was wrong. Resolvable
QET frames for AppImage/Flatpak/Snap/Debian need -rdynamic and archived
debug symbols, which is a packaging discussion, not this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 14:31:49 +12:00
Laurent Trinques 4d800de26a Merge pull request #908 from ispyisail/fix/904-quit-during-modal
Refuse to close an editor while a modal dialog is running
2026-09-18 04:15:03 +02:00
ispyisail d3c8544fd9 Stop the quit-during-modal test taking its full watchdog period every run
The test bounded gdb with a backgrounded watchdog:

    ( sleep 120; kill -9 "$GDB_PID" 2>/dev/null ) &
    WATCHDOG_PID=$!
    wait "$GDB_PID"
    kill "$WATCHDOG_PID" 2>/dev/null

sleep runs as a child of the subshell, so killing the subshell leaves the
sleep orphaned -- and the orphan still holds the write end of whatever this
script's stdout is. Run from a terminal that costs nothing. Run through a
pipe, which is how CTest invokes it, the reader sees no EOF until the sleep
expires, so every run lasted the full 120 seconds regardless of how fast
gdb finished. gdb itself takes ten.

That made modal_quit_regression 92% of the runtime of the entire ctest
suite (122 s of 133 s) and left it 60 s short of its own 180 s CTest
timeout -- near enough that a loaded CI machine could have turned it into
a flaky failure in somebody else's build.

Use timeout(1) instead, which leaves nothing behind, with a plain gdb call
as a fallback where it is unavailable. Suite time drops to 12 s.

Verified on the merged branch: fixed build passes both the .qet and the
read-only .elmt scenario, and with the fix commit reverted both still fail
with signal 6, under ~QETDiagramEditor() and ~QETElementEditor()
respectively.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:53:16 +12:00
ispyisail eea939c31c Merge remote-tracking branch 'upstream/master' into fix/904-quit-during-modal 2026-09-18 13:34:58 +12:00
ispyisail 599228fe6e Register the quit-during-modal test with CTest, and fix two bugs in it
The test added with the #904 fix was run by hand. Register it so it runs
with the rest of the suite, and fix two defects found while extending it
to the element editor -- both of which made it report success it had not
earned.

nm -C "$BINARY" | grep -q <sym> under `set -o pipefail`: grep exits at the
first match, nm dies of SIGPIPE, and the pipeline reports failure. The
symbol check therefore skipped the test on every build that could actually
run it. Read nm's output into a variable once and match with `case`.

The input file was copied to a hardcoded "$SANDBOX/project.qet".
QElectroTech picks the editor from the extension, so passing a .elmt gave
a failed project load in the diagram editor rather than an element editor
-- the run still found a dialog, still called quitQET(), and still
reported a result, just for the wrong window. Preserve the basename.

With that fixed, the element-editor path is exercised on its own: a
read-only .elmt opens a message box, and on a build without the fix the
run aborts with "double free or corruption" under ~QETElementEditor(),
where before it named ~QETDiagramEditor(). The guard QETElementEditor
calls from its closeEvent() covers a real crash, not a theoretical one.

Registered on Linux only, and it reports 77 -- CTest's SKIP_RETURN_CODE --
when it cannot run at all: no gdb, a gdb built without Python, or a
stripped binary whose QETApp symbols it cannot call. A release build that
this test cannot drive is not a failing build.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:34:45 +12:00
Laurent Trinques 3ff02e528d Merge pull request #912 from Kellermorph/fix-checkbox-pdf-export
Fix PDF export checkbox state not being remembered
2026-09-18 01:49:39 +02:00
Kellermorph 520f4245b3 Fix PDF export checkbox state not being remembered
- Save and restore the state of m_component_info_cb, m_fit_in_page_cb,
  and m_use_full_page_cb via QSettings, in addition to the existing
  ExportProperties-based checkboxes (border, titleblock, terminals, etc.)
- Add savePrintProperties() to persist all checkbox states after
  print/export under the "print/default" settings prefix
- Remove unused QPrinter(HighResolution) local variable in launchDialog()
  that caused an unnecessary ~2s CUPS round-trip on Linux
2026-09-17 21:33:05 +02:00
Laurent Trinques 918756a91a Merge pull request #911 from Kellermorph/copying-fix
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m2s
Fix copy-paste bugs in DiagramEventAddPaste and PasteDiagramCommand
2026-09-17 18:39:54 +02:00
Kellermorph 7a73e8075f Merge remote-tracking branch 'upstream/master' into copying-fix
# Conflicts:
#	sources/diagramevent/diagrameventaddpaste.cpp
2026-09-17 17:59:12 +02:00
Kellermorph 53a0f07ca3 Fix copy-paste bugs in DiagramEventAddPaste and PasteDiagramCommand
Fix multiple issues introduced by commit 55c2c0df9 (interactive paste):

Paste placement (diagrameventaddpaste):
- Items now load at their original XML coordinates instead of being
  snapped to the cursor position, preventing jumps on initial placement
- Use delta-based movement: record the actual grid-snapped cursor
  position on first mouseMoveEvent as baseline, then compute
  grid-snapped deltas from there
- Bypass Diagram::snapToGrid() in moveTo() to avoid Ctrl modifier
  causing pixel-snapping instead of grid-snapping
- Remove moveTo() from mouseReleaseEvent to prevent a final jump
  on click
- Warp cursor to group bounding rect origin for visual feedback

Paste command (diagramcommands):
- Always clear PLC slave data (type, address, function, comment,
  cross-ref, label, TC, T1-T4) on paste regardless of the
  erase-label-on-copy user preference
- Block alignment (m_block_alignment / blockAlignmentUpdate) before
  setElementInformations() for both slaves and non-slaves, preventing
  finishAlignment() from shifting right/center-aligned text items
- Clear non-UserText items directly after setElementInformations()
  for slaves as a safety net
2026-09-17 17:54:57 +02:00
Laurent Trinques 864e651966 Merge pull request #910 from arummler/fix-alignment
Fix alignment when pasting an element
2026-09-17 17:49:23 +02:00
Andre Rummler 26285373a3 Fix alignment when pasting an element. 2026-09-17 17:05:15 +02:00
Laurent Trinques b4fcef1829 CI: install the Qt SVG icon engine instead of breeze-icon-theme
draw-bezier-curves only exists as SVG in the qet icon theme; on
Debian/Ubuntu the SVG icon engine lives in qt6-svg-plugins, which
--no-install-recommends leaves out. breeze-icon-theme is not needed:
the test uses the qet theme compiled into the binary.
2026-09-17 15:57:05 +02:00
Laurent Trinques 9519b70826 CI: skip SignPath signing until the API token is configured 2026-09-17 15:46:26 +02:00
Laurent Trinques 218e70c759 CI: install an icon theme for tst_qeticons
The ubuntu:26.04 container has no icon theme, so
QIcon::fromTheme() returns null icons and tst_qeticons fails
on first CI run. Install breeze-icon-theme so the test runs
against a real theme, as it does on developer machines.
2026-09-17 15:27:26 +02:00
Laurent Trinques 26c0fc9812 Merge pull request #872 from ispyisail/ci/linux-build-and-tests
Add Linux CI: build, unit tests, and the IPC regression gate
2026-09-17 15:08:05 +02:00
Laurent Trinques 8c135a009d Clear the tools dock when an editor refuses the selection 2026-09-17 13:59:32 +02:00
Laurent Trinques 261b0f9e12 Merge pull request #675 from IBSYSLevi/improvement/avoid-needless-dock-churn
Avoid needless dock churn in QETElementEditor::updateInformations()
2026-09-17 13:49:52 +02:00
Laurent Trinques 1183849ce2 Merge pull request #895 from Kellermorph/fix-copy-page-plc-cleanup
Clean up stale data when duplicating diagram pages
2026-09-17 13:38:20 +02:00
Laurent Trinques 2b30083193 Merge pull request #909 from arummler/fix-move-insert
additional fix requested in #899
2026-09-17 13:29:07 +02:00
Andre Rummler 863ac5f0ae avoid that another action triggers while aborting operation 2026-09-17 13:25:17 +02:00
ispyisail 8623dd4c6f Refuse to close an editor while a modal dialog is running (#904)
openAndAddProject() shows BackupDialog as a stack object parented to the
editor and exec()s it; every QET::QetMessageBox does the same. exec() runs a
nested event loop, and closing the editor during it turns WA_DeleteOnClose
into a deleteLater() that the nested loop processes: ~QWidget() deletes the
editor's children, the stack-allocated dialog among them, and the process
aborts. Reported on macOS, where File > Quit lives in the application menu
and stays usable while the backup question is up.

The close is now refused while any modal widget is active, and the dialog is
raised so the refused quit is not silent. It is done in QETMainWindow::event()
rather than in closeEvent(), because QETDiagramEditor::closeEvent() starts
closing projects before it decides whether to accept. That covers the diagram
and title-block editors; QETElementEditor is a plain QMainWindow, so its
closeEvent() calls the same helper before canClose(), which itself opens a
modal. QETApp::quitQET() needs nothing: closeEveryEditor() goes through each
editor's close(), and quitQET() already only quits when every close succeeded.

Rejected alternatives, both suggested on the issue:

- Giving the dialog no parent stops the abort but not the deletion. One
  caller of openAndAddProject() is the editor's own constructor, which goes
  on to open the next file and call slot_updateActions() on this -- a loud
  abort would become a silent use-after-free.
- Guarding only QETApp::closeEveryEditor(), which I first recommended on the
  issue, misses the reported route entirely: File > Quit is connected to
  QETDiagramEditor::close(), not to quitQET().

Verified on Linux, where there is nothing to click (the menu bar belongs to
the blocked window, and Qt ignores window-manager close requests for it), by
calling close() from gdb while the dialog's loop was running -- both
QETApp::quitQET() and QWidget::close() on the editor. Unfixed, both abort
with "free(): invalid size" in QObjectPrivate::deleteChildren() under
~QETDiagramEditor(), matching the report frame for frame; fixed, close()
returns false, the editor and the dialog stay up, and after answering the
dialog Ctrl+Q exits normally. The element-editor guard is the same helper
but was not exercised separately.

tests/modal-quit-regression/ turns that into a gate: it breaks on
QDialog::exec(), interrupts inside the nested loop, calls quitQET() and
checks the process survives. It matches no window titles (translated) and no
window ids, runs on the offscreen platform, and needs only gdb with Python.
Checked both ways: exit 1 with the backtrace above on a build without this
change, exit 0 with it.

ctest 8/8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 23:19:59 +12:00
Laurent Trinques 6a84ada6a6 Merge pull request #906 from jp2images/feature-icon-theme-plumbing
Add a "qet" icon theme and load icons by name
2026-09-17 13:19:11 +02:00
Kellermorph b7f516d485 Clean up stale data when duplicating diagram pages
When duplicating a diagram page, copied slave elements retain stale
data from the source: labels, descriptions, link references, and PLC
master information (type, address, function, comment, cross-ref, timer
values) remain in the copies.

Fix by adding clearPendingLinks() to Element to prevent copies from
linking back to source elements via stale UUIDs, and by cleaning up
copied element data after fromXml():

- Slaves always lose their label, formula, comment, location, and
  PLC master data, since their text comes from a master element which
  is not available on the copy. The displayed text on slave elements
  is cleared directly via setPlainText(), but UserText items (free
  text typed by the user) are preserved.

- For PLC slaves, setElementInformations() is called without
  m_block_alignment so that elementInfoChanged() can run
  finishAlignment() to correctly adjust text positions for the
  cleared content.

- Non-slave elements respect the existing erase-label-on-copy
  preference, same as PasteDiagramCommand::redo().

- Conductor labels are also reset when erase-label-on-copy is
  active, matching PasteDiagramCommand::redo() (issue #413).

- Text alignment is preserved by wrapping setElementInformations()
  with m_block_alignment for non-slave elements, same as
  Element::fromXml().
2026-09-17 13:12:15 +02:00
ispyisail 0646f9ca4f Harden the crash reporter: keep every dump, and say what crashed
Three weaknesses, all visible in ChuckNr11's report on #898 -- "the report
appeared only once despite there being 10 or more crashes".

One dump per run instead of one per install
-------------------------------------------
crashDumpPath() was a single fixed crash_dump.log, and the handler opens it
O_TRUNC, so each crash destroyed the evidence from the one before. Ten
crashes left one dump. Dumps now go to a crashes/ directory named
crash_<timestamp>_<pid>.log, and every pending one is offered together,
newest first, with a banner saying how many there are. A crash that repeats
is exactly the case where the earlier dumps matter, because the difference
between them is the evidence.

The name is built in normal context and handed to CrashHandler::install(),
which copies it into a preallocated buffer as before -- the handler still
writes to one fixed path, so its no-allocation invariant is untouched.

The dump now says which signal fired
------------------------------------
The header is built once at install(), so every dump looked identical no
matter what killed the process -- and SIGSEGV and SIGABRT point at very
different bugs. Written with an async-signal-safe integer formatter into a
stack buffer, since snprintf is not on the POSIX safe list.

...and where it was
-------------------
The ring said what the program was doing; nothing said where it died. The
dump now carries a backtrace. backtrace() is warmed once in install() so
its first-call lazy resolution cannot allocate inside the handler, and
backtrace_symbols_fd() writes straight to the fd -- unlike
backtrace_symbols(), which mallocs and must never be used here. Guarded on
__has_include(<execinfo.h>) so platforms without it are unaffected.

QET's own frames currently resolve as offsets rather than names, since the
binary does not export its dynamic symbols. They are still resolvable
offline: the header records the exact git SHA. Building with -rdynamic
would give names directly, but that is a build-flag decision for its own
change.

Deliberately unchanged: the four invariants in crashhandler.h. Nothing
added here allocates, blocks, takes a lock, or swallows the crash.

Verified: three consecutive SIGSEGVs now leave three separate dumps, each
carrying "Signal: 11" and a backtrace with resolved Qt frames; launching
afterwards offers all three in one dialog, newest first, and clears them
once shown. ctest 8/8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:44:59 +12:00
Jeff Patterson 8ef130a59d Add a "qet" icon theme and load icons by name
QET had no icon theme: the 446 entries of the icon table and the 116
iconsets in .ui files each named a resource path, so an icon could only
ever be one file, and a variant for another palette or a vector source
had nowhere to go (GitHub #466, #690, #870). This adds the theme layout
without changing a single pixel; a dark variant comes in a follow-up.

The theme "qet" follows the freedesktop layout Qt's icon loader
understands. misc/make_icon_themes.py generates ico/icon-themes.qrc,
which aliases the existing ico/<size>/<name>.png files into
themes/qet/<size>/<name>.png, and ico/themes/qet/index.theme. No file
moves. The four table entries that paired a 16 pixel file with a 22
pixel file of another name (ConductorSettings, DiagramAdd,
DiagramDelete, DialogInformation) get the 22 pixel file aliased under
the 16 pixel name.

QETApp::initIconTheme() registers the theme before initIcons() and makes
it current on every platform, so a desktop icon theme cannot replace
QET's icons. Icons are then looked up by name: QIcon::fromTheme() in
qeticons.cpp and in the few places that built a QIcon from a resource
path directly, and theme="..." on the iconsets in .ui files, with the
resource path kept as fallback. Flags, color swatches, application and
MIME icons stay on their paths.

One entry does not go through the theme. The elements panel draws the
project root with ProjectFileGP in the 50 pixel slot it reserves for
element previews, and the name "project" also carries the 128 pixel
file the configuration dialog uses. On a 2x display Qt's loader picks
that file for a 50 pixel request and fills the slot. ProjectFileGP
loads the 16 and 22 pixel files directly, as before.

tests/qttest/tst_qeticons: every name in the theme resolves, the four
aliases resolve at 22 pixels, a Fusion tool button shows its icon at
3:1 with disabled weaker than enabled, and the project root icon stays
at 22 pixels or less when asked for 50 at 2x while the configuration
dialog still gets its 128 pixel file. The rendering helpers shared
with tst_qetpalette moved to tests/qttest/inkcontrast.h.
2026-09-17 05:41:50 -05:00
Laurent Trinques c3b0e6725e Merge pull request #896 from ispyisail/feature/886-custom-sql-reports
Add read-only enforcement, preview and import/export to custom SQL reports
2026-09-17 12:38:17 +02:00
Laurent Trinques 3f8e6418d0 Merge pull request #881 from Kellermorph/checkbox-master-slave
Add Hide full masters checkbox to slave linking widget
2026-09-17 12:30:42 +02:00
Laurent Trinques a86fe2a46b Merge pull request #902 from ispyisail/fix/901-crash-report-never-offered
Offer the crash report after the recovery prompt, not instead of it
2026-09-17 12:27:10 +02:00
Laurent Trinques 206edcbb7d Merge pull request #899 from arummler/fix-move-insert
Fix paste problems
2026-09-17 12:25:44 +02:00
Laurent Trinques 0a7ea032d9 Revert "bump KDE KF6 frameworks version to 6.28.0 stable release"
Close https://github.com/qelectrotech/qelectrotech-source-mirror/issues/900
This reverts commit ff3c2e0916.
2026-09-17 12:18:10 +02:00
ispyisail 52713704bc Offer the crash report after the recovery prompt, not instead of it (#901)
QETApp::checkBackupFiles() only reached checkCrashDump() when there was
nothing to recover:

    if (stale_files.isEmpty()) {
        checkCrashDump();
        return;
    }

A crash with a project open always leaves a stale KAutoSaveFile, so on the
next launch the recovery prompt won every time and the dump sat on disk
unoffered -- the report was unreachable in exactly the case it is most
wanted. Reported by ChuckNr11 as a side note in #898: "the report appeared
only once despite there being 10 or more crashes". It appears on the runs
that happen to have nothing to recover.

Discussion #644 step 5 asks that the two prompts never show at the same
time, which this keeps: the recovery prompt is answered first, then the
report. The recovery half moves into offerBackupFiles() so both paths fall
through to the same place.

Verified under Xvfb, from a real crash state (SIGABRT with a project open,
leaving both a stale file and a 5.4 KB crash_dump.log): the recovery prompt
appears, and dismissing it now brings up "Rapport de plantage" carrying the
version, git SHA, OS, Qt version and the log ring. Before this change the
report never appeared -- that half rests on the four lines above rather
than on a captured before/after, since re-creating the crash state for a
clean baseline run kept consuming it.

Not addressed: the dump is a single fixed path opened O_TRUNC, so
consecutive crashes still overwrite one another.

ctest 8/8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 22:10:03 +12:00
Andre Rummler 923723a9de Fix the following issues:
a) paste
- conductor does not move
- pressing escape does not abort
- pressing anything during the process crashes the program instead of aborting
b) similar issue with the escape not working fixed for graphics items, texxt fields (and probably others)
2026-09-17 11:34:25 +02:00
Laurent Trinques ff3c2e0916 bump KDE KF6 frameworks version to 6.28.0 stable release 2026-09-17 06:43:19 +02:00
Laurent Trinques adca07f26f Merge pull request #880 from arummler/remove-qt5-instruction
Remove Qt5
2026-09-17 06:35:57 +02:00
ispyisail 12ecf6b28f Add read-only enforcement, preview, import/export to custom SQL reports
Refs discussion #886: a saved-report manager built on custom SQL and
nomenclature.json. Most of what was asked for already existed --
Projet > Exporter au format CSV already builds/saves/reuses named
SELECT queries via ElementQueryWidget and nomenclature.json, and
Projet > Ajouter une nomenclature already inserts one into a folio.
This fills the three real gaps.

- projectDataBase::isReadOnlySelect() rejects anything that isn't a
  single SELECT/WITH statement. Checked in newQuery() itself, the one
  choke point every query path already goes through -- including a
  query loaded from a saved nomenclature/summary table's <query>
  element on project open, not just the dialog's own custom-SQL box.
  ElementQueryWidget shows the same check live as you type, and
  BOMExportDialog surfaces it before running or exporting anything.
- BOMExportDialog gains a Preview button + table (QSqlQueryModel),
  so a report can be checked on screen before committing to a CSV file.
- ElementQueryWidget gains Importer.../Exporter... buttons that
  read/write nomenclature.json's saved reports as a JSON file, so a
  report can be handed to a colleague or another install. Import asks
  before overwriting a locally-saved report of the same name.

Verified: Qt 6.10.2, builds clean, ctest 8/8. Drove the real dialog
through Xvfb: typed "DROP TABLE element" into the custom-SQL box and
got the inline warning immediately, then confirmed Preview also
refuses it with a "Requête refusée" dialog rather than running it.
Preview against the real default query returned live column headers
and a row. Export opens a save dialog without crashing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 10:09:14 +12:00
Andre Rummler 1ee828da5b Remove titleblocktemplate cahing remains. 2026-09-17 00:07:46 +02:00
Andre Rummler 837e1e7f09 Remove caching in titleblocktemplaterenderer which has not been used since Qt4.8. 2026-09-17 00:07:46 +02:00
Andre Rummler 25962118a2 Disable cacching which was enabled accidentially during Qt5 removal. 2026-09-17 00:07:46 +02:00
Andre Rummler 35275de4cc Remove blank lines. 2026-09-17 00:07:46 +02:00
Andre Rummler d641b964bd Remove very old Qt4 code path. 2026-09-17 00:07:46 +02:00
Andre Rummler e90dddbbc6 Add missing Qt6 code path and remove Qt5. 2026-09-17 00:07:46 +02:00
Andre Rummler ce8884394a Remove all code switches for Qt<6 with one exception: caching in titlebordertemplate. Function for caching not called since Qt4; might profit from complete removal.
Another exception: one non-converted code path in projectprintwindow.cpp to be followed-up.

One issue found in the Qt6 code path of diagramview.cpp which has been fixed.
2026-09-17 00:07:46 +02:00
Andre Rummler 8606cc5328 Removing Qt5/KF5 from build system. 2026-09-17 00:07:46 +02:00
Andre Rummler 60d9299d45 New installation instructions for Qt6/CMake. Proposal: translations as INSTALL_XY.md 2026-09-17 00:07:46 +02:00
Laurent Trinques c71839a291 Merge pull request #869 from Kellermorph/fix-crossref-textfield
Clear stale xref data from PLC slave elementInformations on unlink and position change
2026-09-16 21:28:10 +02:00
Laurent Trinques d9a2539702 Merge pull request #890 from ispyisail/fix/734-conductor-self-retrace
Fix bugtracker #734: stop routing a bridge conductors don't need
2026-09-16 21:23:39 +02:00
Laurent Trinques 22c1ec9321 Merge pull request #891 from ispyisail/feature/162-js-scripting
Add JavaScript scripting: read, export, edit geometry, undo, navigate
2026-09-16 21:22:01 +02:00
ispyisail 4300a655bc Merge remote-tracking branch 'upstream/master' into feature/162-js-scripting
# Conflicts:
#	sources/qetdiagrameditor.cpp
#	sources/qetdiagrameditor.h
2026-09-17 06:50:35 +12:00
Laurent Trinques e7e2864641 Merge pull request #889 from ispyisail/fix/802-reload-element-drawings
Add "Reload element drawings" to refresh placed elements
2026-09-16 19:52:12 +02:00
ispyisail 2ed85f6fc7 Address review: qRound-consistent guards, regression test
Addresses scorpio810's review of PR #890 (bugtracker #734):

- The four cas "3"/"4" bridge-skip guards now compare qRound()ed
  coordinates, matching how the bridge coordinate itself is computed --
  an exact != would miss a pair already grid-equal after rounding but
  off by a sub-pixel remainder, and still route a degenerate bridge for
  it. Verified no behavior change on the shipped corpus: per-file
  self-retrace counts are identical before/after (every coordinate
  there already lands exactly on-grid).
- Added tests/qttest/tst_conductorselfretrace.cpp, fixture
  qet_bug_repro_resaved.qet (the report's own canonical reproduction):
  exports it via the built binary's --export-svg and asserts no
  conductor path is self-retracing. Confirmed it actually catches the
  regression, not just passes vacuously -- reverted conductor.cpp to
  master and reran: fails, 1 self-retracing path found.

Not changed in code: the cas "4" descending-branch dead-code note (kept
for symmetry, as already agreed) and the schema_unifilaire_voltaique2.qet
trade-off (flagged for the reviewer's own visual check).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 05:08:14 +12:00
ispyisail e62d43d024 Revert .ts changes, translations will be updated separately
update_translations dropped 3 .ui files' <location> entries this run
(graphicstablepropertieseditor.ui, inditextpropertieswidget.ui,
projectdbmodelpropertieswidget.ui), marking ~38-44 existing translations
per language as vanished -- lrelease drops those, so merging would have
lost real, in-use translations in about twenty languages. Cause not
yet identified.

scorpio810 will run lupdate separately after merge, so the 37 new
scripting strings get picked up without this loss and without
conflicting with translation work in progress.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 04:45:29 +12:00
Laurent Trinques 6562498ca2 Snap update for qml QJSEngine 2026-09-16 15:27:49 +02:00
Laurent Trinques 38e504fb7e CI windows-build add mingw-w64-ucrt-x86_64-qt6-declarative for QJSEngine 2026-09-16 15:22:13 +02:00
Laurent Trinques 4aef4d17d2 git submodule update --remote elements 2026-09-16 14:58:55 +02:00
Laurent Trinques c89243a36f Merge pull request #894 from jp2images/fix-macos-fusion-dark-palette
Give Fusion a palette it can draw with on macOS
2026-09-16 14:21:13 +02:00
ispyisail 4a83521306 Fix scripting review findings: import, save, backups, timeout
Addresses scorpio810's review of PR #891:

- addElement() now imports the element into the project's own embedded
  collection first (QETProject::importElement()), same as the
  drag-from-collection-panel path -- otherwise the saved .qet referenced
  a definition outside the project, missing on another machine. Also
  found and fixed while wiring this up: ElementsLocation::setPath()
  forces any path to embed:// once a non-null project is passed, so the
  unconditional ElementsLocation(locationPath, m_project) this method
  used before silently broke every common://custom:// call. Refuses
  on an import-collision case that would otherwise reach
  QETProject::importElement()'s own modal ImportElementDialog, with
  nobody there to answer it in a script.
- addElement()/setElementPosition()/moveElement()/deleteElement() refuse
  on a read-only project, matching their GUI equivalents.
- save() goes through QETProject::write() when no path is given (read
  -only handling, saveddate/savedtime, QSaveFile via writeXmlFile()) and
  QET::writeXmlFile() directly for an explicit output path, instead of a
  plain QFile that skipped all of that.
- Interactive "Run Script...": exports briefly disable project backups
  around the temporary QETProject CLIExport::run() opens on the same
  file, so it doesn't race the real open project's own KAutoSaveFile.
  Restored right after -- the headless --run path is untouched, since it
  depends on backups staying off for the whole run.
- A watchdog thread now calls QJSEngine::setInterrupted() after 30s,
  so a runaway script (`while(true){}`) can't freeze the GUI or hang a
  CI job forever. First version used sleep_for() and blocked every run,
  fast ones included, for the full 30s on join() -- caught by testing a
  one-line script, fixed with wait_for() on a condition variable so a
  script that finishes early wakes the watchdog immediately.
- Interactive script errors now also show a QetMessageBox, not just
  stderr (invisible on Windows).
- Ran update_translations (lupdate) to pick up the 37 strings this
  feature had not yet added to the .ts files.

Not applied: wrapping the whole script run in one undo macro. It would
make the script's own qet.undo()/qet.redo() calls silent no-ops for the
run's duration -- QUndoStack ignores undo()/redo() while a macro is
open -- which would break that already-shipped, explicitly requested
capability to get one convenience Ctrl+Z instead.

Verified: Qt 6.10.2, builds clean, ctest 6/6. Ran each fix against a
real project: addElement() on a common:// path now succeeds and the
saved file embeds the definition (embed://import/...); read-only
project refuses addElement(); save("") and save(otherpath) both
produce a correctly embedded file; `while(true){}` under --run is
interrupted at 30s where it previously hung forever, and a normal
script now exits in ~0.3s instead of blocking for the full timeout
budget.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 22:28:06 +12:00
Kellermorph a7f406cd44 Merge remote-tracking branch 'origin/master' into fix-crossref-textfield 2026-09-16 11:49:55 +02:00
Kellermorph dad6206386 Address review feedback: parentGroup guard, ELMT_XREF constant, indentation
- Add !parentGroup() guard to stale xref removal in updateXref()
  to prevent deleting xref data that ElementTextItemGroup::updateXref()
  just stored for a grouped text item.
- Replace QStringLiteral("xref") with QETInformation::ELMT_XREF
  in linkelementcommand.cpp and masterelement.cpp.
- Fix indentation in plclinkwidget.cpp.
2026-09-16 11:49:49 +02:00
Laurent Trinques 451f8c3296 Merge pull request #892 from jp2images/fix-terminal-destructor-snapshot
Delete a terminal's conductors from a snapshot of its conductor list
2026-09-16 11:49:40 +02:00
Laurent Trinques 43a1f6a1a9 Merge pull request #856 from Kellermorph/fix-copy-page
Fix text position shift when duplicating diagram pages
2026-09-16 11:44:18 +02:00
Jeff Patterson b208a4f131 Show the text color as a swatch and keep the terminal plan preview on white
The Selection properties "texts" tab painted the color value's own text in
that color, so the default black was unreadable on a dark palette. Show a
swatch in the cell instead and leave the text in the palette's color.

The terminal plan preview draws black ink with a white brush, like the
printed page it previews, on whatever background the view inherits from
the palette. Give the view a white background.
2026-09-16 04:39:31 -05:00
Jeff Patterson 1c8e7ed052 Give Fusion a palette it can draw with on macOS
main.cpp has forced the Fusion style on macOS since 2019, but the palette
still came from Qt's macOS platform theme, which is built for the native
style. It hands Fusion a Window, Button and Base that are the same color,
a Dark lighter than Light, and in dark mode an Inactive ButtonText of
black. Fusion derives its frames, gradients and indicators from those
roles, so fields had no edges, the radio buttons in the text alignment
dialog vanished, and the "Handles" combo box in the diagram editor
toolbar drew its text black on a dark combo the moment the window lost
focus. Light mode had the same flatness, with Window, Button and Base
all white.

Add QET::Palette (sources/qetpalette.{h,cpp}) with a light and a dark
palette laid out the way Fusion expects, and install one from
QETApp::initStyle() on macOS when the running style is Fusion, choosing
by the platform palette's lightness and keeping the platform's accent
color when it reads at 4.5:1. On macOS the base palette is now applied
whether or not "use system colors" is checked, since the system palette
cannot be drawn by Fusion; that setting only decides whether style.css
is layered on top (#467). On Qt 6.5+ the app follows the OS light/dark
switch through QStyleHints::colorSchemeChanged.

Other platforms are untouched: Fusion is Qt's default style on Linux
desktops without a platform theme, and the palette there carries the
user's desktop colors. Making Fusion and this palette the default
everywhere is discussed in #870.

tests/qttest/tst_qetpalette checks every text role pair at WCAG 4.5:1
(3:1 disabled), that Inactive equals Active, and paints a Fusion combo
box, radio buttons, buttons and a line edit on the offscreen platform to
measure the ink against its background. Set QET_TEST_DUMP_DIR to keep
the rendered images.
2026-09-16 04:39:31 -05:00
Kellermorph e6ffc8b622 Address all review feedback 2026-09-16 11:37:27 +02:00
Laurent Trinques 9004db1d99 Reload element drawings: keep old drawing on failure, skip elements whose geometry changed (#802)
- Element::reloadPicture() now returns a ReloadPictureResult and never
  clears the current drawing before a successful rebuild: a missing or
  unreadable definition leaves the element as it was instead of blank.
- Elements whose size, hotspot or terminals (added, removed or moved)
  differ from the new definition are not redrawn: the new drawing would
  no longer match their bounding rect and live terminals.
- The action lists those elements and warns that they must be removed
  and re-inserted, which deletes the conductors already connected to
  them.
- Status tip states the action is not undoable.
2026-09-16 21:35:17 +12:00
Jeff Patterson edf483d88f Delete a terminal's conductors from a snapshot of its conductor list
Terminal::~Terminal() called qDeleteAll(m_conductors_list) on the live
member. Each Conductor destructor calls removeConductor() on both of its
terminals, and that removes the conductor from the same list qDeleteAll
is iterating. Mutating a QList while iterating it is undefined
behaviour; with two or more conductors on one terminal (terminal strips,
bridged terminals) it can skip a delete or delete one conductor twice,
which leaves another conductor's terminal1/terminal2 pointing at freed
memory.

The pattern dates from a00404bc9 (2021), which replaced a foreach loop
(iterating an implicit copy) with a direct qDeleteAll. It went unnoticed
until the deterministic sort keys added to Diagram::toXml() in #844
started reading pos() on both terminals of every conductor on every
save, including the periodic backup, which turned the stale pointer into
an EXC_BAD_ACCESS in QGraphicsItem::pos() while deleting an element.

Copy the list first and delete from the copy, restoring the pre-2021
behaviour. An isolated regression test (a hub terminal with 2 to 8
conductors, under AddressSanitizer) did not trigger the failure with the
old code, so none is included; the crash analysis and that attempt are
recorded in jp2images/qelectrotech-source-mirror#1.
2026-09-16 04:27:27 -05:00
ispyisail 7a85e2592c Fix stale geometry in LineEditor on multi-select
LineEditor::setPart() no-ops (skipping updateForm()) when the part
passed in is already m_part. That is harmless when the editor widget
is torn down between selections, but this branch keeps the same
editor instance installed across selection changes instead of
recreating it, so a line already shown alone can also be
parts.first() of a later multi-selection -- and the x1/y1/x2/y2
spinboxes then keep showing whatever was in them before, not this
selection's actual first line.

setParts() now always calls updateForm() after setPart() succeeds,
closing the gap regardless of the identity check inside setPart().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 21:27:22 +12:00
ispyisail 7183ed3d99 Merge remote-tracking branch 'upstream/master' into fix/675-line-editor-stale-refresh 2026-09-16 21:21:08 +12:00
ispyisail 0920e82188 Add geometry editing, undo integration, and navigation to scripting
Extends the scripting surface from the previous commit with exactly
the three things explicitly scoped out there, per follow-up direction:
editing geometry, undo integration, and driving the GUI -- the last
one narrowed to select/zoom/message after discussion, since "invoke
any menu action by name" would let a script trigger a modal
QDialog::exec() with nobody there to dismiss it, the same hang class
investigated for bugtracker #882.

## New capabilities

- addElement/setElementPosition/moveElement/deleteElement, through the
  same undo commands the GUI itself uses: AddGraphicsObjectCommand
  (the same one drag-from-collection-panel placement uses),
  QPropertyUndoCommand on the standard `pos` property, and
  DeleteQGraphicsItemCommand (refuses a non-deletable terminal, same
  as the Delete key).
- undo/redo/canUndo/canRedo against the project's real QUndoStack --
  the same one QETDiagramEditor's Ctrl+Z is wired to via
  undo_group.activeStack(), not a parallel mechanism.
- selectElement/deselectAll (scene state, no view required -- works
  headless), zoomFit/zoomToContent/zoomReset (need the active
  DiagramView, so false headless where there is nothing to zoom), and
  showMessage (a modal QET::QetMessageBox::information -- safe headless
  because non-interactive mode is already on for the whole process
  before any script runs).

## Two real bugs caught by testing this, not assumed away

1. save() was still going through the same reopen-from-disk path as
   every export method: it opened a *second*, unmodified copy of the
   project from its file on disk and rewrote that. addElement() and
   friends operate on the live in-memory project, so nothing they did
   ever reached the saved file -- an element counted correctly in
   memory and then silently vanished from the output. Fixed by having
   save() write m_project->toXml() directly, the only method that
   touches the live instance rather than a fresh copy of the file.

2. A script calling setElementPosition() then moveElement() on the
   same element produced a saved position that didn't match either
   call, and undo/redo didn't step through them independently. Traced
   to QPropertyUndoCommand::mergeWith() (pre-existing, not new):
   consecutive commands on the same object+property merge when their
   text() also matches, and both calls build the identical "Déplacer
   %1" text for a given element -- exactly the same collapsing
   dragging an item repeatedly gets. Not a bug in the new code; a
   wrong assumption in the first test. Re-verified against the
   correct, merge-aware expectation: add -> merged move -> undo (back
   to first position) -> undo (element removed) -> redo (element back)
   -> redo (merged move reapplied) landed at the exact predicted final
   position, read back from the saved XML.

## Verified

Qt6, build clean from a fresh reconfigure, ctest 6/6.

- Headless: addElement returns a real uuid and the count updates;
  select/set-position/move all report correctly; the merge-aware
  undo/redo/save round trip above, confirmed against the saved file's
  actual XML, not just in-memory counters.
- Corpus: the existing read-model smoke script re-run against all 24
  shipped example projects on the fixed binary, 0 failures.
- zoomFit correctly returns false headless (no view to act on),
  confirming the "narrowed GUI-driving" scope holds in code, not just
  in the doc comment.
- GUI: running the add-only script via "Exécuter un script..." marked
  the project [modifié] in the title bar, the same change-tracking
  path a manual edit goes through -- consistent with the undo command
  actually being pushed onto the project's real stack rather than some
  side channel invisible to the rest of the application.

Refs #162.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 20:20:58 +12:00
ispyisail eba258f6cd Add JavaScript scripting: --run and "Run Script..." (bugtracker #162)
Following up on my own comments there: a deliberately small, mostly
read-only scripting surface, exposed to scripts as a single global
`qet` object (QetScriptApi) built on QJSEngine rather than an embedded
Python interpreter -- no new toolchain to package (QJSEngine ships in
every Qt SDK QET already targets, via the Qml module), no GIL, no
version pinning, automatic reflection of the QObject-derived core
classes' own methods with no hand-written binding layer.

## What a script can do

- Read the model: project title, file path, folio count/titles,
  element/conductor counts per folio.
- Trigger the same operations the --export-* CLI flags already do
  (pdf/png/svg/cables/wires/bom/wiring/nets/links/info), plus
  set-titleblock and save -- thin wrappers around CLIExport::run(),
  reusing its already-tested logic rather than duplicating it.

Deliberately NOT in this version: creating or editing diagram
geometry, undo integration, driving the GUI. All explicitly out of
scope per the discussion on #162.

## Two entry points, both built and tested

- `qelectrotech --run script.js project.qet` -- headless/CI.
- Projet > "Exécuter un script..." -- an interactive macro against the
  currently open project. Export/save calls act on the project's file
  on disk (see QetScriptApi's class comment for why), so unsaved GUI
  edits aren't visible to the script; save first if that matters.

## Optional dependency, not a hard requirement

Qt::Qml is probed the same way QtPdf already is in this codebase:
QUIET, non-fatal, behind a QET_HAS_SCRIPTING compile definition. A
build without it compiles and links identically; the CLI flag and
menu action are simply absent (main.cpp) or compile to a clear
"not available" stderr message rather than silently disappearing
(qetscripting.cpp), matching the existing QtPdf pattern rather than
introducing a new one.

One real bug caught building this, not assumed away: my first pass
conditionally excluded the new source files from QET_SRC_FILES behind
`if(QET_HAS_SCRIPTING)` inside qet_compilation_vars.cmake -- but that
file is included before QET_HAS_SCRIPTING is set in the top-level
CMakeLists.txt, so the variable didn't exist yet at that point and the
files were silently never compiled, only caught by an undefined-symbol
link error. Fixed by following the QtPdf file's own precedent:
compile the files unconditionally, guard their Qt::Qml-dependent
content internally instead.

## Verified

Qt6, build clean, ctest 6/6.

- Headless: a script reading project/folio/element/conductor counts,
  calling exportInfo() and exportPdf() against a real project --
  correct JSON, a real single-page PDF confirmed with `file`.
  Error paths: a thrown script exception reports file:line:message and
  exit 1; missing script/project arguments exit 2 (matching
  CLIExport's own usage-error convention); a missing project file is
  reported and does not hang.
- Corpus: the same read-model script run against all 24 shipped
  example projects, 0 failures.
- GUI: "Exécuter un script..." opens a real file dialog filtered to
  *.js, running the picked script against the live open project
  produced the exact expected JSON export file, and the application
  was still fully responsive afterward.

Refs #162.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 19:44:14 +12:00
ispyisail 2eed3acd3d Merge pull request #723 from IBSYSLevi/feature/cabinet-layout
Added width/height/depth properties to elements
2026-09-16 18:51:34 +12:00
ispyisail abd5e8978a Fix bugtracker #734: stop routing a bridge conductors don't need
generateConductorPath()'s cas "3"/"4" branches each insert a two-point
bridge along one axis, at a coordinate on the other axis computed as
the midpoint of depart/arrivee and then snapped to the routing grid by
a loop that walks it strictly downward until it divides evenly.

When depart and arrivee already agree on the axis the bridge would run
along, no bridge is needed at all -- the midpoint starts on the
correct, already-shared value. But the snap can still walk it off that
value, or, when it happens to already sit on the grid, the two bridge
points just duplicate depart and arrivee outright. Either way the
conductor renders with an unnecessary out-and-back excursion or a
small looping detour at a join that needed neither.

Fix: skip the bridge in exactly that degenerate case, per branch, on
the axis that branch actually bridges on. descendant and montant are
not mirror images of each other in which axis each cas guards on --
each site says so.

Verified against the report's own canonical reproduction
(qet_bug_repro_resaved.qet from the linked gist): 1 self-retracing
path -> 0. Full corpus of 24 shipped example projects (stored
segments, as shipped, not regenerated): 74 -> 65 self-retracing
conductor paths, zero regressions -- only two files changed, both
improved.

One disclosed trade-off, found while checking for regressions: in
schema_unifilaire_voltaique2.qet, 8 terminal pairs closer together
than twice the extension length (docked stubs already cross before
any bridge is considered) go from an existing small rectangular-loop
artifact to a straight out-and-back retrace covering the full gap --
same count of defective paths (8 -> 8), a different shape, still no
connectivity change either way. Not chased further; a real fix for
that narrower "crossed stubs" case is a separate piece of work.

The shipped affuteuse_250h.qet's own 12 self-retracing paths (verified
by stripping all 185 stored <segment> elements and forcing full
regeneration) are unchanged by this fix -- they are a structurally
different point-count signature (a 4-point back-and-forth reachable
from cas "1"/"2", not the cas "3"/"4" grid-snap bridge this fixes),
consistent with what the issue thread already flagged as a separate,
undiagnosed mechanism.

Refs #734 (own root-cause comment, 2026-08-13).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 17:17:03 +12:00
ispyisail 43d27a9563 Add "Reload element drawings" to refresh placed elements (#802)
A placed element is drawn once from its definition at construction --
buildFromXml() only turns terminal/input/dynamic_text tags into live
child objects, every other primitive (line, rect, ellipse, polygon,
arc, text) is pre-rendered into a QPicture by ElementPictureFactory,
cached forever under the element's uuid with no invalidation path
anywhere in the codebase. Edit and save a symbol's drawing and every
already-placed instance keeps showing the old one until the project
is closed and reopened.

Fix, scoped to what is safe to do without ever risking a conductor or
a dynamic text's per-instance state:

- ElementPictureFactory::dropCache(location) forgets the cached
  drawing for one location, so the next fetch rebuilds it from the
  definition's current content.
- Element::reloadPicture() re-fetches and repaints one instance.
- Projet > "Recharger les dessins des éléments": walks every diagram,
  drops each distinct location's cache once, then reloads every placed
  instance.

Deliberately does not touch terminals or dynamic texts -- a definition
whose terminal positions moved still needs the existing remove-and-
reinsert workflow, since terminals are what conductors are attached to
and a wrong guess there would silently misconnect wires.

Verified: build clean, ctest 6/6. Triggered the new action on a real,
densely-wired project (76 elements) via exact keyboard-menu navigation
cross-checked against the menu's own addAction order -- ran to
completion, correct confirmation dialog, no crash, diagram unchanged
and uncorrupted afterward. Could not complete a live edit-and-watch-
it-update trace: opening the element editor on a selected item via
GUI automation was unreliable in this environment (same class of
friction as PR #888), and this sandbox has no file-based (common://)
element to mutate on disk as a shortcut -- every example project
embeds its elements. The mechanism itself is traced correct:
ElementsLocation::xml() for an embed:// location reads the project's
live in-memory collection DOM on every call, so a dropped cache
rebuilds from whatever was most recently saved.

Refs #802 (own analysis comment, 2026-08-31).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 16:24:44 +12:00
Laurent Trinques d3424db23d Merge pull request #883 from bhangart/persist-diagram-uuid
Persist the folio uuid, derived deterministically for legacy folios
2026-09-16 05:58:19 +02:00
Laurent Trinques 0d722b6033 Merge pull request #888 from ispyisail/fix/879-remember-conductor-color
Remember the F2 conductor color for the rest of the session
2026-09-16 05:52:29 +02:00
Laurent Trinques 96fac96b89 Merge pull request #884 from bhangart/persist-project-uuid
Persist the project uuid, derived from the file content for legacy files
2026-09-16 05:47:12 +02:00
ispyisail 1ec4f56a50 Remember the F2 conductor color for the rest of the session (#879)
The F2 color editor recolors one conductor, but the next one drawn
falls straight back to defaultConductorProperties -- the choice made
via F2 is lost the moment you place another wire, and lost again on
restart. LastUsedStyle already solves the same problem for shapes
(pen/brush) and free text (font), session-scoped and deliberately not
QSettings-backed; this extends it with a conductor color, following
the identical has/get/set shape.

F2's handler records the color after pushing its undo command.
Conductor's constructor -- the one place a new conductor's properties
are set from defaultConductorProperties -- overrides just the color
field when a session color has been recorded, leaving every other
default (style, thickness, text) alone.

Verified: build clean, ctest 6/6. Could not get a reliable headless
GUI trace of "F2 one wire, draw a new one, see it inherit the color"
-- drag-and-drop element placement under Xvfb was unreliable in this
environment (one attempt did nothing, another drew an unintended long
conductor undo didn't fully clear). The code path is otherwise
identical to the already-shipped shape/text mechanism this mirrors.

Refs #461.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 15:40:41 +12:00
Laurent Trinques 839324f231 Merge pull request #887 from ispyisail/fix/885-summary-custom-sql-mode
Fix bugtracker #885: preserve custom summary-table SQL on accept
2026-09-16 05:37:43 +02:00
ispyisail a756bc0722 Fix bugtracker #885: preserve custom summary-table SQL on accept
setQuery() restored the loaded SQL text into the line edit but never
restored the "Edit SQL query" checkbox, so a custom query (a join, a
subquery, a view other than project_summary_view) displayed correctly
while the widget stayed in built-in mode. queryStr() only consults that
checkbox, so accepting the dialog without touching anything silently
replaced the custom query with a freshly generated one.

Detect it instead of trusting a flag that was never set: after parsing
columns from the loaded query, rebuild the query those columns would
produce and compare it against what was loaded. A mismatch means the
widget cannot reconstruct it, so it must be user-written -- check the
box and keep the literal text.

Verified against both cases this has to get right, not just the one
in the report: a genuinely custom query (join) now round-trips through
an untouched accept+save byte-for-byte, and a plain built-in query
written before the #238 pos-ordering fix (examples/industrial.qet, no
ORDER BY clause) is classified as custom rather than silently gaining
an ORDER BY it didn't have -- it round-trips unchanged rather than
being corrupted, though its column picker is now disabled until a user
rebuilds it by hand.

Based on the patch attached to #885.
2026-09-16 13:09:48 +12:00
Beat Hangartner 2269a4641b Persist the project uuid, derived from the file content for legacy files
QETProject::m_uuid was created in the constructor and never written, so
a project got a new uuid every time it was opened. Inside a running
instance it is only used to name the SQLite connection, but nothing
outside the instance could tell which project a file belongs to.

Motivation

A .qet file is increasingly handled by tools outside QElectroTech: Git
repositories on GitHub or GitLab, cloud storage, key-value stores,
per-project locks. All of them need a stable key for "this project":

- The file name and path are not stable: files get renamed, moved,
  checked out in different places.
- The project title is user-editable and not unique.
- Folio uuids (persisted separately) are only unique within their
  project; keying folios globally needs a project identifier as well,
  e.g. projects/{projectUuid}/folios/{folioUuid}.

Change

Write the uuid as an attribute of <project> and restore it in
QETProject::openFile(), right after parsing and before the project is
built from the XML. Older versions ignore the attribute, so files stay
readable in both directions.

The project database is not affected: it takes its connection name
from the uuid created at construction (m_uuid is declared before
m_data_base), before the file is read. Two open projects carrying the
same persisted uuid therefore still get distinct connections.

Files without a uuid: why not a random one

Keeping the random uuid created by the constructor and saving it
conflicts with #754 / #779: saving an unmodified project must give the
same bytes every time. Every example project predates the attribute.
Measured on the 24 example projects (resaved 3x each from the same
original, QT_HASH_SEED=0 so that QDom's attribute order is stable,
isolated HOME per run):

  upstream master              23/24 byte-identical
  persist, random uuid          0/24
  persist, derived uuid (this) 23/24

The remaining project, schema_indus.qet, differs only in element uuids,
the known residual #779 leaves for elements; its project uuid is
stable.

Instead, a project file without a uuid gets a name-based (version 5)
uuid derived from the raw content of the file:

  QUuid::createUuidV5(<fixed QET project namespace>,
                      "qet-project-legacy\n" + file content without CR)

- The same file always yields the same uuid, so resaving an unmodified
  legacy project stays reproducible.
- Different projects practically never share a uuid, because any
  difference in content gives a different one. This is unlike folios,
  where only data such as title and position could be used; the raw
  file bytes are stable input for the whole project.
- Carriage returns are dropped before hashing. QFile's Text mode already
  strips them on Windows but not elsewhere, and git's autocrlf can
  change them on checkout; either way the uuid is the same on every
  platform.
- The uuid is derived once, at load time, and saved from then on. After
  that it is read, never recomputed: renaming the project, editing it
  or changing it in the same session as the migration does not change
  it.
- Two people opening the same legacy file on different branches get the
  same project uuid.

The namespace uuid is fixed in the code and must never change, or every
legacy project would get a different uuid.

Known limitations, open for discussion

- Copies share the uuid. Two byte-identical legacy files get the same
  uuid (examples/cablage-eclairages_sikli-v5.qet and
  câblage-éclairages-sikli-v5.qet are such a pair), and so does a
  migrated file copied in the file manager or saved with "Save as".
  That is what identity means for a copy, and the same happens with Git,
  but a tool that treats the uuid as globally unique has to cope with
  it. Regenerating the uuid on "Save as" could be a follow-up, if that
  is the preferred behaviour.
- A legacy file that differs from another only in formatting (e.g.
  re-indented) gets a different uuid. The two sides of a merge only
  agree if they started from the same bytes, which is the normal case.

Tests (Qt 6.4, offscreen, qelectrotech --resave / --set-titleblock /
--info)

- 24 example projects, 3 resaves each from the same original: results
  above; the project uuid is identical across runs. All 24 uuids are
  distinct, except the byte-identical pair mentioned above.
- Resaving an already migrated file is byte-identical to the first
  output.
- The same legacy file with CRLF line endings gets the same uuid as
  with LF.
- Changing the project title in a migrated file keeps its uuid.
- Migrating and modifying in the same run (--set-titleblock on a legacy
  file) gives the same uuid as a plain resave.
- Re-indenting a legacy file gives a different uuid (expected).
- A migrated file opened with upstream master loads normally; the
  attribute is ignored and dropped on save.
- --info on a migrated file still works.

Refs #754, #779

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDyt4txaott5JyPNGQaeVp
2026-09-15 23:34:24 +00:00
Beat Hangartner 7cd04f2a14 Persist the folio uuid, derived deterministically for legacy folios
Diagram::m_uuid was created in the constructor and never written, so a
folio got a new uuid on every load. Inside a running instance that is
enough (the project database keys on it), but nothing outside it could
tell which folio is which: in the file, folios were only identified by
their position.

Motivation

More and more .qet projects live in version control -- a Git repository
on GitHub or GitLab, reviewed through pull requests, sometimes edited
by several people -- or are synchronised through a cloud or key-value
store. A .qet file is plain XML, so in principle it can be diffed,
merged and split up, but only if the same folio can be recognised in
two versions of the file. Today it cannot:

- Inserting, deleting or reordering a folio shifts every following
  <diagram> element. A line-based diff, and GitHub's review view, then
  pair up unrelated folios and show far more change than was made.
- A three-way merge of two branches that both touched the project has
  no way to match "folio 3" on one side with "folio 3" on the other if
  either side reordered folios.
- Any tool that wants to say "folio X changed in this commit", keep
  per-folio history, lock a single folio, or store folios as separate
  objects has nothing stable to key on. The title and the folio number
  are user-editable and not unique.

Element uuids are already persisted and used for cross-folio links, so
the file format already relies on uuids for identity; the folio itself
was the missing piece. A stable folio uuid is the prerequisite for
later work towards better version control support: per-folio diffs and
locks (check-out / check-in), and possibly storing a project as a
directory with one file per folio.

Change

Write the uuid as an attribute of <diagram> when the whole content is
saved, and restore it first thing when the project is loaded, before any
item is created. Older versions ignore the attribute, so files stay
readable in both directions.

Folios without a uuid: why not a random one

The obvious migration -- keep the random uuid created by the
constructor and save it -- conflicts with #754 / #779: saving an
unmodified project must give the same bytes every time. Every example
project predates the attribute, so each load would invent different
uuids and write them out. Measured on the 24 example projects (resaved
3-4x each from the same original, QT_HASH_SEED=0 so that QDom's
attribute order is stable, isolated HOME per run):

  upstream master              23/24 byte-identical
  persist, random uuid          0/24
  persist, derived uuid (this) 23/24

The remaining project, schema_indus.qet, differs only in element uuids,
the known residual #779 leaves for elements; its folio uuid is stable.

This is the same problem #779 solved for conductors by not writing an
invented uuid back at all. That is not an option here: legacy folios
would never get a persistent uuid, which is the whole point of the
change. Instead, a folio without a uuid gets a name-based (version 5)
uuid, derived only from data read from the file:

  QUuid::createUuidV5(<fixed QET folio namespace>,
                      "legacy" + project title
                               + position of the folio in the file
                               + folio title)

- The same input file always yields the same uuids, so resaving an
  unmodified legacy project stays reproducible.
- The uuid is derived once, at load time, and saved from then on. After
  that it is read, never recomputed: renaming, reordering or editing
  the folio later does not change it. Renaming in the same session as
  the migration does not change it either, since it was derived from
  the title as loaded.
- Two people opening the same legacy file on different branches get
  the same uuid for each folio, even if one of them reorders or renames
  folios before saving. With random uuids the two branches would
  disagree about every folio and a later merge could not match them.
- The folio content is deliberately not part of the name: QDom keeps
  attributes in a hash whose iteration order changes between runs, so
  hashing the content would need a canonical form for no real gain.

Folios are only guaranteed unique within their project. Two unrelated
legacy projects with the same title and the same first folio title get
the same uuid for that folio; anything keying folios globally has to
combine the folio uuid with a project identifier. (The project uuid is
not persisted yet; that is a separate change.)

Duplicated uuids

A hand-edited or merged file can contain the same uuid twice, e.g. a
folio copied by duplicating its XML block. Since the uuid is used as a
key, the second folio gets a derived uuid as well ("duplicate" + the
clashing uuid + the same inputs as above), so this case is
reproducible too. Should a derived uuid ever be taken already, which
takes a hand-crafted file, the name is salted with a counter until it
is free.

The namespace uuid is fixed in the code and must never change, or every
legacy folio would get a different uuid.

Tests (Qt 6.4, offscreen, qelectrotech --resave / --set-titleblock)

- 24 example projects, 3-4 resaves each from the same original: results
  above; all folio uuids identical across runs, no duplicates within
  any project.
- Resaving an already migrated file is byte-identical to the first
  output.
- Renaming a folio in a migrated file keeps its uuid.
- Swapping two <diagram> blocks in a migrated file: each uuid moves
  with its folio.
- Migrating and renaming in the same run (--set-titleblock title=...
  on a legacy file) gives the same uuids as a plain resave.
- A file with a duplicated uuid: the second folio gets a new uuid, the
  same one on every run.
- A migrated file opened with upstream master loads normally; the
  attribute is ignored and dropped on save.

Refs #754, #779

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDyt4txaott5JyPNGQaeVp
2026-09-15 22:09:10 +00:00
Kellermorph c2f405c32d Add Hide full masters checkbox to slave linking widget 2026-09-15 21:48:47 +02:00
Laurent Trinques 265aa44320 Merge pull request #858 from Kellermorph/checkbox-plc-connection
Add Hide linked elements checkbox to PLC link widget
2026-09-15 18:44:24 +02:00
Laurent Trinques 395c6f6602 Merge pull request #876 from ispyisail/fix/keyboard-context-menu
Give the keyboard the folio's context menu, not a generic one
2026-09-15 16:15:04 +02:00
Laurent Trinques fd38f55724 Merge pull request #874 from ispyisail/feature/diagram-selection-shortcuts-v2
Add Tab selection cycling and select-all conductors/text fields
2026-09-15 10:20:28 +02:00
Laurent Trinques 85ed1b8a2a Merge pull request #878 from ispyisail/feature/paste-follows-cursor
Paste under the cursor, and let it be positioned before it lands
2026-09-15 10:16:06 +02:00
Laurent Trinques 607eb4b1ea Merge pull request #871 from ispyisail/test/ipc-open-forwarding-regression
Add a regression test for the forwarded-file use-after-free
2026-09-15 09:55:34 +02:00
Laurent Trinques 1e124450f2 Merge pull request #875 from ispyisail/fix/keyboard-reachable-drawing-tools
Put the drawing tools in a menu so they can be used without a mouse
2026-09-15 09:51:23 +02:00
Laurent Trinques 4d70fcf35c Merge pull request #877 from ispyisail/fix/f10-opens-menubar
Open the menu bar on F10, the key people expect
2026-09-15 09:46:24 +02:00
ispyisail 55c2c0df9d Paste under the cursor and let it be positioned before it lands
Ctrl+V pasted in place, which put the copy exactly on top of the original.
Nothing appeared to happen: the only clue was a doubled outline, and the
copy had to be dragged off the original to be seen at all. The cursor was
ignored entirely.

Ctrl+V now starts a placement. The items appear under the cursor and follow
it until a left click or Return drops them; Escape or a right click takes
them away again. That is the same interaction as placing a new element, so
paste behaves like every other way of putting something on a folio, and the
copy lands where the user is looking.

Implemented as a DiagramEventInterface beside the existing add-element and
add-macro tools. The pasted items are the real ones from the start rather
than a preview: Diagram::fromXml creates them exactly as before, this class
moves them, and PasteDiagramCommand is pushed only once they are dropped.
PasteDiagramCommand's first redo() deliberately does not add items to the
scene -- it assumes fromXml already did -- so pushing it on commit adopts
them rather than duplicating them. One copy of the paste logic, and a
cancelled paste leaves nothing on the undo stack.

Conductors are not moved directly; they are drawn from their terminals and
follow the elements they attach to. On cancel they are removed before the
elements, so none is left in the scene holding a pointer to a freed
terminal.

Verified by counting elements in the saved file rather than by eye:
56 to start, 56 after paste-then-Escape, 57 after paste-then-drop, and 56
again after undo. Save determinism run against this build: pass, no
regressions against baseline. Tests 5/5 on Qt 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 17:07:15 +12:00
ispyisail b94b244919 Correct the comment: say what was measured, not what was assumed
Two claims in the previous comment were wrong.

"Qt implements F10 on Windows but not on X11" was inference I cannot test
here. What is measured is narrower and enough: QMenuBar given Key_F10
directly leaves it unaccepted, and sent to the window the key never reaches
the menu bar at all, because a key press goes to the focused child widget.

"&Édition takes É, which is not on a UK or US keyboard" was wrong outright.
It came from running an uninstalled binary, which cannot find its .qm files
and falls back to the French source strings. With translations loaded the
menus read File, Edit, Project, Display, Settings, Windows, Help, and Alt+E
opens Edit.

The comment now also says plainly that this is convenience rather than
access: Alt tap focuses the bar and Alt with a letter opens a menu, both
verified working, so the menus were already reachable without a mouse. F10
is the key people reach for out of habit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GtMZqGEiUMvDBqcFvVG2vb
2026-09-15 15:51:24 +12:00
ispyisail bd6bed8d61 Open the menu bar on F10, and add a test that can answer whether it works
F10 opens the menu bar in most applications and is the usual way to reach
the menus without a mouse. Qt provides this on Windows but not on X11, so on
Linux the key did nothing and the press fell through to whichever widget had
focus. It matters more here than it might elsewhere: "&Édition" takes É for
its own letter, which is not on a UK or US keyboard, so that menu has no
direct Alt route at all.

A window-context QShortcut rather than a key handler -- key presses go to
the focused child widget, so a keyPressEvent() on the window would never see
F10 while the canvas or a panel has focus.

tests/qttest/tst_menubarkeyboard.cpp covers three things: that Alt and a
letter opens a menu (the control), that plain F10 does nothing in Qt itself
(which is why the shortcut exists, and which will fail loudly if a future Qt
starts handling it), and that the shortcut mechanism opens the bar.

It uses QTest instead of driving a real X server for a specific reason.
xdotool on Xvfb delivers every function key with Alt held: a Qt key logger
shows Key_F10 arriving with modifiers == Qt::AltModifier. --clearmodifiers,
keydown/keyup pairs, --window targeting and flattening the keycode with
xmodmap all made no difference. Two rounds of GUI automation therefore gave
confident, wrong answers about F10 -- first that it was broken, then that
this very fix did not work. QTest posts the event straight to the widget, so
the key arrives as written.

What the test does not cover, since initCommonActions() calls
QETApp::instance() and constructing that pulls in the whole application: it
repeats the wiring rather than driving QETMainWindow. Confirming the real
window responds still needs someone to press F10 in a running QElectroTech.

Verified by breaking it: bound to F11 instead, the test fails. Qt 5 and Qt 6
both build clean, 6/6 tests on each.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:44:59 +12:00
ispyisail 6ed26358e8 Give the keyboard the folio's context menu, not an item's generic one
Pressing the Menu key (or Shift+F10) on a folio produced a bare
Undo/Redo/Cut/Copy/Paste/Delete/Select All menu with almost everything
disabled, instead of the menu a right-click gives.

A keyboard-raised QContextMenuEvent carries no useful position -- Qt does
not aim it at the selection. contextMenuEvent() passed the event to
QGraphicsView first, which handed it to whichever item held focus; that
item answered with its own default menu and accepted the event, so the
early return fired and the folio's menu was never built. Even past that,
the itemAt() lookup below would have used an unrelated point.

A keyboard-raised menu is now built directly rather than offered to the
items first, and aimed at the centre of the selection, or at the middle of
the view when nothing is selected. The mouse path is unchanged.

Measured on the same branch with only this change applied: before, the
menu carried 7 actions, all but one disabled; after, 16, positioned on the
selected element. Builds clean on Qt 5 and Qt 6, tests 5/5 on both.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:03:01 +12:00
ispyisail 6a2b3973bc Put the drawing tools in a menu so they can be reached without a mouse
The nine "Ajouter" actions -- text field, image, PDF, line, rectangle,
ellipse, polyline, curve, terminal strip -- were only ever added to
m_add_item_tool_bar, and the automatic conductor break only to
diagram_tool_bar. None carried a shortcut. A toolbar button has no key,
so someone working without a mouse could not add anything at all to a
folio.

They now appear in an "Ajouter" submenu under Édition, and the conductor
break beside m_auto_conductor in Projet, the setting it pairs with. The
actions themselves are untouched: a QAction can sit in a menu and a
toolbar at once, which is what m_depth_action_group -- created a few lines
away, and added to both its toolbar and menu_edition -- has always done.
That contrast is why this reads as an oversight rather than a decision.

Verified by driving the menus with the keyboard alone under Xvfb: Alt+F
opens the File menu, Down then Right crosses to Édition, and Right again
opens the Ajouter submenu with all eight actions this build compiles
(add_pdf is behind QET_HAS_QTPDF and absent on Qt 5).

Found with tools/keyboard-audit in the qelectrotech-docker harness, which
reports these ten and now reports none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 11:45:51 +12:00
ispyisail d9db6e59e7 Let Escape step back out of the folio, so Tab cannot trap keyboard users
Tab cycles the folio's items, which means focusNextPrevChild() has to
refuse the usual focus traversal. On its own that leaves someone working
without a mouse able to reach the drawing area and never leave it -- the
exact person the Tab cycling was added for.

Escape now steps back out in two stages: it drops the selection first,
then hands focus to the next widget. The one-shot m_releasing_focus flag
is what lets that second Escape through the override.

Verified under Xvfb: with an item selected, Escape clears it (193k pixels
change); a second Escape changes nothing visually; a Tab after that moves
widget focus in the toolbar (306 pixels) instead of selecting on the
canvas, which is the behaviour of a view that no longer holds focus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 11:32:42 +12:00
ispyisail 22469813fe Register the two new selection actions with ShortcutManager
This branch was cut on 31 July, one day before ShortcutManager landed
in 5275fb44f, so the two actions it adds were written before the
convention existed and are the only members of the selection group not
registered: select_all, select_nothing and select_invert all are.

Without this they never appear in the shortcuts configuration page, so
a user cannot bind a key to either of them.

Registered with an empty default sequence. They are menu actions and
neither has an obvious default worth claiming; the point of registering
them is that a user can bind one if they want. ShortcutManager stores
an empty default without setting a shortcut, and the conflict checker
already skips empty sequences.

Master merged in first, because ShortcutManager does not exist at this
branch's original base.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 09:32:50 +12:00
ispyisail 88823ea35f Diagram: Tab/Shift+Tab item-selection cycling + select-all-conductors/text-fields (#574)
Implements the second pillar of #574: keyboard-driven selection on the
diagram canvas.

Tab / Shift+Tab select the next / previous item on the current
diagram, cycling through items() (z-order) and wrapping at either
end. If nothing is selected, Tab selects the first item and
Shift+Tab the last. Skipped while a text item has focus, for the
same reason arrow-key movement already guards on !focusItem().
Candidates use the same "what counts as a real selectable diagram
item" filter (QetGraphicsItem / DiagramTextItem / Conductor) already
established by Diagram::invertSelection(), so the cycling order
always matches what a user could reach by clicking.

Getting Tab to actually reach the scene needed two separate fixes,
each independently discovered by empirical testing rather than
assumption:

- QWidget (DiagramView) intercepts Tab/Backtab for widget focus-chain
  traversal before generating a key event at all. Overriding
  DiagramView::focusNextPrevChild() to return false disables that.
- QGraphicsScene (Diagram) has its own, separate item-focus-chain
  traversal, checked before keyPressEvent() is ever reached. The
  obvious fix -- overriding Diagram::focusNextPrevChild() the same
  way -- silently does nothing on Qt 5, because
  QGraphicsScene::focusNextPrevChild() only becomes virtual in Qt 6
  (guarded by the QT6_VIRTUAL macro); a compile error surfaced this
  immediately when attempted directly, rather than shipping a fix
  that worked on Qt 6 and silently no-opped on Qt 5. Intercepting
  QEvent::KeyPress in Diagram::event() instead is virtual on every Qt
  version and sidesteps the scene's internal traversal entirely.

Also adds Diagram::selectAllConductors() / selectAllTextFields(),
wired up as two new actions in the existing select_all /
select_nothing / select_invert action group in
qetdiagrameditor.cpp, so they appear in the Edit menu and go through
the same QAction -> data() -> selectGroupTriggered() dispatch as the
existing selection commands.

Verified end-to-end in a real running session (Xvfb + xdotool) with
a multi-transistor schematic: Tab/Shift+Tab correctly move a single
selection forward/backward through elements and text fields
(confirmed via the properties panel updating to each new item and
the visual selection box moving on canvas); Tab/Shift+Tab from no
selection correctly select the first/last item; "Select all
conductors" and "Select all text fields" each correctly select every
matching item and deselect everything else.

See discussion #574.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 09:32:50 +12:00
ispyisail a106395679 Add Linux CI: build, unit tests, and the IPC regression gate
Nothing currently builds QElectroTech on Linux in CI, and nothing runs tests/
at all -- the existing workflows build Windows and generate documentation.

Adds one job: configure and build with Qt 6 Debug, run ctest, then run the
IPC open-forwarding regression test.

Verified by running the job's exact step sequence in a clean ubuntu:26.04
container, in both directions:

  with the #868 fix     5/5 unit tests pass, gate passes,  job green
  with the fix reverted 5/5 unit tests pass, gate fails,   job red

The unit tests passing in both arms is the point: the existing suite cannot
see this class of bug, which is what the gate is for.

Three choices that are not cosmetic:

- Runs in an ubuntu:26.04 container rather than on the runner. ubuntu-latest
  ships Qt 6.4; the gate needs 6.10.2, and whether the crash reproduces on
  6.4 has never been checked. A job that cannot go red is worse than none.
- Debug, not Release. The pre-fix commit survives every attempt built
  -O3 -DNDEBUG, so a Release job would never catch a regression here.
- An inconclusive gate run warns rather than fails. It means the crash path
  was not exercised, which proves nothing and is not the same as a
  regression; failing on it would make the job flaky rather than useful.

extra-cmake-modules and the KF6 libraries are installed rather than left to
FetchContent, which otherwise builds ECM from source and fails the configure
demanding Qt6 documentation tools.

Depends on the regression test added in #871.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 07:38:24 +12:00
ispyisail 9363e9bc2e Add a regression test for the forwarded-file use-after-free
Covers the crash fixed in #868: a file forwarded from a second instance was
opened inside SingleApplication's socket handler, so the backup prompt's
nested event loop ran while that handler was still on the stack.

Run by hand; no build system or CI changes.

    tests/ipc-regression/run.sh --binary build/qelectrotech

Validated in both directions on Qt 6.10.2: ceda1e082 (before the fix) crashes
3 times in 3 with exit 139, 199444b6 (after) survives 3 times in 3.

Three requirements are not obvious and are documented in the script:

- Qt 6 only. An unfixed Qt 5 build survives every attempt, so the script
  refuses to run on a Qt 5 binary rather than report a pass that cannot fail.
- A Debug build. The same unfixed commit survives every attempt built
  -O3 -DNDEBUG; whether a use-after-free faults depends on what the allocator
  does with the freed block.
- Dismissing the backup prompt is the step that triggers it. Left open, the
  stack never unwinds and nothing fails, which is why the bug was twice
  reported as not reproducible.

The test runs in its own sandbox on its own X display, works on a copy of the
project so backup files do not land in examples/, and cleans up after itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 07:09:16 +12:00
Kellermorph a5fe544415 Fix text position shift when duplicating diagram pages
duplicateDiagram() called restoreText() on every newly loaded
element.  Each setPlainText() inside restoreText() is wrapped in
m_block_alignment except the last one, so finishAlignment() ran on
elements whose positions came straight from the XML — shifting
center and right-aligned texts.

Fix: use toXml(true, true) which handles correctTextPos/restoreText
internally for Slave and Report elements only, and call restoreText()
on the target for those same element types to recalculate their text
positions for the actual resolved text.
2026-09-14 20:38:15 +02:00
Kellermorph 9bfe18807f Fix undo not restoring PLC slave fields and stale connections on position change
Two additional bugs found after the initial xref cleanup:

1. Undo after unlink did not restore PLC variables (PLC_TYPE,
   PLC_ADDRESS, etc.) because PlcLinkWidget::on_m_unlink_pb_clicked()
   never stored the current group index in the LinkElementCommand.
   makeLink() only populates PLC fields when group_idx >= 0, so
   the slave was re-linked but appeared empty. Fixed by reading the
   group index from the master via groupIndexForElement() and calling
   setGroupIndex() before unlinking.

2. m_update_slave_Xref_connection was only cleared inside the
   if(m_slave_Xref_item) block in the updateXref() cleanup path.
   For AlignHCenter (Text field) position, no m_slave_Xref_item
   is ever created — the connections are stored in
   m_update_slave_Xref_connection but never cleaned up on unlink.
   On re-link, the old stale entries prevented new connections from
   being established. Fixed by clearing the list unconditionally
   in the cleanup path.
2026-09-14 16:19:37 +02:00
Kellermorph 00e75604b0 Clear stale xref data from PLC slave elementInformations on unlink and position change 2026-09-14 12:04:17 +02:00
Kellermorph 3c1c29f6c3 connect searchfield 2026-09-13 21:32:34 +02:00
Kellermorph cafc5bcf6d Add Hide linked elements checkbox to PLC link widget 2026-09-13 20:36:59 +02:00
Levi Jetzer 02e6eceb65 Reject trailing-dot input like "12." in numeric elementInformation fields
numericInfoPattern()'s integer branch no longer allows an optional
trailing ".", so "12." is now Intermediate rather than Acceptable --
already-built hasAcceptableInput() guard then keeps it from being
stored, same as a lone ".". Previously it validated fine and got
saved verbatim, silently freezing in that form since nothing ever
normalized it afterward.
2026-08-21 15:22:41 +02:00
Levi Jetzer 7c941e4697 Guard numeric elementInformation fields against "." and normalize decimal comma
- ElementInfoWidget::currentInfo() now skips a field whose validator
  hasn't accepted its text (e.g. a lone "." mid-typing), which
  previously stored and later parsed to 0.
- New QETInformation::NumericInfoValidator rewrites "," to "." before
  validating, so 80,5 on a German/French keyboard no longer silently
  becomes 805. Used at both existing call sites.
- Restored the header's #1/#2/#3 doc comment (was reflowed into a
  run-on paragraph by a previous edit).
2026-08-21 15:16:36 +02:00
Levi Jetzer 5dbb9f28de Fix numeric field validation gaps found in review
Address feedback on the width/height/depth elementInformation fields:

- The regex accepted "." alone as a complete value ([0-9]* permits
  zero digits on both sides), meaning a field could be committed with
  a literal "." saved to the XML. Require at least one digit either
  before or after the separator.
- The same pattern was duplicated verbatim in elementinfopartwidget.cpp
  and elementpropertieseditorwidget.cpp's EditorDelegate. Factored into
  QETInformation::numericInfoPattern(), a single shared definition both
  call sites now use.
- Tightened the pattern to at most 2 decimal places (down from 4) to
  match the precision actually meaningful for these fields.

Not changed, by design:
- Storage stays a plain string, consistent with every other numeric
  elementInformation field (quantity etc.) in this codebase -- values
  round-trip through XML text regardless, so a long/micron
  representation wouldn't avoid the string<->number conversion, only
  relocate it.
- No decimal-comma normalization needed: with the fixed pattern, only
  digits and "." are ever accepted at the keystroke level, so an
  alternate separator can't enter the field in the first place.
2026-08-12 08:43:42 +02:00
Levi Jetzer c6e7c5d371 Fixed issue of taking wrong variable (key instead of key_) in elementinfopartwidget.cpp for regex checker and helping info (tooltip, placeholder) 2026-08-11 18:28:29 +02:00
Levi Jetzer 7347322221 Replace QDoubleValidator with QRegularExpressionValidator on width/height/depth fields
The selection properties dock (elementinfopartwidget.cpp) and the
element editor's information tree (elementpropertieseditorwidget.cpp's
EditorDelegate) both restrict the width/height/depth fields added for
cabinet layout support to numeric input via a validator.

QDoubleValidator follows the system/UI locale for its decimal
separator, which meant a comma was accepted as an intermediate state
and left the field impossible to leave in some contexts, even though
it was never a valid final value.

Switch both to a QRegularExpressionValidator matching
^[0-9]*\.?[0-9]{0,4}$: "." is a literal character in the pattern, not
locale-dependent, and [0-9] (rather than \d) excludes non-ASCII
digits. This guarantees a value entered this way can always be read
back with QString::toDouble() without locale handling. The check for
which keys are numeric (QETInformation::isNumericInfoKey) is shared
between both call sites; the validator setup itself stays local to
each, since QETInformation intentionally has no Qt Widgets dependency.

Also adds a placeholder ("ex. 80.5") and tooltip explaining the
expected format.
2026-08-11 18:01:10 +02:00
Levi Jetzer c23999acd1 Added width/height/depth properties to element definitions
Adds three new elementInformation keys — width, height, depth (in mm)
— alongside the existing manufacturer/manufacturer_reference fields.
These describe the physical dimensions of the device a symbol
represents, set once per element definition.

Values are restricted to plain decimal numbers via a QDoubleValidator
on the information tree's item delegate (fixed-point, "." as decimal
separator via QLocale::c(), independent of the UI language), so a
later consumer can always parse them with toDouble() without
additional sanitization.
2026-08-11 16:09:34 +02:00
Levi Jetzer 72c8008218 Avoid needless dock churn in QETElementEditor::updateInformations()
updateInformations() runs on every selection change and on every undo
stack index change. It unconditionally called clearToolsDock(), which
removes and hides the current editor widget, and then re-inserted the
same widget into the stack. When the editor for the new selection is
the one already shown, that means removing, hiding, reparenting and
re-adding a widget only to end up in the same state.

Look up the editor first and only clear and re-insert the tools dock
when a different editor is needed. setPart()/setParts() still updates
the contents in every case, so the visible result is unchanged.
2026-08-06 17:20:48 +02:00
375 changed files with 12854 additions and 1916 deletions
+79
View File
@@ -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
+1
View File
@@ -56,6 +56,7 @@ jobs:
mingw-w64-ucrt-x86_64-extra-cmake-modules mingw-w64-ucrt-x86_64-extra-cmake-modules
mingw-w64-ucrt-x86_64-nsis mingw-w64-ucrt-x86_64-nsis
mingw-w64-ucrt-x86_64-angleproject mingw-w64-ucrt-x86_64-angleproject
mingw-w64-ucrt-x86_64-qt6-declarative
- name: Cache ccache - name: Cache ccache
uses: actions/cache@v5 uses: actions/cache@v5
+29 -2
View File
@@ -42,6 +42,9 @@ jobs:
pages: write pages: write
id-token: write # Required by SignPath id-token: write # Required by SignPath
env:
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
outputs: outputs:
qt6_msi: ${{ steps.export.outputs.msi_name_qt6 }} qt6_msi: ${{ steps.export.outputs.msi_name_qt6 }}
@@ -289,8 +292,15 @@ jobs:
# check: forks never have the SignPath secrets, and a fork-originated # check: forks never have the SignPath secrets, and a fork-originated
# PR/run must never attempt a signing request. # PR/run must never attempt a signing request.
# (cf. DieterMayerOSS:fix/msi-signing-fork-guard, d3f60c88) # (cf. DieterMayerOSS:fix/msi-signing-fork-guard, d3f60c88)
# continue-on-error: SignPath's certificate is still pending validation
# (Sept 2026) — the signing request currently fails with a 500 on the
# SignPath side. Kept non-blocking so the nightly MSI still ships
# (unsigned) while the certificate is pending. Remove
# continue-on-error once the certificate is confirmed active.
- name: Sign MSI via SignPath - 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 != ''
continue-on-error: true
uses: signpath/github-action-submit-signing-request@v2 uses: signpath/github-action-submit-signing-request@v2
with: with:
api-token: ${{ secrets.SIGNPATH_API_TOKEN }} api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
@@ -302,7 +312,22 @@ jobs:
wait-for-completion: true wait-for-completion: true
output-artifact-directory: 'dist\' output-artifact-directory: 'dist\'
# If signing succeeded, SignPath already overwrote dist\*.msi with the
# signed copy (output-artifact-directory above) — nothing to do here.
# If it failed/was skipped, dist\*.msi is still the unsigned MSI from
# the "Build MSI" step, so the rest of the pipeline just ships that.
- name: Report signing status
if: always()
shell: pwsh
run: |
if ("${{ steps.sign.outcome }}" -eq "success") {
Write-Host "MSI signed successfully via SignPath."
} else {
Write-Warning "MSI signing skipped or failed (outcome: ${{ steps.sign.outcome }}) — shipping UNSIGNED MSI. Likely cause: SignPath certificate still pending validation."
}
- name: Upload signed MSI artifact - name: Upload signed MSI artifact
if: always()
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v7
with: with:
name: qelectrotech-windows-msi-${{ matrix.flavor }} name: qelectrotech-windows-msi-${{ matrix.flavor }}
@@ -311,6 +336,7 @@ jobs:
if-no-files-found: error if-no-files-found: error
- name: Delete old nightly .msi asset - name: Delete old nightly .msi asset
if: always()
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
@@ -324,6 +350,7 @@ jobs:
shell: pwsh shell: pwsh
- name: Upload MSI to nightly release - name: Upload MSI to nightly release
if: always()
uses: softprops/action-gh-release@v3 uses: softprops/action-gh-release@v3
with: with:
tag_name: nightly tag_name: nightly
@@ -345,7 +372,7 @@ jobs:
run: | run: |
Write-Host "=== MSI build summary (${{ matrix.flavor }}) ===" Write-Host "=== MSI build summary (${{ matrix.flavor }}) ==="
Write-Host "Version : ${{ steps.version.outputs.VERSION_DISPLAY }}" 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é, # Job 2 : Génère et déploie la page GitHub Pages une fois le MSI publié,
+8
View File
@@ -8,3 +8,11 @@ doc/*
QElectroTech.tag QElectroTech.tag
!doc/doc-utils !doc/doc-utils
lang/*.qm lang/*.qm
# -- IDE settings/intermediate files --
# zed
.zed
.cache
# VS Code
.vscode
+81 -97
View File
@@ -28,25 +28,6 @@ include(cmake/copyright_message.cmake)
set(QET_DIR ${PROJECT_SOURCE_DIR}) set(QET_DIR ${PROJECT_SOURCE_DIR})
# QT_VERSION_MAJOR is chosen explicitly by whoever configures the build,
# via -DQT_VERSION_MAJOR=5 or -DQT_VERSION_MAJOR=6.
# Default to Qt5 (current stable) when not specified, so existing
# CI/scripts that don't pass this option keep working unchanged.
# This must happen BEFORE add_subdirectory(tests) and the fetch_*.cmake
# includes below, so every subdirectory and every FetchContent dependency
# sees a consistent, already-defined value.
if(NOT DEFINED QT_VERSION_MAJOR)
set(QT_VERSION_MAJOR 6)
endif()
# Some third-party CMake projects we pull in via FetchContent (e.g.
# SingleApplication) don't know about QET_VERSION_MAJOR: they follow Qt's
# own "QT_DEFAULT_MAJOR_VERSION" convention instead, and silently default
# to Qt5 if it isn't set. Propagate our choice so they stay in sync,
# otherwise they can end up linked against a different Qt major version
# than the rest of QET, which breaks AUTOMOC with an
# INTERFACE_QT_MAJOR_VERSION mismatch at generate time.
set(QT_DEFAULT_MAJOR_VERSION ${QT_VERSION_MAJOR} CACHE STRING "Qt version to use (5 or 6)" FORCE)
include(cmake/paths_compilation_installation.cmake) include(cmake/paths_compilation_installation.cmake)
include(cmake/start_options.cmake) include(cmake/start_options.cmake)
@@ -65,7 +46,7 @@ SET(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package( find_package(
Qt${QT_VERSION_MAJOR} Qt6
COMPONENTS COMPONENTS
${QET_COMPONENTS} ${QET_COMPONENTS}
REQUIRED) REQUIRED)
@@ -76,12 +57,9 @@ find_package(
# and only provide the implicit Qt6::GuiPrivate target created alongside # and only provide the implicit Qt6::GuiPrivate target created alongside
# Qt6::Gui. Try the component quietly, then verify the target below so a # Qt6::Gui. Try the component quietly, then verify the target below so a
# missing private-headers package fails here instead of at compile time. # missing private-headers package fails here instead of at compile time.
# Qt5 has no such component as its GuiPrivate target always exists once Gui is found. find_package(Qt6 QUIET COMPONENTS GuiPrivate)
if(QT_VERSION_MAJOR GREATER_EQUAL 6)
find_package(Qt6 QUIET COMPONENTS GuiPrivate)
endif()
if(QT_VERSION_MAJOR GREATER_EQUAL 6 AND NOT TARGET Qt6::GuiPrivate) if(NOT TARGET Qt6::GuiPrivate)
message(FATAL_ERROR message(FATAL_ERROR
"Qt6::GuiPrivate was not found. It is required for PDF hyperlink " "Qt6::GuiPrivate was not found. It is required for PDF hyperlink "
"support (<private/qpdf_p.h>). Install the Qt6 private headers " "support (<private/qpdf_p.h>). Install the Qt6 private headers "
@@ -98,15 +76,46 @@ endif()
# see the QT_VERSION_CHECK / QET_HAS_QTPDF guards in diagrameventaddpdf.* # see the QT_VERSION_CHECK / QET_HAS_QTPDF guards in diagrameventaddpdf.*
# and pdfpagesdialog.*. # and pdfpagesdialog.*.
set(QET_HAS_QTPDF FALSE) set(QET_HAS_QTPDF FALSE)
if(QT_VERSION_MAJOR GREATER_EQUAL 6) find_package(Qt6 QUIET COMPONENTS Pdf)
find_package(Qt6 QUIET COMPONENTS Pdf) if(TARGET Qt6::Pdf AND NOT Qt6_VERSION VERSION_LESS 6.4.0)
if(TARGET Qt6::Pdf AND NOT Qt6_VERSION VERSION_LESS 6.4.0) set(QET_HAS_QTPDF TRUE)
set(QET_HAS_QTPDF TRUE) list(APPEND QET_PRIVATE_LIBRARIES Qt::Pdf)
list(APPEND QET_PRIVATE_LIBRARIES Qt::Pdf) add_compile_definitions(QET_HAS_QTPDF)
add_compile_definitions(QET_HAS_QTPDF) else()
else() message(STATUS "QtPdf module not available (or Qt < 6.4): PDF page import feature disabled")
message(STATUS "QtPdf module not available (or Qt < 6.4): PDF page import feature disabled") endif()
endif()
# JavaScript scripting (bugtracker #162: `--run script.js`, and later a
# "Run Script..." menu action) needs QJSEngine, in the Qml module. As with
# QtPdf above, this is not fatal when missing: some minimal Qt6 packagings
# may not ship it, and scripting is optional functionality nothing else in
# the application depends on. When it's missing the feature is silently
# disabled - see the QET_HAS_SCRIPTING guard in qetscripting.*.
set(QET_HAS_SCRIPTING FALSE)
find_package(Qt${QT_VERSION_MAJOR} QUIET COMPONENTS Qml)
if(TARGET Qt${QT_VERSION_MAJOR}::Qml)
set(QET_HAS_SCRIPTING TRUE)
list(APPEND QET_PRIVATE_LIBRARIES Qt::Qml)
add_compile_definitions(QET_HAS_SCRIPTING)
else()
message(STATUS "Qt Qml module not available: JavaScript scripting (--run) disabled")
endif()
# The crash handler writes a backtrace into the dump. Detecting this with
# __has_include(<execinfo.h>) is not enough: the header is present on FreeBSD
# too, but backtrace() lives in a separate libexecinfo there, so the compile
# succeeds and the link fails. FindBacktrace resolves both the header and
# whichever library actually provides the symbol, so gate on it instead - see
# the QET_CRASH_BACKTRACE guard in sources/logging/crashhandler.cpp.
find_package(Backtrace QUIET)
if(Backtrace_FOUND)
list(APPEND QET_PRIVATE_LIBRARIES ${Backtrace_LIBRARIES})
include_directories(${Backtrace_INCLUDE_DIRS})
add_compile_definitions(QET_CRASH_BACKTRACE)
else()
message(STATUS
"backtrace() not available: crash dumps will carry the log ring "
"without a backtrace")
endif() endif()
find_package(SQLite3 REQUIRED) find_package(SQLite3 REQUIRED)
@@ -123,11 +132,6 @@ endif()
set(CMAKE_AUTOUIC_SEARCH_PATHS ${QET_DIR}/sources/ui) set(CMAKE_AUTOUIC_SEARCH_PATHS ${QET_DIR}/sources/ui)
if(QT_VERSION_MAJOR EQUAL 6)
set(KF_MAJOR_VERSION 6)
else()
set(KF_MAJOR_VERSION 5)
endif()
include(cmake/fetch_kdeaddons.cmake) include(cmake/fetch_kdeaddons.cmake)
# Add sub directories # Add sub directories
@@ -152,6 +156,7 @@ if(WIN32)
${QET_SRC_FILES} ${QET_SRC_FILES}
${QM_FILES} ${QM_FILES}
${QET_DIR}/qelectrotech.qrc ${QET_DIR}/qelectrotech.qrc
${QET_DIR}/ico/icon-themes.qrc
) )
else() else()
add_executable( add_executable(
@@ -160,6 +165,7 @@ else()
${QET_SRC_FILES} ${QET_SRC_FILES}
${QM_FILES} ${QM_FILES}
${QET_DIR}/qelectrotech.qrc ${QET_DIR}/qelectrotech.qrc
${QET_DIR}/ico/icon-themes.qrc
) )
endif() endif()
@@ -192,71 +198,19 @@ set_source_files_properties(
${TS_FILES} ${TS_FILES}
PROPERTIES OUTPUT_LOCATION "lang" PROPERTIES OUTPUT_LOCATION "lang"
) )
if(QT_VERSION_MAJOR EQUAL 6) if(Qt6_VERSION VERSION_LESS "6.2")
if(Qt6_VERSION VERSION_LESS "6.2") # Qt 6.06.1
# Qt 6.06.1 qt6_add_translation(QM_FILES ${TS_FILES})
qt6_add_translation(QM_FILES ${TS_FILES})
# qt6_add_translation() only creates custom commands. Something must # qt6_add_translation() only creates custom commands. Something must
# depend on their outputs for them to run during the default build. # depend on their outputs for them to run during the default build.
add_custom_target(${PROJECT_NAME}_lrelease ALL
DEPENDS ${QM_FILES}
)
add_custom_target(update_translations
COMMAND
$<TARGET_FILE:Qt6::lupdate>
"${CMAKE_SOURCE_DIR}/sources"
-ts ${TS_FILES}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT
"Updating .ts files from sources/ (lupdate) - developer target"
VERBATIM
)
elseif(Qt6_VERSION VERSION_LESS "6.7")
# Qt 6.26.6: old target-based signature
qt_add_lrelease(
${PROJECT_NAME}
TS_FILES ${TS_FILES}
QM_FILES_OUTPUT_VARIABLE QM_FILES
)
# Automatically creates the update_translations umbrella target.
qt_add_lupdate(
${PROJECT_NAME}
TS_FILES ${TS_FILES}
)
else()
# Qt 6.7+: new signature
qt_add_lrelease(
TS_FILES ${TS_FILES}
LRELEASE_TARGET ${PROJECT_NAME}_lrelease
QM_FILES_OUTPUT_VARIABLE QM_FILES
)
qt_add_lupdate(
SOURCE_TARGETS ${PROJECT_NAME}
TS_FILES ${TS_FILES}
LUPDATE_TARGET update_translations
NO_GLOBAL_TARGET
)
endif()
else()
# Qt 5
qt5_add_translation(QM_FILES ${TS_FILES})
# Likewise, qt5_add_translation() needs a target depending on its outputs,
# unless QM_FILES are already consumed elsewhere.
add_custom_target(${PROJECT_NAME}_lrelease ALL add_custom_target(${PROJECT_NAME}_lrelease ALL
DEPENDS ${QM_FILES} DEPENDS ${QM_FILES}
) )
add_custom_target(update_translations add_custom_target(update_translations
COMMAND COMMAND
$<TARGET_FILE:Qt5::lupdate> $<TARGET_FILE:Qt6::lupdate>
"${CMAKE_SOURCE_DIR}/sources" "${CMAKE_SOURCE_DIR}/sources"
-ts ${TS_FILES} -ts ${TS_FILES}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
@@ -264,8 +218,38 @@ else()
"Updating .ts files from sources/ (lupdate) - developer target" "Updating .ts files from sources/ (lupdate) - developer target"
VERBATIM VERBATIM
) )
elseif(Qt6_VERSION VERSION_LESS "6.7")
# Qt 6.26.6: old target-based signature
qt_add_lrelease(
${PROJECT_NAME}
TS_FILES ${TS_FILES}
QM_FILES_OUTPUT_VARIABLE QM_FILES
)
# Automatically creates the update_translations umbrella target.
qt_add_lupdate(
${PROJECT_NAME}
TS_FILES ${TS_FILES}
)
else()
# Qt 6.7+: new signature
qt_add_lrelease(
TS_FILES ${TS_FILES}
LRELEASE_TARGET ${PROJECT_NAME}_lrelease
QM_FILES_OUTPUT_VARIABLE QM_FILES
)
qt_add_lupdate(
SOURCE_TARGETS ${PROJECT_NAME}
TS_FILES ${TS_FILES}
LUPDATE_TARGET update_translations
NO_GLOBAL_TARGET
)
endif() endif()
# Optional precompiled headers -- see QET_ENABLE_PCH in # Optional precompiled headers -- see QET_ENABLE_PCH in
# cmake/developer_options.cmake for what this trades away. # cmake/developer_options.cmake for what this trades away.
# #
-179
View File
@@ -1,179 +0,0 @@
[Qt6 / CMake]
Building the Qt6 line with CMake additionally requires the private
development headers of QtGui. On Debian/Ubuntu these live in a separate
package that is NOT pulled in by qt6-base-dev:
apt install qt6-base-private-dev
(other distributions: install your Qt 6 "private headers" development
package, e.g. qt6-qtbase-private-devel on Fedora)
QET needs the private QtGui API (QPdfEngine) for clickable hyperlinks in
the PDF export. Without the package, find_package(Qt6 ... GuiPrivate)
succeeds but CMake later fails at generate time with:
"Imported target Qt6::GuiPrivate includes non-existent path".
[ca]
Dependències:
libQt5 (paquets libqt5*)
cupsys-bsd per imprimir
Com compilar:
$ qmake (qmake-qt5 pels sistemes basats en Debian)
$ make
# umask 0022
# make install
[en]
Requirements :
libQt5 (see packages libqt5*)
cupsys-bsd for printing
Howto compile :
$ qmake (qmake-qt5 for Debian-based systems)
$ make
# umask 0022
# make install
[fr]
Pré-requis :
libQt5 (paquets libqt5*)
cupsys-bsd pour l'impression
Comment compiler :
$ qmake (qmake-qt5 pour les systèmes basés sur Debian)
$ make
# umask 0022
# make install
[de]
Abhängigkeiten:
libQt5 (Pakete libqt5*)
cupsys-bsd zum Drucken
Kompilieren:
$ qmake (qmake-qt5 für Debian-basierende Systeme)
$ make
# umask 0022
# make install
[ru]
Зависимости:
libQt5 (пакет libqt5*)
cupsys-bsd для печати
Как компилировать?:
$ qmake (qmake-qt5 для систем основанных на Debian)
$ make
# umask 0022
# make install
[pt]
Dependências:
libQt5 (ver pacotes libqt5*)
cupsys-bsd para impressão
Como compilar:
$ qmake (qmake-qt5 para sistemas baseados em Debian)
$ make
# umask 0022
# make install
[es]
Dependencias:
libQt5 (paquetes libqt5*)
cupsys-bsd para imprimir
Cómo compilar:
$ qmake (qmake-qt5 para los sistemas basados en Debian)
$ make
# umask 0022
# make install
[cs]
Požadavky :
libQt5 (viz balíček libqt5*)
cupsys-bsd kvůli tisku
Jak program sestavit :
$ qmake (qmake-qt5 u systémů založených na Debianu)
$ make
# umask 0022
# make install
[pl]
Zależności:
libQt5 (pakiety libqt5 *)
cupsys-bsd do druku
Sposób kompilacji:
$ qmake (qmake-qt5 dla systemów opartych na Debianie)
$ make
# umask 0022
# make install
[it]
Dipendenze:
libQt5 (vedi pacchetti libqt5*)
cupsys-bsd per la stampa
Come compilare:
$ qmake (qmake-qt5 per sistemi basati su Debian)
$ make
# umask 0022
# make install
[el]
Απαιτήσεις:
libQt5 (δείτε για πακέτα libqt5*)
cupsys-bsd για εκτύπωση
Πως να το μεταγλωττίσετε:
$ qmake (qmake-qt5 για συστήματα βασισμένα σε Debian)
$ make
# umask 0022
# make install
[nl]
vereisten:
libqt5 (zie pakketten libqt5 *)
cupsys-bsd om af te drukken
Hoe te compileren:
$ Qmake (qmake-qt5 voor Debian-gebaseerde systemen)
$ make
# Umask 0022
# Make install
[be]
Vereisten:
libQt5 (pakketten libqt5*)
cupsys-bsd voor het afdrukken
Hoe te compileren :
$ qmake (qmake-qt5 voor systemen op basis van Debian)
$ make
# umask 0022
# make install
[da]
Krav:
libQt5 (se pakke libqt5*)
cupsys-bsd til udskrivning
Hvordan man kompilerer:
$ qmake (qmake-qt5 for Debian baserede systemer)
$ make
# umask 0022
# make install
[ja]
必要条件 :
libQt5 (パッケージ libqt5* を参照)
cupsys-bsd 印刷用
コンパイルの方法 :
$ qmake (Debian ベースのシステムでは qmake-qt5)
$ make
# umask 0022
# make install
+365
View File
@@ -0,0 +1,365 @@
# Building QElectroTech from source
QElectroTech is built with **CMake** and requires the **current Qt6 only**; Qt5 and the
old `qmake`-based build described in earlier versions of this file have
been retired.
## 1. Getting the source
QET uses git submodules, so clone recursively:
```sh
git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
cd qelectrotech-source-mirror
```
If you already have a non-recursive clone:
```sh
git submodule update --init --recursive
```
## 2. Requirements
| Component | Status | Notes |
|---|---|---|
| CMake ≥ 3.5 | required | CMake 4.3+ is also fine, see note below |
| C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) |
| Qt6 base + widgets | required | |
| Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below |
| SQLite3 | required | used by the nomenclature/summary database |
| Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build |
| pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below |
| Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed |
| KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) |
| QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) |
A note on CMake versions: the project declares a minimum of 3.5 but is
routinely built with much newer releases; if your CMake is older than 4.3 it
simply won't have the newer `SQLite3::SQLite3` target name, which the build
script compensates for automatically. There is nothing you need to do either
way.
## 3. Building (out-of-source build)
Always build in a separate directory from the source tree — in-source builds
are not supported.
```sh
mkdir build
cd build
cmake ..
cmake --build . --parallel
```
For a Unix-like platform you'll usually also want an explicit build type,
since CMake's default (empty) build type means no optimization and no
debug info either:
```sh
cmake .. -DCMAKE_BUILD_TYPE=Release
```
To install (default prefix is `/usr/local`; adjust with
`-DCMAKE_INSTALL_PREFIX=...` at the configure step if needed):
```sh
sudo cmake --install .
```
Useful configure-time options (pass as `-D<OPTION>=ON/OFF`):
| Option | Default | Effect |
|---|---|---|
| `PACKAGE_TESTS` | `ON` | build the test suite (needs the Qt Test module) |
| `BUILD_WITH_KF` | distro-dependent | build against KDE Frameworks; see below |
| `QET_ENABLE_PCH` | `OFF` | precompiled headers for faster rebuilds (needs CMake ≥ 3.16) |
## 3.1 Choosing a compiler (Unix)
Both **GCC** and **Clang** are supported on Unix-like platforms; any
reasonably recent release with solid C++17 support works. Neither is
hard-coded — CMake picks up whichever `cc`/`c++` (or `CC`/`CXX` environment
variables) resolve to by default, and you can force one explicitly at
configure time:
```sh
cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
# or
cmake .. -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
```
or equivalently, by exporting `CC`/`CXX` before calling `cmake`. The
per-distro package lists below install GCC by default (via
`build-essential`/`gcc-c++`/the base toolchain); see each section for the
Clang alternative.
## 4. Distribution packages
Exact package names vary by release/branch; if a name below doesn't resolve,
search your package manager (`apt search`, `pkg search`, `brew search`) for
the closest match.
### Debian / Ubuntu
```sh
sudo apt install \
build-essential cmake ninja-build git \
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
libsqlite3-dev \
libkf6coreaddons-dev libkf6widgetsaddons-dev
```
`qt6-base-private-dev` is **not** pulled in automatically by `qt6-base-dev`
but is mandatory for the build (see
[Qt6 private headers](#6-qt6-private-headers-mandatory) below).
For Clang instead of GCC:
```sh
sudo apt install clang
```
Optional, for PDF page import:
```sh
sudo apt install libqt6pdf6-dev
```
Optional, to use a system pugixml instead of letting CMake fetch it:
```sh
sudo apt install libpugixml-dev
```
### Fedora / RHEL
```sh
sudo dnf install \
cmake gcc-c++ git \
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
sqlite-devel \
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
```
For Clang instead of GCC: `sudo dnf install clang`.
Optional, for PDF page import: `qt6-qtwebengine-devel` provides `QtPdf` on
some Fedora releases — check `dnf provides '*/QPdfDocument'` if unsure.
Optional, for a system pugixml: `sudo dnf install pugixml-devel`.
### FreeBSD
```sh
pkg install \
cmake git \
qt6-base qt6-tools \
sqlite3 \
kf6-kcoreaddons kf6-kwidgetsaddons
```
(from ports: `devel/qt6-base`, `devel/qt6-tools`, `databases/sqlite3`,
`devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's
`GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate
package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing
— PDF page import will simply be disabled (see below).
Clang is the FreeBSD base system compiler and needs nothing extra; GCC is
available via `pkg install gcc` if you'd rather use it.
Optional, for a system pugixml: `pkg install pugixml` (`devel/pugixml`).
### macOS
Using [Homebrew](https://brew.sh):
```sh
brew install cmake qt sqlite ninja
```
Homebrew's `qt` formula is Qt6 and includes the private headers, so no
extra package is required there. KDE Frameworks are not practically
available via Homebrew, so macOS builds are normally done **without KF**
(`-DBUILD_WITH_KF=OFF`) — see the caveat below.
Apple's Clang (from the Xcode Command Line Tools, `xcode-select --install`)
is the default and needs no extra package; GCC is available via
`brew install gcc` if you specifically want it instead.
Optional, for a system pugixml: `brew install pugixml`.
### Windows
See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the
package sources differ enough from the Unix-like platforms above (no system
package manager, SQLite3 and Qt aren't provided the same way) that it gets
its own section.
## 5. pugixml
QET links against [pugixml](https://pugixml.org) for XML parsing.
`cmake/fetch_pugixml.cmake` handles this automatically: if a suitable
pugixml isn't already available on the system, CMake fetches and builds it
from source via `FetchContent` as part of the normal configure step — you
don't need to do anything to get a working build.
If you'd rather avoid that network fetch and use your distribution's own
pugixml package instead, install it before running `cmake ..` (package
names are listed per platform in the [Distribution
packages](#4-distribution-packages) section above — e.g. `libpugixml-dev`
on Debian/Ubuntu, `pugixml-devel` on Fedora).
## 6. Building on Windows (MSVC / MinGW)
Both toolchains QET's CMake build targets on Windows are covered here:
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
Unix-like platforms above, there's no single system package manager, so
Qt, SQLite3 and (optionally) KDE Frameworks each need to be sourced
separately per toolchain.
One piece of good news either way: unlike Debian/Fedora, the official Qt
Windows kits (both MSVC and MinGW) **already include the GuiPrivate private
headers** — there's no separate "private headers" package to remember on
Windows (see [Qt6 private headers](#7-qt6-private-headers-mandatory) below
for why this matters).
`QtPdf` is available as an optional component in the Qt Online Installer
(look for "Qt PDF" under the Qt 6.4+ node); if you skip it, PDF page import
is silently disabled exactly as on the other platforms. KDE Frameworks are
not prebuilt for Windows by any of the routes below, so Windows builds
normally use `-DBUILD_WITH_KF=OFF` (see the
[autosave caveat](#9-building-without-kde-frameworks)).
### MSVC (Visual Studio 2019 / 2022)
1. Install Visual Studio with the "Desktop development with C++" workload,
and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit)
via the [Qt Online Installer](https://www.qt.io/download-qt-installer).
2. Get SQLite3 — the simplest route is [vcpkg](https://vcpkg.io):
```bat
vcpkg install sqlite3:x64-windows
```
3. Configure and build from an "x64 Native Tools Command Prompt for VS":
```bat
mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64 ^
-DCMAKE_PREFIX_PATH="C:\Qt\6.x.x\msvc2022_64" ^
-DCMAKE_TOOLCHAIN_FILE="C:\vcpkg\scripts\buildsystems\vcpkg.cmake" ^
-DBUILD_WITH_KF=OFF
cmake --build . --config Release --parallel
```
Ninja works too, in place of the Visual Studio generator, if you prefer
(`-G Ninja -DCMAKE_BUILD_TYPE=Release`, run from the same VS developer
prompt so `cl.exe` is on `PATH`).
### MinGW-w64 (gcc)
Two common ways to get a MinGW toolchain that matches a Qt6 MinGW kit: Qt's
own bundled MinGW (installed alongside a `mingw_64` Qt kit via the Qt
Online Installer), or [MSYS2](https://www.msys2.org).
Using MSYS2, from a **MINGW64** shell:
```sh
pacman -S --needed \
mingw-w64-x86_64-toolchain \
mingw-w64-x86_64-qt6-base mingw-w64-x86_64-qt6-tools \
mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja \
mingw-w64-x86_64-sqlite3
mkdir build && cd build
cmake .. -G Ninja -DBUILD_WITH_KF=OFF -DCMAKE_BUILD_TYPE=Release
cmake --build .
```
If `libbacktrace` is installed in your MSYS2 environment, pass the following
cached CMake variables when configuring. They are only necessary in that
case, because `FindBacktrace` needs them to locate the library:
```sh
cmake .. -G Ninja -DBUILD_WITH_KF=OFF -DCMAKE_BUILD_TYPE=Release \
-DBacktrace_INCLUDE_DIR=/c/msys64/clang64/include \
-DBacktrace_LIBRARY=/c/msys64/clang64/lib/libbacktrace.a
```
`Backtrace_INCLUDE_DIR` must point to the directory containing `backtrace.h`,
and `Backtrace_LIBRARY` to the `libbacktrace.a` file. The paths above are the
usual locations for the MSYS2 `clang64` environment; adjust them if your
installation uses a different prefix.
(KF6 isn't packaged in MSYS2 either, hence `-DBUILD_WITH_KF=OFF` again.)
Using the Qt Online Installer's bundled MinGW kit instead: point
`CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure
its bundled `g++.exe` comes first on `PATH`, or pass
`-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick
up a different MinGW installation. SQLite3 still has to come from elsewhere
in this path — vcpkg with a `mingw`-flavoured triplet, or MSYS2's package as
above.
## 7. Qt6 private headers (mandatory)
QET uses the private `QPdfEngine` API (`<private/qpdf_p.h>`) to produce
clickable hyperlinks in PDF export. This requires Qt's `GuiPrivate` target,
which on several distributions (notably Debian/Ubuntu) is shipped in a
separate "private headers" package rather than the normal `-dev` package —
e.g. `qt6-base-private-dev` on Debian/Ubuntu, or
`qt6-qtbase-private-devel` on Fedora. Without it, CMake's `find_package`
step succeeds but configuration later fails at generate time with:
```
Imported target "Qt6::GuiPrivate" includes non-existent path
```
If you hit this, install your distribution's Qt6 private-headers package
(on Windows this is a non-issue — see [Building on
Windows](#6-building-on-windows-msvc--mingw) above).
## 8. PDF page import (QtPdf)
The toolbar's "Add a PDF" feature (`QPdfDocument::pagePointSize()`) needs
the separate `QtPdf` Qt module, available since Qt 6.4.
**This is optional and soft-fails**: if `QtPdf` isn't found, or is present
but older than 6.4, CMake prints a status message and simply disables the
feature (`QET_HAS_QTPDF` stays `FALSE`) — it does **not** stop the build.
Some Qt6 distributions don't ship `QtPdf` at all (it lives outside Qt's
core module set, alongside QtWebEngine), so this is expected on several
platforms, including FreeBSD as of this writing.
## 9. Building without KDE Frameworks
`BUILD_WITH_KF=OFF` builds QET against a bundled fallback UI layer instead
of KDE Frameworks (`kcoreaddons`/`kwidgetsaddons`). This is a fully
supported configuration and is, in practice, the default on platforms where
KF6 isn't readily packaged (macOS and Windows in particular).
Almost everything works identically either way. The one known caveat:
**autosave behavior is not as polished without KF**, since QET relies on
KCoreAddons' autosave/backup-file handling when it's available. Expect
autosave to work, but less robustly than on a KF-enabled build.
## 10. Translations (developer operation)
Everything in this section is a **maintainer/translator task**, not
something a normal build or a packager needs to touch.
The `.qm` files shipped with the application are compiled from the tracked
`.ts` files automatically as part of every normal build (via `lrelease`) —
regular builds and packages get up-to-date translated UI strings for free,
with no developer action needed.
Refreshing the `.ts` files themselves from the source code (`lupdate`) is
the actual developer operation: it's exposed as its own opt-in build
target, separate from the normal build:
```sh
cmake --build . --target update_translations
```
This is **not** run automatically and should not be added to routine or
parallel CI builds: it rewrites the tracked `.ts` files as a side effect,
and running it concurrently with the normal `lrelease` compilation step (as
would happen under `-j`/parallel builds) can corrupt a `.ts` file mid-write,
causing `lrelease` to fail with "Premature end of document". Run it on its
own, review the diff, and commit the updated `.ts` files separately.
+15 -15
View File
@@ -1,60 +1,60 @@
[ca] [ca]
QElectroTech és una aplicació Qt5 per crear esquemes elèctrics. QElectroTech és una aplicació Qt6 per crear esquemes elèctrics.
QET utilitza el format XML per als seus elements i esquemes i inclou un editor d'esquemes, un editor d'elements i un editor de caixetins. QET utilitza el format XML per als seus elements i esquemes i inclou un editor d'esquemes, un editor d'elements i un editor de caixetins.
[en] [en]
QElectroTech is a Qt5 application to design electric diagrams. QElectroTech is a Qt6 application to design electric diagrams.
It uses XML files for elements and diagrams, and includes both a diagram editor, a element editor, and an titleblock editor. It uses XML files for elements and diagrams, and includes both a diagram editor, a element editor, and an titleblock editor.
[fr] [fr]
QElectroTech est une application Qt5 pour réaliser des schémas électriques. QElectroTech est une application Qt6 pour réaliser des schémas électriques.
QET utilise le format XML pour ses éléments et ses schémas et inclut un éditeur de schémas, un éditeur d'élément, ainsi qu'un editeur de cartouche. QET utilise le format XML pour ses éléments et ses schémas et inclut un éditeur de schémas, un éditeur d'élément, ainsi qu'un editeur de cartouche.
[de] [de]
QElectroTech ist eine Qt5 Software, um Schaltpläne zu erstellen. QElectroTech ist eine Qt6 Software, um Schaltpläne zu erstellen.
QET benutzt das XML Format für seine Bauteile und seine Projekte, und beinhaltet einen Schaltplaneditor, einen Bauteileditor sowie einen Schriftfeldeditor. QET benutzt das XML Format für seine Bauteile und seine Projekte, und beinhaltet einen Schaltplaneditor, einen Bauteileditor sowie einen Schriftfeldeditor.
[ru] [ru]
QElectroTech - приложение написанное на Qt5 и предназначенное для разработки электрических схем. QElectroTech - приложение написанное на Qt6 и предназначенное для разработки электрических схем.
Оно использует XML-файлы для элементов и схем, и включает, как редактор схем, так и редактор элементов. Оно использует XML-файлы для элементов и схем, и включает, как редактор схем, так и редактор элементов.
[pt] [pt]
QElectroTech é uma aplicação baseada em Qt5 para desenhar esquemas eléctricos. QElectroTech é uma aplicação baseada em Qt6 para desenhar esquemas eléctricos.
QET utiliza ficheiros XML para os elementos e para os esquemas e inclui um editor de esquemas e um editor de elementos. QET utiliza ficheiros XML para os elementos e para os esquemas e inclui um editor de esquemas e um editor de elementos.
[es] [es]
QElectroTech es una aplicación Qt5 para diseñar esquemas eléctricos. QElectroTech es una aplicación Qt6 para diseñar esquemas eléctricos.
Utiliza archivos XML para los elementos y esquemas, e incluye un editor de esquemas y un editor de elementos. Utiliza archivos XML para los elementos y esquemas, e incluye un editor de esquemas y un editor de elementos.
[cs] [cs]
QElectroTech je aplikací Qt5 určenou pro návrh nákresů elektrických obvodů. QElectroTech je aplikací Qt6 určenou pro návrh nákresů elektrických obvodů.
Pro prvky a nákresy používá soubory XML, a zahrnuje v sobě jak editor nákresů, tak editor prvků. Pro prvky a nákresy používá soubory XML, a zahrnuje v sobě jak editor nákresů, tak editor prvků.
[pl] [pl]
QElectroTech to aplikacja napisana w Qt5, przeznaczona do tworzenia schematów elektrycznych. QElectroTech to aplikacja napisana w Qt6, przeznaczona do tworzenia schematów elektrycznych.
Wykorzystuje XML do zapisywania plików elementów i projektów. Posiada edytor schematów i elementów. Wykorzystuje XML do zapisywania plików elementów i projektów. Posiada edytor schematów i elementów.
[it] [it]
QElectroTech è una applicazione fatta in Qt5 per disegnare schemi elettrici. QElectroTech è una applicazione fatta in Qt6 per disegnare schemi elettrici.
QET usa il formato XML per i suoi elementi e schemi, includendo anche un editor per gli stessi. QET usa il formato XML per i suoi elementi e schemi, includendo anche un editor per gli stessi.
[el] [el]
Το QElectroTech είναι μια εφαρμογή Qt5 για σχεδίαση ηλεκτρικών διαγραμμάτων. Το QElectroTech είναι μια εφαρμογή Qt6 για σχεδίαση ηλεκτρικών διαγραμμάτων.
Χρησιμοποιεί αρχεία XML για στοιχεία και διαγράμματα, και περιλαμβάνει επεξεργαστή διαγραμμάτων καθώς και επεξεργαστή στοιχείων. Χρησιμοποιεί αρχεία XML για στοιχεία και διαγράμματα, και περιλαμβάνει επεξεργαστή διαγραμμάτων καθώς και επεξεργαστή στοιχείων.
[nl] [nl]
QElectroTech is een Qt5 applicatie om elektrische schema's te ontwerpen. QElectroTech is een Qt6 applicatie om elektrische schema's te ontwerpen.
Het maakt gebruik van XML-bestanden voor elementen en diagrammen, en omvat zowel een diagram bewerker, een element bewerker, en een bloksjabloon bewerker. Het maakt gebruik van XML-bestanden voor elementen en diagrammen, en omvat zowel een diagram bewerker, een element bewerker, en een bloksjabloon bewerker.
[be] [be]
QElectroTech is een Qt5 toepassing voor het maken en beheren van elektrische schema's. QElectroTech is een Qt6 toepassing voor het maken en beheren van elektrische schema's.
QET gebruikt XML voor de elementen en schema's en omvat een schematische editor, itemeditor, en een titel sjabloon editor. QET gebruikt XML voor de elementen en schema's en omvat een schematische editor, itemeditor, en een titel sjabloon editor.
[da] [da]
QElectroTech er et Qt5 program til at redigere elektriske diagrammer. QElectroTech er et Qt6 program til at redigere elektriske diagrammer.
Det bruger XML filer for symboler og diagrammer og inkluderer diagram, symbol og titelblok redigering. Det bruger XML filer for symboler og diagrammer og inkluderer diagram, symbol og titelblok redigering.
[ja] [ja]
QElectroTech は電気回路図を作成する Qt5 アプリケーションです。 QElectroTech は電気回路図を作成する Qt6 アプリケーションです。
QET は要素と回路図に XML 形式を利用し、回路図エディタ、要素エディタ、表題欄エディタを含みます。 QET は要素と回路図に XML 形式を利用し、回路図エディタ、要素エディタ、表題欄エディタを含みます。
+51 -51
View File
@@ -5,9 +5,9 @@
### What it is ### What it is
QElectroTech, or QET in short, is a libre and open source desktop application to create diagrams and schematics. QElectroTech, or QET in short, is a libre and open source desktop application to create diagrams and schematics.
The software is primarily intended to create electrical documentation but it can also be used to draw any kinds of diagrams, such as those made in pneumatics, hydraulics, process industries, electronics... The software is primarily intended to create electrical documentation but it can also be used to draw any kinds of diagrams, such as those made in pneumatics, hydraulics, process industries, electronics...
Generally speaking, QET is a **CAD/CAE editor focusing on schematics drawing features**. Generally speaking, QET is a **CAD/CAE editor focusing on schematics drawing features**.
This means that there are no embedded simulating or calculating functionalities and it is not planned to implement them. This means that there are no embedded simulating or calculating functionalities and it is not planned to implement them.
@@ -24,7 +24,7 @@ Users who want to test and take benefits from the last software implementations
### License ### License
The software is licensed under [GNU/GPL](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html). The software is licensed under [GNU/GPL](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html).
You are free to use, copy, modify and redistribute it under the terms of the license. You are free to use, copy, modify and redistribute it under the terms of the license.
Like many other open source software, QElectroTech is provided as is, without any warranty. Like many other open source software, QElectroTech is provided as is, without any warranty.
@@ -40,13 +40,13 @@ git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror
Here are the technical choices made for the software development: Here are the technical choices made for the software development:
* Integrated development environment: [Qt Framework](https://www.qt.io/ide/) * Integrated development environment: [Qt Framework](https://www.qt.io/ide/)
* Libraries: Qt 5.x * Libraries: Qt 6.x
* [KF5 Framework](https://github.com/KDE) * [KF6 Framework](https://github.com/KDE)
[Cmake](https://cmake.org/install/) [Cmake](https://cmake.org/install/)
[kcoreaddons](https://github.com/KDE/kcoreaddons/tree/kf5) [kcoreaddons](https://github.com/KDE/kcoreaddons/tree/kf6)
[kwidgetsaddons](https://github.com/KDE/kwidgetsaddons/tree/kf5). [kwidgetsaddons](https://github.com/KDE/kwidgetsaddons/tree/kf6).
* Coding language: [C++](https://en.wikipedia.org/wiki/C%2B%2B) * Coding language: [C++](https://en.wikipedia.org/wiki/C%2B%2B)
* GUI translations: [Qt Linguist](http://doc.qt.io/qt-5/qtlinguist-index.html) * GUI translations: [Qt Linguist](http://doc.qt.io/qt-6/qtlinguist-index.html)
* Version control: [GIT](https://github.com/qelectrotech/qelectrotech-source-mirror.git) * Version control: [GIT](https://github.com/qelectrotech/qelectrotech-source-mirror.git)
* Doxygen documentation :[Doxygen](https://download.qelectrotech.org/qet/doxygen/html/) * Doxygen documentation :[Doxygen](https://download.qelectrotech.org/qet/doxygen/html/)
* QtCreator qch doxygen :[QElectroTech.qch](https://download.qelectrotech.org/qet/doxygen/QElectroTech.qch) * QtCreator qch doxygen :[QElectroTech.qch](https://download.qelectrotech.org/qet/doxygen/QElectroTech.qch)
@@ -62,20 +62,20 @@ If you wish to be informed of the latest developments, browse the [archive](http
# Features # Features
QElectroTech is a free and open source software. QElectroTech is a free and open source software.
No need to worry about restrictive licensing, privacy violation or dependency on a company. No need to worry about restrictive licensing, privacy violation or dependency on a company.
Zero cost and no licensing fees! Zero cost and no licensing fees!
But you are welcome to make a donation to support the development But you are welcome to make a donation to support the development
QElectroTech runs on the 3 most widespread operating systems for desktop computers in the world. QElectroTech runs on the 3 most widespread operating systems for desktop computers in the world.
Files that were created on an OS can be edited on another OS without any conversion or restriction. Files that were created on an OS can be edited on another OS without any conversion or restriction.
MS Windows users can even run the "ready-to-use" version of QElectroTech from an external medium with no need to install it on an access restricted computer. MS Windows users can even run the "ready-to-use" version of QElectroTech from an external medium with no need to install it on an access restricted computer.
Take advantage of the modern GUI Take advantage of the modern GUI
Toolbars and panels can be enabled/disabled, moved and displayed the way you want to work. Toolbars and panels can be enabled/disabled, moved and displayed the way you want to work.
Panels can be stacked on each other (as tabs) or docked on the sides (as docks) or completely separated from the main window (as windows). Panels can be stacked on each other (as tabs) or docked on the sides (as docks) or completely separated from the main window (as windows).
The GUI can fit to small or big screens, and even to multi-display configurations. The GUI can fit to small or big screens, and even to multi-display configurations.
![](https://download.qelectrotech.org/qet/images-features/0030.png "GUI overview") ![](https://download.qelectrotech.org/qet/images-features/0030.png "GUI overview")
@@ -86,31 +86,31 @@ You only need to restart the application for the new selected language to take e
Create technical documentation in professional quality Create technical documentation in professional quality
Size, look and informations of the folios (sheets) are fully configurable. Size, look and informations of the folios (sheets) are fully configurable.
You can set vertical and horizontal headers (printed rulers) individually on and off, set number of columns and rows, and set width/height of each column/row. You can set vertical and horizontal headers (printed rulers) individually on and off, set number of columns and rows, and set width/height of each column/row.
Titlebocks can be created and edited with the embedded titleblock editor to perfectly suit your needs. Titlebocks can be created and edited with the embedded titleblock editor to perfectly suit your needs.
Custom variables can be defined to display the informations you wish in the titleblock. Custom variables can be defined to display the informations you wish in the titleblock.
![](https://download.qelectrotech.org/qet/images-features/0055.png "Titleblock editor") ![](https://download.qelectrotech.org/qet/images-features/0055.png "Titleblock editor")
With only 2 mouse clicks you can add a full automatic generated table of content. With only 2 mouse clicks you can add a full automatic generated table of content.
Changes in the documentation are updated on the fly. Changes in the documentation are updated on the fly.
![](https://download.qelectrotech.org/qet/images-features/0060.png "Table of content") ![](https://download.qelectrotech.org/qet/images-features/0060.png "Table of content")
Choose from more than 8.200 symbols... Choose from more than 8.200 symbols...
The embedded QET collection contains a rich library of electric, logic, pneumatic, hydraulic and fluid symbols. The embedded QET collection contains a rich library of electric, logic, pneumatic, hydraulic and fluid symbols.
The library grows at every new release thanks to an active user community. The library grows at every new release thanks to an active user community.
![](https://download.qelectrotech.org/qet/images-features/0070.png "Collections") ![](https://download.qelectrotech.org/qet/images-features/0070.png "Collections")
...or create your own collection ...or create your own collection
The embedded element editor is a nice tool to create your own elements (symbols or anything else). The embedded element editor is a nice tool to create your own elements (symbols or anything else).
Your own elements are stored in the user collection. Your own elements are stored in the user collection.
![](https://download.qelectrotech.org/qet/images-features/0080.png "Element editor") ![](https://download.qelectrotech.org/qet/images-features/0080.png "Element editor")
Quickly find what you need Quickly find what you need
All collections can quickly be searched with the integrated search engine. All collections can quickly be searched with the integrated search engine.
Furthermore, the search request can be restricted to the folder of your choice. Furthermore, the search request can be restricted to the folder of your choice.
![](https://download.qelectrotech.org/qet/images-features/0090.png "Search engine") ![](https://download.qelectrotech.org/qet/images-features/0090.png "Search engine")
@@ -128,7 +128,7 @@ And of course, you can accurately zoom with the mouse wheel over the drawing are
Link elements together to create cross references Link elements together to create cross references
Several types of element can be linked together to display a cross reference text. Several types of element can be linked together to display a cross reference text.
All types of cross references are automatically updated on the fly, you don't need to think about them if you make changes. All types of cross references are automatically updated on the fly, you don't need to think about them if you make changes.
![](https://download.qelectrotech.org/qet/images-features/0112.png "Cross ref elements") ![](https://download.qelectrotech.org/qet/images-features/0112.png "Cross ref elements")
To speed up your work, linkable elements are easily searched and shown. To speed up your work, linkable elements are easily searched and shown.
@@ -143,7 +143,7 @@ This way, you can make your own parts list or bill of material using the full po
![](https://download.qelectrotech.org/qet/images-features/0125.png "Spreadsheet") ![](https://download.qelectrotech.org/qet/images-features/0125.png "Spreadsheet")
Print to pdf and/or export your work to images Print to pdf and/or export your work to images
Your whole documentation or only selected parts of it can be printed to a real printer or to a pdf file. Your whole documentation or only selected parts of it can be printed to a real printer or to a pdf file.
Alternatively, you can export to vector (svg) or pixel (png, jpg, bmp) format images. Alternatively, you can export to vector (svg) or pixel (png, jpg, bmp) format images.
### And much more: ### And much more:
@@ -161,44 +161,44 @@ Alternatively, you can export to vector (svg) or pixel (png, jpg, bmp) format im
Nomenclature Nomenclature
A new nomenclature tool appears in the menu: project -> Add a nomenclature. A new nomenclature tool appears in the menu: project -> Add a nomenclature.
The nomenclature is presented in the form of a configurable table separated into two parts: the display (the form) and the content (the background). The nomenclature is presented in the form of a configurable table separated into two parts: the display (the form) and the content (the background).
- Display: the size and position of the table, the margins between text and the table cell, the alignment of the text in the cells and the font. The configuration of the table headers and the table itself are separate. - Display: the size and position of the table, the margins between text and the table cell, the alignment of the text in the cells and the font. The configuration of the table headers and the table itself are separate.
- Content: the information to display in the table and the order in which it should be displayed. - Content: the information to display in the table and the order in which it should be displayed.
![](https://download.qelectrotech.org/qet/images_depeche_linuxfr/08/dialogue_nomenclature.png "nomenclature dialogue") ![](https://download.qelectrotech.org/qet/images_depeche_linuxfr/08/dialogue_nomenclature.png "nomenclature dialogue")
In order to speed up the establishment of a nomenclature, it is possible to export / import the display and content configurations separately. This is the "Configuration" part that can be seen in the photos above. In order to speed up the establishment of a nomenclature, it is possible to export / import the display and content configurations separately. This is the "Configuration" part that can be seen in the photos above.
Behind the scenes, an SQLite database does the work, so setting up the content is nothing more or less than an SQL query created using a dialog (screenshot by right). Behind the scenes, an SQLite database does the work, so setting up the content is nothing more or less than an SQL query created using a dialog (screenshot by right).
The SQL query is configured as follows (from top to bottom in the screenshot): The SQL query is configured as follows (from top to bottom in the screenshot):
- “Available information”: the information to display; - “Available information”: the information to display;
- "Filter": filter the information (is not empty, is empty, contains, does not contain, is equal to, is not equal to) only one filter can be applied per information, it is not possible combine several; - "Filter": filter the information (is not empty, is empty, contains, does not contain, is equal to, is not equal to) only one filter can be applied per information, it is not possible combine several;
- "Type of elements": allows you to filter on what type of element you want to obtain information. - "Type of elements": allows you to filter on what type of element you want to obtain information.
At the bottom, a checkmark "SQL query" allows you to edit a personalized query, if the basic options are not sufficient. At the bottom, a checkmark "SQL query" allows you to edit a personalized query, if the basic options are not sufficient.
When a nomenclature is too large to be contained in a single folio, it is possible to separate it on several folios, the tables of each folio are then linked together. When creating a nomenclature, this option is activated by default, which has the effect of adding the necessary number of folios, adding a table in each of them and linking them together. When a nomenclature is too large to be contained in a single folio, it is possible to separate it on several folios, the tables of each folio are then linked together. When creating a nomenclature, this option is activated by default, which has the effect of adding the necessary number of folios, adding a table in each of them and linking them together.
Finally two buttons are available in the property panel: Finally two buttons are available in the property panel:
- "Fit the table to the folio": positions and adjusts the size and determines the number of rows in the table in relation to the folio; - "Fit the table to the folio": positions and adjusts the size and determines the number of rows in the table in relation to the folio;
- "Apply geometry to all tables linked to this one": applies the three properties mentioned above to all linked tables in order to save time and maintain aesthetic consistency. - "Apply geometry to all tables linked to this one": applies the three properties mentioned above to all linked tables in order to save time and maintain aesthetic consistency.
And to finish a table And to finish a table
![](https://download.qelectrotech.org/qet/images_depeche_linuxfr/08/tableau.png "table") ![](https://download.qelectrotech.org/qet/images_depeche_linuxfr/08/tableau.png "table")
Summary Summary
The old summary has been completely removed from the code in order to make room for the new one which is exactly the same as the nomenclature (a large amount of the code is common), with the exception of the SQL query (and its dialog to configure it) which offers specific information for editing a summary. The old summary has been completely removed from the code in order to make room for the new one which is exactly the same as the nomenclature (a large amount of the code is common), with the exception of the SQL query (and its dialog to configure it) which offers specific information for editing a summary.
Export of the internal database Export of the internal database
The database used by the nomenclature and the summary can be exported in a “.sqlite” file. The database used by the nomenclature and the summary can be exported in a “.sqlite” file.
Currently this is irrelevant, as the function was created during development for debugging purposes, we left it. Currently this is irrelevant, as the function was created during development for debugging purposes, we left it.
Note that the database will become increasingly important in the future of Qet. Note that the database will become increasingly important in the future of Qet.
Export of the wiring list Export of the wiring list
In order to be able to use the wiring number printers more easily, the names of conductors can be exported in CSV format, the export respects the quantity of conductors in order to print the right quantity of numbers, for example a potential numbered 240 composed of 3 wires will give 6 × 240 (2 numbers per wire × 3 wires) in the CSV. In order to be able to use the wiring number printers more easily, the names of conductors can be exported in CSV format, the export respects the quantity of conductors in order to print the right quantity of numbers, for example a potential numbered 240 composed of 3 wires will give 6 × 240 (2 numbers per wire × 3 wires) in the CSV.
@@ -206,13 +206,13 @@ In order to be able to use the wiring number printers more easily, the names of
### Story ### Story
The QElectroTech project was founded in 2007 by two french students, Xavier and Benoit. The QElectroTech project was founded in 2007 by two french students, Xavier and Benoit.
Xavier developed the base application itself and made all technical choices about the development. Xavier developed the base application itself and made all technical choices about the development.
The first version of QET (0.1) was released on 09.03.2008. The first version of QET (0.1) was released on 09.03.2008.
However, both Xavier and Benoit do not participate anymore in the project since 2013. However, both Xavier and Benoit do not participate anymore in the project since 2013.
Following this period, new developers and contributors took over the project and kept it alive. Following this period, new developers and contributors took over the project and kept it alive.
The development and the many translations are actively maintained. The development and the many translations are actively maintained.
New functionalities and evolutions are planned to make QET ever better. New functionalities and evolutions are planned to make QET ever better.
Nowadays, QET is not only used by many individuals, teachers and students but also by professional electricians and companies all over the world. Nowadays, QET is not only used by many individuals, teachers and students but also by professional electricians and companies all over the world.
+2
View File
@@ -73,6 +73,7 @@ parts:
- git - git
- sqlite3 - sqlite3
- xdg-user-dirs - xdg-user-dirs
- libqt6qml6
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content # libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
# snap (issue #373). Not yet re-verified against kf6-core24 — remove this # snap (issue #373). Not yet re-verified against kf6-core24 — remove this
# if the Qt6 xcb platform plugin loads cleanly without it. # if the Qt6 xcb platform plugin loads cleanly without it.
@@ -84,6 +85,7 @@ parts:
- libsqlite3-dev - libsqlite3-dev
- qt6-tools-dev - qt6-tools-dev
- qt6-base-private-dev - qt6-base-private-dev
- qt6-declarative-dev
- pkgconf - pkgconf
# Qt6 PrintSupport records Cups::Cups as a third-party dependency # Qt6 PrintSupport records Cups::Cups as a third-party dependency
# (qprint_p.h includes <cups/ppd.h>), so find_package(Qt6 PrintSupport) # (qprint_p.h includes <cups/ppd.h>), so find_package(Qt6 PrintSupport)
-1
View File
@@ -68,4 +68,3 @@ else()
add_definitions(-DBUILD_WITHOUT_KF) add_definitions(-DBUILD_WITHOUT_KF)
endif() endif()
message("QET_COMPONENTS :" ${QET_COMPONENTS}) message("QET_COMPONENTS :" ${QET_COMPONENTS})
message("QT_VERSION_MAJOR :" ${QT_VERSION_MAJOR})
+1 -4
View File
@@ -31,10 +31,7 @@ add_definitions(-DQT_MESSAGELOGCONTEXT)
# In order to do so, uncomment the following line. # In order to do so, uncomment the following line.
#add_definitions(-DTODO_LIST) #add_definitions(-DTODO_LIST)
# Build with KDE Frameworks. The major version (KF5/KF6) is derived # Build with KDE Frameworks.
# automatically from QT_VERSION_MAJOR -- KDE Frameworks deliberately
# mirrors Qt's own major version numbering, so there is no independent
# choice to make here. See cmake/fetch_kdeaddons.cmake.
option(BUILD_WITH_KF "Build with KDE Frameworks" ON) option(BUILD_WITH_KF "Build with KDE Frameworks" ON)
# Precompiled headers for the Qt umbrella headers. # Precompiled headers for the Qt umbrella headers.
+32 -17
View File
@@ -16,7 +16,6 @@
message(" - fetch_kdeaddons") message(" - fetch_kdeaddons")
# TODO remove path as soon as Qt5 gets retired
if(BUILD_WITH_KF) if(BUILD_WITH_KF)
Include(FetchContent) Include(FetchContent)
@@ -24,16 +23,32 @@ if(BUILD_WITH_KF)
if(BUILD_KF) if(BUILD_KF)
if(KF_MAJOR_VERSION EQUAL 5) # v6.10.0 is a more or less random version, taken as an conservative
if(NOT DEFINED KF_GIT_TAG) # approach. Pinned to the commits v6.10.0 points at, not to the tags
#https://qelectrotech.org/forum/viewtopic.php?pid=13924#p13924 # themselves; see the note in fetch_pugixml.cmake. Each module lives in its
set(KF_GIT_TAG v5.77.0) # own repository, so the same v6.10.0 release is a different commit in
endif() # each.
#
# KDE uses annotated tags, so "git ls-remote <repo> 'refs/tags/v6.10.0*'"
# prints two hashes per module: refs/tags/v6.10.0 is the tag object (the
# tagger, the date and the tag message) and refs/tags/v6.10.0^{} is the
# commit that object points at. The hashes below are the "^{}" ones, i.e.
# the commits. Lightweight tags, such as pugixml's v1.15 and
# SingleApplication's v3.2.0, have no tag object and print only the
# commit line.
set(KF_ECM_GIT_COMMIT 7dd28cc56c339c3f8fb356f7c53c0e8f61433d81) # v6.10.0
set(KF_KCOREADDONS_GIT_COMMIT c569f974dab24b4784ad186a3db4b76b2fa36612) # v6.10.0
set(KF_KWIDGETSADDONS_GIT_COMMIT 1abbed8a280d6626c59fb197f2c4667d2b1e7445) # v6.10.0
if(DEFINED KF_GIT_TAG)
# Explicit override: -DKF_GIT_TAG=<ref> selects one ref for all three
# modules, unpinned, exactly as it did before.
set(KF_ECM_GIT_COMMIT ${KF_GIT_TAG})
set(KF_KCOREADDONS_GIT_COMMIT ${KF_GIT_TAG})
set(KF_KWIDGETSADDONS_GIT_COMMIT ${KF_GIT_TAG})
else() else()
if(NOT DEFINED KF_GIT_TAG) # Keep KF_GIT_TAG defined: define_definitions.cmake reports it.
# this is a more or less random version, taken as an conservative approach set(KF_GIT_TAG v6.10.0)
set(KF_GIT_TAG v6.10.0)
endif()
endif() endif()
# using a function in order to limit the scope of the variables # using a function in order to limit the scope of the variables
# with CMake >=3.25 we could use a block() # with CMake >=3.25 we could use a block()
@@ -61,29 +76,29 @@ if(BUILD_WITH_KF)
FetchContent_Declare( FetchContent_Declare(
ecm ecm
GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git GIT_REPOSITORY https://invent.kde.org/frameworks/extra-cmake-modules.git
GIT_TAG ${KF_GIT_TAG}) GIT_TAG ${KF_ECM_GIT_COMMIT})
FetchContent_MakeAvailable(ecm) FetchContent_MakeAvailable(ecm)
FetchContent_Declare( FetchContent_Declare(
kcoreaddons kcoreaddons
GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git GIT_REPOSITORY https://invent.kde.org/frameworks/kcoreaddons.git
GIT_TAG ${KF_GIT_TAG}) GIT_TAG ${KF_KCOREADDONS_GIT_COMMIT})
FetchContent_MakeAvailable(kcoreaddons) FetchContent_MakeAvailable(kcoreaddons)
FetchContent_Declare( FetchContent_Declare(
kwidgetsaddons kwidgetsaddons
GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git GIT_REPOSITORY https://invent.kde.org/frameworks/kwidgetsaddons.git
GIT_TAG ${KF_GIT_TAG}) GIT_TAG ${KF_KWIDGETSADDONS_GIT_COMMIT})
FetchContent_MakeAvailable(kwidgetsaddons) FetchContent_MakeAvailable(kwidgetsaddons)
endfunction() endfunction()
qet_make_kf_available() qet_make_kf_available()
else() else()
find_package(KF${KF_MAJOR_VERSION}CoreAddons REQUIRED) find_package(KF6CoreAddons REQUIRED)
find_package(KF${KF_MAJOR_VERSION}WidgetsAddons REQUIRED) find_package(KF6WidgetsAddons REQUIRED)
endif() endif()
set(KF_PRIVATE_LIBRARIES set(KF_PRIVATE_LIBRARIES
KF${KF_MAJOR_VERSION}::WidgetsAddons KF6::WidgetsAddons
KF${KF_MAJOR_VERSION}::CoreAddons KF6::CoreAddons
) )
endif() endif()
+42 -1
View File
@@ -22,10 +22,51 @@ option(BUILD_PUGIXML "Build pugixml library, use system one otherwise" YES)
if(BUILD_PUGIXML) if(BUILD_PUGIXML)
# Pinned to the commit v1.15 points at, not to the tag itself.
#
# A git tag is only a named pointer to a commit, and anyone with push access
# to the upstream repository can move it (git push --force) to any other
# commit. FetchContent fetches whatever the tag points at when the build
# runs, so if a maintainer account or CI token is compromised, the attacker
# can retarget a well-known release tag to malicious code: every fresh build
# of QElectroTech then compiles it, while nothing changes in this repository
# and the tag name still looks correct. A commit hash cannot be moved, because
# it is derived from the content: different code always has a different hash.
#
# This attack has been used in the wild:
# - March 2025, tj-actions/changed-files (CVE-2025-30066): tags v1 through
# v45.0.7 were retargeted to a commit that dumped CI secrets into build
# logs, affecting more than 23,000 repositories.
# - March 2026, aquasecurity/trivy-action (CVE-2026-33634): 76 of 77
# version tags were force-pushed to a credential stealer and stayed
# malicious for about 12 hours.
# Both were GitHub Actions rather than CMake dependencies, but the mechanism
# is the same one FetchContent relies on here: resolving a git tag at build
# time.
#
# To upgrade, look up the commit the new tag points at with
# git ls-remote <repo> 'refs/tags/<tag>*', check that it is the release you
# expect, and update both the hash and the trailing tag comment.
#
# How many lines that prints depends on which of the two kinds of tag
# upstream created:
# - A lightweight tag is nothing but a ref pointing straight at the commit,
# so ls-remote prints a single line, "refs/tags/<tag>", and its hash is
# the commit to pin. pugixml tags this way, which is why the v1.15 hash
# below is what "git ls-remote ... refs/tags/v1.15" reports directly;
# SingleApplication (v3.2.0) does the same.
# - An annotated tag is a git object in its own right, carrying a tagger,
# a date, a message and optionally a GPG signature, and pointing at the
# commit. ls-remote then prints two lines: "refs/tags/<tag>" is the tag
# object and "refs/tags/<tag>^{}" is that object dereferenced, i.e. the
# commit. The KDE Frameworks modules tag this way, so for them it is the
# "^{}" hash that belongs in the pin; the other hash identifies the tag
# object itself, which is not the source revision and changes whenever
# upstream re-creates the tag, even over the very same commit.
FetchContent_Declare( FetchContent_Declare(
pugixml pugixml
GIT_REPOSITORY https://github.com/zeux/pugixml.git GIT_REPOSITORY https://github.com/zeux/pugixml.git
GIT_TAG v1.15) GIT_TAG ee86beb30e4973f5feffe3ce63bfa4fbadf72f38) # v1.15
set(PUGIXML_INSTALL OFF CACHE INTERNAL "") set(PUGIXML_INSTALL OFF CACHE INTERNAL "")
FetchContent_MakeAvailable(pugixml) FetchContent_MakeAvailable(pugixml)
else() else()
+8 -2
View File
@@ -31,9 +31,15 @@ if(EXISTS "${CMAKE_SOURCE_DIR}/SingleApplication/CMakeLists.txt")
set(FETCHCONTENT_SOURCE_DIR_SINGLEAPPLICATION "${CMAKE_SOURCE_DIR}/SingleApplication") set(FETCHCONTENT_SOURCE_DIR_SINGLEAPPLICATION "${CMAKE_SOURCE_DIR}/SingleApplication")
endif() endif()
# Pinned to the commit v3.2.0 points at, not to the tag itself; see the note in
# fetch_pugixml.cmake. v3.2.0 is a lightweight tag, a ref pointing straight at
# the commit, so "git ls-remote <repo> refs/tags/v3.2.0" prints that commit and
# nothing else. An annotated tag, as KDE uses in fetch_kdeaddons.cmake, would
# print the tag object under refs/tags/v3.2.0 as well, with the commit on the
# refs/tags/v3.2.0^{} line.
FetchContent_Declare( FetchContent_Declare(
SingleApplication SingleApplication
GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git
GIT_TAG v3.2.0) GIT_TAG aede311d28d20179216c5419b581087be2a8409f) # v3.2.0
set(QT_DEFAULT_MAJOR_VERSION 6)
FetchContent_MakeAvailable(SingleApplication) FetchContent_MakeAvailable(SingleApplication)
+33 -11
View File
@@ -16,12 +16,11 @@
message(" - qet_compilation_vars") message(" - qet_compilation_vars")
# Note: GuiPrivate is intentionally NOT in this list. Qt6's CMake config only # Note: GuiPrivate is intentionally NOT in this list.
# creates the Qt::GuiPrivate target when "GuiPrivate" is explicitly requested # Qt >= 6.7 ships it as a proper find_package component, but some distro
# as a component, but Qt5 has no Qt5GuiPrivate package at all (the target is # packages (e.g. Ubuntu's qt6-base-private-dev) omit Qt6GuiPrivateConfig.cmake
# created implicitly with Gui), so requesting it as a component breaks the # and only provide the implicit Qt6::GuiPrivate target created alongside
# whole Qt5 configure. It is requested separately, guarded by # Qt6::Gui. Checks are done in the main CMakeLists.txt
# QT_VERSION_MAJOR, after the main find_package.
# (Needed for QPdfEngine::drawHyperlink, the PDF internal links.) # (Needed for QPdfEngine::drawHyperlink, the PDF internal links.)
set(QET_COMPONENTS set(QET_COMPONENTS
LinguistTools LinguistTools
@@ -249,6 +248,12 @@ set(QET_SRC_FILES
${QET_DIR}/sources/qet.h ${QET_DIR}/sources/qet.h
${QET_DIR}/sources/qeticons.cpp ${QET_DIR}/sources/qeticons.cpp
${QET_DIR}/sources/qeticons.h ${QET_DIR}/sources/qeticons.h
${QET_DIR}/sources/palettegraphicsview.cpp
${QET_DIR}/sources/palettegraphicsview.h
${QET_DIR}/sources/qetpalette.cpp
${QET_DIR}/sources/qetpalette.h
${QET_DIR}/sources/qetstyle.cpp
${QET_DIR}/sources/qetstyle.h
${QET_DIR}/sources/qetinformation.cpp ${QET_DIR}/sources/qetinformation.cpp
${QET_DIR}/sources/qetinformation.h ${QET_DIR}/sources/qetinformation.h
${QET_DIR}/sources/qetmainwindow.cpp ${QET_DIR}/sources/qetmainwindow.cpp
@@ -330,6 +335,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/diagramevent/diagrameventinterface.h ${QET_DIR}/sources/diagramevent/diagrameventinterface.h
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.cpp ${QET_DIR}/sources/diagramevent/diagrameventaddmacro.cpp
${QET_DIR}/sources/diagramevent/diagrameventaddmacro.h ${QET_DIR}/sources/diagramevent/diagrameventaddmacro.h
${QET_DIR}/sources/diagramevent/diagrameventaddpaste.cpp
${QET_DIR}/sources/diagramevent/diagrameventaddpaste.h
${QET_DIR}/sources/dvevent/dveventinterface.cpp ${QET_DIR}/sources/dvevent/dveventinterface.cpp
${QET_DIR}/sources/dvevent/dveventinterface.h ${QET_DIR}/sources/dvevent/dveventinterface.h
@@ -431,6 +438,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ElementsCollection/elementcollectionhandler.h ${QET_DIR}/sources/ElementsCollection/elementcollectionhandler.h
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.cpp ${QET_DIR}/sources/ElementsCollection/elementcollectionitem.cpp
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.h ${QET_DIR}/sources/ElementsCollection/elementcollectionitem.h
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.cpp
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp ${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h ${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp ${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
@@ -696,6 +705,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/contactgroupselectiondialog.h ${QET_DIR}/sources/ui/contactgroupselectiondialog.h
${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp ${QET_DIR}/sources/ui/conductorpropertiesdialog.cpp
${QET_DIR}/sources/ui/conductorpropertiesdialog.h ${QET_DIR}/sources/ui/conductorpropertiesdialog.h
${QET_DIR}/sources/ui/conductorcolortoolbutton.cpp
${QET_DIR}/sources/ui/conductorcolortoolbutton.h
${QET_DIR}/sources/ui/conductorpropertieswidget.cpp ${QET_DIR}/sources/ui/conductorpropertieswidget.cpp
${QET_DIR}/sources/ui/conductorpropertieswidget.h ${QET_DIR}/sources/ui/conductorpropertieswidget.h
${QET_DIR}/sources/ui/configsaveloaderwidget.cpp ${QET_DIR}/sources/ui/configsaveloaderwidget.cpp
@@ -837,15 +848,26 @@ if(NOT BUILD_WITH_KF)
) )
endif() endif()
# Qt6-only: PDF page import files list(APPEND QET_SRC_FILES
if(QT_VERSION_MAJOR GREATER_EQUAL 6)
list(APPEND QET_SRC_FILES
${QET_DIR}/sources/diagramevent/diagrameventaddpdf.cpp ${QET_DIR}/sources/diagramevent/diagrameventaddpdf.cpp
${QET_DIR}/sources/diagramevent/diagrameventaddpdf.h ${QET_DIR}/sources/diagramevent/diagrameventaddpdf.h
${QET_DIR}/sources/ui/pdfpagesdialog.cpp ${QET_DIR}/sources/ui/pdfpagesdialog.cpp
${QET_DIR}/sources/ui/pdfpagesdialog.h ${QET_DIR}/sources/ui/pdfpagesdialog.h
) )
endif()
# JavaScript scripting (bugtracker #162). Unconditionally in the source
# list, like the QtPdf files above: this file is included before the
# QET_HAS_SCRIPTING probe runs in the top-level CMakeLists.txt, so the
# variable isn't set yet here. Same pattern as QtPdf: always compiled, the
# actual Qt::Qml dependent code is behind #ifdef QET_HAS_SCRIPTING inside
# qetscripting.cpp/qetscriptapi.cpp themselves, compiling to a harmless
# stub when the module wasn't found.
list(APPEND QET_SRC_FILES
${QET_DIR}/sources/scripting/qetscriptapi.cpp
${QET_DIR}/sources/scripting/qetscriptapi.h
${QET_DIR}/sources/scripting/qetscripting.cpp
${QET_DIR}/sources/scripting/qetscripting.h
)
set(TS_FILES set(TS_FILES
${QET_DIR}/lang/qet_ar.ts ${QET_DIR}/lang/qet_ar.ts
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+29
View File
@@ -0,0 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
<!-- keyboard body with a lighter top edge -->
<rect x="6" y="30" width="116" height="68" rx="8" fill="#31363b"/>
<rect x="6" y="30" width="116" height="34" rx="8" fill="#4d4d4d"/>
<rect x="6" y="46" width="116" height="18" fill="#31363b"/>
<!-- key rows -->
<g fill="#eff0f1">
<rect x="14" y="38" width="8" height="8" rx="1.5"/><rect x="24" y="38" width="8" height="8" rx="1.5"/>
<rect x="34" y="38" width="8" height="8" rx="1.5"/><rect x="44" y="38" width="8" height="8" rx="1.5"/>
<rect x="54" y="38" width="8" height="8" rx="1.5"/><rect x="64" y="38" width="8" height="8" rx="1.5"/>
<rect x="74" y="38" width="8" height="8" rx="1.5"/><rect x="84" y="38" width="8" height="8" rx="1.5"/>
<rect x="94" y="38" width="8" height="8" rx="1.5"/><rect x="104" y="38" width="10" height="8" rx="1.5"/>
<rect x="14" y="50" width="12" height="8" rx="1.5"/><rect x="28" y="50" width="8" height="8" rx="1.5"/>
<rect x="38" y="50" width="8" height="8" rx="1.5"/><rect x="48" y="50" width="8" height="8" rx="1.5"/>
<rect x="58" y="50" width="8" height="8" rx="1.5"/><rect x="68" y="50" width="8" height="8" rx="1.5"/>
<rect x="78" y="50" width="8" height="8" rx="1.5"/><rect x="88" y="50" width="8" height="8" rx="1.5"/>
<rect x="98" y="50" width="16" height="8" rx="1.5"/>
<rect x="14" y="62" width="16" height="8" rx="1.5"/><rect x="32" y="62" width="8" height="8" rx="1.5"/>
<rect x="42" y="62" width="8" height="8" rx="1.5"/><rect x="52" y="62" width="8" height="8" rx="1.5"/>
<rect x="62" y="62" width="8" height="8" rx="1.5"/><rect x="72" y="62" width="8" height="8" rx="1.5"/>
<rect x="82" y="62" width="8" height="8" rx="1.5"/><rect x="92" y="62" width="22" height="8" rx="1.5"/>
<rect x="14" y="74" width="10" height="8" rx="1.5"/><rect x="26" y="74" width="10" height="8" rx="1.5"/>
<rect x="38" y="74" width="52" height="8" rx="1.5"/>
<rect x="92" y="74" width="10" height="8" rx="1.5"/><rect x="104" y="74" width="10" height="8" rx="1.5"/>
</g>
<!-- the two keys of a shortcut, pressed -->
<rect x="14" y="62" width="16" height="8" rx="1.5" fill="#3daee9"/>
<rect x="42" y="62" width="8" height="8" rx="1.5" fill="#3daee9"/>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

+48
View File
@@ -0,0 +1,48 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128">
<!-- DIN rail -->
<rect x="6" y="86" width="116" height="18" rx="3" fill="#7f8c8d"/>
<rect x="6" y="92" width="116" height="6" fill="#4d4d4d"/>
<!-- six terminal blocks: L, L, N, PE, L, spare -->
<g fill="#eff0f1">
<rect x="12" y="26" width="16" height="66" rx="2"/>
<rect x="30" y="26" width="16" height="66" rx="2"/>
<rect x="48" y="26" width="16" height="66" rx="2"/>
<rect x="66" y="26" width="16" height="66" rx="2"/>
<rect x="84" y="26" width="16" height="66" rx="2"/>
<rect x="102" y="26" width="16" height="66" rx="2"/>
</g>
<!-- right-hand shading of each block -->
<g fill="#bdc3c7">
<rect x="24" y="28" width="4" height="62"/>
<rect x="42" y="28" width="4" height="62"/>
<rect x="60" y="28" width="4" height="62"/>
<rect x="78" y="28" width="4" height="62"/>
<rect x="96" y="28" width="4" height="62"/>
<rect x="114" y="28" width="4" height="62"/>
</g>
<!-- colored marker bands -->
<rect x="12" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
<rect x="30" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
<rect x="48" y="26" width="16" height="10" rx="2" fill="#3daee9"/>
<rect x="66" y="26" width="16" height="10" rx="2" fill="#27ae60"/>
<rect x="70" y="26" width="8" height="10" fill="#fdbc4b"/>
<rect x="84" y="26" width="16" height="10" rx="2" fill="#7f8c8d"/>
<rect x="102" y="26" width="16" height="10" rx="2" fill="#da4453"/>
<!-- screws -->
<g fill="#31363b">
<circle cx="20" cy="50" r="4.5"/><circle cx="20" cy="74" r="4.5"/>
<circle cx="38" cy="50" r="4.5"/><circle cx="38" cy="74" r="4.5"/>
<circle cx="56" cy="50" r="4.5"/><circle cx="56" cy="74" r="4.5"/>
<circle cx="74" cy="50" r="4.5"/><circle cx="74" cy="74" r="4.5"/>
<circle cx="92" cy="50" r="4.5"/><circle cx="92" cy="74" r="4.5"/>
<circle cx="110" cy="50" r="4.5"/><circle cx="110" cy="74" r="4.5"/>
</g>
<g fill="#eff0f1">
<rect x="16" y="49" width="8" height="2"/><rect x="16" y="73" width="8" height="2"/>
<rect x="34" y="49" width="8" height="2"/><rect x="34" y="73" width="8" height="2"/>
<rect x="52" y="49" width="8" height="2"/><rect x="52" y="73" width="8" height="2"/>
<rect x="70" y="49" width="8" height="2"/><rect x="70" y="73" width="8" height="2"/>
<rect x="88" y="49" width="8" height="2"/><rect x="88" y="73" width="8" height="2"/>
<rect x="106" y="49" width="8" height="2"/><rect x="106" y="73" width="8" height="2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 233 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

+3
View File
@@ -216,6 +216,9 @@ Clarification:
ico/128x128/diagram.png by the QElectroTech team (License CC BY-ND 3.0) ico/128x128/diagram.png by the QElectroTech team (License CC BY-ND 3.0)
ico/128x128/document-export.png by the QElectroTech team (License CC BY-ND 3.0) ico/128x128/document-export.png by the QElectroTech team (License CC BY-ND 3.0)
ico/128x128/project.png by the QElectroTech team (License CC BY-ND 3.0) ico/128x128/project.png by the QElectroTech team (License CC BY-ND 3.0)
ico/128x128/terminalstrip.png and configure-shortcuts.png by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), rendered from the .svg files beside them
ico/scalable/pdf-import.svg by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), laid out like ico/22x22/insert-image.png
ico/scalable/diagram.svg, folio-new.svg, folio-delete.svg, folio-properties.svg, label.svg by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), the folio icons redrawn in the same style
ico/256x256/* by Nuri from the QElectroTech team (License CC BY-ND 3.0) ico/256x256/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
ico/breeze-icons/* by Nuri from the QElectroTech team (License CC BY-ND 3.0) ico/breeze-icons/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
ico/diagram.png by Nuri from the QElectroTech team (License CC BY-ND 3.0) ico/diagram.png by Nuri from the QElectroTech team (License CC BY-ND 3.0)
+477
View File
@@ -0,0 +1,477 @@
<!DOCTYPE RCC>
<!-- Generated by misc/make_icon_themes.py. Do not edit. -->
<RCC version="1.0">
<qresource prefix="/ico">
<file>themes/qet/index.theme</file>
<file>themes/qet-dark/index.theme</file>
<file alias="themes/qet/16x16/application-exit.png">16x16/application-exit.png</file>
<file alias="themes/qet/16x16/arrow-left-double.png">16x16/arrow-left-double.png</file>
<file alias="themes/qet/16x16/arrow-left.png">16x16/arrow-left.png</file>
<file alias="themes/qet/16x16/arrow-right-double.png">16x16/arrow-right-double.png</file>
<file alias="themes/qet/16x16/arrow-right.png">16x16/arrow-right.png</file>
<file alias="themes/qet/16x16/circle.png">16x16/circle.png</file>
<file alias="themes/qet/16x16/conductor-edit.png">16x16/conductor-edit.png</file>
<file alias="themes/qet/16x16/conductor-reset.png">16x16/conductor-reset.png</file>
<file alias="themes/qet/16x16/conductor.png">16x16/conductor.png</file>
<file alias="themes/qet/16x16/configure-toolbars.png">16x16/configure-toolbars.png</file>
<file alias="themes/qet/16x16/configure.png">16x16/configure.png</file>
<file alias="themes/qet/16x16/diagram.png">16x16/diagram.png</file>
<file alias="themes/qet/16x16/dialog-cancel.png">16x16/dialog-cancel.png</file>
<file alias="themes/qet/16x16/dialog-ok.png">16x16/dialog-ok.png</file>
<file alias="themes/qet/16x16/document-close.png">16x16/document-close.png</file>
<file alias="themes/qet/16x16/document-export.png">16x16/document-export.png</file>
<file alias="themes/qet/16x16/document-import.png">16x16/document-import.png</file>
<file alias="themes/qet/16x16/document-new.png">16x16/document-new.png</file>
<file alias="themes/qet/16x16/document-open-recent.png">16x16/document-open-recent.png</file>
<file alias="themes/qet/16x16/document-open.png">16x16/document-open.png</file>
<file alias="themes/qet/16x16/document-print-frame.png">16x16/document-print-frame.png</file>
<file alias="themes/qet/16x16/document-print.png">16x16/document-print.png</file>
<file alias="themes/qet/16x16/document-save-all.png">16x16/document-save-all.png</file>
<file alias="themes/qet/16x16/document-save-as.png">16x16/document-save-as.png</file>
<file alias="themes/qet/16x16/document-save.png">16x16/document-save.png</file>
<file alias="themes/qet/16x16/east.png">16x16/east.png</file>
<file alias="themes/qet/16x16/edit-clear-locationbar-ltr.png">16x16/edit-clear-locationbar-ltr.png</file>
<file alias="themes/qet/16x16/edit-clear-locationbar-rtl.png">16x16/edit-clear-locationbar-rtl.png</file>
<file alias="themes/qet/16x16/edit-clear.png">16x16/edit-clear.png</file>
<file alias="themes/qet/16x16/edit-copy.png">16x16/edit-copy.png</file>
<file alias="themes/qet/16x16/edit-cut.png">16x16/edit-cut.png</file>
<file alias="themes/qet/16x16/edit-delete.png">16x16/edit-delete.png</file>
<file alias="themes/qet/16x16/edit-download.png">16x16/edit-download.png</file>
<file alias="themes/qet/16x16/edit-paste.png">16x16/edit-paste.png</file>
<file alias="themes/qet/16x16/edit-redo.png">16x16/edit-redo.png</file>
<file alias="themes/qet/16x16/edit-rename.png">16x16/edit-rename.png</file>
<file alias="themes/qet/16x16/edit-select-all.png">16x16/edit-select-all.png</file>
<file alias="themes/qet/16x16/edit-select-invert.png">16x16/edit-select-invert.png</file>
<file alias="themes/qet/16x16/edit-select-none.png">16x16/edit-select-none.png</file>
<file alias="themes/qet/16x16/edit-table-cell-merge.png">16x16/edit-table-cell-merge.png</file>
<file alias="themes/qet/16x16/edit-table-cell-split.png">16x16/edit-table-cell-split.png</file>
<file alias="themes/qet/16x16/edit-table-delete-column.png">16x16/edit-table-delete-column.png</file>
<file alias="themes/qet/16x16/edit-table-delete-row.png">16x16/edit-table-delete-row.png</file>
<file alias="themes/qet/16x16/edit-table-insert-column-left.png">16x16/edit-table-insert-column-left.png</file>
<file alias="themes/qet/16x16/edit-table-insert-column-right.png">16x16/edit-table-insert-column-right.png</file>
<file alias="themes/qet/16x16/edit-table-insert-row-above.png">16x16/edit-table-insert-row-above.png</file>
<file alias="themes/qet/16x16/edit-table-insert-row-under.png">16x16/edit-table-insert-row-under.png</file>
<file alias="themes/qet/16x16/edit-undo.png">16x16/edit-undo.png</file>
<file alias="themes/qet/16x16/element-delete.png">16x16/element-delete.png</file>
<file alias="themes/qet/16x16/element-edit.png">16x16/element-edit.png</file>
<file alias="themes/qet/16x16/element-master.png">16x16/element-master.png</file>
<file alias="themes/qet/16x16/element-new.png">16x16/element-new.png</file>
<file alias="themes/qet/16x16/element-slave.png">16x16/element-slave.png</file>
<file alias="themes/qet/16x16/element.png">16x16/element.png</file>
<file alias="themes/qet/16x16/endline-circle.png">16x16/endline-circle.png</file>
<file alias="themes/qet/16x16/endline-diamond.png">16x16/endline-diamond.png</file>
<file alias="themes/qet/16x16/endline-none.png">16x16/endline-none.png</file>
<file alias="themes/qet/16x16/endline-simple.png">16x16/endline-simple.png</file>
<file alias="themes/qet/16x16/endline-triangle.png">16x16/endline-triangle.png</file>
<file alias="themes/qet/16x16/export-csv.png">16x16/export-csv.png</file>
<file alias="themes/qet/16x16/flip.png">16x16/flip.png</file>
<file alias="themes/qet/16x16/folder-delete.png">16x16/folder-delete.png</file>
<file alias="themes/qet/16x16/folder-edit.png">16x16/folder-edit.png</file>
<file alias="themes/qet/16x16/folder-new.png">16x16/folder-new.png</file>
<file alias="themes/qet/16x16/folder-only-this.png">16x16/folder-only-this.png</file>
<file alias="themes/qet/16x16/folder-open.png">16x16/folder-open.png</file>
<file alias="themes/qet/16x16/folder-properties.png">16x16/folder-properties.png</file>
<file alias="themes/qet/16x16/folder-show-all.png">16x16/folder-show-all.png</file>
<file alias="themes/qet/16x16/folder.png">16x16/folder.png</file>
<file alias="themes/qet/16x16/folio-delete.png">16x16/folio-delete.png</file>
<file alias="themes/qet/16x16/folio-new.png">16x16/folio-new.png</file>
<file alias="themes/qet/16x16/folio-properties.png">16x16/folio-properties.png</file>
<file alias="themes/qet/16x16/folio-ref-coming.png">16x16/folio-ref-coming.png</file>
<file alias="themes/qet/16x16/go-bottom.png">16x16/go-bottom.png</file>
<file alias="themes/qet/16x16/go-company.png">16x16/go-company.png</file>
<file alias="themes/qet/16x16/go-down-double.png">16x16/go-down-double.png</file>
<file alias="themes/qet/16x16/go-down.png">16x16/go-down.png</file>
<file alias="themes/qet/16x16/go-home.png">16x16/go-home.png</file>
<file alias="themes/qet/16x16/go-top.png">16x16/go-top.png</file>
<file alias="themes/qet/16x16/go-up-double.png">16x16/go-up-double.png</file>
<file alias="themes/qet/16x16/go-up.png">16x16/go-up.png</file>
<file alias="themes/qet/16x16/grid.png">16x16/grid.png</file>
<file alias="themes/qet/16x16/ground.png">16x16/ground.png</file>
<file alias="themes/qet/16x16/help-contents.png">16x16/help-contents.png</file>
<file alias="themes/qet/16x16/help-donate.png">16x16/help-donate.png</file>
<file alias="themes/qet/16x16/item-cancel.png">16x16/item-cancel.png</file>
<file alias="themes/qet/16x16/item-copy.png">16x16/item-copy.png</file>
<file alias="themes/qet/16x16/item-move.png">16x16/item-move.png</file>
<file alias="themes/qet/16x16/kdenlive-show-video.png">16x16/kdenlive-show-video.png</file>
<file alias="themes/qet/16x16/label.png">16x16/label.png</file>
<file alias="themes/qet/16x16/list-add.png">16x16/list-add.png</file>
<file alias="themes/qet/16x16/list-remove.png">16x16/list-remove.png</file>
<file alias="themes/qet/16x16/masquer.png">16x16/masquer.png</file>
<file alias="themes/qet/16x16/mirror.png">16x16/mirror.png</file>
<file alias="themes/qet/16x16/move.png">16x16/move.png</file>
<file alias="themes/qet/16x16/neutral.png">16x16/neutral.png</file>
<file alias="themes/qet/16x16/north.png">16x16/north.png</file>
<file alias="themes/qet/16x16/object-group.png">16x16/object-group.png</file>
<file alias="themes/qet/16x16/object-rotate-right.png">16x16/object-rotate-right.png</file>
<file alias="themes/qet/16x16/orientations.png">16x16/orientations.png</file>
<file alias="themes/qet/16x16/phase.png">16x16/phase.png</file>
<file alias="themes/qet/16x16/preferences-desktop-user.png">16x16/preferences-desktop-user.png</file>
<file alias="themes/qet/16x16/project-close.png">16x16/project-close.png</file>
<file alias="themes/qet/16x16/project-delete.png">16x16/project-delete.png</file>
<file alias="themes/qet/16x16/project-new.png">16x16/project-new.png</file>
<file alias="themes/qet/16x16/project-properties.png">16x16/project-properties.png</file>
<file alias="themes/qet/16x16/project.png">16x16/project.png</file>
<file alias="themes/qet/16x16/qet.png">16x16/qet.png</file>
<file alias="themes/qet/16x16/qt.png">16x16/qt.png</file>
<file alias="themes/qet/16x16/run-dxf.png">16x16/run-dxf.png</file>
<file alias="themes/qet/16x16/select.png">16x16/select.png</file>
<file alias="themes/qet/16x16/south.png">16x16/south.png</file>
<file alias="themes/qet/16x16/table-of-content.png">16x16/table-of-content.png</file>
<file alias="themes/qet/16x16/terminal.png">16x16/terminal.png</file>
<file alias="themes/qet/16x16/terminalstrip.png">16x16/terminalstrip.png</file>
<file alias="themes/qet/16x16/text-xml.png">16x16/text-xml.png</file>
<file alias="themes/qet/16x16/transform-rotate.png">16x16/transform-rotate.png</file>
<file alias="themes/qet/16x16/user-busy.png">16x16/user-busy.png</file>
<file alias="themes/qet/16x16/user-online.png">16x16/user-online.png</file>
<file alias="themes/qet/16x16/view-fullscreen.png">16x16/view-fullscreen.png</file>
<file alias="themes/qet/16x16/view-refresh.png">16x16/view-refresh.png</file>
<file alias="themes/qet/16x16/view-restore.png">16x16/view-restore.png</file>
<file alias="themes/qet/16x16/west.png">16x16/west.png</file>
<file alias="themes/qet/16x16/window-new.png">16x16/window-new.png</file>
<file alias="themes/qet/16x16/zoom-draw.png">16x16/zoom-draw.png</file>
<file alias="themes/qet/16x16/zoom-fit-best.png">16x16/zoom-fit-best.png</file>
<file alias="themes/qet/16x16/zoom-in.png">16x16/zoom-in.png</file>
<file alias="themes/qet/16x16/zoom-original.png">16x16/zoom-original.png</file>
<file alias="themes/qet/16x16/zoom-out.png">16x16/zoom-out.png</file>
<file alias="themes/qet/22x22/Actions-player-time-icon.png">22x22/Actions-player-time-icon.png</file>
<file alias="themes/qet/22x22/all_pages.png">22x22/all_pages.png</file>
<file alias="themes/qet/22x22/application-exit.png">22x22/application-exit.png</file>
<file alias="themes/qet/22x22/applications-development-translation.png">22x22/applications-development-translation.png</file>
<file alias="themes/qet/22x22/arc.png">22x22/arc.png</file>
<file alias="themes/qet/22x22/arrow-left-double.png">22x22/arrow-left-double.png</file>
<file alias="themes/qet/22x22/arrow-left.png">22x22/arrow-left.png</file>
<file alias="themes/qet/22x22/arrow-right-double.png">22x22/arrow-right-double.png</file>
<file alias="themes/qet/22x22/arrow-right.png">22x22/arrow-right.png</file>
<file alias="themes/qet/22x22/autoconnect.png">22x22/autoconnect.png</file>
<file alias="themes/qet/22x22/bring_forward.png">22x22/bring_forward.png</file>
<file alias="themes/qet/22x22/conductor2.png">22x22/conductor2.png</file>
<file alias="themes/qet/22x22/conductor-reset.png">22x22/conductor2.png</file>
<file alias="themes/qet/22x22/configure-toolbars.png">22x22/configure-toolbars.png</file>
<file alias="themes/qet/22x22/configure.png">22x22/configure.png</file>
<file alias="themes/qet/22x22/diagram_bg.png">22x22/diagram_bg.png</file>
<file alias="themes/qet/22x22/dialog-cancel.png">22x22/dialog-cancel.png</file>
<file alias="themes/qet/22x22/dialog-ok.png">22x22/dialog-ok.png</file>
<file alias="themes/qet/22x22/document-close.png">22x22/document-close.png</file>
<file alias="themes/qet/22x22/document-export.png">22x22/document-export.png</file>
<file alias="themes/qet/22x22/document-import.png">22x22/document-import.png</file>
<file alias="themes/qet/22x22/document-new.png">22x22/document-new.png</file>
<file alias="themes/qet/22x22/document-open-recent.png">22x22/document-open-recent.png</file>
<file alias="themes/qet/22x22/document-open.png">22x22/document-open.png</file>
<file alias="themes/qet/22x22/document-print-frame.png">22x22/document-print-frame.png</file>
<file alias="themes/qet/22x22/document-print.png">22x22/document-print.png</file>
<file alias="themes/qet/22x22/document-save-all.png">22x22/document-save-all.png</file>
<file alias="themes/qet/22x22/document-save-as.png">22x22/document-save-as.png</file>
<file alias="themes/qet/22x22/document-save.png">22x22/document-save.png</file>
<file alias="themes/qet/22x22/edit-clear-locationbar-ltr.png">22x22/edit-clear-locationbar-ltr.png</file>
<file alias="themes/qet/22x22/edit-clear-locationbar-rtl.png">22x22/edit-clear-locationbar-rtl.png</file>
<file alias="themes/qet/22x22/edit-clear.png">22x22/edit-clear.png</file>
<file alias="themes/qet/22x22/edit-copy.png">22x22/edit-copy.png</file>
<file alias="themes/qet/22x22/edit-cut.png">22x22/edit-cut.png</file>
<file alias="themes/qet/22x22/edit-delete.png">22x22/edit-delete.png</file>
<file alias="themes/qet/22x22/edit-paste.png">22x22/edit-paste.png</file>
<file alias="themes/qet/22x22/edit-redo.png">22x22/edit-redo.png</file>
<file alias="themes/qet/22x22/edit-rename.png">22x22/edit-rename.png</file>
<file alias="themes/qet/22x22/edit-select-all.png">22x22/edit-select-all.png</file>
<file alias="themes/qet/22x22/edit-table-cell-merge.png">22x22/edit-table-cell-merge.png</file>
<file alias="themes/qet/22x22/edit-table-cell-split.png">22x22/edit-table-cell-split.png</file>
<file alias="themes/qet/22x22/edit-table-delete-column.png">22x22/edit-table-delete-column.png</file>
<file alias="themes/qet/22x22/edit-table-delete-row.png">22x22/edit-table-delete-row.png</file>
<file alias="themes/qet/22x22/edit-table-insert-column-left.png">22x22/edit-table-insert-column-left.png</file>
<file alias="themes/qet/22x22/edit-table-insert-column-right.png">22x22/edit-table-insert-column-right.png</file>
<file alias="themes/qet/22x22/edit-table-insert-row-above.png">22x22/edit-table-insert-row-above.png</file>
<file alias="themes/qet/22x22/edit-table-insert-row-under.png">22x22/edit-table-insert-row-under.png</file>
<file alias="themes/qet/22x22/edit-undo.png">22x22/edit-undo.png</file>
<file alias="themes/qet/22x22/element-delete.png">22x22/element-delete.png</file>
<file alias="themes/qet/22x22/element-edit.png">22x22/element-edit.png</file>
<file alias="themes/qet/22x22/element-new.png">22x22/element-new.png</file>
<file alias="themes/qet/22x22/ellipse.png">22x22/ellipse.png</file>
<file alias="themes/qet/22x22/export-csv.png">22x22/export-csv.png</file>
<file alias="themes/qet/22x22/folder-delete.png">22x22/folder-delete.png</file>
<file alias="themes/qet/22x22/folder-edit.png">22x22/folder-edit.png</file>
<file alias="themes/qet/22x22/folder-new.png">22x22/folder-new.png</file>
<file alias="themes/qet/22x22/folder-open.png">22x22/folder-open.png</file>
<file alias="themes/qet/22x22/format-text-subscript.png">22x22/format-text-subscript.png</file>
<file alias="themes/qet/22x22/format-text-superscript.png">22x22/format-text-superscript.png</file>
<file alias="themes/qet/22x22/go-company.png">22x22/go-company.png</file>
<file alias="themes/qet/22x22/go-down.png">22x22/go-down.png</file>
<file alias="themes/qet/22x22/go-home.png">22x22/go-home.png</file>
<file alias="themes/qet/22x22/go-up.png">22x22/go-up.png</file>
<file alias="themes/qet/22x22/grid.png">22x22/grid.png</file>
<file alias="themes/qet/22x22/guides.png">22x22/guides.png</file>
<file alias="themes/qet/22x22/hotspot.png">22x22/hotspot.png</file>
<file alias="themes/qet/22x22/insert-image.png">22x22/insert-image.png</file>
<file alias="themes/qet/22x22/landscape.png">22x22/landscape.png</file>
<file alias="themes/qet/22x22/line.png">22x22/line.png</file>
<file alias="themes/qet/22x22/list-add.png">22x22/list-add.png</file>
<file alias="themes/qet/22x22/list-remove.png">22x22/list-remove.png</file>
<file alias="themes/qet/22x22/lower.png">22x22/lower.png</file>
<file alias="themes/qet/22x22/move.png">22x22/move.png</file>
<file alias="themes/qet/22x22/names.png">22x22/names.png</file>
<file alias="themes/qet/22x22/object-locked.png">22x22/object-locked.png</file>
<file alias="themes/qet/22x22/object-rotate-right.png">22x22/object-rotate-right.png</file>
<file alias="themes/qet/22x22/object-unlocked.png">22x22/object-unlocked.png</file>
<file alias="themes/qet/22x22/polygon.png">22x22/polygon.png</file>
<file alias="themes/qet/22x22/portrait.png">22x22/portrait.png</file>
<file alias="themes/qet/22x22/preferences-desktop-user.png">22x22/preferences-desktop-user.png</file>
<file alias="themes/qet/22x22/project-close.png">22x22/project-close.png</file>
<file alias="themes/qet/22x22/project-delete.png">22x22/project-delete.png</file>
<file alias="themes/qet/22x22/project-new.png">22x22/project-new.png</file>
<file alias="themes/qet/22x22/project.png">22x22/project.png</file>
<file alias="themes/qet/22x22/raise.png">22x22/raise.png</file>
<file alias="themes/qet/22x22/rectangle.png">22x22/rectangle.png</file>
<file alias="themes/qet/22x22/restaurer.png">22x22/restaurer.png</file>
<file alias="themes/qet/22x22/select.png">22x22/select.png</file>
<file alias="themes/qet/22x22/send_backward.png">22x22/send_backward.png</file>
<file alias="themes/qet/22x22/single_page.png">22x22/single_page.png</file>
<file alias="themes/qet/22x22/start.png">22x22/start.png</file>
<file alias="themes/qet/22x22/terminal.png">22x22/terminal.png</file>
<file alias="themes/qet/22x22/terminalstrip.png">22x22/terminalstrip.png</file>
<file alias="themes/qet/22x22/text-xml.png">22x22/text-xml.png</file>
<file alias="themes/qet/22x22/text.png">22x22/text.png</file>
<file alias="themes/qet/22x22/textfield.png">22x22/textfield.png</file>
<file alias="themes/qet/22x22/titleblock-bottom.png">22x22/titleblock-bottom.png</file>
<file alias="themes/qet/22x22/titleblock-right.png">22x22/titleblock-right.png</file>
<file alias="themes/qet/22x22/transform-scale.png">22x22/transform-scale.png</file>
<file alias="themes/qet/22x22/two_pages.png">22x22/two_pages.png</file>
<file alias="themes/qet/22x22/view-fit-window.png">22x22/view-fit-window.png</file>
<file alias="themes/qet/22x22/view-fullscreen.png">22x22/view-fullscreen.png</file>
<file alias="themes/qet/22x22/view-refresh.png">22x22/view-refresh.png</file>
<file alias="themes/qet/22x22/view-restore.png">22x22/view-restore.png</file>
<file alias="themes/qet/22x22/view_fit_width.png">22x22/view_fit_width.png</file>
<file alias="themes/qet/22x22/window-new.png">22x22/window-new.png</file>
<file alias="themes/qet/22x22/zoom-draw.png">22x22/zoom-draw.png</file>
<file alias="themes/qet/22x22/zoom-fit-best.png">22x22/zoom-fit-best.png</file>
<file alias="themes/qet/22x22/zoom-in.png">22x22/zoom-in.png</file>
<file alias="themes/qet/22x22/zoom-original.png">22x22/zoom-original.png</file>
<file alias="themes/qet/22x22/zoom-out.png">22x22/zoom-out.png</file>
<file alias="themes/qet/32x32/application-pdf.png">32x32/application-pdf.png</file>
<file alias="themes/qet/32x32/format-text-bold.png">32x32/format-text-bold.png</file>
<file alias="themes/qet/32x32/format-text-color.png">32x32/format-text-color.png</file>
<file alias="themes/qet/32x32/format-text-italic.png">32x32/format-text-italic.png</file>
<file alias="themes/qet/32x32/format-text-underline.png">32x32/format-text-underline.png</file>
<file alias="themes/qet/32x32/image-x-eps.png">32x32/image-x-eps.png</file>
<file alias="themes/qet/32x32/insert-link.png">32x32/insert-link.png</file>
<file alias="themes/qet/32x32/printer.png">32x32/printer.png</file>
<file alias="themes/qet/32x32/qt.png">32x32/qt.png</file>
<file alias="themes/qet/32x32/simplifyrichtext.png">32x32/simplifyrichtext.png</file>
<file alias="themes/qet/32x32/text-xml.png">32x32/text-xml.png</file>
<file alias="themes/qet/48x48/document-print-frame.png">48x48/document-print-frame.png</file>
<file alias="themes/qet/48x48/user-away-extended.png">48x48/user-away-extended.png</file>
<file alias="themes/qet/48x48/user-away.png">48x48/user-away.png</file>
<file alias="themes/qet/48x48/view-pim-journal.png">48x48/view-pim-journal.png</file>
<file alias="themes/qet/128x128/configure-shortcuts.png">128x128/configure-shortcuts.png</file>
<file alias="themes/qet/128x128/diagram.png">128x128/diagram.png</file>
<file alias="themes/qet/128x128/document-export.png">128x128/document-export.png</file>
<file alias="themes/qet/128x128/plasmagik.png">128x128/plasmagik.png</file>
<file alias="themes/qet/128x128/printer.png">128x128/printer.png</file>
<file alias="themes/qet/128x128/project.png">128x128/project.png</file>
<file alias="themes/qet/128x128/settings.png">128x128/settings.png</file>
<file alias="themes/qet/128x128/terminalstrip.png">128x128/terminalstrip.png</file>
<file alias="themes/qet/scalable/diagram.svg">scalable/diagram.svg</file>
<file alias="themes/qet/scalable/folio-delete.svg">scalable/folio-delete.svg</file>
<file alias="themes/qet/scalable/folio-new.svg">scalable/folio-new.svg</file>
<file alias="themes/qet/scalable/folio-properties.svg">scalable/folio-properties.svg</file>
<file alias="themes/qet/scalable/label.svg">scalable/label.svg</file>
<file alias="themes/qet/scalable/pdf-import.svg">scalable/pdf-import.svg</file>
<file alias="themes/qet/scalable/edit-opacity.svg">breeze-icons/scalable/apps/hidef/edit-opacity.svg</file>
<file alias="themes/qet/scalable/image-flip-horizontal.svg">breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg</file>
<file alias="themes/qet/scalable/image-flip-vertical.svg">breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg</file>
<file alias="themes/qet/scalable/draw-bezier-curves.svg">breeze-icons/scalable/apps/hidef/draw-bezier-curves.svg</file>
<file alias="themes/qet/scalable/transform-crop.svg">breeze-icons/scalable/apps/hidef/transform-crop.svg</file>
<file alias="themes/qet/scalable/ellipse-to-bezier.svg">generated/ellipse-to-bezier.svg</file>
<file alias="themes/qet/scalable/rect-to-bezier.svg">generated/rect-to-bezier.svg</file>
<file alias="themes/qet/scalable/rect-to-polyline.svg">generated/rect-to-polyline.svg</file>
<file>themes/qet-dark/16x16/arrow-left-double.png</file>
<file>themes/qet-dark/16x16/arrow-left.png</file>
<file>themes/qet-dark/16x16/arrow-right-double.png</file>
<file>themes/qet-dark/16x16/arrow-right.png</file>
<file>themes/qet-dark/16x16/circle.png</file>
<file>themes/qet-dark/16x16/conductor-edit.png</file>
<file>themes/qet-dark/16x16/configure-toolbars.png</file>
<file>themes/qet-dark/16x16/configure.png</file>
<file>themes/qet-dark/16x16/dialog-cancel.png</file>
<file>themes/qet-dark/16x16/dialog-ok.png</file>
<file>themes/qet-dark/16x16/document-export.png</file>
<file>themes/qet-dark/16x16/document-import.png</file>
<file>themes/qet-dark/16x16/document-new.png</file>
<file>themes/qet-dark/16x16/document-open-recent.png</file>
<file>themes/qet-dark/16x16/document-print-frame.png</file>
<file>themes/qet-dark/16x16/document-print.png</file>
<file>themes/qet-dark/16x16/document-save-all.png</file>
<file>themes/qet-dark/16x16/document-save-as.png</file>
<file>themes/qet-dark/16x16/document-save.png</file>
<file>themes/qet-dark/16x16/edit-clear-locationbar-ltr.png</file>
<file>themes/qet-dark/16x16/edit-clear-locationbar-rtl.png</file>
<file>themes/qet-dark/16x16/edit-clear.png</file>
<file>themes/qet-dark/16x16/edit-copy.png</file>
<file>themes/qet-dark/16x16/edit-cut.png</file>
<file>themes/qet-dark/16x16/edit-download.png</file>
<file>themes/qet-dark/16x16/edit-paste.png</file>
<file>themes/qet-dark/16x16/edit-redo.png</file>
<file>themes/qet-dark/16x16/edit-rename.png</file>
<file>themes/qet-dark/16x16/edit-select-all.png</file>
<file>themes/qet-dark/16x16/edit-select-invert.png</file>
<file>themes/qet-dark/16x16/edit-table-cell-merge.png</file>
<file>themes/qet-dark/16x16/edit-undo.png</file>
<file>themes/qet-dark/16x16/element-delete.png</file>
<file>themes/qet-dark/16x16/element-edit.png</file>
<file>themes/qet-dark/16x16/element-new.png</file>
<file>themes/qet-dark/16x16/element.png</file>
<file>themes/qet-dark/16x16/endline-circle.png</file>
<file>themes/qet-dark/16x16/endline-diamond.png</file>
<file>themes/qet-dark/16x16/endline-none.png</file>
<file>themes/qet-dark/16x16/endline-simple.png</file>
<file>themes/qet-dark/16x16/endline-triangle.png</file>
<file>themes/qet-dark/16x16/flip.png</file>
<file>themes/qet-dark/16x16/folder-delete.png</file>
<file>themes/qet-dark/16x16/folder-edit.png</file>
<file>themes/qet-dark/16x16/folder-new.png</file>
<file>themes/qet-dark/16x16/folder-open.png</file>
<file>themes/qet-dark/16x16/folder-show-all.png</file>
<file>themes/qet-dark/16x16/folder.png</file>
<file>themes/qet-dark/16x16/folio-ref-coming.png</file>
<file>themes/qet-dark/16x16/go-company.png</file>
<file>themes/qet-dark/16x16/go-down-double.png</file>
<file>themes/qet-dark/16x16/go-down.png</file>
<file>themes/qet-dark/16x16/go-home.png</file>
<file>themes/qet-dark/16x16/go-up-double.png</file>
<file>themes/qet-dark/16x16/go-up.png</file>
<file>themes/qet-dark/16x16/grid.png</file>
<file>themes/qet-dark/16x16/help-contents.png</file>
<file>themes/qet-dark/16x16/help-donate.png</file>
<file>themes/qet-dark/16x16/item-cancel.png</file>
<file>themes/qet-dark/16x16/item-copy.png</file>
<file>themes/qet-dark/16x16/item-move.png</file>
<file>themes/qet-dark/16x16/kdenlive-show-video.png</file>
<file>themes/qet-dark/16x16/list-add.png</file>
<file>themes/qet-dark/16x16/list-remove.png</file>
<file>themes/qet-dark/16x16/masquer.png</file>
<file>themes/qet-dark/16x16/mirror.png</file>
<file>themes/qet-dark/16x16/move.png</file>
<file>themes/qet-dark/16x16/object-group.png</file>
<file>themes/qet-dark/16x16/object-rotate-right.png</file>
<file>themes/qet-dark/16x16/orientations.png</file>
<file>themes/qet-dark/16x16/preferences-desktop-user.png</file>
<file>themes/qet-dark/16x16/select.png</file>
<file>themes/qet-dark/16x16/table-of-content.png</file>
<file>themes/qet-dark/16x16/terminal.png</file>
<file>themes/qet-dark/16x16/text-xml.png</file>
<file>themes/qet-dark/16x16/transform-rotate.png</file>
<file>themes/qet-dark/16x16/view-fullscreen.png</file>
<file>themes/qet-dark/16x16/view-restore.png</file>
<file>themes/qet-dark/16x16/window-new.png</file>
<file>themes/qet-dark/16x16/zoom-draw.png</file>
<file>themes/qet-dark/16x16/zoom-fit-best.png</file>
<file>themes/qet-dark/16x16/zoom-in.png</file>
<file>themes/qet-dark/16x16/zoom-original.png</file>
<file>themes/qet-dark/16x16/zoom-out.png</file>
<file>themes/qet-dark/22x22/Actions-player-time-icon.png</file>
<file>themes/qet-dark/22x22/all_pages.png</file>
<file>themes/qet-dark/22x22/applications-development-translation.png</file>
<file>themes/qet-dark/22x22/arc.png</file>
<file>themes/qet-dark/22x22/arrow-left-double.png</file>
<file>themes/qet-dark/22x22/arrow-left.png</file>
<file>themes/qet-dark/22x22/arrow-right-double.png</file>
<file>themes/qet-dark/22x22/arrow-right.png</file>
<file>themes/qet-dark/22x22/configure-toolbars.png</file>
<file>themes/qet-dark/22x22/configure.png</file>
<file>themes/qet-dark/22x22/dialog-cancel.png</file>
<file>themes/qet-dark/22x22/dialog-ok.png</file>
<file>themes/qet-dark/22x22/document-export.png</file>
<file>themes/qet-dark/22x22/document-import.png</file>
<file>themes/qet-dark/22x22/document-new.png</file>
<file>themes/qet-dark/22x22/document-open-recent.png</file>
<file>themes/qet-dark/22x22/document-print-frame.png</file>
<file>themes/qet-dark/22x22/document-print.png</file>
<file>themes/qet-dark/22x22/document-save-all.png</file>
<file>themes/qet-dark/22x22/document-save-as.png</file>
<file>themes/qet-dark/22x22/document-save.png</file>
<file>themes/qet-dark/22x22/edit-clear-locationbar-ltr.png</file>
<file>themes/qet-dark/22x22/edit-clear-locationbar-rtl.png</file>
<file>themes/qet-dark/22x22/edit-clear.png</file>
<file>themes/qet-dark/22x22/edit-copy.png</file>
<file>themes/qet-dark/22x22/edit-cut.png</file>
<file>themes/qet-dark/22x22/edit-paste.png</file>
<file>themes/qet-dark/22x22/edit-redo.png</file>
<file>themes/qet-dark/22x22/edit-rename.png</file>
<file>themes/qet-dark/22x22/edit-select-all.png</file>
<file>themes/qet-dark/22x22/edit-table-cell-merge.png</file>
<file>themes/qet-dark/22x22/edit-undo.png</file>
<file>themes/qet-dark/22x22/element-delete.png</file>
<file>themes/qet-dark/22x22/element-edit.png</file>
<file>themes/qet-dark/22x22/element-new.png</file>
<file>themes/qet-dark/22x22/ellipse.png</file>
<file>themes/qet-dark/22x22/folder-delete.png</file>
<file>themes/qet-dark/22x22/folder-edit.png</file>
<file>themes/qet-dark/22x22/folder-new.png</file>
<file>themes/qet-dark/22x22/folder-open.png</file>
<file>themes/qet-dark/22x22/go-company.png</file>
<file>themes/qet-dark/22x22/go-down.png</file>
<file>themes/qet-dark/22x22/go-home.png</file>
<file>themes/qet-dark/22x22/go-up.png</file>
<file>themes/qet-dark/22x22/grid.png</file>
<file>themes/qet-dark/22x22/insert-image.png</file>
<file>themes/qet-dark/22x22/landscape.png</file>
<file>themes/qet-dark/22x22/line.png</file>
<file>themes/qet-dark/22x22/list-add.png</file>
<file>themes/qet-dark/22x22/list-remove.png</file>
<file>themes/qet-dark/22x22/lower.png</file>
<file>themes/qet-dark/22x22/move.png</file>
<file>themes/qet-dark/22x22/names.png</file>
<file>themes/qet-dark/22x22/object-locked.png</file>
<file>themes/qet-dark/22x22/object-rotate-right.png</file>
<file>themes/qet-dark/22x22/object-unlocked.png</file>
<file>themes/qet-dark/22x22/polygon.png</file>
<file>themes/qet-dark/22x22/portrait.png</file>
<file>themes/qet-dark/22x22/preferences-desktop-user.png</file>
<file>themes/qet-dark/22x22/raise.png</file>
<file>themes/qet-dark/22x22/rectangle.png</file>
<file>themes/qet-dark/22x22/restaurer.png</file>
<file>themes/qet-dark/22x22/select.png</file>
<file>themes/qet-dark/22x22/single_page.png</file>
<file>themes/qet-dark/22x22/start.png</file>
<file>themes/qet-dark/22x22/text-xml.png</file>
<file>themes/qet-dark/22x22/text.png</file>
<file>themes/qet-dark/22x22/textfield.png</file>
<file>themes/qet-dark/22x22/transform-scale.png</file>
<file>themes/qet-dark/22x22/two_pages.png</file>
<file>themes/qet-dark/22x22/view-fit-window.png</file>
<file>themes/qet-dark/22x22/view-fullscreen.png</file>
<file>themes/qet-dark/22x22/view-restore.png</file>
<file>themes/qet-dark/22x22/view_fit_width.png</file>
<file>themes/qet-dark/22x22/window-new.png</file>
<file>themes/qet-dark/22x22/zoom-draw.png</file>
<file>themes/qet-dark/22x22/zoom-fit-best.png</file>
<file>themes/qet-dark/22x22/zoom-in.png</file>
<file>themes/qet-dark/22x22/zoom-original.png</file>
<file>themes/qet-dark/22x22/zoom-out.png</file>
<file>themes/qet-dark/32x32/format-text-bold.png</file>
<file>themes/qet-dark/32x32/format-text-color.png</file>
<file>themes/qet-dark/32x32/format-text-italic.png</file>
<file>themes/qet-dark/32x32/format-text-underline.png</file>
<file>themes/qet-dark/32x32/insert-link.png</file>
<file>themes/qet-dark/32x32/printer.png</file>
<file>themes/qet-dark/32x32/text-xml.png</file>
<file>themes/qet-dark/48x48/user-away-extended.png</file>
<file>themes/qet-dark/48x48/user-away.png</file>
<file>themes/qet-dark/48x48/view-pim-journal.png</file>
<file>themes/qet-dark/128x128/plasmagik.png</file>
<file>themes/qet-dark/128x128/settings.png</file>
<file>themes/qet-dark/scalable/diagram.svg</file>
<file>themes/qet-dark/scalable/folio-delete.svg</file>
<file>themes/qet-dark/scalable/folio-new.svg</file>
<file>themes/qet-dark/scalable/folio-properties.svg</file>
<file>themes/qet-dark/scalable/label.svg</file>
<file>themes/qet-dark/scalable/pdf-import.svg</file>
<file>themes/qet-dark/scalable/edit-opacity.svg</file>
<file>themes/qet-dark/scalable/image-flip-horizontal.svg</file>
<file>themes/qet-dark/scalable/image-flip-vertical.svg</file>
<file>themes/qet-dark/scalable/draw-bezier-curves.svg</file>
<file>themes/qet-dark/scalable/transform-crop.svg</file>
<file>themes/qet-dark/scalable/ellipse-to-bezier.svg</file>
<file>themes/qet-dark/scalable/rect-to-bezier.svg</file>
<file>themes/qet-dark/scalable/rect-to-polyline.svg</file>
<file alias="themes/qet-dark/128x128/document-export.png">128x128/document-export.png</file>
<file alias="themes/qet-dark/128x128/printer.png">128x128/printer.png</file>
</qresource>
</RCC>
+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<g transform="translate(1,1)">
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<g transform="translate(1,1)">
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<g transform="translate(1,1)">
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 17 18 h 1 v 1 h -1 Z M 17 19 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<g transform="translate(1,1)">
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 8 h 1 v 1 h -1 Z M 6 10 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 7 10 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 8 h 1 v 1 h -1 Z M 8 10 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 8 h 1 v 1 h -1 Z M 10 10 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 8 h 1 v 1 h -1 Z M 12 10 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 8 h 1 v 1 h -1 Z M 14 10 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 8 h 1 v 1 h -1 Z M 15 10 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<g transform="translate(1,1)">
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 14 h 1 v 1 h -1 Z M 4 15 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 14 h 1 v 1 h -1 Z M 5 15 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 14 h 1 v 1 h -1 Z M 6 15 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 14 h 1 v 1 h -1 Z M 7 15 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 14 h 1 v 1 h -1 Z M 8 15 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 14 h 1 v 1 h -1 Z M 9 15 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 14 h 1 v 1 h -1 Z M 10 15 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 14 h 1 v 1 h -1 Z M 12 15 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 14 h 1 v 1 h -1 Z M 13 15 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 14 h 1 v 1 h -1 Z M 14 15 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 14 h 1 v 1 h -1 Z M 15 15 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 14 h 1 v 1 h -1 Z M 16 15 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#4d4d4d;
}
</style>
</defs>
<g transform="translate(1,1)">
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 3 h 16 v 1 h -16 Z M 3 4 h 1 v 15 h -1 Z M 4 18 h 10 v 1 h -10 Z M 18 4 h 1 v 8 h -1 Z M 16 14 h 1 v 5 h -1 Z M 14 16 h 5 v 1 h -5 Z" class="ColorScheme-Text"/>
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 5 7 h 1 v 1 h -1 Z M 6 7 h 1 v 1 h -1 Z M 7 7 h 1 v 1 h -1 Z M 5 8 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 5 9 h 1 v 1 h -1 Z M 6 9 h 1 v 1 h -1 Z M 7 9 h 1 v 1 h -1 Z M 5 10 h 1 v 1 h -1 Z M 5 11 h 1 v 1 h -1 Z M 9 7 h 1 v 1 h -1 Z M 10 7 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 9 9 h 1 v 1 h -1 Z M 11 9 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 9 11 h 1 v 1 h -1 Z M 10 11 h 1 v 1 h -1 Z M 13 7 h 1 v 1 h -1 Z M 14 7 h 1 v 1 h -1 Z M 15 7 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 9 h 1 v 1 h -1 Z M 14 9 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 11 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 409 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 430 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 261 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Some files were not shown because too many files have changed in this diff Show More