#include <QAbstractItemView>
#include <QClipboard>
#include <QColor>
+#include <QListView>
#include <QPainter>
#include <QScrollBar>
#include <QIcon>
+/**
+ * If the passed item view is an instance of QListView, expensive
+ * layout operations are blocked in the constructor and are unblocked
+ * again in the destructor.
+ *
+ * This helper class is a workaround for the following huge performance
+ * problem when having directories with several 1000 items:
+ * - each change of an icon emits a dataChanged() signal from the model
+ * - QListView iterates through all items on each dataChanged() signal
+ * and invokes QItemDelegate::sizeHint()
+ * - the sizeHint() implementation of KFileItemDelegate is quite complex,
+ * invoking it 1000 times for each icon change might block the UI
+ *
+ * QListView does not invoke QItemDelegate::sizeHint() when the
+ * uniformItemSize property has been set to true, so this property is
+ * set before exchanging a block of icons. It is important to reset
+ * it again before the event loop is entered, otherwise QListView
+ * would not get the correct size hints after dispatching the layoutChanged()
+ * signal.
+ */
+class LayoutBlocker {
+public:
+ LayoutBlocker(QAbstractItemView* view) :
+ m_uniformSizes(false),
+ m_view(qobject_cast<QListView*>(view))
+ {
+ if (m_view != 0) {
+ m_uniformSizes = m_view->uniformItemSizes();
+ m_view->setUniformItemSizes(true);
+ }
+ }
+
+ ~LayoutBlocker()
+ {
+ if (m_view != 0) {
+ m_view->setUniformItemSizes(m_uniformSizes);
+ }
+ }
+
+private:
+ bool m_uniformSizes;
+ QListView* m_view;
+};
+
IconManager::IconManager(QAbstractItemView* parent, DolphinSortFilterProxyModel* model) :
QObject(parent),
m_showPreview(false),
m_clearItemQueues(true),
+ m_pendingVisiblePreviews(0),
m_view(parent),
m_previewTimer(0),
m_scrollAreaTimer(0),
if ((m_previewJobs.count() == 0) && m_clearItemQueues) {
m_pendingItems.clear();
m_dispatchedItems.clear();
+ m_pendingVisiblePreviews = 0;
+ QMetaObject::invokeMethod(this, "dispatchPreviewQueue", Qt::QueuedConnection);
}
}
void IconManager::dispatchPreviewQueue()
{
- int previewsCount = m_previews.count();
+ const int previewsCount = m_previews.count();
if (previewsCount > 0) {
// Applying the previews to the model must be done step by step
// in larger blocks: Applying a preview immediately when getting the signal
// 'gotPreview()' from the PreviewJob is too expensive, as a relayout
// of the view would be triggered for each single preview.
-
- int dispatchCount = 30;
- if (dispatchCount > previewsCount) {
- dispatchCount = previewsCount;
- }
-
- for (int i = 0; i < dispatchCount; ++i) {
+ LayoutBlocker blocker(m_view);
+ for (int i = 0; i < previewsCount; ++i) {
const ItemInfo& preview = m_previews.first();
replaceIcon(preview.url, preview.pixmap);
m_previews.pop_front();
+ if (m_pendingVisiblePreviews > 0) {
+ --m_pendingVisiblePreviews;
+ }
}
-
- previewsCount = m_previews.count();
}
- const bool workingPreviewJobs = (m_previewJobs.count() > 0);
- if (workingPreviewJobs) {
- // poll for previews as long as not all preview jobs are finished
+ if (m_pendingVisiblePreviews > 0) {
+ // As long as there are pending previews for visible items, poll
+ // the preview queue each 200 ms. If there are no pending previews,
+ // the queue is dispatched in slotPreviewJobFinished().
m_previewTimer->start(200);
- } else if (previewsCount > 0) {
- // all preview jobs are finished but there are still pending previews
- // in the queue -> poll more aggressively
- m_previewTimer->start(10);
}
}
// generated earlier.
items.removeAt(index);
items.insert(0, item);
+ ++m_pendingVisiblePreviews;
}
}
} else {
// generated earlier.
items.insert(0, items[i]);
items.removeAt(i + 1);
+ ++m_pendingVisiblePreviews;
}
}
}