|  | Home · Examples

# Star Delegate Example

Code:

- StarDelegate.java

The Star Delegate example shows how to create a delegate that can paint itself and that supports edition.
When displaying data in a QListView, QTableView, or QTreeView, the individual items are drawn by a delegate. Also, when the user starts editing an item (e.g., by double-clicking the item), the delegate provides an editor widget that is placed on top of the item while editing takes place.

Delegates are subclasses of QAbstractItemDelegate. Qt Jambi provides QItemDelegate, which inherits QAbstractItemDelegate and handles the most common data types (notably `Integer` and String). If we need to support custom data types, or want to customize the rendering or the editing for existing data types, we can subclass QAbstractItemDelegate or QItemDelegate. See Delegate Classes for more information about delegates, and Model/View Programming if you need a high-level introduction to Qt Jambi's model/view architecture (including delegates).

In this example, we will see how to implement a custom delegate to render and edit a "star rating" data type, which can stores values such as "1 out of 5 stars".

The example consists of the following classes:

- `StarRating` is the custom data type. It stores a rating expressed as stars, such as "2 out of 5 stars" or "5 out of 6 stars".
- `StarDelegate` inherits QItemDelegate and provides support for `StarRating` (in addition to the data types already handled by QItemDelegate).
- `StarEditor` inherits QWidget and is used by `StarDelegate` to let the user edit a star rating using the mouse.
- `StarWindow` fills a QTableWidget with some data and installs the delegate on it.

## StarDelegate Class Implementation

The StarDelegate consists of public methods reimplemented from QItemDelegate to provide custom rendering and editing.

We provide the class with a constructor that takes a QWidget, which we use for instantiating the superclass. The native Qt code demands that all objects that inherit QObject has a parent; otherwise the program will fail.

The paint() method is reimplemented from QItemDelegate and is called whenever the view needs to repaint an item:

            public void paint(QPainter painter, QStyleOptionViewItem option, QModelIndex index)
            {
                Object data = index.data();

                if (data != null && data instanceof StarRating) {
                    if (option.state().isSet(QStyle.StateFlag.State_Selected)) {
                        painter.fillRect(option.rect(), option.palette().highlight());
                    }
                    ((StarRating) data).paint(painter, option.rect(), option.palette(),
                                              StarRating.ReadOnly);
                } else
                    super.paint(painter, option, index);
            }

The method is invoked once for each item, represented by a QModelIndex object from the model. If the data stored in the item is a `StarRating`, we paint it ourselves; otherwise, we let QItemDelegate paint it for us. This ensures that the `StarDelegate` can handle the most common data types.

In the case where the item is a `StarRating`, we draw the background if the item is selected, and we draw the item using `StarRating.paint()`, which we will review later.

Any kind of Object can be stored in a model, but when the item delegate encounters items it does not know how to paint, it will leave the view for that item empty.

The createEditor() method is called when the user starts editing an item:

            public QWidget createEditor(QWidget parent, QStyleOptionViewItem item,
                                        QModelIndex index)
            {
                Object data = index.data();

                if (data instanceof StarRating)
                    return new StarEditor(parent, (StarRating) data);
                else
                    return super.createEditor(parent, item, index);
            }

If the item is a `StarRating`, we create a `StarEditor`. The editor must have the `parent`; if not, it would be displayed as a top-level window.

The setEditorData() method is called when an editor is created to initialize it with data from the model:

            public void setEditorData(QWidget editor, QModelIndex index)
            {
                Object data = index.data();

                if (data instanceof StarRating)
                    ((StarEditor) editor).setStarRating((StarRating) data);
                else
                    super.setEditorData(editor, index);
            }

We simply call `setStarRating()` on the editor.

The setModelData() method is called when editing is finished, to commit data from the editor to the model:

            public void setModelData(QWidget editor, QAbstractItemModel model,
                                     QModelIndex index)
            {
                if (index.data() instanceof StarRating)
                    model.setData(index, ((StarEditor) editor).starRating());
                else
                    super.setModelData(editor, model, index);
            }

The editing is finished when the user clicks on the editor. Since we propagate the mouse click event (i.e., do not accept it), the view will close the editor for us when it receives the QMouseEvent.

The `sizeHint()` method returns an item's preferred size:

            public QSize sizeHint(QStyleOptionViewItem option, QModelIndex index)
            {
                Object data = index.data();

                if (data instanceof StarRating)
                    return ((StarRating) data).sizeHint();
                else
                    return super.sizeHint(option, index);
            }

We simply forward the call to `StarRating`.

## StarEditor Class Implementation

The `StarEditor` is used by `StarDelegate` to edit items. The user edits a `StarRating` by moving the mouse over the editor. When the editing is finished the value of the star rating can be retrieved with `getRating()`.

The protected methods are reimplemented from QWidget to handle mouse and paint events. The private method `starAtPosition()` is a helper method that returns the number of the star under the mouse pointer.

Let's start with the constructor:

            public StarEditor(QWidget parent, StarRating rating)
            {
                super(parent);

                starRating = rating;
                setMouseTracking(true);
                setAutoFillBackground(true);
            }

