Make KListView capable of drawing categories on our own way. This make things easier

when we are trying to customize it. We can also benefit from KStyle if some day it 
supports category drawing.

KListView keyboard navigation. Tricier than I thought.

Pending renaming to KCategorizedView. Seems a good name.

svn path=/trunk/KDE/kdebase/apps/; revision=684478
This commit is contained in:
Rafael Fernández López 2007-07-06 17:41:04 +00:00
parent 0661c42dd9
commit 98d01c5e23
10 changed files with 594 additions and 146 deletions

View file

@ -17,6 +17,7 @@ set(dolphinprivate_LIB_SRCS
dolphinitemcategorizer.cpp
klistview.cpp
ksortfilterproxymodel.cpp
kitemcategorizer.cpp
dolphinsettings.cpp
viewproperties.cpp
dolphinsortfilterproxymodel.cpp

View file

@ -147,12 +147,14 @@ void DolphinIconsView::dragMoveEvent(QDragMoveEvent* event)
void DolphinIconsView::dropEvent(QDropEvent* event)
{
const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
if (!urls.isEmpty()) {
m_controller->indicateDroppedUrls(urls,
indexAt(event->pos()),
event->source());
event->acceptProposedAction();
if (!selectionModel()->isSelected(indexAt(event->pos()))) {
const KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
if (!urls.isEmpty()) {
m_controller->indicateDroppedUrls(urls,
indexAt(event->pos()),
event->source());
event->acceptProposedAction();
}
}
KListView::dropEvent(event);
m_dragging = false;

View file

@ -32,11 +32,18 @@
#include <kdatetime.h>
#include <kdirmodel.h>
#include <kfileitem.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kurl.h>
#include <kuser.h>
#include <kmimetype.h>
#include <kstandarddirs.h>
#include <kpixmapeffect.h>
#include <QList>
#include <QSortFilterProxyModel>
#include <QPainter>
#include <QDir>
DolphinItemCategorizer::DolphinItemCategorizer() :
KItemCategorizer()
@ -48,7 +55,7 @@ DolphinItemCategorizer::~DolphinItemCategorizer()
}
QString DolphinItemCategorizer::categoryForItem(const QModelIndex& index,
int sortRole)
int sortRole) const
{
QString retString;
@ -167,11 +174,8 @@ QString DolphinItemCategorizer::categoryForItem(const QModelIndex& index,
#ifdef HAVE_NEPOMUK
case DolphinView::SortByRating: {
const quint32 rating = DolphinSortFilterProxyModel::ratingForIndex(index);
if (rating) {
retString = i18np("1 star", "%1 stars", rating);
} else {
retString = i18n("Not yet rated");
}
retString = QString::number(rating);
break;
}
@ -188,3 +192,174 @@ QString DolphinItemCategorizer::categoryForItem(const QModelIndex& index,
return retString;
}
void DolphinItemCategorizer::drawCategory(const QModelIndex &index,
int sortRole,
const QStyleOption &option,
QPainter *painter) const
{
QRect starRect = option.rect;
int iconSize = KIconLoader::global()->theme()->defaultSize(K3Icon::Small);
const QString category = categoryForItem(index, sortRole);
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.state & QStyle::State_MouseOver)
{
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);
}
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);
opt.rect.setLeft(opt.rect.left() + (iconSize / 4));
starRect.setLeft(starRect.left() + (iconSize / 4));
starRect.setRight(starRect.right() + (iconSize / 4));
bool paintIcon = true;
bool paintText = true;
QPixmap icon;
switch (sortRole) {
case DolphinView::SortByName:
paintIcon = false;
break;
case DolphinView::SortByDate:
paintIcon = false;
break;
case DolphinView::SortByPermissions:
paintIcon = false; // FIXME: let's think about how to represent permissions
break;
case DolphinView::SortByOwner: {
opt.rect.setTop(option.rect.top() + (iconSize / 4));
KUser user(category);
icon = QPixmap::fromImage(QImage(user.homeDir() + QDir::separator() + ".face.icon")).scaled(iconSize, iconSize);
break;
}
case DolphinView::SortByGroup:
paintIcon = false;
break;
case DolphinView::SortBySize:
paintIcon = false;
break;
case DolphinView::SortByType: {
opt.rect.setTop(option.rect.top() + (option.rect.height() / 2) - (iconSize / 2));
const KDirModel *model = static_cast<const KDirModel*>(index.model());
KFileItem *item = model->itemForIndex(index);
icon = KIconLoader::global()->loadIcon(KMimeType::iconNameForUrl(item->url()),
K3Icon::Small);
break;
}
#ifdef HAVE_NEPOMUK
case DolphinView::SortByRating: {
paintText = false;
paintIcon = false;
starRect.setTop(option.rect.top() + (option.rect.height() / 2) - (iconSize / 2));
starRect.setSize(QSize(iconSize, iconSize));
QPixmap pixmap = KIconLoader::global()->loadIcon("rating", K3Icon::Small);
QPixmap smallPixmap = KIconLoader::global()->loadIcon("rating", K3Icon::NoGroup, iconSize / 2);
QPixmap disabledPixmap = KIconLoader::global()->loadIcon("rating", K3Icon::Small);
KPixmapEffect::toGray(disabledPixmap, false);
int rating = category.toInt();
for (int i = 0; i < rating - (rating % 2); i += 2) {
painter->drawPixmap(starRect, pixmap);
starRect.setLeft(starRect.left() + iconSize + (iconSize / 4) /* separator between stars */);
}
if (rating && rating % 2) {
starRect.setTop(option.rect.top() + (option.rect.height() / 2) - (iconSize / 4));
starRect.setSize(QSize(iconSize / 2, iconSize / 2));
painter->drawPixmap(starRect, smallPixmap);
starRect.setTop(opt.rect.top() + (option.rect.height() / 2) - (iconSize / 2));
starRect.setSize(QSize(iconSize / 2, iconSize / 2));
starRect.setLeft(starRect.left() + (iconSize / 2) + (iconSize / 4));
starRect.setSize(QSize(iconSize, iconSize));
}
for (int i = rating; i < 9; i += 2) {
painter->drawPixmap(starRect, disabledPixmap);
starRect.setLeft(starRect.left() + iconSize + (iconSize / 4));
}
break;
}
case DolphinView::SortByTags:
paintIcon = false;
break;
#endif
}
if (paintIcon) {
painter->drawPixmap(QRect(opt.rect.left(), opt.rect.top(), iconSize, iconSize), icon);
opt.rect.setLeft(opt.rect.left() + iconSize + (iconSize / 4));
}
if (paintText) {
opt.rect.setTop(option.rect.top() + (iconSize / 4));
opt.rect.setBottom(opt.rect.bottom() - 2);
painter->setPen(color);
painter->drawText(opt.rect, Qt::AlignVCenter | Qt::AlignLeft,
metrics.elidedText(category, Qt::ElideRight, opt.rect.width()));
}
painter->restore();
}
int DolphinItemCategorizer::categoryHeight(const QStyleOption &option) const
{
int iconSize = KIconLoader::global()->theme()->defaultSize(K3Icon::Small);
return qMax(option.fontMetrics.height() + (iconSize / 4) * 2 + 2, iconSize + (iconSize / 4) * 2 + 2) /* 2 gradient */;
}

