~ubuntu-branches/ubuntu/precise/gwenview/precise-proposed

« back to all changes in this revision

Viewing changes to lib/thumbnailview/thumbnailview.cpp

  • Committer: Package Import Robot
  • Author(s): Jonathan Riddell
  • Date: 2011-12-15 14:17:54 UTC
  • mto: This revision was merged to the branch mainline in revision 12.
  • Revision ID: package-import@ubuntu.com-20111215141754-z043hyx69dulbggf
Tags: upstream-4.7.90
ImportĀ upstreamĀ versionĀ 4.7.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
49
49
#include "mimetypeutils.h"
50
50
#include "thumbnailloadjob.h"
51
51
 
52
 
namespace Gwenview {
 
52
namespace Gwenview
 
53
{
53
54
 
54
55
#undef ENABLE_LOG
55
56
#undef LOG
68
69
 
69
70
const int WHEEL_ZOOM_MULTIPLIER = 4;
70
71
 
71
 
static KFileItem fileItemForIndex(const QModelIndex& index) {
72
 
        if (!index.isValid()) {
73
 
                LOG("Invalid index");
74
 
                return KFileItem();
75
 
        }
76
 
        QVariant data = index.data(KDirModel::FileItemRole);
77
 
        return qvariant_cast<KFileItem>(data);
 
72
static KFileItem fileItemForIndex(const QModelIndex& index)
 
73
{
 
74
    if (!index.isValid()) {
 
75
        LOG("Invalid index");
 
76
        return KFileItem();
 
77
    }
 
78
    QVariant data = index.data(KDirModel::FileItemRole);
 
79
    return qvariant_cast<KFileItem>(data);
78
80
}
79
81
 
80
 
 
81
 
static KUrl urlForIndex(const QModelIndex& index) {
82
 
        KFileItem item = fileItemForIndex(index);
83
 
        return item.isNull() ? KUrl() : item.url();
 
82
static KUrl urlForIndex(const QModelIndex& index)
 
83
{
 
84
    KFileItem item = fileItemForIndex(index);
 
85
    return item.isNull() ? KUrl() : item.url();
84
86
}
85
87
 
86
88
struct Thumbnail {
87
 
        Thumbnail(const QPersistentModelIndex& index_, const KDateTime& mtime)
88
 
        : mIndex(index_)
89
 
        , mModificationTime(mtime)
90
 
        , mRough(true)
91
 
        , mWaitingForThumbnail(true) {}
92
 
 
93
 
        Thumbnail()
94
 
        : mRough(true)
95
 
        , mWaitingForThumbnail(true) {}
96
 
 
97
 
        /**
98
 
         * Init the thumbnail based on a icon
99
 
         */
100
 
        void initAsIcon(const QPixmap& pix) {
101
 
                mGroupPix = pix;
102
 
                int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large);
103
 
                mFullSize = QSize(largeGroupSize, largeGroupSize);
104
 
        }
105
 
 
106
 
        bool isGroupPixAdaptedForSize(int size) const {
107
 
                if (mWaitingForThumbnail) {
108
 
                        return false;
109
 
                }
110
 
                if (mGroupPix.isNull()) {
111
 
                        return false;
112
 
                }
113
 
                const int groupSize = qMax(mGroupPix.width(), mGroupPix.height());
114
 
                if (groupSize >= size) {
115
 
                        return true;
116
 
                }
117
 
 
118
 
                // groupSize is less than size, but this may be because the full image
119
 
                // is the same size as groupSize
120
 
                return groupSize == qMax(mFullSize.width(), mFullSize.height());
121
 
        }
122
 
 
123
 
        void prepareForRefresh(const KDateTime& mtime) {
124
 
                mModificationTime = mtime;
125
 
                mGroupPix = QPixmap();
126
 
                mAdjustedPix = QPixmap();
127
 
                mFullSize = QSize();
128
 
                mRealFullSize = QSize();
129
 
                mRough = true;
130
 
                mWaitingForThumbnail = true;
131
 
        }
132
 
 
133
 
        QPersistentModelIndex mIndex;
134
 
        KDateTime mModificationTime;
135
 
        /// The pix loaded from .thumbnails/{large,normal}
136
 
        QPixmap mGroupPix;
137
 
        /// Scaled version of mGroupPix, adjusted to ThumbnailView::thumbnailSize
138
 
        QPixmap mAdjustedPix;
139
 
        /// Size of the full image
140
 
        QSize mFullSize;
141
 
        /// Real size of the full image, invalid unless the thumbnail
142
 
        /// represents a raster image (not an icon)
143
 
        QSize mRealFullSize;
144
 
        /// Whether mAdjustedPix represents has been scaled using fast or smooth
145
 
        //transformation
146
 
        bool mRough;
147
 
        /// Set to true if mGroupPix should be replaced with a real thumbnail
148
 
        bool mWaitingForThumbnail;
 
89
    Thumbnail(const QPersistentModelIndex& index_, const KDateTime& mtime)
 
90
        : mIndex(index_)
 
91
        , mModificationTime(mtime)
 
92
        , mRough(true)
 
93
        , mWaitingForThumbnail(true) {}
 
94
 
 
95
    Thumbnail()
 
96
        : mRough(true)
 
97
        , mWaitingForThumbnail(true) {}
 
98
 
 
99
    /**
 
100
     * Init the thumbnail based on a icon
 
101
     */
 
102
    void initAsIcon(const QPixmap& pix)
 
103
    {
 
104
        mGroupPix = pix;
 
105
        int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large);
 
106
        mFullSize = QSize(largeGroupSize, largeGroupSize);
 
107
    }
 
108
 
 
109
    bool isGroupPixAdaptedForSize(int size) const {
 
110
        if (mWaitingForThumbnail) {
 
111
            return false;
 
112
        }
 
113
        if (mGroupPix.isNull()) {
 
114
            return false;
 
115
        }
 
116
        const int groupSize = qMax(mGroupPix.width(), mGroupPix.height());
 
117
        if (groupSize >= size) {
 
118
            return true;
 
119
        }
 
120
 
 
121
        // groupSize is less than size, but this may be because the full image
 
122
        // is the same size as groupSize
 
123
        return groupSize == qMax(mFullSize.width(), mFullSize.height());
 
124
    }
 
125
 
 
126
    void prepareForRefresh(const KDateTime& mtime)
 
127
    {
 
128
        mModificationTime = mtime;
 
129
        mGroupPix = QPixmap();
 
130
        mAdjustedPix = QPixmap();
 
131
        mFullSize = QSize();
 
132
        mRealFullSize = QSize();
 
133
        mRough = true;
 
134
        mWaitingForThumbnail = true;
 
135
    }
 
136
 
 
137
    QPersistentModelIndex mIndex;
 
138
    KDateTime mModificationTime;
 
139
    /// The pix loaded from .thumbnails/{large,normal}
 
140
    QPixmap mGroupPix;
 
141
    /// Scaled version of mGroupPix, adjusted to ThumbnailView::thumbnailSize
 
142
    QPixmap mAdjustedPix;
 
143
    /// Size of the full image
 
144
    QSize mFullSize;
 
145
    /// Real size of the full image, invalid unless the thumbnail
 
146
    /// represents a raster image (not an icon)
 
147
    QSize mRealFullSize;
 
148
    /// Whether mAdjustedPix represents has been scaled using fast or smooth
 
149
    //transformation
 
150
    bool mRough;
 
151
    /// Set to true if mGroupPix should be replaced with a real thumbnail
 
152
    bool mWaitingForThumbnail;
149
153
};
150
154
 
151
155
typedef QHash<QUrl, Thumbnail> ThumbnailForUrl;
153
157
typedef QSet<QPersistentModelIndex> PersistentModelIndexSet;
154
158
 
155
159
struct ThumbnailViewPrivate {
156
 
        ThumbnailView* that;
157
 
        int mThumbnailSize;
158
 
        AbstractDocumentInfoProvider* mDocumentInfoProvider;
159
 
        AbstractThumbnailViewHelper* mThumbnailViewHelper;
160
 
        ThumbnailForUrl mThumbnailForUrl;
161
 
        QTimer mScheduledThumbnailGenerationTimer;
162
 
 
163
 
        UrlQueue mSmoothThumbnailQueue;
164
 
        QTimer mSmoothThumbnailTimer;
165
 
 
166
 
        QPixmap mWaitingThumbnail;
167
 
        QPointer<ThumbnailLoadJob> mThumbnailLoadJob;
168
 
 
169
 
        PersistentModelIndexSet mBusyIndexSet;
170
 
        KPixmapSequence mBusySequence;
171
 
        QTimeLine* mBusyAnimationTimeLine;
172
 
 
173
 
        void setupBusyAnimation() {
174
 
                mBusySequence = KPixmapSequence("process-working", 22);
175
 
                mBusyAnimationTimeLine = new QTimeLine(100 * mBusySequence.frameCount(), that);
176
 
                mBusyAnimationTimeLine->setCurveShape(QTimeLine::LinearCurve);
177
 
                mBusyAnimationTimeLine->setEndFrame(mBusySequence.frameCount() - 1);
178
 
                mBusyAnimationTimeLine->setLoopCount(0);
179
 
                QObject::connect(mBusyAnimationTimeLine, SIGNAL(frameChanged(int)),
180
 
                        that, SLOT(updateBusyIndexes()));
181
 
        }
182
 
 
183
 
        void scheduleThumbnailGenerationForVisibleItems() {
184
 
                if (mThumbnailLoadJob) {
185
 
                        mThumbnailLoadJob->removeItems(mThumbnailLoadJob->pendingItems());
186
 
                }
187
 
                mSmoothThumbnailQueue.clear();
188
 
                mScheduledThumbnailGenerationTimer.start();
189
 
        }
190
 
 
191
 
        void updateThumbnailForModifiedDocument(const QModelIndex& index) {
192
 
                Q_ASSERT(mDocumentInfoProvider);
193
 
                KFileItem item = fileItemForIndex(index);
194
 
                KUrl url = item.url();
195
 
                ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize);
196
 
                QPixmap pix;
197
 
                QSize fullSize;
198
 
                mDocumentInfoProvider->thumbnailForDocument(url, group, &pix, &fullSize);
199
 
                mThumbnailForUrl[url] = Thumbnail(QPersistentModelIndex(index), KDateTime::currentLocalDateTime());
200
 
                that->setThumbnail(item, pix, fullSize);
201
 
        }
202
 
 
203
 
        void generateThumbnailsForItems(const KFileItemList& list) {
204
 
                ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize);
205
 
                if (!mThumbnailLoadJob) {
206
 
                        mThumbnailLoadJob = new ThumbnailLoadJob(list, group);
207
 
                        QObject::connect(mThumbnailLoadJob, SIGNAL(thumbnailLoaded(const KFileItem&, const QPixmap&, const QSize&)),
208
 
                                that, SLOT(setThumbnail(const KFileItem&, const QPixmap&, const QSize&)));
209
 
                        QObject::connect(mThumbnailLoadJob, SIGNAL(thumbnailLoadingFailed(const KFileItem&)),
210
 
                                that, SLOT(setBrokenThumbnail(const KFileItem&)));
211
 
                        mThumbnailLoadJob->start();
212
 
                } else {
213
 
                        mThumbnailLoadJob->setThumbnailGroup(group);
214
 
                        Q_FOREACH(const KFileItem& item, list) {
215
 
                                mThumbnailLoadJob->appendItem(item);
216
 
                        }
217
 
                }
218
 
        }
219
 
 
220
 
