From 11f0a8c50310ebbcaa93318cb097077482268cdd Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Sun, 17 Jun 2007 15:32:31 +0000 Subject: [PATCH] New and powerful KListView. Still pending class renaming. There are two 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 | 1 + src/dolphiniconsview.cpp | 2 +- src/dolphinitemcategorizer.cpp | 67 +- src/dolphinsortfilterproxymodel.cpp | 144 ++++- src/dolphinsortfilterproxymodel.h | 7 +- src/kitemcategorizer.h | 4 +- src/klistview.cpp | 927 +++++++++++++++++++++------- src/klistview.h | 41 +- src/klistview_p.h | 114 +++- src/ksortfilterproxymodel.cpp | 36 ++ src/ksortfilterproxymodel.h | 42 ++ 11 files changed, 1114 insertions(+), 271 deletions(-) create mode 100644 src/ksortfilterproxymodel.cpp create mode 100644 src/ksortfilterproxymodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 04b4b9bd7..6ca332212 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,6 +15,7 @@ set(dolphinprivate_LIB_SRCS dolphiniconsview.cpp dolphinitemcategorizer.cpp klistview.cpp + ksortfilterproxymodel.cpp dolphinsettings.cpp viewproperties.cpp dolphinsortfilterproxymodel.cpp diff --git a/src/dolphiniconsview.cpp b/src/dolphiniconsview.cpp index 50d7f311e..cf5be61ef 100644 --- a/src/dolphiniconsview.cpp +++ b/src/dolphiniconsview.cpp @@ -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); diff --git a/src/dolphinitemcategorizer.cpp b/src/dolphinitemcategorizer.cpp index 05c23a01d..cea6bebd5 100644 --- a/src/dolphinitemcategorizer.cpp +++ b/src/dolphinitemcategorizer.cpp @@ -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(index.model()); - const KDirModel* dirModel = static_cast(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(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; } diff --git a/src/dolphinsortfilterproxymodel.cpp b/src/dolphinsortfilterproxymodel.cpp index 5f6cb3575..9b901d285 100644 --- a/src/dolphinsortfilterproxymodel.cpp +++ b/src/dolphinsortfilterproxymodel.cpp @@ -2,6 +2,7 @@ * Copyright (C) 2006 by Peter Penz * * Copyright (C) 2006 by Dominic Battre * * Copyright (C) 2006 by Martin Pool * + * Copyright (C) 2007 by Rafael Fernández López * * * * 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(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(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); } diff --git a/src/dolphinsortfilterproxymodel.h b/src/dolphinsortfilterproxymodel.h index 437ebca12..c00032f3f 100644 --- a/src/dolphinsortfilterproxymodel.h +++ b/src/dolphinsortfilterproxymodel.h @@ -20,7 +20,7 @@ #ifndef DOLPHINSORTFILTERPROXYMODEL_H #define DOLPHINSORTFILTERPROXYMODEL_H -#include +#include #include #include @@ -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; diff --git a/src/kitemcategorizer.h b/src/kitemcategorizer.h index 7bec65637..1d64206eb 100644 --- a/src/kitemcategorizer.h +++ b/src/kitemcategorizer.h @@ -18,8 +18,8 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __KITEMCATEGORIZER_H__ -#define __KITEMCATEGORIZER_H__ +#ifndef KITEMCATEGORIZER_H +#define KITEMCATEGORIZER_H #include diff --git a/src/klistview.cpp b/src/klistview.cpp index 1d4e41536..b38f18263 100644 --- a/src/klistview.cpp +++ b/src/klistview.cpp @@ -18,25 +18,64 @@ * Boston, MA 02110-1301, USA. */ -// NOTE: rectForIndex() not virtual on QListView !! relevant ? #include "klistview.h" #include "klistview_p.h" -#include -#include -#include -#include +#include // trunc + +#include +#include +#include +#include #include +#include #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(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(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(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(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" diff --git a/src/klistview.h b/src/klistview.h index c8bd5215e..ee02b5ff7 100644 --- a/src/klistview.h +++ b/src/klistview.h @@ -18,8 +18,8 @@ * Boston, MA 02110-1301, USA. */ -#ifndef __KLISTVIEW_H__ -#define __KLISTVIEW_H__ +#ifndef KLISTVIEW_H +#define KLISTVIEW_H #include @@ -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: diff --git a/src/klistview_p.h b/src/klistview_p.h index 5ada074e5..47be30c56 100644 --- a/src/klistview_p.h +++ b/src/klistview_p.h @@ -18,27 +18,121 @@ * 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 categories; - QHash 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 elementsInfo; // in source model + QHash elementsPosition; // in source model + QHash elementDictionary; // mapped indexes + QHash categoriesIndexes; + QHash 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 index 000000000..b716c399b --- /dev/null +++ b/src/ksortfilterproxymodel.cpp @@ -0,0 +1,36 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López + * + * 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 index 000000000..efe12cbc0 --- /dev/null +++ b/src/ksortfilterproxymodel.h @@ -0,0 +1,42 @@ +/** + * This file is part of the KDE project + * Copyright (C) 2007 Rafael Fernández López + * + * 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 + +#include + +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 -- 2.47.3