Enhancing thumbnails with PictureDelegate

By default, a QListView class will request Qt::DisplayRole and Qt::DecorationRole to display text and a picture for each item. Thus, we already have a visual result, for free, that looks like this:

However, our Gallery application deserves better thumbnail rendering. Hopefully, we can easily customize it using the view's delegate concept. A QListView class provides a default item rendering. We can do our own item rendering by creating a class that inherits QStyledItemDelegate. The aim is to paint your dream thumbnails with a name banner like the following screenshot:

Let's take a look at PictureDelegate.h:

#include <QStyledItemDelegate> 
 
class PictureDelegate : public QStyledItemDelegate 
{ 
    Q_OBJECT 
public: 
    PictureDelegate(QObject* parent = 0); 
 
    void paint(QPainter* painter, const QStyleOptionViewItem& 
        option, const QModelIndex& index) const override; 
 
    QSize sizeHint(const QStyleOptionViewItem& option, 
        const QModelIndex& index) const override; 
}; 

That is right, we only have to override two functions. The most important function, paint(), will allow us to paint the item like we want. The sizeHint() function will be used to specify the item size.

We can now see the painter work in PictureDelegate.cpp:

#include "PictureDelegate.h" 
 
#include <QPainter> 
 
const unsigned int BANNER_HEIGHT = 20; 
const unsigned int BANNER_COLOR = 0x303030; 
const unsigned int BANNER_ALPHA = 200; 
const unsigned int BANNER_TEXT_COLOR = 0xffffff; 
const unsigned int HIGHLIGHT_ALPHA = 100; 
 
PictureDelegate::PictureDelegate(QObject* parent) : 
    QStyledItemDelegate(parent) 
{ 
} 
 
void PictureDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const 
{ 
    painter->save(); 
 
    QPixmap pixmap = index.model()->data(index, 
        Qt::DecorationRole).value<QPixmap>(); 
    painter->drawPixmap(option.rect.x(), option.rect.y(), pixmap); 
 
    QRect bannerRect = QRect(option.rect.x(), option.rect.y(), 
        pixmap.width(), BANNER_HEIGHT); 
    QColor bannerColor = QColor(BANNER_COLOR); 
    bannerColor.setAlpha(BANNER_ALPHA); 
    painter->fillRect(bannerRect, bannerColor); 
 
    QString filename = index.model()->data(index, 
        Qt::DisplayRole).toString(); 
    painter->setPen(BANNER_TEXT_COLOR); 
    painter->drawText(bannerRect, Qt::AlignCenter, filename); 
 
    if (option.state.testFlag(QStyle::State_Selected)) { 
        QColor selectedColor = option.palette.highlight().color(); 
        selectedColor.setAlpha(HIGHLIGHT_ALPHA); 
        painter->fillRect(option.rect, selectedColor); 
    } 
 
    painter->restore(); 
} 

Each time QListView needs to display an item, this delegate's paint() function will be called. The paint system can be seen as layers that you paint one on top of each other. The QPainter class allows us to paint anything we want: circles, pies, rectangles, text, and so on. The item area can be retrieved with option.rect(). Here are the steps:

  1. It is easy to break the painter state passed in the parameter list, thus we must save the painter state with painter->save() before doing anything, to be able to restore it when we have finished our drawing.
  2. Retrieve the item thumbnail and draw it with the QPainter::drawPixmap() function.
  3. Paint a translucent gray banner on top of the thumbnail with the QPainter::fillRect() function.
  4. Retrieve the item display name and draw it on the banner using the QPainter::drawText() function.
  5. If the item is selected, we paint a translucent rectangle on the top using the highlight color from the item.
  6. We restore the painter state to its original state.
If you want to draw a more complex item, check the QPainter official documentation at  doc.qt.io/qt-5/qpainter.html.

This is the sizeHint() function's implementation:

QSize PictureDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const 
{ 
    const QPixmap& pixmap = index.model()->data(index, 
        Qt::DecorationRole).value<QPixmap>(); 
    return pixmap.size(); 
} 

This one is easier. We want the item's size to be equal to the thumbnail size. As we kept the aspect ratio of the thumbnail during its creation in Picture::setFilePath(), thumbnails can have a different width and height. Hence, we basically retrieve the thumbnail and return its size.

When you create an item delegate, avoid directly inheriting the QItemDelegate class and instead inherit  QStyledItemDelegate. This last one supports Qt style sheets, allowing you to easily customize the rendering.

Now that PictureDelegate is ready, we can configure our thumbnailListView to use it, updating the AlbumWidget.cpp file like this:

AlbumWidget::AlbumWidget(QWidget *parent) : 
    QWidget(parent), 
    ui(new Ui::AlbumWidget), 
    mAlbumModel(nullptr), 
    mAlbumSelectionModel(nullptr), 
    mPictureModel(nullptr), 
    mPictureSelectionModel(nullptr) 
{ 
    ui->setupUi(this); 
    clearUi(); 
 
    ui->thumbnailListView->setSpacing(5); 
    ui->thumbnailListView->setResizeMode(QListView::Adjust); 
    ui->thumbnailListView->setFlow(QListView::LeftToRight); 
    ui->thumbnailListView->setWrapping(true); 
    ui->thumbnailListView->setItemDelegate( 
        new PictureDelegate(this)); 
    ... 
} 
Qt tip An item delegate can also manage the editing process with the QStyledItemDelegate::createEditor() function.