]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/klistview.cpp
Better drawing when big selection rect is drawn.
[dolphin.git] / src / klistview.cpp
index a7244dfed4ca1da85d05f9fec3c277ca52ddc070..526fb8dfcdf94bca5201f63eb0b4be1e45aef825 100644 (file)
   * Boston, MA 02110-1301, USA.
   */
 
-// NOTE: rectForIndex() not virtual on QListView !! relevant ?
+#include "klistview.h"
+#include "klistview_p.h"
 
+#include <math.h> // trunc on C99 compliant systems
+#include <kdefakes.h> // trunc for not C99 compliant systems
+
+#include <QApplication>
 #include <QPainter>
 #include <QScrollBar>
 #include <QPaintEvent>
-#include <QSortFilterProxyModel>
 
 #include <kdebug.h>
+#include <kstyle.h>
 
-#include "klistview.h"
-#include "klistview_p.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->sortOrder() == Qt::AscendingOrder ?
+                   proxyModel->lessThanGeneralPurpose(left, right) :
+                   !proxyModel->lessThanGeneralPurpose(left, right);
+        }
+
+        return proxyModel->sortOrder() == Qt::AscendingOrder ?
+               proxyModel->lessThanCategoryPurpose(left, right) :
+               !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)
+    , isDragging(false)
+    , dragLeftViewport(false)
     , proxyModel(0)
+    , lastIndex(QModelIndex())
 {
 }
 
@@ -44,148 +90,407 @@ 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)
+    {
+        middle = (top + bottom) / 2;
+
+        index = elementDictionary[proxyModel->index(middle, 0)];
+        indexVisualRect = visualRect(index);
+
+        if (qMax(indexVisualRect.topLeft().y(),
+                 indexVisualRect.bottomRight().y()) < qMin(rect.topLeft().y(),
+                                                        rect.bottomRight().y()))
+        {
+            bottom = middle + 1;
+        }
+        else
+        {
+            top = middle - 1;
+        }
+    }
+
+    for (int i = middle; i < proxyModel->rowCount(); i++)
     {
-        index = listView->model()->index(i, 0);
+        index = elementDictionary[proxyModel->index(i, 0)];
+        indexVisualRect = visualRect(index);
+
+        if (rect.intersects(indexVisualRect))
+            intersectedIndexes.append(index);
 
-        if (rect.intersects(listView->visualRect(index)))
-            modelIndexList.append(index);
+        // If we passed next item, stop searching for hits
+        if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
+                                                  qMin(indexVisualRect.topLeft().y(),
+                                                       indexVisualRect.bottomRight().y()))
+            break;
     }
 
-    return modelIndexList;
+    return intersectedIndexes;
 }
 
-KListView::KListView(QWidget *parent)
-    : QListView(parent)
-    , d(new Private(this))
+QRect KListView::Private::visualRectInViewport(const QModelIndex &index) const
 {
+    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)
+    {
+        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);
+    }
+
+    retRect.setTop(retRect.top() + row * listView->spacing() +
+                   row * itemHeight);
+
+    retRect.setWidth(itemWidth);
+    retRect.setHeight(itemHeight);
+
+    return retRect;
 }
 
-KListView::~KListView()
+QRect KListView::Private::visualCategoryRectInViewport(const QString &category)
+                                                                           const
 {
-    if (d->proxyModel)
+    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 (!elementsPerRow)
+        elementsPerRow++;
+
+    float rows;
+    int rowsInt;
+    foreach (const QString &itCategory, categories)
     {
-        QObject::disconnect(this->model(), SIGNAL(layoutChanged()),
-                            this         , SLOT(itemsLayoutChanged()));
+        if (itCategory == category)
+            break;
+
+        rows = (float) ((float) categoriesIndexes[itCategory].count() /
+                        (float) elementsPerRow);
+        rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
+
+        if (rows - trunc(rows)) rowsInt++;
+
+        retRect.setTop(retRect.top() +
+                       (rowsInt * listView->spacing()) +
+                       (rowsInt * itemHeight) +
+                       30 /* categoryHeight */ +
+                       listView->spacing() * 2);
     }
 
-    delete d;
+    retRect.setHeight(30 /* categoryHeight */);
+
+    return retRect;
 }
 
