From 23b91d0c36d4d8a6726b243276cb7b4fc2169386 Mon Sep 17 00:00:00 2001 From: Rafael Fernández López Date: Mon, 9 Jul 2007 18:08:20 +0000 Subject: Rename KListView to KCategorizedView as decided svn path=/trunk/KDE/kdebase/apps/; revision=685767 --- src/CMakeLists.txt | 2 +- src/dolphiniconsview.cpp | 16 +- src/dolphiniconsview.h | 4 +- src/kcategorizedview.cpp | 1240 ++++++++++++++++++++++++++++++++++++++++++++++ src/kcategorizedview.h | 119 +++++ src/kcategorizedview_p.h | 159 ++++++ src/klistview.cpp | 1240 ---------------------------------------------- src/klistview.h | 119 ----- src/klistview_p.h | 159 ------ 9 files changed, 1529 insertions(+), 1529 deletions(-) create mode 100644 src/kcategorizedview.cpp create mode 100644 src/kcategorizedview.h create mode 100644 src/kcategorizedview_p.h delete mode 100644 src/klistview.cpp delete mode 100644 src/klistview.h delete mode 100644 src/klistview_p.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6b3b5e9ee..802e4ffdd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,7 @@ set(dolphinprivate_LIB_SRCS dolphiniconsview.cpp dolphincolumnview.cpp dolphinitemcategorizer.cpp - klistview.cpp + kcategorizedview.cpp ksortfilterproxymodel.cpp kitemcategorizer.cpp dolphinsettings.cpp diff --git a/src/dolphiniconsview.cpp b/src/dolphiniconsview.cpp index 968d7c94c..237a319c8 100644 --- a/src/dolphiniconsview.cpp +++ b/src/dolphiniconsview.cpp @@ -33,7 +33,7 @@ #include DolphinIconsView::DolphinIconsView(QWidget* parent, DolphinController* controller) : - KListView(parent), + KCategorizedView(parent), m_controller(controller), m_dragging(false) { @@ -68,7 +68,7 @@ DolphinIconsView::DolphinIconsView(QWidget* parent, DolphinController* controlle const IconsModeSettings* settings = DolphinSettings::instance().iconsModeSettings(); Q_ASSERT(settings != 0); - m_viewOptions = KListView::viewOptions(); + m_viewOptions = KCategorizedView::viewOptions(); m_viewOptions.showDecorationSelected = true; QFont font(settings->fontFamily(), settings->fontSize()); @@ -100,7 +100,7 @@ QStyleOptionViewItem DolphinIconsView::viewOptions() const void DolphinIconsView::contextMenuEvent(QContextMenuEvent* event) { - KListView::contextMenuEvent(event); + KCategorizedView::contextMenuEvent(event); m_controller->triggerContextMenuRequest(event->pos()); } @@ -114,7 +114,7 @@ void DolphinIconsView::mousePressEvent(QMouseEvent* event) } } - KListView::mousePressEvent(event); + KCategorizedView::mousePressEvent(event); } void DolphinIconsView::dragEnterEvent(QDragEnterEvent* event) @@ -127,7 +127,7 @@ void DolphinIconsView::dragEnterEvent(QDragEnterEvent* event) void DolphinIconsView::dragLeaveEvent(QDragLeaveEvent* event) { - KListView::dragLeaveEvent(event); + KCategorizedView::dragLeaveEvent(event); // TODO: remove this code when the issue #160611 is solved in Qt 4.4 m_dragging = false; @@ -136,7 +136,7 @@ void DolphinIconsView::dragLeaveEvent(QDragLeaveEvent* event) void DolphinIconsView::dragMoveEvent(QDragMoveEvent* event) { - KListView::dragMoveEvent(event); + KCategorizedView::dragMoveEvent(event); // TODO: remove this code when the issue #160611 is solved in Qt 4.4 const QModelIndex index = indexAt(event->pos()); @@ -156,13 +156,13 @@ void DolphinIconsView::dropEvent(QDropEvent* event) event->acceptProposedAction(); } } - KListView::dropEvent(event); + KCategorizedView::dropEvent(event); m_dragging = false; } void DolphinIconsView::paintEvent(QPaintEvent* event) { - KListView::paintEvent(event); + KCategorizedView::paintEvent(event); // TODO: remove this code when the issue #160611 is solved in Qt 4.4 if (m_dragging) { diff --git a/src/dolphiniconsview.h b/src/dolphiniconsview.h index 83ca615f0..ec18400c6 100644 --- a/src/dolphiniconsview.h +++ b/src/dolphiniconsview.h @@ -20,7 +20,7 @@ #ifndef DOLPHINICONSVIEW_H #define DOLPHINICONSVIEW_H -#include +#include #include #include #include @@ -33,7 +33,7 @@ class DolphinController; * It is also possible that instead of the icon a preview of the item * content is shown. */ -class LIBDOLPHINPRIVATE_EXPORT DolphinIconsView : public KListView +class LIBDOLPHINPRIVATE_EXPORT DolphinIconsView : public KCategorizedView { Q_OBJECT diff --git a/src/kcategorizedview.cpp b/src/kcategorizedview.cpp new file mode 100644 index 000000000..c12f50e23 --- /dev/null +++ b/src/kcategorizedview.cpp @@ -0,0 +1,1240 @@ +/** + * 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 "kcategorizedview.h" +#include "kcategorizedview_p.h" + +#include // trunc on C99 compliant systems +#include // trunc for not C99 compliant systems + +#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->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; +}; + + +//============================================================================== + + +KCategorizedView::Private::Private(KCategorizedView *listView) + : listView(listView) + , itemCategorizer(0) + , mouseButtonPressed(false) + , isDragging(false) + , dragLeftViewport(false) + , proxyModel(0) + , lastIndex(QModelIndex()) +{ +} + +KCategorizedView::Private::~Private() +{ +} + +const QModelIndexList &KCategorizedView::Private::intersectionSet(const QRect &rect) +{ + QModelIndex index; + QRect indexVisualRect; + + intersectedIndexes.clear(); + + // 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 = elementDictionary[proxyModel->index(i, 0)]; + indexVisualRect = visualRect(index); + + if (rect.intersects(indexVisualRect)) + intersectedIndexes.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 intersectedIndexes; +} + +QRect KCategorizedView::Private::visualRectInViewport(const QModelIndex &index) const +{ + if (!index.isValid()) + return QRect(); + + QString curCategory = elementsInfo[index].category; + + QRect retRect(listView->spacing(), listView->spacing() * 2 + + itemCategorizer->categoryHeight(listView->viewOptions()), 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); + + foreach (const QString &category, categories) + { + if (category == curCategory) + break; + + float rows = (float) ((float) categoriesIndexes[category].count() / + (float) elementsPerRow); + int rowsInt = categoriesIndexes[category].count() / elementsPerRow; + + if (rows - trunc(rows)) rowsInt++; + + retRect.setTop(retRect.top() + + (rowsInt * listView->spacing()) + + (rowsInt * itemHeight) + + itemCategorizer->categoryHeight(listView->viewOptions()) + + listView->spacing() * 2); + } + + retRect.setTop(retRect.top() + row * listView->spacing() + + row * itemHeight); + + retRect.setWidth(itemWidth); + retRect.setHeight(itemHeight); + + return retRect; +} + +QRect KCategorizedView::Private::visualCategoryRectInViewport(const QString &category) + const +{ + 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++; + + foreach (const QString &itCategory, categories) + { + if (itCategory == category) + break; + + float rows = (float) ((float) categoriesIndexes[itCategory].count() / + (float) elementsPerRow); + int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow; + + if (rows - trunc(rows)) rowsInt++; + + retRect.setTop(retRect.top() + + (rowsInt * listView->spacing()) + + (rowsInt * itemHeight) + + itemCategorizer->categoryHeight(listView->viewOptions()) + + listView->spacing() * 2); + } + + retRect.setHeight(itemCategorizer->categoryHeight(listView->viewOptions())); + + return retRect; +} + +// We're sure elementsPosition doesn't contain index +const QRect &KCategorizedView::Private::cacheIndex(const QModelIndex &index) +{ + QRect rect = visualRectInViewport(index); + elementsPosition[index] = rect; + + return elementsPosition[index]; +} + +// We're sure categoriesPosition doesn't contain category +const QRect &KCategorizedView::Private::cacheCategory(const QString &category) +{ + QRect rect = visualCategoryRectInViewport(category); + categoriesPosition[category] = rect; + + return categoriesPosition[category]; +} + +const QRect &KCategorizedView::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 &KCategorizedView::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 KCategorizedView::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; +} + +QRect KCategorizedView::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; +} + +void KCategorizedView::Private::drawNewCategory(const QModelIndex &index, + int sortRole, + const QStyleOption &option, + QPainter *painter) +{ + QStyleOption optionCopy = option; + const QString category = itemCategorizer->categoryForItem(index, sortRole); + + if ((category == hoveredCategory) && !mouseButtonPressed) + { + optionCopy.state |= QStyle::State_MouseOver; + } + + itemCategorizer->drawCategory(index, + sortRole, + optionCopy, + painter); +} + + +void KCategorizedView::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); +} + +void KCategorizedView::Private::drawDraggedItems(QPainter *painter) +{ + QStyleOptionViewItemV3 option = listView->viewOptions(); + option.state &= ~QStyle::State_MouseOver; + foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes()) + { + const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset(); + const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset(); + + option.rect = visualRect(index); + option.rect.adjust(dx, dy, dx, dy); + + if (option.rect.intersects(listView->viewport()->rect())) + { + listView->itemDelegate(index)->paint(painter, option, index); + } + } +} + +void KCategorizedView::Private::drawDraggedItems() +{ + QRect rectToUpdate; + QRect currentRect; + foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes()) + { + int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset(); + int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset(); + + currentRect = visualRect(index); + currentRect.adjust(dx, dy, dx, dy); + + if (currentRect.intersects(listView->viewport()->rect())) + { + rectToUpdate = rectToUpdate.united(currentRect); + } + } + + listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate)); + + lastDraggedItemsRect = rectToUpdate; +} + + +//============================================================================== + + +KCategorizedView::KCategorizedView(QWidget *parent) + : QListView(parent) + , d(new Private(this)) +{ +} + +KCategorizedView::~KCategorizedView() +{ + delete d; +} + +void KCategorizedView::setModel(QAbstractItemModel *model) +{ + d->lastSelection = QItemSelection(); + d->currentViewIndex = QModelIndex(); + d->forcedSelectionPosition = 0; + d->elementsInfo.clear(); + d->elementsPosition.clear(); + d->elementDictionary.clear(); + d->invertedElementDictionary.clear(); + d->categoriesIndexes.clear(); + d->categoriesPosition.clear(); + d->categories.clear(); + d->intersectedIndexes.clear(); + d->sourceModelIndexList.clear(); + d->hovered = QModelIndex(); + d->mouseButtonPressed = false; + + 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())); + } + + QListView::setModel(model); + + d->proxyModel = dynamic_cast(model); + + if (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())); + } +} + +QRect KCategorizedView::visualRect(const QModelIndex &index) const +{ + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + return QListView::visualRect(index); + } + + if (!qobject_cast(index.model())) + { + return d->visualRect(d->proxyModel->mapFromSource(index)); + } + + return d->visualRect(index); +} + +KItemCategorizer *KCategorizedView::itemCategorizer() const +{ + return d->itemCategorizer; +} + +void KCategorizedView::setItemCategorizer(KItemCategorizer *itemCategorizer) +{ + d->lastSelection = QItemSelection(); + d->currentViewIndex = QModelIndex(); + d->forcedSelectionPosition = 0; + d->elementsInfo.clear(); + d->elementsPosition.clear(); + d->elementDictionary.clear(); + d->invertedElementDictionary.clear(); + d->categoriesIndexes.clear(); + d->categoriesPosition.clear(); + d->categories.clear(); + d->intersectedIndexes.clear(); + d->sourceModelIndexList.clear(); + d->hovered = QModelIndex(); + d->mouseButtonPressed = false; + + 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) + { + rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1); + } + else + { + updateGeometries(); + } +} + +QModelIndex KCategorizedView::indexAt(const QPoint &point) const +{ + if ((viewMode() != KCategorizedView::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; +} + +void KCategorizedView::reset() +{ + QListView::reset(); + + d->lastSelection = QItemSelection(); + d->currentViewIndex = QModelIndex(); + d->forcedSelectionPosition = 0; + d->elementsInfo.clear(); + d->elementsPosition.clear(); + d->elementDictionary.clear(); + d->invertedElementDictionary.clear(); + d->categoriesIndexes.clear(); + d->categoriesPosition.clear(); + d->categories.clear(); + d->intersectedIndexes.clear(); + d->sourceModelIndexList.clear(); + d->hovered = QModelIndex(); + d->mouseButtonPressed = false; +} + +void KCategorizedView::paintEvent(QPaintEvent *event) +{ + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + QListView::paintEvent(event); + return; + } + + QStyleOptionViewItemV3 option = viewOptions(); + QPainter painter(viewport()); + QRect area = event->rect(); + const bool focus = (hasFocus() || viewport()->hasFocus()) && + currentIndex().isValid(); + const QStyle::State state = option.state; + const bool enabled = (state & QStyle::State_Enabled) != 0; + + painter.save(); + + QModelIndexList dirtyIndexes = d->intersectionSet(area); + foreach (const QModelIndex &index, dirtyIndexes) + { + option.state = state; + option.rect = d->visualRect(index); + + if (selectionModel() && selectionModel()->isSelected(index)) + { + option.state |= QStyle::State_Selected; + } + + if (enabled) + { + QPalette::ColorGroup cg; + if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0) + { + option.state &= ~QStyle::State_Enabled; + cg = QPalette::Disabled; + } + else + { + cg = QPalette::Normal; + } + option.palette.setCurrentColorGroup(cg); + } + + if (focus && currentIndex() == index) + { + option.state |= QStyle::State_HasFocus; + if (this->state() == EditingState) + option.state |= QStyle::State_Editing; + } + + if ((index == d->hovered) && !d->mouseButtonPressed) + option.state |= QStyle::State_MouseOver; + else + option.state &= ~QStyle::State_MouseOver; + + itemDelegate(index)->paint(&painter, option, index); + } + + // Redraw categories + int i = 0; + QStyleOptionViewItem otherOption; + foreach (const QString &category, d->categories) + { + otherOption = option; + otherOption.rect = d->categoryVisualRect(category); + otherOption.state &= ~QStyle::State_MouseOver; + + if (otherOption.rect.intersects(area)) + { + d->drawNewCategory(d->categoriesIndexes[category][0], + d->proxyModel->sortRole(), otherOption, &painter); + } + } + + 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 + { + 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(); + } + + if (d->isDragging && !d->dragLeftViewport) + { + painter.setOpacity(0.5); + d->drawDraggedItems(&painter); + } + + painter.restore(); +} + +void KCategorizedView::resizeEvent(QResizeEvent *event) +{ + QListView::resizeEvent(event); + + // Clear the items positions cache + d->elementsPosition.clear(); + d->categoriesPosition.clear(); + d->forcedSelectionPosition = 0; + + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + return; + } + + d->updateScrollbars(); +} + +void KCategorizedView::setSelection(const QRect &rect, + QItemSelectionModel::SelectionFlags flags) +{ + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + QListView::setSelection(rect, flags); + return; + } + + if (!flags) + return; + + 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]); + d->currentViewIndex = 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; + } + + 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 KCategorizedView::mouseMoveEvent(QMouseEvent *event) +{ + QListView::mouseMoveEvent(event); + + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + return; + } + + const QString previousHoveredCategory = d->hoveredCategory; + + d->mousePosition = event->pos(); + d->hoveredCategory = QString(); + + // Redraw categories + foreach (const QString &category, d->categories) + { + if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos()))) + { + d->hoveredCategory = category; + viewport()->update(d->categoryVisualRect(category)); + } + else if ((category == previousHoveredCategory) && + (!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos())))) + { + 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 + { + start = initialPressPosition; + end = d->mousePosition; + } + + rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16)); + + //viewport()->update(rect.united(d->lastSelectionRect)); + + d->lastSelectionRect = rect; + } +} + +void KCategorizedView::mousePressEvent(QMouseEvent *event) +{ + 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()); + } + + QListView::mousePressEvent(event); +} + +void KCategorizedView::mouseReleaseEvent(QMouseEvent *event) +{ + d->mouseButtonPressed = false; + + QListView::mouseReleaseEvent(event); + + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + return; + } + + 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 KCategorizedView::leaveEvent(QEvent *event) +{ + d->hovered = QModelIndex(); + d->hoveredCategory = QString(); + + QListView::leaveEvent(event); +} + +void KCategorizedView::startDrag(Qt::DropActions supportedActions) +{ + QListView::startDrag(supportedActions); + + d->isDragging = false; + d->mouseButtonPressed = false; + + viewport()->update(d->lastDraggedItemsRect); +} + +void KCategorizedView::dragMoveEvent(QDragMoveEvent *event) +{ + d->mousePosition = event->pos(); + + if (d->mouseButtonPressed) + { + d->isDragging = true; + } + else + { + d->isDragging = false; + } + + d->dragLeftViewport = false; + + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + QListView::dragMoveEvent(event); + return; + } + + d->drawDraggedItems(); +} + +void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event) +{ + d->dragLeftViewport = true; + + QListView::dragLeaveEvent(event); +} + +QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers) +{ + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + return QListView::moveCursor(cursorAction, modifiers); + } + + const QModelIndex current = selectionModel()->currentIndex(); + + int viewportWidth = viewport()->width() - 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 = spacing() + itemWidth; + int elementsPerRow = viewportWidth / itemWidthPlusSeparation; + + QString lastCategory = d->categories[0]; + QString theCategory = d->categories[0]; + QString afterCategory = d->categories[0]; + bool hasToBreak = false; + foreach (const QString &category, d->categories) + { + if (hasToBreak) + { + afterCategory = category; + + break; + } + + if (category == d->elementsInfo[d->proxyModel->mapToSource(current)].category) + { + theCategory = category; + + hasToBreak = true; + } + + if (!hasToBreak) + { + lastCategory = category; + } + } + + switch (cursorAction) + { + case QAbstractItemView::MoveUp: { + if (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory >= elementsPerRow) + { + int indexToMove = d->invertedElementDictionary[current].row(); + indexToMove -= qMin(((d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory) + d->forcedSelectionPosition), elementsPerRow - d->forcedSelectionPosition + (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory % elementsPerRow)); + + return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; + } + else + { + int lastCategoryLastRow = (d->categoriesIndexes[lastCategory].count() - 1) % elementsPerRow; + int indexToMove = d->invertedElementDictionary[current].row() - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory; + + if (d->forcedSelectionPosition >= lastCategoryLastRow) + { + indexToMove -= 1; + } + else + { + indexToMove -= qMin((lastCategoryLastRow - d->forcedSelectionPosition + 1), d->forcedSelectionPosition + elementsPerRow + 1); + } + + return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; + } + } + + case QAbstractItemView::MoveDown: { + if (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory < (d->categoriesIndexes[theCategory].count() - 1 - ((d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow))) + { + int indexToMove = d->invertedElementDictionary[current].row(); + indexToMove += qMin(elementsPerRow, d->categoriesIndexes[theCategory].count() - 1 - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory); + + return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; + } + else + { + int afterCategoryLastRow = qMin(elementsPerRow, d->categoriesIndexes[afterCategory].count()); + int indexToMove = d->invertedElementDictionary[current].row() + (d->categoriesIndexes[theCategory].count() - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory); + + if (d->forcedSelectionPosition >= afterCategoryLastRow) + { + indexToMove += afterCategoryLastRow - 1; + } + else + { + indexToMove += qMin(d->forcedSelectionPosition, elementsPerRow); + } + + return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; + } + } + + case QAbstractItemView::MoveLeft: + d->forcedSelectionPosition = d->elementsInfo[d->proxyModel->mapToSource(d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() - 1, 0)])].relativeOffsetToCategory % elementsPerRow; + + if (d->forcedSelectionPosition < 0) + d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow; + + return d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() - 1, 0)]; + + case QAbstractItemView::MoveRight: + d->forcedSelectionPosition = d->elementsInfo[d->proxyModel->mapToSource(d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() + 1, 0)])].relativeOffsetToCategory % elementsPerRow; + + if (d->forcedSelectionPosition < 0) + d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow; + + return d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() + 1, 0)]; + + default: + break; + } + + return QListView::moveCursor(cursorAction, modifiers); +} + +void KCategorizedView::rowsInserted(const QModelIndex &parent, + int start, + int end) +{ + QListView::rowsInserted(parent, start, end); + + if ((viewMode() != KCategorizedView::IconMode) || !d->proxyModel || + !d->itemCategorizer) + { + d->lastSelection = QItemSelection(); + d->currentViewIndex = QModelIndex(); + d->forcedSelectionPosition = 0; + d->elementsInfo.clear(); + d->elementsPosition.clear(); + d->elementDictionary.clear(); + d->invertedElementDictionary.clear(); + d->categoriesIndexes.clear(); + d->categoriesPosition.clear(); + d->categories.clear(); + d->intersectedIndexes.clear(); + d->sourceModelIndexList.clear(); + d->hovered = QModelIndex(); + d->mouseButtonPressed = false; + + return; + } + + rowsInsertedArtifficial(parent, start, end); +} + +void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent, + int start, + int end) +{ + Q_UNUSED(parent); + + d->lastSelection = QItemSelection(); + d->currentViewIndex = QModelIndex(); + d->forcedSelectionPosition = 0; + d->elementsInfo.clear(); + d->elementsPosition.clear(); + d->elementDictionary.clear(); + d->invertedElementDictionary.clear(); + d->categoriesIndexes.clear(); + d->categoriesPosition.clear(); + d->categories.clear(); + d->intersectedIndexes.clear(); + d->sourceModelIndexList.clear(); + d->hovered = QModelIndex(); + d->mouseButtonPressed = false; + + 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++) + { + 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 (prevCategory != lastCategory) + { + d->categoriesIndexes.insert(prevCategory, modelIndexList); + d->categories << prevCategory; + modelIndexList.clear(); + } + + 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)); + + d->invertedElementDictionary.insert(d->proxyModel->mapFromSource(index), + d->proxyModel->index(j, 0)); + + i++; + j++; + } + + i = 0; + } + + d->updateScrollbars(); +} + +void KCategorizedView::rowsRemoved(const QModelIndex &parent, + int start, + int end) +{ + if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel && + d->itemCategorizer) + { + // Force the view to update all elements + rowsInsertedArtifficial(parent, start, end); + } +} + +void KCategorizedView::updateGeometries() +{ + if ((viewMode() != KCategorizedView::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 KCategorizedView::slotSortingRoleChanged() +{ + if ((viewMode() == KCategorizedView::IconMode) && d->proxyModel && + d->itemCategorizer) + { + // Force the view to update all elements + rowsInsertedArtifficial(QModelIndex(), 0, d->proxyModel->rowCount() - 1); + } +} + +#include "kcategorizedview.moc" diff --git a/src/kcategorizedview.h b/src/kcategorizedview.h new file mode 100644 index 000000000..8741a6654 --- /dev/null +++ b/src/kcategorizedview.h @@ -0,0 +1,119 @@ +/** + * 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 KCATEGORIZEDVIEW_H +#define KCATEGORIZEDVIEW_H + +#include + +#include + +class KItemCategorizer; + +/** + * @short Item view for listing items + * + * KCategorizedView allows you to use it as it were a QListView. You can add an + * itemCategorizer to it, so your items became categorized depending on the + * KItemCategorizer inherited class rules. + * + * @see KItemCategorizer, KSortFilterProxyModel + * + * @author Rafael Fernández López + */ +class LIBDOLPHINPRIVATE_EXPORT KCategorizedView + : public QListView +{ + Q_OBJECT + +public: + KCategorizedView(QWidget *parent = 0); + + ~KCategorizedView(); + + virtual void setModel(QAbstractItemModel *model); + + virtual QRect visualRect(const QModelIndex &index) const; + + /** + * Will return the current categorizer. If none set, this method will + * return 0 + */ + KItemCategorizer *itemCategorizer() const; + + /** + * Sets the categorizer to be used. Causes the item view to repaint + */ + void setItemCategorizer(KItemCategorizer *itemCategorizer); + + virtual QModelIndex indexAt(const QPoint &point) const; + +public Q_SLOTS: + virtual void reset(); + +protected: + virtual void paintEvent(QPaintEvent *event); + + virtual void resizeEvent(QResizeEvent *event); + + virtual void setSelection(const QRect &rect, + QItemSelectionModel::SelectionFlags flags); + + virtual void mouseMoveEvent(QMouseEvent *event); + + virtual void mousePressEvent(QMouseEvent *event); + + virtual void mouseReleaseEvent(QMouseEvent *event); + + virtual void leaveEvent(QEvent *event); + + virtual void startDrag(Qt::DropActions supportedActions); + + virtual void dragMoveEvent(QDragMoveEvent *event); + + virtual void dragLeaveEvent(QDragLeaveEvent *event); + + virtual QModelIndex moveCursor(CursorAction cursorAction, + Qt::KeyboardModifiers modifiers); + +protected Q_SLOTS: + virtual void rowsInserted(const QModelIndex &parent, + int start, + int end); + + virtual void rowsInsertedArtifficial(const QModelIndex &parent, + int start, + int end); + + virtual void rowsRemoved(const QModelIndex &parent, + int start, + int end); + + virtual void updateGeometries(); + + virtual void slotSortingRoleChanged(); + + +private: + class Private; + Private *d; +}; + +#endif // KCATEGORIZEDVIEW_H diff --git a/src/kcategorizedview_p.h b/src/kcategorizedview_p.h new file mode 100644 index 000000000..8ce50441b --- /dev/null +++ b/src/kcategorizedview_p.h @@ -0,0 +1,159 @@ +/** + * 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 KCATEGORIZEDVIEW_P_H +#define KCATEGORIZEDVIEW_P_H + +class KSortFilterProxyModel; + +/** + * @internal + */ +class KCategorizedView::Private +{ +public: + Private(KCategorizedView *listView); + ~Private(); + + + // 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 represented by index + * @param index on the rect specified by @p option.rect, with + * painter @p painter + */ + void drawNewCategory(const QModelIndex &index, + int sortRole, + const QStyleOption &option, + QPainter *painter); + + /** + * This method will update scrollbars ranges. Called when our model changes + * or when the view is resized + */ + void updateScrollbars(); + + /** + * This method will draw dragged items in the painting operation + */ + void drawDraggedItems(QPainter *painter); + + /** + * This method will determine which rect needs to be updated because of a + * dragging operation + */ + void drawDraggedItems(); + + + // Attributes + + struct ElementInfo + { + QString category; + int relativeOffsetToCategory; + }; + + // Basic data + KCategorizedView *listView; + KItemCategorizer *itemCategorizer; + + // Behavior data + bool mouseButtonPressed; + bool isDragging; + bool dragLeftViewport; + QModelIndex hovered; + QString hoveredCategory; + QPoint initialPressPosition; + QPoint mousePosition; + QItemSelection lastSelection; + QModelIndex currentViewIndex; + int forcedSelectionPosition; + + // 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 invertedElementDictionary; // mapped indexes + QHash categoriesIndexes; + QHash categoriesPosition; + QStringList categories; + QModelIndexList intersectedIndexes; + QRect lastDraggedItemsRect; + QRect lastSelectionRect; + + // Attributes for speed reasons + KSortFilterProxyModel *proxyModel; + QModelIndexList sourceModelIndexList; // in source model + QModelIndex lastIndex; +}; + +#endif // KCATEGORIZEDVIEW_P_H diff --git a/src/klistview.cpp b/src/klistview.cpp deleted file mode 100644 index 4db18e1f4..000000000 --- a/src/klistview.cpp +++ /dev/null @@ -1,1240 +0,0 @@ -/** - * 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 "klistview.h" -#include "klistview_p.h" - -#include // trunc on C99 compliant systems -#include // trunc for not C99 compliant systems - -#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->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) - , itemCategorizer(0) - , mouseButtonPressed(false) - , isDragging(false) - , dragLeftViewport(false) - , proxyModel(0) - , lastIndex(QModelIndex()) -{ -} - -KListView::Private::~Private() -{ -} - -const QModelIndexList &KListView::Private::intersectionSet(const QRect &rect) -{ - QModelIndex index; - QRect indexVisualRect; - - intersectedIndexes.clear(); - - // 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 = elementDictionary[proxyModel->index(i, 0)]; - indexVisualRect = visualRect(index); - - if (rect.intersects(indexVisualRect)) - intersectedIndexes.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 intersectedIndexes; -} - -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 + - itemCategorizer->categoryHeight(listView->viewOptions()), 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); - - foreach (const QString &category, categories) - { - if (category == curCategory) - break; - - float rows = (float) ((float) categoriesIndexes[category].count() / - (float) elementsPerRow); - int rowsInt = categoriesIndexes[category].count() / elementsPerRow; - - if (rows - trunc(rows)) rowsInt++; - - retRect.setTop(retRect.top() + - (rowsInt * listView->spacing()) + - (rowsInt * itemHeight) + - itemCategorizer->categoryHeight(listView->viewOptions()) + - listView->spacing() * 2); - } - - retRect.setTop(retRect.top() + row * listView->spacing() + - row * itemHeight); - - retRect.setWidth(itemWidth); - retRect.setHeight(itemHeight); - - return retRect; -} - -QRect KListView::Private::visualCategoryRectInViewport(const QString &category) - const -{ - 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++; - - foreach (const QString &itCategory, categories) - { - if (itCategory == category) - break; - - float rows = (float) ((float) categoriesIndexes[itCategory].count() / - (float) elementsPerRow); - int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow; - - if (rows - trunc(rows)) rowsInt++; - - retRect.setTop(retRect.top() + - (rowsInt * listView->spacing()) + - (rowsInt * itemHeight) + - itemCategorizer->categoryHeight(listView->viewOptions()) + - listView->spacing() * 2); - } - - retRect.setHeight(itemCategorizer->categoryHeight(listView->viewOptions())); - - return retRect; -} - -// We're sure elementsPosition doesn't contain index -const QRect &KListView::Private::cacheIndex(const QModelIndex &index) -{ - QRect rect = visualRectInViewport(index); - elementsPosition[index] = rect; - - 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 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; -} - -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; -} - -void KListView::Private::drawNewCategory(const QModelIndex &index, - int sortRole, - const QStyleOption &option, - QPainter *painter) -{ - QStyleOption optionCopy = option; - const QString category = itemCategorizer->categoryForItem(index, sortRole); - - if ((category == hoveredCategory) && !mouseButtonPressed) - { - optionCopy.state |= QStyle::State_MouseOver; - } - - itemCategorizer->drawCategory(index, - sortRole, - optionCopy, - painter); -} - - -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); -} - -void KListView::Private::drawDraggedItems(QPainter *painter) -{ - QStyleOptionViewItemV3 option = listView->viewOptions(); - option.state &= ~QStyle::State_MouseOver; - foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes()) - { - const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset(); - const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset(); - - option.rect = visualRect(index); - option.rect.adjust(dx, dy, dx, dy); - - if (option.rect.intersects(listView->viewport()->rect())) - { - listView->itemDelegate(index)->paint(painter, option, index); - } - } -} - -void KListView::Private::drawDraggedItems() -{ - QRect rectToUpdate; - QRect currentRect; - foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes()) - { - int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset(); - int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset(); - - currentRect = visualRect(index); - currentRect.adjust(dx, dy, dx, dy); - - if (currentRect.intersects(listView->viewport()->rect())) - { - rectToUpdate = rectToUpdate.united(currentRect); - } - } - - listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate)); - - lastDraggedItemsRect = rectToUpdate; -} - - -//============================================================================== - - -KListView::KListView(QWidget *parent) - : QListView(parent) - , d(new Private(this)) -{ -} - -KListView::~KListView() -{ - delete d; -} - -void KListView::setModel(QAbstractItemModel *model) -{ - d->lastSelection = QItemSelection(); - d->currentViewIndex = QModelIndex(); - d->forcedSelectionPosition = 0; - d->elementsInfo.clear(); - d->elementsPosition.clear(); - d->elementDictionary.clear(); - d->invertedElementDictionary.clear(); - d->categoriesIndexes.clear(); - d->categoriesPosition.clear(); - d->categories.clear(); - d->intersectedIndexes.clear(); - d->sourceModelIndexList.clear(); - d->hovered = QModelIndex(); - d->mouseButtonPressed = false; - - 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())); - } - - QListView::setModel(model); - - d->proxyModel = dynamic_cast(model); - - if (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())); - } -} - -QRect KListView::visualRect(const QModelIndex &index) const -{ - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - return QListView::visualRect(index); - } - - if (!qobject_cast(index.model())) - { - return d->visualRect(d->proxyModel->mapFromSource(index)); - } - - return d->visualRect(index); -} - -KItemCategorizer *KListView::itemCategorizer() const -{ - return d->itemCategorizer; -} - -void KListView::setItemCategorizer(KItemCategorizer *itemCategorizer) -{ - d->lastSelection = QItemSelection(); - d->currentViewIndex = QModelIndex(); - d->forcedSelectionPosition = 0; - d->elementsInfo.clear(); - d->elementsPosition.clear(); - d->elementDictionary.clear(); - d->invertedElementDictionary.clear(); - d->categoriesIndexes.clear(); - d->categoriesPosition.clear(); - d->categories.clear(); - d->intersectedIndexes.clear(); - d->sourceModelIndexList.clear(); - d->hovered = QModelIndex(); - d->mouseButtonPressed = false; - - 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) - { - rowsInserted(QModelIndex(), 0, d->proxyModel->rowCount() - 1); - } - else - { - updateGeometries(); - } -} - -QModelIndex KListView::indexAt(const QPoint &point) const -{ - 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; -} - -void KListView::reset() -{ - QListView::reset(); - - d->lastSelection = QItemSelection(); - d->currentViewIndex = QModelIndex(); - d->forcedSelectionPosition = 0; - d->elementsInfo.clear(); - d->elementsPosition.clear(); - d->elementDictionary.clear(); - d->invertedElementDictionary.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::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - QListView::paintEvent(event); - return; - } - - QStyleOptionViewItemV3 option = viewOptions(); - QPainter painter(viewport()); - QRect area = event->rect(); - const bool focus = (hasFocus() || viewport()->hasFocus()) && - currentIndex().isValid(); - const QStyle::State state = option.state; - const bool enabled = (state & QStyle::State_Enabled) != 0; - - painter.save(); - - QModelIndexList dirtyIndexes = d->intersectionSet(area); - foreach (const QModelIndex &index, dirtyIndexes) - { - option.state = state; - option.rect = d->visualRect(index); - - if (selectionModel() && selectionModel()->isSelected(index)) - { - option.state |= QStyle::State_Selected; - } - - if (enabled) - { - QPalette::ColorGroup cg; - if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0) - { - option.state &= ~QStyle::State_Enabled; - cg = QPalette::Disabled; - } - else - { - cg = QPalette::Normal; - } - option.palette.setCurrentColorGroup(cg); - } - - if (focus && currentIndex() == index) - { - option.state |= QStyle::State_HasFocus; - if (this->state() == EditingState) - option.state |= QStyle::State_Editing; - } - - if ((index == d->hovered) && !d->mouseButtonPressed) - option.state |= QStyle::State_MouseOver; - else - option.state &= ~QStyle::State_MouseOver; - - itemDelegate(index)->paint(&painter, option, index); - } - - // Redraw categories - int i = 0; - QStyleOptionViewItem otherOption; - foreach (const QString &category, d->categories) - { - otherOption = option; - otherOption.rect = d->categoryVisualRect(category); - otherOption.state &= ~QStyle::State_MouseOver; - - if (otherOption.rect.intersects(area)) - { - d->drawNewCategory(d->categoriesIndexes[category][0], - d->proxyModel->sortRole(), otherOption, &painter); - } - } - - 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 - { - 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(); - } - - if (d->isDragging && !d->dragLeftViewport) - { - painter.setOpacity(0.5); - d->drawDraggedItems(&painter); - } - - painter.restore(); -} - -void KListView::resizeEvent(QResizeEvent *event) -{ - QListView::resizeEvent(event); - - // Clear the items positions cache - d->elementsPosition.clear(); - d->categoriesPosition.clear(); - d->forcedSelectionPosition = 0; - - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - return; - } - - d->updateScrollbars(); -} - -void KListView::setSelection(const QRect &rect, - QItemSelectionModel::SelectionFlags flags) -{ - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - QListView::setSelection(rect, flags); - return; - } - - if (!flags) - return; - - 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]); - d->currentViewIndex = 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; - } - - 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) - { - return; - } - - const QString previousHoveredCategory = d->hoveredCategory; - - d->mousePosition = event->pos(); - d->hoveredCategory = QString(); - - // Redraw categories - foreach (const QString &category, d->categories) - { - if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos()))) - { - d->hoveredCategory = category; - viewport()->update(d->categoryVisualRect(category)); - } - else if ((category == previousHoveredCategory) && - (!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos())))) - { - 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 - { - start = initialPressPosition; - end = d->mousePosition; - } - - rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16)); - - //viewport()->update(rect.united(d->lastSelectionRect)); - - d->lastSelectionRect = rect; - } -} - -void KListView::mousePressEvent(QMouseEvent *event) -{ - 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()); - } - - QListView::mousePressEvent(event); -} - -void KListView::mouseReleaseEvent(QMouseEvent *event) -{ - d->mouseButtonPressed = false; - - QListView::mouseReleaseEvent(event); - - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - return; - } - - 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::leaveEvent(QEvent *event) -{ - d->hovered = QModelIndex(); - d->hoveredCategory = QString(); - - QListView::leaveEvent(event); -} - -void KListView::startDrag(Qt::DropActions supportedActions) -{ - QListView::startDrag(supportedActions); - - d->isDragging = false; - d->mouseButtonPressed = false; - - viewport()->update(d->lastDraggedItemsRect); -} - -void KListView::dragMoveEvent(QDragMoveEvent *event) -{ - d->mousePosition = event->pos(); - - if (d->mouseButtonPressed) - { - d->isDragging = true; - } - else - { - d->isDragging = false; - } - - d->dragLeftViewport = false; - - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - QListView::dragMoveEvent(event); - return; - } - - d->drawDraggedItems(); -} - -void KListView::dragLeaveEvent(QDragLeaveEvent *event) -{ - d->dragLeftViewport = true; - - QListView::dragLeaveEvent(event); -} - -QModelIndex KListView::moveCursor(CursorAction cursorAction, - Qt::KeyboardModifiers modifiers) -{ - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - return QListView::moveCursor(cursorAction, modifiers); - } - - const QModelIndex current = selectionModel()->currentIndex(); - - int viewportWidth = viewport()->width() - 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 = spacing() + itemWidth; - int elementsPerRow = viewportWidth / itemWidthPlusSeparation; - - QString lastCategory = d->categories[0]; - QString theCategory = d->categories[0]; - QString afterCategory = d->categories[0]; - bool hasToBreak = false; - foreach (const QString &category, d->categories) - { - if (hasToBreak) - { - afterCategory = category; - - break; - } - - if (category == d->elementsInfo[d->proxyModel->mapToSource(current)].category) - { - theCategory = category; - - hasToBreak = true; - } - - if (!hasToBreak) - { - lastCategory = category; - } - } - - switch (cursorAction) - { - case QAbstractItemView::MoveUp: { - if (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory >= elementsPerRow) - { - int indexToMove = d->invertedElementDictionary[current].row(); - indexToMove -= qMin(((d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory) + d->forcedSelectionPosition), elementsPerRow - d->forcedSelectionPosition + (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory % elementsPerRow)); - - return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; - } - else - { - int lastCategoryLastRow = (d->categoriesIndexes[lastCategory].count() - 1) % elementsPerRow; - int indexToMove = d->invertedElementDictionary[current].row() - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory; - - if (d->forcedSelectionPosition >= lastCategoryLastRow) - { - indexToMove -= 1; - } - else - { - indexToMove -= qMin((lastCategoryLastRow - d->forcedSelectionPosition + 1), d->forcedSelectionPosition + elementsPerRow + 1); - } - - return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; - } - } - - case QAbstractItemView::MoveDown: { - if (d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory < (d->categoriesIndexes[theCategory].count() - 1 - ((d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow))) - { - int indexToMove = d->invertedElementDictionary[current].row(); - indexToMove += qMin(elementsPerRow, d->categoriesIndexes[theCategory].count() - 1 - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory); - - return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; - } - else - { - int afterCategoryLastRow = qMin(elementsPerRow, d->categoriesIndexes[afterCategory].count()); - int indexToMove = d->invertedElementDictionary[current].row() + (d->categoriesIndexes[theCategory].count() - d->elementsInfo[d->proxyModel->mapToSource(current)].relativeOffsetToCategory); - - if (d->forcedSelectionPosition >= afterCategoryLastRow) - { - indexToMove += afterCategoryLastRow - 1; - } - else - { - indexToMove += qMin(d->forcedSelectionPosition, elementsPerRow); - } - - return d->elementDictionary[d->proxyModel->index(indexToMove, 0)]; - } - } - - case QAbstractItemView::MoveLeft: - d->forcedSelectionPosition = d->elementsInfo[d->proxyModel->mapToSource(d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() - 1, 0)])].relativeOffsetToCategory % elementsPerRow; - - if (d->forcedSelectionPosition < 0) - d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow; - - return d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() - 1, 0)]; - - case QAbstractItemView::MoveRight: - d->forcedSelectionPosition = d->elementsInfo[d->proxyModel->mapToSource(d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() + 1, 0)])].relativeOffsetToCategory % elementsPerRow; - - if (d->forcedSelectionPosition < 0) - d->forcedSelectionPosition = (d->categoriesIndexes[theCategory].count() - 1) % elementsPerRow; - - return d->elementDictionary[d->proxyModel->index(d->invertedElementDictionary[current].row() + 1, 0)]; - - default: - break; - } - - return QListView::moveCursor(cursorAction, modifiers); -} - -void KListView::rowsInserted(const QModelIndex &parent, - int start, - int end) -{ - QListView::rowsInserted(parent, start, end); - - if ((viewMode() != KListView::IconMode) || !d->proxyModel || - !d->itemCategorizer) - { - d->lastSelection = QItemSelection(); - d->currentViewIndex = QModelIndex(); - d->forcedSelectionPosition = 0; - d->elementsInfo.clear(); - d->elementsPosition.clear(); - d->elementDictionary.clear(); - d->invertedElementDictionary.clear(); - d->categoriesIndexes.clear(); - d->categoriesPosition.clear(); - d->categories.clear(); - d->intersectedIndexes.clear(); - d->sourceModelIndexList.clear(); - d->hovered = QModelIndex(); - d->mouseButtonPressed = false; - - return; - } - - rowsInsertedArtifficial(parent, start, end); -} - -void KListView::rowsInsertedArtifficial(const QModelIndex &parent, - int start, - int end) -{ - Q_UNUSED(parent); - - d->lastSelection = QItemSelection(); - d->currentViewIndex = QModelIndex(); - d->forcedSelectionPosition = 0; - d->elementsInfo.clear(); - d->elementsPosition.clear(); - d->elementDictionary.clear(); - d->invertedElementDictionary.clear(); - d->categoriesIndexes.clear(); - d->categoriesPosition.clear(); - d->categories.clear(); - d->intersectedIndexes.clear(); - d->sourceModelIndexList.clear(); - d->hovered = QModelIndex(); - d->mouseButtonPressed = false; - - 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++) - { - 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 (prevCategory != lastCategory) - { - d->categoriesIndexes.insert(prevCategory, modelIndexList); - d->categories << prevCategory; - modelIndexList.clear(); - } - - 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)); - - d->invertedElementDictionary.insert(d->proxyModel->mapFromSource(index), - d->proxyModel->index(j, 0)); - - i++; - j++; - } - - i = 0; - } - - d->updateScrollbars(); -} - -void KListView::rowsRemoved(const QModelIndex &parent, - int start, - int end) -{ - if ((viewMode() == KListView::IconMode) && d->proxyModel && - d->itemCategorizer) - { - // Force the view to update all elements - rowsInsertedArtifficial(parent, start, end); - } -} - -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" diff --git a/src/klistview.h b/src/klistview.h deleted file mode 100644 index 57a414c2f..000000000 --- a/src/klistview.h +++ /dev/null @@ -1,119 +0,0 @@ -/** - * 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 KLISTVIEW_H -#define KLISTVIEW_H - -#include - -#include - -class KItemCategorizer; - -/** - * @short Item view for listing items - * - * KListView allows you to use it as it were a QListView. You can add an - * itemCategorizer to it, so your items became categorized depending on the - * KItemCategorizer inherited class rules. - * - * @see KItemCategorizer, KSortFilterProxyModel - * - * @author Rafael Fernández López - */ -class LIBDOLPHINPRIVATE_EXPORT KListView - : public QListView -{ - Q_OBJECT - -public: - KListView(QWidget *parent = 0); - - ~KListView(); - - virtual void setModel(QAbstractItemModel *model); - - virtual QRect visualRect(const QModelIndex &index) const; - - /** - * Will return the current categorizer. If none set, this method will - * return 0 - */ - KItemCategorizer *itemCategorizer() const; - - /** - * Sets the categorizer to be used. Causes the item view to repaint - */ - void setItemCategorizer(KItemCategorizer *itemCategorizer); - - virtual QModelIndex indexAt(const QPoint &point) const; - -public Q_SLOTS: - virtual void reset(); - -protected: - virtual void paintEvent(QPaintEvent *event); - - virtual void resizeEvent(QResizeEvent *event); - - virtual void setSelection(const QRect &rect, - QItemSelectionModel::SelectionFlags flags); - - virtual void mouseMoveEvent(QMouseEvent *event); - - virtual void mousePressEvent(QMouseEvent *event); - - virtual void mouseReleaseEvent(QMouseEvent *event); - - virtual void leaveEvent(QEvent *event); - - virtual void startDrag(Qt::DropActions supportedActions); - - virtual void dragMoveEvent(QDragMoveEvent *event); - - virtual void dragLeaveEvent(QDragLeaveEvent *event); - - virtual QModelIndex moveCursor(CursorAction cursorAction, - Qt::KeyboardModifiers modifiers); - -protected Q_SLOTS: - virtual void rowsInserted(const QModelIndex &parent, - int start, - int end); - - virtual void rowsInsertedArtifficial(const QModelIndex &parent, - int start, - int end); - - virtual void rowsRemoved(const QModelIndex &parent, - int start, - int end); - - virtual void updateGeometries(); - - virtual void slotSortingRoleChanged(); - - -private: - class Private; - Private *d; -}; - -#endif // KLISTVIEW_H diff --git a/src/klistview_p.h b/src/klistview_p.h deleted file mode 100644 index 4d0cec138..000000000 --- a/src/klistview_p.h +++ /dev/null @@ -1,159 +0,0 @@ -/** - * 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 KLISTVIEW_P_H -#define KLISTVIEW_P_H - -class KSortFilterProxyModel; - -/** - * @internal - */ -class KListView::Private -{ -public: - Private(KListView *listView); - ~Private(); - - - // 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 represented by index - * @param index on the rect specified by @p option.rect, with - * painter @p painter - */ - void drawNewCategory(const QModelIndex &index, - int sortRole, - const QStyleOption &option, - QPainter *painter); - - /** - * This method will update scrollbars ranges. Called when our model changes - * or when the view is resized - */ - void updateScrollbars(); - - /** - * This method will draw dragged items in the painting operation - */ - void drawDraggedItems(QPainter *painter); - - /** - * This method will determine which rect needs to be updated because of a - * dragging operation - */ - void drawDraggedItems(); - - - // Attributes - - struct ElementInfo - { - QString category; - int relativeOffsetToCategory; - }; - - // Basic data - KListView *listView; - KItemCategorizer *itemCategorizer; - - // Behavior data - bool mouseButtonPressed; - bool isDragging; - bool dragLeftViewport; - QModelIndex hovered; - QString hoveredCategory; - QPoint initialPressPosition; - QPoint mousePosition; - QItemSelection lastSelection; - QModelIndex currentViewIndex; - int forcedSelectionPosition; - - // 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 invertedElementDictionary; // mapped indexes - QHash categoriesIndexes; - QHash categoriesPosition; - QStringList categories; - QModelIndexList intersectedIndexes; - QRect lastDraggedItemsRect; - QRect lastSelectionRect; - - // Attributes for speed reasons - KSortFilterProxyModel *proxyModel; - QModelIndexList sourceModelIndexList; // in source model - QModelIndex lastIndex; -}; - -#endif // KLISTVIEW_P_H -- cgit v1.3