Compare commits

...

58 Commits

Author SHA1 Message Date
Laurent Trinques 73075a5154 Snap enable DQET_ENABLE_SPACEMOUSE=ON
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m31s
2026-09-24 17:20:38 +02:00
Laurent Trinques 9875de85d7 Merge pull request #635 from ispyisail/feature-3dmouse-spnav
3D mouse (SpaceMouse/SpacePilot) pan/zoom via libspnav — Linux phase (discussion #599)
2026-09-24 16:49:00 +02:00
Laurent Trinques 48883b320e Merge branch 'master' into feature-3dmouse-spnav 2026-09-24 15:45:33 +02:00
Laurent Trinques b93d8d8baa Merge pull request #704 from arummler/master-feature-renum-elements-rebased
Autonumbering: renumbering of elements
2026-09-24 15:17:38 +02:00
Laurent Trinques ae41dd7686 Merge branch 'master' into master-feature-renum-elements-rebased 2026-09-24 15:17:17 +02:00
Laurent Trinques 7fbbb71aac Merge pull request #1018 from ispyisail/revive/785-info-flags-case-insensitive
Fix element flags written as True or 1 showing unticked
2026-09-24 13:08:28 +02:00
Laurent Trinques 232724bf82 Merge pull request #1017 from ispyisail/revive/752-rotate-texts-no-dialog-in-ctor
Move the rotate-texts dialog out of its undo command
2026-09-24 13:06:23 +02:00
Laurent Trinques 12f826e969 Merge pull request #1016 from ispyisail/revive/729-backup-prompt-drops-files
Fix projects opened during the start-up restore prompt being lost
2026-09-24 13:04:19 +02:00
Laurent Trinques 245aafb0d1 Merge pull request #1015 from ispyisail/revive/526-free-terminal-move
Fix the terminal strip's move button getting stuck disabled (#409)
2026-09-24 13:02:43 +02:00
Laurent Trinques 0d231ef548 Merge pull request #1014 from arummler/fix-explicit-file-open
Fix warning about reading XML without explicit file opening.
2026-09-24 13:00:47 +02:00
ispyisail 3513f692fd Read the remaining element flags through QET::infoFlagIsTrue()
5b0785fcc routed most reads of auto_num_locked, potential_isolating and
exclude_from_bom through QET::infoFlagIsTrue(), which accepts the same
spellings as the parts-list query (true/1/yes/on, trimmed, any case).
Four checkbox reads still compared against a literal lowercase "true":
the "exclude from parts list" box in the folio properties panel, and all
three flags in the element editor.

A value such as "True" or "1" from a hand-written .elmt/.qet was
therefore left out of the parts list, but shown unticked in both panels,
and pressing Apply there wrote back "false" and flipped the flag.

Revives the unconverted part of PR #785.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:34:42 +12:00
Laurent Trinques 5a27f3cf80 Merge pull request #1013 from ispyisail/fix/273-skip-open-backup
Fix bugtracker #273: skip crash-recovery backups of an unchanged project
2026-09-24 12:33:18 +02:00
Andre Rummler d3a9adbe44 Fix warning about reading XML without explicit file opening. This was implicit up to now and will go away in future Qt versions. 2026-09-24 12:32:47 +02:00
ispyisail 8ab4e24b61 Take the modal dialog out of RotateTextsCommand's constructor
RotateTextsCommand called QDialog::exec() from inside its constructor, so
the command could not be built without a human answering a dialog. That
made it untestable headlessly, undrivable from any script or test harness,
and it is why bugtracker #312 (PR #707) shipped with its save/reload
round-trip unverified -- the symptom could not be reproduced without a GUI.

The command now takes the angle as a parameter and does no asking. Two
statics carry the interactive half:

  hasSelectedTexts(diagram)  -- is there anything to rotate
  askRotation(rotation)      -- open the dialog, false if cancelled

The single call site in QETDiagramEditor asks first, then builds the
command, so the user-visible behaviour is unchanged: same dialog, same
title, same no-dialog-on-empty-selection. Keeping askRotation() in this
class also keeps the QObject tr() context, so existing translations of
"Orienter les textes sélectionnés" are not invalidated.

Also guards undo()/redo() against a null m_anim_group. When nothing is
selected the constructor calls setObsolete(true) without ever creating the
animation group, and QUndoStack::push() calls redo() before discarding an
obsolete command -- a latent null dereference on that path.

Verified headlessly, which was the point: driving the command through a
scratch --test-ops op on examples/741.qet (67 conductors), rotation
attributes written on save go 0 -> 67 with the #707 fix present and stay
at 0 with it reverted, while the reverted build instead writes userx on
all 67. That is bugtracker #312 reproduced and fixed under test for the
first time.
2026-09-24 22:30:20 +12:00
Laurent Trinques 2d2bf5ca2b Merge pull request #1012 from ispyisail/fix/340-ms-shell-dlg-pdf-font
Fix bugtracker #340: Windows texts in 'MS Shell Dlg 2' render as Arial
2026-09-24 12:27:50 +02:00
ispyisail 2f6ab8f63b Don't drop files handed to a starting instance while the backup prompts are up
QETApp's constructor ends with checkBackupFiles(), which opens modal
dialogs -- the "restore these files?" prompt, and the crash report offer.
Those dialogs run their own event loop, so the constructor does not return
until the user answers them.

main() still has work to do at that point. In particular this, a few lines
later:

    QObject::connect(&app, &SingleApplication::receivedMessage,
                     &qetapp, &QETApp::receiveMessage);

While the prompts are up that connection does not exist yet. A second
instance launched during the window -- double-clicking a project, or
xdg-open, while the first copy is still asking about restore files --
hands its file names over, SingleApplication accepts and delivers them,
and nothing is listening. The message is discarded and the second process
has already exited, so the file is simply lost with no error.

Deferring checkBackupFiles() to the event loop lets the constructor return
promptly. main() finishes wiring up, and the prompts appear immediately
afterwards exactly as before.

Verified with a stale restore file present, sending a project to the
running instance while the restore prompt is displayed: before, the file
was dropped and never appeared, even after answering the prompt; after, it
opens. The restore and backup prompts still appear and still work.

Note this is only observable together with the fix for bugtracker #248 --
before that, no file passed to a running instance was opened under any
circumstances.
2026-09-24 22:26:38 +12:00
ispyisail 2266497b0b Write the move button tooltips in French, the tr() source language
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:20:43 +12:00
Shane Ringrose dcb79b6553 fix(terminal-strip): free-terminal move button disabled and unresponsive
Three bugs stacked to produce the symptom in issue #409 (move button does
nothing):

1. selectionChanged() was declared in freeterminaleditor.h but had no body
   and was never connected to the selection model, so the move button had no
   awareness of whether a terminal was selected. The button could appear
   enabled with nothing selected, then silently return early in
   on_m_move_pb_clicked() at the real_t_vector.isEmpty() guard.

2. The dataChanged→setDisabledMove(true) connection was a one-way trap: any
   cell edit (including accidentally opening and closing a type/function
   combo, or toggling LED back to its current value) permanently disabled the
   move button until reload() was called. There was no tooltip explaining why
   the button was greyed out, so the user had no way to recover.

3. FreeTerminalModel::setData() for LED_CELL had no change guard, unlike
   LABEL_CELL which checks label != value. Clicking the LED combo while it
   was already at the same value still emitted dataChanged and triggered the
   disable.

Fix:
- Implement selectionChanged() to enable the move controls only when at
  least one row is selected AND there are no pending (yellow) edits.
- Connect it to both selectionModel::selectionChanged and model::dataChanged
  so the button state is always consistent with actual UI state.
- Route reload()'s re-enable through selectionChanged() instead of calling
  setDisabledMove(false) directly, so the selection state is respected
  immediately after a move.
- Add a tooltip to m_move_pb explaining the disabled state.
- Add the missing change guard to LED_CELL in setData().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-24 22:20:43 +12:00
ispyisail 745b4a936c Fix bugtracker #273: skip crash-recovery backups of an unchanged project
writeBackup() rebuilds the whole project's XML with toXml() on the GUI
thread before handing the file write to a worker thread. On a big project
that freezes the interface for seconds (bugtracker #273, and #329, where
the interval was raised from 2 to 20 minutes to make it rarer). It ran
right after every project was opened, and then every 20 minutes whether
or not anything had changed.

Only write a backup when something changed since the last one: the undo
stack moved, setModified(true) was called, or the embedded element or
title block collections changed. A project just opened from a file starts
clean, since the file is already what a crash would restore. New projects
and projects restored from a backup are backed up at once, as before.

Measured on examples/industrial.qet (50 folios), each backup blocks the
GUI thread for about 0.28 s on a fast machine; with a 2 s test interval,
the old code backed up on every tick, the new code only after a change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:57:42 +12:00
ispyisail 7ac3719c37 Fix bugtracker #340: map "MS Shell Dlg" aliases on Qt 6/Windows
"MS Shell Dlg 2" is a Windows font alias, not a font, and projects and
settings saved on Windows carry it. Qt 5's GDI backend let Windows resolve
it to Tahoma; Qt 6's DirectWrite backend does not know the alias and falls
back to Arial, so those texts render heavier on screen and in exported PDFs.

Register the substitutions Windows itself uses, before any application
object exists so the headless export and scripting paths get them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 21:41:00 +12:00
Laurent Trinques ddfa234943 Merge pull request #1011 from Kellermorph/save-sheet-colour
Remember the sheet background colour between runs
2026-09-24 10:50:34 +02:00
Kellermorph 9af562fcbd Remember the sheet background colour between runs
Picking a sheet (folio) background colour in the diagram editor was lost
on every restart. Diagram::background_color is a static initialised to
white and PaletteGraphicsView's s_custom_bg a static bool, and neither
was ever written anywhere -- Diagram::toXml() carries no colour attribute
either -- so closing and reopening a project always came back on the
default and the choice had to be made again.

Store it in QSettings under diagrameditor/sheet_background_* as a pair of
values rather than one: the colour, and whether it was picked explicitly.
Both halves are needed. "#ffffff, follow the system" and "#ffffff, always
white" are the same colour and two behaviours -- the first is what the
views invert on a dark palette -- so keeping only the colour would
silently turn one into the other on the next start, which is the reported
problem one step removed.

The colour is written as HexRgb on purpose. The SVG export gives
Diagram::background_color an alpha of 0 to render a transparent
background and never puts it back, and that transient value must not be
persisted as a permanently transparent sheet.

Applied from main() after the headless export and scripting branch -- those
return before reaching it and must keep rendering on plain white, the rule
ProjectPrintWindow already enforces for printing -- and before QETApp is
constructed, since that constructor already loads the projects given on
the command line. The GUI export dialog is left alone: it renders through
drawBackground(), so what you see is what you export, as it already was
within a session.

Saved at the moment the colour is applied rather than at shutdown, so
neither the print window's temporary white nor the SVG export's alpha can
reach it. The button's constructor now mirrors the stored state instead of
always claiming "system colour", and the "recently used" list is stored
alongside it.