View file

@ -31,7 +31,10 @@ class LIBDOLPHINPRIVATE_EXPORT DolphinItemCategorizer : public KItemCategorizer
public:
DolphinItemCategorizer();
virtual ~DolphinItemCategorizer();
virtual QString categoryForItem(const QModelIndex &index, int sortRole);
virtual QString categoryForItem(const QModelIndex &index, int sortRole) const;
virtual void drawCategory(const QModelIndex &index, int sortRole,
const QStyleOption &option, QPainter *painter) const;
virtual int categoryHeight(const QStyleOption &option) const;
};
#endif // DOLPHINITEMCATEGORIZER_H

100
src/kitemcategorizer.cpp Normal file
View file

@ -0,0 +1,100 @@
/**
* This file is part of the KDE project
* Copyright (C) 2007 Rafael Fernández López <ereslibre@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kitemcategorizer.h"
#include <QPainter>
#include <QStyleOption>
KItemCategorizer::KItemCategorizer()
{
}
KItemCategorizer::~KItemCategorizer()
{
}
void KItemCategorizer::drawCategory(const QModelIndex &index,
int sortRole,
const QStyleOption &option,
QPainter *painter) const
{
const QString category = categoryForItem(index, sortRole);
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.state & QStyle::State_MouseOver)
{
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);
}
QFont painterFont = painter->font();
painterFont.setWeight(QFont::Bold);
painterFont.setPointSize(painterFont.pointSize() + 2);
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();
}
int KItemCategorizer::categoryHeight(const QStyleOption &option) const
{
return option.fontMetrics.height() + 6 /* 4 separator; 2 gradient */;
}