-void KListView::setModel(QAbstractItemModel *model)
+// We're sure elementsPosition doesn't contain index
+const QRect &KListView::Private::cacheIndex(const QModelIndex &index)
 {
-    QSortFilterProxyModel *proxyModel =
-                                    qobject_cast<QSortFilterProxyModel*>(model);
+    QRect rect = visualRectInViewport(index);
+    elementsPosition[index] = rect;
 
-    if (this->model() && this->model()->rowCount())
-    {
-        QObject::disconnect(this->model(), SIGNAL(layoutChanged()),
-                            this         , SLOT(itemsLayoutChanged()));
+    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];
+}
 
-        rowsAboutToBeRemovedArtifficial(QModelIndex(), 0,
-                                        this->model()->rowCount() - 1);
+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 KListView::Private::visualRect(const QModelIndex &index)
+{
+    QModelIndex mappedIndex = proxyModel->mapToSource(index);
+
+    QRect retRect = cachedRectIndex(mappedIndex);
+    int dx = -listView->horizontalOffset();
+    int dy = -listView->verticalOffset();
+    retRect.adjust(dx, dy, dx, dy);
+
+    return retRect;
+}
 
-    d->modelSortCapable = (proxyModel != 0);
-    d->proxyModel = proxyModel;
+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);
+
+    return retRect;
+}
 
-    // 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())
+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 ((category == hoveredCategory) && !mouseButtonPressed)
     {
-        rowsInsertedArtifficial(QModelIndex(), 0, model->rowCount() - 1);
+        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);
     }
 
