• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

KDEUI

  • kdeui
  • itemviews
kcategorizedview.cpp
Go to the documentation of this file.
1
32#include "kcategorizedview.h"
33#include "kcategorizedview_p.h"
34
35#include <math.h> // trunc on C99 compliant systems
36#include <kdefakes.h> // trunc for not C99 compliant systems
37
38#include <QPainter>
39#include <QScrollBar>
40#include <QPaintEvent>
41
42#include "kcategorydrawer.h"
43#include "kcategorizedsortfilterproxymodel.h"
44
45//BEGIN: Private part
46
47struct KCategorizedView::Private::Item
48{
49 Item()
50 : topLeft(QPoint())
51 , size(QSize())
52 {
53 }
54
55 QPoint topLeft;
56 QSize size;
57};
58
59struct KCategorizedView::Private::Block
60{
61 Block()
62 : topLeft(QPoint())
63 , height(-1)
64 , firstIndex(QModelIndex())
65 , quarantineStart(QModelIndex())
66 , items(QList<Item>())
67 , outOfQuarantine(false)
68 , alternate(false)
69 , collapsed(false)
70 {
71 }
72
73 bool operator!=(const Block &rhs) const
74 {
75 return firstIndex != rhs.firstIndex;
76 }
77
78 static bool lessThan(const Block &left, const Block &right)
79 {
80 Q_ASSERT(left.firstIndex.isValid());
81 Q_ASSERT(right.firstIndex.isValid());
82 return left.firstIndex.row() < right.firstIndex.row();
83 }
84
85 QPoint topLeft;
86 int height;
87 QPersistentModelIndex firstIndex;
88 // if we have n elements on this block, and we inserted an element at position i. The quarantine
89 // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the
90 // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine
91 // will only affect the current block, since the rest of blocks can be affected only in the way
92 // that the whole block will have different offset, but items will keep the same relative position
93 // in terms of their parent blocks.
94 QPersistentModelIndex quarantineStart;
95 QList<Item> items;
96
97 // this affects the whole block, not items separately. items contain the topLeft point relative
98 // to the block. Because of insertions or removals a whole block can be moved, so the whole block
99 // will enter in quarantine, what is faster than moving all items in absolute terms.
100 bool outOfQuarantine;
101
102 // should we alternate its color ? is just a hint, could not be used
103 bool alternate;
104 bool collapsed;
105};
106
107KCategorizedView::Private::Private(KCategorizedView *q)
108 : q(q)
109 , proxyModel(0)
110 , categoryDrawer(0)
111 , categoryDrawerV2(0)
112 , categoryDrawerV3(0)
113 , categorySpacing(5)
114 , alternatingBlockColors(false)
115 , collapsibleBlocks(false)
116 , hoveredBlock(new Block())
117 , hoveredIndex(QModelIndex())
118 , pressedPosition(QPoint())
119 , rubberBandRect(QRect())
120{
121}
122
123KCategorizedView::Private::~Private()
124{
125 delete hoveredBlock;
126}
127
128bool KCategorizedView::Private::isCategorized() const
129{
130 return proxyModel && categoryDrawer && proxyModel->isCategorizedModel();
131}
132
133QStyleOptionViewItemV4 KCategorizedView::Private::blockRect(const QModelIndex &representative)
134{
135 QStyleOptionViewItemV4 option(q->viewOptions());
136 const int height = categoryDrawer->categoryHeight(representative, option);
137 const QString categoryDisplay = representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
138 QPoint pos = blockPosition(categoryDisplay);
139 pos.ry() -= height;
140 option.rect.setTopLeft(pos);
141 option.rect.setWidth(viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin());
142 option.rect.setHeight(height + blockHeight(categoryDisplay));
143 option.rect = mapToViewport(option.rect);
144
145 return option;
146}
147
148QPair<QModelIndex, QModelIndex> KCategorizedView::Private::intersectingIndexesWithRect(const QRect &_rect) const
149{
150 const int rowCount = proxyModel->rowCount();
151
152 const QRect rect = _rect.normalized();
153
154 // binary search to find out the top border
155 int bottom = 0;
156 int top = rowCount - 1;
157 while (bottom <= top) {
158 const int middle = (bottom + top) / 2;
159 const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
160 const QRect itemRect = q->visualRect(index);
161 if (itemRect.bottomRight().y() <= rect.topLeft().y()) {
162 bottom = middle + 1;
163 } else {
164 top = middle - 1;
165 }
166 }
167
168 const QModelIndex bottomIndex = proxyModel->index(bottom, q->modelColumn(), q->rootIndex());
169
170 // binary search to find out the bottom border
171 bottom = 0;
172 top = rowCount - 1;
173 while (bottom <= top) {
174 const int middle = (bottom + top) / 2;
175 const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex());
176 const QRect itemRect = q->visualRect(index);
177 if (itemRect.topLeft().y() <= rect.bottomRight().y()) {
178 bottom = middle + 1;
179 } else {
180 top = middle - 1;
181 }
182 }
183
184 const QModelIndex topIndex = proxyModel->index(top, q->modelColumn(), q->rootIndex());
185
186 return qMakePair(bottomIndex, topIndex);
187}
188
189QPoint KCategorizedView::Private::blockPosition(const QString &category)
190{
191 Block &block = blocks[category];
192
193 if (block.outOfQuarantine && !block.topLeft.isNull()) {
194 return block.topLeft;
195 }
196
197 QPoint res(categorySpacing, 0);
198
199 const QModelIndex index = block.firstIndex;
200
201 for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
202 Block &block = *it;
203 const QModelIndex categoryIndex = block.firstIndex;
204 if (index.row() < categoryIndex.row()) {
205 continue;
206 }
207 res.ry() += categoryDrawer->categoryHeight(categoryIndex, q->viewOptions()) + categorySpacing;
208 if (index.row() == categoryIndex.row()) {
209 continue;
210 }
211 res.ry() += blockHeight(it.key());
212 }
213
214 block.outOfQuarantine = true;
215 block.topLeft = res;
216
217 return res;
218}
219
220int KCategorizedView::Private::blockHeight(const QString &category)
221{
222 Block &block = blocks[category];
223
224 if (block.collapsed) {
225 return 0;
226 }
227
228 if (block.height > -1) {
229 return block.height;
230 }
231
232 const QModelIndex firstIndex = block.firstIndex;
233 const QModelIndex lastIndex = proxyModel->index(firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
234 const QRect topLeft = q->visualRect(firstIndex);
235 QRect bottomRight = q->visualRect(lastIndex);
236
237 if (hasGrid()) {
238 bottomRight.setHeight(qMax(bottomRight.height(), q->gridSize().height()));
239 } else {
240 if (!q->uniformItemSizes()) {
241 bottomRight.setHeight(highestElementInLastRow(block) + q->spacing() * 2);
242 }
243 }
244
245 const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1;
246 block.height = height;
247
248 return height;
249}
250
251int KCategorizedView::Private::viewportWidth() const
252{
253 return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin();
254}
255
256void KCategorizedView::Private::regenerateAllElements()
257{
258 for (QHash<QString, Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
259 Block &block = *it;
260 block.outOfQuarantine = false;
261 block.quarantineStart = block.firstIndex;
262 block.height = -1;
263 }
264}
265
266void KCategorizedView::Private::rowsInserted(const QModelIndex &parent, int start, int end)
267{
268 if (!isCategorized()) {
269 return;
270 }
271
272 for (int i = start; i <= end; ++i) {
273 const QModelIndex index = proxyModel->index(i, q->modelColumn(), parent);
274
275 Q_ASSERT(index.isValid());
276
277 const QString category = categoryForIndex(index);
278
279 Block &block = blocks[category];
280
281 //BEGIN: update firstIndex
282 // save as firstIndex in block if
283 // - it forced the category creation (first element on this category)
284 // - it is before the first row on that category
285 const QModelIndex firstIndex = block.firstIndex;
286 if (!firstIndex.isValid() || index.row() < firstIndex.row()) {
287 block.firstIndex = index;
288 }
289 //END: update firstIndex
290
291 Q_ASSERT(block.firstIndex.isValid());
292
293 const int firstIndexRow = block.firstIndex.row();
294
295 block.items.insert(index.row() - firstIndexRow, Private::Item());
296 block.height = -1;
297
298 q->visualRect(index);
299 q->viewport()->update();
300 }
301
302 //BEGIN: update the items that are in quarantine in affected categories
303 {
304 const QModelIndex lastIndex = proxyModel->index(end, q->modelColumn(), parent);
305 const QString category = categoryForIndex(lastIndex);
306 Private::Block &block = blocks[category];
307 block.quarantineStart = block.firstIndex;
308 }
309 //END: update the items that are in quarantine in affected categories
310
311 //BEGIN: mark as in quarantine those categories that are under the affected ones
312 {
313 const QModelIndex firstIndex = proxyModel->index(start, q->modelColumn(), parent);
314 const QString category = categoryForIndex(firstIndex);
315 const QModelIndex firstAffectedCategory = blocks[category].firstIndex;
316 //BEGIN: order for marking as alternate those blocks that are alternate
317 QList<Block> blockList = blocks.values();
318 qSort(blockList.begin(), blockList.end(), Block::lessThan);
319 QList<int> firstIndexesRows;
320 foreach (const Block &block, blockList) {
321 firstIndexesRows << block.firstIndex.row();
322 }
323 //END: order for marking as alternate those blocks that are alternate
324 for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) {
325 Private::Block &block = *it;
326 if (block.firstIndex.row() > firstAffectedCategory.row()) {
327 block.outOfQuarantine = false;
328 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
329 } else if (block.firstIndex.row() == firstAffectedCategory.row()) {
330 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
331 }
332 }
333 }
334 //END: mark as in quarantine those categories that are under the affected ones
335}
336
337QRect KCategorizedView::Private::mapToViewport(const QRect &rect) const
338{
339 const int dx = -q->horizontalOffset();
340 const int dy = -q->verticalOffset();
341 return rect.adjusted(dx, dy, dx, dy);
342}
343
344QRect KCategorizedView::Private::mapFromViewport(const QRect &rect) const
345{
346 const int dx = q->horizontalOffset();
347 const int dy = q->verticalOffset();
348 return rect.adjusted(dx, dy, dx, dy);
349}
350
351int KCategorizedView::Private::highestElementInLastRow(const Block &block) const
352{
353 //Find the highest element in the last row
354 const QModelIndex lastIndex = proxyModel->index(block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex());
355 const QRect prevRect = q->visualRect(lastIndex);
356 int res = prevRect.height();
357 QModelIndex prevIndex = proxyModel->index(lastIndex.row() - 1, q->modelColumn(), q->rootIndex());
358 if (!prevIndex.isValid()) {
359 return res;
360 }
361 Q_FOREVER {
362 const QRect tempRect = q->visualRect(prevIndex);
363 if (tempRect.topLeft().y() < prevRect.topLeft().y()) {
364 break;
365 }
366 res = qMax(res, tempRect.height());
367 if (prevIndex == block.firstIndex) {
368 break;
369 }
370 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
371 }
372
373 return res;
374}
375
376bool KCategorizedView::Private::hasGrid() const
377{
378 const QSize gridSize = q->gridSize();
379 return gridSize.isValid() && !gridSize.isNull();
380}
381
382QString KCategorizedView::Private::categoryForIndex(const QModelIndex &index) const
383{
384 const QModelIndex categoryIndex = index.model()->index(index.row(), proxyModel->sortColumn(), index.parent());
385 return categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
386}
387
388void KCategorizedView::Private::leftToRightVisualRect(const QModelIndex &index, Item &item,
389 const Block &block, const QPoint &blockPos) const
390{
391 const int firstIndexRow = block.firstIndex.row();
392
393 if (hasGrid()) {
394 const int relativeRow = index.row() - firstIndexRow;
395 const int maxItemsPerRow = qMax(viewportWidth() / q->gridSize().width(), 1);
396 if (q->layoutDirection() == Qt::LeftToRight) {
397 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin();
398 } else {
399 item.topLeft.rx() = viewportWidth() - ((relativeRow % maxItemsPerRow) + 1) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing;
400 }
401 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * q->gridSize().height();
402 } else {
403 if (q->uniformItemSizes()) {
404 const int relativeRow = index.row() - firstIndexRow;
405 const QSize itemSize = q->sizeHintForIndex(index);
406 const int maxItemsPerRow = qMax((viewportWidth() - q->spacing()) / (itemSize.width() + q->spacing()), 1);
407 if (q->layoutDirection() == Qt::LeftToRight) {
408 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin();
409 } else {
410 item.topLeft.rx() = viewportWidth() - (relativeRow % maxItemsPerRow) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing;
411 }
412 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * itemSize.height();
413 } else {
414 const QSize currSize = q->sizeHintForIndex(index);
415 if (index != block.firstIndex) {
416 const int viewportW = viewportWidth() - q->spacing();
417 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
418 QRect prevRect = q->visualRect(prevIndex);
419 prevRect = mapFromViewport(prevRect);
420 if ((prevRect.bottomRight().x() + 1) + currSize.width() - blockPos.x() + q->spacing() > viewportW) {
421 // we have to check the whole previous row, and see which one was the
422 // highest.
423 Q_FOREVER {
424 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex());
425 const QRect tempRect = q->visualRect(prevIndex);
426 if (tempRect.topLeft().y() < prevRect.topLeft().y()) {
427 break;
428 }
429 if (tempRect.bottomRight().y() > prevRect.bottomRight().y()) {
430 prevRect = tempRect;
431 }
432 if (prevIndex == block.firstIndex) {
433 break;
434 }
435 }
436 if (q->layoutDirection() == Qt::LeftToRight) {
437 item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing();
438 } else {
439 item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
440 }
441 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
442 } else {
443 if (q->layoutDirection() == Qt::LeftToRight) {
444 item.topLeft.rx() = (prevRect.bottomRight().x() + 1) + q->spacing();
445 } else {
446 item.topLeft.rx() = (prevRect.bottomLeft().x() - 1) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing;
447 }
448 item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y();
449 }
450 } else {
451 if (q->layoutDirection() == Qt::LeftToRight) {
452 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
453 } else {
454 item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing;
455 }
456 item.topLeft.ry() = q->spacing();
457 }
458 }
459 }
460 item.size = q->sizeHintForIndex(index);
461}
462
463void KCategorizedView::Private::topToBottomVisualRect(const QModelIndex &index, Item &item,
464 const Block &block, const QPoint &blockPos) const
465{
466 const int firstIndexRow = block.firstIndex.row();
467
468 if (hasGrid()) {
469 const int relativeRow = index.row() - firstIndexRow;
470 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
471 item.topLeft.ry() = relativeRow * q->gridSize().height();
472 } else {
473 if (q->uniformItemSizes()) {
474 const int relativeRow = index.row() - firstIndexRow;
475 const QSize itemSize = q->sizeHintForIndex(index);
476 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin();
477 item.topLeft.ry() = relativeRow * itemSize.height();
478 } else {
479 if (index != block.firstIndex) {
480 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex());
481 QRect prevRect = q->visualRect(prevIndex);
482 prevRect = mapFromViewport(prevRect);
483 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
484 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y();
485 } else {
486 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing();
487 item.topLeft.ry() = q->spacing();
488 }
489 }
490 }
491 item.size = q->sizeHintForIndex(index);
492 item.size.setWidth(viewportWidth());
493}
494
495void KCategorizedView::Private::_k_slotCollapseOrExpandClicked(QModelIndex)
496{
497}
498
499//END: Private part
500
501//BEGIN: Public part
502
503KCategorizedView::KCategorizedView(QWidget *parent)
504 : QListView(parent)
505 , d(new Private(this))
506{
507}
508
509KCategorizedView::~KCategorizedView()
510{
511 delete d;
512}
513
514void KCategorizedView::setModel(QAbstractItemModel *model)
515{
516 if (d->proxyModel == model) {
517 return;
518 }
519
520 d->blocks.clear();
521
522 if (d->proxyModel) {
523 disconnect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()));
524 }
525
526 d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*>(model);
527
528 if (d->proxyModel) {
529 connect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged()));
530 }
531
532 QListView::setModel(model);
533
534 // if the model already had information inserted, update our data structures to it
535 if (model->rowCount()) {
536 slotLayoutChanged();
537 }
538}
539
540void KCategorizedView::setGridSize(const QSize &size)
541{
542 setGridSizeOwn(size);
543}
544
545void KCategorizedView::setGridSizeOwn(const QSize &size)
546{
547 d->regenerateAllElements();
548 QListView::setGridSize(size);
549}
550
551QRect KCategorizedView::visualRect(const QModelIndex &index) const
552{
553 if (!d->isCategorized()) {
554 return QListView::visualRect(index);
555 }
556
557 if (!index.isValid()) {
558 return QRect();
559 }
560
561 const QString category = d->categoryForIndex(index);
562
563 if (!d->blocks.contains(category)) {
564 return QRect();
565 }
566
567 Private::Block &block = d->blocks[category];
568 const int firstIndexRow = block.firstIndex.row();
569
570 Q_ASSERT(block.firstIndex.isValid());
571
572 if (index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count()) {
573 return QRect();
574 }
575
576 const QPoint blockPos = d->blockPosition(category);
577
578 Private::Item &ritem = block.items[index.row() - firstIndexRow];
579
580 if (ritem.topLeft.isNull() || (block.quarantineStart.isValid() &&
581 index.row() >= block.quarantineStart.row())) {
582 if (flow() == LeftToRight) {
583 d->leftToRightVisualRect(index, ritem, block, blockPos);
584 } else {
585 d->topToBottomVisualRect(index, ritem, block, blockPos);
586 }
587
588 //BEGIN: update the quarantine start
589 const bool wasLastIndex = (index.row() == (block.firstIndex.row() + block.items.count() - 1));
590 if (index.row() == block.quarantineStart.row()) {
591 if (wasLastIndex) {
592 block.quarantineStart = QModelIndex();
593 } else {
594 const QModelIndex nextIndex = d->proxyModel->index(index.row() + 1, modelColumn(), rootIndex());
595 block.quarantineStart = nextIndex;
596 }
597 }
598 //END: update the quarantine start
599 }
600
601 // we get now the absolute position through the relative position of the parent block. do not
602 // save this on ritem, since this would override the item relative position in block terms.
603 Private::Item item(ritem);
604 item.topLeft.ry() += blockPos.y();
605
606 const QSize sizeHint = item.size;
607
608 if (d->hasGrid()) {
609 const QSize sizeGrid = gridSize();
610 const QSize resultingSize = sizeHint.boundedTo(sizeGrid);
611 QRect res(item.topLeft.x() + ((sizeGrid.width() - resultingSize.width()) / 2),
612 item.topLeft.y(), resultingSize.width(), resultingSize.height());
613 if (block.collapsed) {
614 // we can still do binary search, while we "hide" items. We move those items in collapsed
615 // blocks to the left and set a 0 height.
616 res.setLeft(-resultingSize.width());
617 res.setHeight(0);
618 }
619 return d->mapToViewport(res);
620 }
621
622 QRect res(item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height());
623 if (block.collapsed) {
624 // we can still do binary search, while we "hide" items. We move those items in collapsed
625 // blocks to the left and set a 0 height.
626 res.setLeft(-sizeHint.width());
627 res.setHeight(0);
628 }
629 return d->mapToViewport(res);
630}
631
632KCategoryDrawer *KCategorizedView::categoryDrawer() const
633{
634 return d->categoryDrawer;
635}
636
637void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer)
638{
639 if (d->categoryDrawerV2) {
640 disconnect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)),
641 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
642 }
643
644 d->categoryDrawer = categoryDrawer;
645 d->categoryDrawerV2 = dynamic_cast<KCategoryDrawerV2*>(categoryDrawer);
646 d->categoryDrawerV3 = dynamic_cast<KCategoryDrawerV3*>(categoryDrawer);
647
648 if (d->categoryDrawerV2) {
649 connect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)),
650 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex)));
651 }
652}
653
654int KCategorizedView::categorySpacing() const
655{
656 return d->categorySpacing;
657}
658
659void KCategorizedView::setCategorySpacing(int categorySpacing)
660{
661 if (d->categorySpacing == categorySpacing) {
662 return;
663 }
664
665 d->categorySpacing = categorySpacing;
666
667 for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
668 Private::Block &block = *it;
669 block.outOfQuarantine = false;
670 }
671}
672
673bool KCategorizedView::alternatingBlockColors() const
674{
675 return d->alternatingBlockColors;
676}
677
678void KCategorizedView::setAlternatingBlockColors(bool enable)
679{
680 d->alternatingBlockColors = enable;
681}
682
683bool KCategorizedView::collapsibleBlocks() const
684{
685 return d->collapsibleBlocks;
686}
687
688void KCategorizedView::setCollapsibleBlocks(bool enable)
689{
690 d->collapsibleBlocks = enable;
691}
692
693QModelIndexList KCategorizedView::block(const QString &category)
694{
695 QModelIndexList res;
696 const Private::Block &block = d->blocks[category];
697 if (block.height == -1) {
698 return res;
699 }
700 QModelIndex current = block.firstIndex;
701 const int first = current.row();
702 for (int i = 1; i <= block.items.count(); ++i) {
703 if (current.isValid()) {
704 res << current;
705 }
706 current = d->proxyModel->index(first + i, modelColumn(), rootIndex());
707 }
708 return res;
709}
710
711QModelIndexList KCategorizedView::block(const QModelIndex &representative)
712{
713 return block(representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString());
714}
715
716QModelIndex KCategorizedView::indexAt(const QPoint &point) const
717{
718 if (!d->isCategorized()) {
719 return QListView::indexAt(point);
720 }
721
722 const int rowCount = d->proxyModel->rowCount();
723 if (!rowCount) {
724 return QModelIndex();
725 }
726
727 // Binary search that will try to spot if there is an index under point
728 int bottom = 0;
729 int top = rowCount - 1;
730 while (bottom <= top) {
731 const int middle = (bottom + top) / 2;
732 const QModelIndex index = d->proxyModel->index(middle, modelColumn(), rootIndex());
733 const QRect rect = visualRect(index);
734 if (rect.contains(point)) {
735 if (index.model()->flags(index) & Qt::ItemIsEnabled) {
736 return index;
737 }
738 return QModelIndex();
739 }
740 bool directionCondition;
741 if (layoutDirection() == Qt::LeftToRight) {
742 directionCondition = point.x() >= rect.bottomLeft().x();
743 } else {
744 directionCondition = point.x() <= rect.bottomRight().x();
745 }
746 if (point.y() < rect.topLeft().y()) {
747 top = middle - 1;
748 } else if (directionCondition) {
749 bottom = middle + 1;
750 } else if (point.y() <= rect.bottomRight().y()) {
751 top = middle - 1;
752 } else {
753 bool after = true;
754 for (int i = middle - 1;i >= bottom;i--) {
755 const QModelIndex newIndex =
756 d->proxyModel->index(i, modelColumn(), rootIndex());
757 const QRect newRect = visualRect(newIndex);
758 if (newRect.topLeft().y() < rect.topLeft().y()) {
759 break;
760 } else if (newRect.contains(point)) {
761 if (newIndex.model()->flags(newIndex) & Qt::ItemIsEnabled) {
762 return newIndex;
763 }
764 return QModelIndex();
765 } else if ((layoutDirection() == Qt::LeftToRight) ?
766 (newRect.topLeft().x() <= point.x()) :
767 (newRect.topRight().x() >= point.x())) {
768 break;
769 } else if (newRect.bottomRight().y() >= point.y()) {
770 after = false;
771 }
772 }
773 if (!after) {
774 return QModelIndex();
775 }
776 bottom = middle + 1;
777 }
778 }
779 return QModelIndex();
780}
781
782void KCategorizedView::reset()
783{
784 d->blocks.clear();
785 QListView::reset();
786}
787
788void KCategorizedView::paintEvent(QPaintEvent *event)
789{
790 if (!d->isCategorized()) {
791 QListView::paintEvent(event);
792 return;
793 }
794
795 const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(viewport()->rect().intersected(event->rect()));
796
797 QPainter p(viewport());
798 p.save();
799
800 Q_ASSERT(selectionModel()->model() == d->proxyModel);
801
802 //BEGIN: draw categories
803 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
804 while (it != d->blocks.constEnd()) {
805 const Private::Block &block = *it;
806 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
807 QStyleOptionViewItemV4 option(viewOptions());
808 option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate
809 : QStyleOptionViewItemV4::None;
810 option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open
811 : QStyle::State_None;
812 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
813 QPoint pos = d->blockPosition(it.key());
814 pos.ry() -= height;
815 option.rect.setTopLeft(pos);
816 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
817 option.rect.setHeight(height + d->blockHeight(it.key()));
818 option.rect = d->mapToViewport(option.rect);
819 if (!option.rect.intersects(viewport()->rect())) {
820 ++it;
821 continue;
822 }
823 d->categoryDrawer->drawCategory(categoryIndex, d->proxyModel->sortRole(), option, &p);
824 ++it;
825 }
826 //END: draw categories
827
828 if (intersecting.first.isValid() && intersecting.second.isValid()) {
829 //BEGIN: draw items
830 int i = intersecting.first.row();
831 int indexToCheckIfBlockCollapsed = i;
832 QModelIndex categoryIndex;
833 QString category;
834 Private::Block *block = 0;
835 while (i <= intersecting.second.row()) {
836 //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting
837 if (i == indexToCheckIfBlockCollapsed) {
838 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
839 category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
840 block = &d->blocks[category];
841 indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count();
842 if (block->collapsed) {
843 i = indexToCheckIfBlockCollapsed;
844 continue;
845 }
846 }
847 //END: first check if the block is collapsed. if so, we have to skip the item painting
848
849 Q_ASSERT(block);
850
851 const bool alternateItem = (i - block->firstIndex.row()) % 2;
852
853 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
854 const Qt::ItemFlags flags = d->proxyModel->flags(index);
855 QStyleOptionViewItemV4 option(viewOptions());
856 option.rect = visualRect(index);
857 option.widget = this;
858 option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText
859 : QStyleOptionViewItemV2::None;
860 option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate
861 : QStyleOptionViewItemV4::None;
862 if (flags & Qt::ItemIsSelectable) {
863 option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected
864 : QStyle::State_None;
865 } else {
866 option.state &= ~QStyle::State_Selected;
867 }
868 option.state |= (index == currentIndex()) ? QStyle::State_HasFocus
869 : QStyle::State_None;
870 if (!(flags & Qt::ItemIsEnabled)) {
871 option.state &= ~QStyle::State_Enabled;
872 } else {
873 option.state |= (index == d->hoveredIndex) ? QStyle::State_MouseOver
874 : QStyle::State_None;
875 }
876
877 itemDelegate(index)->paint(&p, option, index);
878 ++i;
879 }
880 //END: draw items
881 }
882
883 //BEGIN: draw selection rect
884 if (isSelectionRectVisible() && d->rubberBandRect.isValid()) {
885 QStyleOptionRubberBand opt;
886 opt.initFrom(this);
887 opt.shape = QRubberBand::Rectangle;
888 opt.opaque = false;
889 opt.rect = d->mapToViewport(d->rubberBandRect).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
890 p.save();
891 style()->drawControl(QStyle::CE_RubberBand, &opt, &p);
892 p.restore();
893 }
894 //END: draw selection rect
895
896 p.restore();
897}
898
899void KCategorizedView::resizeEvent(QResizeEvent *event)
900{
901 d->regenerateAllElements();
902 QListView::resizeEvent(event);
903}
904
905void KCategorizedView::setSelection(const QRect &rect,
906 QItemSelectionModel::SelectionFlags flags)
907{
908 if (!d->isCategorized()) {
909 QListView::setSelection(rect, flags);
910 return;
911 }
912
913 if (rect.topLeft() == rect.bottomRight()) {
914 const QModelIndex index = indexAt(rect.topLeft());
915 selectionModel()->select(index, flags);
916 return;
917 }
918
919 const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(rect);
920
921 QItemSelection selection;
922
923 //TODO: think of a faster implementation
924 QModelIndex firstIndex;
925 QModelIndex lastIndex;
926 for (int i = intersecting.first.row(); i <= intersecting.second.row(); ++i) {
927 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex());
928 const bool visualRectIntersects = visualRect(index).intersects(rect);
929 if (firstIndex.isValid()) {
930 if (visualRectIntersects) {
931 lastIndex = index;
932 } else {
933 selection << QItemSelectionRange(firstIndex, lastIndex);
934 firstIndex = QModelIndex();
935 }
936 } else if (visualRectIntersects) {
937 firstIndex = index;
938 lastIndex = index;
939 }
940 }
941
942 if (firstIndex.isValid()) {
943 selection << QItemSelectionRange(firstIndex, lastIndex);
944 }
945
946 selectionModel()->select(selection, flags);
947}
948
949void KCategorizedView::mouseMoveEvent(QMouseEvent *event)
950{
951 QListView::mouseMoveEvent(event);
952 d->hoveredIndex = indexAt(event->pos());
953 const SelectionMode itemViewSelectionMode = selectionMode();
954 if (state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection
955 && itemViewSelectionMode != NoSelection) {
956 QRect rect(d->pressedPosition, event->pos() + QPoint(horizontalOffset(), verticalOffset()));
957 rect = rect.normalized();
958 update(rect.united(d->rubberBandRect));
959 d->rubberBandRect = rect;
960 }
961 if (!d->categoryDrawerV2) {
962 return;
963 }
964 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
965 while (it != d->blocks.constEnd()) {
966 const Private::Block &block = *it;
967 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
968 QStyleOptionViewItemV4 option(viewOptions());
969 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option);
970 QPoint pos = d->blockPosition(it.key());
971 pos.ry() -= height;
972 option.rect.setTopLeft(pos);
973 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin());
974 option.rect.setHeight(height + d->blockHeight(it.key()));
975 option.rect = d->mapToViewport(option.rect);
976 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
977 if (option.rect.contains(mousePos)) {
978 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1 && *d->hoveredBlock != block) {
979 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
980 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
981 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect);
982 *d->hoveredBlock = block;
983 d->hoveredCategory = it.key();
984 viewport()->update(option.rect);
985 } else if (d->hoveredBlock->height == -1) {
986 *d->hoveredBlock = block;
987 d->hoveredCategory = it.key();
988 } else if (d->categoryDrawerV3) {
989 d->categoryDrawerV3->mouseMoved(categoryIndex, option.rect, event);
990 } else {
991 d->categoryDrawerV2->mouseButtonMoved(categoryIndex, event);
992 }
993 viewport()->update(option.rect);
994 return;
995 }
996 ++it;
997 }
998 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) {
999 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1000 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1001 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect);
1002 *d->hoveredBlock = Private::Block();
1003 d->hoveredCategory = QString();
1004 viewport()->update(option.rect);
1005 }
1006}
1007
1008void KCategorizedView::mousePressEvent(QMouseEvent *event)
1009{
1010 if (event->button() == Qt::LeftButton) {
1011 d->pressedPosition = event->pos();
1012 d->pressedPosition.rx() += horizontalOffset();
1013 d->pressedPosition.ry() += verticalOffset();
1014 }
1015 if (!d->categoryDrawerV2) {
1016 QListView::mousePressEvent(event);
1017 return;
1018 }
1019 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
1020 while (it != d->blocks.constEnd()) {
1021 const Private::Block &block = *it;
1022 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1023 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1024 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
1025 if (option.rect.contains(mousePos)) {
1026 if (d->categoryDrawerV3) {
1027 d->categoryDrawerV3->mouseButtonPressed(categoryIndex, option.rect, event);
1028 } else {
1029 d->categoryDrawerV2->mouseButtonPressed(categoryIndex, event);
1030 }
1031 viewport()->update(option.rect);
1032 if (!event->isAccepted()) {
1033 QListView::mousePressEvent(event);
1034 }
1035 return;
1036 }
1037 ++it;
1038 }
1039 QListView::mousePressEvent(event);
1040}
1041
1042void KCategorizedView::mouseReleaseEvent(QMouseEvent *event)
1043{
1044 d->pressedPosition = QPoint();
1045 d->rubberBandRect = QRect();
1046 if (!d->categoryDrawerV2) {
1047 QListView::mouseReleaseEvent(event);
1048 return;
1049 }
1050 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin());
1051 while (it != d->blocks.constEnd()) {
1052 const Private::Block &block = *it;
1053 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1054 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1055 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos());
1056 if (option.rect.contains(mousePos)) {
1057 if (d->categoryDrawerV3) {
1058 d->categoryDrawerV3->mouseButtonReleased(categoryIndex, option.rect, event);
1059 } else {
1060 d->categoryDrawerV2->mouseButtonReleased(categoryIndex, event);
1061 }
1062 viewport()->update(option.rect);
1063 if (!event->isAccepted()) {
1064 QListView::mouseReleaseEvent(event);
1065 }
1066 return;
1067 }
1068 ++it;
1069 }
1070 QListView::mouseReleaseEvent(event);
1071}
1072
1073void KCategorizedView::leaveEvent(QEvent *event)
1074{
1075 QListView::leaveEvent(event);
1076 if (d->hoveredIndex.isValid()) {
1077 viewport()->update(visualRect(d->hoveredIndex));
1078 d->hoveredIndex = QModelIndex();
1079 }
1080 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) {
1081 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex());
1082 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex);
1083 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect);
1084 *d->hoveredBlock = Private::Block();
1085 d->hoveredCategory = QString();
1086 viewport()->update(option.rect);
1087 }
1088}
1089
1090void KCategorizedView::startDrag(Qt::DropActions supportedActions)
1091{
1092 QListView::startDrag(supportedActions);
1093}
1094
1095void KCategorizedView::dragMoveEvent(QDragMoveEvent *event)
1096{
1097 QListView::dragMoveEvent(event);
1098 d->hoveredIndex = indexAt(event->pos());
1099}
1100
1101void KCategorizedView::dragEnterEvent(QDragEnterEvent *event)
1102{
1103 QListView::dragEnterEvent(event);
1104}
1105
1106void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event)
1107{
1108 QListView::dragLeaveEvent(event);
1109}
1110
1111void KCategorizedView::dropEvent(QDropEvent *event)
1112{
1113 QListView::dropEvent(event);
1114}
1115
1116//TODO: improve se we take into account collapsed blocks
1117//TODO: take into account when there is no grid and no uniformItemSizes
1118QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction,
1119 Qt::KeyboardModifiers modifiers)
1120{
1121 if (!d->isCategorized()) {
1122 return QListView::moveCursor(cursorAction, modifiers);
1123 }
1124
1125 const QModelIndex current = currentIndex();
1126 const QRect currentRect = visualRect(current);
1127 if (!current.isValid()) {
1128 const int rowCount = d->proxyModel->rowCount(rootIndex());
1129 if (!rowCount) {
1130 return QModelIndex();
1131 }
1132 return d->proxyModel->index(0, modelColumn(), rootIndex());
1133 }
1134
1135 switch (cursorAction) {
1136 case MoveLeft: {
1137 if (!current.row()) {
1138 return QModelIndex();
1139 }
1140 const QModelIndex previous = d->proxyModel->index(current.row() - 1, modelColumn(), rootIndex());
1141 const QRect previousRect = visualRect(previous);
1142 if (previousRect.top() == currentRect.top()) {
1143 return previous;
1144 }
1145
1146 return QModelIndex();
1147 }
1148 case MoveRight: {
1149 if (current.row() == d->proxyModel->rowCount() - 1) {
1150 return QModelIndex();
1151 }
1152 const QModelIndex next = d->proxyModel->index(current.row() + 1, modelColumn(), rootIndex());
1153 const QRect nextRect = visualRect(next);
1154 if (nextRect.top() == currentRect.top()) {
1155 return next;
1156 }
1157
1158 return QModelIndex();
1159 }
1160 case MoveDown: {
1161 if (d->hasGrid() || uniformItemSizes()) {
1162 const QModelIndex current = currentIndex();
1163 const QSize itemSize = d->hasGrid() ? gridSize()
1164 : sizeHintForIndex(current);
1165 const Private::Block &block = d->blocks[d->categoryForIndex(current)];
1166 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
1167 const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() +
1168 block.items.count();
1169
1170 if (canMove) {
1171 return d->proxyModel->index(current.row() + maxItemsPerRow, modelColumn(), rootIndex());
1172 }
1173
1174 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
1175 const QModelIndex nextIndex = d->proxyModel->index(block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex());
1176
1177 if (!nextIndex.isValid()) {
1178 return QModelIndex();
1179 }
1180
1181 const Private::Block &nextBlock = d->blocks[d->categoryForIndex(nextIndex)];
1182
1183 if (nextBlock.items.count() <= currentRelativePos) {
1184 return QModelIndex();
1185 }
1186
1187 if (currentRelativePos < (block.items.count() % maxItemsPerRow)) {
1188 return d->proxyModel->index(nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex());
1189 }
1190
1191 return QModelIndex();
1192 }
1193 }
1194 case MoveUp: {
1195 if (d->hasGrid() || uniformItemSizes()) {
1196 const QModelIndex current = currentIndex();
1197 const QSize itemSize = d->hasGrid() ? gridSize()
1198 : sizeHintForIndex(current);
1199 const Private::Block &block = d->blocks[d->categoryForIndex(current)];
1200 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1);
1201 const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row();
1202
1203 if (canMove) {
1204 return d->proxyModel->index(current.row() - maxItemsPerRow, modelColumn(), rootIndex());
1205 }
1206
1207 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow;
1208 const QModelIndex prevIndex = d->proxyModel->index(block.firstIndex.row() - 1, modelColumn(), rootIndex());
1209
1210 if (!prevIndex.isValid()) {
1211 return QModelIndex();
1212 }
1213
1214 const Private::Block &prevBlock = d->blocks[d->categoryForIndex(prevIndex)];
1215
1216 if (prevBlock.items.count() <= currentRelativePos) {
1217 return QModelIndex();
1218 }
1219
1220 const int remainder = prevBlock.items.count() % maxItemsPerRow;
1221 if (currentRelativePos < remainder) {
1222 return d->proxyModel->index(prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex());
1223 }
1224
1225 return QModelIndex();
1226 }
1227 }
1228 default:
1229 break;
1230 }
1231
1232 return QModelIndex();
1233}
1234
1235void KCategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent,
1236 int start,
1237 int end)
1238{
1239 if (!d->isCategorized()) {
1240 QListView::rowsAboutToBeRemoved(parent, start, end);
1241 return;
1242 }
1243
1244 *d->hoveredBlock = Private::Block();
1245 d->hoveredCategory = QString();
1246
1247 if (end - start + 1 == d->proxyModel->rowCount()) {
1248 d->blocks.clear();
1249 QListView::rowsAboutToBeRemoved(parent, start, end);
1250 return;
1251 }
1252
1253 // Removal feels a bit more complicated than insertion. Basically we can consider there are
1254 // 3 different cases when going to remove items. (*) represents an item, Items between ([) and
1255 // (]) are the ones which are marked for removal.
1256 //
1257 // - 1st case:
1258 // ... * * * * * * [ * * * ...
1259 //
1260 // The items marked for removal are the last part of this category. No need to mark any item
1261 // of this category as in quarantine, because no special offset will be pushed to items at
1262 // the right because of any changes (since the removed items are those on the right most part
1263 // of the category).
1264 //
1265 // - 2nd case:
1266 // ... * * * * * * ] * * * ...
1267 //
1268 // The items marked for removal are the first part of this category. We have to mark as in
1269 // quarantine all items in this category. Absolutely all. All items will have to be moved to
1270 // the left (or moving up, because rows got a different offset).
1271 //
1272 // - 3rd case:
1273 // ... * * [ * * * * ] * * ...
1274 //
1275 // The items marked for removal are in between of this category. We have to mark as in
1276 // quarantine only those items that are at the right of the end of the removal interval,
1277 // (starting on "]").
1278 //
1279 // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are
1280 // located under the top most affected category as in quarantine (the block itself, as a whole),
1281 // because such a change can force it to have a different offset (note that items themselves
1282 // contain relative positions to the block, so marking the block as in quarantine is enough).
1283 //
1284 // Also note that removal implicitly means that we have to update correctly firstIndex of each
1285 // block, and in general keep updated the internal information of elements.
1286
1287 QStringList listOfCategoriesMarkedForRemoval;
1288
1289 QString lastCategory;
1290 int alreadyRemoved = 0;
1291 for (int i = start; i <= end; ++i) {
1292 const QModelIndex index = d->proxyModel->index(i, modelColumn(), parent);
1293
1294 Q_ASSERT(index.isValid());
1295
1296 const QString category = d->categoryForIndex(index);
1297
1298 if (lastCategory != category) {
1299 lastCategory = category;
1300 alreadyRemoved = 0;
1301 }
1302
1303 Private::Block &block = d->blocks[category];
1304 block.items.removeAt(i - block.firstIndex.row() - alreadyRemoved);
1305 ++alreadyRemoved;
1306
1307 if (!block.items.count()) {
1308 listOfCategoriesMarkedForRemoval << category;
1309 }
1310
1311 block.height = -1;
1312
1313 viewport()->update();
1314 }
1315
1316 //BEGIN: update the items that are in quarantine in affected categories
1317 {
1318 const QModelIndex lastIndex = d->proxyModel->index(end, modelColumn(), parent);
1319 const QString category = d->categoryForIndex(lastIndex);
1320 Private::Block &block = d->blocks[category];
1321 if (block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row()) {
1322 block.firstIndex = d->proxyModel->index(end + 1, modelColumn(), parent);
1323 }
1324 block.quarantineStart = block.firstIndex;
1325 }
1326 //END: update the items that are in quarantine in affected categories
1327
1328 Q_FOREACH (const QString &category, listOfCategoriesMarkedForRemoval) {
1329 d->blocks.remove(category);
1330 }
1331
1332 //BEGIN: mark as in quarantine those categories that are under the affected ones
1333 {
1334 //BEGIN: order for marking as alternate those blocks that are alternate
1335 QList<Private::Block> blockList = d->blocks.values();
1336 qSort(blockList.begin(), blockList.end(), Private::Block::lessThan);
1337 QList<int> firstIndexesRows;
1338 foreach (const Private::Block &block, blockList) {
1339 firstIndexesRows << block.firstIndex.row();
1340 }
1341 //END: order for marking as alternate those blocks that are alternate
1342 for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) {
1343 Private::Block &block = *it;
1344 if (block.firstIndex.row() > start) {
1345 block.outOfQuarantine = false;
1346 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
1347 } else if (block.firstIndex.row() == start) {
1348 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2;
1349 }
1350 }
1351 }
1352 //END: mark as in quarantine those categories that are under the affected ones
1353
1354 QListView::rowsAboutToBeRemoved(parent, start, end);
1355}
1356
1357void KCategorizedView::updateGeometries()
1358{
1359 const int oldVerticalOffset = verticalOffset();
1360 const Qt::ScrollBarPolicy verticalP = verticalScrollBarPolicy(), horizontalP = horizontalScrollBarPolicy();
1361
1362 //BEGIN bugs 213068, 287847 ------------------------------------------------------------
1363 /*
1364 * QListView::updateGeometries() has it's own opinion on whether the scrollbars should be visible (valid range) or not
1365 * and triggers a (sometimes additionally timered) resize through ::layoutChildren()
1366 * http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/itemviews/qlistview.cpp#line1499
1367 * (the comment above the main block isn't all accurate, layoutChldren is called regardless of the policy)
1368 *
1369 * As a result QListView and KCategorizedView occasionally started a race on the scrollbar visibility, effectively blocking the UI
1370 * So we prevent QListView from having an own opinion on the scrollbar visibility by
1371 * fixing it before calling the baseclass QListView::updateGeometries()
1372 *
1373 * Since the implicit show/hide by the followin range setting will cause further resizes if the policy is Qt::ScrollBarAsNeeded
1374 * we keep it static until we're done, then restore the original value and ultimately change the scrollbar visibility ourself.
1375 */
1376 if (d->isCategorized()) { // important! - otherwise we'd pollute the setting if the view is initially not categorized
1377 setVerticalScrollBarPolicy((verticalP == Qt::ScrollBarAlwaysOn || verticalScrollBar()->isVisibleTo(this)) ?
1378 Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
1379 setHorizontalScrollBarPolicy((horizontalP == Qt::ScrollBarAlwaysOn || horizontalScrollBar()->isVisibleTo(this)) ?
1380 Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
1381 }
1382 //END bugs 213068, 287847 --------------------------------------------------------------
1383
1384 QListView::updateGeometries();
1385
1386 if (!d->isCategorized()) {
1387 return;
1388 }
1389
1390 const int rowCount = d->proxyModel->rowCount();
1391 if (!rowCount) {
1392 verticalScrollBar()->setRange(0, 0);
1393 // unconditional, see function end todo
1394 horizontalScrollBar()->setRange(0, 0);
1395 return;
1396 }
1397
1398 const QModelIndex lastIndex = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex());
1399 Q_ASSERT(lastIndex.isValid());
1400 QRect lastItemRect = visualRect(lastIndex);
1401
1402 if (d->hasGrid()) {
1403 lastItemRect.setSize(lastItemRect.size().expandedTo(gridSize()));
1404 } else {
1405 if (uniformItemSizes()) {
1406 QSize itemSize = sizeHintForIndex(lastIndex);
1407 itemSize.setHeight(itemSize.height() + spacing());
1408 lastItemRect.setSize(itemSize);
1409 } else {
1410 QSize itemSize = sizeHintForIndex(lastIndex);
1411 const QString category = d->categoryForIndex(lastIndex);
1412 itemSize.setHeight(d->highestElementInLastRow(d->blocks[category]) + spacing());
1413 lastItemRect.setSize(itemSize);
1414 }
1415 }
1416
1417 const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height();
1418
1419 if (verticalScrollMode() == ScrollPerItem) {
1420 verticalScrollBar()->setSingleStep(lastItemRect.height());
1421 const int rowsPerPage = qMax(viewport()->height() / lastItemRect.height(), 1);
1422 verticalScrollBar()->setPageStep(rowsPerPage * lastItemRect.height());
1423 }
1424
1425 verticalScrollBar()->setRange(0, bottomRange);
1426 verticalScrollBar()->setValue(oldVerticalOffset);
1427
1428 //TODO: also consider working with the horizontal scroll bar. since at this level I am not still
1429 // supporting "top to bottom" flow, there is no real problem. If I support that someday
1430 // (think how to draw categories), we would have to take care of the horizontal scroll bar too.
1431 // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar.
1432 horizontalScrollBar()->setRange(0, 0);
1433
1434 //BEGIN bugs 213068, 287847 ------------------------------------------------------------
1435 // restoring values from above ...
1436 setVerticalScrollBarPolicy(verticalP);
1437 setHorizontalScrollBarPolicy(horizontalP);
1438 // ... and correct the visibility
1439 bool validRange = verticalScrollBar()->maximum() != verticalScrollBar()->minimum();
1440 if (verticalP == Qt::ScrollBarAsNeeded && (verticalScrollBar()->isVisibleTo(this) != validRange))
1441 verticalScrollBar()->setVisible(validRange);
1442 validRange = horizontalScrollBar()->maximum() > horizontalScrollBar()->minimum();
1443 if (horizontalP == Qt::ScrollBarAsNeeded && (horizontalScrollBar()->isVisibleTo(this) != validRange))
1444 horizontalScrollBar()->setVisible(validRange);
1445 //END bugs 213068, 287847 --------------------------------------------------------------
1446}
1447
1448void KCategorizedView::currentChanged(const QModelIndex &current,
1449 const QModelIndex &previous)
1450{
1451 QListView::currentChanged(current, previous);
1452}
1453
1454void KCategorizedView::dataChanged(const QModelIndex &topLeft,
1455 const QModelIndex &bottomRight)
1456{
1457 QListView::dataChanged(topLeft, bottomRight);
1458 if (!d->isCategorized()) {
1459 return;
1460 }
1461
1462 *d->hoveredBlock = Private::Block();
1463 d->hoveredCategory = QString();
1464
1465 //BEGIN: since the model changed data, we need to reconsider item sizes
1466 int i = topLeft.row();
1467 int indexToCheck = i;
1468 QModelIndex categoryIndex;
1469 QString category;
1470 Private::Block *block;
1471 while (i <= bottomRight.row()) {
1472 const QModelIndex currIndex = d->proxyModel->index(i, modelColumn(), rootIndex());
1473 if (i == indexToCheck) {
1474 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex());
1475 category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
1476 block = &d->blocks[category];
1477 block->quarantineStart = currIndex;
1478 indexToCheck = block->firstIndex.row() + block->items.count();
1479 }
1480 visualRect(currIndex);
1481 ++i;
1482 }
1483 //END: since the model changed data, we need to reconsider item sizes
1484}
1485
1486void KCategorizedView::rowsInserted(const QModelIndex &parent,
1487 int start,
1488 int end)
1489{
1490 QListView::rowsInserted(parent, start, end);
1491 if (!d->isCategorized()) {
1492 return;
1493 }
1494
1495 *d->hoveredBlock = Private::Block();
1496 d->hoveredCategory = QString();
1497 d->rowsInserted(parent, start, end);
1498}
1499
1500#ifndef KDE_NO_DEPRECATED
1501void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent,
1502 int start,
1503 int end)
1504{
1505 Q_UNUSED(parent);
1506 Q_UNUSED(start);
1507 Q_UNUSED(end);
1508}
1509#endif
1510
1511#ifndef KDE_NO_DEPRECATED
1512void KCategorizedView::rowsRemoved(const QModelIndex &parent,
1513 int start,
1514 int end)
1515{
1516 Q_UNUSED(parent);
1517 Q_UNUSED(start);
1518 Q_UNUSED(end);
1519}
1520#endif
1521
1522void KCategorizedView::slotLayoutChanged()
1523{
1524 if (!d->isCategorized()) {
1525 return;
1526 }
1527
1528 d->blocks.clear();
1529 *d->hoveredBlock = Private::Block();
1530 d->hoveredCategory = QString();
1531 if (d->proxyModel->rowCount()) {
1532 d->rowsInserted(rootIndex(), 0, d->proxyModel->rowCount() - 1);
1533 }
1534}
1535
1536//END: Public part
1537
1538#include "kcategorizedview.moc"
KCategorizedSortFilterProxyModel
This class lets you categorize a view.
Definition: kcategorizedsortfilterproxymodel.h:47
KCategorizedSortFilterProxyModel::CategoryDisplayRole
@ CategoryDisplayRole
This role is used for asking the category to a given index.
Definition: kcategorizedsortfilterproxymodel.h:52
KCategorizedView
Item view for listing items in a categorized fashion optionally.
Definition: kcategorizedview.h:81
KCategorizedView::KCategorizedView
KCategorizedView(QWidget *parent=0)
Definition: kcategorizedview.cpp:503
KCategorizedView::alternatingBlockColors
bool alternatingBlockColors
Definition: kcategorizedview.h:84
KCategorizedView::setCategorySpacing
void setCategorySpacing(int categorySpacing)
Stablishes the category spacing.
Definition: kcategorizedview.cpp:659
KCategorizedView::updateGeometries
virtual void updateGeometries()
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1357
KCategorizedView::rowsAboutToBeRemoved
virtual void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1235
KCategorizedView::setModel
virtual void setModel(QAbstractItemModel *model)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:514
KCategorizedView::paintEvent
virtual void paintEvent(QPaintEvent *event)
Reimplemented from QWidget.
Definition: kcategorizedview.cpp:788
KCategorizedView::dragMoveEvent
virtual void dragMoveEvent(QDragMoveEvent *event)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1095
KCategorizedView::dragLeaveEvent
virtual void dragLeaveEvent(QDragLeaveEvent *event)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1106
KCategorizedView::rowsInsertedArtifficial
virtual void rowsInsertedArtifficial(const QModelIndex &parent, int start, int end)
Definition: kcategorizedview.cpp:1501
KCategorizedView::mouseReleaseEvent
virtual void mouseReleaseEvent(QMouseEvent *event)
Reimplemented from QWidget.
Definition: kcategorizedview.cpp:1042
KCategorizedView::rowsInserted
virtual void rowsInserted(const QModelIndex &parent, int start, int end)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1486
KCategorizedView::rowsRemoved
virtual void rowsRemoved(const QModelIndex &parent, int start, int end)
Definition: kcategorizedview.cpp:1512
KCategorizedView::categoryDrawer
KCategoryDrawer * categoryDrawer() const
Returns the current category drawer.
Definition: kcategorizedview.cpp:632
KCategorizedView::dragEnterEvent
virtual void dragEnterEvent(QDragEnterEvent *event)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1101
KCategorizedView::setCollapsibleBlocks
void setCollapsibleBlocks(bool enable)
Sets whether blocks can be collapsed or not.
Definition: kcategorizedview.cpp:688
KCategorizedView::~KCategorizedView
~KCategorizedView()
Definition: kcategorizedview.cpp:509
KCategorizedView::leaveEvent
virtual void leaveEvent(QEvent *event)
Reimplemented from QWidget.
Definition: kcategorizedview.cpp:1073
KCategorizedView::setAlternatingBlockColors
void setAlternatingBlockColors(bool enable)
Sets whether blocks should be drawn with alternating colors.
Definition: kcategorizedview.cpp:678
KCategorizedView::moveCursor
virtual QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1118
KCategorizedView::visualRect
virtual QRect visualRect(const QModelIndex &index) const
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:551
KCategorizedView::mouseMoveEvent
virtual void mouseMoveEvent(QMouseEvent *event)
Reimplemented from QWidget.
Definition: kcategorizedview.cpp:949
KCategorizedView::collapsibleBlocks
bool collapsibleBlocks
Definition: kcategorizedview.h:85
KCategorizedView::currentChanged
virtual void currentChanged(const QModelIndex &current, const QModelIndex &previous)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1448
KCategorizedView::mousePressEvent
virtual void mousePressEvent(QMouseEvent *event)
Reimplemented from QWidget.
Definition: kcategorizedview.cpp:1008
KCategorizedView::setCategoryDrawer
void setCategoryDrawer(KCategoryDrawer *categoryDrawer)
The category drawer that will be used for drawing categories.
Definition: kcategorizedview.cpp:637
KCategorizedView::startDrag
virtual void startDrag(Qt::DropActions supportedActions)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1090
KCategorizedView::dropEvent
virtual void dropEvent(QDropEvent *event)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1111
KCategorizedView::reset
virtual void reset()
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:782
KCategorizedView::block
QModelIndexList block(const QString &category)
Definition: kcategorizedview.cpp:693
KCategorizedView::setSelection
virtual void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:905
KCategorizedView::indexAt
virtual QModelIndex indexAt(const QPoint &point) const
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:716
KCategorizedView::setGridSizeOwn
void setGridSizeOwn(const QSize &size)
Definition: kcategorizedview.cpp:545
KCategorizedView::dataChanged
virtual void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
Reimplemented from QAbstractItemView.
Definition: kcategorizedview.cpp:1454
KCategorizedView::categorySpacing
int categorySpacing
Definition: kcategorizedview.h:83
KCategorizedView::slotLayoutChanged
virtual void slotLayoutChanged()
Definition: kcategorizedview.cpp:1522
KCategorizedView::setGridSize
void setGridSize(const QSize &size)
Calls to setGridSizeOwn().
Definition: kcategorizedview.cpp:540
KCategorizedView::resizeEvent
virtual void resizeEvent(QResizeEvent *event)
Reimplemented from QWidget.
Definition: kcategorizedview.cpp:899
KCategoryDrawerV2
Definition: kcategorydrawer.h:116
KCategoryDrawerV3
Definition: kcategorydrawer.h:152
KCategoryDrawer
Definition: kcategorydrawer.h:44
QAbstractItemModel
QHash
QListView
QList
QPair
QWidget
kcategorizedsortfilterproxymodel.h
kcategorizedview.h
kcategorydrawer.h
lessThan
bool lessThan(const QString &left, const QString &right)
Definition: kcompletion.cpp:812
operator!=
bool operator!=(const KEntry &k1, const KEntry &k2)
KStandardShortcut::end
const KShortcut & end()
Goto end of the document.
Definition: kstandardshortcut.cpp:348
Item
Item
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KDEUI

Skip menu "KDEUI"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Modules
  • Related Pages

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal