The child lookup iterated parent_element.childNodes() via item(i), and
QDomNodeList::item() walks the sibling chain from the start on every
call - making the loop quadratic in the number of children, with an
extra QList allocation and a second pass on top. This lookup runs
several times per element instance while loading a project, against the
"import" category that holds every embedded definition, so the cost
scales with (instances x embedded definitions).
Replace it with a firstChildElement()/nextSiblingElement() walk with an
early return. Same semantics (first tag+name match in document order).
Measured on the Kaefer_1303 reference project (3.9 MB, 23 folios,
432 instances, media of 6 runs, Windows/MinGW, same GCC for both):
before after
Qt5 5.116 s 5.068 s
Qt6 6.683 s 4.640 s (-31 %)
This removes the entire Qt6 load-time regression discussed in #553 -
Qt6 goes from +31 % slower to 8 % faster than Qt5 on the very project
that exposed it (Qt6''s QDom makes the quadratic pattern much more
expensive than Qt5''s did). Smaller projects gain too (3.4 MB example:
-9 % on Qt6).
(cherry picked from commit 0d4ef8eca27601c37ba2b75d7c058e9d8e2beea4)
Set QDomImplementation::setInvalidDataPolicy(ReturnNullNode) at startup.
Qt before 6.12 defaults to accepting invalid data when building QDom
nodes, so untrusted text inserted into comments, CDATA sections or
processing instructions could break out of its node on serialization
(XML injection, low severity). Qt 6.12 flips the default to
ReturnNullNode; opting in explicitly gives the same behavior on any
Qt 5/6 version, so no version guard is needed.
Verified: --resave of a 5-folio project produces valid XML with all
folios intact, and --export-pdf still works on the result.
(cherry picked from commit 7804ef3864a0b8643cfe13b68b8e0e6235508ab6)
QHash/QMap::keys() allocates a list of every key on each call, then
contains() searches it linearly - an accidental O(n) plus allocation
where a direct O(1) lookup was meant. 35 occurrences across 7 files,
found while profiling project load times (context: #553/#560).
The hot one is ElementPictureFactory::getPictures(), which runs once
per element instance on project load: on the 3399 KiB example project
(191 instances, 129 cache hits) the keys() detour cost 45 ms of the
1.34 s total - measured, not estimated; the fix reproducibly shaves
~35-45 ms off that load. The remaining call sites are UI paths
(search&replace, dynamic text model, undo commands) where the waste
scales with selection/model size.
No behavior change: for QHash/QMap, keys().contains(k) and
contains(k) are equivalent by definition.
(cherry picked from commit 0a7f8f072fa68de7c01a9fc134a4bc8e16d62062)
Two further empty Qt6 guard branches found by ispyisail in
qelectrotech#553:
- Element editor parts list: the QGraphicsItem* was only stored into
the list item on Qt5, so on Qt6 selecting a part in the list silently
stopped selecting it on the canvas. QVariant::fromValue() works on
both (Qt itself declares the metatype), guard removed.
- Print dialog: setEnabledOptions() is a Qt4-era API removed in Qt6;
setOptions() is the modern spelling with the same replace-the-set
semantics and exists on both, guard removed.
Both builds (Qt 5.15.2 and Qt 6.11.1) compile clean.
(cherry picked from commit 759d1c078e23664482850bad2e91d668b93aadcf)
QETProject::writeBackup() was Qt5-only: the Qt6 branch of the guard was
an empty placeholder, so on Qt6 no backup was ever written - a silent
data-loss risk (a crash loses everything since the last manual save),
found by ispyisail in qelectrotech#553.
The Qt5-style QtConcurrent::run(function, reference-args) call did not
survive the Qt6 API change; a lambda capturing the (implicitly shared)
document copy behaves identically on both, so the version guard goes
away entirely.
Verified at runtime on the Qt6/Windows build: opening a project creates
the autosave triple (.qetautosave + .lock + .path) with valid XML
content, and a clean exit removes it again. The CLI keeps backups
disabled via setBackupEnabled(false), unchanged.
(cherry picked from commit 0f65ae8c4b2782fbfe97111bc8b369cc105ec592)
qInstallMessageHandler() ran inside the startup worker thread, which is
scheduled after QETApp construction - but QETApp's constructor performs
the entire startup (collections, editor, opening projects passed on the
command line). Everything logged during that window went to the default
handler, i.e. stderr, which is invisible in a Windows GUI session: the
daily log file ended right after the machine-info block, and exactly
the interesting lines - the elements-collection timer and the project
load timer from #560 - never reached it.
Install the handler synchronously before SingleApplication instead;
the worker keeps the old-log cleanup and machine-info dump. The CLI
path returns earlier and intentionally keeps plain stderr logging.
Verified: a GUI session now logs the full timeline, e.g.
12:37:03.623 Elements collection reload
12:37:03.776 ... finished to be loaded in 0.151 seconds
12:37:04.744 Project "..." (726 KiB) opened in 1.583 seconds
(cherry picked from commit 72b1a1d9ec2e3fd3787224759797978d710880bd)
Requested in #553 to compare Qt5 and Qt6 builds. QET already reports how
long the elements collection takes to load (ElementsCollectionWidget::
reload); this adds the equivalent for opening a project.
The phases are reported separately rather than as a single total. Reading
the XML and building the objects is mostly independent of the Qt version,
whereas refreshing the diagrams is graphics-scene work -- a single number
would mix the two and could suggest a Qt version makes no difference when
the part that changed is simply not where the time goes. Measured on the
example projects, XML parsing is 3-7% of the total and diagram
construction 77-83%, so the distinction matters in practice.
QETProject::openFile() reports the total with the parse/build split, and
readProjectXml() reports the build phases:
Project content built in 1.391 seconds (elements collection 0.009,
diagrams 1.153, terminal strips 0, refresh 0.196, database 0.033)
Project "example.qet" (3399 KiB) opened in 1.505 seconds
(xml parsing 0.11, content 1.395)
Logged with qInfo(), matching the existing collection timer, so it lands
in the normal log without a debug build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
commonElementsDir(), commonTitleBlockTemplatesDir() and languagesPath()
return the compile-time path verbatim when it is not marked
*_RELATIVE_TO_BINARY_PATH. On Windows those paths are relative
("./elements/", "./titleblocks/", "./lang/"), so they resolve against the
process working directory.
That only holds when QET is started from its own installation folder.
Opening a document from a file manager sets the working directory to the
document's folder, and the data is then looked for next to the user's
file. The shortcuts hide this by passing --common-elements-dir,
--common-tbt-dir and --lang-dir explicitly; anything that launches the
binary without them does not (see #554).
Add resolveConfiguredDataPath(): absolute paths are returned unchanged,
and a relative one is tried against the working directory first (so any
setup relying on the old behaviour keeps working), then next to the
executable, then in its parent -- the layout used by the Windows
packaging, where the binary sits in bin/ with the data beside it. This is
the same fallback the no-compile-option branch already performs for
issue #86, which was unreachable whenever the compile option is set.
The *_RELATIVE_TO_BINARY_PATH defines are left alone; they are only set
for macOS in qelectrotech.pro, and the CMake guard that would set them
tests a variable that is never defined.
QETApp::useSystemPalette(true) installed a one-rule application stylesheet
whose only declaration was invalid CSS:
QAbstractScrollArea#mdiarea {
background-color -> setPalette(initial_palette_);
}
That is not a CSS declaration but a note-to-self, committed in e6c32bc0
("Background set to use System Palette", 2014) when a hardcoded
background-color:#D5D2D1;
was replaced with a reminder to derive the color from the palette instead.
Qt's CSS parser silently skips invalid declarations, and at the time the
same rule still carried valid background-image/-repeat/-position
properties, so the block kept working and nothing looked wrong. Those
properties were dropped later, leaving a rule with no valid declarations
at all.
The rule has therefore styled nothing for some time. It is not harmless
though: a non-empty application stylesheet wraps every widget in
QStyleSheetStyle, which overrides per-widget QWidget::setStyle(). QET
does not currently call QWidget::setStyle() anywhere, so nothing is
visibly broken today, but it blocks that API for future work — it was
found while prototyping a palette-based dark mode (see #553).
Replace it with an explicit setStyleSheet(QString()). The behavior of the
"use system colors" branch is unchanged: it already dropped whatever
style.css had loaded (by overwriting it with the inert rule), and the
qApp->setPalette(initial_palette_) call on the line above is what actually
supplies the system colors — which is what the 2014 note was asking for.
The style.css path (use == false) is untouched.
Reported by DieterMayerOSS in #553.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The six user-facing error strings in EdzArchive::extract() were hard-coded
QStringLiteral, so they could not be translated (reported by plc-user in
PR #513).
Wrap them in tr() via Q_DECLARE_TR_FUNCTIONS — EdzArchive is not a QObject,
so this gives it its own translation context without pulling in a moc
dependency. The *.part.xml glob stays a QStringLiteral: it is a filename
pattern, not user-facing text.
Adds German and French translations for all six. German wording for
"Cannot read %1" is plc-user's ("Kann %1 nicht lesen.").
Note for review: the rest of QET uses French source strings translated to
en/de in the .ts files, whereas these strings are English in the source
(as merged in PR #513). This commit keeps them English rather than
rewriting strings already reviewed; happy to flip them to French sources
for consistency if preferred.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Shows the licensing/liability warning text agreed on in PR #513
(scorpio810) before the file picker opens. Import stays disabled
until the "I have read and accept these terms" checkbox is ticked.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Use QT_VERSION_CHECK to detect Qt6 at compile time and report
version 0.200.1 instead of 0.100.1 in that case, so Qt6 builds
are clearly distinguishable from Qt5 builds.
Clears the last five spots that stop master from compiling against Qt6
(all mirrored from the proven qt6-build line):
- qgimanager.h/.cpp: in Qt6 QVector is an alias for QList, so the
deprecated QList overloads of manage()/release() collide with the
QVector ones (same signature). Keep them on Qt5 only.
- diagramview.cpp: one unguarded QTextStream::setCodec() call (removed
in Qt6, which defaults to UTF-8).
- print/projectprintwindow.cpp: QApplication::desktop() was removed in
Qt6; use QWidget::screen() there, keep the old path on Qt5.
- titleblocktemplate.cpp: QDomDocument::setContent() returns a
ParseResult in Qt6 whose operator bool is explicit; static_cast keeps
the bool initialization working on both.
With these, master configures and builds to a running binary with
Qt 6.11 (mingw, BUILD_WITH_KF5=OFF); every change is guarded or
dual-safe, the Qt5 build is unaffected.
The name of the elements and folders of the collection are not displayed
until we hover the item with the mouse.
This due that QtConcurent::run was disabled at loading of collection in
the goal of use QtConcurrent::run with Qt6.
Run is made to run a function once.
Map is made to run a fonction for each item of a sequence (what we need
in this case).
Remove code of run and re-enable code for map.
- Add missing <QHash> include: QHash<QUuid, QVector<QPointF>> was
only forward-declared transitively under Qt5's heavier headers;
Qt6's leaner <QPainter> etc. no longer pull it in.
- Replace QPolygonF{points_} with QPolygonF(points_): under Qt6,
QPolygonF inherits QList<QPointF>'s constructors via 'using
QList<QPointF>::QList;', including the std::initializer_list<T>
one. Brace-init-list construction with a single argument first
considers only initializer-list constructors before falling
back to regular ones, which trips up overload resolution here
even though points_ is exactly a QList<QPointF> (QVector is a
plain alias for QList under Qt6). Plain parenthesised
construction sidesteps that resolution phase entirely and binds
directly to QPolygonF(const QList<QPointF>&).
QVector::size() (== QList::size() under Qt6) returns qsizetype
(64-bit), while 'level' is a plain int. std::min(level,
m_real_terminal.size()-1) therefore tries to deduce a single T
from two different argument types, which fails - GCC reports it
against the unrelated std::min(initializer_list<T>) overload,
but the real issue is the type mismatch between the two-argument
candidates. Under Qt5, QVector::size() returned int, so this
compiled fine.
Force T=int explicitly via std::min<int>(...) and cast size()
down to int (physical terminal counts are always tiny), matching
the pattern already used safely elsewhere in the codebase (e.g.
qetgraphicstableitem.cpp).
Since Qt 6.5, QDomDocument::setContent() returns a ParseResult
struct with an *explicit* operator bool(), so 'bool x =
doc.setContent(...)' (copy-initialization) no longer compiles -
explicit conversions aren't considered there.
This exact issue was already fixed in titleblocktemplate.cpp by
dropping the intermediate bool and testing the call directly in
the if condition (contextual bool conversion in an if() is fine
even for an explicit operator bool), but this second occurrence
in templatescollection.cpp used the same pattern and was missed.
Applying the same fix here for consistency.
templateview.h only included <QGraphicsView> but uses
QGraphicsGridLayout (tbgrid_ member) and QGraphicsLayoutItem
(indexOf/removeItem signatures) directly. Some Qt5 header
apparently pulled these in transitively; Qt6's leaner headers
don't, so the class fields/methods silently failed to resolve
and the compiler picked bogus 'int*' overloads instead.
Other files in the same directory already get these symbols
either via the <QtWidgets> umbrella header or a direct include,
so this was an isolated gap.
The Qt6 branch of the #if QT_VERSION guard swapped QRegExp for
QRegularExpression but kept calling QRegExp-only methods
(exactMatch()/cap()), which QRegularExpression doesn't have.
Use the correct QRegularExpression API instead: match() returns
a QRegularExpressionMatch, tested with hasMatch() and read with
captured(n).
- qetxml.h: add missing <QUuid> include (used in propertyUuid/
createXmlProperty signatures but never included directly).
- machine_info.cpp: fix ambiguous QString/int operator> in
send_info_to_debug() for it1/it2/it3; just count every
matching file found instead of the meaningless comparison.
The project panel's "copy and paste" action duplicates a folio through
an XML round-trip (toXml/fromXml), and Element::fromXml adopts the uuid
stored in the XML - so every element on the duplicated folio kept the
uuid of its source element. On-diagram copy/paste already handles this
(PasteDiagramCommand::redo() calls newUuid()), but the folio-duplicate
path bypasses that command.
Since element.uuid is the PRIMARY KEY of the project database, the
duplicated elements failed to insert ("UNIQUE constraint failed:
element.uuid" spam in the log) and silently disappeared from
nomenclature/summary tables, even though they are valid on the folio.
Renew the uuid of every element on the freshly duplicated folio, exactly
as the paste command does. In-memory links established during fromXml
are pointer-based and unaffected; the new uuids are written on save.
Clears the 9 non-deprecation warnings from the Qt6 build:
- qHash(QColor): hash rgba() (unambiguous QRgb) instead of name(), and
use the size_t seed signature on Qt6 (guarded for Qt5). Fixes the
ambiguous-overload warning in terminalstripmodel.h.
- Two qsizetype->int narrowings in brace-init: explicit static_cast<int>
(elementscene.cpp, terminalstrip.cpp).
- main.cpp: keep the QtConcurrent::run QFuture in a [[maybe_unused]]
variable (nodiscard).
- qetapp.cpp: guard the stylesheet load on QFile::open() succeeding
(nodiscard) instead of ignoring the result.
QVariant::canConvert(int) is deprecated in Qt6. Use the non-deprecated
canConvert<T>() template (canConvert<QString>() / canConvert<int>()),
which is available on Qt5 too. Clears the last 2 -Wdeprecated-
declarations warnings.
Three deprecated APIs have replacements that only exist in newer Qt6:
- QLocale::nativeCountryName() -> nativeTerritoryName() (Qt 6.2)
- QDomDocument::setContent() overload -> ParseResult (Qt 6.5)
- qt_ntfs_permission_lookup -> QNtfsPermissionCheckGuard RAII (Qt 6.6)
Each is wrapped in QT_VERSION_CHECK so Qt5 keeps the old path. Clears
4 -Wdeprecated-declarations warnings.
QString::count() (no-arg) is deprecated -> size(); QColor::setNamedColor()
is deprecated -> the QColor(QString) constructor. Both replacements are
non-deprecated on Qt5 too. Clears 2 -Wdeprecated-declarations warnings.
QSqlDatabase::exec(const QString&) is deprecated in Qt6. Route the
PRAGMA/CREATE TABLE statements through QSqlQuery(db).exec() instead.
Clears 11 -Wdeprecated-declarations warnings; same statements, same
database connection, no behavioural change.
QVariant::type() and the QVariant::Type enum are deprecated in Qt6.
Switch on userType() (non-deprecated, returns the QMetaType id and
works on Qt5 too) with QMetaType enum cases. Clears 6 -Wdeprecated-
declarations warnings; the numeric type ids are unchanged.
qAsConst was deprecated in Qt 6.6; std::as_const (C++17, already the
project standard) is the drop-in replacement. Clears 46 -Wdeprecated-
declarations warnings across 18 files. No behavioural change.
NamesList::name() looked up the display name using the full locale from
langFromSetting() (e.g. "de_DE") and jumped straight to English if it was
absent. Element and folder names in the collection are keyed by 2-letter
codes (<name lang="de">), so a "de_DE" UI showed the whole collection in
English/French even though German names exist.
Try the base language ("de") before the English fallback, mirroring what
setLanguage() already does for the UI translations.