-    QListView::setModel(model);
+    /*if (const KStyle *style = dynamic_cast<const KStyle*>(QApplication::style()))
+        {
+        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();
+}
+
 
-    QObject::connect(model, SIGNAL(layoutChanged()),
-                     this , SLOT(itemsLayoutChanged()));
+void KListView::Private::updateScrollbars()
+{
+    int lastItemBottom = cachedRectIndex(lastIndex).bottom() +
+                           listView->spacing() - listView->viewport()->height();
+
+    listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
+    listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
+    listView->verticalScrollBar()->setRange(0, lastItemBottom);
 }
 
-QRect KListView::visualRect(const QModelIndex &index) const
+void KListView::Private::drawDraggedItems(QPainter *painter)
 {
-    // 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)
+    QStyleOptionViewItemV3 option = listView->viewOptions();
+    option.state &= ~QStyle::State_MouseOver;
+    int dx;
+    int dy;
+    foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
+    {
+        dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
+        dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
 
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
-        !d->itemCategorizer)
+        option.rect = visualRect(index);
+        option.rect.adjust(dx, dy, dx, dy);
+
+        listView->itemDelegate(index)->paint(painter, option, index);
+    }
+}
+
+void KListView::Private::drawDraggedItems()
+{
+    int dx;
+    int dy;
+    QRect rectToUpdate;
+    QRect currentRect;
+    foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
     {
-        return QListView::visualRect(index);
+        dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
+        dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
+
+        currentRect = visualRect(index);
+        currentRect.adjust(dx, dy, dx, dy);
+
+        rectToUpdate = rectToUpdate.united(currentRect);
     }
 
-    QRect retRect(spacing(), spacing(), 0, 0);
-    int viewportWidth = viewport()->width() - spacing();
-    int dx = -horizontalOffset();
-    int dy = -verticalOffset();
+    listView->viewport()->update(lastDraggedItemsRect);
 
-    if (verticalScrollBar() && !verticalScrollBar()->isHidden())
-        viewportWidth -= verticalScrollBar()->width();
+    lastDraggedItemsRect = rectToUpdate;
 
-    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();
+    listView->viewport()->update(rectToUpdate);
+}
 
-    int rowsForCategory;
-    int lastIndexShown = -1;
-    foreach (QString category, d->categories)
-    {
-        retRect.setTop(retRect.top() + spacing());
 
-        if (category == itemCategory)
-        {
-            break;
-        }
+//==============================================================================
 
-        rowsForCategory = (d->elementsPerCategory[category] / elementsPerRow);
 
-        if ((d->elementsPerCategory[category] % elementsPerRow) ||
-            !rowsForCategory)
-        {
-            rowsForCategory++;
-        }
+KListView::KListView(QWidget *parent)
+    : QListView(parent)
+    , d(new Private(this))
+{
+}
 
-        lastIndexShown += d->elementsPerCategory[category];
+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)));
 
-        retRect.setTop(retRect.top() + categoryHeight(viewOptions()) +
-                       (rowsForCategory * spacing() * 2) +
-                       (rowsForCategory * itemHeight));
+        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::IconMode) || !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 +500,92 @@ 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);
+    }
+    else
+    {
+        updateGeometries();
+    }
 }
 
 QModelIndex KListView::indexAt(const QPoint &point) const
 {
-    QModelIndex index;
-
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    if ((viewMode() != KListView::IconMode) || !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::IconMode) || !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::IconMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         QListView::paintEvent(event);
         return;
     }
 
-    if (!itemDelegate())
-        return;
-
     QStyleOptionViewItemV3 option = viewOptions();
     QPainter painter(viewport());
     QRect area = event->rect();
@@ -270,16 +594,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 +621,7 @@ void KListView::paintEvent(QPaintEvent *event)
             }
             option.palette.setCurrentColorGroup(cg);
         }
+
         if (focus && currentIndex() == index)
         {
             option.state |= QStyle::State_HasFocus;
@@ -301,149 +629,504 @@ 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);
+    }
+
+    if (d->mouseButtonPressed && !d->isDragging)
+    {
+        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())
         {
-            prevCategory = d->itemCategorizer->categoryForItem(index,
-                                                     d->proxyModel->sortRole());
-            drawNewCategory(prevCategory, option, &painter);
+            start = d->mousePosition;
+            end = initialPressPosition;
         }
-        itemDelegate(index)->paint(&painter, option, index);
+        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();
     }
+
+    // Redraw categories
+    QStyleOptionViewItem otherOption;
+    foreach (const QString &category, d->categories)
+    {
+        otherOption = option;
+        otherOption.rect = d->categoryVisualRect(category);
+
+        if (otherOption.rect.intersects(area))
+        {
+            d->drawNewCategory(category, otherOption, &painter);
+        }
+    }
+
+    if (d->isDragging && !d->dragLeftViewport)
+    {
+        painter.setOpacity(0.5);
+        d->drawDraggedItems(&painter);
+    }
+
+    painter.restore();
+}
+
+void KListView::resizeEvent(QResizeEvent *event)
+{
+    QListView::resizeEvent(event);
+
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    // Clear the items positions cache
+    d->elementsPosition.clear();
+    d->categoriesPosition.clear();
+
+    d->updateScrollbars();
 }
 
 void KListView::setSelection(const QRect &rect,
                              QItemSelectionModel::SelectionFlags flags)
 {
-    // TODO: implement me
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        QListView::setSelection(rect, flags);
+        return;
+    }
+
+    if (!flags)
+        return;
 
-    QListView::setSelection(rect, flags);
+    selectionModel()->clear();
+
+    if (flags & QItemSelectionModel::Clear)
+    {
+        d->lastSelection = QItemSelection();
+    }
+
+    QModelIndexList dirtyIndexes = d->intersectionSet(rect);
+    QItemSelection selection;
+
+    if (!dirtyIndexes.count())
+    {
+        if (d->lastSelection.count())
+        {
+            selectionModel()->select(d->lastSelection, flags);
+        }
+
+        return;
+    }
+
+    if (!d->mouseButtonPressed)
+    {
+        selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
+    }
+    else
+    {
+        QModelIndex first = dirtyIndexes[0];
+        QModelIndex last;
+        foreach (const QModelIndex &index, dirtyIndexes)
+        {
+            if (last.isValid() && last.row() + 1 != index.row())
+            {
+                QItemSelectionRange range(first, last);
+
+                selection << range;
+
+                first = index;
+            }
 
-    /*if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+            last = index;
+        }
+
+        if (last.isValid())
+            selection << QItemSelectionRange(first, last);
+    }
+
+    if (d->lastSelection.count() && !d->mouseButtonPressed)
+    {
+        selection.merge(d->lastSelection, flags);
+    }
+    else if (d->lastSelection.count())
+    {
+        selection.merge(d->lastSelection, QItemSelectionModel::Select);
+    }
+
+    selectionModel()->select(selection, flags);
+}
+
+void KListView::mouseMoveEvent(QMouseEvent *event)
+{
+    QListView::mouseMoveEvent(event);
+
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
-        QListView::setSelection(rect, flags);
         return;
     }
 
-    QModelIndex index;
-    for (int i = 0; i < d->proxyModel->rowCount(); i++)
+    d->mousePosition = event->pos();
+    d->hoveredCategory = QString();
+
+    // Redraw categories
+    foreach (const QString &category, d->categories)
     {
-        index = d->proxyModel->index(i, 0);
-        if (rect.intersects(visualRect(index)))
+        if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
         {
-            selectionModel()->select(index, QItemSelectionModel::Select);
+            d->hoveredCategory = category;
+        }
+
+        viewport()->update(d->categoryVisualRect(category));
+    }
+
+    QRect rect;
+    if (d->mouseButtonPressed && !d->isDragging)
+    {
+        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
         {
-            selectionModel()->select(index, QItemSelectionModel::Deselect);
+            start = initialPressPosition;
+            end = d->mousePosition;
         }
-    }*/
 
