Entries Tagged 'C++' ↓

propertypp: Objective-C-like properties for C++ classes

The short version: propertypp on GitHub

The long version:

A co-worker recently told me he had made some member variables in a C++ class public, because a getter and setter for them would just do exactly the same thing as reading and writing those member variables directly. I told him that was bad design, and he told me he was aware of it but that in that particular case making those member variables public would hardly have any negative impact on the system, because it was a small piece of code with a very limited and controlled scope. I told him he should still consider making those member variables private and writing getters and setters, but then he admitted he found the get*() and set*() syntax too cumbersome.

I agree with him on that last point. Calling getters and setters makes code so ugly when you could be using dot notation. Objective-C and other languages have this nice little feature called properties, which allow you to use dot notation for accessing member variables of a class, but having getters and setters getting called behind the scenes.

I began wondering if it would be possible to implement properties in C++ and soon realized it is, indeed, possible. You can declare public member variables of wrapper classes that overload cast and assignment operators and read and write private member variables containing property values. If that explanation wasn’t enough to help you picture the solution, here are some example implementations:

http://www.cplusplus.com/forum/general/8147/

http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4031

Those aren’t the only C++ property implementations available out there, but I found that all implementations required too much code to be used.

I set out to write a generic C++ property library that required as little effort to be used by the programmer as possible. propertypp is the result of that. I wont’t go into detail here on how to use it and what code that uses it looks like, because it’s all there on the README on GitHub.

Notice how I’ve managed to have the name of the class containing the properties to be informed only on getter and setter synthetization. If you read the code on propertypp.h, you’ll see there was no way out of that. Another nice thing is that it doesn’t have any dependencies on third-party libraries: it’s just plain C++ and STL. My initial, unpublished implementation used Boost.Function to wrap the function objects for getters and setters, but I didn’t want to impose that dependency on every project that shall use propertypp.

propertypp is far from finished. At present, it only works with fundamental data types and pointers. That prevents having a struct or object as a property, unless it’s a pointer to that struct or object. I also want to support read-only properties.

I have to admit I’m quite proud of my work on propertypp. So far, the people I’ve shown it to thought it nice. However, criticism will be appreciated if anyone has any 🙂

Drawing scalable pixels in the Qt Graphics View framework

Say you have a custom QGraphicsItem that you would like to draw pixel by pixel, and want it to appear pixelated when that item is scaled (or the QGraphicsView it is being displayed on, or whatever). At first, you might try drawing your pixels with QPainter::drawPoint():

void MyItem::paint(
    QPainter* painter,
    const QStyleOptionGraphicsItem* option,
    QWidget* widget)
{
    ...
    painter->drawPoint(x, y);
    ...
}

If you don’t mess with the width of the QPainter’s QPen, that won’t work. Your pixels will always be the size of 1 display pixel, no matter the item’s or the view’s scaling factor. That happens because the default pen width is 0, which guarantees points, outlines, etc. are drawn 1 pixel thick, no matter the scale. Notice the single pixel drawn on the following image, which shows a scaled 16×16 QGraphicsItem:

QPainter::drawPoint will always draw 1-pixel points no matter the scale, if the pen width is set to 0.

But guess what? Setting the pen width to 1 won’t do it either, at least not without a little extra effort. Things will start to look weird. A look into QGraphicsItem’s documentation reveals that QPainter will draw things half outside the pen’s outline, and half inside.

There are two options here:

  1. Set the pen width to 1 and compensate for the way QPainter draws things with its QPen; or
  2. Find another way to draw single pixel-sized… pixels.

The first option can be achieved like this:

QPen pen;
pen.setWidth(1);
painter->setPen(pen);
painter->drawPoint(QPointF(x + 0.5,y + 0.5));

But the results aren’t the best. Take a look at the following screenshot. It shows a QGraphicsView with its background QBrush set to black. There is a single 16×16 QGraphicsItem that draws a 16×16 white rectangle and a single red point at it’s bottom right corner. Notice that the red point is off the rectangle’s right border by one pixel. I’m not sure about this, but I think it might be due to floating-point error.

Drawing single pixels in QGraphicsItem::paint with a pen width of 1 does not yield the best possible results.

It seems that the most appropriate way to achieve the desired result is to draw a filled 1×1 rectangle, as in:

painter->fillRect(x, y, 1, 1, Qt::red);

Notice that you should leave/set the pen width to 0. As you can see below, there is no error like in the previous approach:

A better way to draw single pixels in QGraphicsItem::paint is by drawing 1x1 filled rectangles width the pen width set to 0.

In conclusion, you should draw single pixels by drawing filled 1×1 rectangles with a pen width of 0, instead of drawings points with a pen width of 1.