        void roughAdjustThumbnail(Thumbnail* thumbnail) {
221
 
                const QPixmap& mGroupPix = thumbnail->mGroupPix;
222
 
                const int groupSize = qMax(mGroupPix.width(), mGroupPix.height());
223
 
                const int fullSize = qMax(thumbnail->mFullSize.width(), thumbnail->mFullSize.height());
224
 
                if (fullSize == groupSize && groupSize <= mThumbnailSize) {
225
 
                        thumbnail->mAdjustedPix = mGroupPix;
226
 
                        thumbnail->mRough = false;
227
 
                } else {
228
 
                        thumbnail->mAdjustedPix = mGroupPix.scaled(mThumbnailSize, mThumbnailSize, Qt::KeepAspectRatio);
229
 
                        thumbnail->mRough = true;
230
 
                }
231
 
        }
232
 
 
233
 
        QPixmap dragPixmapForIndex(const QModelIndex& index) const {
234
 
                KUrl url = urlForIndex(index);
235
 
                QPixmap pix = mThumbnailForUrl.value(url).mAdjustedPix;
236
 
                if (qMax(pix.width(), pix.height()) > DRAG_THUMB_SIZE) {
237
 
                        return pix.scaled(DRAG_THUMB_SIZE, DRAG_THUMB_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation);
238
 
                } else {
239
 
                        return pix;
240
 
                }
241
 
        }
242
 
 
243
 
        QPixmap createDragPixmap(const QModelIndexList& indexes) {
244
 
                const int MAX_THUMBS = 3;
245
 
 
246
 
                int thumbCount;
247
 
                bool more;
248
 
                if (indexes.count() > MAX_THUMBS) {
249
 
                        thumbCount = MAX_THUMBS;
250
 
                        more = true;
251
 
                } else {
252
 
                        thumbCount = indexes.count();
253
 
                        more = false;
254
 
                }
255
 
                QList<QPixmap> thumbs;
256
 
                int width = 0;
257
 
                int height = 0;
258
 
                for (int row=0; row<thumbCount; ++row) {
259
 
                        QModelIndex index;
260
 
                        if (row == thumbCount-1 && more) {
261
 
                                QString text = "(...)";
262
 
                                QPixmap pix(that->fontMetrics().boundingRect(text).size());
263
 
                                pix.fill(Qt::transparent);
264
 
                                {
265
 
                                        QPainter painter(&pix);
266
 
                                        painter.drawText(pix.rect(), Qt::AlignHCenter | Qt::AlignBottom, text);
267
 
                                }
268
 
                                index = indexes.last();
269
 
                                width += pix.width();
270
 
                                thumbs << pix;
271
 
                        } else {
272
 
                                index = indexes[row];
273
 
                        }
274
 
                        QPixmap thumb = dragPixmapForIndex(index);
275
 
                        height = qMax(height, thumb.height());
276
 
                        width += thumb.width();
277
 
                        thumbs << thumb;
278
 
                }
279
 
 
280
 
                QPixmap pix(
281
 
                        width + (thumbs.count() + 1) * DRAG_THUMB_SPACING,
282
 
                        height + 2 * DRAG_THUMB_SPACING
283
 
                        );
284
 
                pix.fill(QToolTip::palette().color(QPalette::Inactive, QPalette::ToolTipBase));
285
 
                QPainter painter(&pix);
286
 
 
287
 
                int x = DRAG_THUMB_SPACING;
288
 
                Q_FOREACH(const QPixmap& thumb, thumbs) {
289
 
                        painter.drawPixmap(x, (pix.height() - thumb.height()) / 2, thumb);
290
 
                        x += thumb.width() + DRAG_THUMB_SPACING;
291
 
                }
292
 
                return pix;
293
 
        }
 
160
    ThumbnailView* q;
 
161
    int mThumbnailSize;
 
162
    AbstractDocumentInfoProvider* mDocumentInfoProvider;
 
163
    AbstractThumbnailViewHelper* mThumbnailViewHelper;
 
164
    ThumbnailForUrl mThumbnailForUrl;
 
165
    QTimer mScheduledThumbnailGenerationTimer;
 
166
 
 
167
    UrlQueue mSmoothThumbnailQueue;
 
168
    QTimer mSmoothThumbnailTimer;
 
169
 
 
170
    QPixmap mWaitingThumbnail;
 
171
    QPointer<ThumbnailLoadJob> mThumbnailLoadJob;
 
172
 
 
173
    PersistentModelIndexSet mBusyIndexSet;
 
174
    KPixmapSequence mBusySequence;
 
175
    QTimeLine* mBusyAnimationTimeLine;
 
176
 
 
177
    void setupBusyAnimation()
 
178
    {
 
179
        mBusySequence = KPixmapSequence("process-working", 22);
 
180
        mBusyAnimationTimeLine = new QTimeLine(100 * mBusySequence.frameCount(), q);
 
181
        mBusyAnimationTimeLine->setCurveShape(QTimeLine::LinearCurve);
 
182
        mBusyAnimationTimeLine->setEndFrame(mBusySequence.frameCount() - 1);
 
183
        mBusyAnimationTimeLine->setLoopCount(0);
 
184
        QObject::connect(mBusyAnimationTimeLine, SIGNAL(frameChanged(int)),
 
185
                         q, SLOT(updateBusyIndexes()));
 
186
    }
 
187
 
 
188
    void scheduleThumbnailGenerationForVisibleItems()
 
189
    {
 
190
        if (mThumbnailLoadJob) {
 
191
            mThumbnailLoadJob->removeItems(mThumbnailLoadJob->pendingItems());
 
192
        }
 
193
        mSmoothThumbnailQueue.clear();
 
194
        mScheduledThumbnailGenerationTimer.start();
 
195
    }
 
196
 
 
197
    void updateThumbnailForModifiedDocument(const QModelIndex& index)
 
198
    {
 
199
        Q_ASSERT(mDocumentInfoProvider);
 
200
        KFileItem item = fileItemForIndex(index);
 
201
        KUrl url = item.url();
 
202
        ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize);
 
203
        QPixmap pix;
 
204
        QSize fullSize;
 
205
        mDocumentInfoProvider->thumbnailForDocument(url, group, &pix, &fullSize);
 
206
        mThumbnailForUrl[url] = Thumbnail(QPersistentModelIndex(index), KDateTime::currentLocalDateTime());
 
207
        q->setThumbnail(item, pix, fullSize);
 
208
    }
 
209
 
 
210
    void generateThumbnailsForItems(const KFileItemList& list)
 
211
    {
 
212
        ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize);
 
213
        if (!mThumbnailLoadJob) {
 
214
            mThumbnailLoadJob = new ThumbnailLoadJob(list, group);
 
215
            QObject::connect(mThumbnailLoadJob, SIGNAL(thumbnailLoaded(KFileItem, QPixmap, QSize)),
 
216
                             q, SLOT(setThumbnail(KFileItem, QPixmap, QSize)));
 
217
            QObject::connect(mThumbnailLoadJob, SIGNAL(thumbnailLoadingFailed(KFileItem)),
 
218
                             q, SLOT(setBrokenThumbnail(KFileItem)));
 
219
            mThumbnailLoadJob->start();
 
220
        } else {
 
221
            mThumbnailLoadJob->setThumbnailGroup(group);
 
222
            Q_FOREACH(const KFileItem & item, list) {
 
223
                mThumbnailLoadJob->appendItem(item);
 
224
            }
 
225
        }
 
226
    }
 
227
 
 
228
    void roughAdjustThumbnail(Thumbnail* thumbnail)
 
