]> cloud.milkyroute.net Git - dolphin.git/commitdiff
New and powerful KListView. Still pending class renaming. There are two
authorRafael Fernández López <ereslibre@kde.org>
Sun, 17 Jun 2007 15:32:31 +0000 (15:32 +0000)
committerRafael Fernández López <ereslibre@kde.org>
Sun, 17 Jun 2007 15:32:31 +0000 (15:32 +0000)
methods that I need to think about it, and boost. Small issues like
reloading all data when sorting role suddenly changes. In general terms
it will work nice when you sort by name or size. We have to work further
when we sort by other roles. Nice times.

svn path=/trunk/KDE/kdebase/apps/; revision=676732

src/CMakeLists.txt
src/dolphiniconsview.cpp
src/dolphinitemcategorizer.cpp
src/dolphinsortfilterproxymodel.cpp
src/dolphinsortfilterproxymodel.h
src/kitemcategorizer.h
src/klistview.cpp
src/klistview.h
src/klistview_p.h
src/ksortfilterproxymodel.cpp [new file with mode: 0644]
src/ksortfilterproxymodel.h [new file with mode: 0644]

index 04b4b9bd743216a49aa9b5c5d4e000f634024e80..6ca332212e451464cccf2635248d86de3074f661 100644 (file)
@@ -15,6 +15,7 @@ set(dolphinprivate_LIB_SRCS
     dolphiniconsview.cpp
     dolphinitemcategorizer.cpp
     klistview.cpp
+    ksortfilterproxymodel.cpp
     dolphinsettings.cpp
     viewproperties.cpp
     dolphinsortfilterproxymodel.cpp
index 50d7f311e92284b9ebc5b0d62c470cf44b616545..cf5be61ef5d9042c3348a3fd2c3085554e8c7102 100644 (file)
@@ -38,7 +38,7 @@ DolphinIconsView::DolphinIconsView(QWidget* parent, DolphinController* controlle
     Q_ASSERT(controller != 0);
     setViewMode(QListView::IconMode);
     setResizeMode(QListView::Adjust);
-
+    setSpacing(10);
     setMouseTracking(true);
     viewport()->setAttribute(Qt::WA_Hover);
 
index 05c23a01d606be2f4165710d1ac49309352506e4..cea6bebd5ee28ab7c54704b5ac576f9531ab956c 100644 (file)
@@ -41,38 +41,79 @@ QString DolphinItemCategorizer::categoryForItem(const QModelIndex& index,
 {
     QString retString;
 
-    if (!index.isValid()) {
+    if (!index.isValid())
+    {
+        return retString;
+    }
+
+    int indexColumn;
+
+    switch (sortRole)
+    {
+        case DolphinView::SortByName:
+            indexColumn = KDirModel::Name;
+            break;
+        case DolphinView::SortBySize:
+            indexColumn = KDirModel::Size;
+            break;
+        default:
         return retString;
     }
 
     // KDirModel checks columns to know to which role are
     // we talking about
     QModelIndex theIndex = index.model()->index(index.row(),
-                           sortRole,
+                                                indexColumn,
                            index.parent());
 
-    const QSortFilterProxyModel* proxyModel = static_cast<const QSortFilterProxyModel*>(index.model());
-    const KDirModel* dirModel = static_cast<const KDirModel*>(proxyModel->sourceModel());
+    if (!theIndex.isValid()) {
+        return retString;
+    }
 
     QVariant data = theIndex.model()->data(theIndex, Qt::DisplayRole);
 
-    QModelIndex mappedIndex = proxyModel->mapToSource(theIndex);
-    KFileItem* item = dirModel->itemForIndex(mappedIndex);
+    const KDirModel *dirModel = qobject_cast<const KDirModel*>(index.model());
+    KFileItem* item = dirModel->itemForIndex(index);
 
-    switch (sortRole) {
+    switch (sortRole)
+    {
     case DolphinView::SortByName:
-        retString = data.toString().toUpper().at(0);
+            if (data.toString().size())
+            {
+                if (!item->isHidden() && data.toString().at(0).isLetter())
+                    retString = data.toString().toUpper().at(0);
+                else if (item->isHidden() && data.toString().at(0) == '.' &&
+                         data.toString().at(1).isLetter())
+                    retString = i18n(".%1 (Hidden)", data.toString().toUpper().at(1));
+                else if (item->isHidden() && data.toString().at(0) == '.' &&
+                         !data.toString().at(1).isLetter())
+                    retString = i18n("Others (Hidden)");
+                else if (item->isHidden() && data.toString().at(0) != '.')
+                    retString = i18n("%1 (Hidden)", data.toString().toUpper().at(0));
+                else if (item->isHidden())
+                    retString = data.toString().toUpper().at(0);
+                else
+                    retString = i18n("Others");
+            }
         break;
     case DolphinView::SortBySize:
         int fileSize = (item) ? item->size() : -1;
-        if (item && item->isDir()) {
-            retString = i18n("Unknown");
-        } else if (fileSize < 5242880) {
+        if (item && item->isDir() && !item->isHidden()) {
+                retString = i18n("Folders");
+        } else if (fileSize < 5242880 && !item->isHidden()) {
             retString = i18n("Small");
-        } else if (fileSize < 10485760) {
+        } else if (fileSize < 10485760 && !item->isHidden()) {
             retString = i18n("Medium");
-        } else {
+        } else if (!item->isHidden()){
             retString = i18n("Big");
+        } else if (item && item->isDir() && item->isHidden()) {
+                retString = i18n("Folders (Hidden)");
+        } else if (fileSize < 5242880 && item->isHidden()) {
+            retString = i18n("Small (Hidden)");
+        } else if (fileSize < 10485760 && item->isHidden()) {
+            retString = i18n("Medium (Hidden)");
+        } else if (item->isHidden()){
+            retString = i18n("Big (Hidden)");
         }
         break;
     }
index 5f6cb357511d41b8cfe96e6e9a42704ef1f89808..9b901d2855217389205aa6cc9e9684f827e33781 100644 (file)
@@ -2,6 +2,7 @@
  *   Copyright (C) 2006 by Peter Penz <peter.penz@gmx.at>                  *
  *   Copyright (C) 2006 by Dominic Battre <dominic@battre.de>              *
  *   Copyright (C) 2006 by Martin Pool <mbp@canonical.com>                 *
+ *   Copyright (C) 2007 by Rafael Fernández López <ereslibre@gmail.com>    *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   it under the terms of the GNU General Public License as published by  *
@@ -49,7 +50,7 @@ static DolphinView::Sorting dirModelColumnToDolphinView[] =
 
 
 DolphinSortFilterProxyModel::DolphinSortFilterProxyModel(QObject* parent) :
-    QSortFilterProxyModel(parent),
+    KSortFilterProxyModel(parent),
     m_sortColumn(0),
     m_sorting(DolphinView::SortByName),
     m_sortOrder(Qt::AscendingOrder)
@@ -57,7 +58,7 @@ DolphinSortFilterProxyModel::DolphinSortFilterProxyModel(QObject* parent) :
     setDynamicSortFilter(true);
 
     // sort by the user visible string for now
-    setSortRole(Qt::DisplayRole);
+    setSortRole(DolphinView::SortByName);
     setSortCaseSensitivity(Qt::CaseInsensitive);
     sort(KDirModel::Name, Qt::AscendingOrder);
 }
@@ -87,6 +88,7 @@ void DolphinSortFilterProxyModel::sort(int column, Qt::SortOrder sortOrder)
     m_sorting = (column >= 0) && (column < dolphinMapSize) ?
                 dirModelColumnToDolphinView[column]  :
                 DolphinView::SortByName;
+    setSortRole(m_sorting);
     QSortFilterProxyModel::sort(column, sortOrder);
 }
 
@@ -110,38 +112,150 @@ DolphinView::Sorting DolphinSortFilterProxyModel::sortingForColumn(int column)
     return DolphinView::SortByName;
 }
 
+bool DolphinSortFilterProxyModel::lessThanGeneralPurpose(const QModelIndex &left,
+                                                         const QModelIndex &right) const
+{
+    KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
+
+    const KFileItem *leftFileItem  = dirModel->itemForIndex(left);
+     const KFileItem *rightFileItem = dirModel->itemForIndex(right);
+
+    if (sortRole() == DolphinView::SortByName) // If we are sorting by name
+    {
+        const QVariant leftData  = dirModel->data(left,  sortRole());
+        const QVariant rightData = dirModel->data(right, sortRole());
+
+        // Give preference to hidden items. They will be shown above regular
+        // items
+        if (leftFileItem->isHidden() && !rightFileItem->isHidden())
+            return true;
+        else if (!leftFileItem->isHidden() && rightFileItem->isHidden())
+            return false;
+
+        // If we are handling two items of the same preference, just take in
+        // count their names. There is no need to check for case sensitivity
+        // here, since this is the method that explores for new categories
+        return leftData.toString().toLower() < rightData.toString().toLower();
+    }
+    else if (sortRole() == DolphinView::SortBySize) // If we are sorting by size
+    {
+        // Give preference to hidden items. They will be shown above regular
+        // items
+        if (leftFileItem->isHidden() && !rightFileItem->isHidden())
+            return true;
+        else if (!leftFileItem->isHidden() && rightFileItem->isHidden())
+            return false;
+
+        // If we are sorting by size, show folders first. We will sort them
+        // correctly later
+        if (leftFileItem->isDir() && !rightFileItem->isDir())
+            return true;
+
+        return false;
+    }
+}
+
 bool DolphinSortFilterProxyModel::lessThan(const QModelIndex& left,
-        const QModelIndex& right) const
+                                           const QModelIndex& right) const
 {
     KDirModel* dirModel = static_cast<KDirModel*>(sourceModel());
 
     QVariant leftData  = dirModel->data(left,  sortRole());
     QVariant rightData = dirModel->data(right, sortRole());
 
-    if ((leftData.type() == QVariant::String) && (rightData.type() == QVariant::String)) {
-        // assure that directories are always sorted before files
-        // if the sorting is done by the 'Name' column
-        if (m_sortColumn == KDirModel::Name) {
-            const bool leftIsDir  = dirModel->itemForIndex(left)->isDir();
-            const bool rightIsDir = dirModel->itemForIndex(right)->isDir();
-            if (leftIsDir && !rightIsDir) {
+    const KFileItem *leftFileItem  = dirModel->itemForIndex(left);
+    const KFileItem *rightFileItem = dirModel->itemForIndex(right);
+
+    if (sortRole() == DolphinView::SortByName) { // If we are sorting by name
+        if ((leftData.type() == QVariant::String) && (rightData.type() ==
+                                                            QVariant::String)) {
+            // Priority: hidden > folders > regular files. If an item is
+            // hidden (doesn't matter if file or folder) will have higher
+            // preference than a non-hidden item
+            if (leftFileItem->isHidden() && !rightFileItem->isHidden()) {
                 return true;
             }
+            else if (!leftFileItem->isHidden() && rightFileItem->isHidden()) {
+                return false;
+            }
 
-            if (!leftIsDir && rightIsDir) {
+            // On our priority, folders go above regular files
+            if (leftFileItem->isDir() && !rightFileItem->isDir()) {
+                return true;
+            }
+            else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
                 return false;
             }
+
+            // So we are in the same priority, what counts now is their names
+            const QString leftStr = leftData.toString();
+            const QString rightStr = rightData.toString();
+
+            return sortCaseSensitivity() ? (naturalCompare(leftStr, rightStr) < 0) :
+                   (naturalCompare(leftStr.toLower(), rightStr.toLower()) < 0);
+        }
+    }
+    else if (sortRole() == DolphinView::SortBySize) { // If we are sorting by size
+        // Priority: hidden > folders > regular files. If an item is
+        // hidden (doesn't matter if file or folder) will have higher
+        // preference than a non-hidden item
+        if (leftFileItem->isHidden() && !rightFileItem->isHidden()) {
+            return true;
+        }
+        else if (!leftFileItem->isHidden() && rightFileItem->isHidden()) {
+            return false;
+        }
+
+        // On our priority, folders go above regular files
+        if (leftFileItem->isDir() && !rightFileItem->isDir()) {
+            return true;
+        }
+        else if (!leftFileItem->isDir() && rightFileItem->isDir()) {
+            return false;
+        }
+
+        // If we have two folders, what we have to measure is the number of
+        // items that contains each other
+        if (leftFileItem->isDir() && rightFileItem->isDir()) {
+            const QVariant leftValue = dirModel->data(left, KDirModel::ChildCountRole);
+            const int leftCount = leftValue.type() == QVariant::Int ? leftValue.toInt() : KDirModel::ChildCountUnknown;
+
+            const QVariant rightValue = dirModel->data(right, KDirModel::ChildCountRole);
+            const int rightCount = rightValue.type() == QVariant::Int ? rightValue.toInt() : KDirModel::ChildCountUnknown;
+
+            // In the case they two have the same child items, we sort them by
+            // their names. So we have always everything ordered. We also check
+            // if we are taking in count their cases
+            if (leftCount == rightCount) {
+                const QString leftStr = leftData.toString();
+                const QString rightStr = rightData.toString();
+
+                return sortCaseSensitivity() ? (naturalCompare(leftStr, rightStr) < 0) :
+                       (naturalCompare(leftStr.toLower(), rightStr.toLower()) < 0);
+            }
+
+            // If they had different number of items, we sort them depending
+            // on how many items had each other
+            return leftCount < rightCount;
         }
 
-        const QString leftStr  = leftData.toString();
-        const QString rightStr = rightData.toString();
+        // If what we are measuring is two files and they have the same size,
+        // sort them by their file names
+        if (leftFileItem->size() == rightFileItem->size()) {
+            const QString leftStr = leftData.toString();
+            const QString rightStr = rightData.toString();
 
-        return sortCaseSensitivity() ? (naturalCompare(leftStr, rightStr) < 0) :
-               (naturalCompare(leftStr.toLower(), rightStr.toLower()) < 0);
+            return sortCaseSensitivity() ? (naturalCompare(leftStr, rightStr) < 0) :
+                   (naturalCompare(leftStr.toLower(), rightStr.toLower()) < 0);
+        }
+
+        // If their sizes are different, sort them by their sizes, as expected
+        return leftFileItem->size() < rightFileItem->size();
     }
 
     // We have set a SortRole and trust the ProxyModel to do
     // the right thing for now.
+
     return QSortFilterProxyModel::lessThan(left, right);
 }
 
index 437ebca126ddad172f14a7d1246822deff686fce..c00032f3f1c6182f1988710d0b18e1c8d4be4493 100644 (file)
@@ -20,7 +20,7 @@
 #ifndef DOLPHINSORTFILTERPROXYMODEL_H
 #define DOLPHINSORTFILTERPROXYMODEL_H
 
-#include <QtGui/QSortFilterProxyModel>
+#include <ksortfilterproxymodel.h>
 #include <dolphinview.h>
 #include <libdolphin_export.h>
 
@@ -39,7 +39,7 @@
  *
  * It is assured that directories are always sorted before files.
  */
-class LIBDOLPHINPRIVATE_EXPORT DolphinSortFilterProxyModel : public QSortFilterProxyModel
+class LIBDOLPHINPRIVATE_EXPORT DolphinSortFilterProxyModel : public KSortFilterProxyModel
 {
     Q_OBJECT
 
@@ -81,6 +81,9 @@ public:
      */
     static DolphinView::Sorting sortingForColumn(int column);
 
+    virtual bool lessThanGeneralPurpose(const QModelIndex &left,
+                                        const QModelIndex &right) const;
+
 protected:
     virtual bool lessThan(const QModelIndex& left,
                           const QModelIndex& right) const;
index 7bec65637049fb2264fd5f16cbd5fcaf9b8abbfd..1d64206eb60cf24ae388fd2219e8d0f8824f35b2 100644 (file)
@@ -18,8 +18,8 @@
   * Boston, MA 02110-1301, USA.
   */
 
-#ifndef __KITEMCATEGORIZER_H__
-#define __KITEMCATEGORIZER_H__
+#ifndef KITEMCATEGORIZER_H
+#define KITEMCATEGORIZER_H
 
 #include <libdolphin_export.h>
 
index 1d4e4153687ad7a4d025c84f997977942b8733f7..b38f18263fa689c8b6d12801095f834f5faf32d9 100644 (file)
   * Boston, MA 02110-1301, USA.
   */
 
-// NOTE: rectForIndex() not virtual on QListView !! relevant ?
 #include "klistview.h"
 #include "klistview_p.h"
 
-#include <QtGui/QPainter>
-#include <QtGui/QScrollBar>
-#include <QtGui/QKeyEvent>
-#include <QtGui/QSortFilterProxyModel>
+#include <math.h> // trunc
+
+#include <QApplication>
+#include <QPainter>
+#include <QScrollBar>
+#include <QPaintEvent>
 
 #include <kdebug.h>
+#include <kstyle.h>
 
 #include "kitemcategorizer.h"
+#include "ksortfilterproxymodel.h"
+
+class LessThan
+{
+public:
+    enum Purpose
+    {
+        GeneralPurpose = 0,
+        CategoryPurpose
+    };
+
+    inline LessThan(const KSortFilterProxyModel *proxyModel,
+                    Purpose purpose)
+        : proxyModel(proxyModel)
+        , purpose(purpose)
+    {
+    }
+
+    inline bool operator()(const QModelIndex &left,
+                           const QModelIndex &right) const
+    {
+        if (purpose == GeneralPurpose)
+        {
+            return proxyModel->lessThanGeneralPurpose(left, right);
+        }
+
+        return proxyModel->lessThanCategoryPurpose(left, right);
+    }
+
+private:
+    const KSortFilterProxyModel *proxyModel;
+    const Purpose purpose;
+};
+
+
+//==============================================================================
+
 
 KListView::Private::Private(KListView *listView)
     : listView(listView)
-    , modelSortCapable(false)
     , itemCategorizer(0)
-    , numCategories(0)
+    , mouseButtonPressed(false)
     , proxyModel(0)
+    , lastIndex(QModelIndex())
 {
 }
 
@@ -44,148 +83,365 @@ KListView::Private::~Private()
 {
 }
 
-QModelIndexList KListView::Private::intersectionSet(const QRect &rect) const
+const QModelIndexList &KListView::Private::intersectionSet(const QRect &rect)
 {
-    // FIXME: boost me, I suck (ereslibre)
+    QModelIndex index;
+    QRect indexVisualRect;
 
-    QModelIndexList modelIndexList;
+    intersectedIndexes.clear();
 
-    QModelIndex index;
-    for (int i = 0; i < listView->model()->rowCount(); i++)
+    // Lets find out where we should start
+    int top = proxyModel->rowCount() - 1;
+    int bottom = 0;
+    int middle = (top + bottom) / 2;
+    while (bottom <= top)
     {
-        index = listView->model()->index(i, 0);
+        middle = (top + bottom) / 2;
+
+        index = elementDictionary[proxyModel->index(middle, 0)];
+        indexVisualRect = visualRect(index);
 
-        if (rect.intersects(listView->visualRect(index)))
-            modelIndexList.append(index);
+        if (qMax(indexVisualRect.topLeft().y(),
+                 indexVisualRect.bottomRight().y()) < qMin(rect.topLeft().y(),
+                                                        rect.bottomRight().y()))
+        {
+            bottom = middle + 1;
+        }
+        else
+        {
+            top = middle - 1;
+        }
     }
 
-    return modelIndexList;
-}
+    int j = 0;
+    for (int i = middle; i < proxyModel->rowCount(); i++)
+    {
+        index = elementDictionary[proxyModel->index(i, 0)];
+        indexVisualRect = visualRect(index);
 
-KListView::KListView(QWidget *parent)
-    : QListView(parent)
-    , d(new Private(this))
-{
+        if (rect.intersects(indexVisualRect))
+            intersectedIndexes.append(index);
+
+        // If we passed next item, stop searching for hits
+        if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
+                                                  indexVisualRect.topLeft().y())
+            break;
+
+        j++;
+    }
+
+    return intersectedIndexes;
 }
 
-KListView::~KListView()
+QRect KListView::Private::visualRectInViewport(const QModelIndex &index) const
 {
-    if (d->proxyModel)
+    if (!index.isValid())
+        return QRect();
+
+    QString curCategory = elementsInfo[index].category;
+
+    QRect retRect(listView->spacing(), listView->spacing() * 2 +
+                       30 /* categoryHeight */, 0, 0);
+
+    int viewportWidth = listView->viewport()->width() - listView->spacing();
+
+    // We really need all items to be of same size. Otherwise we cannot do this
+    // (ereslibre)
+    // QSize itemSize =
+    //             listView->sizeHintForIndex(proxyModel->mapFromSource(index));
+    // int itemHeight = itemSize.height();
+    // int itemWidth = itemSize.width();*/
+    int itemHeight = 107;
+    int itemWidth = 130;
+    int itemWidthPlusSeparation = listView->spacing() + itemWidth;
+    int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
+    if (!elementsPerRow)
+        elementsPerRow++;
+
+    int column = elementsInfo[index].relativeOffsetToCategory % elementsPerRow;
+    int row = elementsInfo[index].relativeOffsetToCategory / elementsPerRow;
+
+    retRect.setLeft(retRect.left() + column * listView->spacing() +
+                    column * itemWidth);
+
+    float rows;
+    int rowsInt;
+    foreach (const QString &category, categories)
     {
-        QObject::disconnect(this->model(), SIGNAL(layoutChanged()),
-                            this         , SLOT(itemsLayoutChanged()));
+        if (category == curCategory)
+            break;
+
+        rows = (float) ((float) categoriesIndexes[category].count() /
+                        (float) elementsPerRow);
+        rowsInt = categoriesIndexes[category].count() / elementsPerRow;
+
+        if (rows - trunc(rows)) rowsInt++;
+
+        retRect.setTop(retRect.top() +
+                       (rowsInt * listView->spacing()) +
+                       (rowsInt * itemHeight) +
+                       30 /* categoryHeight */ +
+                       listView->spacing() * 2);
     }
 
-    delete d;
+    retRect.setTop(retRect.top() + row * listView->spacing() +
+                   row * itemHeight);
+
+    retRect.setWidth(itemWidth);
+    retRect.setHeight(itemHeight);
+
+    return retRect;
 }
 
-void KListView::setModel(QAbstractItemModel *model)
+QRect KListView::Private::visualCategoryRectInViewport(const QString &category)
+                                                                           const
 {
-    QSortFilterProxyModel *proxyModel =
-                                    qobject_cast<QSortFilterProxyModel*>(model);
+    QRect retRect(listView->spacing(),
+                  listView->spacing(),
+                  listView->viewport()->width() - listView->spacing() * 2,
+                  0);
+
+    if (!proxyModel->rowCount() || !categories.contains(category))
+        return QRect();
+
+    QModelIndex index = proxyModel->index(0, 0, QModelIndex());
+
+    int viewportWidth = listView->viewport()->width() - listView->spacing();
+
+    // We really need all items to be of same size. Otherwise we cannot do this
+    // (ereslibre)
+    // QSize itemSize = listView->sizeHintForIndex(index);
+    // int itemHeight = itemSize.height();
+    // int itemWidth = itemSize.width();
+    int itemHeight = 107;
+    int itemWidth = 130;
+    int itemWidthPlusSeparation = listView->spacing() + itemWidth;
+    int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
 
-    if (this->model() && this->model()->rowCount())
+    if (!elementsPerRow)
+        elementsPerRow++;
+
+    float rows;
+    int rowsInt;
+    foreach (const QString &itCategory, categories)
     {
-        QObject::disconnect(this->model(), SIGNAL(layoutChanged()),
-                            this         , SLOT(itemsLayoutChanged()));
+        if (itCategory == category)
+            break;
 
-        rowsAboutToBeRemovedArtifficial(QModelIndex(), 0,
-                                        this->model()->rowCount() - 1);
-    }
+        rows = (float) ((float) categoriesIndexes[itCategory].count() /
+                        (float) elementsPerRow);
+        rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
 
-    d->modelSortCapable = (proxyModel != 0);
-    d->proxyModel = proxyModel;
+        if (rows - trunc(rows)) rowsInt++;
 
-    // If the model was initialized before applying to the view, we update
-    // internal data structure of the view with the model information
-    if (model->rowCount())
-    {
-        rowsInsertedArtifficial(QModelIndex(), 0, model->rowCount() - 1);
+        retRect.setTop(retRect.top() +
+                       (rowsInt * listView->spacing()) +
+                       (rowsInt * itemHeight) +
+                       30 /* categoryHeight */ +
+                       listView->spacing() * 2);
     }
 
-    QListView::setModel(model);
+    retRect.setHeight(30 /* categoryHeight */);
 
-    QObject::connect(model, SIGNAL(layoutChanged()),
-                     this , SLOT(itemsLayoutChanged()));
+    return retRect;
 }
 
-QRect KListView::visualRect(const QModelIndex &index) const
+// We're sure elementsPosition doesn't contain index
+const QRect &KListView::Private::cacheIndex(const QModelIndex &index)
 {
-    // FIXME: right to left languages (ereslibre)
-    // FIXME: drag & drop support (ereslibre)
-    // FIXME: do not forget to remove itemWidth's hard-coded values that were
-    //        only for testing purposes. We might like to calculate the best
-    //        width, but what we would really like for sure is that all items
-    //        have the same width, as well as the same height (ereslibre)
+    QRect rect = visualRectInViewport(index);
+    elementsPosition[index] = rect;
 
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
-        !d->itemCategorizer)
-    {
-        return QListView::visualRect(index);
+    return elementsPosition[index];
+}
+
+// We're sure categoriesPosition doesn't contain category
+const QRect &KListView::Private::cacheCategory(const QString &category)
+{
+    QRect rect = visualCategoryRectInViewport(category);
+    categoriesPosition[category] = rect;
+
+    return categoriesPosition[category];
+}
+
+const QRect &KListView::Private::cachedRectIndex(const QModelIndex &index)
+{
+    if (elementsPosition.contains(index)) // If we have it cached
+    {                                        // return it
+        return elementsPosition[index];
+    }
+    else                                     // Otherwise, cache it
+    {                                        // and return it
+        return cacheIndex(index);
+    }
+}
+
+const QRect &KListView::Private::cachedRectCategory(const QString &category)
+{
+    if (categoriesPosition.contains(category)) // If we have it cached
+    {                                                // return it
+        return categoriesPosition[category];
     }
+    else                                            // Otherwise, cache it and
+    {                                               // return it
+        return cacheCategory(category);
+    }
+}
 
-    QRect retRect(spacing(), spacing(), 0, 0);
-    int viewportWidth = viewport()->width() - spacing();
-    int dx = -horizontalOffset();
-    int dy = -verticalOffset();
+QRect KListView::Private::visualRect(const QModelIndex &index)
+{
+    QModelIndex mappedIndex = proxyModel->mapToSource(index);
 
-    if (verticalScrollBar() && !verticalScrollBar()->isHidden())
-        viewportWidth -= verticalScrollBar()->width();
+    QRect retRect = cachedRectIndex(mappedIndex);
+    int dx = -listView->horizontalOffset();
+    int dy = -listView->verticalOffset();
+    retRect.adjust(dx, dy, dx, dy);
 
-    int itemHeight = sizeHintForIndex(index).height();
-    int itemWidth = 130; // NOTE: ghosts in here !
-    int itemWidthPlusSeparation = spacing() + itemWidth;
-    int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
-    if (!elementsPerRow)
-        elementsPerRow++;
-    QModelIndex currentIndex = d->proxyModel->index(index.row(), 0);
-    QString itemCategory = d->itemCategorizer->categoryForItem(currentIndex,
-                                                     d->proxyModel->sortRole());
-    int naturalRow = index.row() / elementsPerRow;
-    int naturalTop = naturalRow * itemHeight + naturalRow * spacing();
+    return retRect;
+}
+
+QRect KListView::Private::categoryVisualRect(const QString &category)
+{
+    QRect retRect = cachedRectCategory(category);
+    int dx = -listView->horizontalOffset();
+    int dy = -listView->verticalOffset();
+    retRect.adjust(dx, dy, dx, dy);
 
-    int rowsForCategory;
-    int lastIndexShown = -1;
-    foreach (QString category, d->categories)
+    return retRect;
+}
+
+void KListView::Private::drawNewCategory(const QString &category,
+                                         const QStyleOption &option,
+                                         QPainter *painter)
+{
+    QColor color = option.palette.color(QPalette::Text);
+
+    painter->save();
+    painter->setRenderHint(QPainter::Antialiasing);
+
+    QStyleOptionButton opt;
+
+    opt.rect = option.rect;
+    opt.palette = option.palette;
+    opt.direction = option.direction;
+    opt.text = category;
+
+    if (option.rect.contains(listView->viewport()->mapFromGlobal(QCursor::pos())) &&
+        !mouseButtonPressed)
     {
-        retRect.setTop(retRect.top() + spacing());
+        const QPalette::ColorGroup group =
+                                          option.state & QStyle::State_Enabled ?
+                                          QPalette::Normal : QPalette::Disabled;
+
+        QLinearGradient gradient(option.rect.topLeft(),
+                                 option.rect.bottomRight());
+        gradient.setColorAt(0,
+                            option.palette.color(group,
+                                                 QPalette::Highlight).light());
+        gradient.setColorAt(1, Qt::transparent);
+
+        painter->fillRect(option.rect, gradient);
+    }
 
-        if (category == itemCategory)
+    /*if (const KStyle *style = dynamic_cast<const KStyle*>(QApplication::style()))
         {
-            break;
+        style->drawControl(KStyle::CE_Category, &opt, painter, this);
         }
+    else
+    {*/
+        QFont painterFont = painter->font();
+        painterFont.setWeight(QFont::Bold);
+        QFontMetrics metrics(painterFont);
+        painter->setFont(painterFont);
+
+        QPainterPath path;
+        path.addRect(option.rect.left(),
+                     option.rect.bottom() - 2,
+                     option.rect.width(),
+                     2);
+
+        QLinearGradient gradient(option.rect.topLeft(),
+                                 option.rect.bottomRight());
+        gradient.setColorAt(0, color);
+        gradient.setColorAt(1, Qt::transparent);
+
+        painter->setBrush(gradient);
+        painter->fillPath(path, gradient);
+
+        painter->setPen(color);
+
+        painter->drawText(option.rect, Qt::AlignVCenter | Qt::AlignLeft,
+             metrics.elidedText(category, Qt::ElideRight, option.rect.width()));
+    //}
+    painter->restore();
+}
 
-        rowsForCategory = (d->elementsPerCategory[category] / elementsPerRow);
 
-        if ((d->elementsPerCategory[category] % elementsPerRow) ||
-            !rowsForCategory)
-        {
-            rowsForCategory++;
-        }
+void KListView::Private::updateScrollbars()
+{
+    int lastItemBottom = cachedRectIndex(lastIndex).bottom() +
+                           listView->spacing() - listView->viewport()->height();
+    listView->verticalScrollBar()->setRange(0, lastItemBottom);
+}
+
+
+//==============================================================================
 
-        lastIndexShown += d->elementsPerCategory[category];
 
-        retRect.setTop(retRect.top() + categoryHeight(viewOptions()) +
-                       (rowsForCategory * spacing() * 2) +
-                       (rowsForCategory * itemHeight));
+KListView::KListView(QWidget *parent)
+    : QListView(parent)
+    , d(new Private(this))
+{
+}
+
+KListView::~KListView()
+{
+    delete d;
+}
+
+void KListView::setModel(QAbstractItemModel *model)
+{
+    if (d->proxyModel)
+    {
+        QObject::disconnect(d->proxyModel,
+                            SIGNAL(rowsRemoved(QModelIndex,int,int)),
+                            this, SLOT(rowsRemoved(QModelIndex,int,int)));
+
+        QObject::disconnect(d->proxyModel,
+                            SIGNAL(sortingRoleChanged()),
+                            this, SLOT(slotSortingRoleChanged()));
     }
 
-    int rowToPosition = (index.row() - (lastIndexShown + 1)) / elementsPerRow;
-    int columnToPosition = (index.row() - (lastIndexShown + 1)) %
-                                                                 elementsPerRow;
+    QListView::setModel(model);
 
-    retRect.setTop(retRect.top() + (rowToPosition * spacing() * 2) +
-                   (rowToPosition * itemHeight));
+    d->proxyModel = dynamic_cast<KSortFilterProxyModel*>(model);
 
-    retRect.setLeft(retRect.left() + (columnToPosition * spacing()) +
-                    (columnToPosition * itemWidth));
+    if (d->proxyModel)
+    {
+        QObject::connect(d->proxyModel,
+                         SIGNAL(rowsRemoved(QModelIndex,int,int)),
+                         this, SLOT(rowsRemoved(QModelIndex,int,int)));
 
-    retRect.setWidth(130); // NOTE: ghosts in here !
-    retRect.setHeight(itemHeight);
+        QObject::connect(d->proxyModel,
+                         SIGNAL(sortingRoleChanged()),
+                         this, SLOT(slotSortingRoleChanged()));
+    }
+}
 
-    retRect.adjust(dx, dy, dx, dy);
+QRect KListView::visualRect(const QModelIndex &index) const
+{
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return QListView::visualRect(index);
+    }
 
-    return retRect;
+    if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
+    {
+        return d->visualRect(d->proxyModel->mapFromSource(index));
+    }
+
+    return d->visualRect(index);
 }
 
 KItemCategorizer *KListView::itemCategorizer() const
@@ -195,73 +451,88 @@ KItemCategorizer *KListView::itemCategorizer() const
 
 void KListView::setItemCategorizer(KItemCategorizer *itemCategorizer)
 {
+    if (!itemCategorizer && d->proxyModel)
+    {
+        QObject::disconnect(d->proxyModel,
+                            SIGNAL(rowsRemoved(QModelIndex,int,int)),
+                            this, SLOT(rowsRemoved(QModelIndex,int,int)));
+
+        QObject::disconnect(d->proxyModel,
+                            SIGNAL(sortingRoleChanged()),
+                            this, SLOT(slotSortingRoleChanged()));
+    }
+    else if (itemCategorizer && d->proxyModel)
+    {
+        QObject::connect(d->proxyModel,
+                         SIGNAL(rowsRemoved(QModelIndex,int,int)),
+                         this, SLOT(rowsRemoved(QModelIndex,int,int)));
+
+        QObject::connect(d->proxyModel,
+                         SIGNAL(sortingRoleChanged()),
+                         this, SLOT(slotSortingRoleChanged()));
+    }
+
     d->itemCategorizer = itemCategorizer;
 
     if (itemCategorizer)
-        itemsLayoutChanged();
+    {
+        rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
+    }
 }
 
 QModelIndex KListView::indexAt(const QPoint &point) const
 {
-    QModelIndex index;
-
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         return QListView::indexAt(point);
     }
 
+    QModelIndex index;
+
     QModelIndexList item = d->intersectionSet(QRect(point, point));
 
     if (item.count() == 1)
+    {
         index = item[0];
+    }
 
     d->hovered = index;
 
     return index;
 }
 
-int KListView::sizeHintForRow(int row) const
+void KListView::reset()
 {
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    QListView::reset();
+
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
-        return QListView::sizeHintForRow(row);
+        return;
     }
 
-    QModelIndex index = d->proxyModel->index(0, 0);
-
-    if (!index.isValid())
-        return 0;
-
-    return sizeHintForIndex(index).height() + categoryHeight(viewOptions()) +
-           spacing();
-}
-
-void KListView::drawNewCategory(const QString &category,
-                                const QStyleOptionViewItem &option,
-                                QPainter *painter)
-{
-    painter->drawText(option.rect.topLeft(), category);
-}
-
-int KListView::categoryHeight(const QStyleOptionViewItem &option) const
-{
-    return option.fontMetrics.height();
+    d->elementsInfo.clear();
+    d->elementsPosition.clear();
+    d->elementDictionary.clear();
+    d->categoriesIndexes.clear();
+    d->categoriesPosition.clear();
+    d->categories.clear();
+    d->intersectedIndexes.clear();
+    d->sourceModelIndexList.clear();
+    d->hovered = QModelIndex();
+    d->mouseButtonPressed = false;
 }
 
 void KListView::paintEvent(QPaintEvent *event)
 {
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         QListView::paintEvent(event);
         return;
     }
 
-    if (!itemDelegate())
-        return;
-
     QStyleOptionViewItemV3 option = viewOptions();
     QPainter painter(viewport());
     QRect area = event->rect();
@@ -270,16 +541,19 @@ void KListView::paintEvent(QPaintEvent *event)
     const QStyle::State state = option.state;
     const bool enabled = (state & QStyle::State_Enabled) != 0;
 
-    int totalHeight = 0;
-    QModelIndex index;
-    QString prevCategory;
+    painter.save();
+
     QModelIndexList dirtyIndexes = d->intersectionSet(area);
     foreach (const QModelIndex &index, dirtyIndexes)
     {
         option.state = state;
-        option.rect = visualRect(index);
+        option.rect = d->visualRect(index);
+
         if (selectionModel() && selectionModel()->isSelected(index))
+        {
             option.state |= QStyle::State_Selected;
+        }
+
         if (enabled)
         {
             QPalette::ColorGroup cg;
@@ -294,6 +568,7 @@ void KListView::paintEvent(QPaintEvent *event)
             }
             option.palette.setCurrentColorGroup(cg);
         }
+
         if (focus && currentIndex() == index)
         {
             option.state |= QStyle::State_HasFocus;
@@ -301,149 +576,387 @@ void KListView::paintEvent(QPaintEvent *event)
                 option.state |= QStyle::State_Editing;
         }
 
-        if (index == d->hovered)
+        if ((index == d->hovered) && !d->mouseButtonPressed)
             option.state |= QStyle::State_MouseOver;
         else
             option.state &= ~QStyle::State_MouseOver;
 
-        if (prevCategory != d->itemCategorizer->categoryForItem(index,
-                                                     d->proxyModel->sortRole()))
+        itemDelegate(index)->paint(&painter, option, index);
+    }
+
+    // Redraw categories
+    QStyleOptionViewItem otherOption;
+    foreach (const QString &category, d->categories)
+    {
+        otherOption = option;
+        otherOption.rect = d->categoryVisualRect(category);
+
+        if (otherOption.rect.intersects(area))
         {
-            prevCategory = d->itemCategorizer->categoryForItem(index,
-                                                     d->proxyModel->sortRole());
-            drawNewCategory(prevCategory, option, &painter);
+            d->drawNewCategory(category, otherOption, &painter);
         }
-        itemDelegate(index)->paint(&painter, option, index);
     }
+
+    if (d->mouseButtonPressed)
+    {
+        QPoint start, end, initialPressPosition;
+
+        initialPressPosition = d->initialPressPosition;
+
+        initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
+        initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
+
+        if (d->initialPressPosition.x() > d->mousePosition.x() ||
+            d->initialPressPosition.y() > d->mousePosition.y())
+        {
+            start = d->mousePosition;
+            end = initialPressPosition;
+        }
+        else
+        {
+            start = initialPressPosition;
+            end = d->mousePosition;
+        }
+
+        QStyleOptionRubberBand yetAnotherOption;
+        yetAnotherOption.initFrom(this);
+        yetAnotherOption.shape = QRubberBand::Rectangle;
+        yetAnotherOption.opaque = false;
+        yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
+        painter.save();
+        style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
+        painter.restore();
+    }
+
+    painter.restore();
 }
 
-void KListView::setSelection(const QRect &rect,
-                             QItemSelectionModel::SelectionFlags flags)
+void KListView::resizeEvent(QResizeEvent *event)
 {
-    // TODO: implement me
+    QListView::resizeEvent(event);
+
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    // Clear the items positions cache
+    d->elementsPosition.clear();
+    d->categoriesPosition.clear();
 
-    QListView::setSelection(rect, flags);
+    d->updateScrollbars();
+}
 
-    /*if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+void KListView::setSelection(const QRect &rect,
+                             QItemSelectionModel::SelectionFlags flags)
+{
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         QListView::setSelection(rect, flags);
         return;
     }
 
-    QModelIndex index;
-    for (int i = 0; i < d->proxyModel->rowCount(); i++)
+    // FIXME: I have to rethink and rewrite this method (ereslibre)
+
+    QModelIndexList dirtyIndexes = d->intersectionSet(rect);
+    foreach (const QModelIndex &index, dirtyIndexes)
     {
-        index = d->proxyModel->index(i, 0);
-        if (rect.intersects(visualRect(index)))
+        if (!d->mouseButtonPressed && rect.intersects(visualRect(index)))
         {
-            selectionModel()->select(index, QItemSelectionModel::Select);
+            selectionModel()->select(index, flags);
         }
         else
         {
-            selectionModel()->select(index, QItemSelectionModel::Deselect);
+            selectionModel()->select(index, QItemSelectionModel::Select);
+
+            if (d->mouseButtonPressed)
+                d->tempSelected.append(index);
         }
-    }*/
+    }
+
+    if (d->mouseButtonPressed)
+    {
+        foreach (const QModelIndex &index, selectionModel()->selectedIndexes())
+        {
+            if (!rect.intersects(visualRect(index)))
+            {
+                selectionModel()->select(index, QItemSelectionModel::Deselect);
 
-    //selectionModel()->select(selection, flags);
+                if (d->mouseButtonPressed)
+                {
+                    d->tempSelected.removeAll(index);
+                }
+            }
+        }
+    }
 }
 