Aligning text in QGraphicsTextItem

The Qt Graphics View Framework provides a rich set of resources to create applications with interactive graphics scenes that display arbitrary shapes, text and even Qt widgets. While the framework provides almost everything you need regarding interactivity, scene hierarchy management and graphics display, allowing you to concentrate on your application-specific code, some things require doing some extra work.

One of those things is setting the text alignment on QGraphicsTextItem items. Even though the class doesn’t provide a setAlignment() method, you have access to the underlying QTextDocument and QTextCursor objects that manage all the text processing and layout for the item. However, simply setting the text alignment on those objects also doesn’t get the job done.

The text in a QGraphicsTextItem is only displayed with any set alignment if the text width for the item is set. The text width determines the maximum width the text displayed by the item might have. If the text is actually larger than that width, automatic line breaks are inserted so that the text doesn’t extend past it. The text width property is also used by the framework to calculate the text position when the alignment is set to something other than left.

So how can the text width of a QGraphicsTextItem be set so that no automatic line breaks are inserted by the framework, and so that the text alignment can be set and be made to work? The answer is quite simple: just set it to the width of the item’s bounding rectangle:

item->setTextWidth(item->boundingRect().width());

Now, to align the text to the right, for example, you can use the item’s QTextCursor to merge it with a QTextBlockFormat with the alignment set to Qt::AlignRight:

QTextBlockFormat format;
format.setAlignment(Qt::AlignRight);
QTextCursor cursor = item->textCursor();
cursor.select(QTextCursor::Document);
cursor.mergeBlockFormat(format);
cursor.clearSelection();
item->setTextCursor(cursor);

Notice that before setting the alignment, the entire text under the cursor i.e. the text in the QGraphicsTextItem object must be selected, and aftwerwards the selection should be cleared so that the text is not displayed as selected. Failing to select all the text might result in having only part of the text aligned in the desired way.

Here’s a screenshot of a QGraphicsView displaying a scene with a right-aligned QGraphicsTextItem:

Right-aligned QGraphicsTextItem

So that was easy. Now let’s improve this.

The preceding approach is limited if you are not setting the QGraphicsTextItem’s text and alignment only once in your application. It requires you to set the text width and the text alignment every time the text content changes.

In the remainder of this post I will demonstrate how to create a subclass of QGraphicsTextItem with a setAlignment() method for setting the text alignment and that automatically updates the text width of the item whenever a change in its contents occurs. Also, this subclass will have the interesting characteristic of growing or shriking to the left when the text is right-aligned, thus anchoring it to its top-right corner, which might make sense in some applications.

Let’s start out by the class definition. We must use the Q_OBJECT macro since QGraphicsTextItem is a subclass of QObject (QGraphicsTextItem -> QGraphicsObject -> QObject) and we’re going to define a slot on the new class.

#include <QGraphicsTextItem>
 
class TextItem : public QGraphicsTextItem
{
    Q_OBJECT
 
public:
    enum { Type = UserType + 1 };
 
    TextItem(QGraphicsItem* parent = 0);
    TextItem(const QString& text, QGraphicsItem* parent = 0);
    void setAlignment(Qt::Alignment alignment);
    virtual int type() const;
 
public slots:
    void updateGeometry(int, int, int);
    void updateGeometry();
 
private:
    void init();
};

The constructors are quite simple: they just initialize the base object and the alignment_ attribute and invoke the private method init() to do the rest of the initialization:

TextItem::TextItem(QGraphicsItem* parent)
    : QGraphicsTextItem(parent),
      alignment_(Qt::AlignLeft)
{
    init();
}
 
TextItem::TextItem(const QString& text, QGraphicsItem* parent)
    : QGraphicsTextItem(text, parent),
      alignment_(Qt::AlignLeft)
{
    init();
}

The init() method is responsible for properly initializing the object. It calls updateGeometry() (which will soon be explained) to set the initial item’s text width. It also connects the QTextDocument::contentsChange() signal to the updateGeometry() slot so that the text width is recomputed after any change to the text, thus preventing automatic line breaks:

void TextItem::init()
{
    updateGeometry();
    connect(document(), SIGNAL(contentsChange(int, int, int)),
            this, SLOT(updateGeometry(int, int, int)));
}

Note that the updateGeometry() slot has two different signatures: one which receives no arguments and one which receives the three int arguments from the QTextDocument::contentsChange() signal. As will be shown later, the one with three int arguments just calls the one that doesn’t take any arguments. QTextDocument has another signal called contentsChanged() that carries no arguments, but unfortunatelt is cannot be used here. This will be explained after the code to updateGeometry() is presented.

