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
andQIconEngine
, 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 aroundfromTheme
's refusal to fall back tohicolor
(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.
-
@ssokolow
icons get requested viaQAbstractItemModel::data()
with theQt::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:- 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.
- Speculatively generating every scaled copy the delegate might request.
-
@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.
-
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:- Check what sizes
QIcon
has available and retrieve the closest one. - Depending on how close we are, apply different mixes of scaling algorithms to reach the target size.
- 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. - Check what sizes
-
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 thedata
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 theQTableView
use the larger icon size from theQListView
, 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
andQIcon::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
orQListView
'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.