mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 07:14:13 +02:00
Merge pull request #889 from ispyisail/fix/802-reload-element-drawings
Add "Reload element drawings" to refresh placed elements
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 *
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user