mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-20 15:24:14 +02:00
Reload element drawings: keep old drawing on failure, skip elements whose geometry changed (#802)
- Element::reloadPicture() now returns a ReloadPictureResult and never clears the current drawing before a successful rebuild: a missing or unreadable definition leaves the element as it was instead of blank. - Elements whose size, hotspot or terminals (added, removed or moved) differ from the new definition are not redrawn: the new drawing would no longer match their bounding rect and live terminals. - The action lists those elements and warns that they must be removed and re-inserted, which deletes the conductors already connected to them. - Status tip states the action is not undoable.
This commit is contained in:
committed by
ispyisail
parent
43d27a9563
commit
9004db1d99
@@ -555,7 +555,7 @@ void QETDiagramEditor::setUpActions()
|
||||
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"));
|
||||
" 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
|
||||
@@ -2990,9 +2990,10 @@ void QETDiagramEditor::slot_terminalNumbering() {
|
||||
is closed and reopened.
|
||||
|
||||
Purely visual and not undoable, the way pressing a "refresh" button
|
||||
would be: it does not touch position, rotation, links, elementInformations,
|
||||
labels or dynamic texts, and does not detect or handle a definition whose
|
||||
terminals moved -- those still need the usual remove-and-reinsert.
|
||||
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();
|
||||
@@ -3015,12 +3016,62 @@ void QETDiagramEditor::slot_reloadElementDrawings() {
|
||||
}
|
||||
}
|
||||
|
||||
for (Element *elmt : elements) {
|
||||
elmt->reloadPicture();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QET::QetMessageBox::information(
|
||||
this,
|
||||
tr("Recharger les dessins des éléments"),
|
||||
tr("%n élément(s) redessiné(s).", "", elements.size()));
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -1792,24 +1792,129 @@ ElementsLocation Element::location() const
|
||||
(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: the caller
|
||||
is expected to have already dropped the shared ElementPictureFactory
|
||||
cache for this location, or the fresh fetch below just returns the same
|
||||
cached picture unchanged.
|
||||
(bugtracker #802). This is the per-instance half of the fix.
|
||||
|
||||
Deliberately limited to the drawing: terminals and dynamic texts are
|
||||
live objects carrying state a reload cannot safely fabricate --
|
||||
terminal positions are what conductors are attached to, and dynamic
|
||||
texts carry per-instance overrides (position, visibility) a rebuild
|
||||
would have to invent a default for. A definition whose terminals moved
|
||||
still needs the element removed and re-inserted, same as today.
|
||||
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
|
||||
*/
|
||||
void Element::reloadPicture()
|
||||
Element::ReloadPictureResult Element::reloadPicture()
|
||||
{
|
||||
m_picture = QPicture();
|
||||
m_low_zoom_picture = QPicture();
|
||||
ElementPictureFactory::instance()->getPictures(m_location, m_picture, m_low_zoom_picture);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,7 +154,13 @@ class Element : public QetGraphicsItem
|
||||
|
||||
QString name() const override;
|
||||
ElementsLocation location() const;
|
||||
void reloadPicture();
|
||||
/// 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