How can I wrap text in QGraphicsItem? - qt

1) How can I wrap text in a QGraphicsTextItem to fit a fixed rectangle, with width and height ?
Right now I am experimenting with creating a text, getting its bounding rectangle, and resizing it to fit the box - but I can't get wrapping.
class TTT: public QGraphicsTextItem {
TTT() {
{
setPlainText("abcd");
qreal x = m_itemSize.width()/boundingRect().width();
qreal y = m_itemSize.height()/boundingRect().height();
scale(x, y);
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
// experiment with clip regions
// text gets covered by hole in clip
QRegion r0(boundingRect().toRect());
QRegion r1(QRect(5, 5, 10, 10), QRegion::Ellipse);
QRegion r2 = r0.subtracted(r1);
painter->setClipRegion(r2);
painter->setBrush(Qt::yellow);
painter->drawRect(boundingRect());
QGraphicsTextItem::paint(painter, option, widget);
}
}
What makes wrapping happen, how can I trigger it ?
Right now, as I keep typing, the box is automatically expanding.
2) Is it possible to wrap the text in a QGraphicsItem / QGraphicTextItem subclass in a shape that is not a rectangle ?
(Something like in the image above)
I tried to use clipRegion, see code above, but I guess it is not the right way to go, clipping cuts the text but did not wrap.
Maybe it would... If I could figure out how to wrap text in the first place ?
Qt 4.8

You did not specify Qt version but try:
void QGraphicsTextItem::setTextWidth(qreal width)
Sets the preferred width for the item's text. If the actual text is wider than >the specified width then it will be broken into multiple lines.
If width is set to -1 then the text will not be broken into multiple lines >unless it is enforced through an explicit line break or a new paragraph.
The default value is -1.

In answer to 1) I'd opt not to use the QGraphicsTextItem, but draw the text directly in your QGraphicsItem's paint function using the drawText overloaded function, which takes a QTextOption parameter.
Using this, you can set the WrapMode, for example, with a call to
QTextOption::setWrapMode(QTextOption:: WordWrap)
As for 2) with a non-rectangular shape, I don't think Qt will do this for you.
Doing it yourself you can use QFontMetrics, to work out just how much text would fit in each line, depending upon where it lies within its bounding item.
Alternatively, you could adapt the concept of a text-to-path method.

Related

Progress bar adding a threshold line

I've progress bar to display the instantaneous value of real-time sensor.
the range for the QProgressBar is set as
ui->progressBar->setRange(0, 0.5*100); // to make range integer
ui->progressBar->setValue(sensor_read*100);
It is working ok. But I need to set a "threshold" value using a doubleSpinBox , as shown below:
The position of the dotted line (which is a QLabel) is the threshold that can be set using a doubleSpinBox.
My requirement is to change the height of the dotted line with respect to the threshold value.
the top and bottom y coordinates of the QProgressBar is 250 and 450
How do I get proportional y coordinate for the QLabel (dotted line) when I set a threshold value using doubleSpinBox?
Using a QLabel is possible, but it might be hard to get the exact position of the line inside the QLabel itself to adjust correctly. If your QLabel is only 1px high, this should work fine, otherwise you would need to adjust a bit more in detail.
So if the QLabel has the QProgressBar set as parent, then you can set the y coordinate of the QLabel inside the QProgressBar accordingly with something like below.
Call this function every time the threshold is modified, or when the widget (i.e. the progress bar) is resized. This is done by connecting on the signal:
connect(spinBox, &QDoubleSpinBox::valueChanged, this, &MyClass::updateThreshold);
void MyClass::updateThreshold()
{
const double threshold = spinBox->value();
int labelY = qRound(threshold / progressBar->maximum() * progressBar->height());
label->move(0, progressBar->height() - labelY); // y is inverted
}
void MyClass::resizeEvent(QResizeEvent *event)
{
updateThreshold();
}
Another way is to create a new class that inherits QProgressBar and to override the paintEvent() function. Inside the paintEvent, you could draw a horizontal line and the text using the QPainter at the correct position, using the same calculation than above. There you would not need a QLabel.

Set QGraphicsTextItem text contents of exact height and width