We enable mouse tracking on the widget so we can follow the cursor even when the user doesn't hold down any mouse button. We also turn on QWidget's auto-fill background feature to obtain an opaque background. (Without the call, the view's background would shine through the editor.)

The paintEvent() method is reimplemented from QWidget:

            public void paintEvent(QPaintEvent event)
            {
                QPainter painter = new QPainter(this);
                starRating.paint(painter, rect(), palette(), StarRating.ReadWrite);
            }

We simply call `StarRating.paint()` to draw the stars, just like we did when implementing `StarDelegate`.

            public void mouseMoveEvent(QMouseEvent event)
            {
                int star = starAtPosition(event.x());

                if (star != starRating.getRating() && star > 0) {
                    starRating.setRating(star);
                    update();
                }
            }

In the mouse event handler, we call `setRating()` on the private data member `starCount` to reflect the current cursor position, and we call QWidget.update() to force a repaint.

            public int starAtPosition(int x)
            {
                int star = (x / (starRating.sizeHint().width()
                                / starRating.getMaxRating())) + 1;

                if (star <= 0 || star > starRating.getMaxRating())
                    return -1;

                return star;
            }

The `starAtPosition()` method uses basic linear algebra to find out which star is under the cursor.

## StarRating Class Implementation

The `StarRating` class represents a rating as a number of stars. In addition to holding the data, it is also capable of painting the stars on a QPaintDevice, which in this example is either a view or an editor. The `starCount` member variable stores the current rating, and `maxStarCount` stores the highest possible rating (typically 5). The polygons used for drawing stars and diamonds are set up in a static block.

The constructor initializes `starCount` and `maxStarCount`:

            public StarRating(int rating, int maxRating)
            {
                setupPolygons();
                maxCount = maxRating;
                setRating(rating);
            }

The `paint()` method paints the stars in this `StarRating` object on a paint device:

            public void paint(QPainter painter, QRect rect, QPalette palette,
                          int mode)
            {
                painter.save();

                painter.setRenderHint(QPainter.RenderHint.Antialiasing, true);
                painter.setPen(Qt.PenStyle.NoPen);

                if (mode == ReadWrite)
                    painter.setBrush(palette.highlight());
                else
                    painter.setBrush(palette.text());

                int yOffset = (rect.height() - PaintingFactor) / 2;
                painter.translate(rect.x(), rect.y() + yOffset);
                painter.scale(PaintingFactor, PaintingFactor);

                for (int i = 0; i < maxCount; i++) {
                    if (i < starCount)
                        painter.drawPolygon(starPolygon, Qt.FillRule.WindingFill);
                    else
                        painter.drawPolygon(diamondPolygon, Qt.FillRule.WindingFill);

                    painter.translate(1.0, 0.0);
                }

                painter.restore();
            }

We first set the pen and brush we will use for painting. The `mode` parameter can be either `ReadWrite` or `ReadOnly`. If `mode` is read and write, we use the Highlight color instead of the Foreground color to draw the stars.

Then we draw the stars. If we are in `ReadWrite` mode, we paint diamonds in place of stars if the rating is less than the highest rating.

The `sizeHint()` method returns the preferred size for an area to paint the stars on:

            public QSize sizeHint(QStyleOptionViewItem option, QModelIndex index)
            {
                Object data = index.data();

                if (data instanceof StarRating)
                    return ((StarRating) data).sizeHint();
                else
                    return super.sizeHint(option, index);
            }

The preferred size is just enough to paint the maximum number of stars. The method is called by both `StarDelegate.sizeHint()` and `StarEditor.sizeHint()`.

## StarWindow Class Implementation

StarWindow inherits QWidget and displays a table that has a star delegate installed.

The table is set up in the `createTable()` method:

        public void createTable()
        {
            LinkedList<String> headers = new LinkedList<String>();

            table = new QTableWidget(4, 4);

            table.setItemDelegate(new Delegate(table));

            table.setEditTriggers(QAbstractItemView.EditTrigger.DoubleClicked,
                                  QAbstractItemView.EditTrigger.SelectedClicked);
            table.setSelectionBehavior(
                QAbstractItemView.SelectionBehavior.SelectRows);

The `createTable()` method creates a QTableWidget and sets a `StarDelegate` on it. We set...

        }

The rest of the method fills the table with data including star ratings. DoubleClicked and SelectedClicked are set as edit triggers, so that the editor is opened by a single click when the star rating item is selected.

## Possible Extensions and Suggestions

There are many ways to customize Qt Jambi's model/view framework. The approach used in this example is appropriate for most custom delegates and editors. Examples of possibilities not used by the star delegate and star editor are:

- It is possible to open editors programmatically by calling QAbstractItemView.edit(), instead of relying on edit triggers. This could be use to support other edit triggers than those offered by QAbstractItemView. For example, in the Star Delegate example, hovering over an item with the mouse might make sense as a way to pop up an editor.
- By reimplementing QAbstractItemDelegate.editorEvent(), it is possible to implement the editor directly in the delegate, instead of creating a separate QWidget subclass.