229
    {
 
230
        const QPixmap& mGroupPix = thumbnail->mGroupPix;
 
231
        const int groupSize = qMax(mGroupPix.width(), mGroupPix.height());
 
232
        const int fullSize = qMax(thumbnail->mFullSize.width(), thumbnail->mFullSize.height());
 
233
        if (fullSize == groupSize && groupSize <= mThumbnailSize) {
 
234
            thumbnail->mAdjustedPix = mGroupPix;
 
235
            thumbnail->mRough = false;
 
236
        } else {
 
237
            thumbnail->mAdjustedPix = mGroupPix.scaled(mThumbnailSize, mThumbnailSize, Qt::KeepAspectRatio);
 
238
            thumbnail->mRough = true;
 
239
        }
 
240
    }
 
241
 
 
242
    QPixmap dragPixmapForIndex(const QModelIndex& index) const {
 
243
        KUrl url = urlForIndex(index);
 
244
        QPixmap pix = mThumbnailForUrl.value(url).mAdjustedPix;
 
245
        if (qMax(pix.width(), pix.height()) > DRAG_THUMB_SIZE) {
 
246
            return pix.scaled(DRAG_THUMB_SIZE, DRAG_THUMB_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation);
 
247
        } else {
 
248
            return pix;
 
249
        }
 
250
    }
 
251
 
 
252
    QPixmap createDragPixmap(const QModelIndexList& indexes)
 
253
    {
 
254
        const int MAX_THUMBS = 3;
 
255
 
 
256
        int thumbCount;
 
257
        bool more;
 
258
        if (indexes.count() > MAX_THUMBS) {
 
259
            thumbCount = MAX_THUMBS;
 
260
            more = true;
 
261
        } else {
 
262
            thumbCount = indexes.count();
 
263
            more = false;
 
264
        }
 
265
        QList<QPixmap> thumbs;
 
266
        int width = 0;
 
267
        int height = 0;
 
268
        for (int row = 0; row < thumbCount; ++row) {
 
269
            QModelIndex index;
 
270
            if (row == thumbCount - 1 && more) {
 
271
                QString text = "(...)";
 
272
                QPixmap pix(q->fontMetrics().boundingRect(text).size());
 
273
                pix.fill(Qt::transparent);
 
274
                {
 
275
                    QPainter painter(&pix);
 
276
                    painter.drawText(pix.rect(), Qt::AlignHCenter | Qt::AlignBottom, text);
 
277
                }
 
278
                index = indexes.last();
 
279
                width += pix.width();
 
280
                thumbs << pix;
 
281
            } else {
 
282
                index = indexes[row];
 
283
            }
 
284
            QPixmap thumb = dragPixmapForIndex(index);
 
285
            height = qMax(height, thumb.height());
 
286
            width += thumb.width();
 
287
            thumbs << thumb;
 
288
        }
 
289
 
 
290
        QPixmap pix(
 
291
            width + (thumbs.count() + 1) * DRAG_THUMB_SPACING,
 
292
            height + 2 * DRAG_THUMB_SPACING
 
293
        );
 
294
        pix.fill(QToolTip::palette().color(QPalette::Inactive, QPalette::ToolTipBase));
 
295
        QPainter painter(&pix);
 
296
 
 
297
        int x = DRAG_THUMB_SPACING;
 
298
        Q_FOREACH(const QPixmap & thumb, thumbs) {
 
299
            painter.drawPixmap(x, (pix.height() - thumb.height()) / 2, thumb);
 
300
            x += thumb.width() + DRAG_THUMB_SPACING;
 
301
        }
 
302
        return pix;
 
303
    }
294
304
};
295
305
 
296
 
 
297
306
ThumbnailView::ThumbnailView(QWidget* parent)
298
307
: QListView(parent)
299
 
, d(new ThumbnailViewPrivate) {
300
 
        d->that = this;
301
 
        d->mThumbnailViewHelper = 0;
302
 
        d->mDocumentInfoProvider = 0;
303
 
        // Init to some stupid value so that the first call to setThumbnailSize()
304
 
        // is not ignored (do not use 0 in case someone try to divide by
305
 
        // mThumbnailSize...)
306
 
        d->mThumbnailSize = 1;
307
 
 
308
 
        setFrameShape(QFrame::NoFrame);
309
 
        setViewMode(QListView::IconMode);
310
 
        setResizeMode(QListView::Adjust);
311
 
        setDragEnabled(true);
312
 
        setAcceptDrops(true);
313
 
        setDropIndicatorShown(true);
314
 
        setUniformItemSizes(true);
315
 
        setEditTriggers(QAbstractItemView::EditKeyPressed);
316
 
 
317
 
        d->setupBusyAnimation();
318
 
 
319
 
        viewport()->setMouseTracking(true);
320
 
        // Set this attribute, otherwise the item delegate won't get the
321
 
        // State_MouseOver state
322
 
        viewport()->setAttribute(Qt::WA_Hover);
323
 
 
324
 
        setVerticalScrollMode(ScrollPerPixel);
325
 
        setHorizontalScrollMode(ScrollPerPixel);
326
 
 
327
 
        d->mScheduledThumbnailGenerationTimer.setSingleShot(true);
328
 
        d->mScheduledThumbnailGenerationTimer.setInterval(500);
329
 
        connect(&d->mScheduledThumbnailGenerationTimer, SIGNAL(timeout()),
330
 
                SLOT(generateThumbnailsForVisibleItems()) );
331
 
 
332
 
        d->mSmoothThumbnailTimer.setSingleShot(true);
333
 
        connect(&d->mSmoothThumbnailTimer, SIGNAL(timeout()),
334
 
                SLOT(smoothNextThumbnail()) );
335
 
 
336
 
        setContextMenuPolicy(Qt::CustomContextMenu);
337
 
        connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
338
 
                SLOT(showContextMenu()) );
339
 
 
340
 
        if (KGlobalSettings::singleClick()) {
341
 
                connect(this, SIGNAL(clicked(const QModelIndex&)),
342
 
                        SLOT(emitIndexActivatedIfNoModifiers(const QModelIndex&)) );
343
 
        } else {
344
 
                connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
345
 
                        SLOT(emitIndexActivatedIfNoModifiers(const QModelIndex&)) );
346
 
        }
347
 
}
348
 
 
349
 
 
350
 
ThumbnailView::~ThumbnailView() {
351
 
        delete d;
352
 
}
353
 
 
354
 
 
355
 
void ThumbnailView::setModel(QAbstractItemModel* newModel) {
356
 
        if (model()) {
357
 
                disconnect(model(), 0, this, 0);
358
 
        }
359
 
        QListView::setModel(newModel);
360
 
        connect(model(), SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
361
 
                SIGNAL(rowsRemovedSignal(const QModelIndex&, int, int)));
362
 
}
363
 
 
364
 
 
365
 
void ThumbnailView::setThumbnailSize(int value) {
366
 
        if (d->mThumbnailSize == value) {
367
 
                return;
368
 
        }
369
 
        d->mThumbnailSize = value;
370
 
 
371
 
        // mWaitingThumbnail
372
 
        int waitingThumbnailSize;
373
 
        if (value > 64) {
374
 
                waitingThumbnailSize = 48;
375
 
        } else {
376
 
                waitingThumbnailSize = 32;
377
 
        }
378
 
        if (d->mWaitingThumbnail.width() != waitingThumbnailSize) {
379
 
                QPixmap icon = DesktopIcon("chronometer", waitingThumbnailSize);
380
 
                QPixmap pix(icon.size());
381
 
                pix.fill(Qt::transparent);
382
 
                QPainter painter(&pix);
383
 
                painter.setOpacity(0.5);
384
 
                painter.drawPixmap(0, 0, icon);
385
 
                painter.end();
386
 
                d->mWaitingThumbnail = pix;
387
 
        }
388
 
 
389
 
        // Stop smoothing
390
 
        d->mSmoothThumbnailTimer.stop();
391
 
        d->mSmoothThumbnailQueue.clear();
392
 
 
393
 
        // Clear adjustedPixes
394
 
        ThumbnailForUrl::iterator
395
 
                it = d->mThumbnailForUrl.begin(),
396
 
                end = d->mThumbnailForUrl.end();
397
 
        for (; it!=end; ++it) {
398
 
                it.value().mAdjustedPix = QPixmap();
399
 
        }
400
 
 
401
 
        thumbnailSizeChanged(value);
402
 
        d->scheduleThumbnailGenerationForVisibleItems();
403
 
}
404
 
 
405
 
 
406
 
int ThumbnailView::thumbnailSize() const {
407
 
        return d->mThumbnailSize;
408
 
}
409
 
 
410
 
 
411
 
void ThumbnailView::setThumbnailViewHelper(AbstractThumbnailViewHelper* helper) {
412
 
        d->mThumbnailViewHelper = helper;
413
 
}
414
 
 
415
 
AbstractThumbnailViewHelper* ThumbnailView::thumbnailViewHelper() const {
416
 
        return d->mThumbnailViewHelper;
417
 
}
418
 
 
419
 
 
420
 
void ThumbnailView::setDocumentInfoProvider(AbstractDocumentInfoProvider* provider) {
421
 
        d->mDocumentInfoProvider = provider;
422
 
        if (provider) {
423
 
                connect(provider, SIGNAL(busyStateChanged(const QModelIndex&, bool)),
424
 
                        SLOT(updateThumbnailBusyState(const QModelIndex&, bool)));
425
 
                connect(provider, SIGNAL(documentChanged(const QModelIndex&)),
426
 
                        SLOT(updateThumbnail(const QModelIndex&)));
427
 
        }
428
 
}
429
 
 
430
 