I am required to create text items with exact width and height of text contents.
The height of the text is the most important requirement.
The position of the text should be relative to the text itself.
I also have to be able to place it on canvas in an exact spot.
Assuming a (printable) canvas (on a larger QGraphicsScene), say 5 inch width and 1 inch height, my text should be able to stretch top-bottom-left-right - and be placed on canvas, not part in part out.
I am sub-classing QGraphicsTextItem for my item type. I am resizing it, using QTransform(), to required size - in inches or mm or pixels (72*in).
Also setting the document() margin to 0, and anything inside (like QTextBlockFormat margins) also to 0.
I have implemented a setItemSize(QSizeF sz) (with sz in pixels), that resizes the QGraphicsTextItem as required.
The sz is initialized using the item bounding rect.
Assuming no wrap, single line text (multi-line could be solved separately once this issue is resolved).
When adding the item to canvas, I still see a top and bottom margin - and this varies based on font choice.
I drew a rectangle around the item to see it.
The top/bottom distances depend on font choices.
I have tried to use font metrics to determine these distances (in paint() I have been drawing lines to try to determine the position and rectangle in which the text fits).
I would be happy to at least be able to determine correct size to use for upper case, no accents or special characters fonts (it would be a start, though naturally I would need to be able to use any characters).
But at least some way to determine the size and position (relative to the (0,0) of item) of the text content even in the simplest case.....
The font metrics tightBoundingRect() seems the most accurate for size, but it seems impossible to determine its position so that I can somehow create my items correctly, and maybe resize/shift them correctly to fit on canvas.
Here are some examples of my struggle to determine at least exact size and position of text, relative to the (0,0) of the item (assuming that once I do that, I am able to expose that info to outside or include the shift in the item transform on resize).
Notice that the size of the text advertised by font metrics does not always cover the text, and for different fonts I am not able to position the tight bounding rect (magenta) around the text itself. (I did multiple guesses, the code below is just one - the lines are trying to show different font metrics sizes).
The above were experiments in paint function of the text item inheriting QGraphicsTextItem:
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
// draw text
QGraphicsTextItem::paint(painter, option, widget);
QPen p;
p.setWidthF(0);
QFontMetricsF fm(this->font());
qreal ascent = fm.ascent(),
descent = fm.descent(),
hheight = fm.height();
QRectF r = QGraphicsTextItem::boundingRect();
QRectF rFont= fm.tightBoundingRect(toPlainText());
qreal xmax = r.right();
painter->save();
painter->setBrush(Qt::NoBrush);
// where is "ascent + descent"
p.setColor(Qt::green);
painter->setPen(p);
painter->drawLine(QPointF(2, ascent), QPointF(2, ascent + descent));
painter->drawLine(QPointF(2, ascent + descent), QPointF(xmax/2, ascent + descent));
// where is "height"
p.setColor(Qt::red);
painter->setPen(p);
painter->drawLine(QPointF(xmax/2, 0), QPointF(xmax/2, hheight));
painter->drawLine(QPointF(xmax/2, ascent + descent), QPointF(xmax, ascent + descent));
// where is "ascent"
p.setColor(Qt::yellow);
painter->setPen(p);
painter->drawLine(QPointF(6, 0), QPointF(6, ascent));
painter->drawLine(QPointF(6, ascent), QPointF(xmax, ascent));
// something that may look like top of the text
p.setColor(Qt::blue);
painter->setPen(p);
qreal yyy = ascent + rFont.y() + 1;
painter->drawLine(QPointF(5, yyy), QPointF(xmax, yyy));
// this should be useful... should be the natural offset
qreal yoffset = (r.height() - rFont.height()) / 2;
// qDebug() << yoffset << r << rFont;
//qreal y0 = (r.height() - fm.height())/2;
p.setColor(Qt::darkGreen);
painter->drawEllipse(10, yoffset, 1, 1);
// where is the font rect
p.setColor(Qt::magenta);
painter->setPen(p);
yoffset = (r.height() + rFont.height()) / 2;
painter->translate(0, yoffset);
painter->drawRect(rFont);
painter->restore();
}
I have also tried not using QGraphicsTextItem, but paint text inside a rectangle. The same thing happens.
(Qt 4.7 - 5.x)
This is not a good solution. This is an attempt to solve my own problem - in a first iteration - of setting text with given width and height, using font metrics.
Reasons for not being good -
Even after resize, text is smaller than desired, I don't understand why
The position is incorrect, based on font style the text can be above or below the canvas, meaning it gets clipped.
I resize it using a factor calculated from the item bounding rect size and the font metrics bounding rect (I used the tight bounding rect for more accurate size).
myText->setItemFontSize(12); // If I use font metrics I need to reset text size on every change, because resizing loses font info
QFontMetricsF fm(myText->font());
QRectF fmRect = fm.tightBoundingRect(myText.toPlainText().toUpper());
// without toUpper() the size is too small - even so it is a bit small
// I read tightBoundingRect is slow - but boundingRect and height and ascent all give values that result in even smaller size
//qreal absH = fm.ascent();
qreal absH = fmRect.height();
qreal absW = fmRect.width();
qreal absHeightRatio = myText->getItemSize().height() / absH;
qreal absWidthRatio = myText->getItemSize().width() / absW;
Then setting size:
myText->setItemSize(QSizeF(absWidthRatio * textLength, absHeightRatio * fontHeight));
// This function scales the `QTransform` on item
// but since I request a final position dependent on item size
// (including blank space around it) - it has no chance of being accurate.....
// This is where my next effort will go, figuring out how to get rid of the fluff
// around the item inside the scaling
The function for setting position: trying to center text:
// leftShift = 10, rightShift = 10 in example
myText->setPos(0,0);
QRectF r = myText->mapToScene(myText->boundingRect()).boundingRect();
QSizeF sz = r.size();
qreal w = sz.width();
qreal h = sz.height();
qreal cx = (m_docLength - w + leftShift - rightShift)/2 - r.left();
qreal cy = (m_docHeight - h)/2 - r.top();
myText->setPos(cx, cy);
The images below are for fontHeight = m_docHeight -
Desirable:
- either the entire size of text (ascent + descent) equals doc height, and text is centered vertically based on content
- or the upper case size text equals doc height and the descent is below document (text centered based on only upper case) - this would seem easier based on how QGraphicsTextItem seems to position it
Actual:
- the text is smaller no matter which parameters I use to scale, and centered based on upper case text
As shown above - I have no idea how I could center vertically based on content (so for edge-to-edge text the descent would fit in) - and in lieu of that, all I really want is edge-to-edge uppercase letters, but I can't seem able to achieve that.
Oh and these are for Arial type font - one of the best-behaved. Other fonts jump all over the place, either above or below the canvas. And for some fonts, the resulting text is actually smaller - which is inexplicable to me, because how can the tight bounding rectangle be smaller than the item bounding rectangle...
Still this is as far as I got to getting my text as close to "true" size and placed on a canvas that matches its size.