-void KListView::timerEvent(QTimerEvent *event)
+void KListView::mouseMoveEvent(QMouseEvent *event)
 {
-    QListView::timerEvent(event);
+    QListView::mouseMoveEvent(event);
+
+    d->mousePosition = event->pos();
 
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         return;
     }
-}
 
-void KListView::rowsInserted(const QModelIndex &parent,
-                             int start,
-                             int end)
-{
-    QListView::rowsInserted(parent, start, end);
-    rowsInsertedArtifficial(parent, start, end);
+    event->accept();
+
+    viewport()->update();
 }
 
-void KListView::rowsAboutToBeRemoved(const QModelIndex &parent,
-                                     int start,
-                                     int end)
+void KListView::mousePressEvent(QMouseEvent *event)
 {
-    QListView::rowsAboutToBeRemoved(parent, start, end);
-    rowsAboutToBeRemovedArtifficial(parent, start, end);
+    QListView::mousePressEvent(event);
+
+    d->tempSelected.clear();
+
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    event->accept();
+
+    if (event->button() == Qt::LeftButton)
+    {
+        d->mouseButtonPressed = true;
+
+        d->initialPressPosition = event->pos();
+        d->initialPressPosition.setY(d->initialPressPosition.y() +
+                                                              verticalOffset());
+        d->initialPressPosition.setX(d->initialPressPosition.x() +
+                                                            horizontalOffset());
+    }
+
+    viewport()->update();
 }
 
-void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
-                                        int start,
-                                        int end)
+void KListView::mouseReleaseEvent(QMouseEvent *event)
 {
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    QListView::mouseReleaseEvent(event);
+
+    d->mouseButtonPressed = false;
+
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         return;
     }
 
-    QString category;
-    QModelIndex index;
-    for (int i = start; i <= end; i++)
-    {
-        index = d->proxyModel->index(i, 0, parent);
-        category = d->itemCategorizer->categoryForItem(index,
-                                                     d->proxyModel->sortRole());
+    event->accept();
 
-        if (d->elementsPerCategory.contains(category))
-            d->elementsPerCategory[category]++;
-        else
+    // FIXME: I have to rethink and rewrite this method (ereslibre)
+
+    QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
+    initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
+    initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
+
+    if (initialPressPosition == d->initialPressPosition)
+    {
+        foreach(const QString &category, d->categories)
         {
-            d->elementsPerCategory.insert(category, 1);
-            d->categories.append(category);
+            if (d->categoryVisualRect(category).contains(event->pos()))
+            {
+                QModelIndex index;
+                QItemSelectionModel::SelectionFlag flag;
+                foreach (const QModelIndex &mappedIndex,
+                         d->categoriesIndexes[category])
+                {
+                    index = d->proxyModel->mapFromSource(mappedIndex);
+
+                    if (selectionModel()->selectedIndexes().contains(index))
+                    {
+                        flag = QItemSelectionModel::Deselect;
+                    }
+                    else
+                    {
+                        flag = QItemSelectionModel::Select;
+                    }
+
+                    selectionModel()->select(index, flag);
+                }
+            }
         }
     }
+
+    viewport()->update();
 }
 
