Merge pull request #889 from ispyisail/fix/802-reload-element-drawings

Add "Reload element drawings" to refresh placed elements
This commit is contained in:
Laurent Trinques
2026-09-16 19:52:12 +02:00
committed by GitHub
6 changed files with 274 additions and 2 deletions
+23
View File
@@ -94,6 +94,29 @@ void ElementPictureFactory::getPictures(const ElementsLocation &location, QPictu
}
}
/**
@brief ElementPictureFactory::dropCache
Forget the cached drawing of the element at @p location, so the next
getPictures()/pixmap()/getPrimitives() call rebuilds it from the
definition's current content instead of returning what was cached the
first time this location was drawn.
A placed Element keeps its own copy of the picture in m_picture /
m_low_zoom_picture (set once, in buildFromXml()), so dropping the shared
cache here does not by itself change what is on screen -- callers doing
a manual refresh (bugtracker #802) still need each Element to re-fetch
its picture afterwards.
@param location
*/
void ElementPictureFactory::dropCache(const ElementsLocation &location)
{
const QUuid uuid = cacheKey(location);
m_pictures_H.remove(uuid);
m_low_pictures_H.remove(uuid);
m_pixmap_H.remove(uuid);
m_primitives_H.remove(uuid);
}
/**
@brief ElementPictureFactory::pixmap
@param location
+3 -2
View File
@@ -85,13 +85,14 @@ class ElementPictureFactory
void getPictures(const ElementsLocation &location, QPicture &picture, QPicture &low_picture);
QPixmap pixmap(const ElementsLocation &location);
ElementPictureFactory::primitives getPrimitives(const ElementsLocation &location);
void dropCache(const ElementsLocation &location);
private:
ElementPictureFactory() {}
ElementPictureFactory (const ElementPictureFactory &);
ElementPictureFactory operator= (const ElementPictureFactory &);
~ElementPictureFactory();
static QUuid cacheKey(const ElementsLocation &location);
bool build(const ElementsLocation &location, QPicture *picture=nullptr, QPicture *low_picture=nullptr);
void parseElement(const QDomElement &dom, QPainter &painter, primitives &prim) const;
+105
View File
@@ -22,6 +22,7 @@
#include "autoNum/ui/autonumberingdockwidget.h"
#include "conductornumexport.h"
#include "diagramcommands.h"
#include "diagramcontent.h"
#include "diagramevent/diagrameventaddimage.h"
#ifdef QET_HAS_QTPDF
#include "diagramevent/diagrameventaddpdf.h"
@@ -32,6 +33,7 @@
#include "diagramevent/diagrameventaddpaste.h"
#include "diagramview.h"
#include "elementspanelwidget.h"
#include "factory/elementpicturefactory.h"
#include "factory/qetgraphicstablefactory.h"
#include "print/projectprintwindow.h"
#include "project/projectpropertieshandler.h"
@@ -549,6 +551,13 @@ void QETDiagramEditor::setUpActions()
m_terminal_numbering = new QAction(QET::Icons::TerminalStrip, tr("Numérotation automatique des bornes"), this);
connect(m_terminal_numbering, &QAction::triggered, this, &QETDiagramEditor::slot_terminalNumbering);
// Reload element drawings from their current definition (bugtracker #802)
m_reload_element_drawings = new QAction(QET::Icons::ViewRefresh, tr("Recharger les dessins des éléments"), this);
m_reload_element_drawings->setStatusTip(
tr("Redessine chaque élément placé d'après sa définition actuelle,"
" sans avoir à fermer et rouvrir le projet (action non annulable)"));
connect(m_reload_element_drawings, &QAction::triggered, this, &QETDiagramEditor::slot_reloadElementDrawings);
#ifdef QET_EXPORT_PROJECT_DB
m_export_project_db = new QAction(QET::Icons::DocumentSpreadsheet, tr("Exporter la base de donnée interne du projet"), this);
connect(m_export_project_db, &QAction::triggered, [this]() {
@@ -1002,6 +1011,7 @@ void QETDiagramEditor::setUpMenu()
menu_project -> addAction(m_project_export_wiring_list);
menu_project -> addAction(m_project_wiring_list_view);
menu_project -> addAction(m_terminal_numbering);
menu_project -> addAction(m_reload_element_drawings);
#ifdef QET_EXPORT_PROJECT_DB
menu_project -> addSeparator();
menu_project -> addAction(m_export_project_db);
@@ -1856,6 +1866,7 @@ void QETDiagramEditor::slot_updateActions()
m_project_export_wiring_list -> setEnabled(opened_project);
m_project_wiring_list_view -> setEnabled(opened_project);
m_terminal_numbering -> setEnabled(editable_project);
m_reload_element_drawings -> setEnabled(opened_project);
#ifdef QET_EXPORT_PROJECT_DB
m_export_project_db -> setEnabled(editable_project);
#endif
@@ -2970,3 +2981,97 @@ void QETDiagramEditor::slot_terminalNumbering() {
}
}
}
/**
@brief QETDiagramEditor::slot_reloadElementDrawings
Redraw every placed element of the current project from its current
definition (bugtracker #802): a symbol edited and saved after being
placed keeps showing its old drawing otherwise, until the whole project
is closed and reopened.
Purely visual and not undoable, the way pressing a "refresh" button
would be: nothing is pushed on the undo stack and the project is not
marked as modified. Elements whose size, hotspot or terminals changed
are skipped and listed: they must be removed and re-inserted, which
deletes the conductors already connected to them.
*/
void QETDiagramEditor::slot_reloadElementDrawings() {
QETProject *project = currentProject();
if (!project) return;
QList<Element *> elements;
QSet<QString> dropped_locations;
for (Diagram *diagram : project->diagrams())
{
DiagramContent content(diagram, false);
for (Element *elmt : content.m_elements)
{
elements << elmt;
const QString key = elmt->location().toString();
if (!dropped_locations.contains(key))
{
ElementPictureFactory::instance()->dropCache(elmt->location());
dropped_locations.insert(key);
}
}
}
int reloaded = 0;
int unavailable = 0;
QStringList geometry_changed;
for (Element *elmt : elements)
{
switch (elmt->reloadPicture())
{
case Element::ReloadPictureResult::Reloaded:
++reloaded;
break;
case Element::ReloadPictureResult::Unavailable:
++unavailable;
break;
case Element::ReloadPictureResult::GeometryChanged:
{
const Diagram *diagram = elmt->diagram();
const QString folio = diagram
? tr("folio %1").arg(project->folioIndex(diagram) + 1)
: QString();
geometry_changed << QStringLiteral("%1 (%2)").arg(elmt->name(), folio);
break;
}
}
}
QString message = tr("%n élément(s) redessiné(s).", "", reloaded);
if (unavailable) {
message += QStringLiteral("\n\n")
% tr("%n élément(s) dont la définition est introuvable ou illisible :"
" leur dessin actuel a été conservé.", "", unavailable);
}
if (geometry_changed.isEmpty())
{
QET::QetMessageBox::information(
this, tr("Recharger les dessins des éléments"), message);
return;
}
message += QStringLiteral("\n\n")
% tr("%n élément(s) non redessiné(s) : leur taille, leur point de saisie"
" ou leurs bornes ont changé (borne ajoutée, supprimée ou déplacée).",
"", geometry_changed.size())
% QStringLiteral("\n\n")
% tr("Pour les mettre à jour, il faut les supprimer puis les réinsérer."
" Attention : cette opération supprime les conducteurs déjà reliés"
" à ces éléments, qu'il faudra retracer.");
//The full list goes in the expandable, scrollable details area
//so the dialog stays readable on large projects.
QMessageBox box(QMessageBox::Warning,
tr("Recharger les dessins des éléments"),
message,
QMessageBox::Ok,
this);
box.setDetailedText(geometry_changed.join(QLatin1Char('\n')));
box.exec();
}
+2
View File
@@ -136,6 +136,7 @@ class QETDiagramEditor : public QETMainWindow
void editProjectProperties(ProjectView *);
void editProjectProperties(QETProject *);
void slot_terminalNumbering();
void slot_reloadElementDrawings();
void editDiagramProperties(DiagramView *);
void editDiagramProperties(Diagram *);
void addDiagramToProject(QETProject *);
@@ -211,6 +212,7 @@ class QETDiagramEditor : public QETMainWindow
*m_project_export_wiring_list, ///< Action to export the wiring list
*m_project_wiring_list_view, ///< Action to show the wiring list read from the project database
*m_terminal_numbering, ///< Action to launch terminal numbering
*m_reload_element_drawings, ///< Action to redraw every placed element from its current definition
*m_export_project_db, ///Export to file the internal database of the current project
*m_tile_window, ///< Show MDI subwindows as tile
*m_cascade_window, ///< Show MDI subwindows as cascade
+133
View File
@@ -1784,6 +1784,139 @@ ElementsLocation Element::location() const
return m_location;
}
/**
@brief Element::reloadPicture
Re-fetch this element's drawing from its location and repaint.
A placed element is drawn once from its definition, at construction
(buildFromXml()), and nothing afterwards ever makes it look again --
editing and saving the definition leaves every already-placed instance
showing the old drawing until the project is closed and reopened
(bugtracker #802). This is the per-instance half of the fix.
Deliberately limited to the drawing. Terminals are what conductors are
attached to: if the new definition adds, removes or moves a terminal,
or changes the element size or hotspot, the new drawing would no longer
match the live terminals and bounding rect. Such an element is left
untouched and GeometryChanged is returned; it has to be removed and
re-inserted, which deletes the conductors already connected to it.
If the definition cannot be found or read, the current drawing is kept
and Unavailable is returned, so the element never goes blank.
Purely visual: nothing is pushed on the undo stack and the project is
not marked as modified.
@return what happened to this element
*/
Element::ReloadPictureResult Element::reloadPicture()
{
if (!m_location.exist()) {
return ReloadPictureResult::Unavailable;
}
const QDomElement definition = m_location.xml();
if (definition.isNull()) {
return ReloadPictureResult::Unavailable;
}
if (!definitionGeometryMatches(definition)) {
return ReloadPictureResult::GeometryChanged;
}
QPicture picture;
QPicture low_zoom_picture;
ElementPictureFactory::instance()->getPictures(m_location,
picture,
low_zoom_picture);
if (picture.isNull()) {
return ReloadPictureResult::Unavailable;
}
m_picture = picture;
m_low_zoom_picture = low_zoom_picture;
update();
return ReloadPictureResult::Reloaded;
}
/**
@brief Element::definitionGeometryMatches
Compare the geometry described by @p definition with this live element:
size and hotspot (normalized the same way setSize()/setHotspot() do it)
and the set of terminal positions (same parsing rules as
TerminalData::fromXml()).
@param definition : the <definition> root of the element
@return true if the new drawing can be applied without desynchronizing
the bounding rect or the terminals
*/
bool Element::definitionGeometryMatches(const QDomElement &definition) const
{
int w = 0, h = 0, hot_x = 0, hot_y = 0;
if (!QET::attributeIsAnInteger(definition, QStringLiteral("width"), &w) ||
!QET::attributeIsAnInteger(definition, QStringLiteral("height"), &h) ||
!QET::attributeIsAnInteger(definition, QStringLiteral("hotspot_x"), &hot_x) ||
!QET::attributeIsAnInteger(definition, QStringLiteral("hotspot_y"), &hot_y)) {
return false;
}
//Same rounding as setSize()
while (w % 10) ++w;
while (h % 10) ++h;
if (QSize(w, h) != dimensions) {
return false;
}
//Same clamping as setHotspot()
const QPoint new_hotspot = dimensions.isNull()
? QPoint(0, 0)
: QPoint(qMin(hot_x, w), qMin(hot_y, h));
if (new_hotspot != hotspot_coord) {
return false;
}
//Terminal positions described by the new definition
QList<QPointF> new_terminals;
for (QDomElement description = definition.firstChildElement(QStringLiteral("description")) ;
!description.isNull() ;
description = description.nextSiblingElement(QStringLiteral("description")))
{
for (QDomElement terminal = description.firstChildElement(QStringLiteral("terminal")) ;
!terminal.isNull() ;
terminal = terminal.nextSiblingElement(QStringLiteral("terminal")))
{
qreal x = 0.0, y = 0.0;
if (QET::attributeIsAReal(terminal, QStringLiteral("x"), &x) &&
QET::attributeIsAReal(terminal, QStringLiteral("y"), &y)) {
new_terminals << QPointF(x, y);
}
}
}
if (new_terminals.size() != m_terminals.size()) {
return false;
}
//Every live terminal must still exist at the same place
for (const Terminal *terminal : m_terminals)
{
const QPointF live_pos = mapFromScene(terminal->dockConductor());
bool found = false;
for (int i = 0 ; i < new_terminals.size() ; ++i)
{
const QPointF delta = new_terminals.at(i) - live_pos;
if (qAbs(delta.x()) < 0.01 && qAbs(delta.y()) < 0.01) {
new_terminals.removeAt(i);
found = true;
break;
}
}
if (!found) {
return false;
}
}
return true;
}
/**
* @brief Element::updateConductorTexts
*Slot that is triggered when a cable is *
+8
View File
@@ -83,6 +83,7 @@ class Element : public QetGraphicsItem
Element::kind link_type = Element::Simple);
~Element() override;
private:
bool definitionGeometryMatches(const QDomElement &definition) const;
Element(const Element &);
// attributes
@@ -153,6 +154,13 @@ class Element : public QetGraphicsItem
QString name() const override;
ElementsLocation location() const;
/// Result of Element::reloadPicture()
enum class ReloadPictureResult {
Reloaded, ///< drawing replaced by the current definition
Unavailable, ///< definition missing or unreadable, old drawing kept
GeometryChanged ///< size, hotspot or terminals changed, old drawing kept
};
ReloadPictureResult reloadPicture();
virtual void setHighlighted(bool);
void displayHelpLine(bool b = true);
QSize size() const;