mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-09-27 12:34:14 +02:00
Merge pull request #1068 from ispyisail/feat/349-image-label
Add a label to pictures (#349)
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)
|
||||
@@ -1474,7 +1559,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");
|
||||
@@ -1495,6 +1580,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);
|
||||
}
|
||||
@@ -1525,6 +1611,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
|
||||
const QByteArray &array = encodedPng(pixmap_, m_png_cache, m_png_cache_key);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "../ui/imagetransparentcolordialog.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QFont>
|
||||
#include <QList>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <QVector>
|
||||
@@ -46,6 +47,7 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
Q_PROPERTY(qreal skewX READ skewX WRITE setSkewX NOTIFY transformChanged)
|
||||
Q_PROPERTY(qreal skewY READ skewY WRITE setSkewY NOTIFY transformChanged)
|
||||
Q_PROPERTY(QPointF pivot READ pivot WRITE setPivot NOTIFY transformChanged)
|
||||
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
|
||||
// A second, deliberately non-compensating property on the SAME
|
||||
// underlying value -- setPivot() (above) intentionally adjusts
|
||||
// pos() to keep the image visually in place, which is exactly
|
||||
@@ -123,10 +125,13 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
QPointF pivot() const { return m_transform.pivot; }
|
||||
void setPivot(const QPointF &pivot);
|
||||
void setPivotRaw(const QPointF &pivot);
|
||||
QString label() const { return m_label; }
|
||||
void setLabel(const QString &label);
|
||||
|
||||
signals:
|
||||
void pixmapChanged();
|
||||
void transformChanged();
|
||||
void labelChanged();
|
||||
|
||||
protected:
|
||||
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override;
|
||||
@@ -176,6 +181,9 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
static QString hintForHandleRole(HandleRole role);
|
||||
void showStatusHint(const QString &text) const;
|
||||
void clearStatusHint() const;
|
||||
QRectF imageRect() const;
|
||||
QRectF labelRect() const;
|
||||
void updateLabelScale();
|
||||
|
||||
protected:
|
||||
QPixmap pixmap_;
|
||||
@@ -223,6 +231,13 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
QPointF m_original_pos; // scene position at the start of a resize/rotate/pivot drag, for Escape-to-cancel
|
||||
ShapeTransform m_original_transform;
|
||||
bool m_deferHandleReposition = false; // see setPivot()'s comment
|
||||
// Optional caption drawn centred under the picture (issue #349).
|
||||
// Empty by default, and then neither saved nor painted, so a picture
|
||||
// without one costs exactly what it did before.
|
||||
QString m_label;
|
||||
QFont m_label_font;
|
||||
QSizeF m_label_size; // in scene units, measured once in setLabel()
|
||||
QPointF m_label_scale{1.0, 1.0}; // scale the label rect was last computed for -- see updateLabelScale()
|
||||
bool m_resizeCenterAnchored = false; // decided once, at press time -- see handlerMousePressEvent()'s comment for why, mirroring the identical fix already made for shape creation
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -64,16 +64,21 @@ void ImagePropertiesWidget::setImageItem(DiagramImageItem *image)
|
||||
this->setEnabled(true);
|
||||
if (m_image == image) return;
|
||||
if (m_image)
|
||||
{
|
||||
disconnect(m_image, &DiagramImageItem::transformChanged, this, &ImagePropertiesWidget::updateUi);
|
||||
disconnect(m_image, &DiagramImageItem::labelChanged, this, &ImagePropertiesWidget::updateUi);
|
||||
}
|
||||
|
||||
m_image = image;
|
||||
connect(m_image, &DiagramImageItem::transformChanged, this, &ImagePropertiesWidget::updateUi);
|
||||
connect(m_image, &DiagramImageItem::labelChanged, this, &ImagePropertiesWidget::updateUi);
|
||||
m_movable = image->isMovable();
|
||||
m_scaleX = m_image->scaleFactorX();
|
||||
m_scaleY = m_image->scaleFactorY();
|
||||
m_rotation = m_image->rotationAngle();
|
||||
m_skewX = m_image->skewX();
|
||||
m_skewY = m_image->skewY();
|
||||
m_label = m_image->label();
|
||||
updateUi();
|
||||
}
|
||||
|
||||
@@ -101,6 +106,7 @@ void ImagePropertiesWidget::apply()
|
||||
m_rotation = m_image->rotationAngle();
|
||||
m_skewX = m_image->skewX();
|
||||
m_skewY = m_image->skewY();
|
||||
m_label = m_image->label();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -116,6 +122,7 @@ void ImagePropertiesWidget::reset()
|
||||
m_image->setRotationAngle(m_rotation);
|
||||
m_image->setSkewX(m_skewX);
|
||||
m_image->setSkewY(m_skewY);
|
||||
m_image->setLabel(m_label);
|
||||
m_image->setMovable(m_movable);
|
||||
updateUi();
|
||||
}
|
||||
@@ -138,6 +145,7 @@ bool ImagePropertiesWidget::setLiveEdit(bool live_edit)
|
||||
connect (ui->m_angle_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
connect (ui->m_skew_x_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
connect (ui->m_skew_y_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
connect (ui->m_label_le, &QLineEdit::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -146,6 +154,7 @@ bool ImagePropertiesWidget::setLiveEdit(bool live_edit)
|
||||
disconnect (ui->m_angle_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
disconnect (ui->m_skew_x_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
disconnect (ui->m_skew_y_sb, &QDoubleSpinBox::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
disconnect (ui->m_label_le, &QLineEdit::editingFinished, this, &ImagePropertiesWidget::apply);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -191,6 +200,19 @@ QUndoCommand* ImagePropertiesWidget::associatedUndo() const
|
||||
chain("skewX", m_skewX, newSkewX, tr("Modifier l'inclinaison d'une image"));
|
||||
chain("skewY", m_skewY, newSkewY, tr("Modifier l'inclinaison d'une image"));
|
||||
|
||||
// Not through chain(): a string cannot be animated.
|
||||
const QString newLabel = ui->m_label_le->text();
|
||||
if (newLabel != m_label)
|
||||
{
|
||||
if (undo)
|
||||
new QPropertyUndoCommand(m_image, "label", m_label, newLabel, undo);
|
||||
else
|
||||
{
|
||||
undo = new QPropertyUndoCommand(m_image, "label", m_label, newLabel);
|
||||
undo->setText(tr("Modifier le libellé d'une image"));
|
||||
}
|
||||
}
|
||||
|
||||
return undo;
|
||||
}
|
||||
|
||||
@@ -221,6 +243,8 @@ void ImagePropertiesWidget::updateUi()
|
||||
ui->m_skew_x_sb->setValue(m_image->skewX());
|
||||
ui->m_skew_y_sb->setValue(m_image->skewY());
|
||||
ui->m_lock_pos_cb->setChecked(!m_image->isMovable());
|
||||
if (ui->m_label_le->text() != m_image->label())
|
||||
ui->m_label_le->setText(m_image->label());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,6 +70,7 @@ class ImagePropertiesWidget : public PropertiesEditorWidget
|
||||
qreal m_rotation;
|
||||
qreal m_skewX;
|
||||
qreal m_skewY;
|
||||
QString m_label;
|
||||
// Guards the width/height spinboxes' mutual updates when
|
||||
// "Conserver les proportions" is checked, so setting one
|
||||
// programmatically in response to the other doesn't re-trigger
|
||||
|
||||
@@ -152,14 +152,28 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_label">
|
||||
<property name="text">
|
||||
<string>Libellé</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="m_label_le">
|
||||
<property name="toolTip">
|
||||
<string>Texte affiché sous l'image. Il suit l'image quand elle est déplacée, copiée ou tournée.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="m_lock_pos_cb">
|
||||
<property name="text">
|
||||
<string>Verrouiller la position</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<item row="8" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
|
||||
Reference in New Issue
Block a user