PDA

View Full Version : Thumbnail View



MTK358
6th September 2010, 14:57
How would I make a list of image files and their thumbnails? What kind of model and delegate would I need, and how do I use threads to make it not block the GUI while loading the image files?

tbscope
7th September 2010, 05:52
A model would be pretty simple.
Use a standard item or list model, you can put an image or a pointer to an image in the data.
You'll need to create a simple delegate that actually displays the image. You can use a custom role for that when you add the image to the model.

As for the scaling, there's a nice example: http://doc.qt.nokia.com/4.6/qtconcurrent-imagescaling.html

MTK358
7th September 2010, 13:21
OK, so the model will store the filename and thumbnail, and the delegate will get the thumbnail and draw it.

Also, I'd like to use a thread to load the thumbnails so that the UI isn't blocked, and just insert a temporary image while the thumbnail is loaded. Once the thread signals the model to set a thumbnail, how do I tell the delegate to redraw the item?

tbscope
7th September 2010, 13:43
You can put the thread creating the thumbnail in your model if you want.

But, do not use a thread to do the actual painting in the view. This means: draw a thumnail that is already there directly on the screen, don't do this via a thread. Qt considers drawing on widgets in a separate thread illegal (unless you apply some voodoo magic)

MTK358
7th September 2010, 15:05
But still, when the thread gives the model the image, I have to tell the delegate to redraw the item for it to show, right? How do I do that?

Lykurg
7th September 2010, 15:35
See QAbstractItemModel::dataChanged().

MTK358
7th September 2010, 18:36
OK, but I have a few more questions:

How do I load only one thumbnail at a time? I don't want to instantly start hundreds of threads for each image, right?

If the data in the model changed, how do I cancel any thumbnail threads for indexes that don't exist any more or change those whose index changed?

How to I pass a QImage in a QVariant? Somehow the delegate has to get the thumbnail. Or maybe have a thumbnail(QModelIndex) method instead of using the data() method?

tbscope
7th September 2010, 18:50
This is a little tricky but once you get the hang of it, it's pretty easy.

For any custom type that you want to use with a QVariant, you need to declare and register it so the metacompiler knows about it.

You declare your custom type with:

Q_DECLARE_METATYPE(QImage);

The registering is done via:

qRegisterMetaType<QImage>();
qRegisterMetaType<QImage *>();
Registering is mandatory when using threads (queued connections)

http://doc.qt.nokia.com/4.6/custom-types.html

You can then use it in a QVariant like this:

QImage myImage(...);
QVariant myImageVariant = QVariant(myImage);
QImage anotherImage = myImageVariant.value<QImage>();

About the drawing:
Always draw only those things that are really on the screen. Things that are not on the screen waste computing power when they are drawn (unless of course you want to do some caching for example).

The models come with an option called fetchMore and canFetchMore.
Implement those to tell the view to only get the data from the model for the items on the screen, or the first 20 items etc...

MTK358
7th September 2010, 19:54
This is a little tricky but once you get the hang of it, it's pretty easy.

For any custom type that you want to use with a QVariant, you need to declare and register it so the metacompiler knows about it.

You declare your custom type with:

Q_DECLARE_METATYPE(QImage);

The registering is done via:

qRegisterMetaType<QImage>();
qRegisterMetaType<QImage *>();

Where do I put those?


Registering is mandatory when using threads (queued connections)

What do you mean? And what is a metatype?

tbscope
7th September 2010, 20:09
Where do I put those?

You don't need to add these lines, sorry.
QImage is already registered.

Quoting the docs:

A Note on GUI Types
Because QVariant is part of the QtCore library, it cannot provide conversion functions to data types defined in QtGui, such as QColor, QImage, and QPixmap. In other words, there is no toColor() function. Instead, you can use the QVariant::value() or the qVariantValue() template function. For example:

QVariant variant;
...
QColor color = variant.value<QColor>();The inverse conversion (e.g., from QColor to QVariant) is automatic for all data types supported by QVariant, including GUI-related types:

QColor color = palette().background().color();
QVariant variant = color;

MTK358
14th September 2010, 01:50
Anyway, I made a class called "ThumbThread" that inherits QThread, and it has one slot that adds a file to its internal queue. In its main thread, it loops forever, yielding if the queue is empty, otherwise retrieving a thumbnail of the first file in the queue and emitting a signal. But there are problems:

It still kind of blocks the GUI (but not much) and the thumbnails appear in groups, instead of one by one.