Center text vertically when drawing with QPainter's drawText()

My strategy when centering text on images is to get bounding rectangle for that text and divide width or height by two. I did the same in this case. This is example I have created:
void CanvasWidget::paintEvent(QPaintEvent*)
{
//Create image:
QImage image(rect().width(), rect().height(), QImage::Format_RGB32);
QPainter paint(&image);
// White background
image.fill(QColor("#FFF"));
// set some metrics, position and the text to draw
QFontMetrics metrics = paint.fontMetrics();
int yposition = 100;
QString text = "Hello world.";
// Draw gray line to easily see if centering worked
paint.setPen(QPen(QColor("#666"), 1, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
paint.drawLine(0, yposition, image.width(), yposition);
// Get rectangle
QRect fontRect = metrics.boundingRect(text);
// Black text
paint.setPen(QPen(QColor("#000"), 1, Qt::SolidLine, Qt::FlatCap, Qt::RoundJoin));
// Add half the height to position (note that Qt has [0,0] coordinates at the bottom of the image
paint.drawText(4, yposition+round(((double)fontRect.height())/2.0), text);
QPainter p(this);
p.drawImage(rect(), image, image.rect());
p.end();
}
This is the result - the text is under line instead centered on the line:
Android:
Windows:
I used lines to draw frame around the text based on metrics rectangle:
Intended result was to center visible text exactly around the given point/line:
To put you in perspective, this is the actual problem I am having:
The numbers should be in the middle of the lines, not so much below.
The function I am using returns size including accents and other big characters that aren't there. How do I get the rectangle in pixels only for characters that are there?
Not quite sure what you're asking, but if it's why does the bounding rect appear wrong, it's because you're not taking into account characters that have accents in the font such as é, å etc. The bounding rect returned from font metrics includes these.
As it states in the boundingRect documentation
The height of the bounding rectangle is at least as large as the value returned by height().
This is not the case for tightBoundingRect that will, I expect, provide the right result.

Shift `QGraphicsTextItem` position relative to the center of the text?

I have a number of classes that inherit from QGraphicsItem, that get to be arranged in a certain way. For simplicity of calculations, I made the scenes, and items, centered in (0, 0) (with the boundingRect() having +/- coordinates).
QGraphicsTextItem subclass defies me, its pos() is relative to top left point.
I have tried a number of things to shift it so it centers in the text center (for example, the suggested solution here - the code referenced actually cuts my text and only shows the bottom left quarter).
I imagined that the solution should be something simple, like
void TextItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
{
painter->translate( -boundingRect().width()/2.0, -boundingRect().height()/2.0 );
QGraphicsTextItem::paint(painter, option, widget );
}
the above "sort of" works - but as I increase the item scale -> increase the font, the displayed item is cut off...
I tried to set the pos() - but the problem is, I still need to track the actual position on the scene, so I cannot just replace it.
A slightly unpleasant side effect - centering the QGraphicsView on the element does not work either.
How can I make my QGraphicsTextItem show its position relative to the center of the text ?
Edit: one of the experiments of changing the boundingRect():
QRectF TextItem::boundingRect() const
{
QRectF rect = QGraphicsTextItem::boundingRect();
rect.translate(QPointF(-rect.width()/2.0, -rect.height()/2.0));
return rect;
}
I had to shift the initial position, as well as the resize, to trigger a new position - I was unable to do it in paint() because, as I thought from the start, any repaint would continuously recalculate the position.
Only the initial position needs to be adjusted - but as the font size (or style...) changes, its bounding rectangle also changes, so the position must be recalculated - based on previous position.
In the constructor,
setPos(- boundingRect().width()/2, - boundingRect().height()/2);
in the function that modifies item (font) size,
void TextItem::setSize(int s)
{
QRectF oldRect = boundingRect();
QFont f;
f.setPointSize(s);
setFont(f);
if(m_scale != s)
{
m_scale = s;
qreal x = pos().x() - boundingRect().width()/2.0 + oldRect.width()/2.0;
qreal y = pos().y() - boundingRect().height()/2.0 + oldRect.height()/2.0;
setPos(QPointF(x, y));
}
}

Preventing font scale in QGraphicsItem

I am using QGraphicsTextItem to paint the text on the scene. Text is painted along the path (QGraphicsPathItem), wich is parent of my QGraphicsTextItem - so the text rotation is changed to be along the path element and is sticked to it while zooming the view. But the font size of QGraphicsTextItem is also changing while zooming the view - this is what I am trying to avoid. Of I set QGraphicsItem::ItemIgnoresTransformations flag to the QGraphicsTextItem it stops rotating while it's parent (QGraphicsPathItem) does.
I do understand that I have to re-implement QGraphicsTextItem::paint function, but I am stuck with the coordination system. Here is the code (Label class inherits public QGraphicsTextItem):
void Label::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget )
{
// Store current position and rotation
QPointF position = pos();
qreal angle = rotation();
// Store current transformation matrix
QTransform transform = painter->worldTransform();
// Reset painter transformation
painter->setTransform( QTransform() );
// Rotate painter to the stored angle
painter->rotate( angle );
// Draw the text
painter->drawText( mapToScene( position ), toPlainText() );
// Restore transformation matrix
painter->setTransform( transform );
}
The position (and rotation) of my text on the screen is unpredictable :(
What am I doing wrong? Thank you very much in advance.
I solved a problem this way - for drawing a line/circle/rectangle/path, which I want to be transformed, I use an appropriate QGraphicsLine/Ellipse/Rect/PathItem. For drawing the text (which I do NOT want to be transformed) I use QGraphicsSimpleTextItem. I set text's flag to ignore transormations and set it's parent to Line/Ellipse/Rect/Path item. The Line/Ellipse/Rect/Path item transforms, but text does not - this is what I wanted. I can also rotate text and set it's position.
Thank you very much for answers.
The following solution worked perfectly for me:
void MyDerivedQGraphicsItem::paint(QPainter *painter, const StyleOptionGraphicsItem *option, QWidget *widget)
{
double scaleValue = scale()/painter->transform().m11();
painter->save();
painter->scale(scaleValue, scaleValue);
painter->drawText(...);
painter->restore();
...
}
We can also multiply the scaleValue by other mesures we want to keep its size constant outside the save/restore environment.
QPointF ref(500, 500);
QPointF vector = scaleValue * QPointF(100, 100);
painter->drawLine(ref+vector, ref-vector);
I had this issue once. Instead of ignoring transformations, you need to scale down the items you don't want to be zoomed in in your zoom-in function.
When you zoom in, if you change the scale by ds for example, scale the items by 1.0 / ds
You might need to change their positions though.
I hope this helps.
Edit: I hope I understood the question right.

Resources