KDEUI
kcategorizedview.cpp
Go to the documentation of this file.
00001 00032 #include "kcategorizedview.h" 00033 #include "kcategorizedview_p.h" 00034 00035 #include <math.h> // trunc on C99 compliant systems 00036 #include <kdefakes.h> // trunc for not C99 compliant systems 00037 00038 #include <QPainter> 00039 #include <QScrollBar> 00040 #include <QPaintEvent> 00041 00042 #include "kcategorydrawer.h" 00043 #include "kcategorizedsortfilterproxymodel.h" 00044 00045 //BEGIN: Private part 00046 00047 struct KCategorizedView::Private::Item 00048 { 00049 Item() 00050 : topLeft(QPoint()) 00051 , size(QSize()) 00052 { 00053 } 00054 00055 QPoint topLeft; 00056 QSize size; 00057 }; 00058 00059 struct KCategorizedView::Private::Block 00060 { 00061 Block() 00062 : topLeft(QPoint()) 00063 , height(-1) 00064 , firstIndex(QModelIndex()) 00065 , quarantineStart(QModelIndex()) 00066 , items(QList<Item>()) 00067 , outOfQuarantine(false) 00068 , alternate(false) 00069 , collapsed(false) 00070 { 00071 } 00072 00073 bool operator!=(const Block &rhs) const 00074 { 00075 return firstIndex != rhs.firstIndex; 00076 } 00077 00078 static bool lessThan(const Block &left, const Block &right) 00079 { 00080 Q_ASSERT(left.firstIndex.isValid()); 00081 Q_ASSERT(right.firstIndex.isValid()); 00082 return left.firstIndex.row() < right.firstIndex.row(); 00083 } 00084 00085 QPoint topLeft; 00086 int height; 00087 QPersistentModelIndex firstIndex; 00088 // if we have n elements on this block, and we inserted an element at position i. The quarantine 00089 // will start at index (i, column, parent). This means that for all elements j where i <= j <= n, the 00090 // visual rect position of item j will have to be recomputed (cannot use the cached point). The quarantine 00091 // will only affect the current block, since the rest of blocks can be affected only in the way 00092 // that the whole block will have different offset, but items will keep the same relative position 00093 // in terms of their parent blocks. 00094 QPersistentModelIndex quarantineStart; 00095 QList<Item> items; 00096 00097 // this affects the whole block, not items separately. items contain the topLeft point relative 00098 // to the block. Because of insertions or removals a whole block can be moved, so the whole block 00099 // will enter in quarantine, what is faster than moving all items in absolute terms. 00100 bool outOfQuarantine; 00101 00102 // should we alternate its color ? is just a hint, could not be used 00103 bool alternate; 00104 bool collapsed; 00105 }; 00106 00107 KCategorizedView::Private::Private(KCategorizedView *q) 00108 : q(q) 00109 , proxyModel(0) 00110 , categoryDrawer(0) 00111 , categoryDrawerV2(0) 00112 , categoryDrawerV3(0) 00113 , categorySpacing(5) 00114 , alternatingBlockColors(false) 00115 , collapsibleBlocks(false) 00116 , hoveredBlock(new Block()) 00117 , hoveredIndex(QModelIndex()) 00118 , pressedPosition(QPoint()) 00119 , rubberBandRect(QRect()) 00120 { 00121 } 00122 00123 KCategorizedView::Private::~Private() 00124 { 00125 delete hoveredBlock; 00126 } 00127 00128 bool KCategorizedView::Private::isCategorized() const 00129 { 00130 return proxyModel && categoryDrawer && proxyModel->isCategorizedModel(); 00131 } 00132 00133 QStyleOptionViewItemV4 KCategorizedView::Private::blockRect(const QModelIndex &representative) 00134 { 00135 QStyleOptionViewItemV4 option(q->viewOptions()); 00136 const int height = categoryDrawer->categoryHeight(representative, option); 00137 const QString categoryDisplay = representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 00138 QPoint pos = blockPosition(categoryDisplay); 00139 pos.ry() -= height; 00140 option.rect.setTopLeft(pos); 00141 option.rect.setWidth(viewportWidth() + categoryDrawer->leftMargin() + categoryDrawer->rightMargin()); 00142 option.rect.setHeight(height + blockHeight(categoryDisplay)); 00143 option.rect = mapToViewport(option.rect); 00144 00145 return option; 00146 } 00147 00148 QPair<QModelIndex, QModelIndex> KCategorizedView::Private::intersectingIndexesWithRect(const QRect &_rect) const 00149 { 00150 const int rowCount = proxyModel->rowCount(); 00151 00152 const QRect rect = _rect.normalized(); 00153 00154 // binary search to find out the top border 00155 int bottom = 0; 00156 int top = rowCount - 1; 00157 while (bottom <= top) { 00158 const int middle = (bottom + top) / 2; 00159 const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex()); 00160 QRect itemRect = q->visualRect(index); 00161 const int verticalOff = q->verticalOffset(); 00162 const int horizontalOff = q->horizontalOffset(); 00163 itemRect.topLeft().ry() += verticalOff; 00164 itemRect.topLeft().rx() += horizontalOff; 00165 itemRect.bottomRight().ry() += verticalOff; 00166 itemRect.bottomRight().rx() += horizontalOff; 00167 if (itemRect.bottomRight().y() <= rect.topLeft().y()) { 00168 bottom = middle + 1; 00169 } else { 00170 top = middle - 1; 00171 } 00172 } 00173 00174 const QModelIndex bottomIndex = proxyModel->index(bottom, q->modelColumn(), q->rootIndex()); 00175 00176 // binary search to find out the bottom border 00177 bottom = 0; 00178 top = rowCount - 1; 00179 while (bottom <= top) { 00180 const int middle = (bottom + top) / 2; 00181 const QModelIndex index = proxyModel->index(middle, q->modelColumn(), q->rootIndex()); 00182 QRect itemRect = q->visualRect(index); 00183 const int verticalOff = q->verticalOffset(); 00184 const int horizontalOff = q->horizontalOffset(); 00185 itemRect.topLeft().ry() += verticalOff; 00186 itemRect.topLeft().rx() += horizontalOff; 00187 itemRect.bottomRight().ry() += verticalOff; 00188 itemRect.bottomRight().rx() += horizontalOff; 00189 if (itemRect.topLeft().y() <= rect.bottomRight().y()) { 00190 bottom = middle + 1; 00191 } else { 00192 top = middle - 1; 00193 } 00194 } 00195 00196 const QModelIndex topIndex = proxyModel->index(top, q->modelColumn(), q->rootIndex()); 00197 00198 return qMakePair(bottomIndex, topIndex); 00199 } 00200 00201 QPoint KCategorizedView::Private::blockPosition(const QString &category) 00202 { 00203 Block &block = blocks[category]; 00204 00205 if (block.outOfQuarantine && !block.topLeft.isNull()) { 00206 return block.topLeft; 00207 } 00208 00209 QPoint res(categorySpacing, 0); 00210 00211 const QModelIndex index = block.firstIndex; 00212 00213 for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) { 00214 Block &block = *it; 00215 const QModelIndex categoryIndex = block.firstIndex; 00216 if (index.row() < categoryIndex.row()) { 00217 continue; 00218 } 00219 res.ry() += categoryDrawer->categoryHeight(categoryIndex, q->viewOptions()) + categorySpacing; 00220 if (index.row() == categoryIndex.row()) { 00221 continue; 00222 } 00223 res.ry() += blockHeight(it.key()); 00224 } 00225 00226 block.outOfQuarantine = true; 00227 block.topLeft = res; 00228 00229 return res; 00230 } 00231 00232 int KCategorizedView::Private::blockHeight(const QString &category) 00233 { 00234 Block &block = blocks[category]; 00235 00236 if (block.collapsed) { 00237 return 0; 00238 } 00239 00240 if (block.height > -1) { 00241 return block.height; 00242 } 00243 00244 const QModelIndex firstIndex = block.firstIndex; 00245 const QModelIndex lastIndex = proxyModel->index(firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex()); 00246 const QRect topLeft = q->visualRect(firstIndex); 00247 QRect bottomRight = q->visualRect(lastIndex); 00248 00249 if (hasGrid()) { 00250 bottomRight.setHeight(qMax(bottomRight.height(), q->gridSize().height())); 00251 } else { 00252 if (!q->uniformItemSizes()) { 00253 bottomRight.setHeight(highestElementInLastRow(block) + q->spacing() * 2); 00254 } 00255 } 00256 00257 const int height = bottomRight.bottomRight().y() - topLeft.topLeft().y() + 1; 00258 block.height = height; 00259 00260 return height; 00261 } 00262 00263 int KCategorizedView::Private::viewportWidth() const 00264 { 00265 return q->viewport()->width() - categorySpacing * 2 - categoryDrawer->leftMargin() - categoryDrawer->rightMargin(); 00266 } 00267 00268 void KCategorizedView::Private::regenerateAllElements() 00269 { 00270 for (QHash<QString, Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) { 00271 Block &block = *it; 00272 block.outOfQuarantine = false; 00273 block.quarantineStart = block.firstIndex; 00274 block.height = -1; 00275 } 00276 } 00277 00278 void KCategorizedView::Private::rowsInserted(const QModelIndex &parent, int start, int end) 00279 { 00280 if (!isCategorized()) { 00281 return; 00282 } 00283 00284 for (int i = start; i <= end; ++i) { 00285 const QModelIndex index = proxyModel->index(i, q->modelColumn(), parent); 00286 00287 Q_ASSERT(index.isValid()); 00288 00289 const QString category = categoryForIndex(index); 00290 00291 Block &block = blocks[category]; 00292 00293 //BEGIN: update firstIndex 00294 // save as firstIndex in block if 00295 // - it forced the category creation (first element on this category) 00296 // - it is before the first row on that category 00297 const QModelIndex firstIndex = block.firstIndex; 00298 if (!firstIndex.isValid() || index.row() < firstIndex.row()) { 00299 block.firstIndex = index; 00300 } 00301 //END: update firstIndex 00302 00303 Q_ASSERT(block.firstIndex.isValid()); 00304 00305 const int firstIndexRow = block.firstIndex.row(); 00306 00307 block.items.insert(index.row() - firstIndexRow, Private::Item()); 00308 block.height = -1; 00309 00310 q->visualRect(index); 00311 q->viewport()->update(); 00312 } 00313 00314 //BEGIN: update the items that are in quarantine in affected categories 00315 { 00316 const QModelIndex lastIndex = proxyModel->index(end, q->modelColumn(), parent); 00317 const QString category = categoryForIndex(lastIndex); 00318 Private::Block &block = blocks[category]; 00319 block.quarantineStart = block.firstIndex; 00320 } 00321 //END: update the items that are in quarantine in affected categories 00322 00323 //BEGIN: mark as in quarantine those categories that are under the affected ones 00324 { 00325 const QModelIndex firstIndex = proxyModel->index(start, q->modelColumn(), parent); 00326 const QString category = categoryForIndex(firstIndex); 00327 const QModelIndex firstAffectedCategory = blocks[category].firstIndex; 00328 //BEGIN: order for marking as alternate those blocks that are alternate 00329 QList<Block> blockList = blocks.values(); 00330 qSort(blockList.begin(), blockList.end(), Block::lessThan); 00331 QList<int> firstIndexesRows; 00332 foreach (const Block &block, blockList) { 00333 firstIndexesRows << block.firstIndex.row(); 00334 } 00335 //END: order for marking as alternate those blocks that are alternate 00336 for (QHash<QString, Private::Block>::Iterator it = blocks.begin(); it != blocks.end(); ++it) { 00337 Private::Block &block = *it; 00338 if (block.firstIndex.row() > firstAffectedCategory.row()) { 00339 block.outOfQuarantine = false; 00340 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2; 00341 } else if (block.firstIndex.row() == firstAffectedCategory.row()) { 00342 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2; 00343 } 00344 } 00345 } 00346 //END: mark as in quarantine those categories that are under the affected ones 00347 } 00348 00349 QRect KCategorizedView::Private::mapToViewport(const QRect &rect) const 00350 { 00351 const int dx = -q->horizontalOffset(); 00352 const int dy = -q->verticalOffset(); 00353 return rect.adjusted(dx, dy, dx, dy); 00354 } 00355 00356 QRect KCategorizedView::Private::mapFromViewport(const QRect &rect) const 00357 { 00358 const int dx = q->horizontalOffset(); 00359 const int dy = q->verticalOffset(); 00360 return rect.adjusted(dx, dy, dx, dy); 00361 } 00362 00363 int KCategorizedView::Private::highestElementInLastRow(const Block &block) const 00364 { 00365 //Find the highest element in the last row 00366 const QModelIndex lastIndex = proxyModel->index(block.firstIndex.row() + block.items.count() - 1, q->modelColumn(), q->rootIndex()); 00367 const QRect prevRect = q->visualRect(lastIndex); 00368 int res = prevRect.height(); 00369 QModelIndex prevIndex = proxyModel->index(lastIndex.row() - 1, q->modelColumn(), q->rootIndex()); 00370 if (!prevIndex.isValid()) { 00371 return res; 00372 } 00373 Q_FOREVER { 00374 const QRect tempRect = q->visualRect(prevIndex); 00375 if (tempRect.topLeft().y() < prevRect.topLeft().y()) { 00376 break; 00377 } 00378 res = qMax(res, tempRect.height()); 00379 if (prevIndex == block.firstIndex) { 00380 break; 00381 } 00382 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex()); 00383 } 00384 00385 return res; 00386 } 00387 00388 bool KCategorizedView::Private::hasGrid() const 00389 { 00390 const QSize gridSize = q->gridSize(); 00391 return gridSize.isValid() && !gridSize.isNull(); 00392 } 00393 00394 QString KCategorizedView::Private::categoryForIndex(const QModelIndex &index) const 00395 { 00396 const QModelIndex categoryIndex = index.model()->index(index.row(), proxyModel->sortColumn(), index.parent()); 00397 return categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 00398 } 00399 00400 void KCategorizedView::Private::leftToRightVisualRect(const QModelIndex &index, Item &item, 00401 const Block &block, const QPoint &blockPos) const 00402 { 00403 const int firstIndexRow = block.firstIndex.row(); 00404 00405 if (hasGrid()) { 00406 const int relativeRow = index.row() - firstIndexRow; 00407 const int maxItemsPerRow = qMax(viewportWidth() / q->gridSize().width(), 1); 00408 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin(); 00409 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * q->gridSize().height(); 00410 } else { 00411 if (q->uniformItemSizes()) { 00412 const int relativeRow = index.row() - firstIndexRow; 00413 const QSize itemSize = q->sizeHintForIndex(index); 00414 const int maxItemsPerRow = qMax((viewportWidth() - q->spacing()) / (itemSize.width() + q->spacing()), 1); 00415 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin(); 00416 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * itemSize.height(); 00417 } else { 00418 if (index != block.firstIndex) { 00419 const int viewportW = viewportWidth() - q->spacing(); 00420 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex()); 00421 QRect prevRect = q->visualRect(prevIndex); 00422 prevRect = mapFromViewport(prevRect); 00423 const QSize currSize = q->sizeHintForIndex(index); 00424 if ((prevRect.bottomRight().x() + 1) + currSize.width() - blockPos.x() + q->spacing() > viewportW) { 00425 // we have to check the whole previous row, and see which one was the 00426 // highest. 00427 Q_FOREVER { 00428 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex()); 00429 const QRect tempRect = q->visualRect(prevIndex); 00430 if (tempRect.topLeft().y() < prevRect.topLeft().y()) { 00431 break; 00432 } 00433 if (tempRect.bottomRight().y() > prevRect.bottomRight().y()) { 00434 prevRect = tempRect; 00435 } 00436 if (prevIndex == block.firstIndex) { 00437 break; 00438 } 00439 } 00440 item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing(); 00441 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y(); 00442 } else { 00443 item.topLeft.rx() = (prevRect.bottomRight().x() + 1) + q->spacing(); 00444 item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y(); 00445 } 00446 } else { 00447 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); 00448 item.topLeft.ry() = q->spacing(); 00449 } 00450 } 00451 } 00452 00453 item.size = q->sizeHintForIndex(index); 00454 } 00455 00456 void KCategorizedView::Private::topToBottomVisualRect(const QModelIndex &index, Item &item, 00457 const Block &block, const QPoint &blockPos) const 00458 { 00459 const int firstIndexRow = block.firstIndex.row(); 00460 00461 if (hasGrid()) { 00462 const int relativeRow = index.row() - firstIndexRow; 00463 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); 00464 item.topLeft.ry() = relativeRow * q->gridSize().height(); 00465 } else { 00466 if (q->uniformItemSizes()) { 00467 const int relativeRow = index.row() - firstIndexRow; 00468 const QSize itemSize = q->sizeHintForIndex(index); 00469 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); 00470 item.topLeft.ry() = relativeRow * itemSize.height(); 00471 } else { 00472 if (index != block.firstIndex) { 00473 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex()); 00474 QRect prevRect = q->visualRect(prevIndex); 00475 prevRect = mapFromViewport(prevRect); 00476 const QSize currSize = q->sizeHintForIndex(index); 00477 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); 00478 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y(); 00479 } else { 00480 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); 00481 item.topLeft.ry() = q->spacing(); 00482 } 00483 } 00484 } 00485 00486 item.size = q->sizeHintForIndex(index); 00487 item.size.setWidth(viewportWidth()); 00488 } 00489 00490 void KCategorizedView::Private::_k_slotCollapseOrExpandClicked(QModelIndex) 00491 { 00492 } 00493 00494 //END: Private part 00495 00496 //BEGIN: Public part 00497 00498 KCategorizedView::KCategorizedView(QWidget *parent) 00499 : QListView(parent) 00500 , d(new Private(this)) 00501 { 00502 } 00503 00504 KCategorizedView::~KCategorizedView() 00505 { 00506 delete d; 00507 } 00508 00509 void KCategorizedView::setModel(QAbstractItemModel *model) 00510 { 00511 if (d->proxyModel == model) { 00512 return; 00513 } 00514 00515 d->blocks.clear(); 00516 00517 if (d->proxyModel) { 00518 disconnect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged())); 00519 } 00520 00521 d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*>(model); 00522 00523 if (d->proxyModel) { 00524 connect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged())); 00525 } 00526 00527 QListView::setModel(model); 00528 00529 // if the model already had information inserted, update our data structures to it 00530 if (model->rowCount()) { 00531 slotLayoutChanged(); 00532 } 00533 } 00534 00535 void KCategorizedView::setGridSize(const QSize &size) 00536 { 00537 setGridSizeOwn(size); 00538 } 00539 00540 void KCategorizedView::setGridSizeOwn(const QSize &size) 00541 { 00542 d->regenerateAllElements(); 00543 QListView::setGridSize(size); 00544 } 00545 00546 QRect KCategorizedView::visualRect(const QModelIndex &index) const 00547 { 00548 if (!d->isCategorized()) { 00549 return QListView::visualRect(index); 00550 } 00551 00552 if (!index.isValid()) { 00553 return QRect(); 00554 } 00555 00556 const QString category = d->categoryForIndex(index); 00557 00558 if (!d->blocks.contains(category)) { 00559 return QRect(); 00560 } 00561 00562 Private::Block &block = d->blocks[category]; 00563 const int firstIndexRow = block.firstIndex.row(); 00564 00565 Q_ASSERT(block.firstIndex.isValid()); 00566 00567 if (index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count()) { 00568 return QRect(); 00569 } 00570 00571 const QPoint blockPos = d->blockPosition(category); 00572 00573 Private::Item &ritem = block.items[index.row() - firstIndexRow]; 00574 00575 if (ritem.topLeft.isNull() || (block.quarantineStart.isValid() && 00576 index.row() >= block.quarantineStart.row())) { 00577 if (flow() == LeftToRight) { 00578 d->leftToRightVisualRect(index, ritem, block, blockPos); 00579 } else { 00580 d->topToBottomVisualRect(index, ritem, block, blockPos); 00581 } 00582 00583 //BEGIN: update the quarantine start 00584 const bool wasLastIndex = (index.row() == (block.firstIndex.row() + block.items.count() - 1)); 00585 if (index.row() == block.quarantineStart.row()) { 00586 if (wasLastIndex) { 00587 block.quarantineStart = QModelIndex(); 00588 } else { 00589 const QModelIndex nextIndex = d->proxyModel->index(index.row() + 1, modelColumn(), rootIndex()); 00590 block.quarantineStart = nextIndex; 00591 } 00592 } 00593 //END: update the quarantine start 00594 } 00595 00596 // we get now the absolute position through the relative position of the parent block. do not 00597 // save this on ritem, since this would override the item relative position in block terms. 00598 Private::Item item(ritem); 00599 item.topLeft.ry() += blockPos.y(); 00600 00601 const QSize sizeHint = item.size; 00602 00603 if (d->hasGrid()) { 00604 const QSize sizeGrid = gridSize(); 00605 const QSize resultingSize = sizeHint.boundedTo(sizeGrid); 00606 QRect res(item.topLeft.x() + ((sizeGrid.width() - resultingSize.width()) / 2), 00607 item.topLeft.y(), resultingSize.width(), resultingSize.height()); 00608 if (block.collapsed) { 00609 // we can still do binary search, while we "hide" items. We move those items in collapsed 00610 // blocks to the left and set a 0 height. 00611 res.setLeft(-resultingSize.width()); 00612 res.setHeight(0); 00613 } 00614 return d->mapToViewport(res); 00615 } 00616 00617 QRect res(item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height()); 00618 if (block.collapsed) { 00619 // we can still do binary search, while we "hide" items. We move those items in collapsed 00620 // blocks to the left and set a 0 height. 00621 res.setLeft(-sizeHint.width()); 00622 res.setHeight(0); 00623 } 00624 return d->mapToViewport(res); 00625 } 00626 00627 KCategoryDrawer *KCategorizedView::categoryDrawer() const 00628 { 00629 return d->categoryDrawer; 00630 } 00631 00632 void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer) 00633 { 00634 if (d->categoryDrawerV2) { 00635 disconnect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)), 00636 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex))); 00637 } 00638 00639 d->categoryDrawer = categoryDrawer; 00640 d->categoryDrawerV2 = dynamic_cast<KCategoryDrawerV2*>(categoryDrawer); 00641 d->categoryDrawerV3 = dynamic_cast<KCategoryDrawerV3*>(categoryDrawer); 00642 00643 if (d->categoryDrawerV2) { 00644 connect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)), 00645 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex))); 00646 } 00647 } 00648 00649 int KCategorizedView::categorySpacing() const 00650 { 00651 return d->categorySpacing; 00652 } 00653 00654 void KCategorizedView::setCategorySpacing(int categorySpacing) 00655 { 00656 if (d->categorySpacing == categorySpacing) { 00657 return; 00658 } 00659 00660 d->categorySpacing = categorySpacing; 00661 00662 for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { 00663 Private::Block &block = *it; 00664 block.outOfQuarantine = false; 00665 } 00666 } 00667 00668 bool KCategorizedView::alternatingBlockColors() const 00669 { 00670 return d->alternatingBlockColors; 00671 } 00672 00673 void KCategorizedView::setAlternatingBlockColors(bool enable) 00674 { 00675 d->alternatingBlockColors = enable; 00676 } 00677 00678 bool KCategorizedView::collapsibleBlocks() const 00679 { 00680 return d->collapsibleBlocks; 00681 } 00682 00683 void KCategorizedView::setCollapsibleBlocks(bool enable) 00684 { 00685 d->collapsibleBlocks = enable; 00686 } 00687 00688 QModelIndexList KCategorizedView::block(const QString &category) 00689 { 00690 QModelIndexList res; 00691 const Private::Block &block = d->blocks[category]; 00692 if (block.height == -1) { 00693 return res; 00694 } 00695 QModelIndex current = block.firstIndex; 00696 const int first = current.row(); 00697 for (int i = 1; i <= block.items.count(); ++i) { 00698 if (current.isValid()) { 00699 res << current; 00700 } 00701 current = d->proxyModel->index(first + i, modelColumn(), rootIndex()); 00702 } 00703 return res; 00704 } 00705 00706 QModelIndexList KCategorizedView::block(const QModelIndex &representative) 00707 { 00708 return block(representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString()); 00709 } 00710 00711 QModelIndex KCategorizedView::indexAt(const QPoint &point) const 00712 { 00713 if (!d->isCategorized()) { 00714 return QListView::indexAt(point); 00715 } 00716 00717 const int rowCount = d->proxyModel->rowCount(); 00718 if (!rowCount) { 00719 return QModelIndex(); 00720 } 00721 00722 // Binary search that will try to spot if there is an index under point 00723 int bottom = 0; 00724 int top = rowCount - 1; 00725 while (bottom <= top) { 00726 const int middle = (bottom + top) / 2; 00727 const QModelIndex index = d->proxyModel->index(middle, modelColumn(), rootIndex()); 00728 QRect rect = visualRect(index); 00729 const int verticalOff = verticalOffset(); 00730 const int horizontalOff = horizontalOffset(); 00731 rect.topLeft().ry() += verticalOff; 00732 rect.topLeft().rx() += horizontalOff; 00733 rect.bottomRight().ry() += verticalOff; 00734 rect.bottomRight().rx() += horizontalOff; 00735 if (rect.contains(point)) { 00736 if (index.model()->flags(index) & Qt::ItemIsEnabled) { 00737 return index; 00738 } 00739 return QModelIndex(); 00740 } 00741 if (point.y() > rect.bottomRight().y() || 00742 (point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && 00743 point.x() > rect.bottomRight().x())) { 00744 bottom = middle + 1; 00745 } else { 00746 top = middle - 1; 00747 } 00748 } 00749 00750 return QModelIndex(); 00751 } 00752 00753 void KCategorizedView::reset() 00754 { 00755 d->blocks.clear(); 00756 QListView::reset(); 00757 } 00758 00759 void KCategorizedView::paintEvent(QPaintEvent *event) 00760 { 00761 if (!d->isCategorized()) { 00762 QListView::paintEvent(event); 00763 return; 00764 } 00765 00766 const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(viewport()->rect().intersected(event->rect())); 00767 00768 QPainter p(viewport()); 00769 p.save(); 00770 00771 Q_ASSERT(selectionModel()->model() == d->proxyModel); 00772 00773 //BEGIN: draw categories 00774 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 00775 while (it != d->blocks.constEnd()) { 00776 const Private::Block &block = *it; 00777 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00778 QStyleOptionViewItemV4 option(viewOptions()); 00779 option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate 00780 : QStyleOptionViewItemV4::None; 00781 option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open 00782 : QStyle::State_None; 00783 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option); 00784 QPoint pos = d->blockPosition(it.key()); 00785 pos.ry() -= height; 00786 option.rect.setTopLeft(pos); 00787 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin()); 00788 option.rect.setHeight(height + d->blockHeight(it.key())); 00789 option.rect = d->mapToViewport(option.rect); 00790 if (!option.rect.intersects(viewport()->rect())) { 00791 ++it; 00792 continue; 00793 } 00794 d->categoryDrawer->drawCategory(categoryIndex, d->proxyModel->sortRole(), option, &p); 00795 ++it; 00796 } 00797 //END: draw categories 00798 00799 if (intersecting.first.isValid() && intersecting.second.isValid()) { 00800 //BEGIN: draw items 00801 int i = intersecting.first.row(); 00802 int indexToCheckIfBlockCollapsed = i; 00803 QModelIndex categoryIndex; 00804 QString category; 00805 Private::Block *block = 0; 00806 while (i <= intersecting.second.row()) { 00807 //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting 00808 if (i == indexToCheckIfBlockCollapsed) { 00809 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex()); 00810 category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 00811 block = &d->blocks[category]; 00812 indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); 00813 if (block->collapsed) { 00814 i = indexToCheckIfBlockCollapsed; 00815 continue; 00816 } 00817 } 00818 //END: first check if the block is collapsed. if so, we have to skip the item painting 00819 00820 Q_ASSERT(block); 00821 00822 const bool alternateItem = (i - block->firstIndex.row()) % 2; 00823 00824 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex()); 00825 const Qt::ItemFlags flags = d->proxyModel->flags(index); 00826 QStyleOptionViewItemV4 option(viewOptions()); 00827 option.rect = visualRect(index); 00828 option.widget = this; 00829 option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText 00830 : QStyleOptionViewItemV2::None; 00831 option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate 00832 : QStyleOptionViewItemV4::None; 00833 if (flags & Qt::ItemIsSelectable) { 00834 option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected 00835 : QStyle::State_None; 00836 } else { 00837 option.state &= ~QStyle::State_Selected; 00838 } 00839 option.state |= (index == currentIndex()) ? QStyle::State_HasFocus 00840 : QStyle::State_None; 00841 if (!(flags & Qt::ItemIsEnabled)) { 00842 option.state &= ~QStyle::State_Enabled; 00843 } else { 00844 option.state |= (index == d->hoveredIndex) ? QStyle::State_MouseOver 00845 : QStyle::State_None; 00846 } 00847 itemDelegate(index)->paint(&p, option, index); 00848 ++i; 00849 } 00850 //END: draw items 00851 } 00852 00853 //BEGIN: draw selection rect 00854 if (isSelectionRectVisible() && d->rubberBandRect.isValid()) { 00855 QStyleOptionRubberBand opt; 00856 opt.initFrom(this); 00857 opt.shape = QRubberBand::Rectangle; 00858 opt.opaque = false; 00859 opt.rect = d->mapToViewport(d->rubberBandRect).intersected(viewport()->rect().adjusted(-16, -16, 16, 16)); 00860 p.save(); 00861 style()->drawControl(QStyle::CE_RubberBand, &opt, &p); 00862 p.restore(); 00863 } 00864 //END: draw selection rect 00865 00866 p.restore(); 00867 } 00868 00869 void KCategorizedView::resizeEvent(QResizeEvent *event) 00870 { 00871 d->regenerateAllElements(); 00872 QListView::resizeEvent(event); 00873 } 00874 00875 void KCategorizedView::setSelection(const QRect &rect, 00876 QItemSelectionModel::SelectionFlags flags) 00877 { 00878 if (!d->isCategorized()) { 00879 QListView::setSelection(rect, flags); 00880 return; 00881 } 00882 00883 if (rect.topLeft() == rect.bottomRight()) { 00884 const QModelIndex index = indexAt(rect.topLeft()); 00885 selectionModel()->select(index, flags); 00886 return; 00887 } 00888 00889 const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(rect); 00890 00891 QItemSelection selection; 00892 00893 //TODO: think of a faster implementation 00894 QModelIndex firstIndex; 00895 QModelIndex lastIndex; 00896 for (int i = intersecting.first.row(); i <= intersecting.second.row(); ++i) { 00897 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex()); 00898 const bool visualRectIntersects = visualRect(index).intersects(rect); 00899 if (firstIndex.isValid()) { 00900 if (visualRectIntersects) { 00901 lastIndex = index; 00902 } else { 00903 selection << QItemSelectionRange(firstIndex, lastIndex); 00904 firstIndex = QModelIndex(); 00905 } 00906 } else if (visualRectIntersects) { 00907 firstIndex = index; 00908 lastIndex = index; 00909 } 00910 } 00911 00912 if (firstIndex.isValid()) { 00913 selection << QItemSelectionRange(firstIndex, lastIndex); 00914 } 00915 00916 selectionModel()->select(selection, flags); 00917 } 00918 00919 void KCategorizedView::mouseMoveEvent(QMouseEvent *event) 00920 { 00921 QListView::mouseMoveEvent(event); 00922 d->hoveredIndex = indexAt(event->pos()); 00923 const SelectionMode itemViewSelectionMode = selectionMode(); 00924 if (state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection 00925 && itemViewSelectionMode != NoSelection) { 00926 QRect rect(d->pressedPosition, event->pos() + QPoint(horizontalOffset(), verticalOffset())); 00927 rect = rect.normalized(); 00928 update(rect.united(d->rubberBandRect)); 00929 d->rubberBandRect = rect; 00930 } 00931 if (!d->categoryDrawerV2) { 00932 return; 00933 } 00934 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 00935 while (it != d->blocks.constEnd()) { 00936 const Private::Block &block = *it; 00937 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00938 QStyleOptionViewItemV4 option(viewOptions()); 00939 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option); 00940 QPoint pos = d->blockPosition(it.key()); 00941 pos.ry() -= height; 00942 option.rect.setTopLeft(pos); 00943 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin()); 00944 option.rect.setHeight(height + d->blockHeight(it.key())); 00945 option.rect = d->mapToViewport(option.rect); 00946 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos()); 00947 if (option.rect.contains(mousePos)) { 00948 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1 && *d->hoveredBlock != block) { 00949 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00950 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 00951 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect); 00952 *d->hoveredBlock = block; 00953 d->hoveredCategory = it.key(); 00954 viewport()->update(option.rect); 00955 } else if (d->hoveredBlock->height == -1) { 00956 *d->hoveredBlock = block; 00957 d->hoveredCategory = it.key(); 00958 } else if (d->categoryDrawerV3) { 00959 d->categoryDrawerV3->mouseMoved(categoryIndex, option.rect, event); 00960 } else { 00961 d->categoryDrawerV2->mouseButtonMoved(categoryIndex, event); 00962 } 00963 viewport()->update(option.rect); 00964 return; 00965 } 00966 ++it; 00967 } 00968 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) { 00969 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00970 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 00971 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect); 00972 *d->hoveredBlock = Private::Block(); 00973 d->hoveredCategory = QString(); 00974 viewport()->update(option.rect); 00975 } 00976 } 00977 00978 void KCategorizedView::mousePressEvent(QMouseEvent *event) 00979 { 00980 if (event->button() == Qt::LeftButton) { 00981 d->pressedPosition = event->pos(); 00982 d->pressedPosition.rx() += horizontalOffset(); 00983 d->pressedPosition.ry() += verticalOffset(); 00984 } 00985 if (!d->categoryDrawerV2) { 00986 QListView::mousePressEvent(event); 00987 return; 00988 } 00989 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 00990 while (it != d->blocks.constEnd()) { 00991 const Private::Block &block = *it; 00992 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00993 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 00994 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos()); 00995 if (option.rect.contains(mousePos)) { 00996 if (d->categoryDrawerV3) { 00997 d->categoryDrawerV3->mouseButtonPressed(categoryIndex, option.rect, event); 00998 } else { 00999 d->categoryDrawerV2->mouseButtonPressed(categoryIndex, event); 01000 } 01001 viewport()->update(option.rect); 01002 if (!event->isAccepted()) { 01003 QListView::mousePressEvent(event); 01004 } 01005 return; 01006 } 01007 ++it; 01008 } 01009 QListView::mousePressEvent(event); 01010 } 01011 01012 void KCategorizedView::mouseReleaseEvent(QMouseEvent *event) 01013 { 01014 d->pressedPosition = QPoint(); 01015 d->rubberBandRect = QRect(); 01016 if (!d->categoryDrawerV2) { 01017 QListView::mouseReleaseEvent(event); 01018 return; 01019 } 01020 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 01021 while (it != d->blocks.constEnd()) { 01022 const Private::Block &block = *it; 01023 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 01024 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 01025 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos()); 01026 if (option.rect.contains(mousePos)) { 01027 if (d->categoryDrawerV3) { 01028 d->categoryDrawerV3->mouseButtonReleased(categoryIndex, option.rect, event); 01029 } else { 01030 d->categoryDrawerV2->mouseButtonReleased(categoryIndex, event); 01031 } 01032 viewport()->update(option.rect); 01033 if (!event->isAccepted()) { 01034 QListView::mouseReleaseEvent(event); 01035 } 01036 return; 01037 } 01038 ++it; 01039 } 01040 QListView::mouseReleaseEvent(event); 01041 } 01042 01043 void KCategorizedView::leaveEvent(QEvent *event) 01044 { 01045 QListView::leaveEvent(event); 01046 if (d->hoveredIndex.isValid()) { 01047 viewport()->update(visualRect(d->hoveredIndex)); 01048 d->hoveredIndex = QModelIndex(); 01049 } 01050 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) { 01051 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 01052 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 01053 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect); 01054 *d->hoveredBlock = Private::Block(); 01055 d->hoveredCategory = QString(); 01056 viewport()->update(option.rect); 01057 } 01058 } 01059 01060 void KCategorizedView::startDrag(Qt::DropActions supportedActions) 01061 { 01062 QListView::startDrag(supportedActions); 01063 } 01064 01065 void KCategorizedView::dragMoveEvent(QDragMoveEvent *event) 01066 { 01067 QListView::dragMoveEvent(event); 01068 d->hoveredIndex = indexAt(event->pos()); 01069 } 01070 01071 void KCategorizedView::dragEnterEvent(QDragEnterEvent *event) 01072 { 01073 QListView::dragEnterEvent(event); 01074 } 01075 01076 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event) 01077 { 01078 QListView::dragLeaveEvent(event); 01079 } 01080 01081 void KCategorizedView::dropEvent(QDropEvent *event) 01082 { 01083 QListView::dropEvent(event); 01084 } 01085 01086 //TODO: improve se we take into account collapsed blocks 01087 //TODO: take into account when there is no grid and no uniformItemSizes 01088 QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction, 01089 Qt::KeyboardModifiers modifiers) 01090 { 01091 if (!d->isCategorized()) { 01092 return QListView::moveCursor(cursorAction, modifiers); 01093 } 01094 01095 const QModelIndex current = currentIndex(); 01096 const QRect currentRect = visualRect(current); 01097 if (!current.isValid()) { 01098 const int rowCount = d->proxyModel->rowCount(rootIndex()); 01099 if (!rowCount) { 01100 return QModelIndex(); 01101 } 01102 return d->proxyModel->index(0, modelColumn(), rootIndex()); 01103 } 01104 01105 switch (cursorAction) { 01106 case MoveLeft: { 01107 if (!current.row()) { 01108 return QModelIndex(); 01109 } 01110 const QModelIndex previous = d->proxyModel->index(current.row() - 1, modelColumn(), rootIndex()); 01111 const QRect previousRect = visualRect(previous); 01112 if (previousRect.top() == currentRect.top()) { 01113 return previous; 01114 } 01115 01116 return QModelIndex(); 01117 } 01118 case MoveRight: { 01119 if (current.row() == d->proxyModel->rowCount() - 1) { 01120 return QModelIndex(); 01121 } 01122 const QModelIndex next = d->proxyModel->index(current.row() + 1, modelColumn(), rootIndex()); 01123 const QRect nextRect = visualRect(next); 01124 if (nextRect.top() == currentRect.top()) { 01125 return next; 01126 } 01127 01128 return QModelIndex(); 01129 } 01130 case MoveDown: { 01131 if (d->hasGrid() || uniformItemSizes()) { 01132 const QModelIndex current = currentIndex(); 01133 const QSize itemSize = d->hasGrid() ? gridSize() 01134 : sizeHintForIndex(current); 01135 const Private::Block &block = d->blocks[d->categoryForIndex(current)]; 01136 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1); 01137 const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + 01138 block.items.count(); 01139 01140 if (canMove) { 01141 return d->proxyModel->index(current.row() + maxItemsPerRow, modelColumn(), rootIndex()); 01142 } 01143 01144 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow; 01145 const QModelIndex nextIndex = d->proxyModel->index(block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex()); 01146 01147 if (!nextIndex.isValid()) { 01148 return QModelIndex(); 01149 } 01150 01151 const Private::Block &nextBlock = d->blocks[d->categoryForIndex(nextIndex)]; 01152 01153 if (nextBlock.items.count() <= currentRelativePos) { 01154 return QModelIndex(); 01155 } 01156 01157 if (currentRelativePos < (block.items.count() % maxItemsPerRow)) { 01158 return d->proxyModel->index(nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex()); 01159 } 01160 01161 return QModelIndex(); 01162 } 01163 } 01164 case MoveUp: { 01165 if (d->hasGrid() || uniformItemSizes()) { 01166 const QModelIndex current = currentIndex(); 01167 const QSize itemSize = d->hasGrid() ? gridSize() 01168 : sizeHintForIndex(current); 01169 const Private::Block &block = d->blocks[d->categoryForIndex(current)]; 01170 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1); 01171 const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); 01172 01173 if (canMove) { 01174 return d->proxyModel->index(current.row() - maxItemsPerRow, modelColumn(), rootIndex()); 01175 } 01176 01177 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow; 01178 const QModelIndex prevIndex = d->proxyModel->index(block.firstIndex.row() - 1, modelColumn(), rootIndex()); 01179 01180 if (!prevIndex.isValid()) { 01181 return QModelIndex(); 01182 } 01183 01184 const Private::Block &prevBlock = d->blocks[d->categoryForIndex(prevIndex)]; 01185 01186 if (prevBlock.items.count() <= currentRelativePos) { 01187 return QModelIndex(); 01188 } 01189 01190 const int remainder = prevBlock.items.count() % maxItemsPerRow; 01191 if (currentRelativePos < remainder) { 01192 return d->proxyModel->index(prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex()); 01193 } 01194 01195 return QModelIndex(); 01196 } 01197 } 01198 default: 01199 break; 01200 } 01201 01202 return QModelIndex(); 01203 } 01204 01205 void KCategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, 01206 int start, 01207 int end) 01208 { 01209 if (!d->isCategorized()) { 01210 QListView::rowsAboutToBeRemoved(parent, start, end); 01211 return; 01212 } 01213 01214 *d->hoveredBlock = Private::Block(); 01215 d->hoveredCategory = QString(); 01216 01217 if (end - start + 1 == d->proxyModel->rowCount()) { 01218 d->blocks.clear(); 01219 QListView::rowsAboutToBeRemoved(parent, start, end); 01220 return; 01221 } 01222 01223 // Removal feels a bit more complicated than insertion. Basically we can consider there are 01224 // 3 different cases when going to remove items. (*) represents an item, Items between ([) and 01225 // (]) are the ones which are marked for removal. 01226 // 01227 // - 1st case: 01228 // ... * * * * * * [ * * * ... 01229 // 01230 // The items marked for removal are the last part of this category. No need to mark any item 01231 // of this category as in quarantine, because no special offset will be pushed to items at 01232 // the right because of any changes (since the removed items are those on the right most part 01233 // of the category). 01234 // 01235 // - 2nd case: 01236 // ... * * * * * * ] * * * ... 01237 // 01238 // The items marked for removal are the first part of this category. We have to mark as in 01239 // quarantine all items in this category. Absolutely all. All items will have to be moved to 01240 // the left (or moving up, because rows got a different offset). 01241 // 01242 // - 3rd case: 01243 // ... * * [ * * * * ] * * ... 01244 // 01245 // The items marked for removal are in between of this category. We have to mark as in 01246 // quarantine only those items that are at the right of the end of the removal interval, 01247 // (starting on "]"). 01248 // 01249 // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are 01250 // located under the top most affected category as in quarantine (the block itself, as a whole), 01251 // because such a change can force it to have a different offset (note that items themselves 01252 // contain relative positions to the block, so marking the block as in quarantine is enough). 01253 // 01254 // Also note that removal implicitly means that we have to update correctly firstIndex of each 01255 // block, and in general keep updated the internal information of elements. 01256 01257 QStringList listOfCategoriesMarkedForRemoval; 01258 01259 QString lastCategory; 01260 int alreadyRemoved = 0; 01261 for (int i = start; i <= end; ++i) { 01262 const QModelIndex index = d->proxyModel->index(i, modelColumn(), parent); 01263 01264 Q_ASSERT(index.isValid()); 01265 01266 const QString category = d->categoryForIndex(index); 01267 01268 if (lastCategory != category) { 01269 lastCategory = category; 01270 alreadyRemoved = 0; 01271 } 01272 01273 Private::Block &block = d->blocks[category]; 01274 block.items.removeAt(i - block.firstIndex.row() - alreadyRemoved); 01275 ++alreadyRemoved; 01276 01277 if (!block.items.count()) { 01278 listOfCategoriesMarkedForRemoval << category; 01279 } 01280 01281 block.height = -1; 01282 01283 viewport()->update(); 01284 } 01285 01286 //BEGIN: update the items that are in quarantine in affected categories 01287 { 01288 const QModelIndex lastIndex = d->proxyModel->index(end, modelColumn(), parent); 01289 const QString category = d->categoryForIndex(lastIndex); 01290 Private::Block &block = d->blocks[category]; 01291 if (block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row()) { 01292 block.firstIndex = d->proxyModel->index(end + 1, modelColumn(), parent); 01293 } 01294 block.quarantineStart = block.firstIndex; 01295 } 01296 //END: update the items that are in quarantine in affected categories 01297 01298 Q_FOREACH (const QString &category, listOfCategoriesMarkedForRemoval) { 01299 d->blocks.remove(category); 01300 } 01301 01302 //BEGIN: mark as in quarantine those categories that are under the affected ones 01303 { 01304 //BEGIN: order for marking as alternate those blocks that are alternate 01305 QList<Private::Block> blockList = d->blocks.values(); 01306 qSort(blockList.begin(), blockList.end(), Private::Block::lessThan); 01307 QList<int> firstIndexesRows; 01308 foreach (const Private::Block &block, blockList) { 01309 firstIndexesRows << block.firstIndex.row(); 01310 } 01311 //END: order for marking as alternate those blocks that are alternate 01312 for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { 01313 Private::Block &block = *it; 01314 if (block.firstIndex.row() > start) { 01315 block.outOfQuarantine = false; 01316 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2; 01317 } else if (block.firstIndex.row() == start) { 01318 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2; 01319 } 01320 } 01321 } 01322 //END: mark as in quarantine those categories that are under the affected ones 01323 01324 QListView::rowsAboutToBeRemoved(parent, start, end); 01325 } 01326 01327 void KCategorizedView::updateGeometries() 01328 { 01329 const int oldVerticalOffset = verticalOffset(); 01330 01331 QListView::updateGeometries(); 01332 01333 if (!d->isCategorized()) { 01334 return; 01335 } 01336 01337 const int rowCount = d->proxyModel->rowCount(); 01338 if (!rowCount) { 01339 verticalScrollBar()->setRange(0, 0); 01340 return; 01341 } 01342 01343 const QModelIndex lastIndex = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex()); 01344 Q_ASSERT(lastIndex.isValid()); 01345 QRect lastItemRect = visualRect(lastIndex); 01346 01347 if (d->hasGrid()) { 01348 lastItemRect.setSize(lastItemRect.size().expandedTo(gridSize())); 01349 } else { 01350 if (uniformItemSizes()) { 01351 QSize itemSize = sizeHintForIndex(lastIndex); 01352 itemSize.setHeight(itemSize.height() + spacing()); 01353 lastItemRect.setSize(itemSize); 01354 } else { 01355 QSize itemSize = sizeHintForIndex(lastIndex); 01356 const QString category = d->categoryForIndex(lastIndex); 01357 itemSize.setHeight(d->highestElementInLastRow(d->blocks[category]) + spacing()); 01358 lastItemRect.setSize(itemSize); 01359 } 01360 } 01361 01362 const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); 01363 01364 if (verticalScrollMode() == ScrollPerItem) { 01365 verticalScrollBar()->setSingleStep(lastItemRect.height()); 01366 const int rowsPerPage = qMax(viewport()->height() / lastItemRect.height(), 1); 01367 verticalScrollBar()->setPageStep(rowsPerPage * lastItemRect.height()); 01368 } 01369 01370 verticalScrollBar()->setRange(0, bottomRange); 01371 verticalScrollBar()->setValue(oldVerticalOffset); 01372 01373 //TODO: also consider working with the horizontal scroll bar. since at this level I am not still 01374 // supporting "top to bottom" flow, there is no real problem. If I support that someday 01375 // (think how to draw categories), we would have to take care of the horizontal scroll bar too. 01376 // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. 01377 horizontalScrollBar()->setRange(0, 0); 01378 } 01379 01380 void KCategorizedView::currentChanged(const QModelIndex ¤t, 01381 const QModelIndex &previous) 01382 { 01383 QListView::currentChanged(current, previous); 01384 } 01385 01386 void KCategorizedView::dataChanged(const QModelIndex &topLeft, 01387 const QModelIndex &bottomRight) 01388 { 01389 QListView::dataChanged(topLeft, bottomRight); 01390 if (!d->isCategorized()) { 01391 return; 01392 } 01393 01394 *d->hoveredBlock = Private::Block(); 01395 d->hoveredCategory = QString(); 01396 01397 //BEGIN: since the model changed data, we need to reconsider item sizes 01398 int i = topLeft.row(); 01399 int indexToCheck = i; 01400 QModelIndex categoryIndex; 01401 QString category; 01402 Private::Block *block; 01403 while (i <= bottomRight.row()) { 01404 const QModelIndex currIndex = d->proxyModel->index(i, modelColumn(), rootIndex()); 01405 if (i == indexToCheck) { 01406 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex()); 01407 category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 01408 block = &d->blocks[category]; 01409 block->quarantineStart = currIndex; 01410 indexToCheck = block->firstIndex.row() + block->items.count(); 01411 } 01412 visualRect(currIndex); 01413 ++i; 01414 } 01415 //END: since the model changed data, we need to reconsider item sizes 01416 } 01417 01418 void KCategorizedView::rowsInserted(const QModelIndex &parent, 01419 int start, 01420 int end) 01421 { 01422 QListView::rowsInserted(parent, start, end); 01423 if (!d->isCategorized()) { 01424 return; 01425 } 01426 01427 *d->hoveredBlock = Private::Block(); 01428 d->hoveredCategory = QString(); 01429 d->rowsInserted(parent, start, end); 01430 } 01431 01432 #ifndef KDE_NO_DEPRECATED 01433 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent, 01434 int start, 01435 int end) 01436 { 01437 Q_UNUSED(parent); 01438 Q_UNUSED(start); 01439 Q_UNUSED(end); 01440 } 01441 #endif 01442 01443 #ifndef KDE_NO_DEPRECATED 01444 void KCategorizedView::rowsRemoved(const QModelIndex &parent, 01445 int start, 01446 int end) 01447 { 01448 Q_UNUSED(parent); 01449 Q_UNUSED(start); 01450 Q_UNUSED(end); 01451 } 01452 #endif 01453 01454 void KCategorizedView::slotLayoutChanged() 01455 { 01456 if (!d->isCategorized()) { 01457 return; 01458 } 01459 01460 d->blocks.clear(); 01461 *d->hoveredBlock = Private::Block(); 01462 d->hoveredCategory = QString(); 01463 if (d->proxyModel->rowCount()) { 01464 d->rowsInserted(rootIndex(), 0, d->proxyModel->rowCount() - 1); 01465 } 01466 } 01467 01468 //END: Public part 01469 01470 #include "kcategorizedview.moc"
KDE 4.6 API Reference