The setAlignment() method contains the code for setting the text alignment that was shown at the beginning of this post. It also stores the last set alignment in the alignment_ attribute so that the object “remembers” its last alignment:

void TextItem::setAlignment(Qt::Alignment alignment)
{
    alignment_ = alignment;
    QTextBlockFormat format;
    format.setAlignment(alignment);
    QTextCursor cursor = textCursor();
    cursor.select(QTextCursor::Document);
    cursor.mergeBlockFormat(format);
    cursor.clearSelection();
    setTextCursor(cursor);
}

The updateGeometry() method is responsible for setting the item’s text width to a size that removes any automatic line breaks that might have been inserted after a change to the item’s contents:

void TextItem::updateGeometry(int, int, int)
{
    updateGeometry();
}
 
void TextItem::updateGeometry()
{
    setTextWidth(-1);
    setTextWidth(boundingRect().width());
    setAlignment(alignment_);
}

Note that the text width is first set to -1, then to the item’s bounding rectangle’s width. Setting the item’s text width to -1 “resets” the text width, changing the item’s size so that the text is displayed without any automatic line breaks that might have been inserted to the limit imposed by the previous text width. This effectively recomputes the item’s bounding rectangle, allowing its new width to be used again to set the item’s text width.

Also note that setAlignment() is called after the new text width is set. When the text width is set to -1, any alignment that might have been set before is lost, because a text width value of -1 indicates the item has no text width value. As explained before, the text width value is required by the framework to align the text to any position other than left. Since the alignment was lost, setAlignment() is called to restore the item’s last set alignment.

Remember I said that the QTextDocument::contentsChanged() signal could not be used to directly connect it to updateGeometry()? The problem is that QTextDocument::contentsChanged() is not emitted exactly like contentsChange(). The contentsChanged() signal is also emitted when the text alignment is changed, so that would start an infinite recursion (and eventually a stack overflow would happen) in updateGeometry() because of the call to setAlignment(). It is not actually necessary to have two different signatures for updateGeometry(), but since the int arguments from the QTextDocument::contentsChange() signal are ignored, having an updateGeometry() signature which takes no arguments provides a cleaner interface for TextItem.

The Type constant and the type() method are required by the framework if item type information is necessary. That is not really relevant in the present discussion, but here’s the code for type():

int TextItem::type() const
{
    return Type;
}

Obviously, Type might be set to any other value allowed by Qt.

What about making the item grow or shrink to the left when the text is right-aligned? This requires a slight modification to updateGeometry(). The trick is to save the item’s top right position before recomputing it’s text width and then displacing its position by the difference between the new top right position and the saved one:

void TextItem::updateGeometry()
{
    QPointF topRightPrev = boundingRect().topRight();
    setTextWidth(-1);
    setTextWidth(boundingRect().width());
    setAlignment(alignment_);
    QPointF topRight = boundingRect().topRight();
 
    if (alignment_ & Qt::AlignRight)
    {
        setPos(pos() + (topRightPrev - topRight));
    }
}

Another change is necessary for TextItem to work properly. If it is editable i.e. its text interaction flags are set to something like Qt::TextEditorInteraction, the cursor position must be saved and restored whenever the alignment is set:

void TextItem::setAlignment(Qt::Alignment alignment)
{
    alignment_ = alignment;
    QTextBlockFormat format;
    format.setAlignment(alignment);
    QTextCursor cursor = textCursor();      // save cursor position
    int position = textCursor().position();
    cursor.select(QTextCursor::Document);
    cursor.mergeBlockFormat(format);
    cursor.clearSelection();
    cursor.setPosition(position);           // restore cursor position
    setTextCursor(cursor);
}

This is necessary because the cursor position is moved to the end of the text when the cursor selects all the text in the item. Failing to save and restore the cursor position completely breaks the interaction when the item is editable, since setAlignment() is called on every key stroke (because QTextDocument::contentsChange() is emitted due to the change in content caused by the keystroke).

Unfortunately, I don’t know if there is a way to detect a font change on a QGraphicsTextItem. So TextItem works neatly when the text itself is changed, but if the font is changed to a larger size, automatic line breaks will be inserted by the framework because updateGeometry() will not get called in that case. Of course, you can work around that by calling updateGeometry() manually on a TextItem whenever its font is changed by the application. Another strategy would be to override setFont(), but then polymorphism will not work because setFont() is not virtual. Also, a different method like changeFont() could be added to TextItem that calls setFont() and then updateGeometry(), but that defines an interface different from QGraphicsTextItem. If you know how to make the item update itself when a font change occurs, please leave a comment!