AbstractDocumentInfoProvider* ThumbnailView::documentInfoProvider() const {
431
 
        return d->mDocumentInfoProvider;
432
 
}
433
 
 
434
 
 
435
 
void ThumbnailView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) {
436
 
        QListView::rowsAboutToBeRemoved(parent, start, end);
437
 
 
438
 
        // Remove references to removed items
439
 
        KFileItemList itemList;
440
 
        for (int pos=start; pos<=end; ++pos) {
441
 
                QModelIndex index = model()->index(pos, 0, parent);
442
 
                KFileItem item = fileItemForIndex(index);
443
 
                if (item.isNull()) {
444
 
                        kDebug() << "Skipping invalid item!" << index.data().toString();
445
 
                        continue;
446
 
                }
447
 
 
448
 
                QUrl url = item.url();
449
 
                d->mThumbnailForUrl.remove(url);
450
 
                d->mSmoothThumbnailQueue.removeAll(url);
451
 
 
452
 
                itemList.append(item);
453
 
        }
454
 
 
455
 
        if (d->mThumbnailLoadJob) {
456
 
                d->mThumbnailLoadJob->removeItems(itemList);
457
 
        }
458
 
 
459
 
        // Update current index if it is among the deleted rows
460
 
        const int row = currentIndex().row();
461
 
        if (start <= row && row <= end) {
462
 
                QModelIndex index;
463
 
                if (end < model()->rowCount() - 1) {
464
 
                        index = model()->index(end + 1, 0);
465
 
                } else if (start > 0) {
466
 
                        index = model()->index(start - 1, 0);
467
 
                }
468
 
                setCurrentIndex(index);
469
 
        }
470
 
 
471
 
        // Removing rows might make new images visible, make sure their thumbnail
472
 
        // is generated
473
 
        d->mScheduledThumbnailGenerationTimer.start();
474
 
}
475
 
 
476
 
 
477
 
void ThumbnailView::rowsInserted(const QModelIndex& parent, int start, int end) {
478
 
        QListView::rowsInserted(parent, start, end);
479
 
        d->mScheduledThumbnailGenerationTimer.start();
480
 
        rowsInsertedSignal(parent, start, end);
481
 
}
482
 
 
483
 
 
484
 
void ThumbnailView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) {
485
 
        QListView::dataChanged(topLeft, bottomRight);
486
 
        bool thumbnailsNeedRefresh = false;
487
 
        for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
488
 
                QModelIndex index = model()->index(row, 0);
489
 
                KFileItem item = fileItemForIndex(index);
490
 
                if (item.isNull()) {
491
 
                        kWarning() << "Invalid item for index" << index << ". This should not happen!";
492
 
                        continue;
493
 
                }
494
 
 
495
 
                ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(item.url());
496
 
                if (it != d->mThumbnailForUrl.end()) {
497
 
                        // All thumbnail views are connected to the model, so
498
 
                        // ThumbnailView::dataChanged() is called for all of them. As a
499
 
                        // result this method will also be called for views which are not
500
 
                        // currently visible, and do not yet have a thumbnail for the
501
 
                        // modified url.
502
 
                        KDateTime mtime = item.time(KFileItem::ModificationTime);
503
 
                        if (it->mModificationTime != mtime) {
504
 
                                // dataChanged() is called when the file changes but also when
505
 
                                // the model fetched additional data such as semantic info. To
506
 
                                // avoid needless refreshes, we only trigger a refresh if the
507
 
                                // modification time changes.
508
 
                                thumbnailsNeedRefresh = true;
509
 
                                it->prepareForRefresh(mtime);
510
 
                        }
511
 
                }
512
 
        }
513
 
        if (thumbnailsNeedRefresh) {
514
 
                d->mScheduledThumbnailGenerationTimer.start();
515
 
        }
516
 
}
517
 
 
518
 
 
519
 
void ThumbnailView::showContextMenu() {
520
 
        d->mThumbnailViewHelper->showContextMenu(this);
521
 
}
522
 
 
523
 
 
524
 
void ThumbnailView::emitIndexActivatedIfNoModifiers(const QModelIndex& index) {
525
 
        if (QApplication::keyboardModifiers() == Qt::NoModifier) {
526
 
                emit indexActivated(index);
527
 
        }
528
 
}
529
 
 
530
 
 
531
 
void ThumbnailView::setThumbnail(const KFileItem& item, const QPixmap& pixmap, const QSize& size) {
532
 
        ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url());
533
 
        if (it == d->mThumbnailForUrl.end()) {
534
 
                return;
535
 
        }
536
 
        Thumbnail& thumbnail = it.value();
537
 
        thumbnail.mGroupPix = pixmap;
538
 
        thumbnail.mAdjustedPix = QPixmap();
539
 
        int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large);
540
 
        thumbnail.mFullSize = size.isValid() ? size : QSize(largeGroupSize, largeGroupSize);
541
 
        thumbnail.mRealFullSize = size;
542
 
        thumbnail.mWaitingForThumbnail = false;
543
 
 
544
 
        update(thumbnail.mIndex);
545
 
}
546
 
 
547
 
 
548
 
void ThumbnailView::setBrokenThumbnail(const KFileItem& item) {
549
 
        ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url());
550
 
        if (it == d->mThumbnailForUrl.end()) {
551
 
                return;
552
 
        }
553
 
        Thumbnail& thumbnail = it.value();
554
 
        MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
555
 
        if (kind == MimeTypeUtils::KIND_VIDEO) {
556
 
                // Special case for videos because our kde install may come without
557
 
                // support for video thumbnails so we show the mimetype icon instead of
558
 
                // a broken image icon
559
 
                ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(d->mThumbnailSize);
560
 
                QPixmap pix = item.pixmap(ThumbnailGroup::pixelSize(group));
561
 
                thumbnail.initAsIcon(pix);
562
 
        } else if (kind == MimeTypeUtils::KIND_DIR) {
563
 
                // Special case for folders because ThumbnailLoadJob does not return a
564
 
                // thumbnail if there is no images
565
 
                thumbnail.mWaitingForThumbnail = false;
566
 
                return;
567
 
        } else {
568
 
                thumbnail.initAsIcon(DesktopIcon("image-missing", 48));
569
 
                thumbnail.mFullSize = thumbnail.mGroupPix.size();
570
 
        }
571
 
        update(thumbnail.mIndex);
572
 
}
573
 
 
574
 
 
575
 
QPixmap ThumbnailView::thumbnailForIndex(const QModelIndex& index, QSize* fullSize) {
576
 
        KFileItem item = fileItemForIndex(index);
577
 
        if (item.isNull()) {
578
 
                LOG("Invalid item");
579
 
                if (fullSize) {
580
 
                        *fullSize = QSize();
581
 
                }
582
 
                return QPixmap();
583
 
        }
584
 
        KUrl url = item.url();
585
 
 
586
 
        // Find or create Thumbnail instance
587
 
        ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url);
588
 
        if (it == d->mThumbnailForUrl.end()) {
589
 
                Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime));
590
 
                it = d->mThumbnailForUrl.insert(url, thumbnail);
591
 
        }
592
 
        Thumbnail& thumbnail = it.value();
593
 
 
594
 
        // If dir or archive, generate a thumbnail from fileitem pixmap
595
 
        MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
596
 
        if (kind == MimeTypeUtils::KIND_ARCHIVE || kind == MimeTypeUtils::KIND_DIR) {
597
 
                int groupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::fromPixelSize(d->mThumbnailSize));
598
 
                if (thumbnail.mGroupPix.isNull() || thumbnail.mGroupPix.width() < groupSize) {
599
 
                        QPixmap pix = item.pixmap(groupSize);
600
 
                        thumbnail.initAsIcon(pix);
601
 
                        if (kind == MimeTypeUtils::KIND_ARCHIVE) {
602
 
                                // No thumbnails for archives
603
 
                                thumbnail.mWaitingForThumbnail = false;
604
 
                        } else {
605
 
                                // set mWaitingForThumbnail to true (necessary in the case
606
 
                                // 'thumbnail' already existed before, but with a too small
607
 
                                // mGroupPix)
608
 
                                thumbnail.mWaitingForThumbnail = true;
609
 
                        }
610
 
                }
611
 
        }
612
 
 
613
 
        if (thumbnail.mGroupPix.isNull()) {
614
 
                if (fullSize) {
615
 
                        *fullSize = QSize();
616
 
                }
617
 
                return d->mWaitingThumbnail;
618
 
        }
619
 
 
620
 
        // Adjust thumbnail
621
 
        if (thumbnail.mAdjustedPix.isNull()) {
622
 
                d->roughAdjustThumbnail(&thumbnail);
623
 
        }
624
 
        if (thumbnail.mRough && !d->mSmoothThumbnailQueue.contains(url)) {
625
 
                d->mSmoothThumbnailQueue.enqueue(url);
626
 
                if (!d->mSmoothThumbnailTimer.isActive()) {
627
 
                        d->mSmoothThumbnailTimer.start(SMOOTH_DELAY);
628
 
                }
629
 
        }
630
 
        if (fullSize) {
631
 
                *fullSize = thumbnail.mRealFullSize;
632
 
        }
633
 
        return thumbnail.mAdjustedPix;
634
 
}
635
 
 
636
 
 
637
 
bool ThumbnailView::isModified(const QModelIndex& index) const {
638
 
        if (!d->mDocumentInfoProvider) {
639
 
                return false;
640
 
        }
641
 
        KUrl url = urlForIndex(index);
642
 
        return d->mDocumentInfoProvider->isModified(url);
643
 
}
644
 
 
645
 
 
646
 