View file

@ -24,20 +24,49 @@
#include <libdolphin_export.h>
class QString;
class QPainter;
class QModelIndex;
class QStyleOption;
/**
* @short Class for item categorizing on KListView
*
* This class is meant to be used with KListView class. Its purpose is
* to decide to which category belongs a given index with the given role.
* Additionally it will let you to customize the way categories are drawn,
* only in the case that you want to do so
*
* @see KListView
*
* @author Rafael Fernández López <ereslibre@gmail.com>
*/
class LIBDOLPHINPRIVATE_EXPORT KItemCategorizer
{
public:
KItemCategorizer()
{
}
KItemCategorizer();
virtual ~KItemCategorizer()
{
}
virtual ~KItemCategorizer();
virtual QString categoryForItem(const QModelIndex &index, int sortRole) = 0;
/**
* This method will return the category where @param index fit on with the
* given @param sortRole role
*/
virtual QString categoryForItem(const QModelIndex &index,
int sortRole) const = 0;
/**
* This method purpose is to draw a category represented by the given
* @param index with the given @param sortRole sorting role
*
* @note This method will be called one time per category, always with the
* first element in that category
*/
virtual void drawCategory(const QModelIndex &index,
int sortRole,
const QStyleOption &option,
QPainter *painter) const;
virtual int categoryHeight(const QStyleOption &option) const;
};
#endif // KITEMCATEGORIZER_H

View file