-void KListView::rowsAboutToBeRemovedArtifficial(const QModelIndex &parent,
-                                                int start,
-                                                int end)
+void KListView::leaveEvent(QEvent *event)
 {
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    QListView::leaveEvent(event);
+
+    d->hovered = QModelIndex();
+
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         return;
     }
 
+    event->accept();
+
+    viewport()->update();
+}
+
+void KListView::rowsInserted(const QModelIndex &parent,
+                             int start,
+                             int end)
+{
+    QListView::rowsInserted(parent, start, end);
+
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    rowsInsertedArtifficial(parent, start, end);
+}
+
+void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
+                                                int start,
+                                                int end)
+{
+    d->elementsInfo.clear();
+    d->elementsPosition.clear();
+    d->elementDictionary.clear();
+    d->categoriesIndexes.clear();
+    d->categoriesPosition.clear();
+    d->categories.clear();
+    d->intersectedIndexes.clear();
+    d->sourceModelIndexList.clear();
     d->hovered = QModelIndex();
+    d->mouseButtonPressed = false;
 
-    QString category;
-    QModelIndex index;
-    for (int i = start; i <= end; i++)
+    if (start > end || end < 0 || start < 0 || !d->proxyModel->rowCount())
+    {
+        return;
+    }
+
+    // Add all elements mapped to the source model
+    for (int i = 0; i < d->proxyModel->rowCount(); i++)
     {
-        index = d->proxyModel->index(i, 0, parent);
-        category = d->itemCategorizer->categoryForItem(index,
+        d->sourceModelIndexList <<
+                         d->proxyModel->mapToSource(d->proxyModel->index(i, 0));
+    }
+
+    // Sort them with the general purpose lessThan method
+    LessThan generalLessThan(d->proxyModel,
+                             LessThan::GeneralPurpose);
+
+    qStableSort(d->sourceModelIndexList.begin(), d->sourceModelIndexList.end(),
+                generalLessThan);
+
+    // Explore categories
+    QString prevCategory =
+                 d->itemCategorizer->categoryForItem(d->sourceModelIndexList[0],
+                                                     d->proxyModel->sortRole());
+    QString lastCategory = prevCategory;
+    QModelIndexList modelIndexList;
+    struct Private::ElementInfo elementInfo;
+    foreach (const QModelIndex &index, d->sourceModelIndexList)
+    {
+        lastCategory = d->itemCategorizer->categoryForItem(index,
                                                      d->proxyModel->sortRole());
 
-        if (d->elementsPerCategory.contains(category))
+        elementInfo.category = lastCategory;
+
+        if (prevCategory != lastCategory)
         {
-            d->elementsPerCategory[category]--;
+            d->categoriesIndexes.insert(prevCategory, modelIndexList);
+            d->categories << prevCategory;
+            modelIndexList.clear();
+        }
 
-            if (!d->elementsPerCategory[category])
-            {
-                d->elementsPerCategory.remove(category);
-                d->categories.removeAll(category);
-            }
+        modelIndexList << index;
+        prevCategory = lastCategory;
+
+        d->elementsInfo.insert(index, elementInfo);
+    }
+
+    d->categoriesIndexes.insert(prevCategory, modelIndexList);
+    d->categories << prevCategory;
+
+    // Sort items locally in their respective categories with the category
+    // purpose lessThan
+    LessThan categoryLessThan(d->proxyModel,
+                              LessThan::CategoryPurpose);
+
+    foreach (const QString &key, d->categories)
+    {
+        QModelIndexList &indexList = d->categoriesIndexes[key];
+
+        qStableSort(indexList.begin(), indexList.end(), categoryLessThan);
+    }
+
+    d->lastIndex = d->categoriesIndexes[d->categories[d->categories.count() - 1]][d->categoriesIndexes[d->categories[d->categories.count() - 1]].count() - 1];
+
+    // Finally, fill data information of items situation. This will help when
+    // trying to compute an item place in the viewport
+    int i = 0; // position relative to the category beginning
+    int j = 0; // number of elements before current
+    foreach (const QString &key, d->categories)
+    {
+        foreach (const QModelIndex &index, d->categoriesIndexes[key])
+        {
+            struct Private::ElementInfo &elementInfo = d->elementsInfo[index];
+
+            elementInfo.relativeOffsetToCategory = i;
+
+            d->elementDictionary.insert(d->proxyModel->index(j, 0),
+                                        d->proxyModel->mapFromSource(index));
+
+            i++;
+            j++;
         }
+
+        i = 0;
     }
+
+    d->updateScrollbars();
 }
 
-void KListView::itemsLayoutChanged()
+void KListView::rowsRemoved(const QModelIndex &parent,
+                            int start,
+                            int end)
 {
-    d->elementsPerCategory.clear();
-    d->categories.clear();
+    if (d->proxyModel)
+    {
+        // Force the view to update all elements
+        rowsInsertedArtifficial(parent, start, end);
+    }
+}
 
-    if (d->proxyModel && d->proxyModel->rowCount())
-        rowsInsertedArtifficial(QModelIndex(), 0,
-                                                 d->proxyModel->rowCount() - 1);
+void KListView::updateGeometries()
+{
+    if ((viewMode() == KListView::ListMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        QListView::updateGeometries();
+        return;
+    }
+
+    // Avoid QListView::updateGeometries(), since it will try to set another
+    // range to our scroll bars, what we don't want (ereslibre)
+    QAbstractItemView::updateGeometries();
+}
+
+void KListView::slotSortingRoleChanged()
+{
+    if (d->proxyModel)
+    {
+        // Force the view to update all elements
+        rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() -
+                                                                             1);
+    }
 }
 
 #include "klistview.moc"
index c8bd5215e83a3f87e0d82a279c1cfe19c87c5db2..ee02b5ff70f3895569414e533ea6978f8fc6bb5e 100644 (file)
@@ -18,8 +18,8 @@
   * Boston, MA 02110-1301, USA.
   */
 
-#ifndef __KLISTVIEW_H__
-#define __KLISTVIEW_H__
+#ifndef KLISTVIEW_H
+#define KLISTVIEW_H
 
 #include <QtGui/QListView>
 
@@ -37,9 +37,9 @@ public:
 
     ~KListView();
 
-    void setModel(QAbstractItemModel *model);
+    virtual void setModel(QAbstractItemModel *model);
 
-    QRect visualRect(const QModelIndex &index) const;
+    virtual QRect visualRect(const QModelIndex &index) const;
 
     KItemCategorizer *itemCategorizer() const;
 
@@ -47,42 +47,41 @@ public:
 
     virtual QModelIndex indexAt(const QPoint &point) const;
 
-    virtual int sizeHintForRow(int row) const;
-
+public Q_SLOTS:
+    virtual void reset();
 
 protected:
-    virtual void drawNewCategory(const QString &category,
-                                 const QStyleOptionViewItem &option,
-                                 QPainter *painter);
-
-    virtual int categoryHeight(const QStyleOptionViewItem &option) const;
-
     virtual void paintEvent(QPaintEvent *event);
 
+    virtual void resizeEvent(QResizeEvent *event);
+
     virtual void setSelection(const QRect &rect,
                               QItemSelectionModel::SelectionFlags flags);
 
-    virtual void timerEvent(QTimerEvent *event);
+    virtual void mouseMoveEvent(QMouseEvent *event);
 
+    virtual void mousePressEvent(QMouseEvent *event);
+
+    virtual void mouseReleaseEvent(QMouseEvent *event);
+
+    virtual void leaveEvent(QEvent *event);
 
 protected Q_SLOTS:
     virtual void rowsInserted(const QModelIndex &parent,
                               int start,
                               int end);
 
-    virtual void rowsAboutToBeRemoved(const QModelIndex &parent,
-                                      int start,
-                                      int end);
-
     virtual void rowsInsertedArtifficial(const QModelIndex &parent,
                                          int start,
                                          int end);
 
-    virtual void rowsAboutToBeRemovedArtifficial(const QModelIndex &parent,
-                                                 int start,
-                                                 int end);
+    virtual void rowsRemoved(const QModelIndex &parent,
+                             int start,
+                             int end);
+
+    virtual void updateGeometries();
 
-    virtual void itemsLayoutChanged();
+    virtual void slotSortingRoleChanged();
 
 
 private:
index 5ada074e55d1396722cd9a8ae5de89cf694eca2c..47be30c5630189d2157efb31669779f4c7ddc8bd 100644 (file)
   * Boston, MA 02110-1301, USA.
   */
 
-#ifndef __KLISTVIEW_P_H__
-#define __KLISTVIEW_P_H__
+#ifndef KLISTVIEW_P_H
+#define KLISTVIEW_P_H
 
-class QSortFilterProxyModel;
+class KSortFilterProxyModel;
 
+/**
+  * @internal
+  */
 class KListView::Private
 {
 public:
     Private(KListView *listView);
     ~Private();
 
-    QModelIndexList intersectionSet(const QRect &rect) const;
 
+    // Methods
+
+    /**
+      * Returns the list of items that intersects with @p rect
+      */
+    const QModelIndexList &intersectionSet(const QRect &rect);
+
+    /**
+      * Gets the item rect in the viewport for @p index
+      */
+    QRect visualRectInViewport(const QModelIndex &index) const;
+
+    /**
+      * Returns the category rect in the viewport for @p category
+      */
+    QRect visualCategoryRectInViewport(const QString &category) const;
+
+    /**
+      * Caches and returns the rect that corresponds to @p index
+      */
+    const QRect &cacheIndex(const QModelIndex &index);
+
+    /**
+      * Caches and returns the rect that corresponds to @p category
+      */
+    const QRect &cacheCategory(const QString &category);
+
+    /**
+      * Returns the rect that corresponds to @p index
+      * @note If the rect is not cached, it becomes cached
+      */
+    const QRect &cachedRectIndex(const QModelIndex &index);
+
+    /**
+      * Returns the rect that corresponds to @p category
+      * @note If the rect is not cached, it becomes cached
+      */
+    const QRect &cachedRectCategory(const QString &category);
+
+    /**
+      * Returns the visual rect (taking in count x and y offsets) for @p index
+      * @note If the rect is not cached, it becomes cached
+      */
+    QRect visualRect(const QModelIndex &index);
+
+    /**
+      * Returns the visual rect (taking in count x and y offsets) for @p category
+      * @note If the rect is not cached, it becomes cached
+      */
+    QRect categoryVisualRect(const QString &category);
+
+    /**
+      * This method will draw a new category with name @p category on the rect
+      * specified by @p option.rect, with painter @p painter
+      */
+    void drawNewCategory(const QString &category,
+                         const QStyleOption &option,
+                         QPainter *painter);
+
+    /**
+      * This method will update scrollbars ranges. Called when our model changes
+      * or when the view is resized
+      */
+    void updateScrollbars();
+
+
+    // Attributes
+
+    struct ElementInfo
+    {
+        QString category;
+        int relativeOffsetToCategory;
+    };
+
+    // Basic data
     KListView *listView;
-    QModelIndex hovered;
-    bool modelSortCapable;
-    int numCategories;
-    QList<QString> categories;
-    QHash<QString, int> elementsPerCategory;
     KItemCategorizer *itemCategorizer;
-    QSortFilterProxyModel *proxyModel;
+
+    // Behavior data
+    bool mouseButtonPressed;
+    QModelIndex hovered;
+    QPoint initialPressPosition;
+    QPoint mousePosition;
+
+    // Cache data
+    // We cannot merge some of them into structs because it would affect
+    // performance
+    QHash<QModelIndex, struct ElementInfo> elementsInfo; // in source model
+    QHash<QModelIndex, QRect> elementsPosition;          // in source model
+    QHash<QModelIndex, QModelIndex> elementDictionary;   // mapped indexes
+    QHash<QString, QModelIndexList> categoriesIndexes;
+    QHash<QString, QRect> categoriesPosition;
+    QStringList categories;
+    QModelIndexList intersectedIndexes;
+    QModelIndexList tempSelected;
+
+    // Attributes for speed reasons
+    KSortFilterProxyModel *proxyModel;
+    QModelIndexList sourceModelIndexList;                // in source model
+    QModelIndex lastIndex;
 };
 
 #endif // __KLISTVIEW_P_H__
diff --git a/src/ksortfilterproxymodel.cpp b/src/ksortfilterproxymodel.cpp
new file mode 100644 (file)
index 0000000..b716c39
--- /dev/null
@@ -0,0 +1,36 @@
+/**
+  * This file is part of the KDE project
+  * Copyright (C) 2007 Rafael Fernández López <ereslibre@gmail.com>
+  *
+  * This library is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Library General Public
+  * License as published by the Free Software Foundation; either
+  * version 2 of the License, or (at your option) any later version.
+  *
+  * This library is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * Library General Public License for more details.
+  *
+  * You should have received a copy of the GNU Library General Public License
+  * along with this library; see the file COPYING.LIB.  If not, write to
+  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+  * Boston, MA 02110-1301, USA.
+  */
+
+#include "ksortfilterproxymodel.h"
+
+KSortFilterProxyModel::KSortFilterProxyModel(QObject *parent)
+    : QSortFilterProxyModel(parent)
+{
+}
+
+KSortFilterProxyModel::~KSortFilterProxyModel()
+{
+}
+
+bool KSortFilterProxyModel::lessThanCategoryPurpose(const QModelIndex &left,
+                                                    const QModelIndex &right) const
+{
+    return lessThan(left, right);
+}
diff --git a/src/ksortfilterproxymodel.h b/src/ksortfilterproxymodel.h
new file mode 100644 (file)
index 0000000..efe12cb
--- /dev/null
@@ -0,0 +1,42 @@
+/**
+  * This file is part of the KDE project
+  * Copyright (C) 2007 Rafael Fernández López <ereslibre@gmail.com>
+  *
+  * This library is free software; you can redistribute it and/or
+  * modify it under the terms of the GNU Library General Public
+  * License as published by the Free Software Foundation; either
+  * version 2 of the License, or (at your option) any later version.
+  *
+  * This library is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  * Library General Public License for more details.
+  *
+  * You should have received a copy of the GNU Library General Public License
+  * along with this library; see the file COPYING.LIB.  If not, write to
+  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+  * Boston, MA 02110-1301, USA.
+  */
+
+#ifndef KSORTFILTERPROXYMODEL_H
+#define KSORTFILTERPROXYMODEL_H
+
+#include <QtGui/QSortFilterProxyModel>
+
+#include <libdolphin_export.h>
+
+class LIBDOLPHINPRIVATE_EXPORT KSortFilterProxyModel
+    : public QSortFilterProxyModel
+{
+public:
+    KSortFilterProxyModel(QObject *parent = 0);
+    ~KSortFilterProxyModel();
+
+    virtual bool lessThanGeneralPurpose(const QModelIndex &left,
+                                        const QModelIndex &right) const = 0;
+
+    virtual bool lessThanCategoryPurpose(const QModelIndex &left,
+                                         const QModelIndex &right) const;
+};
+
+#endif