bool ThumbnailView::isBusy(const QModelIndex& index) const {
647
 
        if (!d->mDocumentInfoProvider) {
648
 
                return false;
649
 
        }
650
 
        KUrl url = urlForIndex(index);
651
 
        return d->mDocumentInfoProvider->isBusy(url);
652
 
}
653
 
 
654
 
 
655
 
void ThumbnailView::startDrag(Qt::DropActions supportedActions) {
656
 
        QModelIndexList indexes = selectionModel()->selectedIndexes();
657
 
        if (indexes.isEmpty()) {
658
 
                return;
659
 
        }
660
 
        QDrag* drag = new QDrag(this);
661
 
        drag->setMimeData(model()->mimeData(indexes));
662
 
        QPixmap pix = d->createDragPixmap(indexes);
663
 
        drag->setPixmap(pix);
664
 
        drag->exec(supportedActions, Qt::CopyAction);
665
 
}
666
 
 
667
 
 
668
 
void ThumbnailView::dragEnterEvent(QDragEnterEvent* event) {
669
 
        QAbstractItemView::dragEnterEvent(event);
670
 
        if (event->mimeData()->hasUrls()) {
671
 
                event->acceptProposedAction();
672
 
        }
673
 
}
674
 
 
675
 
 
676
 
void ThumbnailView::dragMoveEvent(QDragMoveEvent* event) {
677
 
        // Necessary, otherwise we don't reach dropEvent()
678
 
        QAbstractItemView::dragMoveEvent(event);
679
 
        event->acceptProposedAction();
680
 
}
681
 
 
682
 
 
683
 
void ThumbnailView::dropEvent(QDropEvent* event) {
684
 
        const KUrl::List urlList = KUrl::List::fromMimeData(event->mimeData());
685
 
        if (urlList.isEmpty()) {
686
 
                return;
687
 
        }
688
 
 
689
 
        QModelIndex destIndex = indexAt(event->pos());
690
 
        if (destIndex.isValid()) {
691
 
                KFileItem item = fileItemForIndex(destIndex);
692
 
                if (item.isDir()) {
693
 
                        KUrl destUrl = item.url();
694
 
                        d->mThumbnailViewHelper->showMenuForUrlDroppedOnDir(this, urlList, destUrl);
695
 
                        return;
696
 
                }
697
 
        }
698
 
 
699
 
        d->mThumbnailViewHelper->showMenuForUrlDroppedOnViewport(this, urlList);
700
 
 
701
 
        event->acceptProposedAction();
702
 
}
703
 
 
704
 
 
705
 
void ThumbnailView::keyPressEvent(QKeyEvent* event) {
706
 
        QListView::keyPressEvent(event);
707
 
        if (event->key() == Qt::Key_Return) {
708
 
                const QModelIndex index = selectionModel()->currentIndex();
709
 
                if (index.isValid() && selectionModel()->selectedIndexes().count() == 1) {
710
 
                        emit indexActivated(index);
711
 
                }
712
 
        }
713
 
}
714
 
 
715
 
 
716
 
void ThumbnailView::resizeEvent(QResizeEvent* event) {
717
 
        QListView::resizeEvent(event);
718
 
        d->scheduleThumbnailGenerationForVisibleItems();
719
 
}
720
 
 
721
 
 
722
 
void ThumbnailView::showEvent(QShowEvent* event) {
723
 
        QListView::showEvent(event);
724
 
        d->scheduleThumbnailGenerationForVisibleItems();
725
 
        QTimer::singleShot(0, this, SLOT(scrollToSelectedIndex()));
726
 
}
727
 
 
728
 
 
729
 
void ThumbnailView::wheelEvent(QWheelEvent* event) {
730
 
        // If we don't adjust the single step, the wheel scroll exactly one item up
731
 
        // and down, giving the impression that the items do not move but only
732
 
        // their label changes.
733
 
        // For some reason it is necessary to set the step here: setting it in
734
 
        // setThumbnailSize() does not work
735
 
        //verticalScrollBar()->setSingleStep(d->mThumbnailSize / 5);
736
 
        if (event->modifiers() == Qt::ControlModifier) {
737
 
                int size = d->mThumbnailSize + (event->delta() > 0 ? 1 : -1) * WHEEL_ZOOM_MULTIPLIER;
738
 
                size = qMax(int(MinThumbnailSize), qMin(size, int(MaxThumbnailSize)));
739
 
                setThumbnailSize(size);
740
 
        } else {
741
 
                QListView::wheelEvent(event);
742
 
        }
743
 
}
744
 
 
745
 
 
746
 
void ThumbnailView::scrollToSelectedIndex() {
747
 
        QModelIndexList list = selectedIndexes();
748
 
        if (list.count() >= 1) {
749
 
                scrollTo(list.first(), PositionAtCenter);
750
 
        }
751
 
}
752
 
 
753
 
 
754
 
void ThumbnailView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
755
 
        QListView::selectionChanged(selected, deselected);
756
 
        emit selectionChangedSignal(selected, deselected);
757
 
}
758
 
 
759
 
 
760
 
void ThumbnailView::scrollContentsBy(int dx, int dy) {
761
 
        QListView::scrollContentsBy(dx, dy);
762
 
        d->scheduleThumbnailGenerationForVisibleItems();
763
 
}
764
 
 
765
 
 
766
 
void ThumbnailView::generateThumbnailsForVisibleItems() {
767
 
        if (!isVisible() || !model()) {
768
 
                return;
769
 
        }
770
 
        KFileItemList list;
771
 
        QRect visibleRect = viewport()->rect();
772
 
        // Adjust visibleRect so that next invisible rows of thumbnails
773
 
        // get generated too
774
 
        if (isWrapping()) {
775
 
                visibleRect.adjust(0, 0, 0, d->mThumbnailSize * 2);
776
 
        } else {
777
 
                visibleRect.adjust(0, 0, visibleRect.width() / 2, 0);
778
 
        }
779
 
 
780
 
        for (int row=0; row < model()->rowCount(); ++row) {
781
 
                QModelIndex index = model()->index(row, 0);
782
 
                KFileItem item = fileItemForIndex(index);
783
 
                QUrl url = item.url();
784
 
 
785
 
                // Filter out invisible items
786
 
                QRect rect = visualRect(index);
787
 
                if (!visibleRect.intersects(rect)) {
788
 
                        continue;
789
 
                }
790
 
 
791
 
                // Filter out archives
792
 
                MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
793
 
                if (kind == MimeTypeUtils::KIND_ARCHIVE) {
794
 
                        continue;
795
 
                }
796
 
 
797
 
                // Immediately update modified items
798
 
                if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) {
799
 
                        d->updateThumbnailForModifiedDocument(index);
800
 
                        continue;
801
 
                }
802
 
 
803
 
                // Filter out items which already have a thumbnail
804
 
                ThumbnailForUrl::ConstIterator it = d->mThumbnailForUrl.constFind(url);
805
 
                if (it != d->mThumbnailForUrl.constEnd() && it.value().isGroupPixAdaptedForSize(d->mThumbnailSize)) {
806
 
                        continue;
807
 
                }
808
 
 
809
 
                // Add the item to our list
810
 
                list << item;
811
 
 
812
 
                // Insert the thumbnail in mThumbnailForUrl, so that
813
 
                // setThumbnail() can find the item to update
814
 
                if (it == d->mThumbnailForUrl.constEnd()) {
815
 
                        Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime));
816
 
                        d->mThumbnailForUrl.insert(url, thumbnail);
817
 
                }
818
 
        }
819
 
 
820
 
        if (!list.empty()) {
821
 
                d->generateThumbnailsForItems(list);
822
 
        }
823
 
}
824
 
 
825
 
 
826
 
void ThumbnailView::updateThumbnail(const QModelIndex& index) {
827
 
        KFileItem item = fileItemForIndex(index);
828
 
        KUrl url = item.url();
829
 
        if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) {
830
 
                d->updateThumbnailForModifiedDocument(index);
831
 
        } else {
832
 
                KFileItemList list;
833
 
                list << item;
834
 
                d->generateThumbnailsForItems(list);
835
 
        }
836
 
}
837
 
 
838
 
 
839
 
void ThumbnailView::updateThumbnailBusyState(const QModelIndex& _index, bool busy) {
840
 
        QPersistentModelIndex index(_index);
841
 
        if (busy && !d->mBusyIndexSet.contains(index)) {
842
 
                d->mBusyIndexSet << index;
843
 
                update(index);
844
 
                if (d->mBusyAnimationTimeLine->state() != QTimeLine::Running) {
845
 
                        d->mBusyAnimationTimeLine->start();
846
 
                }
847
 
        } else if (!busy && d->mBusyIndexSet.remove(index)) {
848
 
                update(index);
849
 
                if (d->mBusyIndexSet.isEmpty()) {
850
 
                        d->mBusyAnimationTimeLine->stop();
851
 
                }
852
 
        }
853
 
}
854
 
 
855
 
 
856
 
void ThumbnailView::updateBusyIndexes() {
857
 
        Q_FOREACH(const QPersistentModelIndex& index, d->mBusyIndexSet) {
858
 
                update(index);
859
 
        }
860
 
}
861
 
 
862
 
 
863
 
QPixmap ThumbnailView::busySequenceCurrentPixmap() const {
864
 
        return d->mBusySequence.frameAt(d->mBusyAnimationTimeLine->currentFrame());
865
 
}
866
 
 
867
 
 
868
 
void ThumbnailView::smoothNextThumbnail() {
869
 
        if (d->mSmoothThumbnailQueue.isEmpty()) {
870
 
                return;
871
 
        }
872
 
 
873
 
        if (d->mThumbnailLoadJob) {
874
 
                // give mThumbnailLoadJob priority over smoothing
875
 
                d->mSmoothThumbnailTimer.start(SMOOTH_DELAY);
876
 
                return;
877
 
        }
878
 
 
879
 
        KUrl url = d->mSmoothThumbnailQueue.dequeue();
880
 
        ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url);
