2
* This file is part of the KDE project
3
* Copyright (C) 2007, 2009 Rafael Fernández López <ereslibre@kde.org>
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Library General Public
7
* License as published by the Free Software Foundation; either
8
* version 2 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Library General Public License for more details.
15
* You should have received a copy of the GNU Library General Public License
16
* along with this library; see the file COPYING.LIB. If not, write to
17
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18
* Boston, MA 02110-1301, USA.
22
* IMPLEMENTATION NOTES:
24
* QListView::setRowHidden() and QListView::isRowHidden() are not taken into
25
* account. This methods should actually not exist. This effect should be handled
26
* by an hypothetical QSortFilterProxyModel which filters out the desired rows.
28
* In case this needs to be implemented, contact me, but I consider this a faulty
32
#include "kcategorizedview.h"
33
#include "kcategorizedview_p.h"
37
#include <QPaintEvent>
39
#include "kcategorydrawer.h"
40
#include "kcategorizedsortfilterproxymodel.h"
44
struct KCategorizedView::Private::Item {
55
struct KCategorizedView::Private::Block {
59
, firstIndex(QModelIndex())
60
, quarantineStart(QModelIndex())
61
, items(QList<Item>())
62
, outOfQuarantine(false)
68
bool operator!=(const Block &rhs) const
70
return firstIndex != rhs.firstIndex;
73
static bool lessThan(const Block &left, const Block &right)
75
Q_ASSERT(left.firstIndex.isValid());
76
Q_ASSERT(right.firstIndex.isValid());
77
return left.firstIndex.row() < right.firstIndex.row();
82
QPersistentModelIndex firstIndex;
83
// if we have n elements on this block, and we inserted an element at position i. The quarantine
84
// will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the
85
// visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine
86
// will only affect the current block, since the rest of blocks can be affected only in the way
87
// that the whole block will have different offset, but items will keep the same relative position
88
// in terms of their parent blocks.
89
QPersistentModelIndex quarantineStart;
92
// this affects the whole block, not items separately. items contain the topLeft point relative
93
// to the block. Because of insertions or removals a whole block can be moved, so the whole block
94
// will enter in quarantine, what is faster than moving all items in absolute terms.
97
// should we alternate its color ? is just a hint, could not be used
102
KCategorizedView::Private::Private(KCategorizedView *q)
107
, alternatingBlockColors(false)
108
, collapsibleBlocks(false)
109
, hoveredBlock(new Block())
110
, hoveredIndex(QModelIndex())
111
, pressedPosition(QPoint())
112
, rubberBandRect(QRect())
116
KCategorizedView::Private::~Private()
121
bool KCategorizedView::Private::isCategorized() const
123
return proxyModel && categoryDrawer && proxyModel->isCategorizedModel();
126
QStyleOptionViewItemV4 KCategorizedView::Private::blockRect(const QModelIndex &representative)
128
QStyleOptionViewItemV4 option(q->viewOptions());
129
const int height = categoryDrawer->categoryHeight(representative, option);
130
const QString categoryDisplay = representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
131
QPoint pos = blockPosition(categoryDisplay);
133
option.rect.setTopLeft(pos);
134
option.rect.setWidth(viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin());
135
option.rect.setHeight(height + blockHeight(categoryDisplay));
136
option.rect = mapToViewport(option.rect);
141
QPair<QModelIndex, QModelIndex> KCategorizedView::Private::intersectingIndexesWithRect(const QRect &_rect) const
143
const int rowCount = proxyModel->rowCount();
145
const QRect rect = _rect.normalized();
147
// binary search to find out the top border
149
int top = rowCount - 1;
150
while (bottom <= top) {
151
const int middle = (bottom + top) / 2;
152
const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
153
const QRect itemRect = q->visualRect(index);
154
if (itemRect.bottomRight().y() <= rect.topLeft().y()) {
161
const QModelIndex bottomIndex = proxyModel->index(bottom, q->modelColumn(), q->rootIndex());
163
// binary search to find out the bottom border
166
while (bottom <= top) {
167
const int middle = (bottom + top) / 2;
168
const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
169
const QRect itemRect = q->visualRect(index);
170
if (itemRect.topLeft().y() <= rect.bottomRight().y()) {
177
const QModelIndex topIndex = proxyModel->index(top, q->modelColumn(), q->rootIndex());
179
return qMakePair(bottomIndex, topIndex);
182
QPoint KCategorizedView::Private::blockPosition(const QString &category)
184
Block &block = blocks[category];
186
if (block.outOfQuarantine && !block.topLeft.isNull()) {
187
return block.topLeft;
190
QPoint res(categorySpacing, 0);
192
const QModelIndex index = block.firstIndex;
194
for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
196
const QModelIndex categoryIndex = block.firstIndex;
197
if (index.row() < categoryIndex.row()) {
200
res.ry() += categoryDrawer->categoryHeight(categoryIndex, q->viewOptions()) + categorySpacing;
201
if (index.row() == categoryIndex.row()) {
204
res.ry() += blockHeight(it.key());
207
block.outOfQuarantine = true;
213
int KCategorizedView::Private::blockHeight(const QString &category)
215
Block &block = blocks[category];
217
if (block.collapsed) {
221
if (block.height > -1) {
225
const QModelIndex firstIndex = block.firstIndex;
226
const QModelIndex lastIndex = proxyModel->index(firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
227
const QRect topLeft = q->visualRect(firstIndex);
228
QRect bottomRight = q->visualRect(lastIndex);
231
bottomRight.setHeight(qMax(bottomRight.height(), q->gridSize().height()));
233
if (!q->uniformItemSizes()) {
234
bottomRight.setHeight(highestElementInLastRow(block) + q->spacing() * 2);
238
const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1;
239
block.height = height;
244
int KCategorizedView::Private::viewportWidth() const
246
return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin();
249
void KCategorizedView::Private::regenerateAllElements()
251
for (QHash<QString, Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
253
block.outOfQuarantine = false;
254
block.quarantineStart = block.firstIndex;
259
void KCategorizedView::Private::rowsInserted(const QModelIndex &parent, int start, int end)
261
if (!isCategorized()) {
265
for (int i = start; i <= end; ++i) {
266
const QModelIndex index = proxyModel->index(i, q->modelColumn(), parent);
268
Q_ASSERT(index.isValid());
270
const QString category = categoryForIndex(index);
272
Block &block = blocks[category];
274
//BEGIN: update firstIndex
275
// save as firstIndex in block if
276
// - it forced the category creation (first element on this category)
277
// - it is before the first row on that category
278
const QModelIndex firstIndex = block.firstIndex;
279
if (!firstIndex.isValid() || index.row() < firstIndex.row()) {
280
block.firstIndex = index;
282
//END: update firstIndex
284
Q_ASSERT(block.firstIndex.isValid());
286
const int firstIndexRow = block.firstIndex.row();
288
block.items.insert(index.row() - firstIndexRow, Private::Item());
291
q->visualRect(index);
292
q->viewport()->update();
295
//BEGIN: update the items that are in quarantine in affected categories
297
const QModelIndex lastIndex = proxyModel->index(end, q->modelColumn(), parent);
298
const QString category = categoryForIndex(lastIndex);
299
Private::Block &block = blocks[category];
300
block.quarantineStart = block.firstIndex;
302
//END: update the items that are in quarantine in affected categories
304
//BEGIN: mark as in quarantine those categories that are under the affected ones
306
const QModelIndex firstIndex = proxyModel->index(start, q->modelColumn(), parent);
307
const QString category = categoryForIndex(firstIndex);
308
const QModelIndex firstAffectedCategory = blocks[category].firstIndex;
309
//BEGIN: order for marking as alternate those blocks that are alternate
310
QList<Block> blockList = blocks.values();
311
qSort(blockList.begin(), blockList.end(), Block::lessThan);
312
QList<int> firstIndexesRows;
313
foreach (const Block &block, blockList) {
314
firstIndexesRows << block.firstIndex.row();
316
//END: order for marking as alternate those blocks that are alternate
317
for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
318
Private::Block &block = *it;
319
if (block.firstIndex.row() > firstAffectedCategory.row()) {
320
block.outOfQuarantine = false;
321
block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
322
} else if (block.firstIndex.row() == firstAffectedCategory.row()) {
323
block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
327
//END: mark as in quarantine those categories that are under the affected ones
330
QRect KCategorizedView::Private::mapToViewport(const QRect &rect) const
332
const int dx = -q->horizontalOffset();
333
const int dy = -q->verticalOffset();
334
return rect.adjusted(dx, dy, dx, dy);
337
QRect KCategorizedView::Private::mapFromViewport(const QRect &rect) const
339
const int dx = q->horizontalOffset();
340
const int dy = q->verticalOffset();
341
return rect.adjusted(dx, dy, dx, dy);
344
int KCategorizedView::Private::highestElementInLastRow(const Block &block) const
346
//Find the highest element in the last row
347
const QModelIndex lastIndex = proxyModel->index(block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
348
const QRect prevRect = q->visualRect(lastIndex);
349
int res = prevRect.height();
350
QModelIndex prevIndex = proxyModel->index(lastIndex.row() - 1, q->modelColumn(), q->rootIndex());
351
if (!prevIndex.isValid()) {
355
const QRect tempRect = q->visualRect(prevIndex);
356
if (tempRect.topLeft().y() < prevRect.topLeft().y())
360
res = qMax(res, tempRect.height());
361
if (prevIndex == block.firstIndex)
365
prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
371
bool KCategorizedView::Private::hasGrid() const
373
const QSize gridSize = q->gridSize();
374
return gridSize.isValid() && !gridSize.isNull();
377
QString KCategorizedView::Private::categoryForIndex(const QModelIndex &index) const
379
const QModelIndex categoryIndex = index.model()->index(index.row(), proxyModel->sortColumn(), index.parent());
380
return categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
383
void KCategorizedView::Private::leftToRightVisualRect(const QModelIndex &index, Item &item,
384
const Block &block, const QPoint &blockPos) const
386
const int firstIndexRow = block.firstIndex.row();
389
const int relativeRow = index.row() - firstIndexRow;
390
const int maxItemsPerRow = qMax(viewportWidth() / q->gridSize().width(), 1);
391
if (q->layoutDirection() == Qt::LeftToRight) {
392
item.topLeft.rx() = (relativeRow % maxItemsPerRow) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin();
394
item.topLeft.rx() = viewportWidth() - ((relativeRow % maxItemsPerRow) + 1) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing;
396
item.topLeft.ry() = (relativeRow / maxItemsPerRow) * q->gridSize().height();
398
if (q->uniformItemSizes()) {
399
const int relativeRow = index.row() - firstIndexRow;
400
const QSize itemSize = q->sizeHintForIndex(index);
401
const int maxItemsPerRow = qMax((viewportWidth() - q->spacing()) / (itemSize.width() + q->spacing()), 1);
402
if (q->layoutDirection() == Qt::LeftToRight) {
403
item.topLeft.rx() = (relativeRow % maxItemsPerRow) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
405
item.topLeft.rx() = viewportWidth() - (relativeRow % maxItemsPerRow) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing;
407
item.topLeft.ry() = (relativeRow / maxItemsPerRow) * itemSize.height();
409
const QSize currSize = q->sizeHintForIndex(index);
410
if (index != block.firstIndex) {
411
const int viewportW = viewportWidth() - q->spacing();
412
QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
413
QRect prevRect = q->visualRect(prevIndex);
414
prevRect = mapFromViewport(prevRect);
415
if ((prevRect.bottomRight().x() + 1) + currSize.width() - blockPos.x() + q->spacing() > viewportW) {
416
// we have to check the whole previous row, and see which one was the
419
prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
420
const QRect tempRect = q->visualRect(prevIndex);
421
if (tempRect.topLeft().y() < prevRect.topLeft().y())
425
if (tempRect.bottomRight().y() > prevRect.bottomRight().y())
429
if (prevIndex == block.firstIndex)
434
if (q->layoutDirection() == Qt::LeftToRight) {
435
item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing();
437
item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
439
item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
441
if (q->layoutDirection() == Qt::LeftToRight) {
442
item.topLeft.rx() = (prevRect.bottomRight().x() + 1) + q->spacing();
444
item.topLeft.rx() = (prevRect.bottomLeft().x() - 1) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing;
446
item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y();
449
if (q->layoutDirection() == Qt::LeftToRight) {
450
item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
452
item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
454
item.topLeft.ry() = q->spacing();
458
item.size = q->sizeHintForIndex(index);
461
void KCategorizedView::Private::topToBottomVisualRect(const QModelIndex &index, Item &item,
462
const Block &block, const QPoint &blockPos) const
464
const int firstIndexRow = block.firstIndex.row();
467
const int relativeRow = index.row() - firstIndexRow;
468
item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
469
item.topLeft.ry() = relativeRow * q->gridSize().height();
471
if (q->uniformItemSizes()) {
472
const int relativeRow = index.row() - firstIndexRow;
473
const QSize itemSize = q->sizeHintForIndex(index);
474
item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
475
item.topLeft.ry() = relativeRow * itemSize.height();
477
if (index != block.firstIndex) {
478
QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
479
QRect prevRect = q->visualRect(prevIndex);
480
prevRect = mapFromViewport(prevRect);
481
item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
482
item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
484
item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
485
item.topLeft.ry() = q->spacing();
489
item.size = q->sizeHintForIndex(index);
490
item.size.setWidth(viewportWidth());
493
void KCategorizedView::Private::_k_slotCollapseOrExpandClicked(QModelIndex)
501
KCategorizedView::KCategorizedView(QWidget *parent)
503
, d(new Private(this))
507
KCategorizedView::~KCategorizedView()
512
void KCategorizedView::setModel(QAbstractItemModel *model)
514
if (d->proxyModel == model) {
521
disconnect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()));
524
d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel *>(model);
527
connect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()));
530
QListView::setModel(model);
532
// if the model already had information inserted, update our data structures to it
533
if (model->rowCount()) {
538
void KCategorizedView::setGridSize(const QSize &size)
540
setGridSizeOwn(size);
543
void KCategorizedView::setGridSizeOwn(const QSize &size)
545
d->regenerateAllElements();
546
QListView::setGridSize(size);
549
QRect KCategorizedView::visualRect(const QModelIndex &index) const
551
if (!d->isCategorized()) {
552
return QListView::visualRect(index);
555
if (!index.isValid()) {
559
const QString category = d->categoryForIndex(index);
561
if (!d->blocks.contains(category)) {
565
Private::Block &block = d->blocks[category];
566
const int firstIndexRow = block.firstIndex.row();
568
Q_ASSERT(block.firstIndex.isValid());
570
if (index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count()) {
574
const QPoint blockPos = d->blockPosition(category);
576
Private::Item &ritem = block.items[index.row() - firstIndexRow];
578
if (ritem.topLeft.isNull() || (block.quarantineStart.isValid() &&
579
index.row() >= block.quarantineStart.row())) {
580
if (flow() == LeftToRight) {
581
d->leftToRightVisualRect(index, ritem, block, blockPos);
583
d->topToBottomVisualRect(index, ritem, block, blockPos);
586
//BEGIN: update the quarantine start
587
const bool wasLastIndex = (index.row() == (block.firstIndex.row() + block.items.count() - 1));
588
if (index.row() == block.quarantineStart.row()) {
590
block.quarantineStart = QModelIndex();
592
const QModelIndex nextIndex = d->proxyModel->index(index.row() + 1, modelColumn(), rootIndex());
593
block.quarantineStart = nextIndex;
596
//END: update the quarantine start
599
// we get now the absolute position through the relative position of the parent block. do not
600
// save this on ritem, since this would override the item relative position in block terms.
601
Private::Item item(ritem);
602
item.topLeft.ry() += blockPos.y();
604
const QSize sizeHint = item.size;
607
const QSize sizeGrid = gridSize();
608
const QSize resultingSize = sizeHint.boundedTo(sizeGrid);
609
QRect res(item.topLeft.x() + ((sizeGrid.width() - resultingSize.width()) / 2),
610
item.topLeft.y(), resultingSize.width(), resultingSize.height());
611
if (block.collapsed) {
612
// we can still do binary search, while we "hide" items. We move those items in collapsed
613
// blocks to the left and set a 0 height.
614
res.setLeft(-resultingSize.width());
617
return d->mapToViewport(res);
620
QRect res(item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height());
621
if (block.collapsed) {
622
// we can still do binary search, while we "hide" items. We move those items in collapsed
623
// blocks to the left and set a 0 height.
624
res.setLeft(-sizeHint.width());
627
return d->mapToViewport(res);
630
KCategoryDrawer *KCategorizedView::categoryDrawer() const
632
return d->categoryDrawer;
635
void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer)
637
if (d->categoryDrawer) {
638
disconnect(d->categoryDrawer, SIGNAL(collapseOrExpandClicked(QModelIndex)),
639
this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
642
d->categoryDrawer = categoryDrawer;
644
connect(d->categoryDrawer, SIGNAL(collapseOrExpandClicked(QModelIndex)),
645
this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
648
int KCategorizedView::categorySpacing() const
650
return d->categorySpacing;
653
void KCategorizedView::setCategorySpacing(int categorySpacing)
655
if (d->categorySpacing == categorySpacing) {
659
d->categorySpacing = categorySpacing;
661
for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
662
Private::Block &block = *it;
663
block.outOfQuarantine = false;
667
bool KCategorizedView::alternatingBlockColors() const
669
return d->alternatingBlockColors;
672
void KCategorizedView::setAlternatingBlockColors(bool enable)
674
d->alternatingBlockColors = enable;
677
bool KCategorizedView::collapsibleBlocks() const
679
return d->collapsibleBlocks;
682
void KCategorizedView::setCollapsibleBlocks(bool enable)
684
d->collapsibleBlocks = enable;
687
QModelIndexList KCategorizedView::block(const QString &category)
690
const Private::Block &block = d->blocks[category];
691
if (block.height == -1) {
694
QModelIndex current = block.firstIndex;
695
const int first = current.row();
696
for (int i = 1; i <= block.items.count(); ++i) {
697
if (current.isValid()) {
700
current = d->proxyModel->index(first + i, modelColumn(), rootIndex());
705
QModelIndexList KCategorizedView::block(const QModelIndex &representative)
707
return block(representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString());
710
QModelIndex KCategorizedView::indexAt(const QPoint &point) const
712
if (!d->isCategorized()) {
713
return QListView::indexAt(point);
716
const int rowCount = d->proxyModel->rowCount();
718
return QModelIndex();
721
// Binary search that will try to spot if there is an index under point
723
int top = rowCount - 1;
724
while (bottom <= top) {
725
const int middle = (bottom + top) / 2;
726
const QModelIndex index = d->proxyModel->index(middle, modelColumn(), rootIndex());
727
QRect rect = visualRect(index);
728
const int verticalOff = verticalOffset();
729
int horizontalOff = horizontalOffset();
730
if (layoutDirection() == Qt::RightToLeft) {
733
rect.topLeft().ry() += verticalOff;
734
rect.topLeft().rx() += horizontalOff;
735
rect.bottomRight().ry() += verticalOff;
736
rect.bottomRight().rx() += horizontalOff;
737
if (rect.contains(point)) {
738
if (index.model()->flags(index) & Qt::ItemIsEnabled) {
741
return QModelIndex();
743
bool directionCondition;
744
if (layoutDirection() == Qt::LeftToRight) {
745
directionCondition = point.x() > rect.bottomRight().x();
747
directionCondition = point.x() < rect.bottomLeft().x();
749
if (point.y() > rect.bottomRight().y() ||
750
(point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition)) {
756
return QModelIndex();
759
void KCategorizedView::reset()
765
void KCategorizedView::paintEvent(QPaintEvent *event)
767
if (!d->isCategorized()) {
768
QListView::paintEvent(event);
772
const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(viewport()->rect().intersected(event->rect()));
774
QPainter p(viewport());
777
Q_ASSERT(selectionModel()->model() == d->proxyModel);
779
//BEGIN: draw categories
780
QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
781
while (it != d->blocks.constEnd()) {
782
const Private::Block &block = *it;
783
const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
784
QStyleOptionViewItemV4 option(viewOptions());
785
option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate
786
: QStyleOptionViewItemV4::None;
787
option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open
788
: QStyle::State_None;
789
const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
790
QPoint pos = d->blockPosition(it.key());
792
option.rect.setTopLeft(pos);
793
option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
794
option.rect.setHeight(height + d->blockHeight(it.key()));
795
option.rect = d->mapToViewport(option.rect);
796
if (!option.rect.intersects(viewport()->rect())) {
800
d->categoryDrawer->drawCategory(categoryIndex, d->proxyModel->sortRole(), option, &p);
803
//END: draw categories
805
if (intersecting.first.isValid() && intersecting.second.isValid()) {
807
int i = intersecting.first.row();
808
int indexToCheckIfBlockCollapsed = i;
809
QModelIndex categoryIndex;
811
Private::Block *block = 0;
812
while (i <= intersecting.second.row()) {
813
//BEGIN: first check if the block is collapsed. if so, we have to skip the item painting
814
if (i == indexToCheckIfBlockCollapsed) {
815
categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
816
category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
817
block = &d->blocks[category];
818
indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count();
819
if (block->collapsed) {
820
i = indexToCheckIfBlockCollapsed;
824
//END: first check if the block is collapsed. if so, we have to skip the item painting
828
const bool alternateItem = (i - block->firstIndex.row()) % 2;
830
const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
831
const Qt::ItemFlags flags = d->proxyModel->flags(index);
832
QStyleOptionViewItemV4 option(viewOptions());
833
option.rect = visualRect(index);
834
option.widget = this;
835
option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText
836
: QStyleOptionViewItemV2::None;
837
option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate
838
: QStyleOptionViewItemV4::None;
839
if (flags & Qt::ItemIsSelectable) {
840
option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected
841
: QStyle::State_None;
843
option.state &= ~QStyle::State_Selected;
845
option.state |= (index == currentIndex()) ? QStyle::State_HasFocus
846
: QStyle::State_None;
847
if (!(flags & Qt::ItemIsEnabled)) {
848
option.state &= ~QStyle::State_Enabled;
850
option.state |= (index == d->hoveredIndex) ? QStyle::State_MouseOver
851
: QStyle::State_None;
854
itemDelegate(index)->paint(&p, option, index);
860
//BEGIN: draw selection rect
861
if (isSelectionRectVisible() && d->rubberBandRect.isValid()) {
862
QStyleOptionRubberBand opt;
864
opt.shape = QRubberBand::Rectangle;
866
opt.rect = d->mapToViewport(d->rubberBandRect).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
868
style()->drawControl(QStyle::CE_RubberBand, &opt, &p);
871
//END: draw selection rect
876
void KCategorizedView::resizeEvent(QResizeEvent *event)
878
d->regenerateAllElements();
879
QListView::resizeEvent(event);
882
void KCategorizedView::setSelection(const QRect &rect,
883
QItemSelectionModel::SelectionFlags flags)
885
if (!d->isCategorized()) {
886
QListView::setSelection(rect, flags);
890
if (rect.topLeft() == rect.bottomRight()) {
891
const QModelIndex index = indexAt(rect.topLeft());
892
selectionModel()->select(index, flags);
896
const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(rect);
898
QItemSelection selection;
900
//TODO: think of a faster implementation
901
QModelIndex firstIndex;
902
QModelIndex lastIndex;
903
for (int i = intersecting.first.row(); i <= intersecting.second.row(); ++i) {
904
const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
905
const bool visualRectIntersects = visualRect(index).intersects(rect);
906
if (firstIndex.isValid()) {
907
if (visualRectIntersects) {
910
selection << QItemSelectionRange(firstIndex, lastIndex);
911
firstIndex = QModelIndex();
913
} else if (visualRectIntersects) {
919
if (firstIndex.isValid()) {
920
selection << QItemSelectionRange(firstIndex, lastIndex);
923
selectionModel()->select(selection, flags);
926
void KCategorizedView::mouseMoveEvent(QMouseEvent *event)
928
QListView::mouseMoveEvent(event);
929
d->hoveredIndex = indexAt(event->pos());
930
const SelectionMode itemViewSelectionMode = selectionMode();
931
if (state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection
932
&& itemViewSelectionMode != NoSelection) {
933
QRect rect(d->pressedPosition, event->pos() + QPoint(horizontalOffset(), verticalOffset()));
934
rect = rect.normalized();
935
update(rect.united(d->rubberBandRect));
936
d->rubberBandRect = rect;
938
if (!d->categoryDrawer) {
941
QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
942
while (it != d->blocks.constEnd()) {
943
const Private::Block &block = *it;
944
const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
945
QStyleOptionViewItemV4 option(viewOptions());
946
const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
947
QPoint pos = d->blockPosition(it.key());
949
option.rect.setTopLeft(pos);
950
option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
951
option.rect.setHeight(height + d->blockHeight(it.key()));
952
option.rect = d->mapToViewport(option.rect);
953
const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
954
if (option.rect.contains(mousePos)) {
955
if (d->hoveredBlock->height != -1 && *d->hoveredBlock != block) {
956
const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
957
const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
958
d->categoryDrawer->mouseLeft(categoryIndex, option.rect);
959
*d->hoveredBlock = block;
960
d->hoveredCategory = it.key();
961
viewport()->update(option.rect);
962
} else if (d->hoveredBlock->height == -1) {
963
*d->hoveredBlock = block;
964
d->hoveredCategory = it.key();
966
d->categoryDrawer->mouseMoved(categoryIndex, option.rect, event);
968
viewport()->update(option.rect);
973
if (d->hoveredBlock->height != -1) {
974
const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
975
const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
976
d->categoryDrawer->mouseLeft(categoryIndex, option.rect);
977
*d->hoveredBlock = Private::Block();
978
d->hoveredCategory = QString();
979
viewport()->update(option.rect);
983
void KCategorizedView::mousePressEvent(QMouseEvent *event)
985
if (event->button() == Qt::LeftButton) {
986
d->pressedPosition = event->pos();
987
d->pressedPosition.rx() += horizontalOffset();
988
d->pressedPosition.ry() += verticalOffset();
990
if (!d->categoryDrawer) {
991
QListView::mousePressEvent(event);
994
QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
995
while (it != d->blocks.constEnd()) {
996
const Private::Block &block = *it;
997
const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
998
const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
999
const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
1000
if (option.rect.contains(mousePos)) {
1001
d->categoryDrawer->mouseButtonPressed(categoryIndex, option.rect, event);
1002
viewport()->update(option.rect);
1003
if (!event->isAccepted()) {
1004
QListView::mousePressEvent(event);
1010
QListView::mousePressEvent(event);
1013
void KCategorizedView::mouseReleaseEvent(QMouseEvent *event)
1015
d->pressedPosition = QPoint();
1016
d->rubberBandRect = QRect();
1017
if (!d->categoryDrawer) {
1018
QListView::mouseReleaseEvent(event);
1021
QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
1022
while (it != d->blocks.constEnd()) {
1023
const Private::Block &block = *it;
1024
const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1025
const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1026
const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
1027
if (option.rect.contains(mousePos)) {
1028
d->categoryDrawer->mouseButtonReleased(categoryIndex, option.rect, event);
1029
viewport()->update(option.rect);
1030
if (!event->isAccepted()) {
1031
QListView::mouseReleaseEvent(event);
1037
QListView::mouseReleaseEvent(event);
1040
void KCategorizedView::leaveEvent(QEvent *event)
1042
QListView::leaveEvent(event);
1043
if (d->hoveredIndex.isValid()) {
1044
viewport()->update(visualRect(d->hoveredIndex));
1045
d->hoveredIndex = QModelIndex();
1047
if (d->categoryDrawer && d->hoveredBlock->height != -1) {
1048
const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1049
const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1050
d->categoryDrawer->mouseLeft(categoryIndex, option.rect);
1051
*d->hoveredBlock = Private::Block();
1052
d->hoveredCategory = QString();
1053
viewport()->update(option.rect);
1057
void KCategorizedView::startDrag(Qt::DropActions supportedActions)
1059
QListView::startDrag(supportedActions);
1062
void KCategorizedView::dragMoveEvent(QDragMoveEvent *event)
1064
QListView::dragMoveEvent(event);
1065
d->hoveredIndex = indexAt(event->pos());
1068
void KCategorizedView::dragEnterEvent(QDragEnterEvent *event)
1070
QListView::dragEnterEvent(event);
1073
void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event)
1075
QListView::dragLeaveEvent(event);
1078
void KCategorizedView::dropEvent(QDropEvent *event)
1080
QListView::dropEvent(event);
1083
//TODO: improve se we take into account collapsed blocks
1084
//TODO: take into account when there is no grid and no uniformItemSizes
1085
QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction,
1086
Qt::KeyboardModifiers modifiers)
1088
if (!d->isCategorized()) {
1089
return QListView::moveCursor(cursorAction, modifiers);
1092
const QModelIndex current = currentIndex();
1093
const QRect currentRect = visualRect(current);
1094
if (!current.isValid()) {
1095
const int rowCount = d->proxyModel->rowCount(rootIndex());
1097
return QModelIndex();
1099
return d->proxyModel->index(0, modelColumn(), rootIndex());
1102
switch (cursorAction) {
1104
if (!current.row()) {
1105
return QModelIndex();
1107
const QModelIndex previous = d->proxyModel->index(current.row() - 1, modelColumn(), rootIndex());
1108
const QRect previousRect = visualRect(previous);
1109
if (previousRect.top() == currentRect.top()) {
1113
return QModelIndex();
1116
if (current.row() == d->proxyModel->rowCount() - 1) {
1117
return QModelIndex();
1119
const QModelIndex next = d->proxyModel->index(current.row() + 1, modelColumn(), rootIndex());
1120
const QRect nextRect = visualRect(next);
1121
if (nextRect.top() == currentRect.top()) {
1125
return QModelIndex();
1128
if (d->hasGrid() || uniformItemSizes()) {
1129
const QModelIndex current = currentIndex();
1130
const QSize itemSize = d->hasGrid() ? gridSize()
1131
: sizeHintForIndex(current);
1132
const Private::Block &block = d->blocks[d->categoryForIndex(current)];
1133
const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
1134
const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() +
1135
block.items.count();
1138
return d->proxyModel->index(current.row() + maxItemsPerRow, modelColumn(), rootIndex());
1141
const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
1142
const QModelIndex nextIndex = d->proxyModel->index(block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex());
1144
if (!nextIndex.isValid()) {
1145
return QModelIndex();
1148
const Private::Block &nextBlock = d->blocks[d->categoryForIndex(nextIndex)];
1150
if (nextBlock.items.count() <= currentRelativePos) {
1151
return QModelIndex();
1154
if (currentRelativePos < (block.items.count() % maxItemsPerRow)) {
1155
return d->proxyModel->index(nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex());
1158
return QModelIndex();
1162
if (d->hasGrid() || uniformItemSizes()) {
1163
const QModelIndex current = currentIndex();
1164
const QSize itemSize = d->hasGrid() ? gridSize()
1165
: sizeHintForIndex(current);
1166
const Private::Block &block = d->blocks[d->categoryForIndex(current)];
1167
const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
1168
const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row();
1171
return d->proxyModel->index(current.row() - maxItemsPerRow, modelColumn(), rootIndex());
1174
const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
1175
const QModelIndex prevIndex = d->proxyModel->index(block.firstIndex.row() - 1, modelColumn(), rootIndex());
1177
if (!prevIndex.isValid()) {
1178
return QModelIndex();
1181
const Private::Block &prevBlock = d->blocks[d->categoryForIndex(prevIndex)];
1183
if (prevBlock.items.count() <= currentRelativePos) {
1184
return QModelIndex();
1187
const int remainder = prevBlock.items.count() % maxItemsPerRow;
1188
if (currentRelativePos < remainder) {
1189
return d->proxyModel->index(prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex());
1192
return QModelIndex();
1199
return QModelIndex();
1202
void KCategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent,
1206
if (!d->isCategorized()) {
1207
QListView::rowsAboutToBeRemoved(parent, start, end);
1211
*d->hoveredBlock = Private::Block();
1212
d->hoveredCategory = QString();
1214
if (end - start + 1 == d->proxyModel->rowCount()) {
1216
QListView::rowsAboutToBeRemoved(parent, start, end);
1220
// Removal feels a bit more complicated than insertion. Basically we can consider there are
1221
// 3 different cases when going to remove items. (*) represents an item, Items between ([) and
1222
// (]) are the ones which are marked for removal.
1225
// ... * * * * * * [ * * * ...
1227
// The items marked for removal are the last part of this category. No need to mark any item
1228
// of this category as in quarantine, because no special offset will be pushed to items at
1229
// the right because of any changes (since the removed items are those on the right most part
1230
// of the category).
1233
// ... * * * * * * ] * * * ...
1235
// The items marked for removal are the first part of this category. We have to mark as in
1236
// quarantine all items in this category. Absolutely all. All items will have to be moved to
1237
// the left (or moving up, because rows got a different offset).
1240
// ... * * [ * * * * ] * * ...
1242
// The items marked for removal are in between of this category. We have to mark as in
1243
// quarantine only those items that are at the right of the end of the removal interval,
1244
// (starting on "]").
1246
// It hasn't been explicitly said, but when we remove, we have to mark all blocks that are
1247
// located under the top most affected category as in quarantine (the block itself, as a whole),
1248
// because such a change can force it to have a different offset (note that items themselves
1249
// contain relative positions to the block, so marking the block as in quarantine is enough).
1251
// Also note that removal implicitly means that we have to update correctly firstIndex of each
1252
// block, and in general keep updated the internal information of elements.
1254
QStringList listOfCategoriesMarkedForRemoval;
1256
QString lastCategory;
1257
int alreadyRemoved = 0;
1258
for (int i = start; i <= end; ++i) {
1259
const QModelIndex index = d->proxyModel->index(i, modelColumn(), parent);
1261
Q_ASSERT(index.isValid());
1263
const QString category = d->categoryForIndex(index);
1265
if (lastCategory != category) {
1266
lastCategory = category;
1270
Private::Block &block = d->blocks[category];
1271
block.items.removeAt(i - block.firstIndex.row() - alreadyRemoved);
1274
if (!block.items.count()) {
1275
listOfCategoriesMarkedForRemoval << category;
1280
viewport()->update();
1283
//BEGIN: update the items that are in quarantine in affected categories
1285
const QModelIndex lastIndex = d->proxyModel->index(end, modelColumn(), parent);
1286
const QString category = d->categoryForIndex(lastIndex);
1287
Private::Block &block = d->blocks[category];
1288
if (block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row()) {
1289
block.firstIndex = d->proxyModel->index(end + 1, modelColumn(), parent);
1291
block.quarantineStart = block.firstIndex;
1293
//END: update the items that are in quarantine in affected categories
1295
Q_FOREACH (const QString &category, listOfCategoriesMarkedForRemoval) {
1296
d->blocks.remove(category);
1299
//BEGIN: mark as in quarantine those categories that are under the affected ones
1301
//BEGIN: order for marking as alternate those blocks that are alternate
1302
QList<Private::Block> blockList = d->blocks.values();
1303
qSort(blockList.begin(), blockList.end(), Private::Block::lessThan);
1304
QList<int> firstIndexesRows;
1305
foreach (const Private::Block &block, blockList) {
1306
firstIndexesRows << block.firstIndex.row();
1308
//END: order for marking as alternate those blocks that are alternate
1309
for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
1310
Private::Block &block = *it;
1311
if (block.firstIndex.row() > start) {
1312
block.outOfQuarantine = false;
1313
block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
1314
} else if (block.firstIndex.row() == start) {
1315
block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
1319
//END: mark as in quarantine those categories that are under the affected ones
1321
QListView::rowsAboutToBeRemoved(parent, start, end);
1324
void KCategorizedView::updateGeometries()
1326
const int oldVerticalOffset = verticalOffset();
1327
const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy();
1329
//BEGIN bugs 213068, 287847 ------------------------------------------------------------
1331
* QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not
1332
* and triggers a (sometimes additionally timered) resize through ::layoutChildren()
1333
* http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499
1334
* (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy)
1336
* As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI
1337
* So we prevent QListView from having an own opinion on the scrollbar visibility by
1338
* fixing it before calling the baseclass QListView::updateGeometries()
1340
* Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded
1341
* we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself.
1343
if (d->isCategorized()) { // important! - otherwise we'd pollute the setting if the view is initially not categorized
1344
setVerticalScrollBarPolicy((verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo(this)) ?
1345
Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
1346
setHorizontalScrollBarPolicy((horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo(this)) ?
1347
Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
1349
//END bugs 213068, 287847 --------------------------------------------------------------
1351
QListView::updateGeometries();
1353
if (!d->isCategorized()) {
1357
const int rowCount = d->proxyModel->rowCount();
1359
verticalScrollBar()->setRange(0, 0);
1360
// unconditional, see function end todo
1361
horizontalScrollBar()->setRange(0, 0);
1365
const QModelIndex lastIndex = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex());
1366
Q_ASSERT(lastIndex.isValid());
1367
QRect lastItemRect = visualRect(lastIndex);
1370
lastItemRect.setSize(lastItemRect.size().expandedTo(gridSize()));
1372
if (uniformItemSizes()) {
1373
QSize itemSize = sizeHintForIndex(lastIndex);
1374
itemSize.setHeight(itemSize.height() + spacing());
1375
lastItemRect.setSize(itemSize);
1377
QSize itemSize = sizeHintForIndex(lastIndex);
1378
const QString category = d->categoryForIndex(lastIndex);
1379
itemSize.setHeight(d->highestElementInLastRow(d->blocks[category]) + spacing());
1380
lastItemRect.setSize(itemSize);
1384
const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height();
1386
if (verticalScrollMode() == ScrollPerItem) {
1387
verticalScrollBar()->setSingleStep(lastItemRect.height());
1388
const int rowsPerPage = qMax(viewport()->height() / lastItemRect.height(), 1);
1389
verticalScrollBar()->setPageStep(rowsPerPage * lastItemRect.height());
1392
verticalScrollBar()->setRange(0, bottomRange);
1393
verticalScrollBar()->setValue(oldVerticalOffset);
1395
//TODO: also consider working with the horizontal scroll bar. since at this level I am not still
1396
// supporting "top to bottom" flow, there is no real problem. If I support that someday
1397
// (think how to draw categories), we would have to take care of the horizontal scroll bar too.
1398
// In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar.
1399
horizontalScrollBar()->setRange(0, 0);
1401
//BEGIN bugs 213068, 287847 ------------------------------------------------------------
1402
// restoring values from above ...
1403
setVerticalScrollBarPolicy(verticalP);
1404
setHorizontalScrollBarPolicy(horizontalP);
1405
// ... and correct the visibility
1406
bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum();
1407
if (verticalP == Qt::ScrollBarAsNeeded && (verticalScrollBar()->isVisibleTo(this) != validRange)) {
1408
verticalScrollBar()->setVisible(validRange);
1410
validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum();
1411
if (horizontalP == Qt::ScrollBarAsNeeded && (horizontalScrollBar()->isVisibleTo(this) != validRange)) {
1412
horizontalScrollBar()->setVisible(validRange);
1414
//END bugs 213068, 287847 --------------------------------------------------------------
1417
void KCategorizedView::currentChanged(const QModelIndex ¤t,
1418
const QModelIndex &previous)
1420
QListView::currentChanged(current, previous);
1423
void KCategorizedView::dataChanged(const QModelIndex &topLeft,
1424
const QModelIndex &bottomRight,
1425
const QVector<int> &roles)
1427
QListView::dataChanged(topLeft, bottomRight, roles);
1428
if (!d->isCategorized()) {
1432
*d->hoveredBlock = Private::Block();
1433
d->hoveredCategory = QString();
1435
//BEGIN: since the model changed data, we need to reconsider item sizes
1436
int i = topLeft.row();
1437
int indexToCheck = i;
1438
QModelIndex categoryIndex;
1440
Private::Block *block;
1441
while (i <= bottomRight.row()) {
1442
const QModelIndex currIndex = d->proxyModel->index(i, modelColumn(), rootIndex());
1443
if (i == indexToCheck) {
1444
categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
1445
category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
1446
block = &d->blocks[category];
1447
block->quarantineStart = currIndex;
1448
indexToCheck = block->firstIndex.row() + block->items.count();
1450
visualRect(currIndex);
1453
//END: since the model changed data, we need to reconsider item sizes
1456
void KCategorizedView::rowsInserted(const QModelIndex &parent,
1460
QListView::rowsInserted(parent, start, end);
1461
if (!d->isCategorized()) {
1465
*d->hoveredBlock = Private::Block();
1466
d->hoveredCategory = QString();
1467
d->rowsInserted(parent, start, end);
1470
#ifndef KDE_NO_DEPRECATED
1471
void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent,
1481
#ifndef KDE_NO_DEPRECATED
1482
void KCategorizedView::rowsRemoved(const QModelIndex &parent,
1492
void KCategorizedView::slotLayoutChanged()
1494
if (!d->isCategorized()) {
1499
*d->hoveredBlock = Private::Block();
1500
d->hoveredCategory = QString();
1501
if (d->proxyModel->rowCount()) {
1502
d->rowsInserted(rootIndex(), 0, d->proxyModel->rowCount() - 1);
1508
#include "moc_kcategorizedview.cpp"