Question

Context

I have a QTreeView with a QStandardItemModel. My model can be changed to display it's items in 4 different modes:

  1. Level 0: Artist \ Level 1: Album \ Level 2: Tracks
  2. Level 0: Artist - Album \ Level 1: Tracks
  3. Level 0: Albums \ Level 1: Tracks
  4. Level 0: Year \ Level 1: Artist - Album \ Level 2: Tracks

I have made a subclass of QStyledItemDelegate in order to displays stars (like the Star Delegate Example)

In mode 2 or 3, one can enabled cover album in options (and sets the size, like 64 x 64 pixels).

To reduce memory footprint, covers are lazily loaded when items are displayed on screen. No background process is scanning the hard drive each time you're launching the Audio Player.

It's working fine but the User Experience could be improved. In fact, by using the wheelmouse, covers are loaded without any problem. When using the vertical scrollbar, in a 500 albums library, and moving it downward, you can hear your hard-drive scratching when loading *.jpeg or *.png. Once all covers are loaded, scrolling is perfectly smooth (I need to dispose them later).

Here is what I'v made so far:

I have subclassed QScrollBar and detected MousePressEvent and MouseReleaseEvent to temporarily disabled loading.

I have created a signal when one is clicking on the scrollbar, and connected it to my QStyledItemDelegate. However, covers are "popping" on screen.

What I'm looking for:

I would like to display smoothly, with the QPropertyAnimation class (and the Animation Framework). Sadly, QStyledItemDelegate, QIcon, QStandardItem are not QObject nor QWidget, so I cannot use 2 or 3 lines of code to create this kind of animation.

Is there is a workaround, or some kind of (not so ugly) hack?

I would rather not to override paintEvent to recreate everything from scratch in my QTreeView because it seems quite hard to do it, but maybe I'm wrong.

Was it helpful?

Solution

Well, I didn't find a proper way with QPropertyAnimation, so here is a much more complicated solution.

class LibraryScrollBar

void LibraryScrollBar::mouseMoveEvent(QMouseEvent *e)
{
    if (_hasNotEmittedYet) {
        qDebug() << "hide covers when moving";
        emit displayItemDelegate(false);
        _hasNotEmittedYet = false;
    }
    QScrollBar::mouseMoveEvent(e);
}

void LibraryScrollBar::mouseReleaseEvent(QMouseEvent *e)
{
    if (!_hasNotEmittedYet) {
        qDebug() << "show covers when stopped moving";
        emit displayItemDelegate(true);
        _hasNotEmittedYet = true;
    }
    QScrollBar::mouseReleaseEvent(e);
}

In class LibraryTreeView

void LibraryTreeView::init(LibrarySqlModel *sql)
{
    /// some code before
    LibraryScrollBar *vScrollBar = new LibraryScrollBar(this);
    this->setVerticalScrollBar(vScrollBar);
    connect(vScrollBar, &LibraryScrollBar::displayItemDelegate, [=](bool b) {
        _itemDelegate->displayIcon(b);
        b ? _timer->start() : _timer->stop();
    });
    connect(_timer, &QTimer::timeout, this, &LibraryTreeView::repaintIcons);
}

void LibraryTreeView::repaintIcons()
{
    static qreal r = 0;
    if (_timer->isActive()) {
        r += 0.01;
        _itemDelegate->setIconOpacity(r);
        if (r >= 1) {
            _timer->stop();
            r = 0;
        }
        this->viewport()->repaint();
    }
}

In class LibraryItemDelegate

void LibraryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    painter->save();
    painter->setFont(Settings::getInstance()->font(Settings::LIBRARY));
    QStandardItem *item = _libraryModel.data()->itemFromIndex(_proxy.data()->mapToSource(index));
    QStyleOptionViewItem o = option;
    initStyleOption(&o, index);

    // Removes the dotted rectangle to the focused item
    o.state &= ~QStyle::State_HasFocus;
    int type = item->data(LibraryTreeView::Type).toInt();
    switch (type) {
    case LibraryTreeView::Album:
        this->drawAlbum(painter, o, item);
        break;
    /// etc
    }
    painter->restore();
}

/** Albums have covers usually. */
void LibraryItemDelegate::drawAlbum(QPainter *painter, QStyleOptionViewItem &option, QStandardItem *item) const
{
    static QImageReader imageReader;
    static int coverSize = Settings::getInstance()->coverSize();
    QString file = item->data(LibraryTreeView::DataCoverPath).toString();
    // Display a light selection rectangle when one is moving the cursor
    if (option.state & QStyle::State_MouseOver && ~option.state & QStyle::State_Selected) {
        painter->save();
        painter->setPen(option.palette.highlight().color());
        painter->setBrush(option.palette.highlight().color().lighter(175));
        painter->drawRect(option.rect.adjusted(0, 0, -1, -1));
        painter->restore();
    } else if (option.state & QStyle::State_Selected) {
        // Display a not so light rectangle when one has chosen an item. It's darker than the mouse over
        painter->save();
        painter->setPen(option.palette.highlight().color());
        painter->setBrush(option.palette.highlight().color().lighter(160));
        painter->drawRect(option.rect.adjusted(0, 0, -1, -1));
        painter->restore();
    }
    if (_showCovers) {
        /// XXX: extract this elsewhere
        // Qt::UserRole + 20 == false => pixmap not loaded ; == true => pixmap loaded
        if (item->data(Qt::UserRole + 20).toBool() == false && !file.isEmpty()) {
            FileHelper fh(file);
            QFileInfo f(file);
            qDebug() << "loading cover from harddrive";
            // If it's an inner cover, load it
            if (FileHelper::suffixes().contains(f.suffix())) {
                std::unique_ptr<Cover> cover(fh.extractCover());
                if (cover) {
                    QPixmap p;
                    p.loadFromData(cover->byteArray(), cover->format());
                    p = p.scaled(coverSize, coverSize);
                    item->setIcon(p);
                    item->setData(true, Qt::UserRole + 20);
                }
            } else {
                imageReader.setFileName(QDir::fromNativeSeparators(file));
                imageReader.setScaledSize(QSize(coverSize, coverSize));
                item->setIcon(QPixmap::fromImage(imageReader.read()));
                item->setData(true, Qt::UserRole + 20);
            }
        }
    }
    bool b = item->data(Qt::UserRole + 20).toBool();
    if (_showCovers && b) {
        QPixmap p = option.icon.pixmap(QSize(coverSize, coverSize));
        QRect cover(option.rect.x() + 1, option.rect.y() + 1, coverSize, coverSize);
        if (_animateIcons) {
            painter->save();
            painter->setOpacity(_iconOpacity);
            painter->drawPixmap(cover, p);
            painter->restore();
        } else {
            painter->drawPixmap(cover, p);
        }
    }
    // It's possible to have missing covers in your library, so we need to keep alignment.
    QPoint topLeft(option.rect.x() + coverSize + 5, option.rect.y());
    QFontMetrics fmf(Settings::getInstance()->font(Settings::LIBRARY));
    QRect rectText(topLeft, option.rect.bottomRight());
    option.textElideMode = Qt::ElideRight;
    QString s = fmf.elidedText(option.text, Qt::ElideRight, rectText.width());
    painter->drawText(rectText, Qt::AlignVCenter, s);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top