881
 
        if (it == d->mThumbnailForUrl.end()) {
882
 
                kWarning() <<  url << " not in mThumbnailForUrl. This should not happen!";
883
 
                return;
884
 
        }
885
 
 
886
 
        Thumbnail& thumbnail = it.value();
887
 
        thumbnail.mAdjustedPix = thumbnail.mGroupPix.scaled(d->mThumbnailSize, d->mThumbnailSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
888
 
        thumbnail.mRough = false;
889
 
 
890
 
        if (thumbnail.mIndex.isValid()) {
891
 
                update(thumbnail.mIndex);
892
 
        } else {
893
 
                kWarning() << "index for" << url << "is invalid. This should not happen!";
894
 
        }
895
 
 
896
 
        if (!d->mSmoothThumbnailQueue.isEmpty()) {
897
 
                d->mSmoothThumbnailTimer.start(0);
898
 
        }
899
 
}
900
 
 
901
 
 
902
 
void ThumbnailView::reloadThumbnail(const QModelIndex& index) {
903
 
        KUrl url = urlForIndex(index);
904
 
        if (!url.isValid()) {
905
 
                kWarning() << "Invalid url for index" << index;
906
 
                return;
907
 
        }
908
 
        ThumbnailLoadJob::deleteImageThumbnail(url);
909
 
        ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url);
910
 
        if (it == d->mThumbnailForUrl.end()) {
911
 
                return;
912
 
        }
913
 
        d->mThumbnailForUrl.erase(it);
914
 
        generateThumbnailsForVisibleItems();
915
 
}
916
 
 
 
308
, d(new ThumbnailViewPrivate)
 
309
{
 
310
    d->q = this;
 
311
    d->mThumbnailViewHelper = 0;
 
312
    d->mDocumentInfoProvider = 0;
 
313
    // Init to some stupid value so that the first call to setThumbnailSize()
 
314
    // is not ignored (do not use 0 in case someone try to divide by
 
315
    // mThumbnailSize...)
 
316
    d->mThumbnailSize = 1;
 
317
 
 
318
    setFrameShape(QFrame::NoFrame);
 
319
    setViewMode(QListView::IconMode);
 
320
    setResizeMode(QListView::Adjust);
 
321
    setDragEnabled(true);
 
322
    setAcceptDrops(true);
 
323
    setDropIndicatorShown(true);
 
324
    setUniformItemSizes(true);
 
325
    setEditTriggers(QAbstractItemView::EditKeyPressed);
 
326
 
 
327
    d->setupBusyAnimation();
 
328
 
 
329
    viewport()->setMouseTracking(true);
 
330
    // Set this attribute, otherwise the item delegate won't get the
 
331
    // State_MouseOver state
 
332
    viewport()->setAttribute(Qt::WA_Hover);
 
333
 
 
334
    setVerticalScrollMode(ScrollPerPixel);
 
335
    setHorizontalScrollMode(ScrollPerPixel);
 
336
 
 
337
    d->mScheduledThumbnailGenerationTimer.setSingleShot(true);
 
338
    d->mScheduledThumbnailGenerationTimer.setInterval(500);
 
339
    connect(&d->mScheduledThumbnailGenerationTimer, SIGNAL(timeout()),
 
340
            SLOT(generateThumbnailsForVisibleItems()));
 
341
 
 
342
    d->mSmoothThumbnailTimer.setSingleShot(true);
 
343
    connect(&d->mSmoothThumbnailTimer, SIGNAL(timeout()),
 
344
            SLOT(smoothNextThumbnail()));
 
345
 
 
346
    setContextMenuPolicy(Qt::CustomContextMenu);
 
347
    connect(this, SIGNAL(customContextMenuRequested(QPoint)),
 
348
            SLOT(showContextMenu()));
 
349
 
 
350
    if (KGlobalSettings::singleClick()) {
 
351
        connect(this, SIGNAL(clicked(QModelIndex)),
 
352
                SLOT(emitIndexActivatedIfNoModifiers(QModelIndex)));
 
353
    } else {
 
354
        connect(this, SIGNAL(doubleClicked(QModelIndex)),
 
355
                SLOT(emitIndexActivatedIfNoModifiers(QModelIndex)));
 
356
    }
 
357
}
 
358
 
 
359
ThumbnailView::~ThumbnailView()
 
360
{
 
361
    delete d;
 
362
}
 
363
 
 
364
void ThumbnailView::setModel(QAbstractItemModel* newModel)
 
365
{
 
366
    if (model()) {
 
367
        disconnect(model(), 0, this, 0);
 
368
    }
 
369
    QListView::setModel(newModel);
 
370
    connect(model(), SIGNAL(rowsRemoved(QModelIndex, int, int)),
 
371
            SIGNAL(rowsRemovedSignal(QModelIndex, int, int)));
 
372
}
 
373
 
 
374
void ThumbnailView::setThumbnailSize(int value)
 
375
{
 
376
    if (d->mThumbnailSize == value) {
 
377
        return;
 
378
    }
 
379
    d->mThumbnailSize = value;
 
380
 
 
381
    // mWaitingThumbnail
 
382
    int waitingThumbnailSize;
 
383
    if (value > 64) {
 
384
        waitingThumbnailSize = 48;
 
385
    } else {
 
386
        waitingThumbnailSize = 32;
 
387
    }
 
388
    if (d->mWaitingThumbnail.width() != waitingThumbnailSize) {
 
389
        QPixmap icon = DesktopIcon("chronometer", waitingThumbnailSize);
 
390
        QPixmap pix(icon.size());
 
391
        pix.fill(Qt::transparent);
 
392
        QPainter painter(&pix);
 
393
        painter.setOpacity(0.5);
 
394
        painter.drawPixmap(0, 0, icon);
 
395
        painter.end();
 
396
        d->mWaitingThumbnail = pix;
 
397
    }
 
398
 
 
399
    // Stop smoothing
 
400
    d->mSmoothThumbnailTimer.stop();
 
401
    d->mSmoothThumbnailQueue.clear();
 
402
 
 
403
    // Clear adjustedPixes
 
404
    ThumbnailForUrl::iterator
 
405
    it = d->mThumbnailForUrl.begin(),
 
406
    end = d->mThumbnailForUrl.end();
 
407
    for (; it != end; ++it) {
 
408
        it.value().mAdjustedPix = QPixmap();
 
409
    }
 
410
 
 
411
    thumbnailSizeChanged(value);
 
412
    d->scheduleThumbnailGenerationForVisibleItems();
 
413
}
 
414
 
 
415
int ThumbnailView::thumbnailSize() const
 
416
{
 
417
    return d->mThumbnailSize;
 
418
}
 
419
 
 
420
void ThumbnailView::setThumbnailViewHelper(AbstractThumbnailViewHelper* helper)
 
421
{
 
422
    d->mThumbnailViewHelper = helper;
 
423
}
 
424
 
 
425
AbstractThumbnailViewHelper* ThumbnailView::thumbnailViewHelper() const
 
426
{
 
427
    return d->mThumbnailViewHelper;
 
428
}
 
429
 
 
430
void ThumbnailView::setDocumentInfoProvider(AbstractDocumentInfoProvider* provider)
 
431
{
 
432
    d->mDocumentInfoProvider = provider;
 
433
    if (provider) {
 
434
        connect(provider, SIGNAL(busyStateChanged(QModelIndex, bool)),
 
435
                SLOT(updateThumbnailBusyState(QModelIndex, bool)));
 
436
        connect(provider, SIGNAL(documentChanged(QModelIndex)),
 
437
                SLOT(updateThumbnail(QModelIndex)));
 
438
    }
 
439
}
 
440
 
 
441
AbstractDocumentInfoProvider* ThumbnailView::documentInfoProvider() const
 
442
{
 
443
    return d->mDocumentInfoProvider;
 
444
}
 
445
 
 
446
void ThumbnailView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
 
447
{
 
448
    QListView::rowsAboutToBeRemoved(parent, start, end);
 
449
 
 
450
    // Remove references to removed items
 
451
    KFileItemList itemList;
 
452
    for (int pos = start; pos <= end; ++pos) {
 
453
        QModelIndex index = model()->index(pos, 0, parent);
 
454
        KFileItem item = fileItemForIndex(index);
 
455
        if (item.isNull()) {
 
456
            kDebug() << "Skipping invalid item!" << index.data().toString();
 
457
            continue;
 
458
        }
 
459
 
 
460
        QUrl url = item.url();
 
461
        d->mThumbnailForUrl.remove(url);
 
462
        d->mSmoothThumbnailQueue.removeAll(url);
 
463
 
 
464
        itemList.append(item);
 
465
    }
 
466
 
 
467
    if (d->mThumbnailLoadJob) {
 
468
        d->mThumbnailLoadJob->removeItems(itemList);
 
469
    }
 
470
 
 
471
    // Update current index if it is among the deleted rows
 
472
    const int row = currentIndex().row();
 
473
    if (start <= row && row <= end) {
 
474
        QModelIndex index;
 
475
        if (end < model()->rowCount() - 1) {
 
476
            index = model()->index(end + 1, 0);
 
477
        } else if (start > 0) {
 
478
            index = model()->index(start - 1, 0);
 
479
        }
 
480
        setCurrentIndex(index);
 
481
    }
 
482
 
 
483
    // Removing rows might make new images visible, make sure their thumbnail
 
484
    // is generated
 
485
    d->mScheduledThumbnailGenerationTimer.start();
 
486
}
 