@ -146,7 +146,7 @@ QRect KListView::Private::visualRectInViewport(const QModelIndex &index) const
QString curCategory = elementsInfo[index].category;
QRect retRect(listView->spacing(), listView->spacing() * 2 +
30 /* categoryHeight */, 0, 0);
itemCategorizer->categoryHeight(listView->viewOptions()), 0, 0);
int viewportWidth = listView->viewport()->width() - listView->spacing();
@ -183,7 +183,7 @@ QRect KListView::Private::visualRectInViewport(const QModelIndex &index) const
retRect.setTop(retRect.top() +
(rowsInt * listView->spacing()) +
(rowsInt * itemHeight) +
30 /* categoryHeight */ +
itemCategorizer->categoryHeight(listView->viewOptions()) +
listView->spacing() * 2);
}
@ -238,11 +238,11 @@ QRect KListView::Private::visualCategoryRectInViewport(const QString &category)
retRect.setTop(retRect.top() +
(rowsInt * listView->spacing()) +
(rowsInt * itemHeight) +
30 /* categoryHeight */ +
itemCategorizer->categoryHeight(listView->viewOptions()) +
listView->spacing() * 2);
}
retRect.setHeight(30 /* categoryHeight */);
retRect.setHeight(itemCategorizer->categoryHeight(listView->viewOptions()));
return retRect;
}
@ -311,69 +311,23 @@ QRect KListView::Private::categoryVisualRect(const QString &category)
return retRect;
}
void KListView::Private::drawNewCategory(const QString &category,
void KListView::Private::drawNewCategory(const QModelIndex &index,
int sortRole,
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;
QStyleOption optionCopy = option;
const QString category = itemCategorizer->categoryForItem(index, sortRole);
if ((category == hoveredCategory) && !mouseButtonPressed)
{
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);
optionCopy.state |= QStyle::State_MouseOver;
}
/*if (const KStyle *style = dynamic_cast<const KStyle*>(QApplication::style()))
{
style->drawControl(KStyle::CE_Category, &opt, painter, this);
}
else
{*/
QFont painterFont = painter->font();
painterFont.setWeight(QFont::Bold);
QFontMetrics metrics(painterFont);
painter->setFont(painterFont);
QPainterPath path;
path.addRect(option.rect.left(),
option.rect.bottom() - 2,
option.rect.width(),
2);
QLinearGradient gradient(option.rect.topLeft(),
option.rect.bottomRight());
gradient.setColorAt(0, color);
gradient.setColorAt(1, Qt::transparent);
painter->setBrush(gradient);
painter->fillPath(path, gradient);
painter->setPen(color);
painter->drawText(option.rect, Qt::AlignVCenter | Qt::AlignLeft,
metrics.elidedText(category, Qt::ElideRight, option.rect.width()));
//}
painter->restore();
itemCategorizer->drawCategory(index,
sortRole,
optionCopy,
painter);
}
@ -424,11 +378,9 @@ void KListView::Private::drawDraggedItems()
}
}
listView->viewport()->update(lastDraggedItemsRect);
listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate));
lastDraggedItemsRect = rectToUpdate;
listView->viewport()->update(rectToUpdate);
}
@ -448,6 +400,21 @@ KListView::~KListView()
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,
@ -498,6 +465,21 @@ KItemCategorizer *KListView::itemCategorizer() const
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,
@ -557,15 +539,13 @@ void KListView::reset()
{
QListView::reset();
if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
!d->itemCategorizer)
{
return;
}
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();
@ -635,6 +615,22 @@ void KListView::paintEvent(QPaintEvent *event)
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;
@ -666,19 +662,6 @@ void KListView::paintEvent(QPaintEvent *event)
painter.restore();
}
// Redraw categories
QStyleOptionViewItem otherOption;
foreach (const QString &category, d->categories)
{
otherOption = option;
otherOption.rect = d->categoryVisualRect(category);
if (otherOption.rect.intersects(area))
{
d->drawNewCategory(category, otherOption, &painter);
}
}
if (d->isDragging && !d->dragLeftViewport)
{
painter.setOpacity(0.5);
@ -692,16 +675,17 @@ 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;
}
// Clear the items positions cache
d->elementsPosition.clear();
d->categoriesPosition.clear();
d->updateScrollbars();
}
@ -726,6 +710,7 @@ void KListView::setSelection(const QRect &rect,
}
QModelIndexList dirtyIndexes = d->intersectionSet(rect);
QItemSelection selection;
if (!dirtyIndexes.count())
@ -741,6 +726,7 @@ void KListView::setSelection(const QRect &rect,
if (!d->mouseButtonPressed)
{
selection = QItemSelection(dirtyIndexes[0], dirtyIndexes[0]);
d->currentViewIndex = dirtyIndexes[0];
}
else
{
@ -786,6 +772,8 @@ void KListView::mouseMoveEvent(QMouseEvent *event)
return;
}
const QString previousHoveredCategory = d->hoveredCategory;
d->mousePosition = event->pos();
d->hoveredCategory = QString();
@ -795,9 +783,13 @@ void KListView::mouseMoveEvent(QMouseEvent *event)
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));
}
viewport()->update(d->categoryVisualRect(category));
}
QRect rect;
@ -822,11 +814,9 @@ void KListView::mouseMoveEvent(QMouseEvent *event)
end = d->mousePosition;
}
viewport()->update(d->lastSelectionRect);
rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
viewport()->update(rect);
//viewport()->update(rect.united(d->lastSelectionRect));
d->lastSelectionRect = rect;
}
@ -834,14 +824,6 @@ void KListView::mouseMoveEvent(QMouseEvent *event)
void KListView::mousePressEvent(QMouseEvent *event)
{
QListView::mousePressEvent(event);
if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
!d->itemCategorizer)
{
return;
}
d->dragLeftViewport = false;
if (event->button() == Qt::LeftButton)
@ -854,10 +836,14 @@ void KListView::mousePressEvent(QMouseEvent *event)
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 ||
@ -866,8 +852,6 @@ void KListView::mouseReleaseEvent(QMouseEvent *event)
return;
}
d->mouseButtonPressed = false;
QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
@ -902,16 +886,10 @@ void KListView::mouseReleaseEvent(QMouseEvent *event)
void KListView::leaveEvent(QEvent *event)
{
QListView::leaveEvent(event);
if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
!d->itemCategorizer)
{
return;
}
d->hovered = QModelIndex();
d->hoveredCategory = QString();
QListView::leaveEvent(event);
}
void KListView::startDrag(Qt::DropActions supportedActions)
@ -920,17 +898,12 @@ void KListView::startDrag(Qt::DropActions supportedActions)
d->isDragging = false;
d->mouseButtonPressed = false;
viewport()->update(d->lastDraggedItemsRect);
}
void KListView::dragMoveEvent(QDragMoveEvent *event)
{
if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
!d->itemCategorizer)
{
QListView::dragMoveEvent(event);
return;
}
d->mousePosition = event->pos();
if (d->mouseButtonPressed)
@ -944,20 +917,21 @@ void KListView::dragMoveEvent(QDragMoveEvent *event)
d->dragLeftViewport = false;
if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
!d->itemCategorizer)
{
QListView::dragMoveEvent(event);
return;
}
d->drawDraggedItems();
}
void KListView::dragLeaveEvent(QDragLeaveEvent *event)
{
QListView::dragLeaveEvent(event);
if ((viewMode() != KListView::IconMode) || !d->proxyModel ||
!d->itemCategorizer)
{
return;
}
d->dragLeftViewport = true;
QListView::dragLeaveEvent(event);
}
QModelIndex KListView::moveCursor(CursorAction cursorAction,
@ -969,6 +943,119 @@ QModelIndex KListView::moveCursor(CursorAction cursorAction,
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);
}
@ -981,6 +1068,21 @@ void KListView::rowsInserted(const QModelIndex &parent,
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;
}
@ -994,9 +1096,12 @@ void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
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();
@ -1083,6 +1188,9 @@ void KListView::rowsInsertedArtifficial(const QModelIndex &parent,
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++;
}

