mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-17 19:34:13 +02:00
Add an optional label to pictures (#349)
A picture can now carry a caption, set from its properties panel
("Libellé"). It is drawn centred under the picture at the folio's
normal text size whatever the picture's scale, turns with it, and
moves, copies and prints with it because the picture itself paints it.
Clicking the caption selects the picture.
Saved as a "label" attribute on <image>, written only when non-empty:
a project without labels saves byte-for-byte as before, and older
versions open a labelled project and simply ignore the caption.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G2d2Zi8BfrYRPX88zhaoFG
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
#include <QBuffer>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QFontMetricsF>
|
||||
#include <QGraphicsSceneContextMenuEvent>
|
||||
#include <QImageWriter>
|
||||
#include <QMenu>
|
||||
@@ -69,7 +70,7 @@ DiagramImageItem::DiagramImageItem(const QPixmap &pixmap, QetGraphicsItem *paren
|
||||
// those are a single uniform scale() float, which is exactly why an
|
||||
// image could never break its own aspect ratio before this class
|
||||
// gained proper independent scaleX/scaleY.
|
||||
m_transform.pivot = boundingRect().center();
|
||||
m_transform.pivot = imageRect().center();
|
||||
setTransform(m_transform.toMatrix());
|
||||
setFlags(QGraphicsItem::ItemIsSelectable|QGraphicsItem::ItemIsMovable|QGraphicsItem::ItemSendsGeometryChanges);
|
||||
setAcceptHoverEvents(true);
|
||||
@@ -110,6 +111,45 @@ void DiagramImageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *
|
||||
painter -> drawRect(boundingRect());
|
||||
painter -> restore();
|
||||
}
|
||||
|
||||
if (!m_label.isEmpty())
|
||||
{
|
||||
// Undo the picture's own scale so the caption keeps the size of
|
||||
// every other text on the folio whether the picture is shown at
|
||||
// 5 % or 500 %. Rotation and skew are applied after scale in
|
||||
// ShapeTransform::toMatrix(), so the caption still turns with it.
|
||||
const QRectF r = labelRect();
|
||||
painter -> save();
|
||||
painter -> translate(r.topLeft());
|
||||
painter -> scale(1.0 / m_label_scale.x(), 1.0 / m_label_scale.y());
|
||||
painter -> setFont(m_label_font);
|
||||
painter -> setPen(Qt::black);
|
||||
painter -> drawText(QRectF(QPointF(0, 0), m_label_size), Qt::AlignCenter, m_label);
|
||||
painter -> restore();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::setLabel
|
||||
Set the caption drawn under the picture. An empty string removes it.
|
||||
@param label
|
||||
*/
|
||||
void DiagramImageItem::setLabel(const QString &label)
|
||||
{
|
||||
if (label == m_label)
|
||||
return;
|
||||
|
||||
prepareGeometryChange();
|
||||
m_label = label;
|
||||
// Font looked up here, not in paint(): diagramTextsFont() reads
|
||||
// QSettings, far too slow to do on every repaint.
|
||||
if (!m_label.isEmpty())
|
||||
m_label_font = QETApp::diagramTextsFont();
|
||||
m_label_size = m_label.isEmpty()
|
||||
? QSizeF()
|
||||
: QFontMetricsF(m_label_font).size(0, m_label);
|
||||
updateLabelScale();
|
||||
emit labelChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,7 +318,7 @@ void DiagramImageItem::setPivotRaw(const QPointF &newPivot)
|
||||
void DiagramImageItem::resetPivotToBoundingRectCenter()
|
||||
{
|
||||
m_pivotIsCustom = false;
|
||||
setPivot(boundingRect().center());
|
||||
setPivot(imageRect().center());
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -1263,6 +1303,11 @@ QVariant DiagramImageItem::itemChange(GraphicsItemChange change, const QVariant
|
||||
}
|
||||
refreshInteractionHints();
|
||||
}
|
||||
else if (change == ItemTransformChange && !m_label.isEmpty())
|
||||
{
|
||||
prepareGeometryChange();
|
||||
updateLabelScale();
|
||||
}
|
||||
else if (change == ItemPositionHasChanged || change == ItemTransformHasChanged)
|
||||
{
|
||||
if (!m_deferHandleReposition)
|
||||
@@ -1308,6 +1353,18 @@ QPixmap DiagramImageItem::computeDisplayPixmap(const QPixmap &base, const QRect
|
||||
@return a QRectF represent the bounding rectangle
|
||||
*/
|
||||
QRectF DiagramImageItem::boundingRect() const
|
||||
{
|
||||
if (m_label.isEmpty())
|
||||
return imageRect();
|
||||
return imageRect().united(labelRect());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::imageRect
|
||||
@return the picture's own rectangle, without its label. Handles, the
|
||||
pivot and every other piece of geometry work on this one.
|
||||
*/
|
||||
QRectF DiagramImageItem::imageRect() const
|
||||
{
|
||||
if (!pixmap_.isNull()) {
|
||||
return (QRectF(pixmap_.rect()));
|
||||
@@ -1317,6 +1374,34 @@ QRectF DiagramImageItem::boundingRect() const
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::labelRect
|
||||
@return the label's rectangle in item coordinates, centred just under
|
||||
the picture. Divided by the picture's scale, because paint() draws
|
||||
the label unscaled.
|
||||
*/
|
||||
QRectF DiagramImageItem::labelRect() const
|
||||
{
|
||||
const QRectF image = imageRect();
|
||||
const qreal w = m_label_size.width() / m_label_scale.x();
|
||||
const qreal h = m_label_size.height() / m_label_scale.y();
|
||||
const qreal gap = 2.0 / m_label_scale.y();
|
||||
return QRectF(image.center().x() - w / 2.0, image.bottom() + gap, w, h);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::updateLabelScale
|
||||
The label rect depends on the picture's scale, so boundingRect()
|
||||
changes with it. m_transform is already updated by the time the item
|
||||
transform changes, so the scale is cached here and only refreshed
|
||||
after prepareGeometryChange() has recorded the old rect.
|
||||
*/
|
||||
void DiagramImageItem::updateLabelScale()
|
||||
{
|
||||
const auto safe = [](qreal v) { return qFuzzyIsNull(v) ? 1.0 : qAbs(v); };
|
||||
m_label_scale = QPointF(safe(m_transform.scaleX), safe(m_transform.scaleY));
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::name
|
||||
@return the generic name of this item (picture)
|
||||
@@ -1426,7 +1511,7 @@ bool DiagramImageItem::fromXml(const QDomElement &e)
|
||||
m_transform.rotation = e.attribute("rotation").toDouble();
|
||||
m_transform.scaleX = e.attribute("size").toDouble();
|
||||
m_transform.scaleY = m_transform.scaleX;
|
||||
m_transform.pivot = boundingRect().center();
|
||||
m_transform.pivot = imageRect().center();
|
||||
m_pivotIsCustom = false;
|
||||
|
||||
const QDomElement transformElement = e.firstChildElement("transform");
|
||||
@@ -1447,6 +1532,7 @@ bool DiagramImageItem::fromXml(const QDomElement &e)
|
||||
QGraphicsObject::setPos(e.attribute("x").toDouble(), e.attribute("y").toDouble());
|
||||
setZValue(e.attribute("z", QString::number(this->zValue())).toDouble());
|
||||
is_movable_ = (e.attribute("is_movable").toInt());
|
||||
setLabel(e.attribute("label"));
|
||||
|
||||
return (true);
|
||||
}
|
||||
@@ -1477,6 +1563,10 @@ QDomElement DiagramImageItem::toXml(QDomDocument &document) const
|
||||
result.setAttribute("rotation", QString::number(QET::correctAngle(m_transform.rotation)));
|
||||
result.setAttribute("size", QString::number(m_transform.scaleX));
|
||||
result.setAttribute("is_movable", bool(is_movable_));
|
||||
// An attribute, not a child element: fromXml() of every earlier
|
||||
// version requires the base64 text to be the first child.
|
||||
if (!m_label.isEmpty())
|
||||
result.setAttribute("label", m_label);
|
||||
|
||||
//write the pixmap in the xml element after he was been transformed to base64
|
||||
QByteArray array;
|
||||
|
||||
Reference in New Issue
Block a user