487
 
 
488
void ThumbnailView::rowsInserted(const QModelIndex& parent, int start, int end)
 
489
{
 
490
    QListView::rowsInserted(parent, start, end);
 
491
    d->mScheduledThumbnailGenerationTimer.start();
 
492
    rowsInsertedSignal(parent, start, end);
 
493
}
 
494
 
 
495
void ThumbnailView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
 
496
{
 
497
    QListView::dataChanged(topLeft, bottomRight);
 
498
    bool thumbnailsNeedRefresh = false;
 
499
    for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
 
500
        QModelIndex index = model()->index(row, 0);
 
501
        KFileItem item = fileItemForIndex(index);
 
502
        if (item.isNull()) {
 
503
            kWarning() << "Invalid item for index" << index << ". This should not happen!";
 
504
            continue;
 
505
        }
 
506
 
 
507
        ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(item.url());
 
508
        if (it != d->mThumbnailForUrl.end()) {
 
509
            // All thumbnail views are connected to the model, so
 
510
            // ThumbnailView::dataChanged() is called for all of them. As a
 
511
            // result this method will also be called for views which are not
 
512
            // currently visible, and do not yet have a thumbnail for the
 
513
            // modified url.
 
514
            KDateTime mtime = item.time(KFileItem::ModificationTime);
 
515
            if (it->mModificationTime != mtime) {
 
516
                // dataChanged() is called when the file changes but also when
 
517
                // the model fetched additional data such as semantic info. To
 
518
                // avoid needless refreshes, we only trigger a refresh if the
 
519
                // modification time changes.
 
520
                thumbnailsNeedRefresh = true;
 
521
                it->prepareForRefresh(mtime);
 
522
            }
 
523
        }
 
524
    }
 
525
    if (thumbnailsNeedRefresh) {
 
526
        d->mScheduledThumbnailGenerationTimer.start();
 
527
    }
 
528
}
 
529
 
 
530
void ThumbnailView::showContextMenu()
 
531
{
 
532
    d->mThumbnailViewHelper->showContextMenu(this);
 
533
}
 
534
 
 
535
void ThumbnailView::emitIndexActivatedIfNoModifiers(const QModelIndex& index)
 
536
{
 
537
    if (QApplication::keyboardModifiers() == Qt::NoModifier) {
 
538
        emit indexActivated(index);
 
539
    }
 
540
}
 
541
 
 
542
void ThumbnailView::setThumbnail(const KFileItem& item, const QPixmap& pixmap, const QSize& size)
 
543
{
 
544
    ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url());
 
545
    if (it == d->mThumbnailForUrl.end()) {
 
546
        return;
 
547
    }
 
548
    Thumbnail& thumbnail = it.value();
 
549
    thumbnail.mGroupPix = pixmap;
 
550
    thumbnail.mAdjustedPix = QPixmap();
 
551
    int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large);
 
552
    thumbnail.mFullSize = size.isValid() ? size : QSize(largeGroupSize, largeGroupSize);
 
553
    thumbnail.mRealFullSize = size;
 
554
    thumbnail.mWaitingForThumbnail = false;
 
555
 
 
556
    update(thumbnail.mIndex);
 
557
}
 
558
 
 
559
void ThumbnailView::setBrokenThumbnail(const KFileItem& item)
 
560
{
 
561
    ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url());
 
562
    if (it == d->mThumbnailForUrl.end()) {
 
563
        return;
 
564
    }
 
565
    Thumbnail& thumbnail = it.value();
 
566
    MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
 
567
    if (kind == MimeTypeUtils::KIND_VIDEO) {
 
568
        // Special case for videos because our kde install may come without
 
569
        // support for video thumbnails so we show the mimetype icon instead of
 
570
        // a broken image icon
 
571
        ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(d->mThumbnailSize);
 
572
        QPixmap pix = item.pixmap(ThumbnailGroup::pixelSize(group));
 
573
        thumbnail.initAsIcon(pix);
 
574
    } else if (kind == MimeTypeUtils::KIND_DIR) {
 
575
        // Special case for folders because ThumbnailLoadJob does not return a
 
576
        // thumbnail if there is no images
 
577
        thumbnail.mWaitingForThumbnail = false;
 
578
        return;
 
579
    } else {
 
580
        thumbnail.initAsIcon(DesktopIcon("image-missing", 48));
 
581
        thumbnail.mFullSize = thumbnail.mGroupPix.size();
 
582
    }
 
583
    update(thumbnail.mIndex);
 
584
}
 
585
 
 
586
QPixmap ThumbnailView::thumbnailForIndex(const QModelIndex& index, QSize* fullSize)
 
587
{
 
588
    KFileItem item = fileItemForIndex(index);
 
589
    if (item.isNull()) {
 
590
        LOG("Invalid item");
 
591
        if (fullSize) {
 
592
            *fullSize = QSize();
 
593
        }
 
594
        return QPixmap();
 
595
    }
 
596
    KUrl url = item.url();
 
597
 
 
598
    // Find or create Thumbnail instance
 
599
    ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url);
 
600
    if (it == d->mThumbnailForUrl.end()) {
 
601
        Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime));
 
602
        it = d->mThumbnailForUrl.insert(url, thumbnail);
 
603
    }
 
604
    Thumbnail& thumbnail = it.value();
 
605
 
 
606
    // If dir or archive, generate a thumbnail from fileitem pixmap
 
607
    MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
 
608
    if (kind == MimeTypeUtils::KIND_ARCHIVE || kind == MimeTypeUtils::KIND_DIR) {
 
609
        int groupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::fromPixelSize(d->mThumbnailSize));
 
610
        if (thumbnail.mGroupPix.isNull() || thumbnail.mGroupPix.width() < groupSize) {
 
611
            QPixmap pix = item.pixmap(groupSize);
 
612
            thumbnail.initAsIcon(pix);
 
613
            if (kind == MimeTypeUtils::KIND_ARCHIVE) {
 
614
                // No thumbnails for archives
 
615
                thumbnail.mWaitingForThumbnail = false;
 
616
            } else {
 
617
                // set mWaitingForThumbnail to true (necessary in the case
 
618
                // 'thumbnail' already existed before, but with a too small
 
619
                // mGroupPix)
 
620
                thumbnail.mWaitingForThumbnail = true;
 
621
            }
 
622
        }
 
623
    }
 
624
 
 
625
    if (thumbnail.mGroupPix.isNull()) {
 
626
        if (fullSize) {
 
627
            *fullSize = QSize();
 
628
        }
 
629
        return d->mWaitingThumbnail;
 
630
    }
 
631
 
 
632
    // Adjust thumbnail
 
633
    if (thumbnail.mAdjustedPix.isNull()) {
 
634
        d->roughAdjustThumbnail(&thumbnail);
 
635
    }
 
636
    if (thumbnail.mRough && !d->mSmoothThumbnailQueue.contains(url)) {
 
637
        d->mSmoothThumbnailQueue.enqueue(url);
 
638
        if (!d->mSmoothThumbnailTimer.isActive()) {
 
639
            d->mSmoothThumbnailTimer.start(SMOOTH_DELAY);
 
640
        }
 
641
    }
 
642
    if (fullSize) {
 
643
        *fullSize = thumbnail.mRealFullSize;
 
644
    }
 
645
    return thumbnail.mAdjustedPix;
 
646
}
 
647
 
 
648
bool ThumbnailView::isModified(const QModelIndex& index) const
 
649
{
 
650
    if (!d->mDocumentInfoProvider) {
 
651
        return false;
 
652
    }
 
653
    KUrl url = urlForIndex(index);
 
654
    return d->mDocumentInfoProvider->isModified(url);
 
655
}
 
656
 
 
657
bool ThumbnailView::isBusy(const QModelIndex& index) const
 
658
{
 
659
    if (!d->mDocumentInfoProvider) {
 
660
        return false;
 
661
    }
 
662
    KUrl url = urlForIndex(index);
 
663
    return d->mDocumentInfoProvider->isBusy(url);
 
664
}
 
665
 
 
666
void ThumbnailView::startDrag(Qt::DropActions supportedActions)
 
667
{
 
668
    QModelIndexList indexes = selectionModel()->selectedIndexes();
 
669
    if (indexes.isEmpty()) {
 
670
        return;
 
671
    }
 
672
    QDrag* drag = new QDrag(this);
 
673
    drag->setMimeData(model()->mimeData(indexes));
 
674
    QPixmap pix = d->createDragPixmap(indexes);
 
675
    drag->setPixmap(pix);
 
676
    drag->exec(supportedActions, Qt::CopyAction);
 
677
}
 
678
 
 
679
void ThumbnailView::dragEnterEvent(QDragEnterEvent* event)
 
680
{
 
681
    QAbstractItemView::dragEnterEvent(event);
 
682
    if (event->mimeData()->hasUrls()) {
 
683
        event->acceptProposedAction();
 
684
    }
 
685
}
 
686
 
 
687
void ThumbnailView::dragMoveEvent(QDragMoveEvent* event)
 
688
{
 
689
    // Necessary, otherwise we don't reach dropEvent()
 
690
    QAbstractItemView::dragMoveEvent(event);
 
691
    event->acceptProposedAction();
 
692
}
 
693
 
 
694
void ThumbnailView::dropEvent(QDropEvent* event)
 