How do I draw the standard selection indicator from my delegate (or can I do this without a custom delegate)?

Should I scale the image in the thumbnail thread or in the GUI thread based on the zoom setting, to be later implemented?

What if files are removed from the model and useless files are queued in the thread?

MTK358
15th September 2010, 01:49
Anyone?

(10 char limit)

wysota
15th September 2010, 11:00
You would do perfectly fine without using QThread and all the problems and programming errors related to it. Use QtConcurrent::run() instead.

As for the delegate - do you need a custom delegate? The default one can draw thumbnails just fine.

MTK358
15th September 2010, 13:46
You would do perfectly fine without using QThread and all the problems and programming errors related to it. Use QtConcurrent::run() instead.

I'll read about that.


As for the delegate - do you need a custom delegate? The default one can draw thumbnails just fine.

How do I do that? Do I need to return an image for a certain role?

wysota
15th September 2010, 14:35
Do I need to return an image for a certain role?
Yes, for Qt::DecorationRole.

MTK358
15th September 2010, 17:22
How do I make the thumbnail function return two values (a persistent model index and an image)?

Also, I think that to make it easier to manage I need the model's internal list to be one of filename/thumbnail pairs. How wouldI do that?

wysota
15th September 2010, 17:54
Why do you need a persistent model index?

MTK358
15th September 2010, 18:03
What if items are added/removed from my model while the concurrent function is getting the thumbnail (and it somehow needs to tell the model which item the thumbnail belongs to)?

wysota
15th September 2010, 18:15
I don't know what is your internal representation of the model but you can map QFuture to pointers to your items (in the internal representation) and use that to find the proper item. Of course you can use a persistent model index for this too but the concurrent function doesn't have to know the index. You can have something like QMap<QFuture*,QPersistentModelIndex> in your model. Just be aware that the keeping many persistent indexes is quite expensive.

MTK358
15th September 2010, 18:23
My internal representation is a list of file paths. Seems like it would make a lot of sense for it to be a list of file path/thumbnail pairs.

wysota
15th September 2010, 19:07
Have a list of instances of a structure, you might want to add some more info later and there is no point in redesigning everything then. Or just use QStandardItemModel.

MTK358
15th September 2010, 21:06
That's a good idea. The problem with QStandardItems is that it must store the whole path, but show only the name in the view.

Also, how do I avoid starting huge amounts of the concurrent thumbnail functions at once? Or is it OK?

wysota
15th September 2010, 21:14
The problem with QStandardItems is that it must store the whole path, but show only the name in the view.
Sorry? There is Qt::DisplayRole for what gets shown and an infinite number of custom roles you can use for any data you want (such as the full path).


Also, how do I avoid starting huge amounts of the concurrent thumbnail functions at once? Or is it OK?
It's ok, they will be queued.

MTK358
15th September 2010, 21:17
Sorry? There is Qt::DisplayRole for what gets shown and an infinite number of custom roles you can use for any data you want (such as the full path).

How am I supposed to do that when I'm not using a custom model?


It's ok, they will be queued.

They will be queued automatically without me taking any action?

wysota
15th September 2010, 22:29
How am I supposed to do that when I'm not using a custom model?
You can pass any integer equal or larger than Qt::UserRole as the role for any method accepting a role identifier.


They will be queued automatically without me taking any action?
Yes. QtConcurrent will only run concurrently at most as many jobs as you have cores available in your machine (so 1 task for 1 core, 2 tasks for 2 cores, etc.), excluding the main execution thread (so Qt Concurrent guarantees that at least one job will be ran). The remaining jobs will be queued until there are idle threads available.

MTK358
15th September 2010, 23:51
You can pass any integer equal or larger than Qt::UserRole as the role for any method accepting a role identifier.

So you can add a non-visible piece of data to be used internally by your program to a QStandardItem?


Yes. QtConcurrent will only run concurrently at most as many jobs as you have cores available in your machine (so 1 task for 1 core, 2 tasks for 2 cores, etc.), excluding the main execution thread (so Qt Concurrent guarantees that at least one job will be ran). The remaining jobs will be queued until there are idle threads available.

OK, that's nice.

wysota
16th September 2010, 00:04
So you can add a non-visible piece of data to be used internally by your program to a QStandardItem?
Yes, that's correct.


enum { myCustomStringRole = Qt::UserRole+1, myCustomColorRole };
//...
QStandardItem *item = ...
item->setData("my custom string", myCustomStringRole);
item->setData(QColor("red"), myCustomColorRole);