How do I customize icon scaling behaviour in a QListView?



  • I have an application where I want to mix QIcon::fromTheme with custom upscaling in my listview.

    Specifically, I don't want to lose fromTheme's ability to retrieve the most appropriate icon size offered by the theme, but I then want to apply custom upscaling if I receive an icon that's too small.

    (It's a game browser and I've found that I can get better results with tiny icons from old games if I carefully control what mixture of nearest neighbour and smooth scaling gets used.)

    I've looked at the docs for QIcon and QIconEngine, since I thought it'd be nice to address it at the same time as I hook up a fallback to PyXDG's icon resolver to work around fromTheme's refusal to fall back to hicolor (in Qt 5.2.1) like the desktop does when searching for icons. However, I'm having trouble finding the how the default item delegate requests the icon and where Qt actually allows me to hook in and interfere without going overboard and reinventing the moon.

    While I technically am prototyping this in PyQt, I'm asking in here because I might want to change languages once the prototype's design stabilizes and I'm much more knowledgeable with Python than C++ and so I'm more likely to have trouble adapting Python advice to C++ than vice-versa.


  • Moderators

    @ssokolow
    icons get requested via QAbstractItemModel::data() with the Qt::DecorationRole item role.



  • @raven-worx I'm using a custom model, so I do understand that.

    What I need to know is the least reinvention-heavy way to customize the interaction between QIcon and the default item delegate so that I'm no longer forced to choose between two less-than-ideal options:

    1. Forcing the largest possible icon on the delegate when the theme may provide a pre-scaled icon with the design tweaked for the requested size.
    2. Speculatively generating every scaled copy the delegate might request.

  • Moderators

    @ssokolow
    only the item delegate knows what size to request. Since a model can be shared between views and each view might have it's own icon size.

    A QIcon can contain various sizes. I don't know if your theme returns such icons.



  • @raven-worx

    Hence my decision to ask about putting some code in between the QIcon code which services the request and the delegate code which makes the request (or the closest feasible alternative), so I can implement algorithms like this:

    1. Check what sizes QIcon has available and retrieve the closest one.
    2. Depending on how close we are, apply different mixes of scaling algorithms to reach the target size.
    3. Return what the default delegate code asked for.

    That way, I don't have to reinvent QIcon or the default item delegate when all I want to do is customize the scaling behaviour.

    EDIT: For example, if I could figure out how the default item delegate actually handles requesting the icon, maybe I'd discover a method I could wrap or replace in a subclass to rework the code relating to Qt::DecorationRole without having to write a whole new delegate.


  • Moderators

    The road for an icon from a model to the screen is quite short - the delegate calls index.data() and the model returns it.

    Subclassing a delegate in this case is ugly because it forces you to use that particular delegate and reimplement it in every delegate you'd need.

    Returning a pixmap of the desired size from the model is ugly too, because, as @raven-worx mentioned the model can be shared between views and should not know that size in general.

    The other option that Qt offers in cases like this are proxy models. You can subclass QIdentityProxyModel and implement the needed transformation in the data method. You'd have an instance of that proxy for each view with a different size "hint" set.
    This way you're not tied to any particular delegate or model.

    A possible implementation of such proxy would look something like this:

    class MyProxy : public QIdentityProxyModel
    {
        QSize desired_size;
    public:
        MyProxy(QObject* parent) : QIdentityProxyModel(parent) {}
        void setDesiredSize(const QSize& size) { desired_size = size; }
    
        QVariant data(const QModelIndex &proxyIndex, int role) const override
        {
            if (role == Qt::DecorationRole)
            {
                QVariant orig_data = QIdentityProxyModel::data(proxyIndex, role);
                if (orig_data.canConvert<QIcon>())
                {
                    QPixmap p = orig_data.value<QIcon>().pixmap(desired_size);
                    //do the needed transformation to p
                    return p;
                }
            }
            return QIdentityProxyModel::data(proxyIndex, role);
        }
    };
    

    You could of course cache the pixmaps or do other stuff there if you wanted to.



  • @Chris-Kawa Sorry for the delayed response. Busy couple of days.

    Your idea had promise, but then I remembered that setSelectionModel's "same model" requirement has no way to see through proxy models, so I'd be forced to have the QTableView use the larger icon size from the QListView, which puts me right back where I started.

    (Not wanting the QTableView to have to use a naïve 16x16px down-scale of the icon view's icon (which could be as large as 256x256px) when the icon theme is likely to use different SVG source files for the large and small scales to ensure they remain recognizable.)

    ...which, again, makes me wish that QIcon::paint and QIcon::pixmap were virtual, so I could just wrap them in a subclass and fix this in the simplest, cleanest way possible.

    As-is, unless anyone can suggest another approach, it looks like I'm back to asking about how to extend either QIconEngineV2 or QListView's default item delegate with minimal wheel-reinvention.

    (And I'm unsure whether PyQt's support for loading Python-based Qt plugins extends beyond Qt Designer, which makes the icon engine approach of questionable appropriateness for an answer that should apply to both C++ and Python codebases.)



  • Hi, have you looked at using a QListWidget instead of a QListView?
    (A couple of times I wanted similar customization as yours and instead of fighting with the model, I switched to using the simpler QListWidget.)

    Edit: or using a QTableWidget instead of a QTableView.



  • @hskoglund I already have an underlying data store and I'm using a subclass of QAbstractTableModel to bridge it into a Qt GUI.

    Making two in-memory copies of that data store and then jerry-rigging View-like synchronization between the actual data and two widget-specific data stores (plus all the requisite automated testing to make sure it works reliably) seems like the exact opposite of what I want.



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