Qt: measure tooltip height, or anchor tooltip from bottom - qt

I have the following code to place a tooltip at text-cursor position in a QLineEdit (i.e. the bottom left corner of the tooltip stays just above the text cursor):
setToolTip(tip);
QPoint cur = mapToGlobal(cursorRect().topLeft());
QHelpEvent *event = new QHelpEvent(QEvent::ToolTip,
QPoint(pos().x(), pos().y()),
QPoint(cur.x(), cur.y() - 2 * height() - 2));
QApplication::postEvent(this, event);
I roughly estimated tooltip height as QLineEdit::height(), but that's wrong, and becomes terribly wrong when the tooltip wraps on multiple lines, because it would cover the line edit.
Is there a way to measure a tooltip text height?
Or a way to place a tooltip by specifying the bottom-left or bottom-center as anchor point?

It seems using QFontMetrics with QToolTip::font() works fine.
Here's my solution:
setToolTip(tip);
QFontMetrics fm(QToolTip::font());
QRect r = fm.boundingRect(QRect(0, 0, 500, 50), 0, tip);
QPoint cur = mapToGlobal(cursorRect().topLeft());
QHelpEvent *event = new QHelpEvent(QEvent::ToolTip,
QPoint(pos().x(), pos().y()),
QPoint(cur.x(), cur.y() - height() - r.height() - 4));
QApplication::postEvent(this, event);

Related

How to align to the a rectangle to the left of a QGraphicsView without scaling the scene?

Here is what I want to do.
I need to draw in a QGraphicsView a series of rectangles that are aligned left and right. By this I mean that if rectangle i has posion (0,y) rectangle i+1 needs to have position (0,max) where max is such that the right side of the rectangle "touches" the right side of the QGraphicsView.
When the window is resized I need to recalculate the value of max such that the rectangle is always touching the right side of the screen.
Here is how I add my scene (this references a class that inherits QGraphicsView)
scene = new QGraphicsScene(this);
this->setScene(scene);
this->setAlignment(Qt::AlignTop|Qt::AlignLeft);
To add a rectangle that touches the left border I add it a (0,yvalue,width,height).
How do I calculate the value of X so that that the rectangle will touch the right border?
Ok. So this should go in the resize event of the QGraphicsView. But this does what I wanted:
void AView::resized(){
scene->clear();
QSize newsize = this->size();
qreal w = newsize.width()*.99;
qreal h = newsize.height()*.99;
this->setSceneRect(0,0,w,h);
scene->setSceneRect(0,0,w,h);
qreal rectwidth = 100;
qreal newx = w - rectwidth;
left = new QGraphicsRectItem(0,0,rectwidth,100);
left->setBrush(QBrush(Qt::red));
right = new QGraphicsRectItem(newx,100,rectwidth,100);
right->setBrush(QColor(Qt::blue));
scene->addItem(left);
scene->addItem(right);
}
In this way the blue rectangle is pretty much always at the border. Aslo there is no stretching of the image. There is a gap between the right borders which increases due to the window resizing, but it seems that the size returned by this->size() is slightly larger than the "white area" that you see on screen. Adding the .99 gives a much better results from my experiments.
This little example should calculate the shift and move all items from a selection or list of items, based on alignment you desire (I showed right, but let, center, top, bottom would be the same).
QRectF refRect = scene()->sceneRect();
QList<QGraphicsItem*> sel = allItemsYouWantAligned; // scene()->selectedItems(); for example
foreach(QGraphicsItem* selItem, sel)
{
qreal dx = 0, dy = 0;
QRectF itemRect = selItem->mapToScene(selItem->boundingRect()).boundingRect();
if(align_right)
dx = refRect.right() - itemRect.right();
else
.. calculate either dx or dy on how you want to align
selItem->moveBy(dx, dy);
}
Re-reading the question - I see you don't really need to move rectangle, but create new larger and larger rectangles.
Your solution is simple enough. If you want to resize instead of move -
you would have to setRect() on your items by dx increment:
QRectF r = selItem->rect();
r.setWidth(r.width + dx);
selItem->setRect(r);

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.

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));
}
}

manual new line in QGraphicsText Item

i created a editable text class inheriting QGraphicsTextitem and with setTextIntraction flag im allowing the user to enter the text in the QGraphicsTextItem.
now i want to add a new line manually when the graphicsTextitem width exceeds the viewport width . but i cant find the relevant way to manually add a new line when the textWidth exceeds the width of the viewport.
i managed to shift the text left and up as
QGraphicsView *view = scene()->views()[0];
QRect viewport_rect(0, 0, view->viewport()->width(), view->viewport()->height());
QRectF visible_scene_rect = view->mapToScene(viewport_rect).boundingRect();
qreal sceneRightEnd = visible_scene_rect.right();
qreal sceneBottom = visible_scene_rect.bottom();
QPointF textItemRight = mapToScene( now.bottomRight() ) ;
if(textItemRight.x() >= sceneRightEnd)
{
this->moveBy(sceneRightEnd - textItemRight.x(), 0);
}
if(textItemRight.y() >= sceneBottom )
{
this->moveBy( 0, ( sceneBottom - textItemRight.y() ));
}
You can set the width of your text item by calling:
QGraphicsTextItem::setTextWidth(qreal width)
The default width is -1 which causes the text not to be broken into multiple lines.
If you set a width and the actual text is wider than the specified width then it will be broken into multiple lines.

QT paintEvent for a QWidget

I've a class that inherits QPushButton widget. I want to have custom look of that button so i've overrided paintEvent method. All buttons that I want to paint are childs of QFrame object.
And there I have a problem. I can't repaint those objects.
My paintEvent function:
void Machine::paintEvent(QPaintEvent*) {
QPainter painter(this);
QRect geo = this->geometry();
int x, y, width, height;
x = geo.x()-10;
y = geo.y()-10;
width = geo.width()-3;
height = geo.height()-5;
painter.fillRect(x, y, width, height, QColor(220,220,220));
painter.drawText(x+10, y+10, "Machine " + QString::number(id));
}
When a widget is in top left corner of QFrame, desired effect is ok. But when I move button somewhere else, widget starts to disappear. On images you can see whats going on:
Button is just moved some px down-left. Why it works like this? QFrame which is a container for that button is big enough.
Thanks in advance ;)
The reason is in coordinate system: geometry method returns position relatively to parent, but QPainter::drawRect accepts rectangle in local coordinates. Try this code:
void Machine::paintEvent(QPaintEvent*) {
QPainter painter(this);
int width = size().width() - 3;
int height = size().height() - 5;
painter.fillRect(0, 0, width, height, QColor(220,220,220));
painter.drawText(10, 10, "Machine " + QString::number(id));
}

Resources