aboutsummaryrefslogtreecommitdiff
path: root/libgroupview/src
diff options
context:
space:
mode:
authorPetr Mrázek <peterix@gmail.com>2013-03-11 22:19:17 +0100
committerPetr Mrázek <peterix@gmail.com>2013-03-11 22:19:17 +0100
commit46f93311afc9f1e2afc306f63cee0e4f462758e2 (patch)
treecc945d66e6ca5e68c43b354d3f8f6d6e70cf5326 /libgroupview/src
parent36396f7c6aca9fcc61c8620e10c31ed2c8999ebd (diff)
downloadPrismLauncher-46f93311afc9f1e2afc306f63cee0e4f462758e2.tar.gz
PrismLauncher-46f93311afc9f1e2afc306f63cee0e4f462758e2.tar.bz2
PrismLauncher-46f93311afc9f1e2afc306f63cee0e4f462758e2.zip
Instance view, model, delegate.
Diffstat (limited to 'libgroupview/src')
-rw-r--r--libgroupview/src/kcategorizedsortfilterproxymodel.cpp168
-rw-r--r--libgroupview/src/kcategorizedsortfilterproxymodel_p.h48
-rw-r--r--libgroupview/src/kcategorizedview.cpp1696
-rw-r--r--libgroupview/src/kcategorizedview_p.h157
-rw-r--r--libgroupview/src/kcategorydrawer.cpp231
5 files changed, 2300 insertions, 0 deletions
diff --git a/libgroupview/src/kcategorizedsortfilterproxymodel.cpp b/libgroupview/src/kcategorizedsortfilterproxymodel.cpp
new file mode 100644
index 00000000..46a845e0
--- /dev/null
+++ b/libgroupview/src/kcategorizedsortfilterproxymodel.cpp
@@ -0,0 +1,168 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
+ * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
+ *
+ * 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 "kcategorizedsortfilterproxymodel.h"
+#include "kcategorizedsortfilterproxymodel_p.h"
+
+#include <limits.h>
+
+#include <QItemSelection>
+#include <QStringList>
+#include <QSize>
+
+KCategorizedSortFilterProxyModel::KCategorizedSortFilterProxyModel ( QObject *parent )
+ : QSortFilterProxyModel ( parent )
+ , d ( new Private() )
+{
+}
+
+KCategorizedSortFilterProxyModel::~KCategorizedSortFilterProxyModel()
+{
+ delete d;
+}
+
+void KCategorizedSortFilterProxyModel::sort ( int column, Qt::SortOrder order )
+{
+ d->sortColumn = column;
+ d->sortOrder = order;
+
+ QSortFilterProxyModel::sort ( column, order );
+}
+
+bool KCategorizedSortFilterProxyModel::isCategorizedModel() const
+{
+ return d->categorizedModel;
+}
+
+void KCategorizedSortFilterProxyModel::setCategorizedModel ( bool categorizedModel )
+{
+ if ( categorizedModel == d->categorizedModel )
+ {
+ return;
+ }
+
+ d->categorizedModel = categorizedModel;
+
+ invalidate();
+}
+
+int KCategorizedSortFilterProxyModel::sortColumn() const
+{
+ return d->sortColumn;
+}
+
+Qt::SortOrder KCategorizedSortFilterProxyModel::sortOrder() const
+{
+ return d->sortOrder;
+}
+
+void KCategorizedSortFilterProxyModel::setSortCategoriesByNaturalComparison ( bool sortCategoriesByNaturalComparison )
+{
+ if ( sortCategoriesByNaturalComparison == d->sortCategoriesByNaturalComparison )
+ {
+ return;
+ }
+
+ d->sortCategoriesByNaturalComparison = sortCategoriesByNaturalComparison;
+
+ invalidate();
+}
+
+bool KCategorizedSortFilterProxyModel::sortCategoriesByNaturalComparison() const
+{
+ return d->sortCategoriesByNaturalComparison;
+}
+
+bool KCategorizedSortFilterProxyModel::lessThan ( const QModelIndex &left, const QModelIndex &right ) const
+{
+ if ( d->categorizedModel )
+ {
+ int compare = compareCategories ( left, right );
+
+ if ( compare > 0 ) // left is greater than right
+ {
+ return false;
+ }
+ else if ( compare < 0 ) // left is less than right
+ {
+ return true;
+ }
+ }
+
+ return subSortLessThan ( left, right );
+}
+
+bool KCategorizedSortFilterProxyModel::subSortLessThan ( const QModelIndex &left, const QModelIndex &right ) const
+{
+ return QSortFilterProxyModel::lessThan ( left, right );
+}
+
+int KCategorizedSortFilterProxyModel::compareCategories ( const QModelIndex &left, const QModelIndex &right ) const
+{
+ QVariant l = ( left.model() ? left.model()->data ( left, CategorySortRole ) : QVariant() );
+ QVariant r = ( right.model() ? right.model()->data ( right, CategorySortRole ) : QVariant() );
+
+ Q_ASSERT ( l.isValid() );
+ Q_ASSERT ( r.isValid() );
+ Q_ASSERT ( l.type() == r.type() );
+
+ if ( l.type() == QVariant::String )
+ {
+ QString lstr = l.toString();
+ QString rstr = r.toString();
+
+ /*
+ if ( d->sortCategoriesByNaturalComparison )
+ {
+ return KStringHandler::naturalCompare ( lstr, rstr );
+ }
+ else
+ {
+ */
+ if ( lstr < rstr )
+ {
+ return -1;
+ }
+
+ if ( lstr > rstr )
+ {
+ return 1;
+ }
+
+ return 0;
+ //}
+ }
+
+ qlonglong lint = l.toLongLong();
+ qlonglong rint = r.toLongLong();
+
+ if ( lint < rint )
+ {
+ return -1;
+ }
+
+ if ( lint > rint )
+ {
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/libgroupview/src/kcategorizedsortfilterproxymodel_p.h b/libgroupview/src/kcategorizedsortfilterproxymodel_p.h
new file mode 100644
index 00000000..d7e7c9a0
--- /dev/null
+++ b/libgroupview/src/kcategorizedsortfilterproxymodel_p.h
@@ -0,0 +1,48 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2007 Rafael Fernández López <ereslibre@kde.org>
+ * Copyright (C) 2007 John Tapsell <tapsell@kde.org>
+ *
+ * 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 KCATEGORIZEDSORTFILTERPROXYMODEL_P_H
+#define KCATEGORIZEDSORTFILTERPROXYMODEL_P_H
+
+class KCategorizedSortFilterProxyModel;
+
+class KCategorizedSortFilterProxyModel::Private
+{
+public:
+ Private()
+ : sortColumn ( 0 )
+ , sortOrder ( Qt::AscendingOrder )
+ , categorizedModel ( false )
+ , sortCategoriesByNaturalComparison ( true )
+ {
+ }
+
+ ~Private()
+ {
+ }
+
+ int sortColumn;
+ Qt::SortOrder sortOrder;
+ bool categorizedModel;
+ bool sortCategoriesByNaturalComparison;
+};
+
+#endif
diff --git a/libgroupview/src/kcategorizedview.cpp b/libgroupview/src/kcategorizedview.cpp
new file mode 100644
index 00000000..ba4ae2dc
--- /dev/null
+++ b/libgroupview/src/kcategorizedview.cpp
@@ -0,0 +1,1696 @@
+/**
+ * This file is part of the KDE project
+ * Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
+ *
+ * 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.
+ */
+
+/**
+ * IMPLEMENTATION NOTES:
+ *
+ * QListView::setRowHidden() and QListView::isRowHidden() are not taken into
+ * account. This methods should actually not exist. This effect should be handled
+ * by an hypothetical QSortFilterProxyModel which filters out the desired rows.
+ *
+ * In case this needs to be implemented, contact me, but I consider this a faulty
+ * design.
+ */
+
+#include "kcategorizedview.h"
+#include "kcategorizedview_p.h"
+
+#include <math.h> // trunc on C99 compliant systems
+#include <kdefakes.h> // trunc for not C99 compliant systems
+
+#include <QPainter>
+#include <QScrollBar>
+#include <QPaintEvent>
+
+#include "kcategorydrawer.h"
+#include "kcategorizedsortfilterproxymodel.h"
+
+//BEGIN: Private part
+
+struct KCategorizedView::Private::Item
+{
+ Item()
+ : topLeft ( QPoint() )
+ , size ( QSize() )
+ {
+ }
+
+ QPoint topLeft;
+ QSize size;
+};
+
+struct KCategorizedView::Private::Block
+{
+ Block()
+ : topLeft ( QPoint() )
+ , height ( -1 )
+ , firstIndex ( QModelIndex() )
+ , quarantineStart ( QModelIndex() )
+ , items ( QList<Item>() )
+ , outOfQuarantine ( false )
+ , alternate ( false )
+ , collapsed ( false )
+ {
+ }
+
+ bool operator!= ( const Block &rhs ) const
+ {
+ return firstIndex != rhs.firstIndex;
+ }
+
+ static bool lessThan ( const Block &left, const Block &right )
+ {
+ Q_ASSERT ( left.firstIndex.isValid() );
+ Q_ASSERT ( right.firstIndex.isValid() );
+ return left.firstIndex.row() < right.firstIndex.row();
+ }
+
+ QPoint topLeft;
+ int height;
+ QPersistentModelIndex firstIndex;
+ // if we have n elements on this block, and we inserted an element at position i. The quarantine
+ // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the
+ // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine
+ // will only affect the current block, since the rest of blocks can be affected only in the way
+ // that the whole block will have different offset, but items will keep the same relative position
+ // in terms of their parent blocks.
+ QPersistentModelIndex quarantineStart;
+ QList<Item> items;
+
+ // this affects the whole block, not items separately. items contain the topLeft point relative
+ // to the block. Because of insertions or removals a whole block can be moved, so the whole block
+ // will enter in quarantine, what is faster than moving all items in absolute terms.
+ bool outOfQuarantine;
+
+ // should we alternate its color ? is just a hint, could not be used
+ bool alternate;
+ bool collapsed;
+};
+
+KCategorizedView::Private::Private ( KCategorizedView *q )
+ : q ( q )
+ , proxyModel ( 0 )
+ , categoryDrawer ( 0 )
+ , categorySpacing ( 5 )
+ , alternatingBlockColors ( false )
+ , collapsibleBlocks ( false )
+ , hoveredBlock ( new Block() )
+ , hoveredIndex ( QModelIndex() )
+ , pressedPosition ( QPoint() )
+ , rubberBandRect ( QRect() )
+{
+}
+
+KCategorizedView::Private::~Private()
+{
+ delete hoveredBlock;
+}
+
+bool KCategorizedView::Private::isCategorized() const
+{
+ return proxyModel && categoryDrawer && proxyModel->isCategorizedModel();
+}
+
+QStyleOptionViewItemV4 KCategorizedView::Private::blockRect ( const QModelIndex &representative )
+{
+ QStyleOptionViewItemV4 option ( q->viewOptions() );
+ const int height = categoryDrawer->categoryHeight ( representative, option );
+ const QString categoryDisplay = representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString();
+ QPoint pos = blockPosition ( categoryDisplay );
+ pos.ry() -= height;
+ option.rect.setTopLeft ( pos );
+ option.rect.setWidth ( viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin() );
+ option.rect.setHeight ( height + blockHeight ( categoryDisplay ) );
+ option.rect = mapToViewport ( option.rect );
+
+ return option;
+}
+
+QPair<QModelIndex, QModelIndex> KCategorizedView::Private::intersectingIndexesWithRect ( const QRect &_rect ) const
+{
+ const int rowCount = proxyModel->rowCount();
+
+ const QRect rect = _rect.normalized();
+
+ // binary search to find out the top border
+ int bottom = 0;
+ int top = rowCount - 1;
+ while ( bottom <= top )
+ {
+ const int middle = ( bottom + top ) / 2;
+ const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() );
+ QRect itemRect = q->visualRect ( index );
+ const int verticalOff = q->verticalOffset();
+ const int horizontalOff = q->horizontalOffset();
+ itemRect.topLeft().ry() += verticalOff;
+ itemRect.topLeft().rx() += horizontalOff;
+ itemRect.bottomRight().ry() += verticalOff;
+ itemRect.bottomRight().rx() += horizontalOff;
+ if ( itemRect.bottomRight().y() <= rect.topLeft().y() )
+ {
+ bottom = middle + 1;
+ }
+ else
+ {
+ top = middle - 1;
+ }
+ }
+
+ const QModelIndex bottomIndex = proxyModel->index ( bottom, q->modelColumn(), q->rootIndex() );
+
+ // binary search to find out the bottom border
+ bottom = 0;
+ top = rowCount - 1;
+ while ( bottom <= top )
+ {
+ const int middle = ( bottom + top ) / 2;
+ const QModelIndex index = proxyModel->index ( middle, q->modelColumn(), q->rootIndex() );
+ QRect itemRect = q->visualRect ( index );
+ const int verticalOff = q->verticalOffset();
+ const int horizontalOff = q->horizontalOffset();
+ itemRect.topLeft().ry() += verticalOff;
+ itemRect.topLeft().rx() += horizontalOff;
+ itemRect.bottomRight().ry() += verticalOff;
+ itemRect.bottomRight().rx() += horizontalOff;
+ if ( itemRect.topLeft().y() <= rect.bottomRight().y() )
+ {
+ bottom = middle + 1;
+ }
+ else
+ {
+ top = middle - 1;
+ }
+ }
+
+ const QModelIndex topIndex = proxyModel->index ( top, q->modelColumn(), q->rootIndex() );
+
+ return qMakePair ( bottomIndex, topIndex );
+}
+
+QPoint KCategorizedView::Private::blockPosition ( const QString &category )
+{
+ Block &block = blocks[category];
+
+ if ( block.outOfQuarantine && !block.topLeft.isNull() )
+ {
+ return block.topLeft;
+ }
+
+ QPoint res ( categorySpacing, 0 );
+
+ const QModelIndex index = block.firstIndex;
+
+ for ( QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it )
+ {
+ Block &block = *it;
+ const QModelIndex categoryIndex = block.firstIndex;
+ if ( index.row() < categoryIndex.row() )
+ {
+ continue;
+ }
+ res.ry() += categoryDrawer->categoryHeight ( categoryIndex, q->viewOptions() ) + categorySpacing;
+ if ( index.row() == categoryIndex.row() )
+ {
+ continue;
+ }
+ res.ry() += blockHeight ( it.key() );
+ }
+
+ block.outOfQuarantine = true;
+ block.topLeft = res;
+
+ return res;
+}
+
+int KCategorizedView::Private::blockHeight ( const QString &category )
+{
+ Block &block = blocks[category];
+
+ if ( block.collapsed )
+ {
+ return 0;
+ }
+
+ if ( block.height > -1 )
+ {
+ return block.height;
+ }
+
+ const QModelIndex firstIndex = block.firstIndex;
+ const QModelIndex lastIndex = proxyModel->index ( firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() );
+ const QRect topLeft = q->visualRect ( firstIndex );
+ QRect bottomRight = q->visualRect ( lastIndex );
+
+ if ( hasGrid() )
+ {
+ bottomRight.setHeight ( qMax ( bottomRight.height(), q->gridSize().height() ) );
+ }
+ else
+ {
+ if ( !q->uniformItemSizes() )
+ {
+ bottomRight.setHeight ( highestElementInLastRow ( block ) + q->spacing() * 2 );
+ }
+ }
+
+ const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1;
+ block.height = height;
+
+ return height;
+}
+
+int KCategorizedView::Private::viewportWidth() const
+{
+ return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin();
+}
+
+void KCategorizedView::Private::regenerateAllElements()
+{
+ for ( QHash<QString, Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it )
+ {
+ Block &block = *it;
+ block.outOfQuarantine = false;
+ block.quarantineStart = block.firstIndex;
+ block.height = -1;
+ }
+}
+
+void KCategorizedView::Private::rowsInserted ( const QModelIndex &parent, int start, int end )
+{
+ if ( !isCategorized() )
+ {
+ return;
+ }
+
+ for ( int i = start; i <= end; ++i )
+ {
+ const QModelIndex index = proxyModel->index ( i, q->modelColumn(), parent );
+
+ Q_ASSERT ( index.isValid() );
+
+ const QString category = categoryForIndex ( index );
+
+ Block &block = blocks[category];
+
+ //BEGIN: update firstIndex
+ // save as firstIndex in block if
+ // - it forced the category creation (first element on this category)
+ // - it is before the first row on that category
+ const QModelIndex firstIndex = block.firstIndex;
+ if ( !firstIndex.isValid() || index.row() < firstIndex.row() )
+ {
+ block.firstIndex = index;
+ }
+ //END: update firstIndex
+
+ Q_ASSERT ( block.firstIndex.isValid() );
+
+ const int firstIndexRow = block.firstIndex.row();
+
+ block.items.insert ( index.row() - firstIndexRow, Private::Item() );
+ block.height = -1;
+
+ q->visualRect ( index );
+ q->viewport()->update();
+ }
+
+ //BEGIN: update the items that are in quarantine in affected categories
+ {
+ const QModelIndex lastIndex = proxyModel->index ( end, q->modelColumn(), parent );
+ const QString category = categoryForIndex ( lastIndex );
+ Private::Block &block = blocks[category];
+ block.quarantineStart = block.firstIndex;
+ }
+ //END: update the items that are in quarantine in affected categories
+
+ //BEGIN: mark as in quarantine those categories that are under the affected ones
+ {
+ const QModelIndex firstIndex = proxyModel->index ( start, q->modelColumn(), parent );
+ const QString category = categoryForIndex ( firstIndex );
+ const QModelIndex firstAffectedCategory = blocks[category].firstIndex;
+ //BEGIN: order for marking as alternate those blocks that are alternate
+ QList<Block> blockList = blocks.values();
+ qSort ( blockList.begin(), blockList.end(), Block::lessThan );
+ QList<int> firstIndexesRows;
+ foreach ( const Block &block, blockList )
+ {
+ firstIndexesRows << block.firstIndex.row();
+ }
+ //END: order for marking as alternate those blocks that are alternate
+ for ( QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it )
+ {
+ Private::Block &block = *it;
+ if ( block.firstIndex.row() > firstAffectedCategory.row() )
+ {
+ block.outOfQuarantine = false;
+ block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2;
+ }
+ else if ( block.firstIndex.row() == firstAffectedCategory.row() )
+ {
+ block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2;
+ }
+ }
+ }
+ //END: mark as in quarantine those categories that are under the affected ones
+}
+
+QRect KCategorizedView::Private::mapToViewport ( const QRect &rect ) const
+{
+ const int dx = -q->horizontalOffset();
+ const int dy = -q->verticalOffset();
+ return rect.adjusted ( dx, dy, dx, dy );
+}
+
+QRect KCategorizedView::Private::mapFromViewport ( const QRect &rect ) const
+{
+ const int dx = q->horizontalOffset();
+ const int dy = q->verticalOffset();
+ return rect.adjusted ( dx, dy, dx, dy );
+}
+
+int KCategorizedView::Private::highestElementInLastRow ( const Block &block ) const
+{
+ //Find the highest element in the last row
+ const QModelIndex lastIndex = proxyModel->index ( block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex() );
+ const QRect prevRect = q->visualRect ( lastIndex );
+ int res = prevRect.height();
+ QModelIndex prevIndex = proxyModel->index ( lastIndex.row() - 1, q->modelColumn(), q->rootIndex() );
+ if ( !prevIndex.isValid() )
+ {
+ return res;
+ }
+ Q_FOREVER
+ {
+ const QRect tempRect = q->visualRect ( prevIndex );
+ if ( tempRect.topLeft().y() < prevRect.topLeft().y() )
+ {
+ break;
+ }
+ res = qMax ( res, tempRect.height() );
+ if ( prevIndex == block.firstIndex )
+ {
+ break;
+ }
+ prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() );
+ }
+
+ return res;
+}
+
+bool KCategorizedView::Private::hasGrid() const
+{
+ const QSize gridSize = q->gridSize();
+ return gridSize.isValid() && !gridSize.isNull();
+}
+
+QString KCategorizedView::Private::categoryForIndex ( const QModelIndex &index ) const
+{
+ const QModelIndex categoryIndex = index.model()->index ( index.row(), proxyModel->sortColumn(), index.parent() );
+ return categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString();
+}
+
+void KCategorizedView::Private::leftToRightVisualRect ( const QModelIndex &index, Item &item,
+ const Block &block, const QPoint &blockPos ) const
+{
+ const int firstIndexRow = block.firstIndex.row();
+
+ if ( hasGrid() )
+ {
+ const int relativeRow = index.row() - firstIndexRow;
+ const int maxItemsPerRow = qMax ( viewportWidth() / q->gridSize().width(), 1 );
+ if ( q->layoutDirection() == Qt::LeftToRight )
+ {
+ item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin();
+ }
+ else
+ {
+ item.topLeft.rx() = viewportWidth() - ( ( relativeRow % maxItemsPerRow ) + 1 ) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing;
+ }
+ item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * q->gridSize().height();
+ }
+ else
+ {
+ if ( q->uniformItemSizes() )
+ {
+ const int relativeRow = index.row() - firstIndexRow;
+ const QSize itemSize = q->sizeHintForIndex ( index );
+ const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() ) / ( itemSize.width() + q->spacing() ), 1 );
+ if ( q->layoutDirection() == Qt::LeftToRight )
+ {
+ item.topLeft.rx() = ( relativeRow % maxItemsPerRow ) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
+ }
+ else
+ {
+ item.topLeft.rx() = viewportWidth() - ( relativeRow % maxItemsPerRow ) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing;
+ }
+ item.topLeft.ry() = ( relativeRow / maxItemsPerRow ) * itemSize.height();
+ }
+ else
+ {
+ const QSize currSize = q->sizeHintForIndex ( index );
+ if ( index != block.firstIndex )
+ {
+ const int viewportW = viewportWidth() - q->spacing();
+ QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() );
+ QRect prevRect = q->visualRect ( prevIndex );
+ prevRect = mapFromViewport ( prevRect );
+ if ( ( prevRect.bottomRight().x() + 1 ) + currSize.width() - blockPos.x() + q->spacing() > viewportW )
+ {
+ // we have to check the whole previous row, and see which one was the
+ // highest.
+ Q_FOREVER
+ {
+ prevIndex = proxyModel->index ( prevIndex.row() - 1, q->modelColumn(), q->rootIndex() );
+ const QRect tempRect = q->visualRect ( prevIndex );
+ if ( tempRect.topLeft().y() < prevRect.topLeft().y() )
+ {
+ break;
+ }
+ if ( tempRect.bottomRight().y() > prevRect.bottomRight().y() )
+ {
+ prevRect = tempRect;
+ }
+ if ( prevIndex == block.firstIndex )
+ {
+ break;
+ }
+ }
+ if ( q->layoutDirection() == Qt::LeftToRight )
+ {
+ item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing();
+ }
+ else
+ {
+ item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
+ }
+ item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y();
+ }
+ else
+ {
+ if ( q->layoutDirection() == Qt::LeftToRight )
+ {
+ item.topLeft.rx() = ( prevRect.bottomRight().x() + 1 ) + q->spacing();
+ }
+ else
+ {
+ item.topLeft.rx() = ( prevRect.bottomLeft().x() - 1 ) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing;
+ }
+ item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y();
+ }
+ }
+ else
+ {
+ if ( q->layoutDirection() == Qt::LeftToRight )
+ {
+ item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
+ }
+ else
+ {
+ item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
+ }
+ item.topLeft.ry() = q->spacing();
+ }
+ }
+ }
+ item.size = q->sizeHintForIndex ( index );
+}
+
+void KCategorizedView::Private::topToBottomVisualRect ( const QModelIndex &index, Item &item,
+ const Block &block, const QPoint &blockPos ) const
+{
+ const int firstIndexRow = block.firstIndex.row();
+
+ if ( hasGrid() )
+ {
+ const int relativeRow = index.row() - firstIndexRow;
+ item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
+ item.topLeft.ry() = relativeRow * q->gridSize().height();
+ }
+ else
+ {
+ if ( q->uniformItemSizes() )
+ {
+ const int relativeRow = index.row() - firstIndexRow;
+ const QSize itemSize = q->sizeHintForIndex ( index );
+ item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
+ item.topLeft.ry() = relativeRow * itemSize.height();
+ }
+ else
+ {
+ if ( index != block.firstIndex )
+ {
+ QModelIndex prevIndex = proxyModel->index ( index.row() - 1, q->modelColumn(), q->rootIndex() );
+ QRect prevRect = q->visualRect ( prevIndex );
+ prevRect = mapFromViewport ( prevRect );
+ item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
+ item.topLeft.ry() = ( prevRect.bottomRight().y() + 1 ) + q->spacing() - blockPos.y();
+ }
+ else
+ {
+ item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
+ item.topLeft.ry() = q->spacing();
+ }
+ }
+ }
+ item.size = q->sizeHintForIndex ( index );
+ item.size.setWidth ( viewportWidth() );
+}
+
+void KCategorizedView::Private::_k_slotCollapseOrExpandClicked ( QModelIndex )
+{
+}
+
+//END: Private part
+
+//BEGIN: Public part
+
+KCategorizedView::KCategorizedView ( QWidget *parent )
+ : QListView ( parent )
+ , d ( new Private ( this ) )
+{
+}
+
+KCategorizedView::~KCategorizedView()
+{
+ delete d;
+}
+
+void KCategorizedView::setModel ( QAbstractItemModel *model )
+{
+ if ( d->proxyModel == model )
+ {
+ return;
+ }
+
+ d->blocks.clear();
+
+ if ( d->proxyModel )
+ {
+ disconnect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) );
+ }
+
+ d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*> ( model );
+
+ if ( d->proxyModel )
+ {
+ connect ( d->proxyModel, SIGNAL ( layoutChanged() ), this, SLOT ( slotLayoutChanged() ) );
+ }
+
+ QListView::setModel ( model );
+
+ // if the model already had information inserted, update our data structures to it
+ if ( model->rowCount() )
+ {
+ slotLayoutChanged();
+ }
+}
+
+void KCategorizedView::setGridSize ( const QSize &size )
+{
+ setGridSizeOwn ( size );
+}
+
+void KCategorizedView::setGridSizeOwn ( const QSize &size )
+{
+ d->regenerateAllElements();
+ QListView::setGridSize ( size );
+}
+
+QRect KCategorizedView::visualRect ( const QModelIndex &index ) const
+{
+ if ( !d->isCategorized() )
+ {
+ return QListView::visualRect ( index );
+ }
+
+ if ( !index.isValid() )
+ {
+ return QRect();
+ }
+
+ const QString category = d->categoryForIndex ( index );
+
+ if ( !d->blocks.contains ( category ) )
+ {
+ return QRect();
+ }
+
+ Private::Block &block = d->blocks[category];
+ const int firstIndexRow = block.firstIndex.row();
+
+ Q_ASSERT ( block.firstIndex.isValid() );
+
+ if ( index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count() )
+ {
+ return QRect();
+ }
+
+ const QPoint blockPos = d->blockPosition ( category );
+
+ Private::Item &ritem = block.items[index.row() - firstIndexRow];
+
+ if ( ritem.topLeft.isNull() || ( block.quarantineStart.isValid() &&
+ index.row() >= block.quarantineStart.row() ) )
+ {
+ if ( flow() == LeftToRight )
+ {
+ d->leftToRightVisualRect ( index, ritem, block, blockPos );
+ }
+ else
+ {
+ d->topToBottomVisualRect ( index, ritem, block, blockPos );
+ }
+
+ //BEGIN: update the quarantine start
+ const bool wasLastIndex = ( index.row() == ( block.firstIndex.row() + block.items.count() - 1 ) );
+ if ( index.row() == block.quarantineStart.row() )
+ {
+ if ( wasLastIndex )
+ {
+ block.quarantineStart = QModelIndex();
+ }
+ else
+ {
+ const QModelIndex nextIndex = d->proxyModel->index ( index.row() + 1, modelColumn(), rootIndex() );
+ block.quarantineStart = nextIndex;
+ }
+ }
+ //END: update the quarantine start
+ }
+
+ // we get now the absolute position through the relative position of the parent block. do not
+ // save this on ritem, since this would override the item relative position in block terms.
+ Private::Item item ( ritem );
+ item.topLeft.ry() += blockPos.y();
+
+ const QSize sizeHint = item.size;
+
+ if ( d->hasGrid() )
+ {
+ const QSize sizeGrid = gridSize();
+ const QSize resultingSize = sizeHint.boundedTo ( sizeGrid );
+ QRect res ( item.topLeft.x() + ( ( sizeGrid.width() - resultingSize.width() ) / 2 ),
+ item.topLeft.y(), resultingSize.width(), resultingSize.height() );
+ if ( block.collapsed )
+ {
+ // we can still do binary search, while we "hide" items. We move those items in collapsed
+ // blocks to the left and set a 0 height.
+ res.setLeft ( -resultingSize.width() );
+ res.setHeight ( 0 );
+ }
+ return d->mapToViewport ( res );
+ }
+
+ QRect res ( item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height() );
+ if ( block.collapsed )
+ {
+ // we can still do binary search, while we "hide" items. We move those items in collapsed
+ // blocks to the left and set a 0 height.
+ res.setLeft ( -sizeHint.width() );
+ res.setHeight ( 0 );
+ }
+ return d->mapToViewport ( res );
+}
+
+KCategoryDrawer *KCategorizedView::categoryDrawer() const
+{
+ return d->categoryDrawer;
+}
+
+void KCategorizedView::setCategoryDrawer ( KCategoryDrawer *categoryDrawer )
+{
+ disconnect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ),
+ this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) );
+ d->categoryDrawer = categoryDrawer;
+
+ connect ( d->categoryDrawer, SIGNAL ( collapseOrExpandClicked ( QModelIndex ) ),
+ this, SLOT ( slotCollapseOrExpandClicked ( QModelIndex ) ) );
+}
+
+int KCategorizedView::categorySpacing() const
+{
+ return d->categorySpacing;
+}
+
+void KCategorizedView::setCategorySpacing ( int categorySpacing )
+{
+ if ( d->categorySpacing == categorySpacing )
+ {
+ return;
+ }
+
+ d->categorySpacing = categorySpacing;
+
+ for ( QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it )
+ {
+ Private::Block &block = *it;
+ block.outOfQuarantine = false;
+ }
+}
+
+bool KCategorizedView::alternatingBlockColors() const
+{
+ return d->alternatingBlockColors;
+}
+
+void KCategorizedView::setAlternatingBlockColors ( bool enable )
+{
+ d->alternatingBlockColors = enable;
+}
+
+bool KCategorizedView::collapsibleBlocks() const
+{
+ return d->collapsibleBlocks;
+}
+
+void KCategorizedView::setCollapsibleBlocks ( bool enable )
+{
+ d->collapsibleBlocks = enable;
+}
+
+QModelIndexList KCategorizedView::block ( const QString &category )
+{
+ QModelIndexList res;
+ const Private::Block &block = d->blocks[category];
+ if ( block.height == -1 )
+ {
+ return res;
+ }
+ QModelIndex current = block.firstIndex;
+ const int first = current.row();
+ for ( int i = 1; i <= block.items.count(); ++i )
+ {
+ if ( current.isValid() )
+ {
+ res << current;
+ }
+ current = d->proxyModel->index ( first + i, modelColumn(), rootIndex() );
+ }
+ return res;
+}
+
+QModelIndexList KCategorizedView::block ( const QModelIndex &representative )
+{
+ return block ( representative.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString() );
+}
+
+QModelIndex KCategorizedView::indexAt ( const QPoint &point ) const
+{
+ if ( !d->isCategorized() )
+ {
+ return QListView::indexAt ( point );
+ }
+
+ const int rowCount = d->proxyModel->rowCount();
+ if ( !rowCount )
+ {
+ return QModelIndex();
+ }
+
+ // Binary search that will try to spot if there is an index under point
+ int bottom = 0;
+ int top = rowCount - 1;
+ while ( bottom <= top )
+ {
+ const int middle = ( bottom + top ) / 2;
+ const QModelIndex index = d->proxyModel->index ( middle, modelColumn(), rootIndex() );
+ QRect rect = visualRect ( index );
+ const int verticalOff = verticalOffset();
+ int horizontalOff = horizontalOffset();
+ if ( layoutDirection() == Qt::RightToLeft )