QGraphicsTextItem dynamic resizing
-
Hello,
I'd like to dynamically resize QGraphicsTextItem objects in QGraphicsScene to view to view rich text data (fonts, colors, indentation, etc.) while the content is always showed in the top left corner.
My problem is that I can't make it to resize continuously while keeping the content in the top left corner. The text is always drawn from the origin of the object so it needs constant repositioning and I can't position it correctly.
The best solution I've made so far is that position correcting happens only when the mouse button is released and the resizing is finished. I'm not happy with it especially when making the object smaller because the text can disappear and it looks very confusing.
Do you have any suggestions on how to make my solution work as intented? Thanks in advance!
The resizer was forked from this project.
I'm using Qt 5.15.
#include <QBrush> #include <qmath.h> #include <QPainter> #include "sizegripitem.h" #include "ltext.h" SizeGripItem::HandleItem::HandleItem(int positionFlags, SizeGripItem* parent) : QGraphicsRectItem(-4, -4, 8, 8, parent), positionFlags_(positionFlags), parent_(parent) { setBrush(QBrush(Qt::lightGray)); setFlag(ItemIsMovable); setFlag(ItemSendsGeometryChanges); } int SizeGripItem::HandleItem::positionFlags() const { return positionFlags_; } QVariant SizeGripItem::HandleItem::itemChange(GraphicsItemChange change, const QVariant &value) { QVariant retVal = value; if (change == ItemPositionChange) { retVal = restrictPosition(value.toPointF()); } else if (change == ItemPositionHasChanged) { QPointF pos = value.toPointF(); switch (positionFlags_) { case TopLeft: parent_->setTopLeft(pos); break; case Top: parent_->setTop(pos.y()); break; case TopRight: parent_->setTopRight(pos); break; case Right: parent_->setRight(pos.x()); break; case BottomRight: parent_->setBottomRight(pos); break; case Bottom: parent_->setBottom(pos.y()); break; case BottomLeft: parent_->setBottomLeft(pos); break; case Left: parent_->setLeft(pos.x()); break; } } return retVal; } void SizeGripItem::HandleItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { parent_->parentItem()->setFlag(ItemIsMovable, false); QGraphicsRectItem::mousePressEvent(mouseEvent); } void SizeGripItem::HandleItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { parent_->parentItem()->setFlag(ItemIsMovable, true); QGraphicsRectItem::mouseReleaseEvent(mouseEvent); parent_->mouseReleased(); } QPointF SizeGripItem::HandleItem::restrictPosition(const QPointF& newPos) { QPointF retVal = pos(); if (positionFlags_ & Top || positionFlags_ & Bottom) retVal.setY(newPos.y()); if (positionFlags_ & Left || positionFlags_ & Right) retVal.setX(newPos.x()); if (positionFlags_ & Top && retVal.y() > parent_->rect_.bottom()) retVal.setY(parent_->rect_.bottom()); else if (positionFlags_ & Bottom && retVal.y() < parent_->rect_.top()) retVal.setY(parent_->rect_.top()); if (positionFlags_ & Left && retVal.x() > parent_->rect_.right()) retVal.setX(parent_->rect_.right()); else if (positionFlags_ & Right && retVal.x() < parent_->rect_.left()) retVal.setX(parent_->rect_.left()); return retVal; } SizeGripItem::SizeGripItem(Resizer *resizer, QGraphicsItem* pItem, QObject* parent) : QObject(parent), resizer_(resizer) { setParentItem(pItem); if (parentItem()) { rect_ = parentItem()->boundingRect(); if (parentItem()->type() == LText::Type) { LText *text = qgraphicsitem_cast<LText*>(parentItem()); connect(text, <ext::signalRectChanged, this, &SizeGripItem::slotUpdateRectSize); } } handleItems_.append(new HandleItem(TopLeft, this)); handleItems_.append(new HandleItem(Top, this)); handleItems_.append(new HandleItem(TopRight, this)); handleItems_.append(new HandleItem(Right, this)); handleItems_.append(new HandleItem(BottomRight, this)); handleItems_.append(new HandleItem(Bottom, this)); handleItems_.append(new HandleItem(BottomLeft, this)); handleItems_.append(new HandleItem(Left, this)); updateHandleItemPositions(); } SizeGripItem::~SizeGripItem() { } QRectF SizeGripItem::boundingRect() const { return rect_; } void SizeGripItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(painter); Q_UNUSED(option); Q_UNUSED(widget); } #define IMPL_SET_FN(TYPE, POS) \ void SizeGripItem::set ## POS (TYPE v) \ { \ rect_.set ## POS (v); \ doResize(); \ } IMPL_SET_FN(qreal, Top) IMPL_SET_FN(qreal, Right) IMPL_SET_FN(qreal, Bottom) IMPL_SET_FN(qreal, Left) IMPL_SET_FN(const QPointF&, TopLeft) IMPL_SET_FN(const QPointF&, TopRight) IMPL_SET_FN(const QPointF&, BottomRight) IMPL_SET_FN(const QPointF&, BottomLeft) void SizeGripItem::doResize() { if (resizer_) { (*resizer_)(parentItem(), rect_); updateHandleItemPositions(); } } void SizeGripItem::updateHandleItemPositions() { foreach (HandleItem* item, handleItems_) { item->setFlag(ItemSendsGeometryChanges, false); switch (item->positionFlags()) { case TopLeft: item->setPos(rect_.topLeft()); break; case Top: item->setPos(rect_.left() + rect_.width() / 2 - 1, rect_.top()); break; case TopRight: item->setPos(rect_.topRight()); break; case Right: item->setPos(rect_.right(), rect_.top() + rect_.height() / 2 - 1); break; case BottomRight: item->setPos(rect_.bottomRight()); break; case Bottom: item->setPos(rect_.left() + rect_.width() / 2 - 1, rect_.bottom()); break; case BottomLeft: item->setPos(rect_.bottomLeft()); break; case Left: item->setPos(rect_.left(), rect_.top() + rect_.height() / 2 - 1); break; } item->setFlag(ItemSendsGeometryChanges, true); } } void SizeGripItem::slotUpdateRectSize() { prepareGeometryChange(); rect_ = parentItem()->boundingRect(); update(rect_); updateHandleItemPositions(); } void SizeGripItem::mouseReleased() { if (parentItem()->type() == LText::Type) { LText *text = qgraphicsitem_cast<LText*>(parentItem()); text->resizeFinished(); } }
class TextResizer : public SizeGripItem::Resizer { public: virtual void operator()(QGraphicsItem* item, const QRectF rect) { LText* textItem = qgraphicsitem_cast<LText*>(item); if (textItem) textItem->updateRect(rect); } };
#ifndef SIZEGRIPITEM_H #define SIZEGRIPITEM_H #include <QGraphicsItem> #include <QGraphicsRectItem> #include <QScopedPointer> #include <QObject> class SizeGripItem : public QObject, public QGraphicsItem { Q_OBJECT Q_INTERFACES(QGraphicsItem) private: enum { Top = 0x1, Bottom = 0x2, Left = 0x4, TopLeft = Top | Left, BottomLeft = Bottom | Left, Right = 0x8, TopRight = Top | Right, BottomRight = Bottom | Right }; class HandleItem : public QGraphicsRectItem { public: HandleItem(int positionFlags, SizeGripItem* parent); int positionFlags() const; protected: virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value) override; virtual void mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) override; private: QPointF restrictPosition(const QPointF& newPos); int positionFlags_; SizeGripItem* parent_; }; public: class Resizer { public: virtual void operator()(QGraphicsItem* item, const QRectF rect) = 0; virtual ~Resizer() {} }; SizeGripItem(Resizer* resizer = 0, QGraphicsItem* pItem = nullptr, QObject* parent = nullptr); virtual ~SizeGripItem(); virtual QRectF boundingRect() const; virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); void setTopLeft(const QPointF& pos); void setTop(qreal y); void setTopRight(const QPointF& pos); void setRight(qreal x); void setBottomRight(const QPointF& pos); void setBottom(qreal y); void setBottomLeft(const QPointF& pos); void setLeft(qreal x); void mouseReleased(); public slots: void slotUpdateRectSize(); private: void doResize(); void updateHandleItemPositions(); QList<HandleItem*> handleItems_; QRectF rect_; QScopedPointer<Resizer> resizer_; }; #endif // SIZEGRIPITEM_H
#ifndef LTEXT_H #define LTEXT_H #include <QGraphicsTextItem> #include <QTextDocument> class LText : public QGraphicsTextItem { Q_OBJECT public: enum { Type = UserType + QGraphicsTextItem::Type }; LText(QGraphicsTextItem *parent = nullptr); ~LText(); int type() const override { return Type; } QRectF boundingRect() const override; void setNeededFlags(); void updateRect(const QRectF rect); void resizeFinished(); signals: void signalRectChanged(); private: QRectF m_rect; QRectF m_oldRect; bool m_resizeFinished; }; #endif // LTEXT_H
#include <QApplication> #include <QRectF> #include <QAbstractTextDocumentLayout> #include <QPainter> #include <qmath.h> #include "ltext.h" LText::LText(QGraphicsTextItem *parent) : QGraphicsTextItem(parent) { setNeededFlags(); QString *text = new QString("Text"); setPlainText(*text); setFont(QFont("Arial", 42)); m_rect = QRectF(); m_oldRect = QRectF(); m_resizeFinished = false; } LText::~LText() { } QRectF LText::boundingRect() const { if (QRectF(0, 0, 0, 0) == m_rect) return QGraphicsTextItem::boundingRect(); return m_rect; } void LText::updateRect(const QRectF rect) { prepareGeometryChange(); if (m_resizeFinished) { setPos(mapToScene(rect.topLeft())); m_rect.moveTo(0, 0); m_rect.setSize(rect.size()); m_resizeFinished = false; } else m_rect = rect; emit signalRectChanged(); update(m_rect); } void LText::setNeededFlags() { setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsFocusable); } void LText::resizeFinished() { m_resizeFinished = true; updateRect(m_rect); }