695
{
 
696
    const KUrl::List urlList = KUrl::List::fromMimeData(event->mimeData());
 
697
    if (urlList.isEmpty()) {
 
698
        return;
 
699
    }
 
700
 
 
701
    QModelIndex destIndex = indexAt(event->pos());
 
702
    if (destIndex.isValid()) {
 
703
        KFileItem item = fileItemForIndex(destIndex);
 
704
        if (item.isDir()) {
 
705
            KUrl destUrl = item.url();
 
706
            d->mThumbnailViewHelper->showMenuForUrlDroppedOnDir(this, urlList, destUrl);
 
707
            return;
 
708
        }
 
709
    }
 
710
 
 
711
    d->mThumbnailViewHelper->showMenuForUrlDroppedOnViewport(this, urlList);
 
712
 
 
713
    event->acceptProposedAction();
 
714
}
 
715
 
 
716
void ThumbnailView::keyPressEvent(QKeyEvent* event)
 
717
{
 
718
    QListView::keyPressEvent(event);
 
719
    if (event->key() == Qt::Key_Return) {
 
720
        const QModelIndex index = selectionModel()->currentIndex();
 
721
        if (index.isValid() && selectionModel()->selectedIndexes().count() == 1) {
 
722
            emit indexActivated(index);
 
723
        }
 
724
    }
 
725
}
 
726
 
 
727
void ThumbnailView::resizeEvent(QResizeEvent* event)
 
728
{
 
729
    QListView::resizeEvent(event);
 
730
    d->scheduleThumbnailGenerationForVisibleItems();
 
731
}
 
732
 
 
733
void ThumbnailView::showEvent(QShowEvent* event)
 
734
{
 
735
    QListView::showEvent(event);
 
736
    d->scheduleThumbnailGenerationForVisibleItems();
 
737
    QTimer::singleShot(0, this, SLOT(scrollToSelectedIndex()));
 
738
}
 
739
 
 
740
void ThumbnailView::wheelEvent(QWheelEvent* event)
 
741
{
 
742
    // If we don't adjust the single step, the wheel scroll exactly one item up
 
743
    // and down, giving the impression that the items do not move but only
 
744
    // their label changes.
 
745
    // For some reason it is necessary to set the step here: setting it in
 
746
    // setThumbnailSize() does not work
 
747
    //verticalScrollBar()->setSingleStep(d->mThumbnailSize / 5);
 
748
    if (event->modifiers() == Qt::ControlModifier) {
 
749
        int size = d->mThumbnailSize + (event->delta() > 0 ? 1 : -1) * WHEEL_ZOOM_MULTIPLIER;
 
750
        size = qMax(int(MinThumbnailSize), qMin(size, int(MaxThumbnailSize)));
 
751
        setThumbnailSize(size);
 
752
    } else {
 
753
        QListView::wheelEvent(event);
 
754
    }
 
755
}
 
756
 
 
757
void ThumbnailView::scrollToSelectedIndex()
 
758
{
 
759
    QModelIndexList list = selectedIndexes();
 
760
    if (list.count() >= 1) {
 
761
        scrollTo(list.first(), PositionAtCenter);
 
762
    }
 
763
}
 
764
 
 
765
void ThumbnailView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
 
766
{
 
767
    QListView::selectionChanged(selected, deselected);
 
768
    emit selectionChangedSignal(selected, deselected);
 
769
}
 
770
 
 
771
void ThumbnailView::scrollContentsBy(int dx, int dy)
 
772
{
 
773
    QListView::scrollContentsBy(dx, dy);
 
774
    d->scheduleThumbnailGenerationForVisibleItems();
 
775
}
 
776
 
 
777
void ThumbnailView::generateThumbnailsForVisibleItems()
 
778
{
 
779
    if (!isVisible() || !model()) {
 
780
        return;
 
781
    }
 
782
    KFileItemList list;
 
783
    QRect visibleRect = viewport()->rect();
 
784
    // Adjust visibleRect so that next thumbnail page|row
 
785
    // get generated too
 
786
    if (isWrapping()) {
 
787
        visibleRect.setHeight(visibleRect.height() * 2);
 
788
    } else {
 
789
        visibleRect.setWidth(visibleRect.width() * 2);
 
790
    }
 
791
 
 
792
    for (int row = 0; row < model()->rowCount(); ++row) {
 
793
        QModelIndex index = model()->index(row, 0);
 
794
        KFileItem item = fileItemForIndex(index);
 
795
        QUrl url = item.url();
 
796
 
 
797
        // Filter out invisible items
 
798
        QRect rect = visualRect(index);
 
799
        if (!visibleRect.intersects(rect)) {
 
800
            continue;
 
801
        }
 
802
 
 
803
        // Filter out archives
 
804
        MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
 
805
        if (kind == MimeTypeUtils::KIND_ARCHIVE) {
 
806
            continue;
 
807
        }
 
808
 
 
809
        // Immediately update modified items
 
810
        if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) {
 
811
            d->updateThumbnailForModifiedDocument(index);
 
812
            continue;
 
813
        }
 
814
 
 
815
        // Filter out items which already have a thumbnail
 
816
        ThumbnailForUrl::ConstIterator it = d->mThumbnailForUrl.constFind(url);
 
817
        if (it != d->mThumbnailForUrl.constEnd() && it.value().isGroupPixAdaptedForSize(d->mThumbnailSize)) {
 
818
            continue;
 
819
        }
 
820
 
 
821
        // Add the item to our list
 
822
        list << item;
 
823
 
 
824
        // Insert the thumbnail in mThumbnailForUrl, so that
 
825
        // setThumbnail() can find the item to update
 
826
        if (it == d->mThumbnailForUrl.constEnd()) {
 
827
            Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime));
 
828
            d->mThumbnailForUrl.insert(url, thumbnail);
 
829
        }
 
830
    }
 
831
 
 
832
    if (!list.empty()) {
 
833
        d->generateThumbnailsForItems(list);
 
834
    }
 
835
}
 
836
 
 
837
void ThumbnailView::updateThumbnail(const QModelIndex& index)
 
838
{
 
839
    KFileItem item = fileItemForIndex(index);
 
840
    KUrl url = item.url();
 
841
    if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) {
 
842
        d->updateThumbnailForModifiedDocument(index);
 
843
    } else {
 
844
        KFileItemList list;
 
845
        list << item;
 
846
        d->generateThumbnailsForItems(list);
 
847
    }
 
848
}
 
849
 
 
850
void ThumbnailView::updateThumbnailBusyState(const QModelIndex& _index, bool busy)
 
851
{
 
852
    QPersistentModelIndex index(_index);
 
853
    if (busy && !d->mBusyIndexSet.contains(index)) {
 
854
        d->mBusyIndexSet << index;
 
855
        update(index);
 
856
        if (d->mBusyAnimationTimeLine->state() != QTimeLine::Running) {
 
857
            d->mBusyAnimationTimeLine->start();
 
858
        }
 
859
    } else if (!busy && d->mBusyIndexSet.remove(index)) {
 
860
        update(index);
 
861
        if (d->mBusyIndexSet.isEmpty()) {
 
862
            d->mBusyAnimationTimeLine->stop();
 
863
        }
 
864
    }
 
865
}
 
866
 
 
867
void ThumbnailView::updateBusyIndexes()
 
868
{
 
869
    Q_FOREACH(const QPersistentModelIndex & index, d->mBusyIndexSet) {
 
870
        update(index);
 
871
    }
 
872
}
 
873
 
 
874
QPixmap ThumbnailView::busySequenceCurrentPixmap() const
 
875
{
 
876
    return d->mBusySequence.frameAt(d->mBusyAnimationTimeLine->currentFrame());
 
877
}
 
878
 
 
879
void ThumbnailView::smoothNextThumbnail()
 
880
{
 
881
    if (d->mSmoothThumbnailQueue.isEmpty()) {
 
882
        return;
 
883
    }
 
884
 
 
885
    if (d->mThumbnailLoadJob) {
 
886
        // give mThumbnailLoadJob priority over smoothing
 
887
        d->mSmoothThumbnailTimer.start(SMOOTH_DELAY);
 
888
        return;
 
889
    }
 
890
 
 
891
    KUrl url = d->mSmoothThumbnailQueue.dequeue();
 
892
    ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url);
 
893
    if (it == d->mThumbnailForUrl.end()) {
 
894
        kWarning() <<  url << " not in mThumbnailForUrl. This should not happen!";
 
895
        return;
 
896
    }
 
897
 
 
898
    Thumbnail& thumbnail = it.value();
 
899
    thumbnail.mAdjustedPix = thumbnail.mGroupPix.scaled(d->mThumbnailSize, d->mThumbnailSize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
 
900
    thumbnail.mRough = false;
 
901
 
 
902
    if (thumbnail.mIndex.isValid()) {
 
903
        update(thumbnail.mIndex);
 
904
    } else {
 
905
        kWarning() << "index for" << url << "is invalid. This should not happen!";
 
906
    }
 
907
 
 
908
    if (!d->mSmoothThumbnailQueue.isEmpty()) {
 
909
        d->mSmoothThumbnailTimer.start(0);
 
910
    }
 
911
}
 
912
 
 
913
void ThumbnailView::reloadThumbnail(const QModelIndex& index)
 
914
{
 
915
    KUrl url = urlForIndex(index);
 
916
    if (!url.isValid()) {
 
917
        kWarning() << "Invalid url for index" << index;
 
918
        return;
 
919
    }
 
920
    ThumbnailLoadJob::deleteImageThumbnail(url);
 
921
    ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url);
 
922
    if (it == d->mThumbnailForUrl.end()) {
 
923
        return;
 
924
    }
 
925
    d->mThumbnailForUrl.erase(it);
 
926
    generateThumbnailsForVisibleItems();
 
927
}
917
928
 
918
929
} // namespace