Covered by tst_sheetbackgroundsetting, which pins the custom flag and the
dropped alpha -- the two rules a single stored colour would lose.
2026-09-24 10:30:01 +02:00
Laurent Trinques c85276f61b Merge pull request #1009 from ispyisail/fix/1002-resize-handle-artefacts
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 6m31s
Fix green fragments left behind when moving or zooming an element (#1002)
2026-09-24 10:17:53 +02:00
ispyisail f3a31c291f Fix green fragments left by text resize handles when moving an element
The two width-resize handles added in #591 were free scene items, moved
with setPos() from inside DynamicElementTextItem::paint(). Moving an item
while the view is painting is outside what QGraphicsView's partial
repaint tracks: the handle was drawn only where the current repaint band
overlapped it, and its old position was not reliably cleared. Moving or
zooming a selected element left green slivers on the folio (#1002).
Since 29d16c333 the handles show on every text of a selected element,
so any ordinary element move triggered it.

Make the handles children of the text instead. Qt then moves and
repaints them together with the element, in local coordinates, and
their position only needs updating when the text's size changes, which
documentSizeChanged reports (text, font, width, undo of a resize).
This also takes paint() out of the handle logic entirely.

Reproduced on Linux (Xvfb), so the issue is not Windows-specific.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 19:46:26 +12:00
Laurent Trinques a06220e8dc Merge pull request #1008 from ispyisail/fix/1004-element-preview-cache
Fix #1004: element preview stays stale after editing until the collection reloads
2026-09-24 09:28:33 +02:00
Laurent Trinques 79990e40ee git submodule update --remote elements 2026-09-24 04:26:56 +02:00
Laurent Trinques e1e7ea4888 Merge pull request #1006 from ispyisail/i18n/ui-phrasebooks-29-languages
Add translation suggestions for the interface to 29 phrase books
2026-09-24 04:24:08 +02:00
Laurent Trinques 54b8fe5df4 Merge pull request #1007 from elevatormind/restore-sqlite-dependencies
Restore last sqlite dependencies
2026-09-24 04:02:28 +02:00
Laurent Trinques be0d214b75 Merge pull request #1005 from ispyisail/fix/1002-conductor-color-equipotential
Fix #1002: conductor colour toolbar swatch only recolours one wire segment
2026-09-24 04:01:13 +02:00
ispyisail 2f988e6e2c Refresh cached element preview after saving from the editor
Editing an element already in the user collection, saving, and closing
the editor left the old thumbnail in the elements panel until the
whole collection was reloaded. ElementsLocation::icon() serves the
preview from two caches keyed by path+uuid -- ElementPictureFactory's
in-memory picture cache and ElementsCollectionCache's on-disk SQLite
cache -- and neither was ever told the file changed.

QETElementEditor::toLocation() writes the new XML and returns;
ElementsCollectionWidget::locationWasSaved() then refreshes the panel
item, but it reads the icon through the same two stale caches, so the
refresh was a no-op. slot_reloadElementDrawings() already shows the
correct invalidation call for ElementPictureFactory; this wires the
same pattern, plus a matching refresh of ElementsCollectionCache's
row, into the save path itself.

Fixes the preview half of #1004. The paste-cursor-jump half of that
report is a live design disagreement between two recent commits from
a different contributor and is written up separately rather than
fixed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 12:09:01 +12:00
Magnus Hellströmer 9786006ca4 fix(database): align sqlite3 forward declarations 2026-09-23 21:45:16 +02:00
ispyisail 91e8956734 Add translation suggestions for the interface to 29 phrase books
Fills lang/phrase/qet_fr_xx.qph for every language QET ships, following
on from discussion #873. A phrase book is never compiled into what
ships -- a translator opens it in Qt Linguist and sees a suggestion
already filled in, to accept or correct.

Only ever appends. phrasebook.write() skips any source string already
in the file (case-insensitive), so no existing entry -- human or
machine -- is touched. 24 languages had no phrase book at all before
this; the other 5 (da, de, nl, ru, sv) keep every line they had.

Built and run with tools/qet-i18n in the qelectrotech-docker harness
(DeepSeek Flash). Companion to qelectrotech-elements#82, which does the
same for the element collection.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 07:34:47 +12:00
ispyisail 6fad3390df Fix issue #1002: conductor-colour toolbar swatch skips the rest of the potential
ConductorColorToolButton::applyColor() only recoloured the selected
Conductor objects. A wire drawn across a junction is several separate
Conductor segments sharing one electrical potential, so selecting one
segment and picking a colour left the rest of the wire its old colour
-- visible only by falling back to F2/double-click, which already
expands to relatedPotentialConductors() for exactly this reason
(ConductorPropertiesDialog, and QetScriptApi::setConductorProperty).

Expand each selected conductor to its potential before building the
undo command, matching that existing pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-24 07:08:31 +12:00
Magnus Hellströmer 231ab990c3 Revert "refactor: use VACUUM INTO for database export"
This reverts commit 27dea3ffab.
2026-09-23 20:18:25 +02:00
Laurent Trinques f10275cb22 Merge pull request #997 from ispyisail/revive/659-preserve-links
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 2m15s
Preserve master/slave links when pasting or duplicating a folio
2026-09-23 18:40:34 +02:00
Laurent Trinques 3d0e7a27a3 Merge pull request #998 from ispyisail/revive/785-case-insensitive-flags
Make auto_num_locked/potential_isolating case-insensitive
2026-09-23 18:38:38 +02:00
Laurent Trinques f14278324c Merge pull request #995 from ispyisail/revive/713-collection-cancel-race
Cancel the collection-loading map before waiting on it in ~ElementsCollectionModel
2026-09-23 18:36:17 +02:00
Laurent Trinques d5318eb7b4 Merge pull request #996 from ispyisail/revive/682-nonfinite-coordinates
Reject non-finite values in QET::attributeIsAReal()
2026-09-23 18:34:57 +02:00
Laurent Trinques f10ef3ded7 Merge pull request #994 from ispyisail/revive/664-element-info-orphan
Fix element_info orphan row causing UNIQUE constraint errors on undo
2026-09-23 18:33:05 +02:00
Laurent Trinques 6f7ea63589 Merge pull request #1000 from ispyisail/fix/933-german-source-strings
Fix bugtracker #933: German source string and comments in templates code
2026-09-23 18:26:45 +02:00
Laurent Trinques 9a6029a2bb Merge pull request #1001 from Kellermorph/plc-table-size
Plc table size
2026-09-23 18:24:15 +02:00
Laurent Trinques bc0ca56c7e Merge pull request #999 from ispyisail/fix/ci-missing-libsqlite3-dev
CI: install libsqlite3-dev, required by PR #983's find_package(SQLite3)
2026-09-23 18:22:47 +02:00
Laurent Trinques 1d4c0326d4 Merge pull request #1003 from qelectrotech/revert-986-no-sqlite
Linux build and tests / Build and test (Qt 6, Debug) (push) Failing after 1m55s
Revert "fix: harden project database export"
2026-09-23 13:51:07 +02:00
Kellermorph 3457a88e81 Open the PLC master properties dialog at three times its width
Element::editProperty() gave PropertiesEditorDialog no size of its own,
so the dialog fell back to its sizeHint, which is sized for the compact
general-purpose editors it usually hosts. A PLC master instead shows a
six-column IO table that grows horizontally, and the dialog came out
too narrow to read those columns in.

For a master whose type is PLC, resize the dialog to three times its
natural width before exec(), keeping the natural height. The width is
clamped to the available screen so it cannot run off the display, and
every other element type opens exactly as before.
2026-09-23 10:28:00 +02:00
Kellermorph 4e361f93fe Let the PLC master IO table columns be resized, and remember the layout
The IO table of MasterPropertiesWidget -- the panel that opens when a
PLC master placed on a schematic is edited -- set every section to
QHeaderView::Stretch. Stretch spreads the sections evenly over the
widget and disables section dragging altogether, so the column
boundaries were permanently fixed: they could be neither widened nor
narrowed, and only the width of the whole panel had any effect.

Use QHeaderView::Interactive instead, with movable sections and a set
of default widths, so the columns can be dragged as they are everywhere
else in the application. The layout reached this way is written to
QSettings under masterpropertieswidget/plc-table-header-state on every
sectionResized and sectionMoved, and restored the next time the table
is built -- the same header-state trick the free and linked element
trees of this widget already use, except saved automatically rather
than only from the context menu.
2026-09-23 10:27:50 +02:00
ispyisail 6a8838e719 Fix bugtracker #933: German source string and comments in templates code
sources/ElementsCollection/fileelementcollectionitem.cpp had a German
tr() source string ("Makros") in a project whose source language is
French/English elsewhere. Renamed to "Macros" (identical in both
languages, so no translation catalog change is needed). Translated
three German-language comments in elementscollectionmodel.cpp and
diagramview.cpp to English.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 17:31:38 +12:00
ispyisail 774f803626 CI: install libsqlite3-dev for find_package(SQLite3 REQUIRED)
PR #983 added a hard SQLite3 system dependency to CMakeLists.txt, but
the workflow only installed libqt6sql6-sqlite (Qt's runtime driver
plugin), not the C headers/library find_package(SQLite3) needs. Every
PR based on current master has been failing CI with "Could NOT find
SQLite3" since #983 merged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 17:02:36 +12:00
ispyisail 5b0785fcce Make auto_num_locked/potential_isolating case-insensitive
Reviving the still-relevant part of #785, closed 2026-09-10 purely to
clear a review backlog, not on merit. Investigated fresh against
current master -- one of the original PR's three targets turned out
to already be fixed independently: element_nomenclature_view's SQL
predicate for exclude_from_bom already does
"COALESCE(LOWER(TRIM(ei.exclude_from_bom)), '') NOT IN ('true', '1',
'yes', 'on')" (projectDataBase::createElementNomenclatureView()).

auto_num_locked and potential_isolating had no equivalent: five call
sites across terminal.cpp, terminalnumberingdialog.cpp and
elementinfowidget.cpp compared the raw stored string against the
literal "true" with QString::operator==, silently treating "True",
"TRUE", a trailing space, or any value written by something other
than this app's own checkbox as off -- with no error and no visible
difference from the checkbox being genuinely unticked.

Added QET::infoFlagIsTrue(), matching the same accepted spellings
("true"/"1"/"yes"/"on", case-insensitive, trimmed) the SQL predicate
already uses, and switched all five call sites to it.

Verified the exact comparison logic in isolation, outside any QET
build: 15 cases including "True", "TRUE", padded whitespace, "1",
"yes", "on", and their false counterparts -- all correctly
discriminated. Qt 6.10.2, ctest 13/13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:47:07 +12:00
ispyisail de9b3eae06 Preserve master/slave links when pasting or duplicating a folio
Reviving #659, closed 2026-09-10 purely to clear a review backlog
(#630), not on merit. Rebuilt fresh against current master rather than
merged from the old branch (elementspanelwidget.cpp had drifted enough
that a textual merge risked silently losing content, as it did earlier
in this same session for a different revival). Builds discussion #607.

Cutting/copying a linked group of elements -- a relay coil with its
contacts, a PLC master with its slave I/O elements -- dropped the
master/slave link entirely. Traced end to end: Element::toXml() writes
each partner's uuid into <link_uuid>, Element::fromXml() reads it back
into a deferred, unresolved buffer (tmp_uuids_link), and the only code
that ever resolves that buffer is initLink(QETProject *) -- called
only from Diagram::refreshContents(), itself only called from full
project load and macro-block insertion. Neither DiagramView::paste()
nor ElementsPanelWidget::duplicateDiagram() ever call it, so
tmp_uuids_link is populated correctly and never resolved: the link is
silently dropped. duplicateDiagram() already knew this and worked
around it by calling clearPendingLinks() -- correct to not link back
to a stale source, but it meant folio duplication never preserved a
link either.

Added Element::initLink(const QList<Element *> &candidates) --
resolves against a caller-supplied list instead of a project-wide
search. The scoping is the subtle part: right after the XML round-trip
and before uuids are renewed, a pasted/duplicated element's
tmp_uuids_link still holds its source's original partner uuid, which
at that exact moment still equals the not-yet-renewed uuid of that
partner's own copy, if it was carried along in the same batch.
Resolving only within the batch is what stops a linked pair pasted
together from matching an original element left elsewhere that
happens to still carry that same soon-to-be-replaced uuid. If only one
half of a linked group is in the batch, its entry finds no match and
is dropped -- the same "leave it unlinked" outcome as before.

Wired into PasteDiagramCommand::redo(), before the existing newUuid()
loop and gated by the same first_redo flag. Wired into
duplicateDiagram() the same way, replacing its clearPendingLinks()
call (initLink() clears tmp_uuids_link internally, matched or not).

Verified live -- the original PR's own test plan left both of these
unchecked, so this closes that gap rather than repeating it. Built a
project with a linked PLC master/slave pair (qet-mcp's link_elements),
then drove the real interaction under Xvfb:

  Ctrl+A, Ctrl+C, Ctrl+V:
    originals   95ad58fc <-> e728632c   (unchanged)
    pasted      513e6bf8 <-> 29aa60b4   (linked to each other)

  Right-click folio > "Copier et coller":
    originals   95ad58fc <-> e728632c   (unchanged)
    duplicated  0a33ccb4 <-> 3264fe66   (linked to each other)

Neither copy links back to an original or comes in unlinked. Qt 6.10.2,
ctest 13/13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:37:45 +12:00
ispyisail 032f2249a3 Reject non-finite values in QET::attributeIsAReal()
Reviving the still-relevant third of #682 (closed 2026-09-18 purely to
clear a review backlog, not on merit). Investigated fresh against
current master rather than merged wholesale -- two of the original
PR's three findings turned out to already be resolved independently:

- Element::valideXml() and Terminal::valideXml() already reject a
  non-finite x/y (qIsFinite checks, with comments citing this exact
  class of bug) -- added by someone else since #682 was written.
  Verified live: a project with x="nan" on an element loads and
  exports cleanly on current master, 3.1s, no hang.
- The illegal-XML-control-byte segfault in QDomDocument::setContent()
  does not reproduce either. Tested both bytes from the original
  report (0x00, 0x0E) against a real Qt6 build: both are now refused
  cleanly (XmlParsingFailed, exit 0), no crash. Qt6's QDom parses
  differently to the Qt5 one #682 was written and tested against.

What's still genuinely open: QET::attributeIsAReal() itself --
QString::toDouble()'s output parameter reports success for "nan"/
"inf"/"-inf", and this shared helper (26+ call sites across the
codebase, per #682's own count) had no finiteness check independent of
element.cpp/terminal.cpp's own since-added ones. Confirmed several
call sites are not behind either of those two gates -- notably
elementpicturefactory.cpp's line/rect/ellipse/circle/arc parsing for a
symbol's own drawing (a corrupted .elmt, not just a corrupted project
file), which was and remains reachable through this helper alone.

Qt 6.10.2, ctest 13/13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:25:39 +12:00
ispyisail 1890cee801 Cancel the collection-loading map before waiting on it in ~ElementsCollectionModel
Reviving #713, closed 2026-09-21 purely to clear a maintainer review
backlog (#630), not on merit; not superseded. The crash #713 was
originally named after (bugtracker #291) was already fixed separately
by 39ac5716c, merged 14 Aug -- confirmed still on master. What's left,
and what this revives, is the one-line follow-up #713 itself narrowed
to after that: m_future.cancel() before the wait.

Without it, ~ElementsCollectionModel()'s wait runs the whole queued
QtConcurrent::map() to completion, so cancelling the open-element
dialog blocks until every remaining item has been processed -- a
visible hang on the button pressed precisely to stop the work.
cancel() drops the not-yet-started items so the wait is short, while
still waiting for whatever item is already in flight (needed so it
can't dereference this object after it's gone).

Qt 6.10.2, ctest 13/13. The responsiveness gain itself is reasoned
from QFuture's documented cancel()/waitForFinished() semantics rather
than timed -- same as the original PR's own stated verification.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:20:38 +12:00
ispyisail 3eea3059bf Fix element_info orphan row causing UNIQUE constraint errors on undo
Reviving #664, closed 2026-09-18 purely to clear a maintainer review
backlog (#630), not on merit; not superseded. Rewritten fresh against
current master rather than merged from the old branch -- that branch
predates the Qt6-only switch and much of dataBase/projectdatabase.cpp's
later rewrite, and the two had diverged too far for a textual merge to
be trustworthy.

projectDataBase::removeElement() only ran DELETE FROM element WHERE
uuid=:uuid. It never touched element_info, even though every element
also has a row there (element_uuid is its PRIMARY KEY, with a FOREIGN
KEY back to element.uuid that isn't enforced by this connection -- no
ON DELETE CASCADE in effect). So deleting an element left its
element_info row orphaned.

Re-adding an element with that same uuid later -- undo of that same
deletion, or a redo replaying it -- goes through addElement(), which
INSERTs into both tables. The element insert succeeds (that row really
was removed). The element_info insert hits the orphaned row's primary
key and fails, silently: the error is logged and swallowed, so the
element re-enters the scene with no element_info row at all, and
nothing later re-syncs it.

removeDiagram() already cascades this cleanup when a whole folio is
removed (a later, unrelated addition) -- confirmed on current master --
but that path never runs for a single element removed on its own,
which is the case this fixes.

Verified on the built binary, not just read: placed an element, deleted
it (Ctrl+A, Delete), undid the deletion (Ctrl+Z). Reverting just this
fix and repeating the identical sequence reproduces the exact reported
error:

  Debug: projectDataBase::addElement insert element info error :
  QSqlError("1555", "Unable to fetch row",
  "UNIQUE constraint failed: element_info.element_uuid")

With the fix, the same sequence produces nothing. Qt 6.10.2, ctest
13/13.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 16:13:04 +12:00
ispyisail bf7f49f334 Merge remote-tracking branch 'upstream/master' into review-635-fix
# Conflicts:
#	CMakeLists.txt
#	cmake/developer_options.cmake
#	cmake/qet_compilation_vars.cmake
#	sources/qetapp.cpp
2026-09-22 23:10:34 +12:00
Andre Rummler 2ec413a578 Adding generated headers to gitignore. 2026-08-09 23:13:09 +02:00
Andre Rummler 61ce75e380 First draft of elements renumber function. 2026-08-09 23:13:09 +02:00
ispyisail 83b9f32bd0 Add device-button-to-action bindings, for any 3D mouse backend
Discussion #599's own scope explicitly deferred this ("Related, not
proposed here... a natural follow-up once basic pan/zoom motion works").
Basic pan/zoom now works (previous commits on this branch), so this adds
it -- generically, for whichever SpaceMouseBackend is in use, not tied to
libspnav specifically, matching the seam the previous commit built.

## Reuses ShortcutManager instead of inventing a second action registry

ShortcutManager is already an app-wide registry of every named, rebindable
action -- undo, redo, rotate selection, cut/copy/paste, autonum configure,
and dozens more -- each carried by a live QAction or QAbstractButton. A
device button binding to one of *those* ids, rather than to a bespoke
QET-3D-mouse-only action list, means the discussion's own examples
(rotate/mirror/undo) are available for free, and any action added to the
app in the future is automatically bindable too.

Added ShortcutManager::trigger(id): find the first still-alive target for
an id and call QAction::trigger() or QAbstractButton::click(), whichever
it is. Deliberately not disambiguated by which window is currently active,
unlike SpaceMouseListener's own pan/zoom dispatch -- a target's owning
top-level window isn't reliably discoverable from a bare QAction. Correct
in the overwhelming common case of one open editor window; documented in
the header as a known simplification, not silently assumed correct.

## The binding itself: SpaceMouseButtonMap

A thin QSettings-backed button-number -> action-id map, unbound by default
for every button on every device -- nothing happens on any button press
until the user opens Configuration > Souris 3D and binds something,
matching this whole feature's "silent until asked for" default.

## Backend side: SpaceMouseBackend::buttonPressed(int)

Added to the platform interface alongside the existing motion() signal.
SpnavBackend now handles SPNAV_EVENT_BUTTON (previously explicitly
ignored) and emits on press only -- release is not reported, since nothing
downstream has a use for it. A future non-spnav backend implements the
same signal and gets button support for free through
SpaceMouseListener::applyButton(), without that logic being duplicated or
re-verified per backend -- the same reasoning the previous commit's seam
was built around.

## Configuration UI: SpaceMouseConfigPage

Modelled directly on the existing ShortcutsConfigPage -- same QTableWidget
shape, same "persist on applyConf(), not live" contract -- one row per
binding: button number (spin box, unbounded, since button count and
numbering genuinely vary from 2 to 30+ across real devices and this could
not be checked against hardware) and action (combo box populated from
ShortcutManager::instance().allShortcuts(), the exact same live registry
the Shortcuts page itself lists). Only added to the Configuration dialog
when QET_SPACEMOUSE_SUPPORT is compiled in.

## Verified, including the one thing that doesn't need hardware to prove

Rebuilt from scratch both ways: option off adds zero new object code
(confirmed via a forced rebuild of the one unconditionally-changed file,
shortcutmanager.cpp, which alone picked up new warning-free code); option
on compiles all four new/changed files warning-free and links clean.

The backend's button *detection* (SPNAV_EVENT_BUTTON -> buttonPressed
signal) still cannot be verified without a real device or daemon -- same
limitation as the motion path from the previous commits, stated plainly
rather than glossed over.

What *is* fully verified, because none of it needs hardware:
 - SpaceMouseButtonMap: unbound by default, set/read-back, clearing via an
   empty id, enumeration -- all confirmed via a standalone harness linked
   against the real compiled objects.
 - ShortcutManager::trigger(): registered a real QAction, confirmed
   trigger() fires it exactly once and returns true; confirmed it returns
   false (not a crash) for an unknown id.
 - SpnavBackend: constructs safely with no daemon present (isAvailable()
   false, as it must be), and both its motion and buttonPressed signals
   are correctly wired per Qt's own metaobject data (QSignalSpy).
 - The configuration page end-to-end, via a real Xvfb session: opened
   Configuration > Souris 3D, confirmed the action combo box lists the
   live, real ShortcutManager registry (undo, rotate, cut/copy/paste,
   dozens more -- not a mock), added rows, edited the button number,
   removed rows, selected "Éditeur de schémas — Pivoter" (Rotate -- the
   discussion's own example) for button 3, clicked OK, and confirmed via
   the actual settings file that it persisted exactly as
   "buttons\3=diagrameditor.rotate_selection". Reopened the dialog and
   confirmed it read back correctly. This is a full, real round trip
   through the UI, not a claim.
2026-08-02 22:11:30 +12:00
ispyisail e35cab33f0 Extract a SpaceMouseBackend seam ahead of a future Windows/macOS backend
The user asked for phase 2 (Windows/macOS via 3Dconnexion's proprietary
3DxWare SDK) on top of #635. This sandbox has no 3DxWare SDK, no Windows
toolchain, and no macOS toolchain -- nothing to compile, link, or run a
single line of platform code against, unlike the Linux/libspnav path,
which was built and actually tested here for real. Writing 3DxWare
integration code that has never even built would be a materially weaker,
unverifiable thing sitting in this PR, so it is not in this commit.

What is: the structural seam that makes adding it later a contained,
reviewable change instead of a rewrite of code that already works.

## Before

SpaceMouseListener did three unrelated things in one class: own the
libspnav connection, read spnav events, and apply motion to the active
DiagramView. A Windows/macOS backend would have had to either duplicate
all of the DiagramView-facing logic (the pan/zoom calls, the Z-to-zoom-
factor mapping, the "which view is active" lookup -- all already verified)
or bolt onto the same class with a maze of #ifdefs. Either way, touching
that file again would put the already-tested Linux path back in scope for
review.

## After

- SpaceMouseBackend: a tiny interface (isAvailable(), a motion(dx,dy,dz)
  signal). A backend's only job is owning one platform's connection to the
  driver/daemon and translating its native event into this one signal.
- SpnavBackend: the libspnav code from the previous commit, moved behind
  that interface with no behaviour change -- still spnav_open() in the
  constructor, still a QSocketNotifier on spnav_fd(), still silent when no
  daemon/device is present.
- SpaceMouseListener: now backend-agnostic. Owns whichever backend the
  platform provides, applies its motion to the active DiagramView exactly
  as before. The DiagramView-facing code (pan/zoom calls,
  zoomFactorForZAxis) did not need to change at all -- only its input
  changed from a spnav_event_motion struct to three plain ints.

A future 3DxWare backend implements SpaceMouseBackend, is selected in
SpaceMouseListener's constructor behind its own
QET_SPACEMOUSE_BACKEND_3DXWARE guard (see the comment marking exactly
where), and never has to touch SpnavBackend or SpaceMouseListener's
DiagramView-facing half.

## CMake: one user option, one define per backend

QET_ENABLE_SPACEMOUSE is unchanged as the single option a user sets.
Internally, find_spacemouse.cmake now decides *which* backend (if any)
that resolves to: on Linux with libspnav found, QET_SPACEMOUSE_BACKEND_SPNAV
plus the umbrella QET_SPACEMOUSE_SUPPORT. Turning the option on anywhere
else today downgrades cleanly with a warning naming discussion #599,
instead of trying (and failing) to find libspnav on a platform that
doesn't ship it. Adding 3DxWare later means adding one more branch here,
not restructuring this file.

## Verified this is a pure refactor, not just "still compiles"

Reconfigured and rebuilt both ways from scratch:
 - option off: unchanged from before -- no new source files compiled, zero
   new object code.
 - option on: both new files compile with zero warnings, binary still
   links against libspnav.so.0 (confirmed via ldd), and run to completion
   in this environment (which has no spacenavd) with zero crashes and zero
   spnav-related output -- identical to before the refactor.
 - zoomFactorForZAxis re-linked and re-run in isolation: identical output
   to the pre-refactor commit (z=0 -> exactly 1.0, z=+-350 -> 1.35/0.65),
   confirming the math moved unchanged rather than being reimplemented.
2026-08-02 21:22:42 +12:00
ispyisail 2e4bb486bb Add 3D mouse (SpaceMouse/SpacePilot) pan/zoom support via libspnav
Implements discussion #599's phase 1 (Linux, libspnav): a 3Dconnexion
6-DOF device pans and zooms the active diagram view, via spacenavd.

## Off by default, zero cost when off

QET_ENABLE_SPACEMOUSE (cmake/developer_options.cmake) is OFF. Verified in
two separate build directories from a clean configure: with it off, the
new cmake/find_spacemouse.cmake step runs and does nothing (no library
lookup, no definition, no new source files compiled), and qetapp.cpp/.h
produce zero new object code -- both are entirely #ifdef'd out. The
default build is byte-for-byte the same shape as before this commit.

With it on, libspnav is located via its pkg-config file (spnav.pc, shipped
by libspnav-dev on Debian/Ubuntu and equivalent packages elsewhere). If the
option is on but the library isn't found, this does not hard-fail
configure: it downgrades back to off with a warning, so an opt-in feature
never blocks a developer who doesn't have the library installed.

## No new navigation logic -- a new input source for the existing one

DiagramView::wheelEvent() already turns a physical wheel's delta into
horizontalScrollBar()/verticalScrollBar() calls for pan and a
zoom(1 + value/1000) call for zoom -- see diagramview.cpp:661-687.
SpaceMouseListener calls exactly those same primitives from spnav motion
events instead of wheel events. It does not reimplement panning or
zooming.

## Safe by construction even when compiled in

The overwhelming majority of users of a build with the option on still
won't have spacenavd running or a device attached -- that must never
surface as an error dialog or a startup warning. SpaceMouseListener::
isAvailable() reflects this: spnav_open() failing is treated as the
ordinary case, not an error, and the object then does nothing at all.
Verified for real in this environment, which genuinely has no spacenavd
running: built with the option on, ran the resulting binary to completion,
and confirmed zero crashes and zero spnav-related output of any kind --
the silence is the point.

Motion is read via a QSocketNotifier on spnav_fd() (event-driven, no
polling loop, no idle cost) and applied to whichever DiagramView is
currently active, found via qApp->activeWindow() -> QETDiagramEditor ->
currentProjectView() -> currentDiagram(): a 6-DOF device is one ambient
input source for the whole application, not something tied to a
particular window, so there is exactly one listener, owned by QETApp.

## What could not be verified without hardware

The Z-axis-to-zoom-factor mapping (SpaceMouseListener::zoomFactorForZAxis)
is a pure function specifically so it could be tested without a live
device: confirmed a centered device (z=0) yields exactly 1.0 (an exact
no-op, not an epsilon-off value that could trip DiagramView::zoom()'s
>=1 branch), and that push/pull produce symmetric zoom-in/out factors.

What genuinely cannot be checked in this environment: which physical axis
is "left/right" vs "up/down" vs "push/forward", their sign, and whether
the ZOOM_DIVISOR/PAN_SCALE constants feel right on real hardware. Both are
named constants specifically so recalibrating them is a one-line change
once someone with a device tries it -- flagged plainly in the PR rather
than presented as verified.

## Not in this commit

Windows/macOS (proprietary 3DxWare SDK, materially bigger lift) and
device button mapping are both explicitly out of scope for this phase per
the discussion.
2026-08-02 21:04:57 +12:00
96 changed files with 126762 additions and 89 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates build-essential cmake ninja-build git pkg-config \ ca-certificates build-essential cmake ninja-build git pkg-config \
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \ qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
libqt6svg6-dev libqt6sql6-sqlite libcups2-dev \ libqt6svg6-dev libqt6sql6-sqlite libcups2-dev libsqlite3-dev \
libxkbcommon-x11-0 \ libxkbcommon-x11-0 \
xvfb openbox xdotool x11-utils xvfb openbox xdotool x11-utils
# extra-cmake-modules and the KF6 libraries are installed rather than # extra-cmake-modules and the KF6 libraries are installed rather than
+10 -1
View File
@@ -49,6 +49,7 @@ jobs:
mingw-w64-ucrt-x86_64-qt6-tools mingw-w64-ucrt-x86_64-qt6-tools
mingw-w64-ucrt-x86_64-qt6-translations mingw-w64-ucrt-x86_64-qt6-translations
mingw-w64-ucrt-x86_64-qt6-pdf mingw-w64-ucrt-x86_64-qt6-pdf
mingw-w64-ucrt-x86_64-sqlite3
mingw-w64-ucrt-x86_64-pkg-config mingw-w64-ucrt-x86_64-pkg-config
mingw-w64-ucrt-x86_64-kwidgetsaddons mingw-w64-ucrt-x86_64-kwidgetsaddons
mingw-w64-ucrt-x86_64-kcoreaddons mingw-w64-ucrt-x86_64-kcoreaddons
@@ -121,7 +122,8 @@ jobs:
-DQET_EXPORT_PROJECT_DB=ON \ -DQET_EXPORT_PROJECT_DB=ON \
-DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \ -DCMAKE_C_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
-DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \ -DCMAKE_CXX_COMPILER_LAUNCHER=/ucrt64/bin/ccache \
-DSQLite3_INCLUDE_DIR=/ucrt64/include \
-DSQLite3_LIBRARY=/ucrt64/lib/libsqlite3.dll.a \
.. ..
ninja -j"$NPROC" ninja -j"$NPROC"
@@ -194,6 +196,13 @@ jobs:
cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/" cp /ucrt64/bin/libgcc_s_seh-1.dll "$BIN/"
cp /ucrt64/bin/libstdc++-6.dll "$BIN/" cp /ucrt64/bin/libstdc++-6.dll "$BIN/"
cp /ucrt64/bin/libwinpthread-1.dll "$BIN/" cp /ucrt64/bin/libwinpthread-1.dll "$BIN/"
SQLITE=$(find /ucrt64/bin -name "libsqlite3*.dll" | head -1)
if [ -n "$SQLITE" ]; then
cp "$SQLITE" "$BIN/"
echo "SQLite3 copied: $(basename $SQLITE)"
else
echo "WARNING: libsqlite3 not found in /ucrt64/bin/"
fi
cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/" cp "$GITHUB_WORKSPACE/build-aux/windows/QET64.nsi" "$NSIS_ROOT/"
cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/" cp "$GITHUB_WORKSPACE/build-aux/windows/lang_extra.nsh" "$NSIS_ROOT/"
+1 -9
View File
@@ -9,12 +9,4 @@ doc/*
!doc/QElectroTech.qch !doc/QElectroTech.qch
QElectroTech.tag QElectroTech.tag
!doc/doc-utils !doc/doc-utils
lang/*.qm ui_*.h
# -- IDE settings/intermediate files --
# zed
.zed
.cache
# VS Code
.vscode
+8
View File
@@ -36,6 +36,7 @@ include(cmake/git_update_submodules.cmake)
include(cmake/git_last_commit_sha.cmake) include(cmake/git_last_commit_sha.cmake)
include(cmake/fetch_singleapplication.cmake) include(cmake/fetch_singleapplication.cmake)
include(cmake/fetch_pugixml.cmake) include(cmake/fetch_pugixml.cmake)
include(cmake/find_spacemouse.cmake)
include(cmake/qet_compilation_vars.cmake) include(cmake/qet_compilation_vars.cmake)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
@@ -169,6 +170,12 @@ else()
) )
endif() endif()
if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED)
list(APPEND QET_SPACEMOUSE_LIBRARIES PkgConfig::SPNAV)
endif()
# A future Windows/macOS (3DxWare) backend would list.APPEND its own SDK
# libraries here, behind its own *_ENABLED guard, alongside this one.
if(APPLE) if(APPLE)
# CFBundleIdentifier must not be empty. CMake's default Info.plist # CFBundleIdentifier must not be empty. CMake's default Info.plist
# template fills it from MACOSX_BUNDLE_GUI_IDENTIFIER; with that unset # template fills it from MACOSX_BUNDLE_GUI_IDENTIFIER; with that unset
@@ -287,6 +294,7 @@ target_link_libraries(
SQLite3::SQLite3 SQLite3::SQLite3
${KF_PRIVATE_LIBRARIES} ${KF_PRIVATE_LIBRARIES}
${QET_PRIVATE_LIBRARIES} ${QET_PRIVATE_LIBRARIES}
${QET_SPACEMOUSE_LIBRARIES}
) )
target_include_directories( target_include_directories(
+21 -7
View File
@@ -27,12 +27,18 @@ git submodule update --init --recursive
| C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) | | C++17 compiler | required | GCC or Clang on Unix-like platforms; MSVC or MinGW-w64 g++ on Windows — see [Choosing a compiler](#3-choosing-a-compiler-unix) / [Building on Windows](#6-building-on-windows-msvc--mingw) |
| Qt6 base + widgets | required | | | Qt6 base + widgets | required | |
| Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below | | Qt6 **GuiPrivate** headers | required | needed for clickable PDF hyperlinks; **hard build failure** at CMake generate time if missing, see below |
| SQLite3 | required | used by the nomenclature/summary database |
| Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build | | Qt Linguist tools (`lrelease`) | required | compiles the tracked `.ts` files into `.qm` as part of every normal build |
| pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below | | pugixml | handled automatically | fetched and built via CMake FetchContent if not already present on the system — see [pugixml](#8-pugixml) below |
| Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed | | Qt Test module | required if building tests | `PACKAGE_TESTS` is `ON` by default; QtTest ships as part of the base Qt6 dev packages listed below on every platform, no extra package needed |
| KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) | | KDE Frameworks (KF6) | optional | see [Building without KDE Frameworks](#9-building-without-kde-frameworks) |
| QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) | | QtPdf module | optional | see [PDF page import](#7-pdf-page-import-qtpdf) |
A note on CMake versions: the project declares a minimum of 3.5 but is
routinely built with much newer releases; if your CMake is older than 4.3 it
simply won't have the newer `SQLite3::SQLite3` target name, which the build
script compensates for automatically. There is nothing you need to do either
way.
## 3. Building (out-of-source build) ## 3. Building (out-of-source build)
@@ -100,7 +106,7 @@ the closest match.
sudo apt install \ sudo apt install \
build-essential cmake ninja-build git \ build-essential cmake ninja-build git \
qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \ qt6-base-dev qt6-base-private-dev qt6-tools-dev qt6-tools-dev-tools \
libsqlite3-dev \
libkf6coreaddons-dev libkf6widgetsaddons-dev libkf6coreaddons-dev libkf6widgetsaddons-dev
``` ```
@@ -132,6 +138,7 @@ sudo apt install libpugixml-dev
sudo dnf install \ sudo dnf install \
cmake gcc-c++ git \ cmake gcc-c++ git \
qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \ qt6-qtbase-devel qt6-qtbase-private-devel qt6-qttools-devel \
sqlite-devel \
kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel kf6-kcoreaddons-devel kf6-kwidgetsaddons-devel
``` ```
@@ -148,10 +155,11 @@ Optional, for a system pugixml: `sudo dnf install pugixml-devel`.
pkg install \ pkg install \
cmake git \ cmake git \
qt6-base qt6-tools \ qt6-base qt6-tools \
sqlite3 \
kf6-kcoreaddons kf6-kwidgetsaddons kf6-kcoreaddons kf6-kwidgetsaddons
``` ```
(from ports: `devel/qt6-base`, `devel/qt6-tools`, (from ports: `devel/qt6-base`, `devel/qt6-tools`, `databases/sqlite3`,
`devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's `devel/kf6-kcoreaddons`, `x11-toolkits/kf6-kwidgetsaddons`.) Qt6's
`GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate `GuiPrivate` headers ship as part of `qt6-base` on FreeBSD, no separate
package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing package is needed. `QtPdf` is not packaged on FreeBSD at the time of writing
@@ -167,7 +175,7 @@ Optional, for a system pugixml: `pkg install pugixml` (`devel/pugixml`).
Using [Homebrew](https://brew.sh): Using [Homebrew](https://brew.sh):
```sh ```sh
brew install cmake qt ninja brew install cmake qt sqlite ninja
``` ```
Homebrew's `qt` formula is Qt6 and includes the private headers, so no Homebrew's `qt` formula is Qt6 and includes the private headers, so no
@@ -185,7 +193,7 @@ Optional, for a system pugixml: `brew install pugixml`.
See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the See [Building on Windows](#6-building-on-windows-msvc--mingw) below — the
package sources differ enough from the Unix-like platforms above (no system package sources differ enough from the Unix-like platforms above (no system
package manager and Qt aren't provided the same way) that it gets package manager, SQLite3 and Qt aren't provided the same way) that it gets
its own section. its own section.
## 5. pugixml ## 5. pugixml
@@ -207,7 +215,7 @@ on Debian/Ubuntu, `pugixml-devel` on Fedora).
Both toolchains QET's CMake build targets on Windows are covered here: Both toolchains QET's CMake build targets on Windows are covered here:
**MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the **MSVC** (Visual Studio 2019/2022) and **MinGW-w64** (gcc). Unlike the
Unix-like platforms above, there's no single system package manager, so Unix-like platforms above, there's no single system package manager, so
Qt and (optionally) KDE Frameworks each need to be sourced Qt, SQLite3 and (optionally) KDE Frameworks each need to be sourced
separately per toolchain. separately per toolchain.
One piece of good news either way: unlike Debian/Fedora, the official Qt One piece of good news either way: unlike Debian/Fedora, the official Qt
@@ -228,7 +236,11 @@ normally use `-DBUILD_WITH_KF=OFF` (see the
1. Install Visual Studio with the "Desktop development with C++" workload, 1. Install Visual Studio with the "Desktop development with C++" workload,
and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit) and install Qt6 for MSVC (e.g. the `msvc2019_64` or `msvc2022_64` kit)
via the [Qt Online Installer](https://www.qt.io/download-qt-installer). via the [Qt Online Installer](https://www.qt.io/download-qt-installer).
2. Configure and build from an "x64 Native Tools Command Prompt for VS": 2. Get SQLite3 — the simplest route is [vcpkg](https://vcpkg.io):
```bat
vcpkg install sqlite3:x64-windows
```
3. Configure and build from an "x64 Native Tools Command Prompt for VS":
```bat ```bat
mkdir build && cd build mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64 ^ cmake .. -G "Visual Studio 17 2022" -A x64 ^
@@ -281,7 +293,9 @@ Using the Qt Online Installer's bundled MinGW kit instead: point
`CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure `CMAKE_PREFIX_PATH` at that kit (e.g. `C:\Qt\6.x.x\mingw_64`) and make sure
its bundled `g++.exe` comes first on `PATH`, or pass its bundled `g++.exe` comes first on `PATH`, or pass
`-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick `-DCMAKE_C_COMPILER`/`-DCMAKE_CXX_COMPILER` explicitly so CMake doesn't pick
up a different MinGW installation. up a different MinGW installation. SQLite3 still has to come from elsewhere
in this path — vcpkg with a `mingw`-flavoured triplet, or MSYS2's package as
above.
## 7. Qt6 private headers (mandatory) ## 7. Qt6 private headers (mandatory)
+6 -2
View File
@@ -50,6 +50,7 @@ parts:
- python3-lxml - python3-lxml
- python3-tk - python3-tk
- libtk8.6 - libtk8.6
- libspnav0
kde-sdk-setup: kde-sdk-setup:
plugin: nil plugin: nil
@@ -71,6 +72,7 @@ parts:
source: . source: .
stage-packages: stage-packages:
- git - git
- sqlite3
- xdg-user-dirs - xdg-user-dirs
- libqt6qml6 - libqt6qml6
# libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content # libxcb-cursor0 workaround was needed against the Qt5/KF5 core22 content
@@ -81,7 +83,7 @@ parts:
- git - git
- cmake - cmake
- ninja-build - ninja-build
- libsqlite3-dev
- qt6-tools-dev - qt6-tools-dev
- qt6-base-private-dev - qt6-base-private-dev
- qt6-declarative-dev - qt6-declarative-dev
@@ -91,6 +93,7 @@ parts:
# runs FindCups at configure time and fails without the CUPS headers. # runs FindCups at configure time and fails without the CUPS headers.
# Build-time only: nothing from it is staged into the snap. # Build-time only: nothing from it is staged into the snap.
- libcups2-dev - libcups2-dev
- libspnav-dev
override-build: | override-build: |
displayed_version=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g') displayed_version=$(cat sources/qetversion.cpp | grep "return QVersionNumber{"| head -n 1| awk -F "{" '{ print $2 }' | awk -F "}" '{ print $1 }' | sed -e 's/,/./g' -e 's/ //g')
snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)" snap_version="${displayed_version}-g$(git rev-parse --short=8 HEAD)"
@@ -105,7 +108,8 @@ parts:
-DBUILD_KF=OFF \ -DBUILD_KF=OFF \
-DPACKAGE_TESTS=OFF \ -DPACKAGE_TESTS=OFF \
-DBUILD_PUGIXML=ON \ -DBUILD_PUGIXML=ON \
-DQET_EXPORT_PROJECT_DB=ON -DQET_EXPORT_PROJECT_DB=ON \
-DQET_ENABLE_SPACEMOUSE=ON
ninja -C build -j${CRAFT_PARALLEL_BUILD_COUNT} ninja -C build -j${CRAFT_PARALLEL_BUILD_COUNT}
DESTDIR="$CRAFT_PART_INSTALL" ninja -C build install DESTDIR="$CRAFT_PART_INSTALL" ninja -C build install
override-stage: | override-stage: |
+5
View File
@@ -47,3 +47,8 @@ option(BUILD_WITH_KF "Build with KDE Frameworks" ON)
# compile for everyone else. Leaving it off keeps CI and contributors on the # compile for everyone else. Leaving it off keeps CI and contributors on the
# strict behaviour, and only developers who opt in trade that for the speed. # strict behaviour, and only developers who opt in trade that for the speed.
option(QET_ENABLE_PCH "Use precompiled headers (developer build speed; may mask missing #includes)" OFF) option(QET_ENABLE_PCH "Use precompiled headers (developer build speed; may mask missing #includes)" OFF)
# Phase 1 (Linux, libspnav) of discussion #599: 3Dconnexion SpaceMouse/
# SpacePilot pan/zoom support. Off by default -- see cmake/find_spacemouse.cmake
# for what happens when it is on but libspnav isn't found.
option(QET_ENABLE_SPACEMOUSE "Build with 3Dconnexion/libspnav 3D mouse support for pan/zoom (Linux, requires libspnav-dev and spacenavd)" OFF)
+63
View File
@@ -0,0 +1,63 @@
# Copyright 2006 The QElectroTech Team
# This file is part of QElectroTech.
#
# QElectroTech is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# QElectroTech is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
message(" - find_spacemouse")
# QET_ENABLE_SPACEMOUSE (cmake/developer_options.cmake) is off by default,
# so none of this runs, and the default build is entirely unaffected: no new
# dependency, no new source files, no new symbols.
#
# When it is on, the platform decides which backend (if any) is available --
# see sources/spacemouse/spacemousebackend.h for what a backend is. Only one
# exists today: SpnavBackend, Linux, via libspnav. A Windows/macOS backend
# would need 3Dconnexion's proprietary 3DxWare SDK and is not implemented
# (see discussion #599's own phase split); until it is, turning this option
# on there downgrades cleanly with a warning rather than failing configure.
set(QET_SPACEMOUSE_ENABLED FALSE)
set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED FALSE)
if(QET_ENABLE_SPACEMOUSE)
if(UNIX AND NOT APPLE)
# libspnav is looked for via its pkg-config file (present on every
# distro package of it this was checked against: Debian/Ubuntu's
# libspnav-dev ships /usr/share/pkgconfig/spnav.pc). Not found does
# not hard-fail the whole configure -- it downgrades the option back
# to off with a clear message, so a developer who doesn't have the
# library installed still gets a normal build instead of a configure
# error for an opt-in feature they didn't ask to block on.
find_package(PkgConfig)
if(PkgConfig_FOUND)
pkg_check_modules(SPNAV IMPORTED_TARGET spnav)
endif()
if(SPNAV_FOUND)
set(QET_SPACEMOUSE_ENABLED TRUE)
set(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED TRUE)
add_definitions(-DQET_SPACEMOUSE_SUPPORT)
add_definitions(-DQET_SPACEMOUSE_BACKEND_SPNAV)
message("QET_ENABLE_SPACEMOUSE ON (backend: libspnav ${SPNAV_VERSION})")
else()
message(WARNING "QET_ENABLE_SPACEMOUSE is ON but libspnav was not found via pkg-config "
"(install libspnav-dev, or the equivalent for your distribution) -- "
"building WITHOUT 3D mouse support.")
endif()
else()
message(WARNING "QET_ENABLE_SPACEMOUSE is ON but no 3D mouse backend is implemented yet "
"for this platform (only Linux/libspnav exists today -- see "
"https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/599) "
"-- building WITHOUT 3D mouse support.")
endif()
endif()
+23
View File
@@ -297,6 +297,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/numerotationcontextcommands.h ${QET_DIR}/sources/autoNum/numerotationcontextcommands.h
${QET_DIR}/sources/autoNum/numerotationcontext.cpp ${QET_DIR}/sources/autoNum/numerotationcontext.cpp
${QET_DIR}/sources/autoNum/numerotationcontext.h ${QET_DIR}/sources/autoNum/numerotationcontext.h
${QET_DIR}/sources/autoNum/renumberelementscommand.cpp
${QET_DIR}/sources/autoNum/renumberelementscommand.h
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp ${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.cpp
${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h ${QET_DIR}/sources/autoNum/ui/autonumberingdockwidget.h
${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp ${QET_DIR}/sources/autoNum/ui/autonumberingmanagementw.cpp
@@ -307,6 +309,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/autoNum/ui/formulaautonumberingw.h ${QET_DIR}/sources/autoNum/ui/formulaautonumberingw.h
${QET_DIR}/sources/autoNum/ui/numparteditorw.cpp ${QET_DIR}/sources/autoNum/ui/numparteditorw.cpp
${QET_DIR}/sources/autoNum/ui/numparteditorw.h ${QET_DIR}/sources/autoNum/ui/numparteditorw.h
${QET_DIR}/sources/autoNum/ui/renumberelementsdialog.cpp
${QET_DIR}/sources/autoNum/ui/renumberelementsdialog.h
${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp ${QET_DIR}/sources/autoNum/ui/selectautonumw.cpp
${QET_DIR}/sources/autoNum/ui/selectautonumw.h ${QET_DIR}/sources/autoNum/ui/selectautonumw.h
@@ -871,6 +875,25 @@ list(APPEND QET_SRC_FILES
${QET_DIR}/sources/scripting/qetscripting.h ${QET_DIR}/sources/scripting/qetscripting.h
) )
if(QET_SPACEMOUSE_ENABLED)
list(APPEND QET_SRC_FILES
${QET_DIR}/sources/spacemouse/spacemousebackend.h
${QET_DIR}/sources/spacemouse/spacemousebuttonmap.cpp
${QET_DIR}/sources/spacemouse/spacemousebuttonmap.h
${QET_DIR}/sources/spacemouse/spacemouselistener.cpp
${QET_DIR}/sources/spacemouse/spacemouselistener.h
${QET_DIR}/sources/ui/configpage/spacemouseconfigpage.cpp
${QET_DIR}/sources/ui/configpage/spacemouseconfigpage.h
)
endif()
if(QET_SPACEMOUSE_BACKEND_SPNAV_ENABLED)
list(APPEND QET_SRC_FILES
${QET_DIR}/sources/spacemouse/spnavbackend.cpp
${QET_DIR}/sources/spacemouse/spnavbackend.h
)
endif()
set(TS_FILES set(TS_FILES
${QET_DIR}/lang/qet_ar.ts ${QET_DIR}/lang/qet_ar.ts
${QET_DIR}/lang/qet_ca.ts ${QET_DIR}/lang/qet_ca.ts
File diff suppressed because it is too large Load Diff
+222
View File
@@ -0,0 +1,222 @@
<!DOCTYPE QPH>
<QPH sourcelanguage="fr_FR" language="ca">
<phrase>
<source>Réf. croisée</source>
<target>Ref. creuada</target>
</phrase>
<phrase>
<source>armoire</source>
<target>armari</target>
<definition>cabinet / enclosure</definition>
</phrase>
<phrase>
<source>bobine</source>
<target>bobina</target>
<definition>coil</definition>
</phrase>
<phrase>
<source>borne</source>
<target>born</target>
<definition>terminal - a connection point on a symbol</definition>
</phrase>
<phrase>
<source>bornier</source>
<target>bornes</target>
<definition>terminal strip</definition>
</phrase>
<phrase>
<source>cable</source>
<target>cable</target>
<definition>cable</definition>
</phrase>
<phrase>
<source>calibre</source>
<target>calibre</target>
<definition>rating - the current rating of a device</definition>
</phrase>
<phrase>
<source>cartouche</source>
<target>cartutx</target>
<definition>title block - the framed information panel</definition>
</phrase>
<phrase>
<source>champ de texte</source>
<target>camp de text</target>
<definition>text field</definition>
</phrase>
<phrase>
<source>collection</source>
<target>col·lecció</target>
<definition>collection - the symbol library</definition>
</phrase>
<phrase>
<source>conducteur</source>
<target>conductor</target>
<definition>conductor - the drawn wire between terminals</definition>
</phrase>
<phrase>
<source>contact</source>
<target>contacte</target>
<definition>contact</definition>
</phrase>
<phrase>
<source>contacteur</source>
<target>contactor</target>
<definition>contactor</definition>
</phrase>
<phrase>
<source>courant</source>
<target>corrent</target>
<definition>current</definition>
</phrase>
<phrase>
<source>disjoncteur</source>
<target>disjuntor</target>
<definition>circuit breaker</definition>
</phrase>
<phrase>
<source>dossier</source>
<target>carpeta</target>
<definition>folder</definition>
</phrase>
<phrase>
<source>element</source>
<target>element</target>
<definition>element - a symbol placed on a sheet</definition>
</phrase>
<phrase>
<source>esclave</source>
<target>esclau</target>
<definition>slave - the contact block belonging to a master</definition>
</phrase>
<phrase>
<source>fil</source>
<target>fil</target>
<definition>wire</definition>
</phrase>
<phrase>
<source>folio</source>
<target>plec</target>
<definition>sheet - one drawing of a set</definition>
</phrase>
<phrase>
<source>fusible</source>
<target>fusible</target>
<definition>fuse</definition>
</phrase>
<phrase>
<source>grille</source>
<target>graella</target>
<definition>grid</definition>
</phrase>
<phrase>
<source>interrupteur</source>
<target>interruptor</target>
<definition>switch</definition>
</phrase>
<phrase>
<source>maitre</source>
<target>mestre</target>
<definition>master - the element a cross-reference points from</definition>
</phrase>
<phrase>
<source>modele</source>
<target>plantilla</target>
<definition>template</definition>
</phrase>
<phrase>
<source>moteur</source>
<target>motor</target>
<definition>motor</definition>
</phrase>
<phrase>
<source>neutre</source>
<target>neutre</target>
<definition>neutral</definition>
</phrase>
<phrase>
<source>nomenclature</source>
<target>nomenclatura</target>
<definition>parts list / bill of materials</definition>
</phrase>
<phrase>
<source>numerotation</source>
<target>numeració</target>
<definition>numbering - automatic wire and part numbering</definition>
</phrase>
<phrase>
<source>phase</source>
<target>fase</target>
<definition>phase</definition>
</phrase>
<phrase>
<source>potentiel</source>
<target>potencial</target>
<definition>potential - conductors electrically joined as one node</definition>
</phrase>
<phrase>
<source>projet</source>
<target>projecte</target>
<definition>project - the .qet file holding the sheets</definition>
</phrase>
<phrase>
<source>puissance</source>
<target>potència</target>
<definition>power</definition>
</phrase>
<phrase>
<source>relais</source>
<target>relé</target>
<definition>relay</definition>
</phrase>
<phrase>
<source>renvoi de folio</source>
<target>referència de plec</target>
<definition>sheet cross-reference</definition>
</phrase>
<phrase>
<source>repere</source>
<target>referència</target>
<definition>designation - the identifying mark on a part</definition>
</phrase>
<phrase>
<source>report de folio</source>
<target>remissió de plec</target>
<definition>sheet cross-reference (the arrow symbol)</definition>
</phrase>
<phrase>
<source>schema</source>
<target>esquema</target>
<definition>diagram</definition>
</phrase>
<phrase>
<source>section</source>
<target>secció</target>
<definition>cross-section - conductor size in mm2</definition>
</phrase>
<phrase>
<source>sommaire</source>
<target>sumari</target>
<definition>summary / table of contents</definition>
</phrase>
<phrase>
<source>tension</source>
<target>tensió</target>
<definition>voltage</definition>
</phrase>
<phrase>
<source>terre</source>
<target>terra</target>
<definition>earth / ground</definition>
</phrase>
<phrase>
<source>texte</source>
<target>text</target>
<definition>text</definition>
</phrase>
<phrase>
<source>transformateur</source>
<target>transformador</target>
<definition>transformer</definition>
</phrase>
</QPH>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4
View File
@@ -100,4 +100,8 @@
<source>traduction</source> <source>traduction</source>
<target>Übersetzung</target> <target>Übersetzung</target>
</phrase> </phrase>
<phrase>
<source>Modifier le nom de la borne</source>
<target>Klemmenname ändern</target>
</phrase>
</QPH> </QPH>
File diff suppressed because it is too large Load Diff
+918
View File
@@ -0,0 +1,918 @@
<!DOCTYPE QPH>
<QPH sourcelanguage="fr_FR" language="en">
<phrase>
<source>Blue : Blue</source>
<target>Blue : Blue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : CornflowerBlue</source>
<target>Blue : CornflowerBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : DarkBlue</source>
<target>Blue : DarkBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : DeepSkyBlue</source>
<target>Blue : DeepSkyBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : DodgerBlue</source>
<target>Blue : DodgerBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : LightBlue</source>
<target>Blue : LightBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : LightSkyBlue</source>
<target>Blue : LightSkyBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : LightSteelBlue</source>
<target>Blue : LightSteelBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : MediumBlue</source>
<target>Blue : MediumBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : MidnightBlue</source>
<target>Blue : MidnightBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : Navy</source>
<target>Blue : Navy</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : PowderBlue</source>
<target>Blue : PowderBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : RoyalBlue</source>
<target>Blue : RoyalBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : SkyBlue</source>
<target>Blue : SkyBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Blue : SteelBlue</source>
<target>Blue : SteelBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Bisque</source>
<target>Brown : Bisque</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : BlanchedAlmond</source>
<target>Brown : BlanchedAlmond</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Brown</source>
<target>Brown : Brown</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Burlywood</source>
<target>Brown : Burlywood</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Chocolate</source>
<target>Brown : Chocolate</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Cornsilk</source>
<target>Brown : Cornsilk</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : DarkGoldenrod</source>
<target>Brown : DarkGoldenrod</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Goldenrod</source>
<target>Brown : Goldenrod</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Maroon</source>
<target>Brown : Maroon</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : NavajoWhite</source>
<target>Brown : NavajoWhite</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Peru</source>
<target>Brown : Peru</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : RosyBrown</source>
<target>Brown : RosyBrown</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : SaddleBrown</source>
<target>Brown : SaddleBrown</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : SandyBrown</source>
<target>Brown : SandyBrown</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Sienna</source>
<target>Brown : Sienna</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Tan</source>
<target>Brown : Tan</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Brown : Wheat</source>
<target>Brown : Wheat</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : Aqua</source>
<target>Cyan : Aqua</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : Aquamarine</source>
<target>Cyan : Aquamarine</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : CadetBlue</source>
<target>Cyan : CadetBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : Cyan</source>
<target>Cyan : Cyan</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : DarkCyan</source>
<target>Cyan : DarkCyan</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : DarkTurquoise</source>
<target>Cyan : DarkTurquoise</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : LightCyan</source>
<target>Cyan : LightCyan</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : LightSeaGreen</source>
<target>Cyan : LightSeaGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : MediumTurquoise</source>
<target>Cyan : MediumTurquoise</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : PaleTurquoise</source>
<target>Cyan : PaleTurquoise</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : Teal</source>
<target>Cyan : Teal</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Cyan : Turquoise</source>
<target>Cyan : Turquoise</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : Black</source>
<target>Gray : Black</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : DarkGray</source>
<target>Gray : DarkGray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : DarkSlateGray</source>
<target>Gray : DarkSlateGray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : DimGray</source>
<target>Gray : DimGray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : Gainsboro</source>
<target>Gray : Gainsboro</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : Gray</source>
<target>Gray : Gray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : LightGray</source>
<target>Gray : LightGray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : LightSlateGray</source>
<target>Gray : LightSlateGray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : Silver</source>
<target>Gray : Silver</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Gray : SlateGray</source>
<target>Gray : SlateGray</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : Chartreuse</source>
<target>Green : Chartreuse</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : DarkGreen</source>
<target>Green : DarkGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : DarkOliveGreen</source>
<target>Green : DarkOliveGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : DarkSeaGreen</source>
<target>Green : DarkSeaGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : ForestGreen</source>
<target>Green : ForestGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : Green</source>
<target>Green : Green</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : GreenYellow</source>
<target>Green : GreenYellow</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : LawnGreen</source>
<target>Green : LawnGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : LightGreen</source>
<target>Green : LightGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : Lime</source>
<target>Green : Lime</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : LimeGreen</source>
<target>Green : LimeGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : MediumAquamarine</source>
<target>Green : MediumAquamarine</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : MediumSeaGreen</source>
<target>Green : MediumSeaGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : MediumSpringGreen</source>
<target>Green : MediumSpringGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : Olive</source>
<target>Green : Olive</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : OliveDrab</source>
<target>Green : OliveDrab</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : PaleGreen</source>
<target>Green : PaleGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : SeaGreen</source>
<target>Green : SeaGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : SpringGreen</source>
<target>Green : SpringGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Green : YellowGreen</source>
<target>Green : YellowGreen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Orange : Coral</source>
<target>Orange : Coral</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Orange : DarkOrange</source>
<target>Orange : DarkOrange</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Orange : Orange</source>
<target>Orange : Orange</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Orange : OrangeRed</source>
<target>Orange : OrangeRed</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Orange : Tomato</source>
<target>Orange : Tomato</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Pink : DeepPink</source>
<target>Pink: DeepPink</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Pink : HotPink</source>
<target>Pink: HotPink</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Pink : LightPink</source>
<target>Pink: LightPink</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Pink : MediumVioletRed</source>
<target>Pink: MediumVioletRed</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Pink : PaleVioletRed</source>
<target>Pink: PaleVioletRed</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Pink : Pink</source>
<target>Pink: Pink</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : BlueViolet</source>
<target>Purple : BlueViolet</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : DarkMagenta</source>
<target>Purple : DarkMagenta</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : DarkOrchid</source>
<target>Purple : DarkOrchid</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : DarkSlateBlue</source>
<target>Purple : DarkSlateBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : DarkViolet</source>
<target>Purple : DarkViolet</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Fuchsia</source>
<target>Purple : Fuchsia</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Indigo</source>
<target>Purple : Indigo</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Lavender</source>
<target>Purple : Lavender</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Magenta</source>
<target>Purple : Magenta</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : MediumOrchid</source>
<target>Purple : MediumOrchid</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : MediumPurple</source>
<target>Purple : MediumPurple</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : MediumSlateBlue</source>
<target>Purple : MediumSlateBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Orchid</source>
<target>Purple : Orchid</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Plum</source>
<target>Purple : Plum</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Purple</source>
<target>Purple : Purple</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : SlateBlue</source>
<target>Purple : SlateBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Thistle</source>
<target>Purple : Thistle</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Purple : Violet</source>
<target>Purple : Violet</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : Crimson</source>
<target>Red : Crimson</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : DarkRed</source>
<target>Red : DarkRed</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : DarkSalmon</source>
<target>Red: DarkSalmon</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : Firebrick</source>
<target>Red : Firebrick</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : IndianRed</source>
<target>Red: IndianRed</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : LightCoral</source>
<target>Red: LightCoral</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : LightSalmon</source>
<target>Red: LightSalmon</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : Red</source>
<target>Red : Red</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Red : Salmon</source>
<target>Red: Salmon</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : AliceBlue</source>
<target>White : AliceBlue</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : AntiqueWhite</source>
<target>White : AntiqueWhite</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Azure</source>
<target>White : Azure</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Beige</source>
<target>White : Beige</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : FloralWhite</source>
<target>White : FloralWhite</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : GhostWhite</source>
<target>White : GhostWhite</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Honeydew</source>
<target>White : Honeydew</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Ivory</source>
<target>White : Ivory</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : LavenderBlush</source>
<target>White : LavenderBlush</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Linen</source>
<target>White : Linen</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : MintCream</source>
<target>White : MintCream</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : MistyRose</source>
<target>White : MistyRose</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : OldLace</source>
<target>White : OldLace</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Seashell</source>
<target>White : Seashell</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : Snow</source>
<target>White : Snow</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : White</source>
<target>White : White</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>White : WhiteSmoke</source>
<target>White : WhiteSmoke</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : DarkKhaki</source>
<target>Yellow : DarkKhaki</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : Gold</source>
<target>Yellow : Gold</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : Khaki</source>
<target>Yellow : Khaki</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : LemonChiffon</source>
<target>Yellow : LemonChiffon</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : LightGoldenrodYellow</source>
<target>Yellow : LightGoldenrodYellow</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : LightYellow</source>
<target>Yellow : LightYellow</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : Moccasin</source>
<target>Yellow : Moccasin</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : PaleGoldenrod</source>
<target>Yellow : PaleGoldenrod</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : PapayaWhip</source>
<target>Yellow : PapayaWhip</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : PeachPuff</source>
<target>Yellow : PeachPuff</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>Yellow : Yellow</source>
<target>Yellow : Yellow</target>
<definition>element part filling</definition>
</phrase>
<phrase>
<source>armoire</source>
<target>cabinet</target>
<definition>cabinet / enclosure</definition>
</phrase>
<phrase>
<source>bobine</source>
<target>coil</target>
<definition>coil</definition>
</phrase>
<phrase>
<source>borne</source>
<target>terminal</target>
<definition>terminal - a connection point on a symbol</definition>
</phrase>
<phrase>
<source>bornier</source>
<target>terminal strip</target>
<definition>terminal strip</definition>
</phrase>
<phrase>
<source>cable</source>
<target>cable</target>
<definition>cable</definition>
</phrase>
<phrase>
<source>calibre</source>
<target>rating</target>
<definition>rating - the current rating of a device</definition>
</phrase>
<phrase>
<source>cartouche</source>
<target>title block</target>
<definition>title block - the framed information panel</definition>
</phrase>
<phrase>
<source>champ de texte</source>
<target>text field</target>
<definition>text field</definition>
</phrase>
<phrase>
<source>collection</source>
<target>collection</target>
<definition>collection - the symbol library</definition>
</phrase>
<phrase>
<source>conducteur</source>
<target>conductor</target>
<definition>conductor - the drawn wire between terminals</definition>
</phrase>
<phrase>
<source>contact</source>
<target>contact</target>
<definition>contact</definition>
</phrase>
<phrase>
<source>contacteur</source>
<target>contactor</target>
<definition>contactor</definition>
</phrase>
<phrase>
<source>courant</source>
<target>current</target>
<definition>current</definition>
</phrase>
<phrase>
<source>disjoncteur</source>
<target>circuit breaker</target>
<definition>circuit breaker</definition>
</phrase>
<phrase>
<source>dossier</source>
<target>folder</target>
<definition>folder</definition>
</phrase>
<phrase>
<source>element</source>
<target>element</target>
<definition>element - a symbol placed on a sheet</definition>
</phrase>
<phrase>
<source>esclave</source>
<target>slave</target>
<definition>slave - the contact block belonging to a master</definition>
</phrase>
<phrase>
<source>fil</source>
<target>wire</target>
<definition>wire</definition>
</phrase>
<phrase>
<source>folio</source>
<target>sheet</target>
<definition>sheet - one drawing of a set</definition>
</phrase>
<phrase>
<source>fusible</source>
<target>fuse</target>
<definition>fuse</definition>
</phrase>
<phrase>
<source>grille</source>
<target>grid</target>
<definition>grid</definition>
</phrase>
<phrase>
<source>interrupteur</source>
<target>switch</target>
<definition>switch</definition>
</phrase>
<phrase>
<source>maitre</source>
<target>master</target>
<definition>master - the element a cross-reference points from</definition>
</phrase>
<phrase>
<source>modele</source>
<target>template</target>
<definition>template</definition>
</phrase>
<phrase>
<source>moteur</source>
<target>motor</target>
<definition>motor</definition>
</phrase>
<phrase>
<source>neutre</source>
<target>neutral</target>
<definition>neutral</definition>
</phrase>
<phrase>
<source>nomenclature</source>
<target>parts list</target>
<definition>parts list / bill of materials</definition>
</phrase>
<phrase>
<source>numerotation</source>
<target>numbering</target>
<definition>numbering - automatic wire and part numbering</definition>
</phrase>
<phrase>
<source>phase</source>
<target>phase</target>
<definition>phase</definition>
</phrase>
<phrase>
<source>potentiel</source>
<target>potential</target>
<definition>potential - conductors electrically joined as one node</definition>
</phrase>
<phrase>
<source>projet</source>
<target>project</target>
<definition>project - the .qet file holding the sheets</definition>
</phrase>
<phrase>
<source>puissance</source>
<target>power</target>
<definition>power</definition>
</phrase>
<phrase>
<source>relais</source>
<target>relay</target>
<definition>relay</definition>
</phrase>
<phrase>
<source>renvoi de folio</source>
<target>sheet cross-reference</target>
<definition>sheet cross-reference</definition>
</phrase>
<phrase>
<source>repere</source>
<target>designation</target>
<definition>designation - the identifying mark on a part</definition>
</phrase>
<phrase>
<source>report de folio</source>
<target>sheet cross-reference</target>
<definition>sheet cross-reference (the arrow symbol)</definition>
</phrase>
<phrase>
<source>schema</source>
<target>diagram</target>
<definition>diagram</definition>
</phrase>
<phrase>
<source>section</source>
<target>cross-section</target>
<definition>cross-section - conductor size in mm2</definition>
</phrase>
<phrase>
<source>sommaire</source>
<target>summary</target>
<definition>summary / table of contents</definition>
</phrase>
<phrase>
<source>tension</source>
<target>voltage</target>
<definition>voltage</definition>
</phrase>
<phrase>
<source>terre</source>
<target>earth</target>
<definition>earth / ground</definition>
</phrase>
<phrase>
<source>texte</source>
<target>text</target>
<definition>text</definition>
</phrase>
<phrase>
<source>transformateur</source>
<target>transformer</target>
<definition>transformer</definition>
</phrase>
</QPH>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+941
View File
@@ -0,0 +1,941 @@
<!DOCTYPE QPH>
<QPH sourcelanguage="fr_FR" language="nl_BE">
<phrase>
<source> Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4
</source>
<target>Contacten: NO: %1, NC: %2, wisselcontacten: %3, andere: %4
</target>
</phrase>
<phrase>
<source> Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8
</source>
<target>Contacten: NO: %1/%2, NC: %3/%4, wisselcontacten: %5/%6, andere: %7/%8
</target>
</phrase>
<phrase>
<source> %</source>
<target>%</target>
</phrase>
<phrase>
<source> (Ctrl pendant le glissement = position libre, sans accrochage à la grille)</source>
<target>(Ctrl tijdens het slepen = vrije positie, zonder vastklikken aan het raster)</target>
</phrase>
<phrase>
<source> -- %1 : mode %2</source>
<target>-- %1 : modus %2</target>
</phrase>
<phrase>
<source> ; point rouge : glisser pour repositionner le centre de rotation</source>
<target>; rood punt: slepen om het draaipunt te verplaatsen</target>
</phrase>
<phrase>
<source> ; point turquoise : arc</source>
<target>; turquoise punt: boog</target>
</phrase>
<phrase>
<source> ; un bord : inclinaison</source>
<target>; een rand: kanteling</target>
</phrase>
<phrase>
<source> °</source>
<target>°</target>
</phrase>
<phrase>
<source> — Cliquer : mode %1</source>
<target>— Klikken: modus %1</target>
</phrase>
<phrase>
<source>, Alt = créer des poignées</source>
<target>, Alt = handgrepen creëren</target>
</phrase>
<phrase>
<source>, Alt = détacher en polyligne</source>
<target>, Alt = losmaken als polylijn</target>
</phrase>
<phrase>
<source>Ajoute une courbe de Bézier sur le folio actuel</source>
<target>Voegt een Béziercurve toe op het huidige blad</target>
</phrase>
<phrase>
<source>Ajouter la borne %1</source>
<target>Klem %1 toevoegen</target>
</phrase>
<phrase>
<source>Ajouter un point à une courbe</source>
<target>Een punt aan een curve toevoegen</target>
</phrase>
<phrase>
<source>Ajouter une courbe</source>
<target>Een curve toevoegen</target>
</phrase>
<phrase>
<source>Angle</source>
<target>Hoek</target>
</phrase>
<phrase>
<source>Anguleux</source>
<target>Hoekig</target>
</phrase>
<phrase>
<source>Aperçu</source>
<target>Voorbeeld</target>
</phrase>
<phrase>
<source>Arrondir les coins d&apos;%1</source>
<target>Hoeken van %1 afronden</target>
</phrase>
<phrase>
<source>Borne 1</source>
<target>Klem 1</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Borne 2</source>
<target>Klem 2</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Catégorie</source>
<target>Categorie</target>
</phrase>
<phrase>
<source>Ce format ne prend pas en charge la transparence : l&apos;image sera enregistrée telle qu&apos;elle était avant l&apos;application de la couleur transparente. Continuer ?</source>
<target>Dit formaat ondersteunt geen transparantie: de afbeelding wordt opgeslagen zoals ze was vóór het toepassen van de transparante kleur. Doorgaan?</target>
</phrase>
<phrase>
<source>Champ de texte</source>
<target>Tekstveld</target>
</phrase>
<phrase>
<source>Clic : positionner à la taille d&apos;origine. Cliquer-glisser : positionner et redimensionner. Clic droit : pivoter de 90°. Ctrl+molette : ajuster la taille.</source>
<target>Klik: op oorspronkelijke grootte plaatsen. Klikken en slepen: plaatsen en formaat wijzigen. Rechtsklik: 90° draaien. Ctrl+muiswiel: formaat aanpassen.</target>
</phrase>
<phrase>
<source>Clic gauche : point suivant ; double-clic ou Entrée : terminer ; clic droit : annuler le dernier point</source>
<target>Linkerklik: volgend punt; dubbelklik of Enter: beëindigen; rechtsklik: laatste punt annuleren</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le coin opposé (Maj = carré, Ctrl = depuis le centre + position libre, Ctrl+Maj = carré centré) ; clic droit : annuler</source>
<target>Linkerklik: tegenoverliggende hoek plaatsen (Shift = vierkant, Ctrl = vanuit het midden + vrije positie, Ctrl+Shift = gecentreerd vierkant); rechtsklik: annuleren</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le coin opposé (Maj = cercle, Ctrl = depuis le centre + position libre, Ctrl+Maj = cercle centré) ; clic droit : annuler</source>
<target>Linkerklik: tegenoverliggende hoek plaatsen (Shift = cirkel, Ctrl = vanuit het midden + vrije positie, Ctrl+Shift = gecentreerde cirkel); rechtsklik: annuleren</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le point de départ (Ctrl = position libre)</source>
<target>Linkerklik: startpunt plaatsen (Ctrl = vrije positie)</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le point final (Ctrl = position libre) ; clic droit : annuler</source>
<target>Linkerklik: eindpunt plaatsen (Ctrl = vrije positie); rechtsklik: annuleren</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le premier coin (Ctrl = point central, position libre)</source>
<target>Linkerklik: eerste hoek plaatsen (Ctrl = middelpunt, vrije positie)</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le premier point (Ctrl = position libre)</source>
<target>Linkerklik: eerste punt plaatsen (Ctrl = vrije positie)</target>
</phrase>
<phrase>
<source>Clic: point anguleux. Cliquer-glisser: point courbe. Clic sur le premier point: fermer. Échap/Entrée: terminer. Clic droit: annuler le dernier point.</source>
<target>Klik: hoekpunt. Klikken en slepen: curvepunt. Klik op het eerste punt: sluiten. Esc/Enter: beëindigen. Rechtsklik: laatste punt annuleren.</target>
</phrase>
<phrase>
<source>Cliquer</source>
<target>Klikken</target>
</phrase>
<phrase>
<source>Cliquer : mode %1</source>
<target>Klikken: modus %1</target>
</phrase>
<phrase>
<source>Cliquez pour choisir une couleur</source>
<target>Klik om een kleur te kiezen</target>
</phrase>
<phrase>
<source>Cliquez sur l&apos;image pour ajouter une couleur. Ajustez la tolérance de chaque couleur avec son curseur, ou cliquez sur × pour la retirer.</source>
<target>Klik op de afbeelding om een kleur toe te voegen. Pas de tolerantie van elke kleur aan met de schuifregelaar, of klik op × om ze te verwijderen.</target>
</phrase>
<phrase>
<source>Cliquez sur l&apos;image pour choisir une couleur</source>
<target>Klik op de afbeelding om een kleur te kiezen</target>
</phrase>
<phrase>
<source>Coins arrondis</source>
<target>Afgeronde hoeken</target>
</phrase>
<phrase>
<source>Composant 1</source>
<target>Component 1</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Composant 2</source>
<target>Component 2</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Convertir %1 en courbe de Bézier</source>
<target>%1 omzetten naar Béziercurve</target>
</phrase>
<phrase>
<source>Convertir %1 en polyligne</source>
<target>%1 omzetten naar polylijn</target>
</phrase>
<phrase>
<source>Convertir en courbe de Bézier</source>
<target>Omzetten naar Béziercurve</target>
</phrase>
<phrase>
<source>Convertir en polyligne</source>
<target>Omzetten naar polylijn</target>
</phrase>
<phrase>
<source>Couleur transparente</source>
<target>Transparante kleur</target>
</phrase>
<phrase>
<source>Couleur transparente...</source>
<target>Transparante kleur...</target>
</phrase>
<phrase>
<source>Courant nominal</source>
<target>Nominale stroom</target>
</phrase>
<phrase>
<source>Deplacer le centre de rotation</source>
<target>Draaipunt verplaatsen</target>
</phrase>
<phrase>
<source>Description</source>
<target>Beschrijving</target>
</phrase>
<phrase>
<source>Distance in pixels between the label and the slave cross reference</source>
<target>Afstand in pixels tussen het label en de slave-kruisverwijzing</target>
</phrase>
<phrase>
<source>Distance label - slave :</source>
<target>Afstand label - slave:</target>
</phrase>
<phrase>
<source>Définir une couleur transparente</source>
<target>Een transparante kleur instellen</target>
</phrase>
<phrase>
<source>Déformer une courbe</source>
<target>Een curve vervormen</target>
</phrase>
<phrase>
<source>Déplacer le centre de rotation d&apos;une image</source>
<target>Het draaipunt van een afbeelding verplaatsen</target>
</phrase>
<phrase>
<source>Déverrouillé : largeur et hauteur peuvent être modifiées indépendamment. Cliquer pour verrouiller.</source>
<target>Ontgrendeld: breedte en hoogte kunnen onafhankelijk worden gewijzigd. Klik om te vergrendelen.</target>
</phrase>
<phrase>
<source>Enregistrer l&apos;image d&apos;origine sous...</source>
<target>Oorspronkelijke afbeelding opslaan als...</target>
</phrase>
<phrase>
<source>Enregistrer l&apos;image sous...</source>
<target>Afbeelding opslaan als...</target>
</phrase>
<phrase>
<source>Erreur</source>
<target>Fout</target>
</phrase>
<phrase>
<source>Exclure de la nomenclature</source>
<target>Uitsluiten van de stuklijst</target>
</phrase>
<phrase>
<source>Faire pivoter %1</source>
<target>%1 roteren</target>
</phrase>
<phrase>
<source>Faire pivoter une image</source>
<target>Een afbeelding draaien</target>
</phrase>
<phrase>
<source>Faites glisser les poignées, ou l&apos;intérieur du cadre, pour ajuster la zone à conserver.</source>
<target>Sleep de handgrepen, of het binnenste van het kader, om het te behouden gebied aan te passen.</target>
</phrase>
<phrase>
<source>Fil</source>
<target>Draad</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Folio</source>
<target>Blad</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Forme fermée</source>
<target>Gesloten vorm</target>
</phrase>
<phrase>
<source>Glisser %1 : rotation (Maj = 15°)</source>
<target>Sleep %1: rotatie (Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser : ajuster l&apos;arc (Ctrl = position libre, Maj = 15°)</source>
<target>Slepen: de boog aanpassen (Ctrl = vrije positie, Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser : arrondir les coins (Ctrl = position libre)</source>
<target>Slepen: hoeken afronden (Ctrl = vrije positie)</target>
</phrase>
<phrase>
<source>Glisser : déformer la courbe (Ctrl = position libre, Alt = briser la tangente)</source>
<target>Slepen: de curve vervormen (Ctrl = vrije positie, Alt = de raaklijn breken)</target>
</phrase>
<phrase>
<source>Glisser : déplacer ce point</source>
<target>Slepen: dit punt verplaatsen</target>
</phrase>
<phrase>
<source>Glisser : déplacer le centre de rotation</source>
<target>Slepen: draaipunt verplaatsen</target>
</phrase>
<phrase>
<source>Glisser : déplacer le point (Ctrl = position libre</source>
<target>Slepen: het punt verplaatsen (Ctrl = vrije positie</target>
</phrase>
<phrase>
<source>Glisser : inclinaison (Ctrl = position libre, Maj = 15°)</source>
<target>Slepen: kanteling (Ctrl = vrije positie, Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser : incliner (Maj = par pas de 15°)</source>
<target>Slepen: kantelen (Shift = stappen van 15°)</target>
</phrase>
<phrase>
<source>Glisser : pivoter (Maj = par pas de 15°)</source>
<target>Slepen: draaien (Shift = stappen van 15°)</target>
</phrase>
<phrase>
<source>Glisser : redimensionner (Ctrl = depuis le centre + position libre, Maj = proportions</source>
<target>Slepen: formaat wijzigen (Ctrl = vanuit het midden + vrije positie, Shift = verhoudingen</target>
</phrase>
<phrase>
<source>Glisser : redimensionner (Maj = conserver les proportions, Ctrl = depuis le centre)</source>
<target>Slepen: formaat wijzigen (Shift = verhoudingen behouden, Ctrl = vanuit het midden)</target>
</phrase>
<phrase>
<source>Glisser : repositionner le centre de rotation (Ctrl = position libre)</source>
<target>Slepen: het draaipunt verplaatsen (Ctrl = vrije positie)</target>
</phrase>
<phrase>
<source>Glisser : rotation (Ctrl = position libre, Maj = 15°)</source>
<target>Slepen: rotatie (Ctrl = vrije positie, Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser le point violet : arrondir les coins</source>
<target>Sleep het paarse punt: hoeken afronden</target>
</phrase>
<phrase>
<source>Glisser un coin : pivoter (Maj = par pas de 15°) ; glisser un bord : incliner (Maj = par pas de 15°) ; point rouge : déplacer le centre de rotation</source>
<target>Sleep een hoek: draaien (Shift = stappen van 15°); sleep een rand: kantelen (Shift = stappen van 15°); rood punt: draaipunt verplaatsen</target>
</phrase>
<phrase>
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = conserver les proportions)</source>
<target>Sleep een hoek/rand: formaat wijzigen (Ctrl = vanuit het midden, Shift = verhoudingen behouden)</target>
</phrase>
<phrase>
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = proportions, Alt = détacher en polyligne)</source>
<target>Sleep een hoek/rand: formaat wijzigen (Ctrl = vanuit het midden, Shift = verhoudingen, Alt = losmaken als polylijn)</target>
</phrase>
<phrase>
<source>Glisser un point : le déplacer</source>
<target>Sleep een punt: het verplaatsen</target>
</phrase>
<phrase>
<source>Glisser une extrémité : la déplacer</source>
<target>Sleep een uiteinde: het verplaatsen</target>
</phrase>
<phrase>
<source>Glisser une poignée ou la courbe : déformer (Alt = briser la tangente) ; Alt+glisser un point anguleux : créer des poignées ; clic droit : menu du nœud le plus proche</source>
<target>Sleep een handgreep of de curve: vervormen (Alt = de raaklijn breken); Alt+sleep een hoekpunt: handgrepen creëren; rechtsklik: menu van het dichtstbijzijnde knooppunt</target>
</phrase>
<phrase>
<source>Géométrie</source>
<target>Geometrie</target>
</phrase>
<phrase>
<source>Hauteur</source>
<target>Hoogte</target>
</phrase>
<phrase>
<source>Image BMP (*.bmp)</source>
<target>BMP-afbeelding (*.bmp)</target>
</phrase>
<phrase>
<source>Image Files (*.png *.jpg *.jpeg *.bmp *.svg)</source>
<target>Afbeeldingsbestanden (*.png *.jpg *.jpeg *.bmp *.svg)</target>
</phrase>
<phrase>
<source>Image JPEG (*.jpg *.jpeg)</source>
<target>JPEG-afbeelding (*.jpg *.jpeg)</target>
</phrase>
<phrase>
<source>Image PNG (*.png)</source>
<target>PNG-afbeelding (*.png)</target>
</phrase>
<phrase>
<source>Image source</source>
<target>Bronafbeelding</target>
</phrase>
<phrase>
<source>Image SVG (*.svg)</source>
<target>SVG-afbeelding (*.svg)</target>
</phrase>
<phrase>
<source>Images non incluses dans l&apos;export DXF</source>
<target>Afbeeldingen niet inbegrepen in de DXF-export</target>
<definition>message box title</definition>
</phrase>
<phrase>
<source>Impossible d&apos;enregistrer l&apos;image à cet emplacement.</source>
<target>Kan de afbeelding niet op die locatie opslaan.</target>
</phrase>
<phrase>
<source>Impossible d&apos;enregistrer la nomenclature dans %1.
%2</source>
<target>Kan de stuklijst niet opslaan in %1.
%2</target>
</phrase>
<phrase>
<source>Impossible de charger l&apos;image.</source>
<target>Kan de afbeelding niet laden.</target>
</phrase>
<phrase>
<source>Inclinaison X</source>
<target>Kanteling X</target>
</phrase>
<phrase>
<source>Inclinaison Y</source>
<target>Kanteling Y</target>
</phrase>
<phrase>
<source>Incliner %1</source>
<target>%1 kantelen</target>
</phrase>
<phrase>
<source>Incliner une image</source>
<target>Een afbeelding kantelen</target>
</phrase>
<phrase>
<source>La limite fixée pour cet élément maître est atteinte (Limite: %1).
Voulez-vous tout de même lier ce contact esclave ?</source>
<target>De limiet voor dit hoofdelement is bereikt (Limiet: %1).
Wilt u dit hulpcontact toch koppelen?</target>
</phrase>
<phrase>
<source>Largeur</source>
<target>Breedte</target>
</phrase>
<phrase>
<source>Le format DXF utilisé ici (AC1006) ne permet pas d&apos;inclure d&apos;image. Les images seront représentées uniquement par un rectangle de contour (position, taille, rotation et inclinaison conservées), sans le contenu de l&apos;image.</source>
<target>Het hier gebruikte DXF-formaat (AC1006) laat niet toe afbeeldingen op te nemen. Afbeeldingen worden enkel voorgesteld door een omtrekrechthoek (positie, grootte, rotatie en kanteling behouden), zonder de inhoud van de afbeelding.</target>
<definition>message box content</definition>
</phrase>
<phrase>
<source>Lisse</source>
<target>Vloeiend</target>
</phrase>
<phrase>
<source>Liste de câblage</source>
<target>Bekabelingslijst</target>
<definition>window title</definition>
</phrase>
<phrase>
<source>Liste de câblage (base de données)</source>
<target>Bekabelingslijst (database)</target>
</phrase>
<phrase>
<source>Localisation :</source>
<target>Locatie:</target>
</phrase>
<phrase>
<source>Longueur</source>
<target>Lengte</target>
</phrase>
<phrase>
<source>margin: 5px; font-weight: bold;</source>
<target>margin: 5px; font-weight: bold;</target>
</phrase>
<phrase>
<source>max:</source>
<target>max:</target>
</phrase>
<phrase>
<source>min:</source>
<target>min:</target>
</phrase>
<phrase>
<source>Miroir horizontal</source>
<target>Horizontaal spiegelen</target>
</phrase>
<phrase>
<source>Miroir horizontal d&apos;une image</source>
<target>Horizontaal spiegelen van een afbeelding</target>
</phrase>
<phrase>
<source>Miroir horizontal de %1</source>
<target>Horizontaal spiegelen van %1</target>
</phrase>
<phrase>
<source>Miroir impossible : inclinaison trop extrême pour cette forme</source>
<target>Spiegelen onmogelijk: kanteling te extreem voor deze vorm</target>
</phrase>
<phrase>
<source>Miroir vertical</source>
<target>Verticaal spiegelen</target>
</phrase>
<phrase>
<source>Miroir vertical d&apos;une image</source>
<target>Verticaal spiegelen van een afbeelding</target>
</phrase>
<phrase>
<source>Miroir vertical de %1</source>
<target>Verticaal spiegelen van %1</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;un arc</source>
<target>Hoek van een boog wijzigen</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;une forme</source>
<target>Hoek van een vorm wijzigen</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;une image</source>
<target>De hoek van een afbeelding wijzigen</target>
</phrase>
<phrase>
<source>Modifier l&apos;inclinaison d&apos;une image</source>
<target>De kanteling van een afbeelding wijzigen</target>
</phrase>
<phrase>
<source>Modifier la courbure d&apos;%1</source>
<target>Kromming van %1 wijzigen</target>
</phrase>
<phrase>
<source>Modifier la forme d&apos;%1</source>
<target>Vorm van %1 wijzigen</target>
</phrase>
<phrase>
<source>Modifier la hauteur d&apos;une image</source>
<target>De hoogte van een afbeelding wijzigen</target>
</phrase>
<phrase>
<source>Modifier la largeur d&apos;une image</source>
<target>De breedte van een afbeelding wijzigen</target>
</phrase>
<phrase>
<source>Modifier la longueur d&apos;une ligne</source>
<target>Lengte van een lijn wijzigen</target>
</phrase>
<phrase>
<source>Modifier la taille d&apos;une forme</source>
<target>Grootte van een vorm wijzigen</target>
</phrase>
<phrase>
<source>Modifier le type d&apos;un nœud</source>
<target>Het type van een knooppunt wijzigen</target>
</phrase>
<phrase>
<source>Modifier une image</source>
<target>Een afbeelding wijzigen</target>
</phrase>
<phrase>
<source>Modèle</source>
<target>Model</target>
</phrase>
<phrase>
<source>Nombre maximum de contacts esclaves définis : non défini
</source>
<target>Maximumaantal gedefinieerde hulpcontacten: niet gedefinieerd
</target>
</phrase>
<phrase>
<source>Notes</source>
<target>Notities</target>
</phrase>
<phrase>
<source>Nœud le plus proche</source>
<target>Dichtstbijzijnde knooppunt</target>
</phrase>
<phrase>
<source>pivoter/incliner</source>
<target>draaien/kantelen</target>
</phrase>
<phrase>
<source>Ponter des bornes entre-elles</source>
<target>Klemmen onderling doorverbinden</target>
</phrase>
<phrase>
<source>Rayon X</source>
<target>Straal X</target>
</phrase>
<phrase>
<source>Rayon Y</source>
<target>Straal Y</target>
</phrase>
<phrase>
<source>redimensionner</source>
<target>formaat wijzigen</target>
</phrase>
<phrase>
<source>Redimensionner %1</source>
<target>%1 vergroten/verkleinen</target>
</phrase>
<phrase>
<source>Redimensionner une image</source>
<target>Formaat van een afbeelding wijzigen</target>
</phrase>
<phrase>
<source>Remplacer l&apos;image...</source>
<target>Afbeelding vervangen...</target>
</phrase>
<phrase>
<source>Remplacer une image</source>
<target>Een afbeelding vervangen</target>
</phrase>
<phrase>
<source>Restaurer les proportions</source>
<target>Verhoudingen herstellen</target>
</phrase>
<phrase>
<source>Restaurer les proportions d&apos;une image</source>
<target>De verhoudingen van een afbeelding herstellen</target>
</phrase>
<phrase>
<source>Retirer cette couleur</source>
<target>Deze kleur verwijderen</target>
</phrase>
<phrase>
<source>Revenir à l&apos;image complète, sans rognage</source>
<target>Terug naar de volledige afbeelding, zonder bijsnijden</target>
</phrase>
<phrase>
<source>rgb(%1, %2, %3)</source>
<target>rgb(%1, %2, %3)</target>
</phrase>
<phrase>
<source>Rogner l&apos;image</source>
<target>Afbeelding bijsnijden</target>
</phrase>
<phrase>
<source>Rogner une image</source>
<target>Een afbeelding bijsnijden</target>
</phrase>
<phrase>
<source>Rogner...</source>
<target>Bijsnijden...</target>
</phrase>
<phrase>
<source>Rotation/Inclinaison</source>
<target>Rotatie/kanteling</target>
</phrase>
<phrase>
<source>Réinitialiser</source>
<target>Herinitialiseren</target>
</phrase>
<phrase>
<source>Selectionner une image...</source>
<target>Een afbeelding selecteren...</target>
</phrase>
<phrase>
<source>Supprimer le nœud le plus proche</source>
<target>Het dichtstbijzijnde knooppunt verwijderen</target>
</phrase>
<phrase>
<source>Supprimer un point d&apos;une courbe</source>
<target>Een punt van een curve verwijderen</target>
</phrase>
<phrase>
<source>Symétrique</source>
<target>Symmetrisch</target>
</phrase>
<phrase>
<source>Séparation de potentiel</source>
<target>Potentiaalscheiding</target>
</phrase>
<phrase>
<source>Taille</source>
<target>Grootte</target>
</phrase>
<phrase>
<source>Tension nominale</source>
<target>Nominale spanning</target>
</phrase>
<phrase>
<source>Tolérance pour cette couleur</source>
<target>Tolerantie voor deze kleur</target>
</phrase>
<phrase>
<source>Tous les fichiers (*)</source>
<target>Alle bestanden (*)</target>
</phrase>
<phrase>
<source>Transformer %1</source>
<target>%1 omvormen</target>
</phrase>
<phrase>
<source>Transparence non conservée</source>
<target>Transparantie niet behouden</target>
</phrase>
<phrase>
<source>un arc</source>
<target>een boog</target>
</phrase>
<phrase>
<source>un coin</source>
<target>een hoek</target>
</phrase>
<phrase>
<source>un point</source>
<target>een punt</target>
</phrase>
<phrase>
<source>une courbe</source>
<target>een curve</target>
</phrase>
<phrase>
<source>une extrémité</source>
<target>een uiteinde</target>
</phrase>
<phrase>
<source>une image</source>
<target>een afbeelding</target>
<definition>part of a sentence listing the content of a diagram</definition>
</phrase>
<phrase>
<source>Verrouiller la numérotation automatique</source>
<target>Automatische nummering vergrendelen</target>
</phrase>
<phrase>
<source>Verrouiller la position</source>
<target>Positie vergrendelen</target>
</phrase>
<phrase>
<source>Verrouillé : modifier la largeur ou la hauteur ajuste l&apos;autre pour conserver les proportions. Cliquer pour déverrouiller.</source>
<target>Vergrendeld: het wijzigen van de breedte of hoogte past de andere aan om de verhoudingen te behouden. Klik om te ontgrendelen.</target>
</phrase>
<phrase>
<source>Échec de l&apos;enregistrement</source>
<target>Opslaan mislukt</target>
</phrase>
<phrase>
<source>Édition des nœuds</source>
<target>Knooppunten bewerken</target>
</phrase>
<phrase>
<source>armoire</source>
<target>kast</target>
<definition>cabinet / enclosure</definition>
</phrase>
<phrase>
<source>bobine</source>
<target>spoel</target>
<definition>coil</definition>
</phrase>
<phrase>
<source>borne</source>
<target>klem</target>
<definition>terminal - a connection point on a symbol</definition>
</phrase>
<phrase>
<source>bornier</source>
<target>klemmenstrook</target>
<definition>terminal strip</definition>
</phrase>
<phrase>
<source>cable</source>
<target>kabel</target>
<definition>cable</definition>
</phrase>
<phrase>
<source>calibre</source>
<target>kaliber</target>
<definition>rating - the current rating of a device</definition>
</phrase>
<phrase>
<source>cartouche</source>
<target>titelblok</target>
<definition>title block - the framed information panel</definition>
</phrase>
<phrase>
<source>collection</source>
<target>collectie</target>
<definition>collection - the symbol library</definition>
</phrase>
<phrase>
<source>conducteur</source>
<target>geleider</target>
<definition>conductor - the drawn wire between terminals</definition>
</phrase>
<phrase>
<source>contact</source>
<target>contact</target>
<definition>contact</definition>
</phrase>
<phrase>
<source>contacteur</source>
<target>contacteur</target>
<definition>contactor</definition>
</phrase>
<phrase>
<source>courant</source>
<target>stroom</target>
<definition>current</definition>
</phrase>
<phrase>
<source>disjoncteur</source>
<target>automaat</target>
<definition>circuit breaker</definition>
</phrase>
<phrase>
<source>dossier</source>
<target>map</target>
<definition>folder</definition>
</phrase>
<phrase>
<source>element</source>
<target>element</target>
<definition>element - a symbol placed on a sheet</definition>
</phrase>
<phrase>
<source>esclave</source>
<target>hulpcontact</target>
<definition>slave - the contact block belonging to a master</definition>
</phrase>
<phrase>
<source>fusible</source>
<target>zekering</target>
<definition>fuse</definition>
</phrase>
<phrase>
<source>grille</source>
<target>raster</target>
<definition>grid</definition>
</phrase>
<phrase>
<source>interrupteur</source>
<target>schakelaar</target>
<definition>switch</definition>
</phrase>
<phrase>
<source>maitre</source>
<target>hoofd</target>
<definition>master - the element a cross-reference points from</definition>
</phrase>
<phrase>
<source>modele</source>
<target>sjabloon</target>
<definition>template</definition>
</phrase>
<phrase>
<source>moteur</source>
<target>motor</target>
<definition>motor</definition>
</phrase>
<phrase>
<source>neutre</source>
<target>nul</target>
<definition>neutral</definition>
</phrase>
<phrase>
<source>nomenclature</source>
<target>stuklijst</target>
<definition>parts list / bill of materials</definition>
</phrase>
<phrase>
<source>numerotation</source>
<target>nummering</target>
<definition>numbering - automatic wire and part numbering</definition>
</phrase>
<phrase>
<source>phase</source>
<target>fase</target>
<definition>phase</definition>
</phrase>
<phrase>
<source>potentiel</source>
<target>potentiaal</target>
<definition>potential - conductors electrically joined as one node</definition>
</phrase>
<phrase>
<source>projet</source>
<target>project</target>
<definition>project - the .qet file holding the sheets</definition>
</phrase>
<phrase>
<source>puissance</source>
<target>vermogen</target>
<definition>power</definition>
</phrase>
<phrase>
<source>relais</source>
<target>relais</target>
<definition>relay</definition>
</phrase>
<phrase>
<source>renvoi de folio</source>
<target>bladverwijzing</target>
<definition>sheet cross-reference</definition>
</phrase>
<phrase>
<source>repere</source>
<target>aanduiding</target>
<definition>designation - the identifying mark on a part</definition>
</phrase>
<phrase>
<source>report de folio</source>
<target>bladverwijzing</target>
<definition>sheet cross-reference (the arrow symbol)</definition>
</phrase>
<phrase>
<source>schema</source>
<target>schema</target>
<definition>diagram</definition>
</phrase>
<phrase>
<source>section</source>
<target>doorsnede</target>
<definition>cross-section - conductor size in mm2</definition>
</phrase>
<phrase>
<source>sommaire</source>
<target>inhoudstafel</target>
<definition>summary / table of contents</definition>
</phrase>
<phrase>
<source>tension</source>
<target>spanning</target>
<definition>voltage</definition>
</phrase>
<phrase>
<source>terre</source>
<target>aarde</target>
<definition>earth / ground</definition>
</phrase>
<phrase>
<source>texte</source>
<target>tekst</target>
<definition>text</definition>
</phrase>
<phrase>
<source>transformateur</source>
<target>transformator</target>
<definition>transformer</definition>
</phrase>
</QPH>
+218
View File
@@ -0,0 +1,218 @@
<!DOCTYPE QPH>
<QPH sourcelanguage="fr_FR" language="pl">
<phrase>
<source>armoire</source>
<target>Szafa</target>
<definition>cabinet / enclosure</definition>
</phrase>
<phrase>
<source>bobine</source>
<target>Cewka</target>
<definition>coil</definition>
</phrase>
<phrase>
<source>borne</source>
<target>Zacisk</target>
<definition>terminal - a connection point on a symbol</definition>
</phrase>
<phrase>
<source>bornier</source>
<target>Listwa zaciskowa</target>
<definition>terminal strip</definition>
</phrase>
<phrase>
<source>cable</source>
<target>Kabel</target>
<definition>cable</definition>
</phrase>
<phrase>
<source>calibre</source>
<target>Prąd znamionowy</target>
<definition>rating - the current rating of a device</definition>
</phrase>
<phrase>
<source>cartouche</source>
<target>Tabliczka rysunkowa</target>
<definition>title block - the framed information panel</definition>
</phrase>
<phrase>
<source>champ de texte</source>
<target>Pole tekstowe</target>
<definition>text field</definition>
</phrase>
<phrase>
<source>collection</source>
<target>Biblioteka</target>
<definition>collection - the symbol library</definition>
</phrase>
<phrase>
<source>conducteur</source>
<target>Przewód</target>
<definition>conductor - the drawn wire between terminals</definition>
</phrase>
<phrase>
<source>contact</source>
<target>Styk</target>
<definition>contact</definition>
</phrase>
<phrase>
<source>contacteur</source>
<target>Stycznik</target>
<definition>contactor</definition>
</phrase>
<phrase>
<source>courant</source>
<target>Prąd</target>
<definition>current</definition>
</phrase>
<phrase>
<source>disjoncteur</source>
<target>Wyłącznik nadprądowy</target>
<definition>circuit breaker</definition>
</phrase>
<phrase>
<source>dossier</source>
<target>Folder</target>
<definition>folder</definition>
</phrase>
<phrase>
<source>element</source>
<target>Element</target>
<definition>element - a symbol placed on a sheet</definition>
</phrase>
<phrase>
<source>esclave</source>
<target>Element podrzędny</target>
<definition>slave - the contact block belonging to a master</definition>
</phrase>
<phrase>
<source>fil</source>
<target>Przewód</target>
<definition>wire</definition>
</phrase>
<phrase>
<source>folio</source>
<target>Arkusz</target>
<definition>sheet - one drawing of a set</definition>
</phrase>
<phrase>
<source>fusible</source>
<target>Bezpiecznik</target>
<definition>fuse</definition>
</phrase>
<phrase>
<source>grille</source>
<target>Siatka</target>
<definition>grid</definition>
</phrase>
<phrase>
<source>interrupteur</source>
<target>Wyłącznik</target>
<definition>switch</definition>
</phrase>
<phrase>
<source>maitre</source>
<target>Element główny</target>
<definition>master - the element a cross-reference points from</definition>
</phrase>
<phrase>
<source>modele</source>
<target>Szablon</target>
<definition>template</definition>
</phrase>
<phrase>
<source>moteur</source>
<target>Silnik</target>
<definition>motor</definition>
</phrase>
<phrase>
<source>neutre</source>
<target>Neutralny</target>
<definition>neutral</definition>
</phrase>
<phrase>
<source>nomenclature</source>
<target>Zestawienie materiałów</target>
<definition>parts list / bill of materials</definition>
</phrase>
<phrase>
<source>numerotation</source>
<target>Numeracja</target>
<definition>numbering - automatic wire and part numbering</definition>
</phrase>
<phrase>
<source>phase</source>
<target>Faza</target>
<definition>phase</definition>
</phrase>
<phrase>
<source>potentiel</source>
<target>Potencjał</target>
<definition>potential - conductors electrically joined as one node</definition>
</phrase>
<phrase>
<source>projet</source>
<target>Projekt</target>
<definition>project - the .qet file holding the sheets</definition>
</phrase>
<phrase>
<source>puissance</source>
<target>Moc</target>
<definition>power</definition>
</phrase>
<phrase>
<source>relais</source>
<target>Przekaźnik</target>
<definition>relay</definition>
</phrase>
<phrase>
<source>renvoi de folio</source>
<target>Odnośnik do arkusza</target>
<definition>sheet cross-reference</definition>
</phrase>
<phrase>
<source>repere</source>
<target>Oznaczenie</target>
<definition>designation - the identifying mark on a part</definition>
</phrase>
<phrase>
<source>report de folio</source>
<target>Odnośnik do arkusza</target>
<definition>sheet cross-reference (the arrow symbol)</definition>
</phrase>
<phrase>
<source>schema</source>
<target>Schemat</target>
<definition>diagram</definition>
</phrase>
<phrase>
<source>section</source>
<target>Przekrój</target>
<definition>cross-section - conductor size in mm2</definition>
</phrase>
<phrase>
<source>sommaire</source>
<target>Spis treści</target>
<definition>summary / table of contents</definition>
</phrase>
<phrase>
<source>tension</source>
<target>Napięcie</target>
<definition>voltage</definition>
</phrase>
<phrase>
<source>terre</source>
<target>Uziemienie</target>
<definition>earth / ground</definition>
</phrase>
<phrase>
<source>texte</source>
<target>Tekst</target>
<definition>text</definition>
</phrase>
<phrase>
<source>transformateur</source>
<target>Transformator</target>
<definition>transformer</definition>
</phrase>
</QPH>
File diff suppressed because it is too large Load Diff
+465
View File
@@ -0,0 +1,465 @@
<!DOCTYPE QPH>
<QPH sourcelanguage="fr_FR" language="pt_BR">
<phrase>
<source> Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4
</source>
<target>Contatos: NA: %1, NF: %2, reversíveis: %3, outros: %4
</target>
</phrase>
<phrase>
<source> Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8
</source>
<target>Contatos: NA: %1/%2, NF: %3/%4, reversíveis: %5/%6, outros: %7/%8
</target>
</phrase>
<phrase>
<source>Aperçu</source>
<target>Pré-visualização</target>
</phrase>
<phrase>
<source>Arrondir les coins d&apos;%1</source>
<target>Arredondar os cantos de %1</target>
</phrase>
<phrase>
<source>Borne 1</source>
<target>Borne 1</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Borne 2</source>
<target>Borne 2</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Catégorie</source>
<target>Categoria</target>
</phrase>
<phrase>
<source>Ce format ne prend pas en charge la transparence : l&apos;image sera enregistrée telle qu&apos;elle était avant l&apos;application de la couleur transparente. Continuer ?</source>
<target>Este formato não suporta transparência: a imagem será salva como estava antes da aplicação da cor transparente. Continuar?</target>
</phrase>
<phrase>
<source>Champ de texte</source>
<target>Campo de texto</target>
</phrase>
<phrase>
<source>Composant 1</source>
<target>Componente 1</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Composant 2</source>
<target>Componente 2</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Convertir %1 en courbe de Bézier</source>
<target>Converter %1 em curva de Bézier</target>
</phrase>
<phrase>
<source>Convertir en courbe de Bézier</source>
<target>Converter em curva de Bézier</target>
</phrase>
<phrase>
<source>Courant nominal</source>
<target>Corrente nominal</target>
</phrase>
<phrase>
<source>Distance in pixels between the label and the slave cross reference</source>
<target>Distância em pixels entre o rótulo e a referência cruzada do escravo</target>
</phrase>
<phrase>
<source>Distance label - slave :</source>
<target>Distância do rótulo - escravo:</target>
</phrase>
<phrase>
<source>Enregistrer l&apos;image d&apos;origine sous...</source>
<target>Salvar a imagem original como...</target>
</phrase>
<phrase>
<source>Enregistrer l&apos;image sous...</source>
<target>Salvar a imagem como...</target>
</phrase>
<phrase>
<source>Erreur</source>
<target>Erro</target>
</phrase>
<phrase>
<source>Exclure de la nomenclature</source>
<target>Excluir da lista de materiais</target>
</phrase>
<phrase>
<source>Faire pivoter %1</source>
<target>Girar %1</target>
</phrase>
<phrase>
<source>Faire pivoter une image</source>
<target>Girar uma imagem</target>
</phrase>
<phrase>
<source>Fil</source>
<target>Fio</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Folio</source>
<target>Folha</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Hauteur</source>
<target>Altura</target>
</phrase>
<phrase>
<source>Image BMP (*.bmp)</source>
<target>Imagem BMP (*.bmp)</target>
</phrase>
<phrase>
<source>Image JPEG (*.jpg *.jpeg)</source>
<target>Imagem JPEG (*.jpg *.jpeg)</target>
</phrase>
<phrase>
<source>Image PNG (*.png)</source>
<target>Imagem PNG (*.png)</target>
</phrase>
<phrase>
<source>Image SVG (*.svg)</source>
<target>Imagem SVG (*.svg)</target>
</phrase>
<phrase>
<source>Images non incluses dans l&apos;export DXF</source>
<target>Imagens não incluídas na exportação DXF</target>
<definition>message box title</definition>
</phrase>
<phrase>
<source>Impossible d&apos;enregistrer l&apos;image à cet emplacement.</source>
<target>Não foi possível salvar a imagem neste local.</target>
</phrase>
<phrase>
<source>Impossible d&apos;enregistrer la nomenclature dans %1.
%2</source>
<target>Não foi possível salvar a lista de materiais em %1.
%2</target>
</phrase>
<phrase>
<source>Impossible de charger l&apos;image.</source>
<target>Não foi possível carregar a imagem.</target>
</phrase>
<phrase>
<source>Incliner %1</source>
<target>Inclinar %1</target>
</phrase>
<phrase>
<source>Incliner une image</source>
<target>Inclinar uma imagem</target>
</phrase>
<phrase>
<source>La limite fixée pour cet élément maître est atteinte (Limite: %1).
Voulez-vous tout de même lier ce contact esclave ?</source>
<target>O limite definido para este elemento mestre foi atingido (Limite: %1).
Deseja vincular este contato escravo mesmo assim?</target>
</phrase>
<phrase>
<source>Largeur</source>
<target>Largura</target>
</phrase>
<phrase>
<source>Le format DXF utilisé ici (AC1006) ne permet pas d&apos;inclure d&apos;image. Les images seront représentées uniquement par un rectangle de contour (position, taille, rotation et inclinaison conservées), sans le contenu de l&apos;image.</source>
<target>O formato DXF usado aqui (AC1006) não permite incluir imagens. As imagens serão representadas apenas por um retângulo de contorno (posição, tamanho, rotação e inclinação preservados), sem o conteúdo da imagem.</target>
<definition>message box content</definition>
</phrase>
<phrase>
<source>Liste de câblage</source>
<target>Lista de cabeamento</target>
<definition>window title</definition>
</phrase>
<phrase>
<source>Liste de câblage (base de données)</source>
<target>Lista de cabeamento (banco de dados)</target>
</phrase>
<phrase>
<source>margin: 5px; font-weight: bold;</source>
<target>margin: 5px; font-weight: bold;</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;un arc</source>
<target>Modificar o ângulo de um arco</target>
</phrase>
<phrase>
<source>Modifier la courbure d&apos;%1</source>
<target>Modificar a curvatura de %1</target>
</phrase>
<phrase>
<source>Modifier la forme d&apos;%1</source>
<target>Modificar a forma de %1</target>
</phrase>
<phrase>
<source>Modèle</source>
<target>Modelo</target>
</phrase>
<phrase>
<source>Nombre maximum de contacts esclaves définis : non défini
</source>
<target>Número máximo de contatos escravos definidos: não definido
</target>
</phrase>
<phrase>
<source>Notes</source>
<target>Observações</target>
</phrase>
<phrase>
<source>Redimensionner %1</source>
<target>Redimensionar %1</target>
</phrase>
<phrase>
<source>Redimensionner une image</source>
<target>Redimensionar uma imagem</target>
</phrase>
<phrase>
<source>Réinitialiser</source>
<target>Redefinir</target>
</phrase>
<phrase>
<source>Selectionner une image...</source>
<target>Selecionar uma imagem...</target>
</phrase>
<phrase>
<source>Séparation de potentiel</source>
<target>Separação de potencial</target>
</phrase>
<phrase>
<source>Taille</source>
<target>Tamanho</target>
</phrase>
<phrase>
<source>Tension nominale</source>
<target>Tensão nominal</target>
</phrase>
<phrase>
<source>Tous les fichiers (*)</source>
<target>Todos os arquivos (*)</target>
</phrase>
<phrase>
<source>Transparence non conservée</source>
<target>Transparência não preservada</target>
</phrase>
<phrase>
<source>une image</source>
<target>uma imagem</target>
<definition>part of a sentence listing the content of a diagram</definition>
</phrase>
<phrase>
<source>Verrouiller la numérotation automatique</source>
<target>Bloquear a numeração automática</target>
</phrase>
<phrase>
<source>Verrouiller la position</source>
<target>Bloquear a posição</target>
</phrase>
<phrase>
<source>Échec de l&apos;enregistrement</source>
<target>Falha ao salvar</target>
</phrase>
<phrase>
<source>armoire</source>
<target>armário</target>
<definition>cabinet / enclosure</definition>
</phrase>
<phrase>
<source>bobine</source>
<target>bobina</target>
<definition>coil</definition>
</phrase>
<phrase>
<source>borne</source>
<target>borne</target>
<definition>terminal - a connection point on a symbol</definition>
</phrase>
<phrase>
<source>bornier</source>
<target>borneira</target>
<definition>terminal strip</definition>
</phrase>
<phrase>
<source>cable</source>
<target>cabo</target>
<definition>cable</definition>
</phrase>
<phrase>
<source>calibre</source>
<target>calibre</target>
<definition>rating - the current rating of a device</definition>
</phrase>
<phrase>
<source>cartouche</source>
<target>bloco de título</target>
<definition>title block - the framed information panel</definition>
</phrase>
<phrase>
<source>collection</source>
<target>coleção</target>
<definition>collection - the symbol library</definition>
</phrase>
<phrase>
<source>conducteur</source>
<target>condutor</target>
<definition>conductor - the drawn wire between terminals</definition>
</phrase>
<phrase>
<source>contact</source>
<target>contato</target>
<definition>contact</definition>
</phrase>
<phrase>
<source>contacteur</source>
<target>contator</target>
<definition>contactor</definition>
</phrase>
<phrase>
<source>courant</source>
<target>corrente</target>
<definition>current</definition>
</phrase>
<phrase>
<source>disjoncteur</source>
<target>disjuntor</target>
<definition>circuit breaker</definition>
</phrase>
<phrase>
<source>dossier</source>
<target>pasta</target>
<definition>folder</definition>
</phrase>
<phrase>
<source>element</source>
<target>elemento</target>
<definition>element - a symbol placed on a sheet</definition>
</phrase>
<phrase>
<source>esclave</source>
<target>escravo</target>
<definition>slave - the contact block belonging to a master</definition>
</phrase>
<phrase>
<source>fusible</source>
<target>fusível</target>
<definition>fuse</definition>
</phrase>
<phrase>
<source>grille</source>
<target>grade</target>
<definition>grid</definition>
</phrase>
<phrase>
<source>interrupteur</source>
<target>interruptor</target>
<definition>switch</definition>
</phrase>
<phrase>
<source>maitre</source>
<target>mestre</target>
<definition>master - the element a cross-reference points from</definition>
</phrase>
<phrase>
<source>modele</source>
<target>modelo</target>
<definition>template</definition>
</phrase>
<phrase>
<source>moteur</source>
<target>motor</target>
<definition>motor</definition>
</phrase>
<phrase>
<source>neutre</source>
<target>neutro</target>
<definition>neutral</definition>
</phrase>
<phrase>
<source>nomenclature</source>
<target>lista de materiais</target>
<definition>parts list / bill of materials</definition>
</phrase>
<phrase>
<source>numerotation</source>
<target>numeração</target>
<definition>numbering - automatic wire and part numbering</definition>
</phrase>
<phrase>
<source>phase</source>
<target>fase</target>
<definition>phase</definition>
</phrase>
<phrase>
<source>potentiel</source>
<target>potencial</target>
<definition>potential - conductors electrically joined as one node</definition>
</phrase>
<phrase>
<source>projet</source>
<target>projeto</target>
<definition>project - the .qet file holding the sheets</definition>
</phrase>
<phrase>
<source>puissance</source>
<target>potência</target>
<definition>power</definition>
</phrase>
<phrase>
<source>relais</source>
<target>relé</target>
<definition>relay</definition>
</phrase>
<phrase>
<source>renvoi de folio</source>
<target>referência de folha</target>
<definition>sheet cross-reference</definition>
</phrase>
<phrase>
<source>repere</source>
<target>designação</target>
<definition>designation - the identifying mark on a part</definition>
</phrase>
<phrase>
<source>report de folio</source>
<target>remissão de folha</target>
<definition>sheet cross-reference (the arrow symbol)</definition>
</phrase>
<phrase>
<source>schema</source>
<target>diagrama</target>
<definition>diagram</definition>
</phrase>
<phrase>
<source>section</source>
<target>seção</target>
<definition>cross-section - conductor size in mm2</definition>
</phrase>
<phrase>
<source>sommaire</source>
<target>sumário</target>
<definition>summary / table of contents</definition>
</phrase>
<phrase>
<source>tension</source>
<target>tensão</target>
<definition>voltage</definition>
</phrase>
<phrase>
<source>terre</source>
<target>terra</target>
<definition>earth / ground</definition>
</phrase>
<phrase>
<source>texte</source>
<target>texto</target>
<definition>text</definition>
</phrase>
<phrase>
<source>transformateur</source>
<target>transformador</target>
<definition>transformer</definition>
</phrase>
</QPH>
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+917
View File
@@ -0,0 +1,917 @@
<!DOCTYPE QPH>
<QPH sourcelanguage="fr_FR" language="zh">
<phrase>
<source> Contacts : NO : %1, NC : %2, inverseurs : %3, autres : %4
</source>
<target>触点:常开:%1,常闭:%2,转换:%3,其他:%4
</target>
</phrase>
<phrase>
<source> Contacts : NO : %1/%2, NC : %3/%4, inverseurs : %5/%6, autres : %7/%8
</source>
<target>触点:常开:%1/%2,常闭:%3/%4,转换:%5/%6,其他:%7/%8
</target>
</phrase>
<phrase>
<source> %</source>
<target>%</target>
</phrase>
<phrase>
<source> (Ctrl pendant le glissement = position libre, sans accrochage à la grille)</source>
<target>(拖动时按 Ctrl = 自由位置,不吸附到网格)</target>
</phrase>
<phrase>
<source> -- %1 : mode %2</source>
<target>-- %1:%2 模式</target>
</phrase>
<phrase>
<source> ; point rouge : glisser pour repositionner le centre de rotation</source>
<target>;红点:拖动以重新定位旋转中心</target>
</phrase>
<phrase>
<source> ; point turquoise : arc</source>
<target>;青色点:圆弧</target>
</phrase>
<phrase>
<source> ; un bord : inclinaison</source>
<target>;边:倾斜</target>
</phrase>
<phrase>
<source> °</source>
<target>°</target>
</phrase>
<phrase>
<source> — Cliquer : mode %1</source>
<target>— 单击:%1 模式</target>
</phrase>
<phrase>
<source>, Alt = créer des poignées</source>
<target>,Alt = 创建控制柄</target>
</phrase>
<phrase>
<source>, Alt = détacher en polyligne</source>
<target>,Alt = 分离为多段线</target>
</phrase>
<phrase>
<source>Ajoute une courbe de Bézier sur le folio actuel</source>
<target>在当前图纸上添加贝塞尔曲线</target>
</phrase>
<phrase>
<source>Ajouter un point à une courbe</source>
<target>向曲线添加点</target>
</phrase>
<phrase>
<source>Ajouter une courbe</source>
<target>添加曲线</target>
</phrase>
<phrase>
<source>Angle</source>
<target>角度</target>
</phrase>
<phrase>
<source>Anguleux</source>
<target>角点</target>
</phrase>
<phrase>
<source>Aperçu</source>
<target>预览</target>
</phrase>
<phrase>
<source>Arrondir les coins d&apos;%1</source>
<target>将%1的角变圆</target>
</phrase>
<phrase>
<source>Borne 1</source>
<target>端子1</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Borne 2</source>
<target>端子2</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Catégorie</source>
<target>类别</target>
</phrase>
<phrase>
<source>Ce format ne prend pas en charge la transparence : l&apos;image sera enregistrée telle qu&apos;elle était avant l&apos;application de la couleur transparente. Continuer ?</source>
<target>此格式不支持透明度:图像将按应用透明色之前的状态保存。是否继续?</target>
</phrase>
<phrase>
<source>Champ de texte</source>
<target>文本字段</target>
</phrase>
<phrase>
<source>Clic : positionner à la taille d&apos;origine. Cliquer-glisser : positionner et redimensionner. Clic droit : pivoter de 90°. Ctrl+molette : ajuster la taille.</source>
<target>单击:按原始尺寸放置。单击拖动:放置并调整大小。右键:旋转 90°。Ctrl+滚轮:调整大小。</target>
</phrase>
<phrase>
<source>Clic gauche : point suivant ; double-clic ou Entrée : terminer ; clic droit : annuler le dernier point</source>
<target>左键单击:下一个点;双击或 Enter:结束;右键单击:撤销上一个点</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le coin opposé (Maj = carré, Ctrl = depuis le centre + position libre, Ctrl+Maj = carré centré) ; clic droit : annuler</source>
<target>左键单击:放置对角(Shift = 正方形,Ctrl = 从中心 + 自由位置,Ctrl+Shift = 居中正方形);右键单击:取消</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le coin opposé (Maj = cercle, Ctrl = depuis le centre + position libre, Ctrl+Maj = cercle centré) ; clic droit : annuler</source>
<target>左键单击:放置对角(Shift = 圆形,Ctrl = 从中心 + 自由位置,Ctrl+Shift = 居中圆形);右键单击:取消</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le point de départ (Ctrl = position libre)</source>
<target>左键单击:放置起点(Ctrl = 自由位置)</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le point final (Ctrl = position libre) ; clic droit : annuler</source>
<target>左键单击:放置终点(Ctrl = 自由位置);右键单击:取消</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le premier coin (Ctrl = point central, position libre)</source>
<target>左键单击:放置第一个角(Ctrl = 中心点,自由位置)</target>
</phrase>
<phrase>
<source>Clic gauche : positionner le premier point (Ctrl = position libre)</source>
<target>左键单击:放置第一个点(Ctrl = 自由位置)</target>
</phrase>
<phrase>
<source>Clic: point anguleux. Cliquer-glisser: point courbe. Clic sur le premier point: fermer. Échap/Entrée: terminer. Clic droit: annuler le dernier point.</source>
<target>单击:角点。单击拖动:曲线点。单击第一个点:闭合。Esc/Enter:结束。右键:撤销上一个点。</target>
</phrase>
<phrase>
<source>Cliquer</source>
<target>单击</target>
</phrase>
<phrase>
<source>Cliquer : mode %1</source>
<target>单击:%1 模式</target>
</phrase>
<phrase>
<source>Cliquez pour choisir une couleur</source>
<target>点击选择颜色</target>
</phrase>
<phrase>
<source>Cliquez sur l&apos;image pour ajouter une couleur. Ajustez la tolérance de chaque couleur avec son curseur, ou cliquez sur × pour la retirer.</source>
<target>单击图像以添加颜色。使用滑块调整每种颜色的容差,或单击 × 将其移除。</target>
</phrase>
<phrase>
<source>Cliquez sur l&apos;image pour choisir une couleur</source>
<target>单击图像选择颜色</target>
</phrase>
<phrase>
<source>Coins arrondis</source>
<target>圆角</target>
</phrase>
<phrase>
<source>Composant 1</source>
<target>元件1</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Composant 2</source>
<target>元件2</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Convertir %1 en courbe de Bézier</source>
<target>将 %1 转换为贝塞尔曲线</target>
</phrase>
<phrase>
<source>Convertir %1 en polyligne</source>
<target>将 %1 转换为多段线</target>
</phrase>
<phrase>
<source>Convertir en courbe de Bézier</source>
<target>转换为贝塞尔曲线</target>
</phrase>
<phrase>
<source>Convertir en polyligne</source>
<target>转换为多段线</target>
</phrase>
<phrase>
<source>Couleur transparente</source>
<target>透明色</target>
</phrase>
<phrase>
<source>Couleur transparente...</source>
<target>透明色...</target>
</phrase>
<phrase>
<source>Courant nominal</source>
<target>额定电流</target>
</phrase>
<phrase>
<source>Deplacer le centre de rotation</source>
<target>移动旋转中心</target>
</phrase>
<phrase>
<source>Distance in pixels between the label and the slave cross reference</source>
<target>标签与从站交叉引用之间的像素距离</target>
</phrase>
<phrase>
<source>Distance label - slave :</source>
<target>标签距离 - 从站:</target>
</phrase>
<phrase>
<source>Définir une couleur transparente</source>
<target>设置透明色</target>
</phrase>
<phrase>
<source>Déformer une courbe</source>
<target>变形曲线</target>
</phrase>
<phrase>
<source>Déplacer le centre de rotation d&apos;une image</source>
<target>移动图像的旋转中心</target>
</phrase>
<phrase>
<source>Déverrouillé : largeur et hauteur peuvent être modifiées indépendamment. Cliquer pour verrouiller.</source>
<target>已解锁:宽度和高度可独立修改。单击锁定。</target>
</phrase>
<phrase>
<source>Enregistrer l&apos;image d&apos;origine sous...</source>
<target>原始图像另存为...</target>
</phrase>
<phrase>
<source>Enregistrer l&apos;image sous...</source>
<target>图像另存为...</target>
</phrase>
<phrase>
<source>Erreur</source>
<target>错误</target>
</phrase>
<phrase>
<source>Exclure de la nomenclature</source>
<target>从物料清单中排除</target>
</phrase>
<phrase>
<source>Faire pivoter %1</source>
<target>旋转 %1</target>
</phrase>
<phrase>
<source>Faire pivoter une image</source>
<target>旋转图像</target>
</phrase>
<phrase>
<source>Faites glisser les poignées, ou l&apos;intérieur du cadre, pour ajuster la zone à conserver.</source>
<target>拖动控制柄或框内区域,调整要保留的区域。</target>
</phrase>
<phrase>
<source>Fil</source>
<target>导线</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Folio</source>
<target>图页</target>
<definition>column title</definition>
</phrase>
<phrase>
<source>Forme fermée</source>
<target>闭合形状</target>
</phrase>
<phrase>
<source>Glisser %1 : rotation (Maj = 15°)</source>
<target>拖动 %1:旋转(Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser : ajuster l&apos;arc (Ctrl = position libre, Maj = 15°)</source>
<target>拖动:调整圆弧(Ctrl = 自由位置,Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser : arrondir les coins (Ctrl = position libre)</source>
<target>拖动:圆角(Ctrl = 自由位置)</target>
</phrase>
<phrase>
<source>Glisser : déformer la courbe (Ctrl = position libre, Alt = briser la tangente)</source>
<target>拖动:变形曲线(Ctrl = 自由位置,Alt = 断开切线)</target>
</phrase>
<phrase>
<source>Glisser : déplacer ce point</source>
<target>拖动:移动此点</target>
</phrase>
<phrase>
<source>Glisser : déplacer le centre de rotation</source>
<target>拖动:移动旋转中心</target>
</phrase>
<phrase>
<source>Glisser : déplacer le point (Ctrl = position libre</source>
<target>拖动:移动点(Ctrl = 自由位置</target>
</phrase>
<phrase>
<source>Glisser : inclinaison (Ctrl = position libre, Maj = 15°)</source>
<target>拖动:倾斜(Ctrl = 自由位置,Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser : incliner (Maj = par pas de 15°)</source>
<target>拖动:倾斜(Shift = 以 15° 为步长)</target>
</phrase>
<phrase>
<source>Glisser : pivoter (Maj = par pas de 15°)</source>
<target>拖动:旋转(Shift = 以 15° 为步长)</target>
</phrase>
<phrase>
<source>Glisser : redimensionner (Ctrl = depuis le centre + position libre, Maj = proportions</source>
<target>拖动:调整大小(Ctrl = 从中心 + 自由位置,Shift = 比例</target>
</phrase>
<phrase>
<source>Glisser : redimensionner (Maj = conserver les proportions, Ctrl = depuis le centre)</source>
<target>拖动:调整大小(Shift = 保持比例,Ctrl = 从中心)</target>
</phrase>
<phrase>
<source>Glisser : repositionner le centre de rotation (Ctrl = position libre)</source>
<target>拖动:重新定位旋转中心(Ctrl = 自由位置)</target>
</phrase>
<phrase>
<source>Glisser : rotation (Ctrl = position libre, Maj = 15°)</source>
<target>拖动:旋转(Ctrl = 自由位置,Shift = 15°)</target>
</phrase>
<phrase>
<source>Glisser le point violet : arrondir les coins</source>
<target>拖动紫色点:圆角</target>
</phrase>
<phrase>
<source>Glisser un coin : pivoter (Maj = par pas de 15°) ; glisser un bord : incliner (Maj = par pas de 15°) ; point rouge : déplacer le centre de rotation</source>
<target>拖动角:旋转(Shift = 以 15° 为步长);拖动边:倾斜(Shift = 以 15° 为步长);红点:移动旋转中心</target>
</phrase>
<phrase>
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = conserver les proportions)</source>
<target>拖动角/边:调整大小(Ctrl = 从中心,Shift = 保持比例)</target>
</phrase>
<phrase>
<source>Glisser un coin/bord : redimensionner (Ctrl = depuis le centre, Maj = proportions, Alt = détacher en polyligne)</source>
<target>拖动角/边:调整大小(Ctrl = 从中心,Shift = 比例,Alt = 分离为多段线)</target>
</phrase>
<phrase>
<source>Glisser un point : le déplacer</source>
<target>拖动点:移动它</target>
</phrase>
<phrase>
<source>Glisser une extrémité : la déplacer</source>
<target>拖动端点:移动它</target>
</phrase>
<phrase>
<source>Glisser une poignée ou la courbe : déformer (Alt = briser la tangente) ; Alt+glisser un point anguleux : créer des poignées ; clic droit : menu du nœud le plus proche</source>
<target>拖动控制柄或曲线:变形(Alt = 断开切线);Alt+拖动角点:创建控制柄;右键单击:最近节点菜单</target>
</phrase>
<phrase>
<source>Géométrie</source>
<target>几何</target>
</phrase>
<phrase>
<source>Hauteur</source>
<target>高度</target>
</phrase>
<phrase>
<source>Image BMP (*.bmp)</source>
<target>BMP 图像 (*.bmp)</target>
</phrase>
<phrase>
<source>Image Files (*.png *.jpg *.jpeg *.bmp *.svg)</source>
<target>图像文件 (*.png *.jpg *.jpeg *.bmp *.svg)</target>
</phrase>
<phrase>
<source>Image JPEG (*.jpg *.jpeg)</source>
<target>JPEG 图像 (*.jpg *.jpeg)</target>
</phrase>
<phrase>
<source>Image PNG (*.png)</source>
<target>PNG 图像 (*.png)</target>
</phrase>
<phrase>
<source>Image source</source>
<target>源图像</target>
</phrase>
<phrase>
<source>Image SVG (*.svg)</source>
<target>SVG 图像 (*.svg)</target>
</phrase>
<phrase>
<source>Images non incluses dans l&apos;export DXF</source>
<target>图像未包含在 DXF 导出中</target>
<definition>message box title</definition>
</phrase>
<phrase>
<source>Impossible d&apos;enregistrer l&apos;image à cet emplacement.</source>
<target>无法将图像保存到该位置。</target>
</phrase>
<phrase>
<source>Impossible d&apos;enregistrer la nomenclature dans %1.
%2</source>
<target>无法将物料清单保存到 %1。
%2</target>
</phrase>
<phrase>
<source>Impossible de charger l&apos;image.</source>
<target>无法加载图像。</target>
</phrase>
<phrase>
<source>Inclinaison X</source>
<target>X 倾斜</target>
</phrase>
<phrase>
<source>Inclinaison Y</source>
<target>Y 倾斜</target>
</phrase>
<phrase>
<source>Incliner %1</source>
<target>倾斜 %1</target>
</phrase>
<phrase>
<source>Incliner une image</source>
<target>倾斜图像</target>
</phrase>
<phrase>
<source>La limite fixée pour cet élément maître est atteinte (Limite: %1).
Voulez-vous tout de même lier ce contact esclave ?</source>
<target>已达到此主元件的限制(限制:%1)。
是否仍要链接此从属触点?</target>
</phrase>
<phrase>
<source>Largeur</source>
<target>宽度</target>
</phrase>
<phrase>
<source>Le format DXF utilisé ici (AC1006) ne permet pas d&apos;inclure d&apos;image. Les images seront représentées uniquement par un rectangle de contour (position, taille, rotation et inclinaison conservées), sans le contenu de l&apos;image.</source>
<target>此处使用的 DXF 格式 (AC1006) 无法包含图像。图像将仅以轮廓矩形表示(保留位置、大小、旋转和倾斜),不包含图像内容。</target>
<definition>message box content</definition>
</phrase>
<phrase>
<source>Lisse</source>
<target>平滑</target>
</phrase>
<phrase>
<source>Liste de câblage</source>
<target>接线列表</target>
<definition>window title</definition>
</phrase>
<phrase>
<source>Liste de câblage (base de données)</source>
<target>接线列表(数据库)</target>
</phrase>
<phrase>
<source>Longueur</source>
<target>长度</target>
</phrase>
<phrase>
<source>margin: 5px; font-weight: bold;</source>
<target>margin: 5px; font-weight: bold;</target>
</phrase>
<phrase>
<source>Miroir horizontal</source>
<target>水平镜像</target>
</phrase>
<phrase>
<source>Miroir horizontal d&apos;une image</source>
<target>图像水平镜像</target>
</phrase>
<phrase>
<source>Miroir horizontal de %1</source>
<target>%1 的水平镜像</target>
</phrase>
<phrase>
<source>Miroir impossible : inclinaison trop extrême pour cette forme</source>
<target>无法镜像:此形状的倾斜过于极端</target>
</phrase>
<phrase>
<source>Miroir vertical</source>
<target>垂直镜像</target>
</phrase>
<phrase>
<source>Miroir vertical d&apos;une image</source>
<target>图像垂直镜像</target>
</phrase>
<phrase>
<source>Miroir vertical de %1</source>
<target>%1 的垂直镜像</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;un arc</source>
<target>修改弧的角度</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;une forme</source>
<target>修改形状角度</target>
</phrase>
<phrase>
<source>Modifier l&apos;angle d&apos;une image</source>
<target>修改图像角度</target>
</phrase>
<phrase>
<source>Modifier l&apos;inclinaison d&apos;une image</source>
<target>修改图像倾斜</target>
</phrase>
<phrase>
<source>Modifier la courbure d&apos;%1</source>
<target>修改%1的曲率</target>
</phrase>
<phrase>
<source>Modifier la forme d&apos;%1</source>
<target>修改%1的形状</target>
</phrase>
<phrase>
<source>Modifier la hauteur d&apos;une image</source>
<target>修改图像高度</target>
</phrase>
<phrase>
<source>Modifier la largeur d&apos;une image</source>
<target>修改图像宽度</target>
</phrase>
<phrase>
<source>Modifier la longueur d&apos;une ligne</source>
<target>修改线条长度</target>
</phrase>
<phrase>
<source>Modifier la taille d&apos;une forme</source>
<target>修改形状大小</target>
</phrase>
<phrase>
<source>Modifier le type d&apos;un nœud</source>
<target>修改节点类型</target>
</phrase>
<phrase>
<source>Modifier une image</source>
<target>修改图像</target>
</phrase>
<phrase>
<source>Modèle</source>
<target>型号</target>
</phrase>
<phrase>
<source>Nombre maximum de contacts esclaves définis : non défini
</source>
<target>已定义的从属触点最大数量:未定义
</target>
</phrase>
<phrase>
<source>Notes</source>
<target>备注</target>
</phrase>
<phrase>
<source>Nœud le plus proche</source>
<target>最近节点</target>
</phrase>
<phrase>
<source>pivoter/incliner</source>
<target>旋转/倾斜</target>
</phrase>
<phrase>
<source>Rayon X</source>
<target>X半径</target>
</phrase>
<phrase>
<source>Rayon Y</source>
<target>Y半径</target>
</phrase>
<phrase>
<source>redimensionner</source>
<target>调整大小</target>
</phrase>
<phrase>
<source>Redimensionner %1</source>
<target>调整 %1 大小</target>
</phrase>
<phrase>
<source>Redimensionner une image</source>
<target>调整图像大小</target>
</phrase>
<phrase>
<source>Remplacer l&apos;image...</source>
<target>替换图像...</target>
</phrase>
<phrase>
<source>Remplacer une image</source>
<target>替换图像</target>
</phrase>
<phrase>
<source>Restaurer les proportions</source>
<target>恢复比例</target>
</phrase>
<phrase>
<source>Restaurer les proportions d&apos;une image</source>
<target>恢复图像比例</target>
</phrase>
<phrase>
<source>Retirer cette couleur</source>
<target>移除此颜色</target>
</phrase>
<phrase>
<source>Revenir à l&apos;image complète, sans rognage</source>
<target>恢复为完整图像,不裁剪</target>
</phrase>
<phrase>
<source>rgb(%1, %2, %3)</source>
<target>rgb(%1, %2, %3)</target>
</phrase>
<phrase>
<source>Rogner l&apos;image</source>
<target>裁剪图像</target>
</phrase>
<phrase>
<source>Rogner une image</source>
<target>裁剪图像</target>
</phrase>
<phrase>
<source>Rogner...</source>
<target>裁剪...</target>
</phrase>
<phrase>
<source>Rotation/Inclinaison</source>
<target>旋转/倾斜</target>
</phrase>
<phrase>
<source>Réinitialiser</source>
<target>重置</target>
</phrase>
<phrase>
<source>Selectionner une image...</source>
<target>选择图像...</target>
</phrase>
<phrase>
<source>Supprimer le nœud le plus proche</source>
<target>删除最近节点</target>
</phrase>
<phrase>
<source>Supprimer un point d&apos;une courbe</source>
<target>从曲线删除点</target>
</phrase>
<phrase>
<source>Symétrique</source>
<target>对称</target>
</phrase>
<phrase>
<source>Séparation de potentiel</source>
<target>电位分隔</target>
</phrase>
<phrase>
<source>Taille</source>
<target>大小</target>
</phrase>
<phrase>
<source>Tension nominale</source>
<target>额定电压</target>
</phrase>
<phrase>
<source>Tolérance pour cette couleur</source>
<target>此颜色的容差</target>
</phrase>
<phrase>
<source>Tous les fichiers (*)</source>
<target>所有文件 (*)</target>
</phrase>
<phrase>
<source>Transformer %1</source>
<target>变换 %1</target>
</phrase>
<phrase>
<source>Transparence non conservée</source>
<target>不保留透明度</target>
</phrase>
<phrase>
<source>un arc</source>
<target>一段弧</target>
</phrase>
<phrase>
<source>un coin</source>
<target>一个角</target>
</phrase>
<phrase>
<source>un point</source>
<target>一个点</target>
</phrase>
<phrase>
<source>une courbe</source>
<target>一条曲线</target>
</phrase>
<phrase>
<source>une extrémité</source>
<target>一个端点</target>
</phrase>
<phrase>
<source>une image</source>
<target>一张图像</target>
<definition>part of a sentence listing the content of a diagram</definition>
</phrase>
<phrase>
<source>Verrouiller la numérotation automatique</source>
<target>锁定自动编号</target>
</phrase>
<phrase>
<source>Verrouiller la position</source>
<target>锁定位置</target>
</phrase>
<phrase>
<source>Verrouillé : modifier la largeur ou la hauteur ajuste l&apos;autre pour conserver les proportions. Cliquer pour déverrouiller.</source>
<target>已锁定:修改宽度或高度会调整另一个以保持比例。单击解锁。</target>
</phrase>
<phrase>
<source>Échec de l&apos;enregistrement</source>
<target>保存失败</target>
</phrase>
<phrase>
<source>Édition des nœuds</source>
<target>节点编辑</target>
</phrase>
<phrase>
<source>armoire</source>
<target>机柜</target>
<definition>cabinet / enclosure</definition>
</phrase>
<phrase>
<source>bobine</source>
<target>线圈</target>
<definition>coil</definition>
</phrase>
<phrase>
<source>borne</source>
<target>端子</target>
<definition>terminal - a connection point on a symbol</definition>
</phrase>
<phrase>
<source>bornier</source>
<target>端子排</target>
<definition>terminal strip</definition>
</phrase>
<phrase>
<source>cable</source>
<target>电缆</target>
<definition>cable</definition>
</phrase>
<phrase>
<source>calibre</source>
<target>额定值</target>
<definition>rating - the current rating of a device</definition>
</phrase>
<phrase>
<source>cartouche</source>
<target>标题栏</target>
<definition>title block - the framed information panel</definition>
</phrase>
<phrase>
<source>collection</source>
<target>符号库</target>
<definition>collection - the symbol library</definition>
</phrase>
<phrase>
<source>conducteur</source>
<target>导线</target>
<definition>conductor - the drawn wire between terminals</definition>
</phrase>
<phrase>
<source>contact</source>
<target>触点</target>
<definition>contact</definition>
</phrase>
<phrase>
<source>contacteur</source>
<target>接触器</target>
<definition>contactor</definition>
</phrase>
<phrase>
<source>courant</source>
<target>电流</target>
<definition>current</definition>
</phrase>
<phrase>
<source>disjoncteur</source>
<target>断路器</target>
<definition>circuit breaker</definition>
</phrase>
<phrase>
<source>dossier</source>
<target>文件夹</target>
<definition>folder</definition>
</phrase>
<phrase>
<source>element</source>
<target>元件</target>
<definition>element - a symbol placed on a sheet</definition>
</phrase>
<phrase>
<source>esclave</source>
<target>从</target>
<definition>slave - the contact block belonging to a master</definition>
</phrase>
<phrase>
<source>fusible</source>
<target>熔断器</target>
<definition>fuse</definition>
</phrase>
<phrase>
<source>grille</source>
<target>网格</target>
<definition>grid</definition>
</phrase>
<phrase>
<source>interrupteur</source>
<target>开关</target>
<definition>switch</definition>
</phrase>
<phrase>
<source>maitre</source>
<target>主</target>
<definition>master - the element a cross-reference points from</definition>
</phrase>
<phrase>
<source>modele</source>
<target>模板</target>
<definition>template</definition>
</phrase>
<phrase>
<source>moteur</source>
<target>电动机</target>
<definition>motor</definition>
</phrase>
<phrase>
<source>neutre</source>
<target>中性线</target>
<definition>neutral</definition>
</phrase>
<phrase>
<source>nomenclature</source>
<target>物料清单</target>
<definition>parts list / bill of materials</definition>
</phrase>
<phrase>
<source>numerotation</source>
<target>编号</target>
<definition>numbering - automatic wire and part numbering</definition>
</phrase>
<phrase>
<source>phase</source>
<target>相</target>
<definition>phase</definition>
</phrase>
<phrase>
<source>potentiel</source>
<target>电位</target>
<definition>potential - conductors electrically joined as one node</definition>
</phrase>
<phrase>
<source>projet</source>
<target>项目</target>
<definition>project - the .qet file holding the sheets</definition>
</phrase>
<phrase>
<source>puissance</source>
<target>功率</target>
<definition>power</definition>
</phrase>
<phrase>
<source>relais</source>
<target>继电器</target>
<definition>relay</definition>
</phrase>
<phrase>
<source>renvoi de folio</source>
<target>图纸交叉引用</target>
<definition>sheet cross-reference</definition>
</phrase>
<phrase>
<source>repere</source>
<target>标识</target>
<definition>designation - the identifying mark on a part</definition>
</phrase>
<phrase>
<source>report de folio</source>
<target>图纸交叉引用</target>
<definition>sheet cross-reference (the arrow symbol)</definition>
</phrase>
<phrase>
<source>schema</source>
<target>原理图</target>
<definition>diagram</definition>
</phrase>
<phrase>
<source>section</source>
<target>截面积</target>
<definition>cross-section - conductor size in mm2</definition>
</phrase>
<phrase>
<source>sommaire</source>
<target>目录</target>
<definition>summary / table of contents</definition>
</phrase>
<phrase>
<source>tension</source>
<target>电压</target>
<definition>voltage</definition>
</phrase>
<phrase>
<source>terre</source>
<target>接地</target>
<definition>earth / ground</definition>
</phrase>
<phrase>
<source>texte</source>
<target>文本</target>
<definition>text</definition>
</phrase>
<phrase>
<source>transformateur</source>
<target>变压器</target>
<definition>transformer</definition>
</phrase>
</QPH>
+1 -1
View File
@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
#Based on raspberry pi 5 8 Gb Trixie #Based on raspberry pi 5 8 Gb Trixie
#sudo apt install git ssh rsync libqt5svg5-dev qt5-qmake qtbase5-dev libkf5widgetsaddons-dev libkf5coreaddons-dev pkgconf libqt5waylandclient5-dev libqt5waylandcompositor5-dev g++ make #sudo apt install git ssh rsync libqt5svg5-dev qt5-qmake qtbase5-dev libkf5widgetsaddons-dev libkf5coreaddons-dev libsqlite3-dev pkgconf libqt5waylandclient5-dev libqt5waylandcompositor5-dev g++ make
#mkdir -p AppImage/0.100.0/aarch64 #mkdir -p AppImage/0.100.0/aarch64
# Get GIT sources # Get GIT sources
#git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git #git clone --recursive https://github.com/qelectrotech/qelectrotech-source-mirror.git
@@ -49,6 +49,14 @@ ElementsCollectionModel::ElementsCollectionModel(QObject *parent) :
*/ */
ElementsCollectionModel::~ElementsCollectionModel() ElementsCollectionModel::~ElementsCollectionModel()
{ {
// Without cancel(), the wait below runs the whole queued
// QtConcurrent::map() to completion, so closing this dialog on a
// large collection blocks until every remaining item has been
// processed -- a visible hang on the button pressed precisely to
// stop the work. cancel() drops the not-yet-started items so the
// wait that follows (still needed, so an in-flight item can't
// dereference this object after it's gone) is short.
m_future.cancel();
m_future.waitForFinished(); m_future.waitForFinished();
} }
@@ -431,7 +439,7 @@ void ElementsCollectionModel::addLocation(const ElementsLocation& location)
collection_name); collection_name);
} }
} }
// ANPASSUNG: Makros und Custom Collection werden hier behandelt! // Macros and Custom Collection are handled here
else if (location.isCustomCollection() || location.isMacrosCollection()) { else if (location.isCustomCollection() || location.isMacrosCollection()) {
QList <ElementCollectionItem *> child_list; QList <ElementCollectionItem *> child_list;
@@ -647,7 +655,7 @@ QModelIndex ElementsCollectionModel::indexFromLocation(
if (eci->type() == FileElementCollectionItem::Type) { if (eci->type() == FileElementCollectionItem::Type) {
if (FileElementCollectionItem *feci = static_cast<FileElementCollectionItem *>(eci)) { if (FileElementCollectionItem *feci = static_cast<FileElementCollectionItem *>(eci)) {
// ANPASSUNG: Makro-Prüfung hinzugefügt, damit das Modell den Pfad im Baum findet! // Macro check added so the model finds the path in the tree
if ( (location.isCommonCollection() && feci->isCommonCollection()) || if ( (location.isCommonCollection() && feci->isCommonCollection()) ||
(location.isCompanyCollection() && feci->isCompanyCollection()) || (location.isCompanyCollection() && feci->isCompanyCollection()) ||
(location.isMacrosCollection() && feci->isMacrosCollection()) || (location.isMacrosCollection() && feci->isMacrosCollection()) ||
@@ -653,9 +653,16 @@ QDomElement ElementsLocation::xml() const
if (!m_project) if (!m_project)
{ {
QFile file (m_file_system_path); QFile file (m_file_system_path);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return QDomElement();
QDomDocument docu; QDomDocument docu;
if (docu.setContent(&file)) if (docu.setContent(&file))
{
file.close();
return docu.documentElement(); return docu.documentElement();
}
file.close();
} }
else else
{ {
@@ -162,7 +162,7 @@ QString FileElementCollectionItem::localName()
else if (m_path == QETApp::customElementsDirN()) else if (m_path == QETApp::customElementsDirN())
setText(QObject::tr("Collection utilisateur")); setText(QObject::tr("Collection utilisateur"));
else if (m_path == macrosPath) else if (m_path == macrosPath)
setText(QObject::tr("Makros")); setText(QObject::tr("Macros"));
else else
setText(QObject::tr("Collection inconnue")); setText(QObject::tr("Collection inconnue"));
} }
@@ -46,10 +46,11 @@ FreeTerminalEditor::FreeTerminalEditor(QETProject *project, QWidget *parent) :
connect(m_project, &QObject::destroyed, this, &FreeTerminalEditor::reload); connect(m_project, &QObject::destroyed, this, &FreeTerminalEditor::reload);
} }
//Disabled the move if the table is currently edited (yellow cell) //Disable the move button while cells are edited (yellow), re-evaluate on every change
connect(m_model, &FreeTerminalModel::dataChanged, this, [=] { connect(m_model, &FreeTerminalModel::dataChanged, this, &FreeTerminalEditor::selectionChanged);
this->setDisabledMove();
}); connect(ui->m_table_view->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &FreeTerminalEditor::selectionChanged);
connect(ui->m_table_view, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index) connect(ui->m_table_view, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &index)
{ {
@@ -102,7 +103,7 @@ void FreeTerminalEditor::reload()
QString str(strip->installation() + " " + strip->location() + " " + strip->name()); QString str(strip->installation() + " " + strip->location() + " " + strip->name());
ui->m_move_in_cb->addItem(str, strip->uuid()); ui->m_move_in_cb->addItem(str, strip->uuid());
} }
setDisabledMove(false); selectionChanged();
} }
} }
@@ -273,10 +274,19 @@ void FreeTerminalEditor::on_m_move_pb_clicked()
reload(); reload();
} }
void FreeTerminalEditor::selectionChanged()
{
const bool has_selection = !ui->m_table_view->selectionModel()->selectedIndexes().isEmpty();
const bool has_pending = !m_model->modifiedModelRealTerminalData().isEmpty();
setDisabledMove(!has_selection || has_pending);
}
void FreeTerminalEditor::setDisabledMove(bool b) void FreeTerminalEditor::setDisabledMove(bool b)
{ {
ui->m_move_label->setDisabled(b); ui->m_move_label->setDisabled(b);
ui->m_move_in_cb->setDisabled(b); ui->m_move_in_cb->setDisabled(b);
ui->m_move_pb->setDisabled(b); ui->m_move_pb->setDisabled(b);
ui->m_move_pb->setToolTip(b ? tr("Appliquez ou annulez les modifications en cours avant de déplacer")
: tr("Déplacer les bornes sélectionnées vers le bornier choisi"));
} }
@@ -182,7 +182,7 @@ bool FreeTerminalModel::setData(const QModelIndex &index, const QVariant &value,
modified_ = true; modified_ = true;
modified_cell = FUNCTION_CELL; modified_cell = FUNCTION_CELL;
} }
else if (column_ == LED_CELL) else if (column_ == LED_CELL && mrtd.led_ != value.toBool())
{ {
mrtd.led_ = value.toBool(); mrtd.led_ = value.toBool();
modified_ = true; modified_ = true;
@@ -0,0 +1,78 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "renumberelementscommand.h"
#include "../qetproject.h"
#include "../qetgraphicsitem/element.h"
RenumberElementsCommand::RenumberElementsCommand(
QETProject *project,
QVector<ElementChange> changes,
QHash<QString, NumerotationContext> old_ctx,
QHash<QString, NumerotationContext> new_ctx,
const QString &text)
: QUndoCommand(text)
, project_(project)
, changes_(std::move(changes))
, old_ctx_(std::move(old_ctx))
, new_ctx_(std::move(new_ctx))
{
}
void RenumberElementsCommand::undo()
{
apply(false);
}
void RenumberElementsCommand::redo()
{
// QUndoStack calls redo() once immediately after push(); keep standard behaviour.
if (first_redo_) {
first_redo_ = false;
}
apply(true);
}
void RenumberElementsCommand::apply(bool use_new)
{
if (!project_) return;
// Restore project contexts
const auto &ctx = use_new ? new_ctx_ : old_ctx_;
for (auto it = ctx.constBegin(); it != ctx.constEnd(); ++it) {
project_->addElementAutoNum(it.key(), it.value());
}
// Apply per-element changes
for (const ElementChange &c : changes_) {
if (!c.element) continue;
const bool frozen = use_new ? c.new_frozen : c.old_frozen;
const auto &infos = use_new ? c.new_infos : c.old_infos;
const auto &seq = use_new ? c.new_seq : c.old_seq;
// Temporarily unfreeze so that label/infos update correctly.
const bool was_frozen = c.element->isFreezeLabel();
if (was_frozen) c.element->freezeLabel(false);
c.element->rSequenceStruct() = seq;
c.element->setElementInformations(infos);
c.element->freezeLabel(frozen);
}
}
+70
View File
@@ -0,0 +1,70 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RENUMBERELEMENTSCOMMAND_H
#define RENUMBERELEMENTSCOMMAND_H
#include <QUndoCommand>
#include <QHash>
#include <QVector>
#include "../diagramcontext.h"
#include "assignvariables.h" // defines autonum::sequentialNumbers
class Element;
class QETProject;
/**
* @brief Undoable renumbering of element labels and sequence structs.
*/
class RenumberElementsCommand final : public QUndoCommand
{
public:
struct ElementChange
{
Element *element = nullptr;
DiagramContext old_infos;
DiagramContext new_infos;
autonum::sequentialNumbers old_seq;
autonum::sequentialNumbers new_seq;
bool old_frozen = false;
bool new_frozen = false;
};
RenumberElementsCommand(
QETProject *project,
QVector<ElementChange> changes,
QHash<QString, NumerotationContext> old_ctx,
QHash<QString, NumerotationContext> new_ctx,
const QString &text);
void undo() override;
void redo() override;
private:
void apply(bool use_new);
private:
QETProject *project_ = nullptr;
QVector<ElementChange> changes_;
QHash<QString, NumerotationContext> old_ctx_;
QHash<QString, NumerotationContext> new_ctx_;
bool first_redo_ = true;
};
#endif // RENUMBERELEMENTSCOMMAND_H
@@ -23,6 +23,7 @@
#include "formulaautonumberingw.h" #include "formulaautonumberingw.h"
#include "numparteditorw.h" #include "numparteditorw.h"
#include "qdebug.h" #include "qdebug.h"
#include "renumberelementsdialog.h"
#include "ui_autonumberingmanagementw.h" #include "ui_autonumberingmanagementw.h"
#include "ui_formulaautonumberingw.h" #include "ui_formulaautonumberingw.h"
@@ -48,6 +49,8 @@ AutoNumberingManagementW::AutoNumberingManagementW(QETProject *project,
ui->m_selected_folios_le->setDisabled(true); ui->m_selected_folios_le->setDisabled(true);
ui->m_selected_folios_le->setReadOnly(true); ui->m_selected_folios_le->setReadOnly(true);
ui->m_apply_project_rb->setChecked(true); ui->m_apply_project_rb->setChecked(true);
// Enabled only when project is in "Under Development" status and not read-only.
ui->m_renumber_elements_pb->setEnabled(ui->m_status_cb->currentIndex() == 0 && project_ && !project_->isReadOnly());
setProjectContext(); setProjectContext();
} }
@@ -87,6 +90,7 @@ void AutoNumberingManagementW::on_m_status_cb_currentIndexChanged(int index)
ui->m_both_conductor_rb->setChecked(true); ui->m_both_conductor_rb->setChecked(true);
ui->m_both_element_rb->setChecked(true); ui->m_both_element_rb->setChecked(true);
ui->m_both_folio_rb->setChecked(true); ui->m_both_folio_rb->setChecked(true);
ui->m_renumber_elements_pb->setEnabled(true);
} }
//Installing //Installing
else if (index == 1) { else if (index == 1) {
@@ -96,15 +100,31 @@ void AutoNumberingManagementW::on_m_status_cb_currentIndexChanged(int index)
ui->m_new_conductor_rb->setChecked(true); ui->m_new_conductor_rb->setChecked(true);
ui->m_new_element_rb->setChecked(true); ui->m_new_element_rb->setChecked(true);
ui->m_new_folio_rb->setChecked(true); ui->m_new_folio_rb->setChecked(true);
ui->m_renumber_elements_pb->setEnabled(true);
} }
//Built //Built
else if (index == 2) { else if (index == 2) {
ui->m_disable_conductor_rb->setChecked(true); ui->m_disable_conductor_rb->setChecked(true);
ui->m_disable_element_rb->setChecked(true); ui->m_disable_element_rb->setChecked(true);
ui->m_disable_folio_rb->setChecked(true); ui->m_disable_folio_rb->setChecked(true);
ui->m_renumber_elements_pb->setEnabled(false);
} }
} }
void AutoNumberingManagementW::on_m_renumber_elements_pb_clicked()
{
if (!project_ || project_->isReadOnly()) return;
// Only allowed during "Under Development"
// if (ui->m_status_cb->currentIndex() != 1) return;
QStringList titles = project_->elementAutoNum().keys();
titles.sort(Qt::CaseInsensitive);
RenumberElementsDialog dlg(titles, this);
if (dlg.exec() != QDialog::Accepted) return;
project_->renumberElementsBySchemeTitle(dlg.selectedSchemeTitle());
}
/** /**
@brief AutoNumberingManagementW::on_m_apply_folios_rb_clicked @brief AutoNumberingManagementW::on_m_apply_folios_rb_clicked
Set From Folios Combobox Set From Folios Combobox
@@ -50,6 +50,7 @@ class AutoNumberingManagementW : public QWidget
void on_m_from_folios_cb_currentIndexChanged(int); void on_m_from_folios_cb_currentIndexChanged(int);
void on_m_to_folios_cb_currentIndexChanged(int); void on_m_to_folios_cb_currentIndexChanged(int);
void on_m_status_cb_currentIndexChanged(int); void on_m_status_cb_currentIndexChanged(int);
void on_m_renumber_elements_pb_clicked();
void on_m_apply_folios_rb_clicked(); void on_m_apply_folios_rb_clicked();
void on_m_apply_project_rb_clicked(); void on_m_apply_project_rb_clicked();
void on_buttonBox_clicked(QAbstractButton *); void on_buttonBox_clicked(QAbstractButton *);
@@ -305,6 +305,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="m_renumber_elements_pb">
<property name="text">
<string>Renumber element(s)…</string>
</property>
</widget>
</item>
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
@@ -0,0 +1,75 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "renumberelementsdialog.h"
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFormLayout>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QRadioButton>
#include <QVBoxLayout>
RenumberElementsDialog::RenumberElementsDialog(const QStringList &scheme_titles, QWidget *parent)
: QDialog(parent)
, m_scheme_titles(scheme_titles)
{
setWindowTitle(tr("Renumber element(s)"));
setModal(true);
auto *vl = new QVBoxLayout(this);
auto *scope = new QGroupBox(tr("Scope"), this);
auto *scope_l = new QVBoxLayout(scope);
m_all_rb = new QRadioButton(tr("All schemes"), scope);
m_one_rb = new QRadioButton(tr("One scheme"), scope);
scope_l->addWidget(m_all_rb);
scope_l->addWidget(m_one_rb);
vl->addWidget(scope);
auto *form = new QFormLayout();
m_combo = new QComboBox(this);
m_combo->addItems(m_scheme_titles);
form->addRow(new QLabel(tr("Scheme:"), this), m_combo);
vl->addLayout(form);
m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(m_buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
vl->addWidget(m_buttons);
m_all_rb->setChecked(true);
updateUi();
connect(m_all_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
connect(m_one_rb, &QRadioButton::toggled, this, &RenumberElementsDialog::updateUi);
}
QString RenumberElementsDialog::selectedSchemeTitle() const
{
if (m_all_rb && m_all_rb->isChecked()) return QString();
return m_combo ? m_combo->currentText() : QString();
}
void RenumberElementsDialog::updateUi()
{
const bool one = m_one_rb && m_one_rb->isChecked();
if (m_combo) m_combo->setEnabled(one);
}
@@ -0,0 +1,59 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RENUMBERELEMENTSDIALOG_H
#define RENUMBERELEMENTSDIALOG_H
#include <QDialog>
#include <QStringList>
class QComboBox;
class QRadioButton;
class QDialogButtonBox;
/**
* @brief Simple dialog to pick renumbering scope for elements.
*
* The user can choose between:
* - all element autonumbering schemes
* - one selected scheme title
*/
class RenumberElementsDialog final : public QDialog
{
Q_OBJECT
public:
explicit RenumberElementsDialog(const QStringList &scheme_titles, QWidget *parent = nullptr);
/**
* @return Empty string if "all" is selected, otherwise the chosen scheme title.
*/
QString selectedSchemeTitle() const;
private slots:
void updateUi();
private:
QStringList m_scheme_titles;
QRadioButton *m_all_rb = nullptr;
QRadioButton *m_one_rb = nullptr;
QComboBox *m_combo = nullptr;
QDialogButtonBox *m_buttons = nullptr;
};
#endif // RENUMBERELEMENTSDIALOG_H
+30 -2
View File
@@ -319,10 +319,23 @@ void projectDataBase::addElement(Element *element)
void projectDataBase::removeElement(Element *element) void projectDataBase::removeElement(Element *element)
{ {
m_content_changed = true; m_content_changed = true;
bool changed = false;
m_remove_element_query.bindValue(":uuid", element->uuid().toString()); m_remove_element_query.bindValue(":uuid", element->uuid().toString());
if(!m_remove_element_query.exec()) { if (m_remove_element_query.exec()) {
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError(); changed = true;
} else { } else {
qDebug() << "projectDataBase::removeElement remove error : " << m_remove_element_query.lastError();
}
m_remove_element_info_query.bindValue(":uuid", element->uuid().toString());
if (m_remove_element_info_query.exec()) {
changed = true;
} else {
qDebug() << "projectDataBase::removeElement remove element_info error : " << m_remove_element_info_query.lastError();
}
if (changed) {
emit dataBaseUpdated(); emit dataBaseUpdated();
} }
} }
@@ -1159,6 +1172,21 @@ void projectDataBase::prepareQuery()
m_remove_element_query = QSqlQuery(m_data_base); m_remove_element_query = QSqlQuery(m_data_base);
m_remove_element_query.prepare(remove_element); m_remove_element_query.prepare(remove_element);
//REMOVE ELEMENT INFO
//element_info has no ON DELETE CASCADE (foreign keys aren't
//enforced by this connection), so removeElement() must clear it
//explicitly. Without this, the row is orphaned under the removed
//element's uuid, and re-adding an element with that same uuid
//later -- undo of this same removal, or a redo replaying it --
//hits element_info's PRIMARY KEY constraint on element_uuid: the
//element re-add succeeds, but its element_info insert silently
//fails and is lost. removeDiagram()'s cascade already clears this
//table when a whole folio goes, but that does not run for a
//single element removed on its own.
QString remove_element_info("DELETE FROM element_info WHERE element_uuid=:uuid");
m_remove_element_info_query = QSqlQuery(m_data_base);
m_remove_element_info_query.prepare(remove_element_info);
//UPDATE ELEMENT INFO //UPDATE ELEMENT INFO
QString update_str("UPDATE element_info SET "); QString update_str("UPDATE element_info SET ");
for (auto string : QETInformation::elementInfoKeys()) { for (auto string : QETInformation::elementInfoKeys()) {
+2 -1
View File
@@ -29,7 +29,7 @@ class QETProject;
class Diagram; class Diagram;
class Conductor; class Conductor;
class Terminal; class Terminal;
class sqlite3; struct sqlite3;
/** /**
@brief The projectDataBase class @brief The projectDataBase class
@@ -139,6 +139,7 @@ class projectDataBase : public QObject
QSqlQuery m_insert_elements_query, QSqlQuery m_insert_elements_query,
m_insert_element_info_query, m_insert_element_info_query,
m_remove_element_query, m_remove_element_query,
m_remove_element_info_query,
m_update_element_query, m_update_element_query,
m_insert_diagram_query, m_insert_diagram_query,
m_remove_diagram_query, m_remove_diagram_query,
+17 -1
View File
@@ -77,6 +77,23 @@ void PasteDiagramCommand::redo()
{ {
first_redo = false; first_redo = false;
//Resolve a linked master/slave pair pasted together (bugtracker
//#607) before anything below renews their uuids: at this exact
//moment a pasted element's tmp_uuids_link still holds its
//source's original partner uuid, which still equals the
//not-yet-renewed uuid of that partner's own pasted copy if it
//was carried along in the same batch. Scoped to this batch only
//(not a project-wide search), so a pair pasted together links to
//each other and not to an original element left elsewhere that
//happens to still carry that same soon-to-be-replaced uuid. If
//only one half of a linked group was pasted, its link entry
//simply finds no match here and is dropped -- same "leave it
//unlinked" outcome as always.
const QList <Element *> elmts_list = content.m_elements;
for (Element *e : elmts_list) {
e->initLink(elmts_list);
}
//make new uuid for every pasted conductor, because old uuid are //make new uuid for every pasted conductor, because old uuid are
//the uuid of the copied conductor //the uuid of the copied conductor
const QList <Conductor *> all_pasted_conductors = content.conductors(); const QList <Conductor *> all_pasted_conductors = content.conductors();
@@ -85,7 +102,6 @@ void PasteDiagramCommand::redo()
} }
//this is the first paste, we do some actions for the new element //this is the first paste, we do some actions for the new element
const QList <Element *> elmts_list = content.m_elements;
for (Element *e : elmts_list) for (Element *e : elmts_list)
{ {
//make new uuid, because old uuid are the uuid of the copied element //make new uuid, because old uuid are the uuid of the copied element
+1 -1
View File
@@ -1477,7 +1477,7 @@ void DiagramView::createTemplateFromSelection()
collection_node.appendChild(collection_elmt); collection_node.appendChild(collection_elmt);
} else { } else {
qDebug() << "Warnung: Konnte XML-Definition für" << old_type << "nicht laden."; qDebug() << "Warning: could not load XML definition for" << old_type;
} }
} }
} }
@@ -17,6 +17,7 @@
*/ */
#include "elementpropertieseditorwidget.h" #include "elementpropertieseditorwidget.h"
#include "../../qet.h"
#include "../../qetapp.h" #include "../../qetapp.h"
#include "../../qetinformation.h" #include "../../qetinformation.h"
#include "ui_elementpropertieseditorwidget.h" #include "ui_elementpropertieseditorwidget.h"
@@ -185,13 +186,13 @@ void ElementPropertiesEditorWidget::upDateInterface()
const DiagramContext &info = m_data.m_informations; const DiagramContext &info = m_data.m_informations;
ui->m_auto_num_locked_cb->setChecked( ui->m_auto_num_locked_cb->setChecked(
info.value(QStringLiteral("auto_num_locked")).toString() == QLatin1String("true")); QET::infoFlagIsTrue(info.value(QStringLiteral("auto_num_locked")).toString()));
ui->m_potential_isolating_cb->setChecked( ui->m_potential_isolating_cb->setChecked(
info.value(QStringLiteral("potential_isolating")).toString() == QLatin1String("true")); QET::infoFlagIsTrue(info.value(QStringLiteral("potential_isolating")).toString()));
} }
ui->m_exclude_from_bom_cb->setChecked( ui->m_exclude_from_bom_cb->setChecked(
m_data.m_informations.value(QStringLiteral("exclude_from_bom")).toString() == QLatin1String("true")); QET::infoFlagIsTrue(m_data.m_informations.value(QStringLiteral("exclude_from_bom")).toString()));
on_m_base_type_cb_currentIndexChanged(ui->m_base_type_cb->currentIndex()); on_m_base_type_cb_currentIndexChanged(ui->m_base_type_cb->currentIndex());
} }
+20
View File
@@ -25,6 +25,8 @@
#include "../../qetapp.h" #include "../../qetapp.h"
#include "../../qetmainwindow.h" #include "../../qetmainwindow.h"
#include "../../recentfiles.h" #include "../../recentfiles.h"
#include "../../elementscollectioncache.h"
#include "../../factory/elementpicturefactory.h"
#include "../graphicspart/customelementpart.h" #include "../graphicspart/customelementpart.h"
#include "../elementitemeditor.h" #include "../elementitemeditor.h"
#include "../styleeditor.h" #include "../styleeditor.h"
@@ -348,6 +350,24 @@ bool QETElementEditor::toLocation(const ElementsLocation &location)
tr("Impossible d'enregistrer l'élément", "message box content")); tr("Impossible d'enregistrer l'élément", "message box content"));
return(false); return(false);
} }
//setXml() just wrote the new drawing to disk, but the preview shown
//in the elements panel comes from two caches keyed by path+uuid that
//nothing here has told about the change: ElementPictureFactory's
//in-memory picture cache and ElementsCollectionCache's on-disk
//SQLite cache (see ElementsLocation::icon()). locationWasSaved()
//(elementscollectionwidget.cpp) re-reads the icon right after this
//call returns, but both caches still hand back the pre-edit pixmap,
//so the panel keeps showing the stale preview until the whole
//collection is reloaded. Drop and rebuild them here.
ElementPictureFactory::instance()->dropCache(location);
if (ElementsCollectionCache *cache = QETApp::collectionCache()) {
if (cache->fetchData(location)) {
cache->cacheName(location.toString(), location.uuid());
cache->cachePixmap(location.toString(), location.uuid());
}
}
return(true); return(true);
} }
+24 -3
View File
@@ -674,6 +674,27 @@ void ElementsPanelWidget::duplicateDiagram()
bool erase_labels = settings.value( bool erase_labels = settings.value(
"diagramcommands/erase-label-on-copy", true).toBool(); "diagramcommands/erase-label-on-copy", true).toBool();
// Resolve a linked pair duplicated together against each other
// (bugtracker #607) before the loop below renews their uuids or
// clears their pending links: at this exact moment a copy's
// tmp_uuids_link still holds its source's original partner
// uuid, which still equals the not-yet-renewed uuid of that
// partner's own copy if both were duplicated together. Scoped
// to this diagram's own copies, not a project-wide search, so
// this never links back to the source elements the copies were
// made from -- if only one half of a linked pair is here, its
// link entry simply finds no match and is dropped, same as
// clearPendingLinks() used to do unconditionally for every copy.
QList<Element *> new_elements;
for (QGraphicsItem *item : new_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) {
new_elements << elmt;
}
}
for (Element *elmt : new_elements) {
elmt->initLink(new_elements);
}
for (QGraphicsItem *item : new_diagram->items()) { for (QGraphicsItem *item : new_diagram->items()) {
if (Element *elmt = dynamic_cast<Element *>(item)) { if (Element *elmt = dynamic_cast<Element *>(item)) {
// The XML round-trip kept the source elements' uuids. Give the // The XML round-trip kept the source elements' uuids. Give the
@@ -695,9 +716,9 @@ void ElementsPanelWidget::duplicateDiagram()
new_diagram->restoreText(elmt); new_diagram->restoreText(elmt);
} }
// Clear pending links so copies don't link back to // initLink() above already cleared tmp_uuids_link for
// the source elements via stale UUIDs. // every copy, matched or not -- nothing left here that
elmt->clearPendingLinks(); // could link back to a stale source uuid.
// Clean up copied element data: // Clean up copied element data:
// 1. Slaves always lose label/formula/comment/location // 1. Slaves always lose label/formula/comment/location
+32
View File
@@ -22,6 +22,8 @@
#include "logging/eventloopwatchdog.h" #include "logging/eventloopwatchdog.h"
#include "logging/qetlogger.h" #include "logging/qetlogger.h"
#include "machine_info.h" #include "machine_info.h"
#include "diagram.h"
#include "palettegraphicsview.h"
#include "qet.h" #include "qet.h"
#include "qetapp.h" #include "qetapp.h"
#include "qetmessagebox.h" #include "qetmessagebox.h"
@@ -31,6 +33,7 @@
#include <QApplication> #include <QApplication>
#include <QDomImplementation> #include <QDomImplementation>
#include <QFont>
#include <QStyleFactory> #include <QStyleFactory>
#include <QtConcurrentRun> #include <QtConcurrentRun>
@@ -105,6 +108,19 @@ int main(int argc, char **argv)
// from Qt 6.12 on; opt in explicitly for older Qt 5/6. // from Qt 6.12 on; opt in explicitly for older Qt 5/6.
QDomImplementation::setInvalidDataPolicy( QDomImplementation::setInvalidDataPolicy(
QDomImplementation::ReturnNullNode); QDomImplementation::ReturnNullNode);
#ifdef Q_OS_WIN
// "MS Shell Dlg 2" is not a font but a Windows alias, and many projects
// and settings saved on Windows carry it. Qt 5's GDI font backend let
// Windows resolve it to Tahoma; Qt 6's DirectWrite backend does not know
// the alias and falls back to Arial, so those texts come out heavier on
// screen and in exported PDFs (bugtracker #340). Resolve both aliases
// the way Windows does. Done before any application object exists so
// that the headless export and scripting runs below get it too.
QFont::insertSubstitution("MS Shell Dlg 2", "Tahoma");
QFont::insertSubstitution("MS Shell Dlg", "Microsoft Sans Serif");
#endif
//Creation and execution of the application //Creation and execution of the application
//HighDPI //HighDPI
qputenv("QT_ENABLE_HIGHDPI_SCALING", "1"); qputenv("QT_ENABLE_HIGHDPI_SCALING", "1");
@@ -144,6 +160,22 @@ int main(int argc, char **argv)
#endif #endif
} }
// Re-apply the sheet background last picked in the diagram editor, so
// every project opened from here on -- existing or new, whichever one
// it is -- draws that background instead of the built-in default that
// would otherwise force the user to pick it again after each start.
//
// Done here rather than in main()'s first lines on purpose: the
// headless export and scripting runs above return before reaching
// this point and must keep rendering on plain white. It also has to
// happen before QETApp is constructed below, since that constructor
// already loads the projects given on the command line.
{
const QetSettings::SheetBackground sheet_background = QetSettings::sheetBackground();
PaletteGraphicsView::setCustomBackgroundColor(sheet_background.custom);
Diagram::background_color = sheet_background.color;
}
// Resolve the logger's state (log directory, session filename, open // Resolve the logger's state (log directory, session filename, open
// file handle) explicitly here, immediately before installing the // file handle) explicitly here, immediately before installing the
// handler -- not implicitly on whichever thread happens to log // handler -- not implicitly on whichever thread happens to log
+24
View File
@@ -20,6 +20,7 @@
#include "qeticons.h" #include "qeticons.h"
#include "shortcutmanager.h" #include "shortcutmanager.h"
#include <cmath>
#include <limits> #include <limits>
#include <QBuffer> #include <QBuffer>
#include <QColorDialog> #include <QColorDialog>
@@ -244,10 +245,33 @@ bool QET::attributeIsAReal(
bool ok; bool ok;
qreal tmp = e.attribute(nom_attribut).toDouble(&ok); qreal tmp = e.attribute(nom_attribut).toDouble(&ok);
if (!ok) return(false); if (!ok) return(false);
// QString::toDouble() sets ok=true for "nan"/"inf"/"-inf" -- these
// parse successfully but are not usable coordinates. A non-finite
// element/terminal position reaches Conductor::shape() during load
// and hangs there at 100% CPU inside QPainterPathStroker::createStroke(),
// confirmed with gdb: not a blocked wait, genuine unbounded computation.
if (!std::isfinite(tmp)) return(false);
if (reel != nullptr) *reel = tmp; if (reel != nullptr) *reel = tmp;
return(true); return(true);
} }
/**
@brief QET::infoFlagIsTrue
@see the header comment for why this exists rather than a bare
== "true" comparison.
@param value the raw elementInformations string to test
@return true if @p value, trimmed and case-folded, is one of the
truthy spellings this codebase already accepts elsewhere
*/
bool QET::infoFlagIsTrue(const QString &value)
{
const QString v = value.trimmed().toLower();
return v == QLatin1String("true")
|| v == QLatin1String("1")
|| v == QLatin1String("yes")
|| v == QLatin1String("on");
}
/** /**
@brief QET::ElementsAndConductorsSentence @brief QET::ElementsAndConductorsSentence
Permet de composer rapidement la proposition "x elements et y conducteurs" Permet de composer rapidement la proposition "x elements et y conducteurs"
+16
View File
@@ -161,6 +161,22 @@ namespace QET {
bool orthogonalProjection(const QPointF &, const QLineF &, QPointF * = nullptr); bool orthogonalProjection(const QPointF &, const QLineF &, QPointF * = nullptr);
bool attributeIsAnInteger(const QDomElement &, const QString& , int * = nullptr); bool attributeIsAnInteger(const QDomElement &, const QString& , int * = nullptr);
bool attributeIsAReal(const QDomElement &, const QString& , qreal * = nullptr); bool attributeIsAReal(const QDomElement &, const QString& , qreal * = nullptr);
/**
Whether an elementInformations flag (auto_num_locked,
potential_isolating, exclude_from_bom, ...) counts as "on".
Case-insensitive and tolerant of surrounding whitespace, and
accepts the same set of truthy spellings ("true", "1", "yes",
"on") that element_nomenclature_view's SQL predicate for
exclude_from_bom already does -- see
projectDataBase::createElementNomenclatureView(). These flags
are only ever written by this app's own checkboxes as literal
"true"/"false" today, but a bare == "true" comparison silently
treats anything else -- "True", "TRUE", a trailing space from
a hand-edited file, a value some other tool wrote -- as off,
with no error and no visible difference from the checkbox
being genuinely unticked (discussion #785).
*/
bool infoFlagIsTrue(const QString &value);
QString ElementsAndConductorsSentence(int elements=0, QString ElementsAndConductorsSentence(int elements=0,
int conductors=0, int conductors=0,
int indi_texts=0, int indi_texts=0,
+25 -1
View File
@@ -43,6 +43,10 @@
#include "machine_info.h" #include "machine_info.h"
#include "TerminalStrip/ui/terminalstripeditorwindow.h" #include "TerminalStrip/ui/terminalstripeditorwindow.h"
#include "qetversion.h" #include "qetversion.h"
#ifdef QET_SPACEMOUSE_SUPPORT
# include "spacemouse/spacemouselistener.h"
# include "ui/configpage/spacemouseconfigpage.h"
#endif
#include "logging/qetlogger.h" #include "logging/qetlogger.h"
#include "logging/ui/diagnosticsreportdialog.h" #include "logging/ui/diagnosticsreportdialog.h"
@@ -164,7 +168,24 @@ QETApp::QETApp() :
m_splash_screen -> hide(); m_splash_screen -> hide();
} }
checkBackupFiles(); #ifdef QET_SPACEMOUSE_SUPPORT
//Always safe to construct: it silently does nothing when spacenavd
//isn't running or no device is attached, which is the common case
//even in a build with this feature compiled in. See
//SpaceMouseListener's class comment.
m_space_mouse_listener = new SpaceMouseListener(this);
#endif
//Deferred so this constructor returns before the prompts appear.
//checkBackupFiles() opens modal dialogs, and main() still has work to
//do once we return -- in particular connecting
//SingleApplication::receivedMessage to receiveMessage(). While those
//prompts were up that connection did not exist yet, so a file handed
//to the already-running instance during start-up was accepted by the
//socket and then dropped on the floor.
QMetaObject::invokeMethod(this, [this]() {
checkBackupFiles();
}, Qt::QueuedConnection);
} }
/** /**
@@ -2192,6 +2213,9 @@ void QETApp::configureQET()
cd.addPage(new ExportConfigPage()); cd.addPage(new ExportConfigPage());
cd.addPage(new PrintConfigPage()); cd.addPage(new PrintConfigPage());
cd.addPage(new ShortcutsConfigPage()); cd.addPage(new ShortcutsConfigPage());
#ifdef QET_SPACEMOUSE_SUPPORT
cd.addPage(new SpaceMouseConfigPage());
#endif
// associates the dialog with a possible parent widget // associates the dialog with a possible parent widget
// associe le dialogue a un eventuel widget parent // associe le dialogue a un eventuel widget parent
+10
View File
@@ -48,6 +48,9 @@ class QETProject;
class QETTitleBlockTemplateEditor; class QETTitleBlockTemplateEditor;
class QTextOrientationSpinBoxWidget; class QTextOrientationSpinBoxWidget;
class RecentFiles; class RecentFiles;
#ifdef QET_SPACEMOUSE_SUPPORT
class SpaceMouseListener;
#endif
/** /**
@brief The QETApp class @brief The QETApp class
@@ -229,6 +232,13 @@ class QETApp : public QObject
static TitleBlockTemplatesFilesCollection *m_company_tbt_collection; static TitleBlockTemplatesFilesCollection *m_company_tbt_collection;
static TitleBlockTemplatesFilesCollection *m_custom_tbt_collection; static TitleBlockTemplatesFilesCollection *m_custom_tbt_collection;
static ElementsCollectionCache *collections_cache_; static ElementsCollectionCache *collections_cache_;
#ifdef QET_SPACEMOUSE_SUPPORT
/// One per application, not per window: a physical 6-DOF device
/// is a single ambient input source, and motion is applied to
/// whichever DiagramView is currently active. See
/// SpaceMouseListener's class comment.
SpaceMouseListener *m_space_mouse_listener = nullptr;
#endif
static QMap<uint, QETProject *> registered_projects_; static QMap<uint, QETProject *> registered_projects_;
static uint next_project_id; static uint next_project_id;
static RecentFiles *m_projects_recent_files; static RecentFiles *m_projects_recent_files;
+12 -1
View File
@@ -1856,7 +1856,18 @@ void QETDiagramEditor::selectionGroupTriggered(QAction *action)
diagram->undoStack().push(c); diagram->undoStack().push(c);
} }
else if (value == "rotate_selected_text") else if (value == "rotate_selected_text")
diagram->undoStack().push(new RotateTextsCommand(diagram)); {
//Ask for the angle first, then build the command: the command
//itself no longer opens a dialog. Guarding on the selection keeps
//the previous behaviour of showing no dialog when there is
//nothing to rotate.
if (RotateTextsCommand::hasSelectedTexts(diagram))
{
qreal rotation = 0;
if (RotateTextsCommand::askRotation(rotation))
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
}
}
else if (value == "find_selected_element" && currentElement()) else if (value == "find_selected_element" && currentElement())
findElementInPanel(currentElement()->location()); findElementInPanel(currentElement()->location());
else if (value == "edit_selected_element") else if (value == "edit_selected_element")
@@ -33,6 +33,7 @@
#include <QDomElement> #include <QDomElement>
#include <QtCore/qnumeric.h> #include <QtCore/qnumeric.h>
#include <QGraphicsSceneMouseEvent> #include <QGraphicsSceneMouseEvent>
#include <QAbstractTextDocumentLayout>
/** /**
@brief DynamicElementTextItem::DynamicElementTextItem @brief DynamicElementTextItem::DynamicElementTextItem
@@ -732,19 +733,6 @@ void DynamicElementTextItem::paint(QPainter *painter, const QStyleOptionGraphics
{ {
DiagramTextItem::paint(painter, option, widget); DiagramTextItem::paint(painter, option, widget);
//Only ever repositions already-existing sibling items here --
//never adds or removes one. paint() runs while QGraphicsScene is
//iterating its item list to draw it, and mutating that list mid
//-iteration (which addResizeHandles()/removeResizeHandles() do,
//through QGraphicsScene::addItem()/removeItem()) crashes. An
//earlier version of this fix called them from here and crashed
//qelectrotech reproducibly on deselecting a text (SIGABRT); see
//refreshResizeHandlesVisibility() for where that now happens
//instead -- itemChange(), Qt's own safe hook for exactly this,
//already used below for this item's own selection.
if (m_left_resize_handle || m_right_resize_handle)
updateResizeHandlesPos();
if (m_frame) if (m_frame)
{ {
painter->save(); painter->save();
@@ -927,11 +915,17 @@ void DynamicElementTextItem::addResizeHandles()
for (QetGraphicsHandlerItem *handle : {m_left_resize_handle, m_right_resize_handle}) for (QetGraphicsHandlerItem *handle : {m_left_resize_handle, m_right_resize_handle})
{ {
scene()->addItem(handle); //Children of this text, not free scene items: Qt then carries
//them along when the parent element moves, rotates or is
//zoomed, and repaints their old and new area in the same
//update as this text. Moving free items from paint() instead
//left green fragments behind (qelectrotech#1002).
handle->setParentItem(this);
handle->setColor(Qt::darkGreen); handle->setColor(Qt::darkGreen);
handle->setZValue(zValue() + 1);
handle->installSceneEventFilter(this); handle->installSceneEventFilter(this);
} }
m_resize_handles_con = connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
this, &DynamicElementTextItem::updateResizeHandlesPos);
updateResizeHandlesPos(); updateResizeHandlesPos();
} }
@@ -941,6 +935,7 @@ void DynamicElementTextItem::addResizeHandles()
*/ */
void DynamicElementTextItem::removeResizeHandles() void DynamicElementTextItem::removeResizeHandles()
{ {
disconnect(m_resize_handles_con);
delete m_left_resize_handle; delete m_left_resize_handle;
delete m_right_resize_handle; delete m_right_resize_handle;
m_left_resize_handle = nullptr; m_left_resize_handle = nullptr;
@@ -973,8 +968,8 @@ void DynamicElementTextItem::updateResizeHandlesPos()
return; return;
QRectF br = boundingRect(); QRectF br = boundingRect();
m_left_resize_handle->setPos(mapToScene(QPointF(br.left(), br.center().y()))); m_left_resize_handle->setPos(br.left(), br.center().y());
m_right_resize_handle->setPos(mapToScene(QPointF(br.right(), br.center().y()))); m_right_resize_handle->setPos(br.right(), br.center().y());
} }
/** /**
@@ -200,6 +200,7 @@ class DynamicElementTextItem : public DiagramTextItem
qreal m_resize_original_width = -1; qreal m_resize_original_width = -1;
qreal m_resize_baseline_width = -1; qreal m_resize_baseline_width = -1;
qreal m_resize_start_local_x = 0; qreal m_resize_start_local_x = 0;
QMetaObject::Connection m_resize_handles_con;
}; };
#endif // DYNAMICELEMENTTEXTITEM_H #endif // DYNAMICELEMENTTEXTITEM_H
+44
View File
@@ -39,7 +39,9 @@
#include "elementtextitemgroup.h" #include "elementtextitemgroup.h"
#include "iostream" #include "iostream"
#include <QApplication>
#include <QCollator> #include <QCollator>
#include <QScreen>
static const QString plcTerminalKeys[] = { static const QString plcTerminalKeys[] = {
QETInformation::ELMT_PLC_T1, QETInformation::ELMT_PLC_T1,
@@ -209,6 +211,21 @@ void Element::editProperty()
//with the "text" tab of ElementPropertiesWidget, //with the "text" tab of ElementPropertiesWidget,
//the ui freeze, until user press escape key //the ui freeze, until user press escape key
dialog.setWindowModality(Qt::WindowModal); dialog.setWindowModality(Qt::WindowModal);
// A PLC master carries a 6-column IO table: without an explicit
// size the dialog falls back to its (cramped) sizeHint, so open it
// at three times its natural width instead. The height stays at
// the natural one, and the width never exceeds the screen.
const ElementData data = elementData();
if (data.m_type == ElementData::Master
&& data.m_master_type == ElementData::PLC) {
const QSize natural = dialog.sizeHint();
int width = natural.width() * 3;
if (QScreen *screen = QApplication::primaryScreen())
width = qMin(width, screen->availableGeometry().width());
dialog.resize(width, natural.height());
}
dialog.exec(); dialog.exec();
} }
} }
@@ -1369,6 +1386,33 @@ void Element::initLink(QETProject *prj)
tmp_uuids_link.clear(); tmp_uuids_link.clear();
} }
/**
@brief Element::initLink
Overload resolving tmp_uuids_link against @p candidates instead of a
project-wide ElementProvider search -- see the header comment for
why the search has to be scoped this way right after a paste or
folio-duplication XML round-trip, before uuids are renewed.
@param candidates the elements to search for a link partner in
*/
void Element::initLink(const QList<Element *> &candidates)
{
// if nothing to link return now
if (tmp_uuids_link.isEmpty()) return;
for (int i = 0; i < tmp_uuids_link.size(); ++i) {
for (Element *elmt : candidates) {
if (elmt->uuid() == tmp_uuids_link[i].uuid) {
elmt->linkToElement(this);
if (tmp_uuids_link[i].group_index >= 0) {
m_group_index_map[elmt] = tmp_uuids_link[i].group_index;
}
break;
}
}
}
tmp_uuids_link.clear();
}
/** /**
* @brief Element::linkTypeToString * @brief Element::linkTypeToString
* \deprecated use instead ElementData::typeToString * \deprecated use instead ElementData::typeToString
+19
View File
@@ -207,6 +207,25 @@ class Element : public QetGraphicsItem
virtual void unlinkAllElements() {} virtual void unlinkAllElements() {}
virtual void unlinkElement(Element *) {} virtual void unlinkElement(Element *) {}
virtual void initLink(QETProject *); virtual void initLink(QETProject *);
/**
Resolve tmp_uuids_link against a caller-supplied candidate
list instead of a project-wide search (bugtracker #607).
Used right after an XML round-trip (paste, folio
duplication) and before the pasted/duplicated elements'
uuids are renewed: at that moment a copy's tmp_uuids_link
still holds its source's original partner uuid, which
still matches the not-yet-renewed uuid of that partner's
own copy if it was carried along in the same batch.
Resolving only within @p candidates -- not the whole
project -- is what stops a linked pair pasted together
from matching an original element left elsewhere that
happens to still carry that same soon-to-be-replaced
uuid. If only one half of a linked group is in
@p candidates, its entry finds no match and is dropped,
same as initLink(QETProject *) leaving an unresolvable
link unlinked.
*/
void initLink(const QList<Element *> &candidates);
QList<Element *> linkedElements (); QList<Element *> linkedElements ();
int groupIndexForElement(Element *elmt) const; int groupIndexForElement(Element *elmt) const;
+2 -1
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "../qetgraphicsitem/terminal.h" #include "../qetgraphicsitem/terminal.h"
#include "../qet.h"
#include "../qetproject.h" #include "../qetproject.h"
#include "../conductorautonumerotation.h" #include "../conductorautonumerotation.h"
#include "../diagram.h" #include "../diagram.h"
@@ -986,7 +987,7 @@ QList<Terminal *> relatedPotentialTerminal (
else if (terminal -> parentElement() -> linkType() & Element::Terminale) else if (terminal -> parentElement() -> linkType() & Element::Terminale)
{ {
// English: Check if the user activated the potential isolation checkbox for this terminal // English: Check if the user activated the potential isolation checkbox for this terminal
if (terminal->parentElement()->elementInformations().value(QStringLiteral("potential_isolating")).toString() == QLatin1String("true")) { if (QET::infoFlagIsTrue(terminal->parentElement()->elementInformations().value(QStringLiteral("potential_isolating")).toString())) {
// English: Potential is isolated. Return an empty list so it does not propagate to the other side. // English: Potential is isolated. Return an empty list so it does not propagate to the other side.
return QList<Terminal *>(); return QList<Terminal *>();
} }
+153
View File
@@ -22,7 +22,9 @@
#include "autoNum/assignvariables.h" #include "autoNum/assignvariables.h"
#include "autoNum/numerotationcontext.h" #include "autoNum/numerotationcontext.h"
#include "autoNum/numerotationcontextcommands.h" #include "autoNum/numerotationcontextcommands.h"
#include "autoNum/renumberelementscommand.h"
#include "diagram.h" #include "diagram.h"
#include "qetgraphicsitem/element.h"
#include "qetapp.h" #include "qetapp.h"
#include "qetmessagebox.h" #include "qetmessagebox.h"
#include "qetresult.h" #include "qetresult.h"
@@ -40,7 +42,30 @@
#include <QHash> #include <QHash>
#include <QTimer> #include <QTimer>
#include <QtConcurrentRun> #include <QtConcurrentRun>
namespace {
/**
* @brief Reset numeric fields of a NumerotationContext so renumbering starts at 1.
* Keeps non-numeric parts (string/idfolio/folio/plant/locmach/elementline/elementcolumn/elementprefix) unchanged.
*/
NumerotationContext resetContextForRenumber(const NumerotationContext &tmpl)
{
NumerotationContext out = tmpl;
for (int i = 0; i < out.size(); ++i) {
const QStringList parts = out.itemAt(i);
if (parts.isEmpty()) continue;
const QString type = parts.at(0);
if (out.keyIsNumber(type)) {
out.replaceValue(i, QStringLiteral("1"));
}
}
return out;
}
} // namespace
#include <QtDebug> #include <QtDebug>
#include <algorithm>
#include <utility> #include <utility>
static int BACKUP_INTERVAL = 1200000; //interval in ms of backup = 20min static int BACKUP_INTERVAL = 1200000; //interval in ms of backup = 20min
@@ -132,6 +157,9 @@ QETProject::QETProject(const QString &path, QObject *parent) :
return; return;
} }
//The file just read already holds everything a crash could lose, so
//there is nothing to back up until the project is changed.
m_backup_needed = false;
init(); init();
} }
@@ -258,6 +286,19 @@ void QETProject::init()
m_undo_stack = new QUndoStack(this); m_undo_stack = new QUndoStack(this);
connect(m_undo_stack, &QUndoStack::cleanChanged, this, &QETProject::undoStackChanged); connect(m_undo_stack, &QUndoStack::cleanChanged, this, &QETProject::undoStackChanged);
//What counts as a change for writeBackup(): the undo stack moving,
//setModified(true), and the embedded collections, which can change
//without going through either.
const auto backup_needed = [this]() { m_backup_needed = true; };
connect(m_undo_stack, &QUndoStack::indexChanged, this, backup_needed);
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::changed, this, backup_needed);
connect(&m_titleblocks_collection, &TitleBlockTemplatesCollection::aboutToRemove, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::elementAdded, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::elementChanged, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::elementRemoved, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::directorieAdded, this, backup_needed);
connect(m_elements_collection, &XmlElementCollection::directoryRemoved, this, backup_needed);
m_save_backup_timer.setInterval(BACKUP_INTERVAL); m_save_backup_timer.setInterval(BACKUP_INTERVAL);
connect(&m_save_backup_timer, &QTimer::timeout, this, &QETProject::writeBackup); connect(&m_save_backup_timer, &QTimer::timeout, this, &QETProject::writeBackup);
m_save_backup_timer.start(); m_save_backup_timer.start();
@@ -793,6 +834,109 @@ void QETProject::setCurrrentElementAutonum(QString autoNum) {
m_current_element_autonum = std::move(autoNum); m_current_element_autonum = std::move(autoNum);
} }
/**
@brief QETProject::renumberElementsBySchemeTitle
Renumber existing elements by element autonumbering scheme title.
Elements do not store the scheme title; they store a "formula" (elementInformations["formula"]).
This method matches elements to schemes by comparing the stored formula with the formula derived
from each scheme's NumerotationContext.
If scheme_title is empty, all schemes are renumbered. Otherwise only that scheme is renumbered.
The operation is undoable.
*/
void QETProject::renumberElementsBySchemeTitle(const QString &scheme_title)
{
if (!m_undo_stack) return;
if (isReadOnly()) return;
// Build map: scheme title -> canonical formula
QHash<QString, QString> scheme_formula;
for (const QString &k : m_element_autonum.keys()) {
if (!scheme_title.isEmpty() && k != scheme_title) continue;
scheme_formula.insert(k, autonum::numerotationContextToFormula(m_element_autonum.value(k)));
}
if (scheme_formula.isEmpty()) return;
// Collect elements per scheme by formula match
QHash<QString, QVector<Element*>> by_key;
for (Diagram *d : diagrams()) {
if (!d) continue;
const auto items = d->items();
for (QGraphicsItem *it : items) {
auto *el = qgraphicsitem_cast<Element*>(it);
if (!el) continue;
if (el->linkType() == Element::Slave || (el->linkType() & Element::AllReport))
continue;
const QString el_formula = el->elementInformations().value(QStringLiteral("formula")).toString();
if (el_formula.isEmpty()) continue;
QString matched_key;
for (auto itf = scheme_formula.constBegin(); itf != scheme_formula.constEnd(); ++itf) {
if (itf.value() == el_formula) { matched_key = itf.key(); break; }
}
if (matched_key.isEmpty()) continue;
by_key[matched_key].append(el);
}
}
if (by_key.isEmpty()) return;
QVector<RenumberElementsCommand::ElementChange> changes;
QHash<QString, NumerotationContext> old_ctx;
QHash<QString, NumerotationContext> new_ctx;
for (auto it = by_key.constBegin(); it != by_key.constEnd(); ++it) {
old_ctx.insert(it.key(), m_element_autonum.value(it.key()));
}
for (auto it = by_key.begin(); it != by_key.end(); ++it) {
const QString key = it.key();
auto &elements = it.value();
std::sort(elements.begin(), elements.end(), [](Element *a, Element *b){ return comparPos(a, b); });
NumerotationContext base_tmpl = m_element_autonum.value(key);
NumerotationContext nc = resetContextForRenumber(base_tmpl);
NumerotationContextCommands ncc(nc);
for (Element *el : elements) {
RenumberElementsCommand::ElementChange ch;
ch.element = el;
ch.old_infos = el->elementInformations();
ch.old_seq = el->sequenceStruct();
ch.old_frozen = el->isFreezeLabel();
ch.new_frozen = ch.old_frozen; // preserve frozen state
const QString formula = ch.old_infos.value(QStringLiteral("formula")).toString();
autonum::sequentialNumbers new_seq;
new_seq.clear();
autonum::setSequential(formula, new_seq, nc, el->diagram(), key);
DiagramContext new_infos = ch.old_infos;
new_infos.addValue(QStringLiteral("label"), autonum::AssignVariables::formulaToLabel(formula, new_seq, el->diagram(), el, nullptr));
ch.new_infos = new_infos;
ch.new_seq = new_seq;
changes.append(ch);
// advance
nc = ncc.next();
ncc = NumerotationContextCommands(nc);
}
new_ctx.insert(key, nc);
}
if (changes.isEmpty()) return;
auto *cmd = new RenumberElementsCommand(
this,
changes,
old_ctx,
new_ctx,
scheme_title.isEmpty() ? tr("Renumber elements") : tr("Renumber elements (%1)").arg(scheme_title));
m_undo_stack->push(cmd);
}
/** /**
@brief QETProject::conductorAutoNumFormula @brief QETProject::conductorAutoNumFormula
@param key : autonum title @param key : autonum title
@@ -1564,6 +1708,9 @@ void QETProject::diagramOrderChanged(int old_index, int new_index) {
Mark this project as modified and emit the projectModified() signal. Mark this project as modified and emit the projectModified() signal.
*/ */
void QETProject::setModified(bool modified) { void QETProject::setModified(bool modified) {
if (modified) {
m_backup_needed = true;
}
if (m_modified != modified) { if (m_modified != modified) {
m_modified = modified; m_modified = modified;
emit projectModified(this, m_modified); emit projectModified(this, m_modified);
@@ -2146,6 +2293,12 @@ void QETProject::writeBackup()
//both would write through &m_backup_file on different threads. //both would write through &m_backup_file on different threads.
if (m_backup_future.isRunning()) if (m_backup_future.isRunning())
return; return;
//toXml() walks the whole project on the GUI thread, which freezes
//big projects for seconds (bugtracker #273, #329). A backup of an
//unchanged project would be identical to the last one, so skip it.
if (!m_backup_needed)
return;
m_backup_needed = false;
//Capture the document by value (implicitly shared, so cheap): the //Capture the document by value (implicitly shared, so cheap): the
//Qt5-style QtConcurrent::run(function, reference-args) call did not //Qt5-style QtConcurrent::run(function, reference-args) call did not
//survive the Qt6 API change, a lambda behaves identically on both. //survive the Qt6 API change, a lambda behaves identically on both.
+16
View File
@@ -169,6 +169,20 @@ class QETProject : public QObject
QString elementCurrentAutoNum() const; QString elementCurrentAutoNum() const;
void setCurrrentElementAutonum(QString autoNum); void setCurrrentElementAutonum(QString autoNum);
/**
* @brief Renumber existing elements by element autonumbering scheme.
*
* Elements do not store the scheme title but they store the corresponding formula.
* This operation matches elements to schemes by comparing the stored formula
* with the formula derived from the scheme's NumerotationContext.
*
* If @p scheme_title is empty, all schemes are renumbered.
* If @p scheme_title is non-empty, only that scheme is renumbered.
*
* The operation is undoable.
*/
void renumberElementsBySchemeTitle(const QString &scheme_title = QString());
//Element //Element
void freezeExistentElementLabel(bool freeze, int from, int to); void freezeExistentElementLabel(bool freeze, int from, int to);
void freezeNewElementLabel(bool freeze, int from, int to); void freezeNewElementLabel(bool freeze, int from, int to);
@@ -297,6 +311,8 @@ class QETProject : public QObject
private: private:
/// When false, writeBackup() is a no-op (set by the headless CLI) /// When false, writeBackup() is a no-op (set by the headless CLI)
static bool m_backup_enabled; static bool m_backup_enabled;
/// Something changed since the last backup, see writeBackup()
bool m_backup_needed = true;
/// File path this project is saved to /// File path this project is saved to
QString m_file_path; QString m_file_path;
/// Current state of the project /// Current state of the project
+31
View File
@@ -17,6 +17,8 @@
*/ */
#include "shortcutmanager.h" #include "shortcutmanager.h"
#include <QAbstractButton>
#include <QAction>
#include <QObject> #include <QObject>
#include <QSettings> #include <QSettings>
#include <QVariant> #include <QVariant>
@@ -166,3 +168,32 @@ void ShortcutManager::resetAllToDefaults()
resetToDefault(id); resetToDefault(id);
} }
} }
/**
@brief ShortcutManager::trigger
@param id
@return see the declaration's doc comment
*/
bool ShortcutManager::trigger(const QString &id) const
{
auto it = m_entries.find(id);
if (it == m_entries.end()) {
return false;
}
for (const QPointer<QObject> &target : qAsConst(it->targets))
{
if (!target) {
continue;
}
if (auto *action = qobject_cast<QAction *>(target.data())) {
action->trigger();
return true;
}
if (auto *button = qobject_cast<QAbstractButton *>(target.data())) {
button->click();
return true;
}
}
return false;
}
+14
View File
@@ -71,6 +71,20 @@ class ShortcutManager
void resetToDefault(const QString &id); void resetToDefault(const QString &id);
void resetAllToDefaults(); void resetAllToDefaults();
/// Activate the first still-alive target registered under \a id
/// -- QAction::trigger() or QAbstractButton::click(), whichever
/// it turns out to be -- for an input source other than the
/// keyboard (e.g. a 3D mouse button) that wants to invoke a
/// named action without knowing or caring which of the two it
/// is. Deliberately not disambiguated by the currently active
/// window when an id has several live targets: a target's
/// owning top-level window isn't reliably discoverable from a
/// bare QAction. Correct in the overwhelming common case of one
/// open editor window; a known simplification in the rarer
/// multi-window case, not a guaranteed-correct dispatch.
/// @return whether a live target was found and triggered.
bool trigger(const QString &id) const;
private: private:
ShortcutManager() = default; ShortcutManager() = default;
ShortcutManager(const ShortcutManager &) = delete; ShortcutManager(const ShortcutManager &) = delete;
+80
View File
@@ -0,0 +1,80 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPACEMOUSEBACKEND_H
#define SPACEMOUSEBACKEND_H
#include <QObject>
/**
@brief The SpaceMouseBackend class
Platform seam for discussion #599's 3D mouse support. A backend owns
one platform's connection to the actual 6-DOF device driver -- opening
it, pumping whatever event source that platform uses, closing it -- and
reports motion through the one signal below. Everything platform-
independent (which DiagramView to apply motion to, the pan/zoom
primitives to call, the Z-to-zoom-factor mapping) lives in
SpaceMouseListener instead, once, so it does not have to be duplicated
or re-verified per backend.
SpnavBackend (Linux, spacenavd/libspnav) is the only implementation so
far -- built, linked, and its "no daemon/device present" path actually
run in the environment this was written in. A Windows/macOS backend
(3Dconnexion's proprietary 3DxWare SDK) would implement this same
interface and be selected in SpaceMouseListener's constructor, without
changing SpnavBackend or anything downstream of the motion signal.
Deliberately not attempted here: this was written on Linux with no
3DxWare SDK and no Windows/macOS toolchain available to compile,
link, or run a single line of it against, and shipping platform code
that has never even built would be a materially different, weaker
thing than everything else in this feature.
*/
class SpaceMouseBackend : public QObject
{
Q_OBJECT
public:
explicit SpaceMouseBackend(QObject *parent = nullptr) : QObject(parent) {}
~SpaceMouseBackend() override = default;
/// True once this backend actually has a live connection to a
/// driver/daemon. False is the ordinary case -- no daemon
/// running, no device attached -- not an error; see
/// SpaceMouseListener's class comment for why that distinction
/// matters.
virtual bool isAvailable() const = 0;
signals:
/// One raw device sample. dx/dy are the two axes
/// SpaceMouseListener maps to horizontal/vertical pan, dz the
/// one it maps to zoom. Units and range are whatever the
/// backend's own driver reports -- SpaceMouseListener's own
/// scale/divisor constants are what turn them into pixels and a
/// zoom factor, not this signal.
void motion(int dx, int dy, int dz);
/// One device button was pressed. \a button is whatever index
/// the backend's own driver numbers it as -- there is no
/// portable numbering across device models, which is exactly
/// why the binding from a button to a QET action
/// (SpaceMouseButtonMap) is user-configurable rather than
/// hardcoded. Emitted on press only; release is not reported,
/// since nothing here has a use for it.
void buttonPressed(int button);
};
#endif // SPACEMOUSEBACKEND_H
@@ -0,0 +1,72 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spacemousebuttonmap.h"
#include <QSettings>
namespace {
const QString SETTINGS_GROUP = QStringLiteral("spacemouse/buttons/");
}
/**
@brief SpaceMouseButtonMap::actionId
@param button
@return see the declaration's doc comment
*/
QString SpaceMouseButtonMap::actionId(int button)
{
QSettings settings;
return settings.value(SETTINGS_GROUP + QString::number(button)).toString();
}
/**
@brief SpaceMouseButtonMap::setActionId
@param button
@param action_id
*/
void SpaceMouseButtonMap::setActionId(int button, const QString &action_id)
{
QSettings settings;
const QString key = SETTINGS_GROUP + QString::number(button);
if (action_id.isEmpty()) {
settings.remove(key);
} else {
settings.setValue(key, action_id);
}
}
/**
@brief SpaceMouseButtonMap::allBindings
@return see the declaration's doc comment
*/
QMap<int, QString> SpaceMouseButtonMap::allBindings()
{
QMap<int, QString> bindings;
QSettings settings;
settings.beginGroup(QStringLiteral("spacemouse/buttons"));
for (const QString &key : settings.childKeys())
{
bool ok = false;
const int button = key.toInt(&ok);
if (ok) {
bindings.insert(button, settings.value(key).toString());
}
}
settings.endGroup();
return bindings;
}
+55
View File
@@ -0,0 +1,55 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPACEMOUSEBUTTONMAP_H
#define SPACEMOUSEBUTTONMAP_H
#include <QMap>
#include <QString>
/**
@brief The SpaceMouseButtonMap class
Persisted mapping from a device button's raw index (backend- and
device-specific -- see SpaceMouseBackend::buttonPressed()) to a
ShortcutManager action id. Deliberately just a thin QSettings wrapper,
the same weight as ShortcutManager::savedSequence(): no caching, reads
and writes the setting directly on every call, since this is called at
most once per button press, never in a hot loop.
Unbound by default for every button on every device: nothing is bound
until the user opens Configuration > 3D Mouse and binds something --
see spacemouseconfigpage.h. A device compiled in and connected but
never configured here does nothing on any button press, matching the
rest of this feature's "silent until asked for" default.
*/
class SpaceMouseButtonMap
{
public:
/// @return the action id bound to \a button, or an empty string
/// if nothing is bound to it.
static QString actionId(int button);
/// Bind \a button to \a action_id. An empty \a action_id
/// removes the binding (equivalent to it never having been set).
static void setActionId(int button, const QString &action_id);
/// @return every currently bound button, for the configuration
/// page to list.
static QMap<int, QString> allBindings();
};
#endif // SPACEMOUSEBUTTONMAP_H
+153
View File
@@ -0,0 +1,153 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spacemouselistener.h"
#include "spacemousebackend.h"
#include "spacemousebuttonmap.h"
#ifdef QET_SPACEMOUSE_BACKEND_SPNAV
# include "spnavbackend.h"
#endif
#include "../diagramview.h"
#include "../projectview.h"
#include "../qetdiagrameditor.h"
#include "../shortcutmanager.h"
#include <QApplication>
#include <QScrollBar>
namespace {
//A device motion delta is an integer on roughly the same order of
//magnitude as a QWheelEvent::angleDelta() tick (about +-120 per
//detent, more under a hard push/twist -- exact range depends on the
//backend and the user's own driver-level sensitivity setting, which
//is configured outside QET and out of scope here).
//DiagramView::wheelEvent() already turns such a tick into a small
//per-event zoom step via zoom(1 + value/1000); reused as a starting
//point.
//
//Neither this divisor nor PAN_SCALE below has been calibrated
//against real hardware -- there is none in the environment this was
//built in. Both are named constants specifically so that is a
//one-line fix once someone with a device tries it.
constexpr qreal ZOOM_DIVISOR = 1000.0;
constexpr qreal PAN_SCALE = 1.0;
}
/**
@brief SpaceMouseListener::SpaceMouseListener
Construct whichever backend is available for this platform and connect
its motion() signal. If none is compiled in, or the one that is can't
reach a device, isAvailable() simply stays false -- see the class
comment.
@param parent
*/
SpaceMouseListener::SpaceMouseListener(QObject *parent) :
QObject(parent)
{
#ifdef QET_SPACEMOUSE_BACKEND_SPNAV
m_backend = new SpnavBackend(this);
#endif
//A future Windows/macOS backend (3Dconnexion's proprietary 3DxWare
//SDK) slots in here behind its own QET_SPACEMOUSE_BACKEND_3DXWARE
//guard, without changing anything below this constructor.
if (m_backend) {
connect(m_backend, &SpaceMouseBackend::motion,
this, &SpaceMouseListener::applyMotion);
connect(m_backend, &SpaceMouseBackend::buttonPressed,
this, &SpaceMouseListener::applyButton);
}
}
/**
@brief SpaceMouseListener::isAvailable
@return whether the platform backend has a live device connection
*/
bool SpaceMouseListener::isAvailable() const
{
return m_backend && m_backend->isAvailable();
}
/**
@brief SpaceMouseListener::zoomFactorForZAxis
@param z : raw Z-axis (push/pull) delta from a device motion sample
@return the multiplicative factor DiagramView::zoom() expects
*/
qreal SpaceMouseListener::zoomFactorForZAxis(int z)
{
return 1.0 + (static_cast<qreal>(z) / ZOOM_DIVISOR);
}
/**
@brief SpaceMouseListener::applyMotion
Apply one motion sample to whichever DiagramView is currently active.
X/Y translation pans it, Z translation zooms it -- the same two
primitives (scrollbars, DiagramView::zoom()) DiagramView::wheelEvent()
already drives from a physical wheel, so there is no new navigation
logic here, only a new input source feeding the existing one.
Which of a device's three translation axes is "left/right" vs
"forward/back" vs "up/down", and their sign, is a hardware convention
this could not be checked against real hardware while writing it -- see
the PR description.
@param dx
@param dy
@param dz
*/
void SpaceMouseListener::applyMotion(int dx, int dy, int dz)
{
auto *editor = qobject_cast<QETDiagramEditor *>(qApp->activeWindow());
if (!editor) {
return;
}
ProjectView *project_view = editor->currentProjectView();
if (!project_view) {
return;
}
DiagramView *view = project_view->currentDiagram();
if (!view) {
return;
}
if (dx || dy)
{
view->horizontalScrollBar()->setValue(
view->horizontalScrollBar()->value() - qRound(dx * PAN_SCALE));
view->verticalScrollBar()->setValue(
view->verticalScrollBar()->value() - qRound(dy * PAN_SCALE));
}
if (dz) {
view->zoom(zoomFactorForZAxis(dz));
}
}
/**
@brief SpaceMouseListener::applyButton
@param button
*/
void SpaceMouseListener::applyButton(int button)
{
const QString action_id = SpaceMouseButtonMap::actionId(button);
if (!action_id.isEmpty()) {
ShortcutManager::instance().trigger(action_id);
}
}
+89
View File
@@ -0,0 +1,89 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPACEMOUSELISTENER_H
#define SPACEMOUSELISTENER_H
#include <QObject>
class SpaceMouseBackend;
/**
@brief The SpaceMouseListener class
https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/599 :
bridges a 3Dconnexion SpaceMouse/SpacePilot 6-DOF device to
DiagramView's existing pan/zoom primitives (the same
horizontalScrollBar()/verticalScrollBar()/zoom() calls
DiagramView::wheelEvent() already uses for a physical wheel), and its
buttons to named QET actions via ShortcutManager -- the same registry
keyboard shortcuts already use, so a device button can trigger anything
in that registry (undo, redo, rotate selection, ...) without QET having
a second, device-specific action list.
Everything here is platform-independent: which DiagramView to apply
motion to, the pan/zoom calls, the Z-to-zoom-factor mapping, and button
dispatch via SpaceMouseButtonMap + ShortcutManager. Talking to the
actual device driver is a SpaceMouseBackend's job (see its class
comment) -- this class owns one and applies whatever it reports,
without knowing or caring which platform it came from.
Only compiled in when QET_SPACEMOUSE_SUPPORT is defined. Even then,
constructing one is always safe: if no backend is available for this
platform, or the one that exists can't reach a driver/daemon (no
device attached -- the expected state for the overwhelming majority of
users, even of a build with the option on), this silently does nothing
rather than failing or nagging the user. There is exactly one of
these, owned by QETApp, because a physical 6-DOF device is a single
ambient input source for the whole application, not something tied to
one window.
*/
class SpaceMouseListener : public QObject
{
Q_OBJECT
public:
explicit SpaceMouseListener(QObject *parent = nullptr);
~SpaceMouseListener() override = default;
/// True once the platform backend has a live connection to a
/// driver/daemon. False is the common case, not an error -- see
/// the class comment -- so callers should not warn the user
/// when this is false.
bool isAvailable() const;
/// Pure translation from a device Z-axis delta to the
/// multiplicative factor DiagramView::zoom() expects. A free
/// function so the mapping can be unit-tested without a live
/// backend connection or a real device.
static qreal zoomFactorForZAxis(int z);
private slots:
/// Apply one motion sample -- from whichever backend is in use
/// -- to whichever DiagramView is currently active.
void applyMotion(int dx, int dy, int dz);
/// Look up which action id, if any, SpaceMouseButtonMap binds
/// \a button to, and trigger it via ShortcutManager. Does
/// nothing for an unbound button -- see SpaceMouseButtonMap's
/// class comment on the "unbound by default" contract.
void applyButton(int button);
private:
SpaceMouseBackend *m_backend = nullptr;
};
#endif // SPACEMOUSELISTENER_H
+80
View File
@@ -0,0 +1,80 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spnavbackend.h"
#include <QSocketNotifier>
#include <spnav.h>
/**
@brief SpnavBackend::SpnavBackend
Try to connect to spacenavd. Failure -- no daemon running, no device
attached -- is left silent: it is the expected state for most users and
must never surface as an error dialog or a log warning on every
ordinary startup.
@param parent
*/
SpnavBackend::SpnavBackend(QObject *parent) :
SpaceMouseBackend(parent)
{
if (spnav_open() == -1) {
return;
}
m_available = true;
m_notifier = new QSocketNotifier(spnav_fd(), QSocketNotifier::Read, this);
connect(m_notifier, &QSocketNotifier::activated,
this, &SpnavBackend::readEvents);
}
/**
@brief SpnavBackend::~SpnavBackend
*/
SpnavBackend::~SpnavBackend()
{
if (m_available) {
spnav_close();
}
}
/**
@brief SpnavBackend::readEvents
Called when the spacenavd socket has data available. Drains every event
currently queued -- spnav_poll_event() returns 0 once the queue is
empty -- rather than handling just one per activation, so events cannot
silently back up if several arrive between two Qt event loop turns.
*/
void SpnavBackend::readEvents()
{
spnav_event event;
while (spnav_poll_event(&event))
{
if (event.type == SPNAV_EVENT_MOTION) {
//A 6-DOF device also reports rotation (rx, ry, rz); QET's
//view has nothing rotation maps to, so those three axes are
//read by nothing here.
emit motion(event.motion.x, event.motion.y, event.motion.z);
}
else if (event.type == SPNAV_EVENT_BUTTON && event.button.press) {
//Release (press == 0) is not reported -- SpaceMouseListener
//triggers a QET action on press, the same way a keyboard
//shortcut triggers on key-down, and has no use for release.
emit buttonPressed(event.button.bnum);
}
}
}
+58
View File
@@ -0,0 +1,58 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPNAVBACKEND_H
#define SPNAVBACKEND_H
#include "spacemousebackend.h"
class QSocketNotifier;
/**
@brief The SpnavBackend class
Linux SpaceMouseBackend, via the spacenavd daemon and libspnav. Only
compiled in when QET_SPACEMOUSE_BACKEND_SPNAV is defined (set by
cmake/find_spacemouse.cmake once it has actually found libspnav).
Connecting is always safe even when no daemon is running or no device
is attached -- the expected state for the overwhelming majority of
users, even of a build with 3D mouse support compiled in -- this
silently leaves isAvailable() false rather than failing loudly.
*/
class SpnavBackend : public SpaceMouseBackend
{
Q_OBJECT
public:
explicit SpnavBackend(QObject *parent = nullptr);
~SpnavBackend() override;
bool isAvailable() const override {return m_available;}
private slots:
/// Drain and emit every event currently queued on the spacenavd
/// socket. Connected to a QSocketNotifier on that socket's fd
/// rather than polled on a timer, so this is idle-cost-free
/// between events.
void readEvents();
private:
bool m_available = false;
QSocketNotifier *m_notifier = nullptr;
};
#endif // SPNAVBACKEND_H
+14 -2
View File
@@ -31,6 +31,7 @@
#include <QMenu> #include <QMenu>
#include <QPainter> #include <QPainter>
#include <QPixmap> #include <QPixmap>
#include <QSet>
namespace { namespace {
/** /**
@@ -167,11 +168,22 @@ void ConductorColorToolButton::applyColor(const QColor &color)
} }
DiagramContent dc(diagram); DiagramContent dc(diagram);
const auto conductors = dc.conductors(DiagramContent::AnyConductor); const auto selected_conductors = dc.conductors(DiagramContent::AnyConductor);
if (conductors.isEmpty()) { if (selected_conductors.isEmpty()) {
return; return;
} }
//A wire drawn across a junction is several Conductor objects
//sharing one electrical potential; selecting one segment must
//still recolour the whole potential, the way the F2 dialog's
//"apply to all" already does.
QSet<Conductor *> conductors;
for (Conductor *conductor : selected_conductors)
{
conductors << conductor;
conductors += conductor->relatedPotentialConductors();
}
QUndoCommand *undo = new QUndoCommand(tr("Modifier la couleur de %n conducteur(s)", QUndoCommand *undo = new QUndoCommand(tr("Modifier la couleur de %n conducteur(s)",
"undo caption", conductors.count())); "undo caption", conductors.count()));
int changed = 0; int changed = 0;
@@ -0,0 +1,223 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "spacemouseconfigpage.h"
#include "../../qeticons.h"
#include "../../shortcutmanager.h"
#include "../../spacemouse/spacemousebuttonmap.h"
#include <QComboBox>
#include <QFrame>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QSpinBox>
#include <QTableWidget>
#include <QToolButton>
#include <QVBoxLayout>
#include <algorithm>
namespace {
constexpr int COL_BUTTON = 0;
constexpr int COL_ACTION = 1;
constexpr int COL_REMOVE = 2;
//The combo box's first entry: no action bound, i.e. this row does
//nothing. Real action ids are never empty (ShortcutManager assigns
//them from source, not user input), so an empty string can never
//collide with a real one.
const QString UNBOUND_LABEL = QObject::tr("(Aucune action)", "spacemouse config: unbound button");
}
/**
@brief SpaceMouseConfigPage::SpaceMouseConfigPage
@param parent
*/
SpaceMouseConfigPage::SpaceMouseConfigPage(QWidget *parent) :
ConfigPage(parent)
{
auto *vlayout = new QVBoxLayout();
QLabel *title_label = new QLabel(this->title());
vlayout->addWidget(title_label);
QFrame *horiz_line = new QFrame();
horiz_line->setFrameShape(QFrame::HLine);
vlayout->addWidget(horiz_line);
auto *intro_label = new QLabel(
tr("Associez un numéro de bouton de votre souris 3D (SpaceMouse, SpacePilot...) "
"à une action de QElectroTech. Le numéro de bouton dépend de votre appareil "
"et de son pilote -- reportez-vous à sa documentation, ou essayez successivement "
"les valeurs à partir de 0.",
"spacemouse config page intro"));
intro_label->setWordWrap(true);
vlayout->addWidget(intro_label);
m_table = new QTableWidget(0, 3, this);
m_table->setHorizontalHeaderLabels({tr("N° bouton"), tr("Action"), QString()});
m_table->horizontalHeader()->setSectionResizeMode(COL_BUTTON, QHeaderView::ResizeToContents);
m_table->horizontalHeader()->setSectionResizeMode(COL_ACTION, QHeaderView::Stretch);
m_table->horizontalHeader()->setSectionResizeMode(COL_REMOVE, QHeaderView::ResizeToContents);
m_table->verticalHeader()->setVisible(false);
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_table->setSelectionMode(QAbstractItemView::NoSelection);
vlayout->addWidget(m_table);
auto *add_button = new QPushButton(QET::Icons::Add, tr("Ajouter une association"), this);
connect(add_button, &QPushButton::clicked, this, &SpaceMouseConfigPage::addRow);
auto *bottom_layout = new QHBoxLayout();
bottom_layout->addWidget(add_button);
bottom_layout->addStretch();
vlayout->addLayout(bottom_layout);
setLayout(vlayout);
populateTable();
}
SpaceMouseConfigPage::~SpaceMouseConfigPage()
{
}
/**
@brief SpaceMouseConfigPage::populateTable
Fill the table with one row per binding currently persisted in
SpaceMouseButtonMap.
*/
void SpaceMouseConfigPage::populateTable()
{
const QMap<int, QString> bindings = SpaceMouseButtonMap::allBindings();
for (auto it = bindings.constBegin(); it != bindings.constEnd(); ++it) {
appendRow(it.key(), it.value());
}
}
/**
@brief SpaceMouseConfigPage::appendRow
Add one row bound to \a button / \a action_id (either or both may be
the "unset" value -- 0 and an empty string respectively -- for a fresh
row added via the "+" button).
@param button
@param action_id
*/
void SpaceMouseConfigPage::appendRow(int button, const QString &action_id)
{
const int row = m_table->rowCount();
m_table->setRowCount(row + 1);
auto *button_spin = new QSpinBox(m_table);
button_spin->setRange(0, 999);
button_spin->setValue(button);
m_table->setCellWidget(row, COL_BUTTON, button_spin);
auto *action_combo = new QComboBox(m_table);
action_combo->addItem(UNBOUND_LABEL, QString());
QList<ShortcutManager::ShortcutInfo> shortcuts = ShortcutManager::instance().allShortcuts();
std::sort(shortcuts.begin(), shortcuts.end(),
[](const ShortcutManager::ShortcutInfo &a, const ShortcutManager::ShortcutInfo &b) {
if (a.category != b.category) {
return a.category < b.category;
}
return a.description < b.description;
});
for (const ShortcutManager::ShortcutInfo &info : shortcuts) {
action_combo->addItem(
QStringLiteral("%1 — %2").arg(info.category, info.description), info.id);
}
int index_for_current = action_combo->findData(action_id);
action_combo->setCurrentIndex(index_for_current >= 0 ? index_for_current : 0);
m_table->setCellWidget(row, COL_ACTION, action_combo);
auto *remove_button = new QToolButton(m_table);
remove_button->setIcon(QET::Icons::EditTableDeleteRow);
remove_button->setToolTip(tr("Supprimer cette association"));
connect(remove_button, &QToolButton::clicked, this, &SpaceMouseConfigPage::removeSelectedRow);
m_table->setCellWidget(row, COL_REMOVE, remove_button);
}
/**
@brief SpaceMouseConfigPage::addRow
Add a fresh, unbound row for the user to fill in.
*/
void SpaceMouseConfigPage::addRow()
{
appendRow(0, QString());
}
/**
@brief SpaceMouseConfigPage::removeSelectedRow
Remove whichever row owns the remove button that was clicked. Found by
looking the sender up in the button column rather than tracked
per-button state, so nothing has to be kept in sync as rows are added
and removed around it.
*/
void SpaceMouseConfigPage::removeSelectedRow()
{
auto *button = qobject_cast<QToolButton *>(sender());
if (!button) {
return;
}
for (int row = 0; row < m_table->rowCount(); ++row) {
if (m_table->cellWidget(row, COL_REMOVE) == button) {
m_table->removeRow(row);
return;
}
}
}
/**
@brief SpaceMouseConfigPage::applyConf
Persist exactly what the table currently shows: every previously saved
binding is cleared first, then every row still in the table (after any
adds/edits/removals) is written back. Simpler and less error-prone than
tracking which individual rows changed, and cheap -- this runs once,
when the user validates the surrounding ConfigDialog, not on every
keystroke.
*/
void SpaceMouseConfigPage::applyConf()
{
const QMap<int, QString> previous = SpaceMouseButtonMap::allBindings();
for (auto it = previous.constBegin(); it != previous.constEnd(); ++it) {
SpaceMouseButtonMap::setActionId(it.key(), QString());
}
for (int row = 0; row < m_table->rowCount(); ++row)
{
auto *button_spin = qobject_cast<QSpinBox *>(m_table->cellWidget(row, COL_BUTTON));
auto *action_combo = qobject_cast<QComboBox *>(m_table->cellWidget(row, COL_ACTION));
if (!button_spin || !action_combo) {
continue;
}
SpaceMouseButtonMap::setActionId(button_spin->value(), action_combo->currentData().toString());
}
}
QString SpaceMouseConfigPage::title() const
{
return tr("Souris 3D", "configuration page title");
}
QIcon SpaceMouseConfigPage::icon() const
{
return QET::Icons::ConfigureToolbars;
}
@@ -0,0 +1,69 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPACEMOUSECONFIGPAGE_H
#define SPACEMOUSECONFIGPAGE_H
#include "configpage.h"
class QTableWidget;
/**
@brief The SpaceMouseConfigPage class
Configuration page listing every device-button-to-action binding
(SpaceMouseButtonMap), letting the user add, edit and remove them.
Modelled directly on ShortcutsConfigPage, one row per binding instead
of one row per shortcut, since the two are close cousins: both end in
the same place -- ShortcutManager::trigger()/registerAction() -- just
from a different input source. Bindings are only persisted (via
SpaceMouseButtonMap::setActionId()) when applyConf() runs, i.e. when
the user validates the surrounding ConfigDialog.
Only compiled in, and only added to the Configuration dialog, when
QET_SPACEMOUSE_SUPPORT is defined -- see qetapp.cpp.
Button numbering is intentionally not fixed to any particular device:
it is exactly whatever integer SpaceMouseBackend::buttonPressed()
reports for the button pressed, which is backend- and device-specific.
Rows are added/removed freely rather than the page assuming a button
count, since that varies from 2 (SpaceMouse Compact) to over 30
(SpacePilot Pro).
*/
class SpaceMouseConfigPage : public ConfigPage
{
Q_OBJECT
public:
explicit SpaceMouseConfigPage(QWidget *parent = nullptr);
~SpaceMouseConfigPage() override;
void applyConf() override;
QString title() const override;
QIcon icon() const override;
private slots:
void addRow();
void removeSelectedRow();
private:
void populateTable();
void appendRow(int button, const QString &action_id);
QTableWidget *m_table;
};
#endif // SPACEMOUSECONFIGPAGE_H
+41 -2
View File
@@ -23,6 +23,7 @@
#include "../qetdiagrameditor.h" #include "../qetdiagrameditor.h"
#include "../projectview.h" #include "../projectview.h"
#include "../qetproject.h" #include "../qetproject.h"
#include "../utils/qetsettings.h"
#include <QApplication> #include <QApplication>
#include <QColorDialog> #include <QColorDialog>
@@ -62,8 +63,28 @@ DiagramBgColorToolButton::DiagramBgColorToolButton(QETDiagramEditor *editor, QWi
setStatusTip(tr("Choisir la couleur de fond du folio", setStatusTip(tr("Choisir la couleur de fond du folio",
"status bar tip")); "status bar tip"));
m_is_system_color = true; //main() already restored the stored choice onto the global
m_current = QApplication::palette().color(QPalette::Base); //Diagram::background_color and PaletteGraphicsView flag; mirror it
//here so the button opens on what the editor actually draws, rather
//than always claiming "system colour" as it used to.
if (PaletteGraphicsView::customBackgroundColor()) {
m_is_system_color = false;
m_current = Diagram::background_color;
} else {
m_is_system_color = true;
m_current = QApplication::palette().color(QPalette::Base);
}
for (const QString &name : QetSettings::sheetBackgroundRecentColors())
{
const QColor c(name);
if (c.isValid()) {
m_recent.append(c);
}
}
while (m_recent.size() > MAX_RECENT) {
m_recent.removeLast();
}
setMenu(new QMenu(this)); setMenu(new QMenu(this));
rebuildMenu(); rebuildMenu();
setSwatch(m_current); setSwatch(m_current);
@@ -123,6 +144,7 @@ void DiagramBgColorToolButton::applyColor(const QColor &color)
PaletteGraphicsView::setCustomBackgroundColor(true); PaletteGraphicsView::setCustomBackgroundColor(true);
Diagram::background_color = color; Diagram::background_color = color;
QetSettings::setSheetBackground(QetSettings::SheetBackground{color, true});
QETDiagramEditor *editor = m_editor; QETDiagramEditor *editor = m_editor;
if (!editor) { if (!editor) {
@@ -146,6 +168,8 @@ void DiagramBgColorToolButton::applySystemColor()
PaletteGraphicsView::setCustomBackgroundColor(false); PaletteGraphicsView::setCustomBackgroundColor(false);
Diagram::background_color = Qt::white; Diagram::background_color = Qt::white;
QetSettings::setSheetBackground(
QetSettings::SheetBackground{QColor(Qt::white), false});
QETDiagramEditor *editor = m_editor; QETDiagramEditor *editor = m_editor;
if (!editor) { if (!editor) {
@@ -187,6 +211,21 @@ void DiagramBgColorToolButton::rememberRecent(const QColor &color)
m_recent.removeLast(); m_recent.removeLast();
} }
rebuildMenu(); rebuildMenu();
persistRecent();
}
/**
@brief DiagramBgColorToolButton::persistRecent
Write the "recently used" colours back to the settings, so they are
still there on the next start instead of starting out empty.
*/
void DiagramBgColorToolButton::persistRecent()
{
QStringList names;
for (const QColor &c : std::as_const(m_recent)) {
names << c.name();
}
QetSettings::setSheetBackgroundRecentColors(names);
} }
/** /**
+1
View File
@@ -52,6 +52,7 @@ class DiagramBgColorToolButton : public QToolButton
void applySystemColor(); void applySystemColor();
void chooseOtherColor(); void chooseOtherColor();
void rememberRecent(const QColor &color); void rememberRecent(const QColor &color);
void persistRecent();
void setSwatch(const QColor &color); void setSwatch(const QColor &color);
static QIcon swatchIcon(const QColor &color); static QIcon swatchIcon(const QColor &color);
+4 -3
View File
@@ -16,6 +16,7 @@
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>. along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "elementinfowidget.h" #include "elementinfowidget.h"
#include "../qet.h"
#include <QCheckBox> #include <QCheckBox>
#include <QPushButton> #include <QPushButton>
#include "../diagram.h" #include "../diagram.h"
@@ -366,18 +367,18 @@ void ElementInfoWidget::updateUi()
// Load the lock status for auto numbering // Load the lock status for auto numbering
if (m_element->elementData().m_type == ElementData::Terminal) { if (m_element->elementData().m_type == ElementData::Terminal) {
QString lock_value = element_info.value(QStringLiteral("auto_num_locked")).toString(); QString lock_value = element_info.value(QStringLiteral("auto_num_locked")).toString();
ui->m_auto_num_locked_cb->setChecked(lock_value == QLatin1String("true")); ui->m_auto_num_locked_cb->setChecked(QET::infoFlagIsTrue(lock_value));
// English: Load the potential isolating status from the element information mapping // English: Load the potential isolating status from the element information mapping
if (m_potential_isolating_cb) { if (m_potential_isolating_cb) {
QString isolating_value = element_info.value(QStringLiteral("potential_isolating")).toString(); QString isolating_value = element_info.value(QStringLiteral("potential_isolating")).toString();
m_potential_isolating_cb->setChecked(isolating_value == QLatin1String("true")); m_potential_isolating_cb->setChecked(QET::infoFlagIsTrue(isolating_value));
} }
} }
// English: Load the BOM exclusion status from the element information mapping // English: Load the BOM exclusion status from the element information mapping
if (m_exclude_from_bom_cb) { if (m_exclude_from_bom_cb) {
QString exclude_bom_value = element_info.value(QStringLiteral("exclude_from_bom")).toString(); QString exclude_bom_value = element_info.value(QStringLiteral("exclude_from_bom")).toString();
m_exclude_from_bom_cb->setChecked(exclude_bom_value == QLatin1String("true")); m_exclude_from_bom_cb->setChecked(QET::infoFlagIsTrue(exclude_bom_value));
} }
if (m_live_edit) { if (m_live_edit) {
+29 -1
View File
@@ -539,7 +539,35 @@ void MasterPropertiesWidget::updateUi()
tr("Type"), tr("Adresse"), tr("Fonction"), tr("Type"), tr("Adresse"), tr("Fonction"),
tr("Commentaire"), tr("Réf. croisée"), tr("Bornes") tr("Commentaire"), tr("Réf. croisée"), tr("Bornes")
}); });
m_plc_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); m_plc_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);
m_plc_table->horizontalHeader()->setSectionsMovable(true);
m_plc_table->horizontalHeader()->resizeSection(0, 120);
m_plc_table->horizontalHeader()->resizeSection(1, 100);
m_plc_table->horizontalHeader()->resizeSection(2, 150);
m_plc_table->horizontalHeader()->resizeSection(3, 150);
m_plc_table->horizontalHeader()->resizeSection(4, 100);
m_plc_table->horizontalHeader()->resizeSection(5, 120);
// Restore a previously saved column layout, if any
auto *plc_hdr = m_plc_table->horizontalHeader();
QSettings plc_settings;
const QVariant plc_hdr_state = plc_settings.value(
QStringLiteral("masterpropertieswidget/plc-table-header-state"));
if (!plc_hdr_state.isNull())
plc_hdr->restoreState(plc_hdr_state.toByteArray());
// Persist the column layout whenever the user resizes or moves a column
auto save_plc_header_state = [plc_hdr]()
{
QSettings settings;
settings.setValue(QStringLiteral("masterpropertieswidget/plc-table-header-state"),
plc_hdr->saveState());
};
connect(plc_hdr, &QHeaderView::sectionResized,
this, save_plc_header_state);
connect(plc_hdr, &QHeaderView::sectionMoved,
this, save_plc_header_state);
m_plc_table->setSelectionBehavior(QAbstractItemView::SelectItems); m_plc_table->setSelectionBehavior(QAbstractItemView::SelectItems);
m_plc_table->setSelectionMode(QAbstractItemView::ExtendedSelection); m_plc_table->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_plc_table->setMinimumHeight(200); m_plc_table->setMinimumHeight(200);
+3 -2
View File
@@ -1,5 +1,6 @@
#include "terminalnumberingdialog.h" #include "terminalnumberingdialog.h"
#include "ui_terminalnumberingdialog.h" #include "ui_terminalnumberingdialog.h"
#include "../qet.h"
#include "../qetproject.h" #include "../qetproject.h"
#include "../diagram.h" #include "../diagram.h"
#include "../qetgraphicsitem/element.h" #include "../qetgraphicsitem/element.h"
@@ -34,7 +35,7 @@ TerminalNumberingDialog::TerminalNumberingDialog(QWidget *parent, QETProject *pr
if (elmt->elementData().m_type == ElementData::Terminal) { if (elmt->elementData().m_type == ElementData::Terminal) {
// Ignore locked terminals // Ignore locked terminals
DiagramContext info = elmt->elementInformations(); DiagramContext info = elmt->elementInformations();
if (info.value(QStringLiteral("auto_num_locked")).toString() == QLatin1String("true")) { if (QET::infoFlagIsTrue(info.value(QStringLiteral("auto_num_locked")).toString())) {
continue; continue;
} }
@@ -158,7 +159,7 @@ QUndoCommand* TerminalNumberingDialog::getUndoCommand(QETProject *project) const
DiagramContext info = elmt->elementInformations(); DiagramContext info = elmt->elementInformations();
// Ignore locked terminals (if the user checked a 'lock' property) // Ignore locked terminals (if the user checked a 'lock' property)
if (info.value(QStringLiteral("auto_num_locked")).toString() == QLatin1String("true")) { if (QET::infoFlagIsTrue(info.value(QStringLiteral("auto_num_locked")).toString())) {
continue; continue;
} }
+47 -9
View File
@@ -25,15 +25,34 @@
#include "../qetgraphicsitem/elementtextitemgroup.h" #include "../qetgraphicsitem/elementtextitemgroup.h"
#include "../qtextorientationspinboxwidget.h" #include "../qtextorientationspinboxwidget.h"
/**
@brief RotateTextsCommand::hasSelectedTexts
@param diagram
@return true if @p diagram has at least one selected text or text group.
Lets a caller decide whether to ask the user for an angle at all, keeping
the previous behaviour where no dialog appeared for an empty selection.
*/
bool RotateTextsCommand::hasSelectedTexts(Diagram *diagram)
{
if(!diagram)
return false;
DiagramContent dc(diagram);
return (!dc.selectedTexts().isEmpty() || !dc.selectedTextsGroup().isEmpty());
}
/** /**
@brief RotateTextsCommand::RotateTextsCommand @brief RotateTextsCommand::RotateTextsCommand
@param diagram : Apply the rotation to the selected texts and group of texts @param diagram : Apply the rotation to the selected texts and group of texts
of diagram at construction time. of diagram at construction time.
@param rotation : the angle to apply, in degrees. Obtain it from
askRotation() for interactive use; pass it directly from a test or script.
@param parent : undo parent @param parent : undo parent
*/ */
RotateTextsCommand::RotateTextsCommand(Diagram *diagram, QUndoCommand *parent) : RotateTextsCommand::RotateTextsCommand(Diagram *diagram, qreal rotation, QUndoCommand *parent) :
QUndoCommand(parent), QUndoCommand(parent),
m_diagram(diagram) m_diagram(diagram),
m_rotation(rotation)
{ {
DiagramContent dc(m_diagram); DiagramContent dc(m_diagram);
QList <DiagramTextItem *> texts_list; QList <DiagramTextItem *> texts_list;
@@ -53,8 +72,6 @@ m_diagram(diagram)
if(texts_list.count() || groups_list.count()) if(texts_list.count() || groups_list.count())
{ {
openDialog();
QStringList parts; QStringList parts;
if (texts_list.count()) if (texts_list.count())
parts << QObject::tr("%n texte(s)", "", texts_list.count()); parts << QObject::tr("%n texte(s)", "", texts_list.count());
@@ -76,6 +93,12 @@ void RotateTextsCommand::undo()
if(m_diagram) if(m_diagram)
m_diagram.data()->showMe(); m_diagram.data()->showMe();
//Nothing was selected at construction time: there is no animation to
//run. QUndoStack::push() calls redo() before it discards an obsolete
//command, so this has to be survivable rather than assumed away.
if(!m_anim_group)
return;
m_anim_group->setDirection(QAnimationGroup::Backward); m_anim_group->setDirection(QAnimationGroup::Backward);
m_anim_group->start(); m_anim_group->start();
@@ -88,6 +111,9 @@ void RotateTextsCommand::redo()
if(m_diagram) if(m_diagram)
m_diagram.data()->showMe(); m_diagram.data()->showMe();
if(!m_anim_group)
return;
m_anim_group->setDirection(QAnimationGroup::Forward); m_anim_group->setDirection(QAnimationGroup::Forward);
m_anim_group->start(); m_anim_group->start();
@@ -95,7 +121,18 @@ void RotateTextsCommand::redo()
cti->forceRotateByUser(true); cti->forceRotateByUser(true);
} }
void RotateTextsCommand::openDialog() /**
@brief RotateTextsCommand::askRotation
Ask the user for an orientation.
@param rotation : set to the chosen angle when the dialog is accepted,
left untouched otherwise.
@return true if the user accepted, false if they cancelled.
Deliberately static and separate from the command: a QUndoCommand that
blocks on a modal in its constructor cannot be built by a test, a script,
or any headless caller.
*/
bool RotateTextsCommand::askRotation(qreal &rotation)
{ {
//Open the dialog //Open the dialog
QDialog ori_text_dialog; QDialog ori_text_dialog;
@@ -120,10 +157,11 @@ void RotateTextsCommand::openDialog()
layout_v.addStretch(); layout_v.addStretch();
layout_v.addWidget(&buttons); layout_v.addWidget(&buttons);
if (ori_text_dialog.exec() == QDialog::Accepted) if (ori_text_dialog.exec() != QDialog::Accepted)
m_rotation = ori_widget->orientation(); return false;
else
setObsolete(true); rotation = ori_widget->orientation();
return true;
} }
void RotateTextsCommand::setupAnimation(QObject *target, const QByteArray &propertyName, const QVariant& start, const QVariant& end) void RotateTextsCommand::setupAnimation(QObject *target, const QByteArray &propertyName, const QVariant& start, const QVariant& end)
+23 -4
View File
@@ -28,19 +28,38 @@ class QParallelAnimationGroup;
/** /**
@brief The RotateTextsCommand class @brief The RotateTextsCommand class
Open a dialog for edit the rotation of the current selected texts and texts group in diagram. Apply @p rotation to the currently selected texts and texts group of a
Just instantiate this undo command and push it in a QUndoStack. diagram. Just instantiate this undo command and push it in a QUndoStack.
This command does not ask the user for anything: obtaining the angle is the
caller's job, via the askRotation() helper below. Keeping the dialog out of
the constructor is what makes the command usable outside an interactive
session — from a test harness, a regression sweep, or a script — and stops
a headless caller from blocking forever on a modal nobody can answer.
Typical interactive use:
@code
if (RotateTextsCommand::hasSelectedTexts(diagram)) {
qreal rotation = 0;
if (RotateTextsCommand::askRotation(rotation))
diagram->undoStack().push(new RotateTextsCommand(diagram, rotation));
}
@endcode
*/ */
class RotateTextsCommand : public QUndoCommand class RotateTextsCommand : public QUndoCommand
{ {
public: public:
RotateTextsCommand(Diagram *diagram, QUndoCommand *parent=nullptr); RotateTextsCommand(Diagram *diagram, qreal rotation, QUndoCommand *parent=nullptr);
/// @return true if @p diagram has at least one selected text or text group to rotate.
static bool hasSelectedTexts(Diagram *diagram);
/// Open the orientation dialog. @return true and set @p rotation if accepted, false if cancelled.
static bool askRotation(qreal &rotation);
void undo() override; void undo() override;
void redo() override; void redo() override;
private: private:
void openDialog();
void setupAnimation(QObject *target, const QByteArray &propertyName, const QVariant& start, const QVariant& end); void setupAnimation(QObject *target, const QByteArray &propertyName, const QVariant& start, const QVariant& end);
private: private:
+63
View File
@@ -152,4 +152,67 @@ namespace QetSettings
QSettings settings; QSettings settings;
settings.setValue("scripting/enabled", enabled); settings.setValue("scripting/enabled", enabled);
} }
/**
* @brief setSheetBackground
* Store the sheet background last picked in the diagram editor, so the
* next start opens on it for every project, old or new. @sa sheetBackground
* @param background
*/
void setSheetBackground(const SheetBackground &background)
{
QSettings settings;
//HexRgb, not HexArgb, on purpose: Diagram::background_color is
//temporarily given an alpha of 0 by the SVG export to make the
//background transparent, and that transient value must never end
//up here as a stored transparent sheet.
settings.setValue(QStringLiteral("diagrameditor/sheet_background_color"),
background.color.name(QColor::HexRgb));
settings.setValue(QStringLiteral("diagrameditor/sheet_background_custom"),
background.custom);
}
/**
* @brief sheetBackground
* @return the sheet background stored by setSheetBackground(), or the
* built-in default (white, following the system) when there is none.
* @sa setSheetBackground
*/
SheetBackground sheetBackground()
{
QSettings settings;
SheetBackground background;
background.custom = settings.value(
QStringLiteral("diagrameditor/sheet_background_custom"),
false).toBool();
const QColor stored(settings.value(
QStringLiteral("diagrameditor/sheet_background_color"),
QStringLiteral("#ffffff")).toString());
background.color = stored.isValid() ? stored : QColor(Qt::white);
return background;
}
/**
* @brief sheetBackgroundRecentColors
* @return the "recently used" sheet background colours, most recent
* first. Empty until the user has picked a non-preset colour.
* @sa setSheetBackgroundRecentColors
*/
QStringList sheetBackgroundRecentColors()
{
QSettings settings;
return settings.value(QStringLiteral("diagrameditor/sheet_background_recent")).toStringList();
}
/**
* @brief setSheetBackgroundRecentColors
* Store the "recently used" sheet background colours, most recent first.
* @sa sheetBackgroundRecentColors
* @param colors
*/
void setSheetBackgroundRecentColors(const QStringList &colors)
{
QSettings settings;
settings.setValue(QStringLiteral("diagrameditor/sheet_background_recent"), colors);
}
} }
+24
View File
@@ -19,6 +19,8 @@
#define QETSETTINGS_H #define QETSETTINGS_H
#include <Qt> #include <Qt>
#include <QColor>
#include <QStringList>
/** /**
*Interface to use QSettings in QElectroTech *Interface to use QSettings in QElectroTech
@@ -36,6 +38,28 @@ namespace QetSettings
bool scriptingEnabled(); bool scriptingEnabled();
void setScriptingEnabled(bool enabled); void setScriptingEnabled(bool enabled);
bool scriptingForcedByEnvironment(); bool scriptingForcedByEnvironment();
/**
@brief The sheet (folio) background the diagram editors draw.
Both halves are needed, not just the colour: with custom == false
the sheet follows the system ("Couleur systeme") and the views are
free to invert the lightness on a dark palette, while the very same
#ffffff with custom == true is an explicit "always white" that
must not be inverted. Storing only the colour would collapse the
two on the next start.
*/
struct SheetBackground
{
QColor color;
bool custom = false;
};
SheetBackground sheetBackground();
void setSheetBackground(const SheetBackground &background);
QStringList sheetBackgroundRecentColors();
void setSheetBackgroundRecentColors(const QStringList &colors);
} }
#endif // QETSETTINGS_H #endif // QETSETTINGS_H
+11
View File
@@ -170,6 +170,17 @@ add_test(NAME tst_scriptingsetting COMMAND tst_scriptingsetting)
target_include_directories(tst_scriptingsetting PRIVATE ${QET_DIR}/sources) target_include_directories(tst_scriptingsetting PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_scriptingsetting PRIVATE Qt::Test Qt::Gui) target_link_libraries(tst_scriptingsetting PRIVATE Qt::Test Qt::Gui)
# QetSettings::sheetBackground() -- the sheet (folio) background the diagram
# editor remembers between runs. Compiles qetsettings.cpp alone for the same
# reason as the test above; Qt::Gui because the pair carries a QColor.
add_executable(
tst_sheetbackgroundsetting
tst_sheetbackgroundsetting.cpp
${QET_DIR}/sources/utils/qetsettings.cpp)
add_test(NAME tst_sheetbackgroundsetting COMMAND tst_sheetbackgroundsetting)
target_include_directories(tst_sheetbackgroundsetting PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_sheetbackgroundsetting PRIVATE Qt::Test Qt::Gui)
# CrashHandler::formatInt() -- the async-signal-safe decimal formatter the # CrashHandler::formatInt() -- the async-signal-safe decimal formatter the
# signal handler uses for the "Signal: N" line of a crash dump. Compiles # signal handler uses for the "Signal: N" line of a crash dump. Compiles
# crashhandler.cpp and logring.cpp alongside; the handler deliberately # crashhandler.cpp and logring.cpp alongside; the handler deliberately
+160
View File
@@ -0,0 +1,160 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
/*
QetSettings::sheetBackground() -- the sheet (folio) background the
diagram editor remembers between runs, so the user does not have to
pick it again on every start.
Two properties are pinned here, and both are why this is a pair of
values rather than a single colour:
* The custom flag. "#ffffff" picked explicitly ("always white, never
inverted on a dark palette") and "#ffffff" left to follow the
system (where the views invert the lightness) are one colour and
two behaviours. Storing only the colour would silently turn one
into the other on the next start -- which is exactly the reported
bug, one step removed.
* The alpha being dropped. The SVG export gives
Diagram::background_color an alpha of 0 to render a transparent
background, and that transient value must not be persisted as a
permanently transparent sheet.
This test owns its own QSettings scope (organization + application
name), so it cannot read or write the configuration of whoever runs
it.
*/
#include "utils/qetsettings.h"
#include <QtTest>
#include <QSettings>
class TstSheetBackgroundSetting : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
void init();
void defaultsToWhiteFollowingTheSystem();
void colourAndCustomFlagAreStoredTogether();
void sameColourStillDistinguishesTheTwoModes();
void alphaIsNotStored();
void unreadableColourFallsBackToWhite();
void recentColorsDefaultToEmpty();
void recentColorsKeepTheirOrder();
};
void TstSheetBackgroundSetting::initTestCase()
{
// A scope of this test's own: whatever this writes must not land in
// the configuration of the account running the suite.
QCoreApplication::setOrganizationName(
QStringLiteral("QElectroTech-tst_sheetbackgroundsetting"));
QCoreApplication::setApplicationName(
QStringLiteral("tst_sheetbackgroundsetting"));
QSettings settings;
settings.clear();
}
void TstSheetBackgroundSetting::init()
{
QSettings settings;
settings.remove(QStringLiteral("diagrameditor/sheet_background_color"));
settings.remove(QStringLiteral("diagrameditor/sheet_background_custom"));
settings.remove(QStringLiteral("diagrameditor/sheet_background_recent"));
settings.sync();
}
void TstSheetBackgroundSetting::defaultsToWhiteFollowingTheSystem()
{
// An untouched installation, i.e. every existing one after an upgrade:
// white sheet, following the system so the views may still invert it.
const auto background = QetSettings::sheetBackground();
QVERIFY(background.color.isValid());
QCOMPARE(background.color, QColor(Qt::white));
QVERIFY(!background.custom);
}
void TstSheetBackgroundSetting::colourAndCustomFlagAreStoredTogether()
{
QetSettings::setSheetBackground(QetSettings::SheetBackground{QColor("#404040"), true});
const auto background = QetSettings::sheetBackground();
QCOMPARE(background.color, QColor("#404040"));
QVERIFY(background.custom);
}
void TstSheetBackgroundSetting::sameColourStillDistinguishesTheTwoModes()
{
// The reported bug in miniature: white is white either way, yet one
// of the two must survive a restart as an explicit choice and the
// other as "follow the system".
QetSettings::setSheetBackground(QetSettings::SheetBackground{QColor("#ffffff"), true});
QVERIFY(QetSettings::sheetBackground().custom);
QetSettings::setSheetBackground(QetSettings::SheetBackground{QColor("#ffffff"), false});
QVERIFY(!QetSettings::sheetBackground().custom);
}
void TstSheetBackgroundSetting::alphaIsNotStored()
{
// What the SVG export leaves on Diagram::background_color once it has
// asked for a transparent background.
QColor transparent(QStringLiteral("#12ab34"));
transparent.setAlpha(0);
QetSettings::setSheetBackground(QetSettings::SheetBackground{transparent, true});
const auto background = QetSettings::sheetBackground();
QCOMPARE(background.color.name(), QStringLiteral("#12ab34"));
QCOMPARE(background.color.alpha(), 255);
}
void TstSheetBackgroundSetting::unreadableColourFallsBackToWhite()
{
// A hand-edited or truncated settings file must not blank the sheet.
QSettings settings;
settings.setValue(QStringLiteral("diagrameditor/sheet_background_color"),
QStringLiteral("not a colour"));
settings.sync();
const auto background = QetSettings::sheetBackground();
QCOMPARE(background.color, QColor(Qt::white));
}
void TstSheetBackgroundSetting::recentColorsDefaultToEmpty()
{
QVERIFY(QetSettings::sheetBackgroundRecentColors().isEmpty());
}
void TstSheetBackgroundSetting::recentColorsKeepTheirOrder()
{
// Most recent first, as the menu lists them.
QetSettings::setSheetBackgroundRecentColors({QStringLiteral("#111111"),
QStringLiteral("#222222")});
QCOMPARE(QetSettings::sheetBackgroundRecentColors(),
QStringList({QStringLiteral("#111111"), QStringLiteral("#222222")}));
QetSettings::setSheetBackgroundRecentColors(QStringList());
QVERIFY(QetSettings::sheetBackgroundRecentColors().isEmpty());
}
QTEST_MAIN(TstSheetBackgroundSetting)
#include "tst_sheetbackgroundsetting.moc"