Why the QFontMetrics::boundingRect() return a wrong size rect? - qt

I'm using Qt4.7.
When I use QFontMetrics to render my text in some situation, I got a wrong width. My code is like this:
QFontMetrics fm(QApplication::font());
QRect rc = fm.boundingRect(str);
I found that fm.boundingRect(str) always return a fixed rect while the dpi changed.

Related

How to set fixed Icon size that scales with HiDPI in Qt

How can I set Icon size in my application so that it's still scaled when the users uses Screen Scaling?
In my application I have a QToolBar in the MainWindow that seems to use an Icon size of 24x24. I have some QToolButton which seem to get an Icon size of 20x20 by default, so I had to manually set it to 24x24 in order to have all Icons with same size, with setIconSize(QSize(24, 24));. Works fine without scaling:
When the Desktop has some scaling enabled, the Icons with the Fixed size don't get scaled, this looks then like this:
Another use case that I have is showing Icons in QLabels, there I have to specify the size when converting QIcon to QPixmap, this also doesn't scale mImageLabel->setPixmap(icon().pixmap(QSize(24, 24)));
Is there any better why then multiplying with the scale factor? How to get the scale factor?
It looks like setting the fixed size prevents any scaling from Qt side so we need to manually adjust the size. In my case it seems that I can get the correct scale factor by dividing the logicalDpi by 96, which is the DPI with scaling factor 1. I'm not sure if this is the best solution for all uses cases (haven't tested against MacOS for example) but fixes my use case.
I wrote a simple class that scales all my fixed size to the correct value:
#include <QApplication>
#include <QDesktopWidget>
QSize ScaledSizeProvider::getScaledSize(const QSize &size)
{
return {static_cast<int>(size.width() * getXScaleFactor()), static_cast<int>(size.height() * getYScaleFactor())};
}
qreal ScaledSizeProvider::getXScaleFactor()
{
auto desktopWidget = QApplication::desktop();
return desktopWidget->logicalDpiX() / getReferenceDpiValue();
}
qreal ScaledSizeProvider::getYScaleFactor()
{
auto desktopWidget = QApplication::desktop();
return desktopWidget->logicalDpiY() / getReferenceDpiValue();
}
qreal ScaledSizeProvider::getReferenceDpiValue()
{
return 96.0;
}
And the simple fetch the correct value with:
ScaledSizeProvider::getScaledSize(QSize(24, 24))

Find the text width in QFont

I have two items in my tree model i have small difference in text alignment.Is this caused by the width of the text but i checked the width of text using QFontMetrics::width() but both text are same.
Text1:111601756
Text2:999999996
As from the image you can see there is a slight alignment problem in the second text.
Here is the sample code i tried :-
QFont font("times",24);
QFontMetrics metrics(font);
qDebug() << "Width 1" << metrics.width(QString::number(111111111));
qDebug() << "Width 2" << metrics.width(QString::number(999999999));
Output:
Width 1 153
Width 2 153
MyDelegate paint function:-
void LiDefaultTreeDelegate::paint(QPainter *painter, const
QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem newOption = option;
if(index.data(Qt::DisplayRole).toString() != NULL)
{
QString text = index.data(Qt::DisplayRole).toString();
QFontMetrics fnMetrics(fn);
newOption.rect = fnMetrics.boundingRect(text);
//Case 1
//newOption.rect.setWidth(fnMetrics.width(text));
//Case 2
//newOption.rect.setWidth(fnMetrics.width('0') * option.rect.width());
}
QStyledItemDelegate::paint(painter, newOption, index);
}
Now the problem is painting happens in the wrong area as from the image you can see the data gets painted on the top of root item. Any clue what I am missing here.
New Output:
Here's a partial answer, and partly speculation:
The widths are correct (for that font). The problem appears to be, that QTreeView does not use that width, it uses the bounding rect's width (this is a guess in my part, not 100% certain). To see the difference in width, try this version of your test code:
QFont font("times",24);
QFontMetrics metrics(font);
qDebug() << "Rect 1" << metrics.boundingRect(QString::number(111111111));
qDebug() << "Rect 2" << metrics.boundingRect(QString::number(999999999));
It should show that first rect is less wide. This is because even though char spacing is same, 1 is actually narrower that 9, so there is more empty space at the left and right of the string. And bounding rect excludes this empty space, it reports the smallest rectangle where everything drawn is shown.
So you need to look into the delegate which draws your model items, the problem is there! If everything else fails, you may have to implement your own delegate to do the drawing right.
Suggested fix for the code now shown in the question:
newOption.rect = fnMetrics.boundingRect(text); // existing line
newOption.rect.setWidth(fnMetrics.width(text)); // add width adjustment
Note that you may also need to adjust the alignment for painting, if it is now centered, since you probably want left-justified text there.
Note, this fix assumes the font has same width for all the number characters (I think this holds for most fonts, since otherwise numbers would be hard to read), and numbers have equally many digits. If not, you might try something like this instead, to get equal width for all items:
newOption.rect.setWidth(fnMetrics.width('0') * desiredColumnWidth); // width adjustment
Here is the answer provided by my friend in other blog.The problem here is the font family for example here the font family is MS Shell Dlg 2 which uses the space available in the text like 1 is more narrower than 9 so it uses that space and thus leads to alignment problem but there are font family that uses fixed width.So the trick here is change the font family that uses fixed width to avoid this problem.
For Example following are some of the families that uses fixed width:-
Times
Courier
Courier new

How can I wrap text in QGraphicsItem?

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.

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.

How can we know the width and height of string?

I want to create a button exactly the same size as the string for this i want the width and height of the string.
To manually get the size of a string, you need to use the QFontMetrics class. This can be manually used like this:
QFont font("times", 24);
QFontMetrics fm(font);
int pixelsWide = fm.width("What's the width of this text?");
int pixelsHigh = fm.height();
If you want to calculate it for the font used in a given widget (which you may not know), then instead of constructing the fontmetrics, get it from the widget:
QFontMetrics fm(button->fontMetrics());
int pixelsWide = fm.width("What's the width of this text?");
int pixelsHigh = fm.height();
Then you can resize the widget to exactly this value.
Use QFontMetrics.
Example: http://www.developer.nokia.com/Community/Wiki/CS001349_-_Calculating_text_width_in_Qt

Resources