Compare commits

...

5 Commits

Author SHA1 Message Date
plc-user c1551c7477 add English and German translations 2026-08-04 23:36:30 +02:00
plc-user 6183518b2f Merge pull request #633 from ispyisail/fix/blank-folder-names
Never leave a collection folder without a name (replaces #622)
Mark elements-folder with an exclamation-mark, when "qet_directory" is missing or faulty.
2026-08-04 23:01:40 +02:00
ispyisail f83aa3f1bc Fix stack-overflow crash in FileElementCollectionItem::setUpIcon()
The unconditional early return was narrowed to non-directories only, so
that the just-added warning badge could be picked up once setUpData()
resolved m_qet_directory_unreadable asynchronously. But every directory
then called setIcon() on every single data(Qt::DecorationRole) query --
not just once -- and QStandardItem::setIcon() -> setData() emits
dataChanged() unconditionally (QIcon has no equality check to suppress
it). QTreeView handles dataChanged() by recomputing the row's size hint,
which re-enters data() for the same index, calling setIcon() again:
unbounded mutual recursion, confirmed by an isolated reproduction to
overflow the stack in a single frame (100k+ frames) well before the
first paint completes. Matches plc-user's report of a segfault right as
the elements tree begins drawing.

The race the guard was widened for doesn't actually occur:
ElementsCollectionModel only attaches itself to the tree view (the only
way data() becomes reachable) from loadingFinished(), which fires after
the QtConcurrent::map over every item -- this one included -- has
already finished. m_qet_directory_unreadable is therefore always final
before setUpIcon() can run for the first time, so the plain, always-only-
once guard is sufficient and the badge still works correctly.
2026-08-05 08:29:16 +12:00
ispyisail f6b448daa7 Badge unreadable-qet_directory folders with a warning icon in the tree
@plc-user asked (review on #633) for a way to see a broken folder
directly in the tree instead of only on tooltip hover, originally
suggesting a "FixMe: " text prefix on the displayed name. That name is
reused verbatim in dialog titles and status-bar messages elsewhere
(elementscollectionwidget.cpp), so baking a prefix into it would leak
into those too. An icon badge gets the same visibility without
touching the name value.

setUpIcon() overlays a small warning glyph on the folder icon when
m_qet_directory_unreadable is set. Also drop the "already has an icon,
skip" guard for directories specifically: that flag is only known once
the async setUpData()/localName() job completes (QtConcurrent::map), so
without this a directory painted before that finished would have its
plain folder icon cached forever and never pick up the badge.
2026-08-04 06:57:16 +12:00
ispyisail 87a010eb75 Never leave a collection folder without a name, and say when it is broken
Fixes https://qelectrotech.org/bugtracker/view.php?id=332

localName() set a non-root folder's label only inside the success path of
loading its qet_directory file. If that load failed -- file missing,
malformed, or unopenable because of the Windows path-encoding problem with
accented characters that plc-user diagnosed on the tracker -- nothing was
set at all, and since a fresh item's text() is null the folder rendered
with a completely blank label. That is the reported symptom.

Resolve the name into a local and always fall back to the folder's own
directory name, so the label is never empty whatever went wrong.

The fallback is applied *after* NamesList::name() rather than passed into
it. This matters: name() returns a caller-supplied fallback before it
reaches its "first available translation" step, so passing m_path in would
replace a perfectly good name in some other language with the raw
directory name. A folder named only in French, viewed under an English
locale, previously showed "Accentué" and must keep doing so.

Falling back on its own would then hide the broken file -- the user sees a
plausible name and never learns there is anything to repair. So a folder
whose qet_directory could not be read now says so in its tooltip, naming
the file, above the collection path that tooltip already carried.
Suggested by plc-user on PR #622. The flag is recorded in localName() and
consumed in setUpData(), because setUpData() assigns the tooltip after
localName() runs and would otherwise discard it.

Only a file-level failure is flagged. A readable qet-directory with no
entry for the current language is not an error; NamesList::name() resolves
that itself and no warning is shown.

Verified on a fixture collection of four folders -- valid, malformed,
missing, and one named only in French:

                  master        this patch
  fr-only         Accentué      Accentué        (no warning)
  malformed       <blank>       malformed       (warning)
  no qet_directory <blank>      no_file         (warning)
  valid           Valid Folder  Valid Folder    (no warning)
2026-08-02 18:13:38 +12:00
6 changed files with 114 additions and 14 deletions
BIN
View File
Binary file not shown.
+11 -5
View File
@@ -9664,30 +9664,36 @@ Was möchten Sie tun?</translation>
<translation>Foliennummer</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="127"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="159"/>
<source>Collection QET</source>
<translation>QET Sammlung</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="129"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="161"/>
<source>Collection Company</source>
<translation>Firmen-Sammlung</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="131"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="163"/>
<source>Collection utilisateur</source>
<translation>Benutzersammlung</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="133"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="165"/>
<source>Makros</source>
<translation>Vorlagen</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="135"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="167"/>
<source>Collection inconnue</source>
<translation>Unbekannte Sammlung</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="416"/>
<source>Le fichier « %1 » est absent ou illisible : le nom traduit de ce dossier n&apos;a pas pu être lu, son nom de dossier est affiché à la place.</source>
<translation>Die Datei &quot;%1&quot; fehlt oder ist nicht lesbar:
Der QET-Name dieses Ordners konnte nicht gelesen werden; stattdessen wird der Ordnername angezeigt.</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/xmlprojectelementcollectionitem.cpp" line="62"/>
<source>Projet sans titre</source>
BIN
View File
Binary file not shown.
+11 -5
View File
@@ -9651,30 +9651,36 @@ What do you wish to do ?</translation>
<translation>Folio number</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="127"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="159"/>
<source>Collection QET</source>
<translation>QET Collection</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="129"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="161"/>
<source>Collection Company</source>
<translation>Company collection</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="131"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="163"/>
<source>Collection utilisateur</source>
<translation>User Collection</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="133"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="165"/>
<source>Makros</source>
<translation>Macros</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="135"/>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="167"/>
<source>Collection inconnue</source>
<translation>Unknown collection</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/fileelementcollectionitem.cpp" line="416"/>
<source>Le fichier « %1 » est absent ou illisible : le nom traduit de ce dossier n&apos;a pas pu être lu, son nom de dossier est affiché à la place.</source>
<translation>The file &quot;%1&quot; is missing or unreadable:
the translated name of this folder could not be read, so its folder name is displayed instead.</translation>
</message>
<message>
<location filename="../sources/ElementsCollection/xmlprojectelementcollectionitem.cpp" line="62"/>
<source>Projet sans titre</source>
@@ -22,7 +22,39 @@
#include "../qeticons.h"
#include "elementslocation.h"
#include <QApplication>
#include <QDir>
#include <QPainter>
#include <QPixmap>
#include <QStyle>
namespace {
/**
@return the folder icon overlaid with a small warning badge in the
bottom-right corner. Used for a directory whose qet_directory could
not be read (@see FileElementCollectionItem::m_qet_directory_unreadable),
so the problem is visible in the tree itself and not only on hover
via the tooltip. Built once: same folder icon, same badge, every time.
*/
const QIcon &unreadableFolderIcon()
{
static const QIcon icon = []() {
QPixmap pixmap = QET::Icons::Folder.pixmap(16, 16);
const QPixmap badge = QApplication::style()
->standardIcon(QStyle::SP_MessageBoxWarning)
.pixmap(9, 9);
QPainter painter(&pixmap);
painter.drawPixmap(pixmap.width() - badge.width(),
pixmap.height() - badge.height(),
badge);
painter.end();
return QIcon(pixmap);
}();
return icon;
}
}
/**
@brief FileElementCollectionItem::FileElementCollectionItem
@@ -136,18 +168,41 @@ QString FileElementCollectionItem::localName()
}
else
{
// Fall back to the raw directory name (m_path) whenever the
// translated name can't be obtained -- qet_directory missing,
// unreadable (e.g. a Windows path-encoding issue with special
// characters, see bugtracker #332), malformed, or present but
// without a usable name entry -- rather than leaving the item
// blank.
QString display_name;
bool readable = false;
QString str(fileSystemPath() % "/qet_directory");
pugi::xml_document docu;
if(docu.load_file(str.toStdWString().c_str()))
if (docu.load_file(str.toStdWString().c_str()))
{
if (QString(docu.document_element().name())
== "qet-directory")
{
readable = true;
NamesList nl;
nl.fromXml(docu.document_element());
setText(nl.name());
// Deliberately no fallback argument: a non-empty one
// is returned *before* NamesList::name() reaches its
// "first available translation" step, so passing
// m_path here would replace a perfectly good name in
// some other language with the raw directory name.
// The fallback belongs after the chain, not inside it.
display_name = nl.name();
}
}
setText(display_name.isEmpty() ? m_path : display_name);
// Only a file-level failure counts: a readable qet-directory
// with no entry for the current language is not an error,
// NamesList::name() resolves that on its own. Recorded here
// and reported by setUpData(), which sets the tooltip after
// this runs.
m_qet_directory_unreadable = !readable;
}
}
else if (isElement()) {
@@ -350,7 +405,21 @@ void FileElementCollectionItem::setUpData()
}
}
setToolTip(collectionPath());
// Falling back to the raw directory name keeps the folder usable, but
// on its own it hides the fact that a file is broken: the user sees a
// plausible name and never learns there is anything to repair. Say so
// above the collection path, which stays as the last line the way the
// element tooltip above builds it.
QStringList tip;
if (isDir() && m_qet_directory_unreadable)
{
tip << QObject::tr("Le fichier « %1 » est absent ou illisible : "
"le nom traduit de ce dossier n'a pas pu être lu, "
"son nom de dossier est affiché à la place.")
.arg(fileSystemPath() % "/qet_directory");
}
tip << collectionPath();
setToolTip(tip.join(QLatin1Char('\n')));
}
/**
@@ -361,6 +430,19 @@ void FileElementCollectionItem::setUpData()
*/
void FileElementCollectionItem::setUpIcon()
{
// Must return unconditionally once an icon is set: setIcon() calls
// setData(), which emits dataChanged() regardless of whether the new
// icon differs from the old one (QIcon has no meaningful equality).
// QTreeView responds to dataChanged() by recomputing the row's size
// hint, which re-enters data() for this same index -- so without this
// guard, any repeated setIcon() here recurses until the stack
// overflows. Confirmed by crash report on PR #633.
//
// This item's m_qet_directory_unreadable is already final by the time
// this can run at all: ElementsCollectionModel only attaches itself
// to the tree view (making data() reachable) from loadingFinished(),
// which fires after the QtConcurrent::map over every item -- this one
// included -- has completed. So there is no race to work around here.
if (!icon().isNull())
return;
@@ -380,7 +462,8 @@ void FileElementCollectionItem::setUpIcon()
else
{
if (isDir()) {
setIcon(QET::Icons::Folder);
setIcon(m_qet_directory_unreadable ? unreadableFolderIcon()
: QET::Icons::Folder);
} else {
if (m_path.endsWith(".qetmak")) {
setIcon(QIcon());
@@ -64,6 +64,11 @@ class FileElementCollectionItem : public ElementCollectionItem
private:
QString m_path;
/// True when this directory's qet_directory file is missing or
/// unreadable, so setUpData() can say so in the tooltip. Recorded
/// rather than acted on in localName(), because setUpData() resets
/// the tooltip afterwards and would otherwise discard it.
bool m_qet_directory_unreadable = false;
};
#endif // FILEELEMENTCOLLECTIONITEM2_H