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

Skip menu "kdelibs-4.10.5 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