[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
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.