View file

@ -27,6 +27,17 @@
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 <ereslibre@gmail.com>
*/
class LIBDOLPHINPRIVATE_EXPORT KListView
: public QListView
{
@ -41,8 +52,15 @@ public:
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;

View file

@ -85,10 +85,12 @@ public:
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
* 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 QString &category,
void drawNewCategory(const QModelIndex &index,
int sortRole,
const QStyleOption &option,
QPainter *painter);
@ -131,6 +133,8 @@ public:
QPoint initialPressPosition;
QPoint mousePosition;
QItemSelection lastSelection;
QModelIndex currentViewIndex;
int forcedSelectionPosition;
// Cache data
// We cannot merge some of them into structs because it would affect
@ -138,6 +142,7 @@ public:
QHash<QModelIndex, struct ElementInfo> elementsInfo; // in source model
QHash<QModelIndex, QRect> elementsPosition; // in source model
QHash<QModelIndex, QModelIndex> elementDictionary; // mapped indexes
QHash<QModelIndex, QModelIndex> invertedElementDictionary; // mapped indexes
QHash<QString, QModelIndexList> categoriesIndexes;
QHash<QString, QRect> categoriesPosition;
QStringList categories;

View file

@ -25,6 +25,13 @@
#include <libdolphin_export.h>
/**
* @internal
*
* This class is meant to be used with KListView class
*
* @see KListView
*/
class LIBDOLPHINPRIVATE_EXPORT KSortFilterProxyModel
: public QSortFilterProxyModel
{