-    //selectionModel()->select(selection, flags);
+        viewport()->update(d->lastSelectionRect);
+
+        rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
+
+        viewport()->update(rect);
+
+        d->lastSelectionRect = rect;
+    }
 }
 
-void KListView::timerEvent(QTimerEvent *event)
+void KListView::mousePressEvent(QMouseEvent *event)
 {
-    QListView::timerEvent(event);
+    QListView::mousePressEvent(event);
 
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         return;
     }
+
+    d->dragLeftViewport = false;
+
+    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());
+    }
 }
 
-void KListView::rowsInserted(const QModelIndex &parent,
-                             int start,
-                             int end)
+void KListView::mouseReleaseEvent(QMouseEvent *event)
 {
-    QListView::rowsInserted(parent, start, end);
-    rowsInsertedArtifficial(parent, start, end);
+    QListView::mouseReleaseEvent(event);
+
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    d->mouseButtonPressed = false;
+
+    QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
+    initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
+    initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
+
+    QItemSelection selection;
+
+    if (initialPressPosition == d->initialPressPosition)
+    {
+        foreach(const QString &category, d->categories)
+        {
+            if (d->categoryVisualRect(category).contains(event->pos()))
+            {
+                QItemSelectionRange selectionRange(d->proxyModel->mapFromSource(d->categoriesIndexes[category][0]),
+                                                   d->proxyModel->mapFromSource(d->categoriesIndexes[category][d->categoriesIndexes[category].count() - 1]));
+
+                selection << selectionRange;
+
+                selectionModel()->select(selection, QItemSelectionModel::Select);
+
+                break;
+            }
+        }
+    }
+
+    d->lastSelection = selectionModel()->selection();
+
+    if (d->hovered.isValid())
+        viewport()->update(d->visualRect(d->hovered));
+    else if (!d->hoveredCategory.isEmpty())
+        viewport()->update(d->categoryVisualRect(d->hoveredCategory));
 }
 
