Generate cabinet placement thumbnails from manufacturer/reference info

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 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-30 11:08:55 +13:00
parent bae336ef5b
commit 6c4ee10f44
2 changed files with 182 additions and 1 deletions
+180 -1
View File
@@ -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 <QApplication>
#include <QDropEvent>
#include <QPainter>
#include <QPointer>
#include <QSet>
#include <QUuid>
#include <algorithm>
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<QAction *> DiagramView::contextMenuActions() const
QList<QAction *> list;
if (QETDiagramEditor *qde = diagramEditor())
{
if (m_diagram->selectedItems().isEmpty())
const QList<QGraphicsItem *> 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<QAction *> 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<Element *>(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<QGraphicsItem *> selected_items = m_diagram->selectedItems();
for (QGraphicsItem *qgi : selected_items)
{
const Element *element = qgraphicsitem_cast<Element *>(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
*/
+2
View File
@@ -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