/** * This file is part of the KDE project * Copyright (C) 2007, 2009 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. */ /** * 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 "categorizedview.h" #include "categorizedview_p.h" #include // trunc on C99 compliant systems //#include // trunc for not C99 compliant systems #include #include #include #include "categorydrawer.h" #include "categorizedsortfilterproxymodel.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() ) , 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 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() ) , constantItemWidth( 0 ) { } 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 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::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::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 blockList = blocks.values(); qSort ( blockList.begin(), blockList.end(), Block::lessThan ); QList firstIndexesRows; foreach ( const Block &block, blockList ) { firstIndexesRows << block.firstIndex.row(); } //END: order for marking as alternate those blocks that are alternate for ( QHash::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() /*|| q->uniformItemWidths()*/ ) { const int relativeRow = index.row() - firstIndexRow; const QSize itemSize = q->sizeHintForIndex ( index ); //HACK: Why is the -2 needed? const int maxItemsPerRow = qMax ( ( viewportWidth() - q->spacing() - 2 ) / ( 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() ); QRect tempRect = q->visualRect ( prevIndex ); tempRect = mapFromViewport ( tempRect ); 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 ( 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::setUniformItemWidths(bool enable) { d->constantItemWidth = enable; } bool KCategorizedView::uniformItemWidths() const { return d->constantItemWidth; } 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::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 ) { horizontalOff *= -1; } rect.topLeft().ry() += verticalOff; rect.topLeft().rx() += horizontalOff; rect.bottomRight().ry() += verticalOff; rect.bottomRight().rx() += horizontalOff; if ( rect.contains ( point ) ) { if ( index.model()->flags ( index ) & Qt::ItemIsEnabled ) { return index; } return QModelIndex(); } bool directionCondition; if ( layoutDirection() == Qt::LeftToRight ) { directionCondition = point.x() > rect.bottomRight().x(); } else { directionCondition = point.x() < rect.bottomLeft().x(); } if ( point.y() > rect.bottomRight().y() || ( point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition ) ) { bottom = middle + 1; } else { top = middle - 1; } } return QModelIndex(); } void KCategorizedView::reset() { d->blocks.clear(); QListView::reset(); } void KCategorizedView::paintEvent ( QPaintEvent *event ) { if ( !d->isCategorized() ) { QListView::paintEvent ( event ); return; } const QPair intersecting = d->intersectingIndexesWithRect ( viewport()->rect().intersected ( event->rect() ) ); QPainter p ( viewport() ); p.save(); Q_ASSERT ( selectionModel()->model() == d->proxyModel ); //BEGIN: draw categories QHash::ConstIterator it ( d->blocks.constBegin() ); while ( it != d->blocks.constEnd() ) { const Private::Block &block = *it; const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); QStyleOptionViewItemV4 option ( viewOptions() ); option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate : QStyleOptionViewItemV4::None; option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open : QStyle::State_None; const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); QPoint pos = d->blockPosition ( it.key() ); pos.ry() -= height; option.rect.setTopLeft ( pos ); option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); option.rect = d->mapToViewport ( option.rect ); if ( !option.rect.intersects ( viewport()->rect() ) ) { ++it; continue; } d->categoryDrawer->drawCategory ( categoryIndex, d->proxyModel->sortRole(), option, &p ); ++it; } //END: draw categories if ( intersecting.first.isValid() && intersecting.second.isValid() ) { //BEGIN: draw items int i = intersecting.first.row(); int indexToCheckIfBlockCollapsed = i; QModelIndex categoryIndex; QString category; Private::Block *block = 0; while ( i <= intersecting.second.row() ) { //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting if ( i == indexToCheckIfBlockCollapsed ) { categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); block = &d->blocks[category]; indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); if ( block->collapsed ) { i = indexToCheckIfBlockCollapsed; continue; } } //END: first check if the block is collapsed. if so, we have to skip the item painting Q_ASSERT ( block ); const bool alternateItem = ( i - block->firstIndex.row() ) % 2; const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); const Qt::ItemFlags flags = d->proxyModel->flags ( index ); QStyleOptionViewItemV4 option ( viewOptions() ); option.rect = visualRect ( index ); option.widget = this; option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText : QStyleOptionViewItemV2::None; option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate : QStyleOptionViewItemV4::None; if ( flags & Qt::ItemIsSelectable ) { option.state |= selectionModel()->isSelected ( index ) ? QStyle::State_Selected : QStyle::State_None; } else { option.state &= ~QStyle::State_Selected; } option.state |= ( index == currentIndex() ) ? QStyle::State_HasFocus : QStyle::State_None; if ( ! ( flags & Qt::ItemIsEnabled ) ) { option.state &= ~QStyle::State_Enabled; } else { option.state |= ( index == d->hoveredIndex ) ? QStyle::State_MouseOver : QStyle::State_None; } itemDelegate ( index )->paint ( &p, option, index ); ++i; } //END: draw items } //BEGIN: draw selection rect if ( isSelectionRectVisible() && d->rubberBandRect.isValid() ) { QStyleOptionRubberBand opt; opt.initFrom ( this ); opt.shape = QRubberBand::Rectangle; opt.opaque = false; opt.rect = d->mapToViewport ( d->rubberBandRect ).intersected ( viewport()->rect().adjusted ( -16, -16, 16, 16 ) ); p.save(); style()->drawControl ( QStyle::CE_RubberBand, &opt, &p ); p.restore(); } //END: draw selection rect p.restore(); } void KCategorizedView::resizeEvent ( QResizeEvent *event ) { d->regenerateAllElements(); QListView::resizeEvent ( event ); } void KCategorizedView::setSelection ( const QRect &rect, QItemSelectionModel::SelectionFlags flags ) { if ( !d->isCategorized() ) { QListView::setSelection ( rect, flags ); return; } if ( rect.topLeft() == rect.bottomRight() ) { const QModelIndex index = indexAt ( rect.topLeft() ); selectionModel()->select ( index, flags ); return; } const QPair intersecting = d->intersectingIndexesWithRect ( rect ); QItemSelection selection; //TODO: think of a faster implementation QModelIndex firstIndex; QModelIndex lastIndex; for ( int i = intersecting.first.row(); i <= intersecting.second.row(); ++i ) { const QModelIndex index = d->proxyModel->index ( i, modelColumn(), rootIndex() ); const bool visualRectIntersects = visualRect ( index ).intersects ( rect ); if ( firstIndex.isValid() ) { if ( visualRectIntersects ) { lastIndex = index; } else { selection << QItemSelectionRange ( firstIndex, lastIndex ); firstIndex = QModelIndex(); } } else if ( visualRectIntersects ) { firstIndex = index; lastIndex = index; } } if ( firstIndex.isValid() ) { selection << QItemSelectionRange ( firstIndex, lastIndex ); } selectionModel()->select ( selection, flags ); } void KCategorizedView::mouseMoveEvent ( QMouseEvent *event ) { QListView::mouseMoveEvent ( event ); d->hoveredIndex = indexAt ( event->pos() ); const SelectionMode itemViewSelectionMode = selectionMode(); if ( state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection && itemViewSelectionMode != NoSelection ) { QRect rect ( d->pressedPosition, event->pos() + QPoint ( horizontalOffset(), verticalOffset() ) ); rect = rect.normalized(); update ( rect.united ( d->rubberBandRect ) ); d->rubberBandRect = rect; } QHash::ConstIterator it ( d->blocks.constBegin() ); while ( it != d->blocks.constEnd() ) { const Private::Block &block = *it; const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); QStyleOptionViewItemV4 option ( viewOptions() ); const int height = d->categoryDrawer->categoryHeight ( categoryIndex, option ); QPoint pos = d->blockPosition ( it.key() ); pos.ry() -= height; option.rect.setTopLeft ( pos ); option.rect.setWidth ( d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin() ); option.rect.setHeight ( height + d->blockHeight ( it.key() ) ); option.rect = d->mapToViewport ( option.rect ); const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); if ( option.rect.contains ( mousePos ) ) { if ( d->categoryDrawer && d->hoveredBlock->height != -1 && *d->hoveredBlock != block ) { const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); *d->hoveredBlock = block; d->hoveredCategory = it.key(); viewport()->update ( option.rect ); } else if ( d->hoveredBlock->height == -1 ) { *d->hoveredBlock = block; d->hoveredCategory = it.key(); } else if ( d->categoryDrawer ) { d->categoryDrawer->mouseMoved ( categoryIndex, option.rect, event ); } viewport()->update ( option.rect ); return; } ++it; } if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) { const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); *d->hoveredBlock = Private::Block(); d->hoveredCategory = QString(); viewport()->update ( option.rect ); } } void KCategorizedView::mousePressEvent ( QMouseEvent *event ) { if ( event->button() == Qt::LeftButton ) { d->pressedPosition = event->pos(); d->pressedPosition.rx() += horizontalOffset(); d->pressedPosition.ry() += verticalOffset(); } if ( !d->categoryDrawer ) { QListView::mousePressEvent ( event ); return; } QHash::ConstIterator it ( d->blocks.constBegin() ); while ( it != d->blocks.constEnd() ) { const Private::Block &block = *it; const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); if ( option.rect.contains ( mousePos ) ) { if ( d->categoryDrawer ) { d->categoryDrawer->mouseButtonPressed ( categoryIndex, option.rect, event ); } viewport()->update ( option.rect ); if ( !event->isAccepted() ) { QListView::mousePressEvent ( event ); } return; } ++it; } QListView::mousePressEvent ( event ); } void KCategorizedView::mouseReleaseEvent ( QMouseEvent *event ) { d->pressedPosition = QPoint(); d->rubberBandRect = QRect(); QHash::ConstIterator it ( d->blocks.constBegin() ); while ( it != d->blocks.constEnd() ) { const Private::Block &block = *it; const QModelIndex categoryIndex = d->proxyModel->index ( block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); const QPoint mousePos = viewport()->mapFromGlobal ( QCursor::pos() ); if ( option.rect.contains ( mousePos ) ) { if ( d->categoryDrawer ) { d->categoryDrawer->mouseButtonReleased ( categoryIndex, option.rect, event ); } viewport()->update ( option.rect ); if ( !event->isAccepted() ) { QListView::mouseReleaseEvent ( event ); } return; } ++it; } QListView::mouseReleaseEvent ( event ); } void KCategorizedView::leaveEvent ( QEvent *event ) { QListView::leaveEvent ( event ); if ( d->hoveredIndex.isValid() ) { viewport()->update ( visualRect ( d->hoveredIndex ) ); d->hoveredIndex = QModelIndex(); } if ( d->categoryDrawer && d->hoveredBlock->height != -1 ) { const QModelIndex categoryIndex = d->proxyModel->index ( d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex() ); const QStyleOptionViewItemV4 option = d->blockRect ( categoryIndex ); d->categoryDrawer->mouseLeft ( categoryIndex, option.rect ); *d->hoveredBlock = Private::Block(); d->hoveredCategory = QString(); viewport()->update ( option.rect ); } } void KCategorizedView::startDrag ( Qt::DropActions supportedActions ) { QListView::startDrag ( supportedActions ); } void KCategorizedView::dragMoveEvent ( QDragMoveEvent *event ) { QListView::dragMoveEvent ( event ); d->hoveredIndex = indexAt ( event->pos() ); } void KCategorizedView::dragEnterEvent ( QDragEnterEvent *event ) { QListView::dragEnterEvent ( event ); } void KCategorizedView::dragLeaveEvent ( QDragLeaveEvent *event ) { QListView::dragLeaveEvent ( event ); } void KCategorizedView::dropEvent ( QDropEvent *event ) { QListView::dropEvent ( event ); } //TODO: improve se we take into account collapsed blocks //TODO: take into account when there is no grid and no uniformItemSizes QModelIndex KCategorizedView::moveCursor ( CursorAction cursorAction, Qt::KeyboardModifiers modifiers ) { if ( !d->isCategorized() ) { return QListView::moveCursor ( cursorAction, modifiers ); } const QModelIndex current = currentIndex(); const QRect currentRect = visualRect ( current ); if ( !current.isValid() ) { const int rowCount = d->proxyModel->rowCount ( rootIndex() ); if ( !rowCount ) { return QModelIndex(); } return d->proxyModel->index ( 0, modelColumn(), rootIndex() ); } switch ( cursorAction ) { case MoveLeft: { if ( !current.row() ) { return QModelIndex(); } const QModelIndex previous = d->proxyModel->index ( current.row() - 1, modelColumn(), rootIndex() ); const QRect previousRect = visualRect ( previous ); if ( previousRect.top() == currentRect.top() ) { return previous; } return QModelIndex(); } case MoveRight: { if ( current.row() == d->proxyModel->rowCount() - 1 ) { return QModelIndex(); } const QModelIndex next = d->proxyModel->index ( current.row() + 1, modelColumn(), rootIndex() ); const QRect nextRect = visualRect ( next ); if ( nextRect.top() == currentRect.top() ) { return next; } return QModelIndex(); } case MoveDown: { if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) { const QModelIndex current = currentIndex(); const QSize itemSize = d->hasGrid() ? gridSize() : sizeHintForIndex ( current ); const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; //HACK: Why is the -2 needed? const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + block.items.count(); if ( canMove ) { return d->proxyModel->index ( current.row() + maxItemsPerRow, modelColumn(), rootIndex() ); } const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; const QModelIndex nextIndex = d->proxyModel->index ( block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex() ); if ( !nextIndex.isValid() ) { return QModelIndex(); } const Private::Block &nextBlock = d->blocks[d->categoryForIndex ( nextIndex )]; if ( nextBlock.items.count() <= currentRelativePos ) { return QModelIndex(); } if ( currentRelativePos < ( block.items.count() % maxItemsPerRow ) ) { return d->proxyModel->index ( nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex() ); } return QModelIndex(); } } case MoveUp: { if ( d->hasGrid() || uniformItemSizes() || uniformItemWidths() ) { const QModelIndex current = currentIndex(); const QSize itemSize = d->hasGrid() ? gridSize() : sizeHintForIndex ( current ); const Private::Block &block = d->blocks[d->categoryForIndex ( current )]; //HACK: Why is the -2 needed? const int maxItemsPerRow = qMax ( ( d->viewportWidth() - spacing() - 2 ) / ( itemSize.width() + spacing() ), 1 ); const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); if ( canMove ) { return d->proxyModel->index ( current.row() - maxItemsPerRow, modelColumn(), rootIndex() ); } const int currentRelativePos = ( current.row() - block.firstIndex.row() ) % maxItemsPerRow; const QModelIndex prevIndex = d->proxyModel->index ( block.firstIndex.row() - 1, modelColumn(), rootIndex() ); if ( !prevIndex.isValid() ) { return QModelIndex(); } const Private::Block &prevBlock = d->blocks[d->categoryForIndex ( prevIndex )]; if ( prevBlock.items.count() <= currentRelativePos ) { return QModelIndex(); } const int remainder = prevBlock.items.count() % maxItemsPerRow; if ( currentRelativePos < remainder ) { return d->proxyModel->index ( prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex() ); } return QModelIndex(); } } default: break; } return QModelIndex(); } void KCategorizedView::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) { if ( !d->isCategorized() ) { QListView::rowsAboutToBeRemoved ( parent, start, end ); return; } *d->hoveredBlock = Private::Block(); d->hoveredCategory = QString(); if ( end - start + 1 == d->proxyModel->rowCount() ) { d->blocks.clear(); QListView::rowsAboutToBeRemoved ( parent, start, end ); return; } // Removal feels a bit more complicated than insertion. Basically we can consider there are // 3 different cases when going to remove items. (*) represents an item, Items between ([) and // (]) are the ones which are marked for removal. // // - 1st case: // ... * * * * * * [ * * * ... // // The items marked for removal are the last part of this category. No need to mark any item // of this category as in quarantine, because no special offset will be pushed to items at // the right because of any changes (since the removed items are those on the right most part // of the category). // // - 2nd case: // ... * * * * * * ] * * * ... // // The items marked for removal are the first part of this category. We have to mark as in // quarantine all items in this category. Absolutely all. All items will have to be moved to // the left (or moving up, because rows got a different offset). // // - 3rd case: // ... * * [ * * * * ] * * ... // // The items marked for removal are in between of this category. We have to mark as in // quarantine only those items that are at the right of the end of the removal interval, // (starting on "]"). // // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are // located under the top most affected category as in quarantine (the block itself, as a whole), // because such a change can force it to have a different offset (note that items themselves // contain relative positions to the block, so marking the block as in quarantine is enough). // // Also note that removal implicitly means that we have to update correctly firstIndex of each // block, and in general keep updated the internal information of elements. QStringList listOfCategoriesMarkedForRemoval; QString lastCategory; int alreadyRemoved = 0; for ( int i = start; i <= end; ++i ) { const QModelIndex index = d->proxyModel->index ( i, modelColumn(), parent ); Q_ASSERT ( index.isValid() ); const QString category = d->categoryForIndex ( index ); if ( lastCategory != category ) { lastCategory = category; alreadyRemoved = 0; } Private::Block &block = d->blocks[category]; block.items.removeAt ( i - block.firstIndex.row() - alreadyRemoved ); ++alreadyRemoved; if ( !block.items.count() ) { listOfCategoriesMarkedForRemoval << category; } block.height = -1; viewport()->update(); } //BEGIN: update the items that are in quarantine in affected categories { const QModelIndex lastIndex = d->proxyModel->index ( end, modelColumn(), parent ); const QString category = d->categoryForIndex ( lastIndex ); Private::Block &block = d->blocks[category]; if ( block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row() ) { block.firstIndex = d->proxyModel->index ( end + 1, modelColumn(), parent ); } block.quarantineStart = block.firstIndex; } //END: update the items that are in quarantine in affected categories Q_FOREACH ( const QString &category, listOfCategoriesMarkedForRemoval ) { d->blocks.remove ( category ); } //BEGIN: mark as in quarantine those categories that are under the affected ones { //BEGIN: order for marking as alternate those blocks that are alternate QList blockList = d->blocks.values(); qSort ( blockList.begin(), blockList.end(), Private::Block::lessThan ); QList firstIndexesRows; foreach ( const Private::Block &block, blockList ) { firstIndexesRows << block.firstIndex.row(); } //END: order for marking as alternate those blocks that are alternate for ( QHash::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it ) { Private::Block &block = *it; if ( block.firstIndex.row() > start ) { block.outOfQuarantine = false; block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; } else if ( block.firstIndex.row() == start ) { block.alternate = firstIndexesRows.indexOf ( block.firstIndex.row() ) % 2; } } } //END: mark as in quarantine those categories that are under the affected ones QListView::rowsAboutToBeRemoved ( parent, start, end ); } void KCategorizedView::updateGeometries() { const int oldVerticalOffset = verticalOffset(); const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy(); //BEGIN bugs 213068, 287847 ------------------------------------------------------------ /* * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not * and triggers a (sometimes additionally timered) resize through ::layoutChildren() * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499 * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy) * * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI * So we prevent QListView from having an own opinion on the scrollbar visibility by * fixing it before calling the baseclass QListView::updateGeometries() * * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself. */ if ( d->isCategorized() ) // important! - otherwise we'd pollute the setting if the view is initially not categorized { setVerticalScrollBarPolicy ( ( verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo ( this ) ) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); setHorizontalScrollBarPolicy ( ( horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo ( this ) ) ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff ); } //END bugs 213068, 287847 -------------------------------------------------------------- QListView::updateGeometries(); if ( !d->isCategorized() ) { return; } const int rowCount = d->proxyModel->rowCount(); if ( !rowCount ) { verticalScrollBar()->setRange ( 0, 0 ); // unconditional, see function end todo horizontalScrollBar()->setRange ( 0, 0 ); return; } const QModelIndex lastIndex = d->proxyModel->index ( rowCount - 1, modelColumn(), rootIndex() ); Q_ASSERT ( lastIndex.isValid() ); QRect lastItemRect = visualRect ( lastIndex ); if ( d->hasGrid() ) { lastItemRect.setSize ( lastItemRect.size().expandedTo ( gridSize() ) ); } else { if ( uniformItemSizes() ) { QSize itemSize = sizeHintForIndex ( lastIndex ); itemSize.setHeight ( itemSize.height() + spacing() ); lastItemRect.setSize ( itemSize ); } else { QSize itemSize = sizeHintForIndex ( lastIndex ); const QString category = d->categoryForIndex ( lastIndex ); itemSize.setHeight ( d->highestElementInLastRow ( d->blocks[category] ) + spacing() ); lastItemRect.setSize ( itemSize ); } } const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); if ( verticalScrollMode() == ScrollPerItem ) { verticalScrollBar()->setSingleStep ( lastItemRect.height() ); const int rowsPerPage = qMax ( viewport()->height() / lastItemRect.height(), 1 ); verticalScrollBar()->setPageStep ( rowsPerPage * lastItemRect.height() ); } verticalScrollBar()->setRange ( 0, bottomRange ); verticalScrollBar()->setValue ( oldVerticalOffset ); //TODO: also consider working with the horizontal scroll bar. since at this level I am not still // supporting "top to bottom" flow, there is no real problem. If I support that someday // (think how to draw categories), we would have to take care of the horizontal scroll bar too. // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. horizontalScrollBar()->setRange ( 0, 0 ); //BEGIN bugs 213068, 287847 ------------------------------------------------------------ // restoring values from above ... setVerticalScrollBarPolicy ( verticalP ); setHorizontalScrollBarPolicy ( horizontalP ); // ... and correct the visibility bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum(); if ( verticalP == Qt::ScrollBarAsNeeded && ( verticalScrollBar()->isVisibleTo ( this ) != validRange ) ) verticalScrollBar()->setVisible ( validRange ); validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum(); if ( horizontalP == Qt::ScrollBarAsNeeded && ( horizontalScrollBar()->isVisibleTo ( this ) != validRange ) ) horizontalScrollBar()->setVisible ( validRange ); //END bugs 213068, 287847 -------------------------------------------------------------- } void KCategorizedView::currentChanged ( const QModelIndex ¤t, const QModelIndex &previous ) { QListView::currentChanged ( current, previous ); } void KCategorizedView::dataChanged ( const QModelIndex &topLeft, const QModelIndex &bottomRight ) { QListView::dataChanged ( topLeft, bottomRight ); if ( !d->isCategorized() ) { return; } *d->hoveredBlock = Private::Block(); d->hoveredCategory = QString(); //BEGIN: since the model changed data, we need to reconsider item sizes int i = topLeft.row(); int indexToCheck = i; QModelIndex categoryIndex; QString category; Private::Block *block; while ( i <= bottomRight.row() ) { const QModelIndex currIndex = d->proxyModel->index ( i, modelColumn(), rootIndex() ); if ( i == indexToCheck ) { categoryIndex = d->proxyModel->index ( i, d->proxyModel->sortColumn(), rootIndex() ); category = categoryIndex.data ( KCategorizedSortFilterProxyModel::CategoryDisplayRole ).toString(); block = &d->blocks[category]; block->quarantineStart = currIndex; indexToCheck = block->firstIndex.row() + block->items.count(); } visualRect ( currIndex ); ++i; } //END: since the model changed data, we need to reconsider item sizes } void KCategorizedView::rowsInserted ( const QModelIndex &parent, int start, int end ) { QListView::rowsInserted ( parent, start, end ); if ( !d->isCategorized() ) { return; } *d->hoveredBlock = Private::Block(); d->hoveredCategory = QString(); d->rowsInserted ( parent, start, end ); } void KCategorizedView::slotLayoutChanged() { if ( !d->isCategorized() ) { return; } d->blocks.clear(); *d->hoveredBlock = Private::Block(); d->hoveredCategory = QString(); if ( d->proxyModel->rowCount() ) { d->rowsInserted ( rootIndex(), 0, d->proxyModel->rowCount() - 1 ); } } //END: Public part void KCategorizedView::slotCollapseOrExpandClicked ( QModelIndex idx ) { d->_k_slotCollapseOrExpandClicked ( idx ); } #include "categorizedview.moc"