* 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 *
DolphinSortFilterProxyModel::DolphinSortFilterProxyModel(QObject* parent) :
- QSortFilterProxyModel(parent),
+ KSortFilterProxyModel(parent),
m_sortColumn(0),
m_sorting(DolphinView::SortByName),
m_sortOrder(Qt::AscendingOrder)
setDynamicSortFilter(true);
// sort by the user visible string for now
- setSortRole(Qt::DisplayRole);
+ setSortRole(DolphinView::SortByName);
setSortCaseSensitivity(Qt::CaseInsensitive);
sort(KDirModel::Name, Qt::AscendingOrder);
}
m_sorting = (column >= 0) && (column < dolphinMapSize) ?
dirModelColumnToDolphinView[column] :
DolphinView::SortByName;
+ setSortRole(m_sorting);
QSortFilterProxyModel::sort(column, sortOrder);
}
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);
}
* 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())
{
}
{
}
-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
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();
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;
}
option.palette.setCurrentColorGroup(cg);
}
+
if (focus && currentIndex() == index)
{
option.state |= QStyle::State_HasFocus;
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"