-void KListView::rowsAboutToBeRemoved(const QModelIndex &parent,
-                                     int start,
-                                     int end)
+void KListView::leaveEvent(QEvent *event)
 {
-    QListView::rowsAboutToBeRemoved(parent, start, end);
-    rowsAboutToBeRemovedArtifficial(parent, start, end);
+    QListView::leaveEvent(event);
+
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    d->hovered = QModelIndex();
+    d->hoveredCategory = QString();
 }
 
-void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
-                                        int start,
-                                        int end)
+void KListView::startDrag(Qt::DropActions supportedActions)
 {
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    QListView::startDrag(supportedActions);
+
+    d->isDragging = false;
+    d->mouseButtonPressed = false;
+}
+
+void KListView::dragMoveEvent(QDragMoveEvent *event)
+{
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
+        QListView::dragMoveEvent(event);
         return;
     }
 
-    QString category;
-    QModelIndex index;
-    for (int i = start; i <= end; i++)
+    d->mousePosition = event->pos();
+
+    if (d->mouseButtonPressed)
     {
-        index = d->proxyModel->index(i, 0, parent);
-        category = d->itemCategorizer->categoryForItem(index,
-                                                     d->proxyModel->sortRole());
+        d->isDragging = true;
+    }
+    else
+    {
+        d->isDragging = false;
+    }
 
-        if (d->elementsPerCategory.contains(category))
-            d->elementsPerCategory[category]++;
-        else
-        {
-            d->elementsPerCategory.insert(category, 1);
-            d->categories.append(category);
-        }
+    d->dragLeftViewport = false;
+
+    d->drawDraggedItems();
+}
+
+void KListView::dragLeaveEvent(QDragLeaveEvent *event)
+{
+    QListView::dragLeaveEvent(event);
+
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return;
+    }
+
+    d->dragLeftViewport = true;
+}
+
+QModelIndex KListView::moveCursor(CursorAction cursorAction,
+                                  Qt::KeyboardModifiers modifiers)
+{
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
+        !d->itemCategorizer)
+    {
+        return QListView::moveCursor(cursorAction, modifiers);
     }
+
+    return QListView::moveCursor(cursorAction, modifiers);
 }
 
-void KListView::rowsAboutToBeRemovedArtifficial(const QModelIndex &parent,
-                                                int start,
-                                                int end)
+void KListView::rowsInserted(const QModelIndex &parent,
+                             int start,
+                             int end)
 {
-    if ((viewMode() == KListView::ListMode) || !d->modelSortCapable ||
+    QListView::rowsInserted(parent, start, end);
+
+    if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
         !d->itemCategorizer)
     {
         return;
     }
 
+    rowsInsertedArtifficial(parent, start, end);
+}
+
+void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
+                                        int start,
+                                        int end)
+{
+    d->lastSelection = QItemSelection();
+    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 k = 0; k < d->proxyModel->rowCount(); k++)
     {
-        index = d->proxyModel->index(i, 0, parent);
-        category = d->itemCategorizer->categoryForItem(index,
+        d->sourceModelIndexList <<
+                         d->proxyModel->mapToSource(d->proxyModel->index(k, 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());
+
+        elementInfo.category = lastCategory;
 
-        if (d->elementsPerCategory.contains(category))
+        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 ((viewMode() == KListView::IconMode) && d->proxyModel &&
+        d->itemCategorizer)
+    {
+        // 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::IconMode) || !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 ((viewMode() == KListView::IconMode) && d->proxyModel &&
+        d->itemCategorizer)
+    {
+        // Force the view to update all elements
+        rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1);
+    }
 }
 
 #include "klistview.moc"