From 6c4ee10f4404ee1ea2dc2ee23300970cd6c82d6c Mon Sep 17 00:00:00 2001 From: ispyisail Date: Wed, 30 Sep 2026 11:08:55 +1300 Subject: [PATCH 1/2] Generate cabinet placement thumbnails from manufacturer/reference info MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Discussion #602. Right-click a selection holding an element with both a manufacturer and a manufacturer reference: "Générer une vignette d'armoire" adds a thumbnail element for each such device to a "Cabinet thumbnails" folder of the project's embedded collection. The thumbnail is a 120x30 frame whose dynamic text shows %{manufacturer} / %{manufacturer_reference}; both values are also in the definition's element information, so it shows them when placed. A device already filed there is skipped, so running it again, or on several copies of one device, adds nothing. Revives #641, closed 2026-09-10 only to clear a review backlog. Changed from it: the frame and text are placed around the hotspot (they were drawn half outside the element), the frame is 120 wide instead of 60 (device names wrapped out of it), the file name goes through QET::stringToFileName() (a '/' in a reference made a folder), the version and font come from QetVersion and QETApp, and the action is left out of the menu when no selected element qualifies. Co-Authored-By: Claude Opus 5.5 --- sources/diagramview.cpp | 181 +++++++++++++++++++++++++++++++++++++++- sources/diagramview.h | 2 + 2 files changed, 182 insertions(+), 1 deletion(-) diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index b4c5cf126..b80335eda 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -45,13 +45,20 @@ #include "ElementsCollection/xmlelementcollection.h" #include "NameList/nameslist.h" #include "elementdialog.h" +#include "qetapp.h" +#include "qetgraphicsitem/element.h" +#include "qetinformation.h" +#include "qetversion.h" #include #include #include #include #include +#include #include +static bool hasCabinetInfo(const DiagramContext &infos); + /** Constructeur @param diagram Schema a afficher ; si diagram vaut 0, un nouveau Diagram est utilise @@ -103,6 +110,10 @@ DiagramView::DiagramView(Diagram *diagram, QWidget *parent) : m_create_template = new QAction(tr("Créer un template", "context menu action"), this); connect(m_create_template, &QAction::triggered, this, &DiagramView::createTemplateFromSelection); + //Setup the action to generate cabinet placement thumbnails (discussion #602) + m_generate_cabinet_thumbnail = new QAction(tr("Générer une vignette d'armoire", "context menu action"), this); + connect(m_generate_cabinet_thumbnail, &QAction::triggered, this, &DiagramView::generateCabinetThumbnails); + //Filled each time the context menu opens, see updateFolioReportMenu() m_folio_report_menu = new QMenu(tr("Renvoi de folio"), this); @@ -1765,7 +1776,8 @@ QList DiagramView::contextMenuActions() const QList list; if (QETDiagramEditor *qde = diagramEditor()) { - if (m_diagram->selectedItems().isEmpty()) + const QList selected_items = m_diagram->selectedItems(); + if (selected_items.isEmpty()) { //Drawing comes first. The row and column actions change //the folio's layout and are rarely wanted, so they sit one @@ -1786,6 +1798,15 @@ QList DiagramView::contextMenuActions() const list << m_multi_paste; list << m_separators.at(0); list << m_create_template; // Add the create template action + //Disabled, and so left out below, unless a selected element + //has both a manufacturer and a manufacturer reference. + m_generate_cabinet_thumbnail->setEnabled(std::any_of( + selected_items.cbegin(), selected_items.cend(), + [](QGraphicsItem *qgi) { + const Element *element = qgraphicsitem_cast(qgi); + return element && hasCabinetInfo(element->elementInformations()); + })); + list << m_generate_cabinet_thumbnail; list << qde->m_conductor_reset; list << m_separators.at(1); list << qde->m_selection_actions_group.actions(); @@ -2073,6 +2094,164 @@ void DiagramView::createTemplateFromSelection() } } +/** + @brief hasCabinetInfo + @return true if @a infos has both a manufacturer and a manufacturer + reference, the two values a cabinet thumbnail shows. +*/ +static bool hasCabinetInfo(const DiagramContext &infos) +{ + return !infos[QETInformation::ELMT_MANUFACTURER].toString().trimmed().isEmpty() + && !infos[QETInformation::ELMT_MANUFACTURER_REF].toString().trimmed().isEmpty(); +} + +/** + @brief cabinetThumbnailDefinition + @return the definition of a thumbnail element, a 120x30 frame showing + the manufacturer and the manufacturer reference in a dynamic text. + The two values are also stored in the definition's element + information, so the thumbnail shows them as soon as it is placed. +*/ +static QDomElement cabinetThumbnailDefinition(QDomDocument &doc, + const QString &display_name, + const QString &manufacturer, + const QString &reference) +{ + QDomElement definition = doc.createElement(QStringLiteral("definition")); + definition.setAttribute(QStringLiteral("version"), QetVersion::currentVersion().toString()); + definition.setAttribute(QStringLiteral("type"), QStringLiteral("element")); + definition.setAttribute(QStringLiteral("link_type"), QStringLiteral("thumbnail")); + definition.setAttribute(QStringLiteral("width"), 120); + definition.setAttribute(QStringLiteral("height"), 30); + definition.setAttribute(QStringLiteral("hotspot_x"), 60); + definition.setAttribute(QStringLiteral("hotspot_y"), 15); + doc.appendChild(definition); + + QDomElement uuid = doc.createElement(QStringLiteral("uuid")); + uuid.setAttribute(QStringLiteral("uuid"), QUuid::createUuid().toString()); + definition.appendChild(uuid); + + NamesList names; + names.addName(QStringLiteral("en"), display_name); + definition.appendChild(names.toXml(doc)); + + QDomElement informations = doc.createElement(QStringLiteral("elementInformations")); + DiagramContext infos; + infos.addValue(QETInformation::ELMT_MANUFACTURER, manufacturer); + infos.addValue(QETInformation::ELMT_MANUFACTURER_REF, reference); + infos.toXml(informations, QStringLiteral("elementInformation")); + definition.appendChild(informations); + + QDomElement description = doc.createElement(QStringLiteral("description")); + + QDomElement rect = doc.createElement(QStringLiteral("rect")); + rect.setAttribute(QStringLiteral("x"), -60); + rect.setAttribute(QStringLiteral("y"), -15); + rect.setAttribute(QStringLiteral("width"), 120); + rect.setAttribute(QStringLiteral("height"), 30); + rect.setAttribute(QStringLiteral("rx"), 0); + rect.setAttribute(QStringLiteral("ry"), 0); + rect.setAttribute(QStringLiteral("style"), + QStringLiteral("line-style:normal;line-weight:normal;filling:none;color:black")); + rect.setAttribute(QStringLiteral("antialias"), QStringLiteral("false")); + description.appendChild(rect); + + QDomElement text = doc.createElement(QStringLiteral("dynamic_text")); + text.setAttribute(QStringLiteral("x"), -57); + text.setAttribute(QStringLiteral("y"), -14); + text.setAttribute(QStringLiteral("z"), 1); + text.setAttribute(QStringLiteral("text_width"), 114); + text.setAttribute(QStringLiteral("Halignment"), QStringLiteral("AlignLeft")); + text.setAttribute(QStringLiteral("Valignment"), QStringLiteral("AlignTop")); + text.setAttribute(QStringLiteral("frame"), QStringLiteral("false")); + text.setAttribute(QStringLiteral("rotation"), 0); + text.setAttribute(QStringLiteral("keep_visual_rotation"), QStringLiteral("false")); + text.setAttribute(QStringLiteral("text_from"), QStringLiteral("CompositeText")); + text.setAttribute(QStringLiteral("uuid"), QUuid::createUuid().toString()); + text.setAttribute(QStringLiteral("font"), QETApp::dynamicTextsItemFont(7).toString()); + + QDomElement text_cache = doc.createElement(QStringLiteral("text")); + text_cache.appendChild(doc.createTextNode(manufacturer + QLatin1Char('\n') + reference)); + text.appendChild(text_cache); + + QDomElement composite_text = doc.createElement(QStringLiteral("composite_text")); + composite_text.appendChild(doc.createTextNode(QStringLiteral("%{manufacturer}\n%{manufacturer_reference}"))); + text.appendChild(composite_text); + + description.appendChild(text); + definition.appendChild(description); + return definition; +} + +/** + @brief DiagramView::generateCabinetThumbnails + Context menu action (discussion #602). For every selected element with + both a manufacturer and a manufacturer reference, add a thumbnail + element showing them to the folder "Cabinet thumbnails" of this + project's embedded collection, from where it can be placed on a + cabinet layout folio like any other element. A device that already + has a thumbnail there is skipped, so placing it several times does + not pile up copies. +*/ +void DiagramView::generateCabinetThumbnails() +{ + QETProject *project = m_diagram->project(); + if (!project || project->isReadOnly()) { + return; + } + XmlElementCollection *collection = project->embeddedElementCollection(); + if (!collection) { + return; + } + + const QString dir_name = QStringLiteral("Cabinet thumbnails"); + const QString dir_path = QStringLiteral("import/") + dir_name; + if (!collection->exist(dir_path)) + { + NamesList dir_names; + dir_names.addName(QStringLiteral("en"), dir_name); + dir_names.addName(QStringLiteral("fr"), tr("Vignettes d'armoire")); + if (!collection->createDir(QStringLiteral("import"), dir_name, dir_names)) { + return; + } + } + + QStringList existing = collection->elementsNames(collection->directory(dir_path)); + int created = 0; + const QList selected_items = m_diagram->selectedItems(); + for (QGraphicsItem *qgi : selected_items) + { + const Element *element = qgraphicsitem_cast(qgi); + if (!element || !hasCabinetInfo(element->elementInformations())) { + continue; + } + const DiagramContext infos = element->elementInformations(); + const QString manufacturer = infos[QETInformation::ELMT_MANUFACTURER].toString().trimmed(); + const QString reference = infos[QETInformation::ELMT_MANUFACTURER_REF].toString().trimmed(); + + //The display name keeps the values as typed; the file name + //cannot hold characters such as '/', which would be read as + //a folder. + const QString display_name = manufacturer + QLatin1Char(' ') + reference; + const QString file_name = QET::stringToFileName(display_name) + QStringLiteral(".elmt"); + if (existing.contains(file_name)) { + continue; + } + + QDomDocument doc; + const QDomElement definition = cabinetThumbnailDefinition(doc, display_name, manufacturer, reference); + if (collection->addElementDefinition(dir_path, file_name, definition)) + { + existing << file_name; + ++created; + } + } + + if (created) { + project->setModified(true); + } +} + /** @return l'editeur de schemas parent ou 0 */ diff --git a/sources/diagramview.h b/sources/diagramview.h index 9ac5a5ad8..11f53ddf2 100644 --- a/sources/diagramview.h +++ b/sources/diagramview.h @@ -58,6 +58,7 @@ class DiagramView : public PaletteGraphicsView QAction *m_paste_here = nullptr; QAction *m_multi_paste = nullptr; QAction *m_create_template = nullptr; + QAction *m_generate_cabinet_thumbnail = nullptr; QMenu *m_folio_report_menu = nullptr; QPoint m_paste_here_pos; QPoint m_press_pos; @@ -197,5 +198,6 @@ class DiagramView : public PaletteGraphicsView void adjustGridToZoom(); void applyReadOnly(); void createTemplateFromSelection(); + void generateCabinetThumbnails(); }; #endif From aa09d0eb2a2a78292f279d44d350292f30c0cc69 Mon Sep 17 00:00:00 2001 From: ispyisail Date: Wed, 30 Sep 2026 11:15:11 +1300 Subject: [PATCH 2/2] Store the thumbnails folder's French name as French, not through tr() tr() returns the interface language, so an English or German session would have written its own text under the "fr" name. Co-Authored-By: Claude Opus 5.5 --- sources/diagramview.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/diagramview.cpp b/sources/diagramview.cpp index b80335eda..d74e524b4 100644 --- a/sources/diagramview.cpp +++ b/sources/diagramview.cpp @@ -2210,7 +2210,9 @@ void DiagramView::generateCabinetThumbnails() { NamesList dir_names; dir_names.addName(QStringLiteral("en"), dir_name); - dir_names.addName(QStringLiteral("fr"), tr("Vignettes d'armoire")); + //Stored per language, like an element's names: not tr(), which + //would store the interface's language under "fr" + dir_names.addName(QStringLiteral("fr"), QStringLiteral("Vignettes d'armoire")); if (!collection->createDir(QStringLiteral("import"), dir_name, dir_names)) { return; }