Give shapes, independent texts and images a persisted uuid

Elements, conductors, terminals and tables already carry a uuid. The
drawing furniture beside them did not, so a script or the MCP server could
only name a line, a box or a note by its index in a position-sorted list,
which shifts whenever one is added or removed.

- QetShapeItem, IndependentTextItem and DiagramImageItem get uuid(),
  newUuid() and setUuid(), read from and written to a "uuid" attribute.
- A folio loaded from a file written before this (or carrying a duplicate
  uuid) derives one from the folio uuid, the item kind and its order in the
  file, so the same file gives the same uuids on every load and a re-save
  is stable -- the #754 lesson for conductors.
- Paste and folio duplication renew them, as they already do for elements
  and conductors.
- Scripting: texts(), shapes() and images() end each line with the uuid;
  textIndex(), shapeIndex() and imageIndex() turn one back into an index.
- misc/qet-mcp: qet_diff keys texts, shapes and images on uuid when both
  sides have one, so a move or edit reads as a change to that item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-27 09:54:54 +13:00
parent d7036ad807
commit a9ad6e2f34
13 changed files with 206 additions and 47 deletions
+47 -6
View File
@@ -1395,11 +1395,12 @@ QStringList QetScriptApi::texts(int folioIndex) const
{
IndependentTextItem *t = list.at(i);
const QPointF at = t->sceneBoundingRect().topLeft();
out << QStringLiteral("%1: '%2' at (%3, %4)")
out << QStringLiteral("%1: '%2' at (%3, %4) %5")
.arg(i)
.arg(t->toPlainText())
.arg(at.x())
.arg(at.y());
.arg(at.y())
.arg(t->uuid().toString());
}
return out;
}
@@ -1518,10 +1519,11 @@ QStringList QetScriptApi::shapes(int folioIndex) const
{
QetShapeItem *shape = list.at(i);
const QRectF r = shape->sceneBoundingRect();
out << QStringLiteral("%1: %2 (%3, %4) to (%5, %6)")
out << QStringLiteral("%1: %2 (%3, %4) to (%5, %6) %7")
.arg(i)
.arg(shape->name())
.arg(r.left()).arg(r.top()).arg(r.right()).arg(r.bottom());
.arg(r.left()).arg(r.top()).arg(r.right()).arg(r.bottom())
.arg(shape->uuid().toString());
}
return out;
}
@@ -2836,13 +2838,52 @@ QStringList QetScriptApi::images(int folioIndex) const
DiagramImageItem *item = list.at(i);
const QPixmap px = item->pixmap();
const QPointF at = item->sceneBoundingRect().topLeft();
out << QStringLiteral("%1: %2x%3 px at (%4, %5) scale=%6 rotation=%7")
out << QStringLiteral("%1: %2x%3 px at (%4, %5) scale=%6 rotation=%7 %8")
.arg(i).arg(px.width()).arg(px.height()).arg(at.x()).arg(at.y())
.arg(item->scaleFactorX()).arg(item->rotationAngle());
.arg(item->scaleFactorX()).arg(item->rotationAngle())
.arg(item->uuid().toString());
}
return out;
}
namespace {
template <typename T>
int indexOfUuid(const QList<T *> &sorted, const QString &uuid)
{
const QUuid wanted(uuid);
if (wanted.isNull()) return -1;
for (int i = 0 ; i < sorted.count() ; ++i) {
if (sorted.at(i)->uuid() == wanted) return i;
}
return -1;
}
} // namespace
/**
@brief QetScriptApi::textIndex
The current index in texts() of the text carrying @p uuid, or -1.
The uuid is what to hold across an edit; the index is what the other
text calls take, and it shifts when a text is added or removed.
*/
int QetScriptApi::textIndex(int folioIndex, const QString &uuid) const
{
return indexOfUuid(sortedTexts(folioIndex), uuid);
}
/// @brief QetScriptApi::shapeIndex as textIndex(), for shapes().
int QetScriptApi::shapeIndex(int folioIndex, const QString &uuid) const
{
return indexOfUuid(sortedShapes(folioIndex), uuid);
}
/// @brief QetScriptApi::imageIndex as textIndex(), for images().
int QetScriptApi::imageIndex(int folioIndex, const QString &uuid) const
{
return indexOfUuid(sortedImages(folioIndex), uuid);
}
/**
@brief QetScriptApi::addImage
Place a picture from a file, as the "add image" tool does after its file