Time for action – Adding text to a custom rectangle

Let's add a number to each of the corner circles:

child->setPos(pos);
QGraphicsSimpleTextItem *text =
    new QGraphicsSimpleTextItem(QString::number(i), child);
text->setBrush(Qt::green);
text->setPos(-text->boundingRect().width() / 2,
             -text->boundingRect().height() / 2);

The QString::number(i) function returns the string representation of number i. The text item is a child of the circle item, so its position is relative to the circle's origin point (in our case, its center). As we saw earlier, the text is displayed to the top-left of the item's origin, so if we want to center the text within the circle, we need to shift it up and right by half of the item's size. Now the text is positioned and rotated along with its parent circle:

However, we don't want the text to be rotated, so we need to enable the ItemIgnoresTransformations flag for the text item:

text->setFlag(QGraphicsItem::ItemIgnoresTransformations);

This flag makes the item ignore any transformations of its parent items or the view. However, the origin of its coordinate system is still defined by the position of pos() in the parent's coordinate system. So, the text item will still follow the circle, but it will no longer be scaled or rotated:

However, now we hit another problem: the text is no longer properly centered in the circle. It will become more apparent if you scale the view again. Why did that happen? With the ItemIgnoresTransformations flag, our text->setPos(...) statement is no longer correct. Indeed, pos() uses coordinates in the parent's coordinate system, but we used the result of boundingRect(), which uses the item's coordinate system. These two coordinate systems were the same before, but with the ItemIgnoresTransformations flag enabled, they are now different.

To elaborate on this problem, let's see what happens with the coordinates (we will consider only x coordinate, since y behaves the same). Let's say that our text item's width is eight pixels, so the pos() we set has x = -4. When no transformations are applied, this pos() results in shifting the text to the left by four pixels. If the ItemIgnoresTransformations flag is disabled and the view is scaled by 2, the text is shifted by eight pixels relative to the circle's center, but the size of the text itself is now 16 pixels, so it's still centered. If the ItemIgnoresTransformations flag is enabled, the text is still shifted to the left by eight pixels relative to the circle's center (because pos() operates in the parent item's coordinate system, and the circle is scaled), but the width of the item is now 8, because it ignores the scale and so it's no longer centered. When the view is rotated, the result is even more incorrect, because setPos() will shift the item in the direction that depends on the rotation. Since the text item itself is not rotated, we always want to shift it up and left.

This problem would go away if the item were already centered around its origin. Unfortunately, QGraphicsSimpleTextItem can't do this. Now, if it were  QGraphicsRectItem, doing this would be easy, but nothing stops us from adding a rectangle that ignores transformations and then adding text inside that rectangle! Let's do this:

QGraphicsSimpleTextItem *text =
        new QGraphicsSimpleTextItem(QString::number(i));
QRectF textRect = text->boundingRect();
textRect.translate(-textRect.center());
QGraphicsRectItem *rectItem = new QGraphicsRectItem(textRect, child);
rectItem->setPen(QPen(Qt::green));
rectItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);
text->setParentItem(rectItem);
text->setPos(textRect.topLeft());
text->setBrush(Qt::green);

In this code, we first create a text item, but don't set its parent. Next, we get the bounding rect of the item that will tell us how much space the text needs. Then, we shift the rect so that its center is at the origin point (0, 0). Now we can create a rect item for this rectangle, set the circle as its parent, and disable transformations for the rect item. Finally, we set the rect item as the parent of the text item and change the position of the text item to place it inside the rectangle.

The rectangle is now properly positioned at the center of the circle, and the text item always follows the rectangle, as children usually do:

Since we didn't originally want the rectangle, we may want to hide it. We can't use rectItem->hide() in this case, because that would also result in hiding its child item (the text). The solution is to disable the painting of the rectangle by calling rectItem->setPen(Qt::NoPen).

An alternative solution to this problem is to translate the text item's coordinate system instead of using setPos(). QGraphicsItem doesn't have a dedicated function for translation, so we will need to use setTransform:

QTransform transform;
transform.translate(-text->boundingRect().width() / 2,
                    -text->boundingRect().height() / 2);
text->setTransform(transform);

Contrary to what you would expect, ItemIgnoresTransformations doesn't cause the item to ignore its own transformations, and this code will position the text correctly without needing an additional rectangle item.