Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

[SOLVED] Multiple background images in QTreeView



  • Hi,
    I am making an audio software on my free time and I would like to display properly multiple background images in a QTreeView.

    My tree view is structured in Artist \ Album \ Tracks (can be changed) and here is what I have done so far :

    Miam-Player

    To do this, I have reimplemented

    void QTreeView::drawBranches(QPainter * painter, const QRect & rect, const QModelIndex & index) const
    
    
    void LibraryTreeView::drawBranches(QPainter *painter, const QRect &r, const QModelIndex &proxyIndex) const
    {
        SettingsPrivate *settings = SettingsPrivate::instance();
        if (settings->isBigCoverEnabled()) {
            QModelIndex index2 = proxyIndex;
            QStandardItem *item = _libraryModel->itemFromIndex(_proxyModel->mapToSource(proxyIndex));
            if (item && item->type() == LibraryFilterProxyModel::IT_Album && isExpanded(index2)) {
                QString cover = item->data(LibraryFilterProxyModel::DF_CoverPath).toString();
                /* Get the area to display cover */
                int w, h;
                w = rect().width() - (r.width() + 2 * verticalScrollBar()->width());
                h = item->rowCount() * this->indexRowSizeHint(index2.child(0, 0));
                QPixmap pixmap(cover);
                w = qMin(h, qMin(w, pixmap.width()));
                QPixmap leftBorder = pixmap.copy(0, 0, 3, pixmap.height());
                leftBorder = leftBorder.scaled(1 + rect().width() - (w + 2 * verticalScrollBar()->width()), w);
                /* Create a mix with 2 images: first one is a 3 pixels subimage of the album cover which is expanded to the left border */
                /* The second one is a computer generated gradient focused on alpha channel */
                if (!leftBorder.isNull()) {
                    QLinearGradient linearAlphaBrush(0, 0, leftBorder.width(), 0);
                    linearAlphaBrush.setColorAt(0, QApplication::palette().base().color());
                    linearAlphaBrush.setColorAt(1, Qt::transparent);
    
                    painter->save();
                    /* Because the expanded border can look strange to one, is blurred with some gaussian function */
                    QImage img = ImageUtils::blurred(leftBorder.toImage(), leftBorder.rect(), 10, false);
                    painter->drawImage(0, r.y() + r.height(), img);
                    painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
                    painter->setPen(Qt::NoPen);
                    painter->setBrush(linearAlphaBrush);
                    painter->drawRect(0, r.y() + r.height(), leftBorder.width(), leftBorder.height());
                    painter->drawPixmap(1 + rect().width() - (w + 2 * verticalScrollBar()->width()), r.y() + r.height(), w, w, pixmap);
    
                    painter->setOpacity(settings->bigCoverOpacity());
                    painter->fillRect(0, r.y() + r.height(), rect().width() - 2 * verticalScrollBar()->width(), leftBorder.height(), QApplication::palette().base());
                    painter->restore();
                }
            }
        }
        TreeView::drawBranches(painter, r, proxyIndex);
    }
    

    Few issues with my implementation that I'm struggling with :

    • when scrolling down, if the node "Album" is out of sight, the faded background image is removed
    • when mouse is over an item, background is destroyed because of repainting the delegate

    First of all, am I in the good direction for achieving multiple background images ? If no, could anyone suggests me smarter ways. If yes, what fixes could I imagine!

    Thanks



  • The solution was to move all the code into my existing QStyledItemDelegate class. Below, you fill find what I have done (simplified here):

    void LibraryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        QStandardItem *item = _libraryModel.data()->itemFromIndex(_proxy.data()->mapToSource(index));
    switch (item->type()) {
    case Miam::IT_Album:
        this->paintRect(painter, o);
        this->drawAlbum(painter, o, static_cast<AlbumItem*>(item));
        break;
    case Miam::IT_Artist:
        this->paintRect(painter, o);
        this->drawArtist(painter, o, static_cast<ArtistItem*>(item));
        break;
    case Miam::IT_Disc:
        this->paintRect(painter, o);
        this->drawDisc(painter, o, static_cast<DiscItem*>(item));
        break;
    case Miam::IT_Separator:
        this->drawLetter(painter, o, static_cast<SeparatorItem*>(item));
        break;
    case Miam::IT_Track:
        this->paintCoverOnTrack(painter, o, static_cast<TrackItem*>(item));
        this->drawTrack(painter, o, static_cast<TrackItem*>(item));
        break;
    default:
        QStyledItemDelegate::paint(painter, o, index);
        break;
    }
    

    }

    And the function paintCoverOnTrack which does the job:

    • Extend the default rect to the left, which is usually where drawBranches do the painting
    • Get the picture loaded in memory when expanding the tree. It's loaded only once
    • Paint the cover on the right
    • Create a subimage to expand it on the left
    • Create a selection rectangle if mouse is over
    • Then display the text

    Here is the code:

    void LibraryItemDelegate::paintCoverOnTrack(QPainter *painter, const QStyleOptionViewItem &opt, const TrackItem *track) const
    {
        SettingsPrivate *settings = SettingsPrivate::instance();
        // Copy QStyleOptionViewItem to be able to expand it to the left, and take the maximum available space
        QStyleOptionViewItem option(opt);
        option.rect.setX(0);
        const QImage *image = _libraryTreeView->expandedCover(track->parent());
        if (!image) {
            return;
        }
    
        int totalHeight = track->model()->rowCount(track->parent()->index()) * option.rect.height();
        QImage scaled;
        QRect subRect;
        if (totalHeight > option.rect.width()) {
            scaled = image->scaledToWidth(option.rect.width());
            subRect = option.rect.translated(option.rect.width() - scaled.width(), -option.rect.y() + option.rect.height() * track->row());
        } else {
            scaled = image->scaledToHeight(totalHeight);
            int dx = option.rect.width() - scaled.width();
            subRect = option.rect.translated(-dx, -option.rect.y() + option.rect.height() * track->row());
        }
    
        // Fill with white when there are too much tracks to paint (height of all tracks is greater than the scaled image)
        QImage subImage = scaled.copy(subRect);
        if (scaled.height() < subRect.y() + subRect.height()) {
            subImage.fill(option.palette.base().color());
        }
    
        painter->save();
        painter->setOpacity(1 - settings->bigCoverOpacity());
        painter->drawImage(option.rect, subImage);
    
        // Over paint black pixel in white
        QRect t(option.rect.x(), option.rect.y(), option.rect.width() - scaled.width(), option.rect.height());
        QImage white(t.size(), QImage::Format_ARGB32);
        white.fill(option.palette.base().color());
        painter->setOpacity(1.0);
        painter->drawImage(t, white);
    
        // Create a mix with 2 images: first one is a 3 pixels subimage of the album cover which is expanded to the left border
        // The second one is a computer generated gradient focused on alpha channel
        QImage leftBorder = scaled.copy(0, subRect.y(), 3, option.rect.height());
        if (!leftBorder.isNull()) {
    
            // Because the expanded border can look strange to one, is blurred with some gaussian function
            leftBorder = leftBorder.scaled(t.width(), option.rect.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
            leftBorder = ImageUtils::blurred(leftBorder, leftBorder.rect(), 10, false);
            painter->setOpacity(1 - settings->bigCoverOpacity());
            painter->drawImage(t, leftBorder);
    
            QLinearGradient linearAlphaBrush(0, 0, leftBorder.width(), 0);
            linearAlphaBrush.setColorAt(0, QApplication::palette().base().color());
            linearAlphaBrush.setColorAt(1, Qt::transparent);
    
            painter->setOpacity(1.0);
            painter->setCompositionMode(QPainter::CompositionMode_SourceOver);
            painter->setPen(Qt::NoPen);
            painter->setBrush(linearAlphaBrush);
            painter->drawRect(t);
        }
        painter->restore();
    
        // Display a light selection rectangle when one is moving the cursor
        painter->save();
        QColor color = option.palette.highlight().color();
        color.setAlphaF(0.66);
        if (option.state.testFlag(QStyle::State_MouseOver) && !option.state.testFlag(QStyle::State_Selected)) {
            if (SettingsPrivate::instance()->isCustomColors()) {
                painter->setPen(option.palette.highlight().color().darker(100));
                painter->setBrush(color.lighter());
            } else {
                painter->setPen(option.palette.highlight().color());
                painter->setBrush(color.lighter(160));
            }
            painter->drawRect(opt.rect.adjusted(0, 0, -1, -1));
        } else if (option.state.testFlag(QStyle::State_Selected)) {
            // Display a not so light rectangle when one has chosen an item. It's darker than the mouse over
            if (SettingsPrivate::instance()->isCustomColors()) {
                painter->setPen(option.palette.highlight().color().darker(150));
                painter->setBrush(color);
            } else {
                painter->setPen(option.palette.highlight().color());
                painter->setBrush(color.lighter(150));
            }
            painter->drawRect(opt.rect.adjusted(0, 0, -1, -1));
        }
        painter->restore();
    }
    

    Here is the result:
    Big covers



  • @Matthieu said:

    Glad you found it, Really nice result!
    Super Meat Boy ftw


Log in to reply