X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/57179872959ea898e4e640b5392acd13f4951202..6861a876830e301878b65cb4e4574bfda4c73340:/src/iconmanager.cpp diff --git a/src/iconmanager.cpp b/src/iconmanager.cpp index b7d75b037..97dac53b7 100644 --- a/src/iconmanager.cpp +++ b/src/iconmanager.cpp @@ -32,10 +32,55 @@ #include #include #include +#include #include #include #include +/** + * 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(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), @@ -215,6 +260,7 @@ void IconManager::dispatchPreviewQueue() dispatchCount = previewsCount; } + LayoutBlocker blocker(m_view); for (int i = 0; i < dispatchCount; ++i) { const ItemInfo& preview = m_previews.first(); replaceIcon(preview.url, preview.pixmap); @@ -486,37 +532,60 @@ void IconManager::orderItems(KFileItemList& items) // Order the items in a way that the preview for the visible items // is generated first, as this improves the feeled performance a lot. // - // Implementation note: using KDirModel::itemForUrl() would lead to a more - // readable code, but it is slower as iterating all model indicess - // and checking whether the index is part of 'items'. + // Implementation note: 2 different algorithms are used for the sorting. + // Algorithm 1 is faster when having a lot of items in comparison + // to the number of rows in the model. Algorithm 2 is faster + // when having quite less items in comparison to the number of rows in + // the model. Choosing the right algorithm is important when having directories + // with several hundreds or thousands of items. const int itemCount = items.count(); - const QRect visibleArea = m_view->viewport()->rect(); - const int rowCount = m_proxyModel->rowCount(); - for (int row = 0; row < rowCount; ++row) { - const QModelIndex proxyIndex = m_proxyModel->index(row, 0); - const QRect itemRect = m_view->visualRect(proxyIndex); - const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex); + const QRect visibleArea = m_view->viewport()->rect(); - KFileItem item = m_dolphinModel->itemForIndex(dirIndex); // O(1) - const KUrl url = item.url(); + if (itemCount * 10 > rowCount) { + // Algorithm 1: The number of items is > 10 % of the row count. Parse all rows + // and check whether the received row is part of the item list. + for (int row = 0; row < rowCount; ++row) { + const QModelIndex proxyIndex = m_proxyModel->index(row, 0); + const QRect itemRect = m_view->visualRect(proxyIndex); + const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex); + + KFileItem item = m_dolphinModel->itemForIndex(dirIndex); // O(1) + const KUrl url = item.url(); + + // check whether the item is part of the item list 'items' + int index = -1; + for (int i = 0; i < itemCount; ++i) { + if (items[i].url() == url) { + index = i; + break; + } + } - // check whether the item is part of the item list 'items' - int index = -1; - for (int i = 0; i < itemCount; ++i) { - if (items[i].url() == url) { - index = i; - break; + if ((index > 0) && itemRect.intersects(visibleArea)) { + // The current item is (at least partly) visible. Move it + // to the front of the list, so that the preview is + // generated earlier. + items.removeAt(index); + items.insert(0, item); } } - - if ((index > 0) && itemRect.intersects(visibleArea)) { - // The current item is (at least partly) visible. Move it - // to the front of the list, so that the preview is - // generated earlier. - items.removeAt(index); - items.insert(0, item); + } else { + // Algorithm 2: The number of items is <= 10 % of the row count. In this case iterate + // all items and receive the corresponding row from the item. + for (int i = 0; i < itemCount; ++i) { + const QModelIndex dirIndex = m_dolphinModel->indexForItem(items[i]); // O(n) (n = number of rows) + const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); + const QRect itemRect = m_view->visualRect(proxyIndex); + + if (itemRect.intersects(visibleArea)) { + // The current item is (at least partly) visible. Move it + // to the front of the list, so that the preview is + // generated earlier. + items.insert(0, items[i]); + items.removeAt(i + 1); + } } } }