diff --git a/sources/dataBase/projectdatabase.cpp b/sources/dataBase/projectdatabase.cpp index 0dc55ea4e..68174761a 100644 --- a/sources/dataBase/projectdatabase.cpp +++ b/sources/dataBase/projectdatabase.cpp @@ -24,12 +24,17 @@ #include "../elementprovider.h" #include "../qetapp.h" #include "../qetgraphicsitem/conductor.h" +#include "../qetgraphicsitem/diagramimageitem.h" #include "../qetgraphicsitem/element.h" +#include "../qetgraphicsitem/independenttextitem.h" +#include "../qetgraphicsitem/qetshapeitem.h" #include "../qetgraphicsitem/terminal.h" #include "../qetinformation.h" #include "../qetproject.h" #include +#include +#include #include #include #include @@ -103,6 +108,7 @@ void projectDataBase::updateDB() //refresh, and what they read back is unchanged either way. if (!m_content_changed) { + flushDrawingItems(); emit dataBaseUpdated(); return; } @@ -112,6 +118,7 @@ void projectDataBase::updateDB() populateElementTable(); populateElementInfoTable(); populateConductorTable(); + populateDrawingItemTables(); m_content_changed = false; emit dataBaseUpdated(); @@ -211,6 +218,11 @@ bool projectDataBase::isReadOnlySelect(const QString &query, QString *error) QSqlQuery projectDataBase::newQuery(const QString &query, QString *error) { QString reason; + //Drawing-item rows are rewritten lazily, see drawingItemChanged(). + //Every read from outside comes through here, so this is the one + //place the queue has to be emptied for a reader to see current rows. + flushDrawingItems(); + // First gate: which kind of statement is acceptable here at all. A // textual check is the right tool for that and the wrong tool for // anything else -- see isReadOnlySelect()'s own comment. It is what @@ -389,6 +401,13 @@ void projectDataBase::addDiagram(Diagram *diagram) qDebug() << "projectDataBase::addDiagram insert info error : " << m_insert_diagram_info_query.lastError(); } + //A folio put back by undoing its removal comes with its items already + //on it, and their rows went with it: queue them again. + const QList items = diagram->items(); + for (QGraphicsItem *item : items) { + addDrawingItem(item); + } + //The information "folio" of other existing diagram can have the variable %total, //so when a new diagram is added this variable change. //We need to update this information in the database. @@ -448,6 +467,31 @@ void projectDataBase::removeDiagram(Diagram *diagram) return; } + for (const QString &table : {QStringLiteral("shape"), + QStringLiteral("independent_text"), + QStringLiteral("image")}) + { + QSqlQuery cascade(m_data_base); + cascade.prepare(QStringLiteral("DELETE FROM %1 WHERE diagram_uuid = :uuid").arg(table)); + cascade.bindValue(QStringLiteral(":uuid"), uuid_str); + if (!cascade.exec()) { + qDebug() << "projectDataBase::removeDiagram" << table << "cascade error : " + << cascade.lastError(); + m_data_base.rollback(); + return; + } + } + //The folio's items keep existing (the removal can be undone), but + //their rows are gone: forget them, so that nothing is deleted or + //skipped later on the strength of a row that no longer exists. + const QList tracked = m_drawing_item_row.keys(); + for (QObject *object : tracked) { + auto *item = dynamic_cast(object); + if (item && item->scene() == diagram) { + forgetDrawingItem(object); + } + } + m_remove_diagram_query.bindValue(":uuid", uuid_str); if (!m_remove_diagram_query.exec()) { qDebug() << "projectDataBase::removeDiagram delete error : " << m_remove_diagram_query.lastError(); @@ -606,6 +650,305 @@ void projectDataBase::bindConductorValues(QSqlQuery &query, Conductor *conductor query.bindValue(QStringLiteral(":text"), conductor->properties().text); } +namespace { + +/// Table holding @p object's row, or an empty string if it has none. +QString drawingItemTable(QObject *object) +{ + if (qobject_cast(object)) return QStringLiteral("shape"); + if (qobject_cast(object)) return QStringLiteral("independent_text"); + if (qobject_cast(object)) return QStringLiteral("image"); + return QString(); +} + +QUuid drawingItemUuid(QObject *object) +{ + if (auto s = qobject_cast(object)) return s->uuid(); + if (auto t = qobject_cast(object)) return t->uuid(); + if (auto i = qobject_cast(object)) return i->uuid(); + return QUuid(); +} + +} // namespace + +/** + @brief projectDataBase::addDrawingItem + Start keeping a row for a shape, an independent text or an image that + was just added to a folio. Anything else is ignored, so Diagram::addItem() + can pass every item it gets. + + The row is not written here but queued, like every later change to it: + see drawingItemChanged() for why. + @param item +*/ +void projectDataBase::addDrawingItem(QGraphicsItem *item) +{ + QGraphicsObject *object = item ? item->toGraphicsObject() : nullptr; + if (!object || drawingItemTable(object).isEmpty()) { + return; + } + + const auto unique = Qt::UniqueConnection; + connect(object, &QGraphicsObject::xChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(object, &QGraphicsObject::yChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(object, &QGraphicsObject::rotationChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(object, &QObject::destroyed, this, &projectDataBase::drawingItemDestroyed, unique); + + if (auto shape = qobject_cast(object)) { + connect(shape, &QetShapeItem::uuidChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(shape, &QetShapeItem::geometryChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(shape, &QetShapeItem::transformChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(shape, &QetShapeItem::penChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(shape, &QetShapeItem::brushChanged, this, &projectDataBase::drawingItemChanged, unique); + } + else if (auto text = qobject_cast(object)) { + connect(text, &IndependentTextItem::uuidChanged, this, &projectDataBase::drawingItemChanged, unique); + //Sent by the document, not the item: drawingItemChanged() walks + //back up to the item. It is the one signal that catches every way + //the text changes -- typing, undo, a script's setTextContent(). + connect(text->document(), &QTextDocument::contentsChanged, + this, &projectDataBase::drawingItemChanged, unique); + } + else if (auto image = qobject_cast(object)) { + connect(image, &DiagramImageItem::uuidChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(image, &DiagramImageItem::transformChanged, this, &projectDataBase::drawingItemChanged, unique); + connect(image, &DiagramImageItem::pixmapChanged, this, &projectDataBase::drawingItemChanged, unique); + } + + m_dirty_drawing_items.insert(object); +} + +/** + @brief projectDataBase::removeDrawingItem + Drop the row of a shape, independent text or image taken off its folio. + The item itself usually lives on in the undo stack, so it is also + disconnected: a change to it there must not bring its row back. + @param item +*/ +void projectDataBase::removeDrawingItem(QGraphicsItem *item) +{ + QGraphicsObject *object = item ? item->toGraphicsObject() : nullptr; + if (!object || drawingItemTable(object).isEmpty()) { + return; + } + + disconnect(object, nullptr, this, nullptr); + if (auto text = qobject_cast(object)) { + disconnect(text->document(), nullptr, this, nullptr); + } + + const QUuid row = m_drawing_item_row.value(object); + if (!row.isNull() && m_drawing_row_owner.value(row) == object) + { + QSqlQuery remove(m_data_base); + remove.prepare(QStringLiteral("DELETE FROM %1 WHERE uuid = :uuid") + .arg(drawingItemTable(object))); + remove.bindValue(QStringLiteral(":uuid"), row.toString()); + if (!remove.exec()) { + qDebug() << "projectDataBase::removeDrawingItem delete error : " << remove.lastError(); + } + } + forgetDrawingItem(object); +} + +/** + @brief projectDataBase::drawingItemChanged + Queue the sender's row to be rewritten. + + Queued rather than written: a move sends xChanged/yChanged for every + mouse step of every selected item, and nothing reads the rows between + two steps. newQuery() and updateDB() flush the queue before anything + does, so a reader never sees a stale row -- a queued write only costs a + set insertion. +*/ +void projectDataBase::drawingItemChanged() +{ + QObject *object = sender(); + //QTextDocument::contentsChanged: the item is an ancestor of the + //document (item -> text control -> document). + while (object && drawingItemTable(object).isEmpty()) { + object = object->parent(); + } + if (object) { + m_dirty_drawing_items.insert(object); + } +} + +/** + @brief projectDataBase::drawingItemDestroyed + An item deleted while still on its folio (e.g. by the scene's own + destructor) never went through removeDrawingItem(). Its type can no + longer be asked -- this runs from QObject's destructor -- so its row, + if it wrote one, is looked for in all three tables. + @param object +*/ +void projectDataBase::drawingItemDestroyed(QObject *object) +{ + const QUuid row = m_drawing_item_row.value(object); + if (!row.isNull() && m_drawing_row_owner.value(row) == object) + { + for (const QString &table : {QStringLiteral("shape"), + QStringLiteral("independent_text"), + QStringLiteral("image")}) + { + QSqlQuery remove(m_data_base); + remove.prepare(QStringLiteral("DELETE FROM %1 WHERE uuid = :uuid").arg(table)); + remove.bindValue(QStringLiteral(":uuid"), row.toString()); + remove.exec(); + } + } + forgetDrawingItem(object); +} + +void projectDataBase::forgetDrawingItem(QObject *object) +{ + const QUuid row = m_drawing_item_row.take(object); + if (!row.isNull() && m_drawing_row_owner.value(row) == object) { + m_drawing_row_owner.remove(row); + } + m_dirty_drawing_items.remove(object); +} + +/** + @brief projectDataBase::writeDrawingItem + Write @p object's row under its current uuid. + @return false if the row must wait: another live item still owns that + uuid. That is a pasted copy in the moment between being added to the + folio and PasteDiagramCommand giving it its own uuid; writing then would + overwrite its source's row with the copy's position. The copy's + uuidChanged() queues it again once it has one. +*/ +bool projectDataBase::writeDrawingItem(QObject *object) +{ + auto *item = dynamic_cast(object); + auto *diagram = item ? qobject_cast(item->scene()) : nullptr; + if (!diagram || !m_project || !m_project->diagrams().contains(diagram)) { + //Not on a folio of this project (any more): nothing to write, + //and nothing to wait for. + return true; + } + + const QUuid uuid = drawingItemUuid(object); + QObject *owner = m_drawing_row_owner.value(uuid); + if (owner && owner != object) { + return false; + } + + QSqlQuery *query = nullptr; + const QRectF rect = item->sceneBoundingRect(); + if (auto shape = qobject_cast(object)) + { + query = &m_insert_shape_query; + const QMetaEnum type = QetShapeItem::staticMetaObject.enumerator( + QetShapeItem::staticMetaObject.indexOfEnumerator("ShapeType")); + query->bindValue(QStringLiteral(":type"), QString::fromLatin1(type.valueToKey(shape->shapeType()))); + query->bindValue(QStringLiteral(":color"), shape->pen().color().name()); + query->bindValue(QStringLiteral(":fill"), shape->brush().style() == Qt::NoBrush + ? QStringLiteral("none") + : shape->brush().color().name()); + } + else if (auto text = qobject_cast(object)) + { + query = &m_insert_independent_text_query; + query->bindValue(QStringLiteral(":text"), text->toPlainText()); + query->bindValue(QStringLiteral(":rotation"), text->rotation()); + } + else if (auto image = qobject_cast(object)) + { + query = &m_insert_image_query; + query->bindValue(QStringLiteral(":pixel_width"), image->pixmap().width()); + query->bindValue(QStringLiteral(":pixel_height"), image->pixmap().height()); + } + if (!query) { + return true; + } + + //Renewed since its last write (a paste, a folio duplication): its + //old row was its own, and describes nothing now. + const QUuid previous = m_drawing_item_row.value(object); + if (!previous.isNull() && previous != uuid + && m_drawing_row_owner.value(previous) == object) + { + QSqlQuery remove(m_data_base); + remove.prepare(QStringLiteral("DELETE FROM %1 WHERE uuid = :uuid") + .arg(drawingItemTable(object))); + remove.bindValue(QStringLiteral(":uuid"), previous.toString()); + remove.exec(); + m_drawing_row_owner.remove(previous); + } + + query->bindValue(QStringLiteral(":uuid"), uuid.toString()); + query->bindValue(QStringLiteral(":diagram_uuid"), diagram->uuid().toString()); + query->bindValue(QStringLiteral(":pos"), diagram->convertPosition(rect.topLeft()).toString()); + query->bindValue(QStringLiteral(":x"), rect.x()); + query->bindValue(QStringLiteral(":y"), rect.y()); + query->bindValue(QStringLiteral(":width"), rect.width()); + query->bindValue(QStringLiteral(":height"), rect.height()); + if (!query->exec()) { + qDebug() << "projectDataBase::writeDrawingItem error : " << query->lastError(); + return true; + } + + m_drawing_item_row.insert(object, uuid); + m_drawing_row_owner.insert(uuid, object); + return true; +} + +/** + @brief projectDataBase::flushDrawingItems + Write every queued drawing-item row. A row that has to wait for its uuid + (see writeDrawingItem()) stays queued. +*/ +void projectDataBase::flushDrawingItems() +{ + if (m_dirty_drawing_items.isEmpty()) { + return; + } + + const QSet dirty = m_dirty_drawing_items; + //One transaction for the batch, unless a caller already holds one. + const bool own_transaction = m_data_base.transaction(); + for (QObject *object : dirty) { + if (writeDrawingItem(object)) { + m_dirty_drawing_items.remove(object); + } + } + if (own_transaction) { + m_data_base.commit(); + } +} + +/** + @brief projectDataBase::populateDrawingItemTables + Rebuild the shape, independent_text and image tables from every folio. +*/ +void projectDataBase::populateDrawingItemTables() +{ + QSqlQuery query_(m_data_base); + query_.exec(QStringLiteral("DELETE FROM shape")); + query_.exec(QStringLiteral("DELETE FROM independent_text")); + query_.exec(QStringLiteral("DELETE FROM image")); + m_drawing_item_row.clear(); + m_drawing_row_owner.clear(); + m_dirty_drawing_items.clear(); + + //Queued directly, not through addDrawingItem(): every one of them + //came onto its folio through Diagram::addItem(), which connected it + //already, and a full rebuild runs on every load. + for (auto diagram : m_project->diagrams()) + { + const QList items = diagram->items(); + for (QGraphicsItem *item : items) + { + QGraphicsObject *object = item->toGraphicsObject(); + if (object && !drawingItemTable(object).isEmpty()) { + m_dirty_drawing_items.insert(object); + } + } + } + flushDrawingItems(); +} + /** @brief projectDataBase::createDataBase Create the data base @@ -735,9 +1078,37 @@ bool projectDataBase::createDataBase() } } + //The folio's drawing furniture: shapes, independent texts, images. + //x, y, width and height are the item's bounding rect on the folio, + //pos the folio cell of its top left corner, as for element. + const QString drawing_columns( + "uuid VARCHAR(50) PRIMARY KEY NOT NULL, " + "diagram_uuid VARCHAR(50) NOT NULL, " + "pos VARCHAR(6), " + "x REAL, y REAL, width REAL, height REAL, "); + for (const QString &table : { + QStringLiteral("CREATE TABLE shape (") + drawing_columns + + "type VARCHAR(20), color VARCHAR(20), fill VARCHAR(20), " + "FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))", + QStringLiteral("CREATE TABLE independent_text (") + drawing_columns + + "text TEXT, rotation REAL, " + "FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))", + QStringLiteral("CREATE TABLE image (") + drawing_columns + + "pixel_width INTEGER, pixel_height INTEGER, " + "FOREIGN KEY (diagram_uuid) REFERENCES diagram (uuid))", + QStringLiteral("CREATE INDEX idx_shape_diagram ON shape (diagram_uuid)"), + QStringLiteral("CREATE INDEX idx_independent_text_diagram ON independent_text (diagram_uuid)"), + QStringLiteral("CREATE INDEX idx_image_diagram ON image (diagram_uuid)") }) + { + if (!query_.exec(table)) { + qDebug() << "drawing item table query : " << query_.lastError(); + } + } + createElementNomenclatureView(); createSummaryView(); createWiringListView(); + createDrawingItemView(); prepareQuery(); updateDB(); return true; @@ -932,6 +1303,32 @@ void projectDataBase::createWiringListView() } } +/** + @brief projectDataBase::createDrawingItemView + One row per shape, independent text and image, whichever table holds it: + find anything by uuid without knowing its kind first. folio is the + folio's position in the project, starting at 1; description is the + shape type, the text, or empty for an image. +*/ +void projectDataBase::createDrawingItemView() +{ + QSqlQuery query(m_data_base); + const QString create_view( + "CREATE VIEW drawing_item_view AS " + "SELECT i.uuid, i.kind, d.pos AS folio, i.diagram_uuid, i.pos, " + "i.x, i.y, i.width, i.height, i.description FROM (" + "SELECT uuid, 'shape' AS kind, diagram_uuid, pos, x, y, width, height, " + "type AS description FROM shape " + "UNION ALL SELECT uuid, 'text', diagram_uuid, pos, x, y, width, height, " + "text FROM independent_text " + "UNION ALL SELECT uuid, 'image', diagram_uuid, pos, x, y, width, height, " + "'' FROM image" + ") AS i LEFT JOIN diagram AS d ON d.uuid = i.diagram_uuid"); + if (!query.exec(create_view)) { + qDebug() << query.lastError(); + } +} + void projectDataBase::populateDiagramTable() { QSqlQuery query_(m_data_base); @@ -1117,6 +1514,23 @@ void projectDataBase::prepareQuery() m_remove_diagram_query = QSqlQuery(m_data_base); m_remove_diagram_query.prepare("DELETE FROM diagram WHERE uuid=:uuid"); + //DRAWING ITEMS. OR REPLACE: a row is rewritten in place on every + //change, see writeDrawingItem(). + const QString drawing_columns("uuid, diagram_uuid, pos, x, y, width, height"); + const QString drawing_values(":uuid, :diagram_uuid, :pos, :x, :y, :width, :height"); + m_insert_shape_query = QSqlQuery(m_data_base); + m_insert_shape_query.prepare("INSERT OR REPLACE INTO shape (" + drawing_columns + + ", type, color, fill) VALUES (" + drawing_values + + ", :type, :color, :fill)"); + m_insert_independent_text_query = QSqlQuery(m_data_base); + m_insert_independent_text_query.prepare("INSERT OR REPLACE INTO independent_text (" + drawing_columns + + ", text, rotation) VALUES (" + drawing_values + + ", :text, :rotation)"); + m_insert_image_query = QSqlQuery(m_data_base); + m_insert_image_query.prepare("INSERT OR REPLACE INTO image (" + drawing_columns + + ", pixel_width, pixel_height) VALUES (" + drawing_values + + ", :pixel_width, :pixel_height)"); + //INSERT DIAGRAM INFO m_insert_diagram_info_query = QSqlQuery(m_data_base); QStringList bind_diag_info_values; @@ -1345,6 +1759,7 @@ void projectDataBase::exportDb(projectDataBase *db, // VACUUM INTO creates a standalone copy of the current database without // requiring access to the SQLite driver's native connection handle. const auto escaped_path = path_.replace("'", "''"); + db->flushDrawingItems(); QSqlQuery query(db->m_data_base); if (!query.exec("VACUUM INTO '" % escaped_path % "'")) { qWarning() << "Unable to export project database:" << query.lastError().text(); diff --git a/sources/dataBase/projectdatabase.h b/sources/dataBase/projectdatabase.h index 6a18b09a0..0ce1167ec 100644 --- a/sources/dataBase/projectdatabase.h +++ b/sources/dataBase/projectdatabase.h @@ -23,12 +23,16 @@ #include #include #include +#include +#include +#include class Element; class QETProject; class Diagram; class Conductor; class Terminal; +class QGraphicsItem; /** @brief The projectDataBase class @@ -97,9 +101,17 @@ class projectDataBase : public QObject void removeConductor (Conductor *conductor); void updateConductor (Conductor *conductor); + //Shapes, independent texts and images: the folio's drawing + //furniture. Anything else passed here is ignored. + void addDrawingItem (QGraphicsItem *item); + void removeDrawingItem (QGraphicsItem *item); + private slots: //Refresh the sender()'s row after Conductor::setProperties(). void conductorPropertiesChanged(); + //Queue the sender()'s drawing-item row for rewriting. + void drawingItemChanged(); + void drawingItemDestroyed(QObject *object); public: @@ -111,11 +123,16 @@ class projectDataBase : public QObject void createElementNomenclatureView(); void createSummaryView(); void createWiringListView(); + void createDrawingItemView(); void populateDiagramTable(); void populateElementTable(); void populateElementInfoTable(); void populateDiagramInfoTable(); void populateConductorTable(); + void populateDrawingItemTables(); + bool writeDrawingItem(QObject *object); + void flushDrawingItems(); + void forgetDrawingItem(QObject *object); void bindConductorValues(QSqlQuery &query, Conductor *conductor, Diagram *diagram); void watchConductor(Conductor *conductor); void insertTerminal(Terminal *terminal); @@ -153,7 +170,20 @@ class projectDataBase : public QObject m_cascade_remove_element_info_query, m_cascade_remove_terminal_query, m_cascade_remove_conductor_query, - m_cascade_remove_element_query; + m_cascade_remove_element_query, + m_insert_shape_query, + m_insert_independent_text_query, + m_insert_image_query; + + //Which uuid's row each drawing item last wrote, and which item + //wrote each row. A pasted copy is added to the folio still + //carrying its source's uuid and renewed only afterwards, so two + //live items can briefly share one: the row belongs to whichever + //wrote it, and the other waits in m_dirty_drawing_items until its + //uuid is its own. @see writeDrawingItem(). + QHash m_drawing_item_row; + QHash m_drawing_row_owner; + QSet m_dirty_drawing_items; #ifdef QET_EXPORT_PROJECT_DB public: diff --git a/sources/diagram.cpp b/sources/diagram.cpp index 1bb5d583d..bbf1165c5 100644 --- a/sources/diagram.cpp +++ b/sources/diagram.cpp @@ -1918,6 +1918,13 @@ void Diagram::addItem(QGraphicsItem *item) m_project->dataBase()->addConductor(conductor); break; } + case QetShapeItem::Type: + case IndependentTextItem::Type: + case DiagramImageItem::Type: + { + m_project->dataBase()->addDrawingItem(item); + break; + } default: {break;} } } @@ -1949,6 +1956,13 @@ void Diagram::removeItem(QGraphicsItem *item) m_project->dataBase()->removeConductor(conductor); break; } + case QetShapeItem::Type: + case IndependentTextItem::Type: + case DiagramImageItem::Type: + { + m_project->dataBase()->removeDrawingItem(item); + break; + } default: {break;} } diff --git a/sources/qetgraphicsitem/diagramimageitem.cpp b/sources/qetgraphicsitem/diagramimageitem.cpp index bc76f906c..ac6d27555 100644 --- a/sources/qetgraphicsitem/diagramimageitem.cpp +++ b/sources/qetgraphicsitem/diagramimageitem.cpp @@ -1339,7 +1339,7 @@ bool DiagramImageItem::fromXml(const QDomElement &e) } const QUuid uuid(e.attribute(QStringLiteral("uuid"))); - if (!uuid.isNull()) m_uuid = uuid; + if (!uuid.isNull() && uuid != m_uuid) setUuid(uuid); QDomNode image_node = e.firstChild(); if (!image_node.isText()) { diff --git a/sources/qetgraphicsitem/diagramimageitem.h b/sources/qetgraphicsitem/diagramimageitem.h index d86854361..e214bb4f1 100644 --- a/sources/qetgraphicsitem/diagramimageitem.h +++ b/sources/qetgraphicsitem/diagramimageitem.h @@ -93,8 +93,8 @@ class DiagramImageItem : public QetGraphicsItem { virtual bool fromXml(const QDomElement &); virtual QDomElement toXml(QDomDocument &) const; QUuid uuid() const {return m_uuid;} - void setUuid(const QUuid &uuid) {m_uuid = uuid;} - void newUuid() {m_uuid = QUuid::createUuid();} //create new uuid for this item + void setUuid(const QUuid &uuid) {m_uuid = uuid; emit uuidChanged();} + void newUuid() {setUuid(QUuid::createUuid());} //create new uuid for this item void editProperty() override; void setPixmap(const QPixmap &pixmap); QPixmap pixmap() const { return pixmap_; } @@ -131,6 +131,7 @@ class DiagramImageItem : public QetGraphicsItem { signals: void pixmapChanged(); void transformChanged(); + void uuidChanged(); protected: void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; diff --git a/sources/qetgraphicsitem/independenttextitem.cpp b/sources/qetgraphicsitem/independenttextitem.cpp index 557861643..03e0ba2bb 100644 --- a/sources/qetgraphicsitem/independenttextitem.cpp +++ b/sources/qetgraphicsitem/independenttextitem.cpp @@ -64,7 +64,7 @@ IndependentTextItem::~IndependentTextItem() */ void IndependentTextItem::fromXml(const QDomElement &e) { const QUuid uuid(e.attribute(QStringLiteral("uuid"))); - if (!uuid.isNull()) m_uuid = uuid; + if (!uuid.isNull() && uuid != m_uuid) setUuid(uuid); setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble()); setHtml(e.attribute("text")); setRotation(e.attribute("rotation").toDouble()); diff --git a/sources/qetgraphicsitem/independenttextitem.h b/sources/qetgraphicsitem/independenttextitem.h index c88a84d63..85b51ffd7 100644 --- a/sources/qetgraphicsitem/independenttextitem.h +++ b/sources/qetgraphicsitem/independenttextitem.h @@ -31,6 +31,9 @@ class IndependentTextItem : public DiagramTextItem Q_OBJECT // constructors, destructor + signals: + void uuidChanged(); + public: IndependentTextItem(); IndependentTextItem(const QString &); @@ -44,8 +47,8 @@ class IndependentTextItem : public DiagramTextItem void fromXml(const QDomElement &) override; QDomElement toXml(QDomDocument &) const override; QUuid uuid() const {return m_uuid;} - void setUuid(const QUuid &uuid) {m_uuid = uuid;} - void newUuid() {m_uuid = QUuid::createUuid();} //create new uuid for this item + void setUuid(const QUuid &uuid) {m_uuid = uuid; emit uuidChanged();} + void newUuid() {setUuid(QUuid::createUuid());} //create new uuid for this item protected: void focusOutEvent(QFocusEvent *event) override; diff --git a/sources/qetgraphicsitem/qetshapeitem.cpp b/sources/qetgraphicsitem/qetshapeitem.cpp index 44a1cca02..81295c571 100644 --- a/sources/qetgraphicsitem/qetshapeitem.cpp +++ b/sources/qetgraphicsitem/qetshapeitem.cpp @@ -2894,7 +2894,7 @@ bool QetShapeItem::fromXml(const QDomElement &e) //Absent in files written before shapes carried a uuid: keep the //one this item already has, Diagram::fromXml() settles it. const QUuid uuid(e.attribute(QStringLiteral("uuid"))); - if (!uuid.isNull()) m_uuid = uuid; + if (!uuid.isNull() && uuid != m_uuid) setUuid(uuid); // fromXml() is also used to *restore* an already-displayed item's // state (PromoteShapeCommand's undo/redo), not just to populate a diff --git a/sources/qetgraphicsitem/qetshapeitem.h b/sources/qetgraphicsitem/qetshapeitem.h index c72693067..fe7408e71 100644 --- a/sources/qetgraphicsitem/qetshapeitem.h +++ b/sources/qetgraphicsitem/qetshapeitem.h @@ -74,7 +74,8 @@ class QetShapeItem : public QetGraphicsItem void YRadiusChanged(); void transformChanged(); void arcChanged(); - void geometryChanged(); // P1/P2, polygon points, or path nodes changed -- lets the properties panel stay in sync while a handle is dragged, not just when it's typed into + void geometryChanged(); + void uuidChanged(); // P1/P2, polygon points, or path nodes changed -- lets the properties panel stay in sync while a handle is dragged, not just when it's typed into public: enum ShapeType {Line =1, @@ -155,8 +156,8 @@ class QetShapeItem : public QetGraphicsItem virtual bool fromXml (const QDomElement &); virtual QDomElement toXml (QDomDocument &document) const; QUuid uuid() const {return m_uuid;} - void setUuid(const QUuid &uuid) {m_uuid = uuid;} - void newUuid() {m_uuid = QUuid::createUuid();} //create new uuid for this item + void setUuid(const QUuid &uuid) {m_uuid = uuid; emit uuidChanged();} + void newUuid() {setUuid(QUuid::createUuid());} //create new uuid for this item virtual bool toDXF (const QString &filepath,const QPen &pen); void editProperty() override;