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 if (q->layoutDirection() == Qt::LeftToRight) { 00409 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * q->gridSize().width() + blockPos.x() + categoryDrawer->leftMargin(); 00410 } else { 00411 item.topLeft.rx() = viewportWidth() - ((relativeRow % maxItemsPerRow) + 1) * q->gridSize().width() + categoryDrawer->leftMargin() + categorySpacing; 00412 } 00413 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * q->gridSize().height(); 00414 } else { 00415 if (q->uniformItemSizes()) { 00416 const int relativeRow = index.row() - firstIndexRow; 00417 const QSize itemSize = q->sizeHintForIndex(index); 00418 const int maxItemsPerRow = qMax((viewportWidth() - q->spacing()) / (itemSize.width() + q->spacing()), 1); 00419 if (q->layoutDirection() == Qt::LeftToRight) { 00420 item.topLeft.rx() = (relativeRow % maxItemsPerRow) * itemSize.width() + blockPos.x() + categoryDrawer->leftMargin(); 00421 } else { 00422 item.topLeft.rx() = viewportWidth() - (relativeRow % maxItemsPerRow) * itemSize.width() + categoryDrawer->leftMargin() + categorySpacing; 00423 } 00424 item.topLeft.ry() = (relativeRow / maxItemsPerRow) * itemSize.height(); 00425 } else { 00426 const QSize currSize = q->sizeHintForIndex(index); 00427 if (index != block.firstIndex) { 00428 const int viewportW = viewportWidth() - q->spacing(); 00429 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex()); 00430 QRect prevRect = q->visualRect(prevIndex); 00431 prevRect = mapFromViewport(prevRect); 00432 if ((prevRect.bottomRight().x() + 1) + currSize.width() - blockPos.x() + q->spacing() > viewportW) { 00433 // we have to check the whole previous row, and see which one was the 00434 // highest. 00435 Q_FOREVER { 00436 prevIndex = proxyModel->index(prevIndex.row() - 1, q->modelColumn(), q->rootIndex()); 00437 const QRect tempRect = q->visualRect(prevIndex); 00438 if (tempRect.topLeft().y() < prevRect.topLeft().y()) { 00439 break; 00440 } 00441 if (tempRect.bottomRight().y() > prevRect.bottomRight().y()) { 00442 prevRect = tempRect; 00443 } 00444 if (prevIndex == block.firstIndex) { 00445 break; 00446 } 00447 } 00448 if (q->layoutDirection() == Qt::LeftToRight) { 00449 item.topLeft.rx() = categoryDrawer->leftMargin() + blockPos.x() + q->spacing(); 00450 } else { 00451 item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; 00452 } 00453 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y(); 00454 } else { 00455 if (q->layoutDirection() == Qt::LeftToRight) { 00456 item.topLeft.rx() = (prevRect.bottomRight().x() + 1) + q->spacing(); 00457 } else { 00458 item.topLeft.rx() = (prevRect.bottomLeft().x() - 1) - q->spacing() - item.size.width() + categoryDrawer->leftMargin() + categorySpacing; 00459 } 00460 item.topLeft.ry() = prevRect.topLeft().y() - blockPos.y(); 00461 } 00462 } else { 00463 if (q->layoutDirection() == Qt::LeftToRight) { 00464 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); 00465 } else { 00466 item.topLeft.rx() = viewportWidth() - currSize.width() + categoryDrawer->leftMargin() + categorySpacing; 00467 } 00468 item.topLeft.ry() = q->spacing(); 00469 } 00470 } 00471 } 00472 item.size = q->sizeHintForIndex(index); 00473 } 00474 00475 void KCategorizedView::Private::topToBottomVisualRect(const QModelIndex &index, Item &item, 00476 const Block &block, const QPoint &blockPos) const 00477 { 00478 const int firstIndexRow = block.firstIndex.row(); 00479 00480 if (hasGrid()) { 00481 const int relativeRow = index.row() - firstIndexRow; 00482 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); 00483 item.topLeft.ry() = relativeRow * q->gridSize().height(); 00484 } else { 00485 if (q->uniformItemSizes()) { 00486 const int relativeRow = index.row() - firstIndexRow; 00487 const QSize itemSize = q->sizeHintForIndex(index); 00488 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin(); 00489 item.topLeft.ry() = relativeRow * itemSize.height(); 00490 } else { 00491 if (index != block.firstIndex) { 00492 QModelIndex prevIndex = proxyModel->index(index.row() - 1, q->modelColumn(), q->rootIndex()); 00493 QRect prevRect = q->visualRect(prevIndex); 00494 prevRect = mapFromViewport(prevRect); 00495 const QSize currSize = q->sizeHintForIndex(index); 00496 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); 00497 item.topLeft.ry() = (prevRect.bottomRight().y() + 1) + q->spacing() - blockPos.y(); 00498 } else { 00499 item.topLeft.rx() = blockPos.x() + categoryDrawer->leftMargin() + q->spacing(); 00500 item.topLeft.ry() = q->spacing(); 00501 } 00502 } 00503 } 00504 item.size = q->sizeHintForIndex(index); 00505 item.size.setWidth(viewportWidth()); 00506 } 00507 00508 void KCategorizedView::Private::_k_slotCollapseOrExpandClicked(QModelIndex) 00509 { 00510 } 00511 00512 //END: Private part 00513 00514 //BEGIN: Public part 00515 00516 KCategorizedView::KCategorizedView(QWidget *parent) 00517 : QListView(parent) 00518 , d(new Private(this)) 00519 { 00520 } 00521 00522 KCategorizedView::~KCategorizedView() 00523 { 00524 delete d; 00525 } 00526 00527 void KCategorizedView::setModel(QAbstractItemModel *model) 00528 { 00529 if (d->proxyModel == model) { 00530 return; 00531 } 00532 00533 d->blocks.clear(); 00534 00535 if (d->proxyModel) { 00536 disconnect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged())); 00537 } 00538 00539 d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*>(model); 00540 00541 if (d->proxyModel) { 00542 connect(d->proxyModel, SIGNAL(layoutChanged()), this, SLOT(slotLayoutChanged())); 00543 } 00544 00545 QListView::setModel(model); 00546 00547 // if the model already had information inserted, update our data structures to it 00548 if (model->rowCount()) { 00549 slotLayoutChanged(); 00550 } 00551 } 00552 00553 void KCategorizedView::setGridSize(const QSize &size) 00554 { 00555 setGridSizeOwn(size); 00556 } 00557 00558 void KCategorizedView::setGridSizeOwn(const QSize &size) 00559 { 00560 d->regenerateAllElements(); 00561 QListView::setGridSize(size); 00562 } 00563 00564 QRect KCategorizedView::visualRect(const QModelIndex &index) const 00565 { 00566 if (!d->isCategorized()) { 00567 return QListView::visualRect(index); 00568 } 00569 00570 if (!index.isValid()) { 00571 return QRect(); 00572 } 00573 00574 const QString category = d->categoryForIndex(index); 00575 00576 if (!d->blocks.contains(category)) { 00577 return QRect(); 00578 } 00579 00580 Private::Block &block = d->blocks[category]; 00581 const int firstIndexRow = block.firstIndex.row(); 00582 00583 Q_ASSERT(block.firstIndex.isValid()); 00584 00585 if (index.row() - firstIndexRow < 0 || index.row() - firstIndexRow >= block.items.count()) { 00586 return QRect(); 00587 } 00588 00589 const QPoint blockPos = d->blockPosition(category); 00590 00591 Private::Item &ritem = block.items[index.row() - firstIndexRow]; 00592 00593 if (ritem.topLeft.isNull() || (block.quarantineStart.isValid() && 00594 index.row() >= block.quarantineStart.row())) { 00595 if (flow() == LeftToRight) { 00596 d->leftToRightVisualRect(index, ritem, block, blockPos); 00597 } else { 00598 d->topToBottomVisualRect(index, ritem, block, blockPos); 00599 } 00600 00601 //BEGIN: update the quarantine start 00602 const bool wasLastIndex = (index.row() == (block.firstIndex.row() + block.items.count() - 1)); 00603 if (index.row() == block.quarantineStart.row()) { 00604 if (wasLastIndex) { 00605 block.quarantineStart = QModelIndex(); 00606 } else { 00607 const QModelIndex nextIndex = d->proxyModel->index(index.row() + 1, modelColumn(), rootIndex()); 00608 block.quarantineStart = nextIndex; 00609 } 00610 } 00611 //END: update the quarantine start 00612 } 00613 00614 // we get now the absolute position through the relative position of the parent block. do not 00615 // save this on ritem, since this would override the item relative position in block terms. 00616 Private::Item item(ritem); 00617 item.topLeft.ry() += blockPos.y(); 00618 00619 const QSize sizeHint = item.size; 00620 00621 if (d->hasGrid()) { 00622 const QSize sizeGrid = gridSize(); 00623 const QSize resultingSize = sizeHint.boundedTo(sizeGrid); 00624 QRect res(item.topLeft.x() + ((sizeGrid.width() - resultingSize.width()) / 2), 00625 item.topLeft.y(), resultingSize.width(), resultingSize.height()); 00626 if (block.collapsed) { 00627 // we can still do binary search, while we "hide" items. We move those items in collapsed 00628 // blocks to the left and set a 0 height. 00629 res.setLeft(-resultingSize.width()); 00630 res.setHeight(0); 00631 } 00632 return d->mapToViewport(res); 00633 } 00634 00635 QRect res(item.topLeft.x(), item.topLeft.y(), sizeHint.width(), sizeHint.height()); 00636 if (block.collapsed) { 00637 // we can still do binary search, while we "hide" items. We move those items in collapsed 00638 // blocks to the left and set a 0 height. 00639 res.setLeft(-sizeHint.width()); 00640 res.setHeight(0); 00641 } 00642 return d->mapToViewport(res); 00643 } 00644 00645 KCategoryDrawer *KCategorizedView::categoryDrawer() const 00646 { 00647 return d->categoryDrawer; 00648 } 00649 00650 void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer) 00651 { 00652 if (d->categoryDrawerV2) { 00653 disconnect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)), 00654 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex))); 00655 } 00656 00657 d->categoryDrawer = categoryDrawer; 00658 d->categoryDrawerV2 = dynamic_cast<KCategoryDrawerV2*>(categoryDrawer); 00659 d->categoryDrawerV3 = dynamic_cast<KCategoryDrawerV3*>(categoryDrawer); 00660 00661 if (d->categoryDrawerV2) { 00662 connect(d->categoryDrawerV2, SIGNAL(collapseOrExpandClicked(QModelIndex)), 00663 this, SLOT(_k_slotCollapseOrExpandClicked(QModelIndex))); 00664 } 00665 } 00666 00667 int KCategorizedView::categorySpacing() const 00668 { 00669 return d->categorySpacing; 00670 } 00671 00672 void KCategorizedView::setCategorySpacing(int categorySpacing) 00673 { 00674 if (d->categorySpacing == categorySpacing) { 00675 return; 00676 } 00677 00678 d->categorySpacing = categorySpacing; 00679 00680 for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { 00681 Private::Block &block = *it; 00682 block.outOfQuarantine = false; 00683 } 00684 } 00685 00686 bool KCategorizedView::alternatingBlockColors() const 00687 { 00688 return d->alternatingBlockColors; 00689 } 00690 00691 void KCategorizedView::setAlternatingBlockColors(bool enable) 00692 { 00693 d->alternatingBlockColors = enable; 00694 } 00695 00696 bool KCategorizedView::collapsibleBlocks() const 00697 { 00698 return d->collapsibleBlocks; 00699 } 00700 00701 void KCategorizedView::setCollapsibleBlocks(bool enable) 00702 { 00703 d->collapsibleBlocks = enable; 00704 } 00705 00706 QModelIndexList KCategorizedView::block(const QString &category) 00707 { 00708 QModelIndexList res; 00709 const Private::Block &block = d->blocks[category]; 00710 if (block.height == -1) { 00711 return res; 00712 } 00713 QModelIndex current = block.firstIndex; 00714 const int first = current.row(); 00715 for (int i = 1; i <= block.items.count(); ++i) { 00716 if (current.isValid()) { 00717 res << current; 00718 } 00719 current = d->proxyModel->index(first + i, modelColumn(), rootIndex()); 00720 } 00721 return res; 00722 } 00723 00724 QModelIndexList KCategorizedView::block(const QModelIndex &representative) 00725 { 00726 return block(representative.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString()); 00727 } 00728 00729 QModelIndex KCategorizedView::indexAt(const QPoint &point) const 00730 { 00731 if (!d->isCategorized()) { 00732 return QListView::indexAt(point); 00733 } 00734 00735 const int rowCount = d->proxyModel->rowCount(); 00736 if (!rowCount) { 00737 return QModelIndex(); 00738 } 00739 00740 // Binary search that will try to spot if there is an index under point 00741 int bottom = 0; 00742 int top = rowCount - 1; 00743 while (bottom <= top) { 00744 const int middle = (bottom + top) / 2; 00745 const QModelIndex index = d->proxyModel->index(middle, modelColumn(), rootIndex()); 00746 QRect rect = visualRect(index); 00747 const int verticalOff = verticalOffset(); 00748 int horizontalOff = horizontalOffset(); 00749 if (layoutDirection() == Qt::RightToLeft) { 00750 horizontalOff *= -1; 00751 } 00752 rect.topLeft().ry() += verticalOff; 00753 rect.topLeft().rx() += horizontalOff; 00754 rect.bottomRight().ry() += verticalOff; 00755 rect.bottomRight().rx() += horizontalOff; 00756 if (rect.contains(point)) { 00757 if (index.model()->flags(index) & Qt::ItemIsEnabled) { 00758 return index; 00759 } 00760 return QModelIndex(); 00761 } 00762 bool directionCondition; 00763 if (layoutDirection() == Qt::LeftToRight) { 00764 directionCondition = point.x() > rect.bottomRight().x(); 00765 } else { 00766 directionCondition = point.x() < rect.bottomLeft().x(); 00767 } 00768 if (point.y() > rect.bottomRight().y() || 00769 (point.y() > rect.topLeft().y() && point.y() < rect.bottomRight().y() && directionCondition)) { 00770 bottom = middle + 1; 00771 } else { 00772 top = middle - 1; 00773 } 00774 } 00775 return QModelIndex(); 00776 } 00777 00778 void KCategorizedView::reset() 00779 { 00780 d->blocks.clear(); 00781 QListView::reset(); 00782 } 00783 00784 void KCategorizedView::paintEvent(QPaintEvent *event) 00785 { 00786 if (!d->isCategorized()) { 00787 QListView::paintEvent(event); 00788 return; 00789 } 00790 00791 const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(viewport()->rect().intersected(event->rect())); 00792 00793 QPainter p(viewport()); 00794 p.save(); 00795 00796 Q_ASSERT(selectionModel()->model() == d->proxyModel); 00797 00798 //BEGIN: draw categories 00799 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 00800 while (it != d->blocks.constEnd()) { 00801 const Private::Block &block = *it; 00802 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00803 QStyleOptionViewItemV4 option(viewOptions()); 00804 option.features |= d->alternatingBlockColors && block.alternate ? QStyleOptionViewItemV4::Alternate 00805 : QStyleOptionViewItemV4::None; 00806 option.state |= !d->collapsibleBlocks || !block.collapsed ? QStyle::State_Open 00807 : QStyle::State_None; 00808 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option); 00809 QPoint pos = d->blockPosition(it.key()); 00810 pos.ry() -= height; 00811 option.rect.setTopLeft(pos); 00812 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin()); 00813 option.rect.setHeight(height + d->blockHeight(it.key())); 00814 option.rect = d->mapToViewport(option.rect); 00815 if (!option.rect.intersects(viewport()->rect())) { 00816 ++it; 00817 continue; 00818 } 00819 d->categoryDrawer->drawCategory(categoryIndex, d->proxyModel->sortRole(), option, &p); 00820 ++it; 00821 } 00822 //END: draw categories 00823 00824 if (intersecting.first.isValid() && intersecting.second.isValid()) { 00825 //BEGIN: draw items 00826 int i = intersecting.first.row(); 00827 int indexToCheckIfBlockCollapsed = i; 00828 QModelIndex categoryIndex; 00829 QString category; 00830 Private::Block *block = 0; 00831 while (i <= intersecting.second.row()) { 00832 //BEGIN: first check if the block is collapsed. if so, we have to skip the item painting 00833 if (i == indexToCheckIfBlockCollapsed) { 00834 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex()); 00835 category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 00836 block = &d->blocks[category]; 00837 indexToCheckIfBlockCollapsed = block->firstIndex.row() + block->items.count(); 00838 if (block->collapsed) { 00839 i = indexToCheckIfBlockCollapsed; 00840 continue; 00841 } 00842 } 00843 //END: first check if the block is collapsed. if so, we have to skip the item painting 00844 00845 Q_ASSERT(block); 00846 00847 const bool alternateItem = (i - block->firstIndex.row()) % 2; 00848 00849 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex()); 00850 const Qt::ItemFlags flags = d->proxyModel->flags(index); 00851 QStyleOptionViewItemV4 option(viewOptions()); 00852 option.rect = visualRect(index); 00853 option.widget = this; 00854 option.features |= wordWrap() ? QStyleOptionViewItemV2::WrapText 00855 : QStyleOptionViewItemV2::None; 00856 option.features |= alternatingRowColors() && alternateItem ? QStyleOptionViewItemV4::Alternate 00857 : QStyleOptionViewItemV4::None; 00858 if (flags & Qt::ItemIsSelectable) { 00859 option.state |= selectionModel()->isSelected(index) ? QStyle::State_Selected 00860 : QStyle::State_None; 00861 } else { 00862 option.state &= ~QStyle::State_Selected; 00863 } 00864 option.state |= (index == currentIndex()) ? QStyle::State_HasFocus 00865 : QStyle::State_None; 00866 if (!(flags & Qt::ItemIsEnabled)) { 00867 option.state &= ~QStyle::State_Enabled; 00868 } else { 00869 option.state |= (index == d->hoveredIndex) ? QStyle::State_MouseOver 00870 : QStyle::State_None; 00871 } 00872 00873 itemDelegate(index)->paint(&p, option, index); 00874 ++i; 00875 } 00876 //END: draw items 00877 } 00878 00879 //BEGIN: draw selection rect 00880 if (isSelectionRectVisible() && d->rubberBandRect.isValid()) { 00881 QStyleOptionRubberBand opt; 00882 opt.initFrom(this); 00883 opt.shape = QRubberBand::Rectangle; 00884 opt.opaque = false; 00885 opt.rect = d->mapToViewport(d->rubberBandRect).intersected(viewport()->rect().adjusted(-16, -16, 16, 16)); 00886 p.save(); 00887 style()->drawControl(QStyle::CE_RubberBand, &opt, &p); 00888 p.restore(); 00889 } 00890 //END: draw selection rect 00891 00892 p.restore(); 00893 } 00894 00895 void KCategorizedView::resizeEvent(QResizeEvent *event) 00896 { 00897 d->regenerateAllElements(); 00898 QListView::resizeEvent(event); 00899 } 00900 00901 void KCategorizedView::setSelection(const QRect &rect, 00902 QItemSelectionModel::SelectionFlags flags) 00903 { 00904 if (!d->isCategorized()) { 00905 QListView::setSelection(rect, flags); 00906 return; 00907 } 00908 00909 if (rect.topLeft() == rect.bottomRight()) { 00910 const QModelIndex index = indexAt(rect.topLeft()); 00911 selectionModel()->select(index, flags); 00912 return; 00913 } 00914 00915 const QPair<QModelIndex, QModelIndex> intersecting = d->intersectingIndexesWithRect(rect); 00916 00917 QItemSelection selection; 00918 00919 //TODO: think of a faster implementation 00920 QModelIndex firstIndex; 00921 QModelIndex lastIndex; 00922 for (int i = intersecting.first.row(); i <= intersecting.second.row(); ++i) { 00923 const QModelIndex index = d->proxyModel->index(i, modelColumn(), rootIndex()); 00924 const bool visualRectIntersects = visualRect(index).intersects(rect); 00925 if (firstIndex.isValid()) { 00926 if (visualRectIntersects) { 00927 lastIndex = index; 00928 } else { 00929 selection << QItemSelectionRange(firstIndex, lastIndex); 00930 firstIndex = QModelIndex(); 00931 } 00932 } else if (visualRectIntersects) { 00933 firstIndex = index; 00934 lastIndex = index; 00935 } 00936 } 00937 00938 if (firstIndex.isValid()) { 00939 selection << QItemSelectionRange(firstIndex, lastIndex); 00940 } 00941 00942 selectionModel()->select(selection, flags); 00943 } 00944 00945 void KCategorizedView::mouseMoveEvent(QMouseEvent *event) 00946 { 00947 QListView::mouseMoveEvent(event); 00948 d->hoveredIndex = indexAt(event->pos()); 00949 const SelectionMode itemViewSelectionMode = selectionMode(); 00950 if (state() == DragSelectingState && isSelectionRectVisible() && itemViewSelectionMode != SingleSelection 00951 && itemViewSelectionMode != NoSelection) { 00952 QRect rect(d->pressedPosition, event->pos() + QPoint(horizontalOffset(), verticalOffset())); 00953 rect = rect.normalized(); 00954 update(rect.united(d->rubberBandRect)); 00955 d->rubberBandRect = rect; 00956 } 00957 if (!d->categoryDrawerV2) { 00958 return; 00959 } 00960 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 00961 while (it != d->blocks.constEnd()) { 00962 const Private::Block &block = *it; 00963 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00964 QStyleOptionViewItemV4 option(viewOptions()); 00965 const int height = d->categoryDrawer->categoryHeight(categoryIndex, option); 00966 QPoint pos = d->blockPosition(it.key()); 00967 pos.ry() -= height; 00968 option.rect.setTopLeft(pos); 00969 option.rect.setWidth(d->viewportWidth() + d->categoryDrawer->leftMargin() + d->categoryDrawer->rightMargin()); 00970 option.rect.setHeight(height + d->blockHeight(it.key())); 00971 option.rect = d->mapToViewport(option.rect); 00972 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos()); 00973 if (option.rect.contains(mousePos)) { 00974 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1 && *d->hoveredBlock != block) { 00975 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00976 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 00977 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect); 00978 *d->hoveredBlock = block; 00979 d->hoveredCategory = it.key(); 00980 viewport()->update(option.rect); 00981 } else if (d->hoveredBlock->height == -1) { 00982 *d->hoveredBlock = block; 00983 d->hoveredCategory = it.key(); 00984 } else if (d->categoryDrawerV3) { 00985 d->categoryDrawerV3->mouseMoved(categoryIndex, option.rect, event); 00986 } else { 00987 d->categoryDrawerV2->mouseButtonMoved(categoryIndex, event); 00988 } 00989 viewport()->update(option.rect); 00990 return; 00991 } 00992 ++it; 00993 } 00994 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) { 00995 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 00996 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 00997 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect); 00998 *d->hoveredBlock = Private::Block(); 00999 d->hoveredCategory = QString(); 01000 viewport()->update(option.rect); 01001 } 01002 } 01003 01004 void KCategorizedView::mousePressEvent(QMouseEvent *event) 01005 { 01006 if (event->button() == Qt::LeftButton) { 01007 d->pressedPosition = event->pos(); 01008 d->pressedPosition.rx() += horizontalOffset(); 01009 d->pressedPosition.ry() += verticalOffset(); 01010 } 01011 if (!d->categoryDrawerV2) { 01012 QListView::mousePressEvent(event); 01013 return; 01014 } 01015 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 01016 while (it != d->blocks.constEnd()) { 01017 const Private::Block &block = *it; 01018 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 01019 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 01020 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos()); 01021 if (option.rect.contains(mousePos)) { 01022 if (d->categoryDrawerV3) { 01023 d->categoryDrawerV3->mouseButtonPressed(categoryIndex, option.rect, event); 01024 } else { 01025 d->categoryDrawerV2->mouseButtonPressed(categoryIndex, event); 01026 } 01027 viewport()->update(option.rect); 01028 if (!event->isAccepted()) { 01029 QListView::mousePressEvent(event); 01030 } 01031 return; 01032 } 01033 ++it; 01034 } 01035 QListView::mousePressEvent(event); 01036 } 01037 01038 void KCategorizedView::mouseReleaseEvent(QMouseEvent *event) 01039 { 01040 d->pressedPosition = QPoint(); 01041 d->rubberBandRect = QRect(); 01042 if (!d->categoryDrawerV2) { 01043 QListView::mouseReleaseEvent(event); 01044 return; 01045 } 01046 QHash<QString, Private::Block>::ConstIterator it(d->blocks.constBegin()); 01047 while (it != d->blocks.constEnd()) { 01048 const Private::Block &block = *it; 01049 const QModelIndex categoryIndex = d->proxyModel->index(block.firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 01050 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 01051 const QPoint mousePos = viewport()->mapFromGlobal(QCursor::pos()); 01052 if (option.rect.contains(mousePos)) { 01053 if (d->categoryDrawerV3) { 01054 d->categoryDrawerV3->mouseButtonReleased(categoryIndex, option.rect, event); 01055 } else { 01056 d->categoryDrawerV2->mouseButtonReleased(categoryIndex, event); 01057 } 01058 viewport()->update(option.rect); 01059 if (!event->isAccepted()) { 01060 QListView::mouseReleaseEvent(event); 01061 } 01062 return; 01063 } 01064 ++it; 01065 } 01066 QListView::mouseReleaseEvent(event); 01067 } 01068 01069 void KCategorizedView::leaveEvent(QEvent *event) 01070 { 01071 QListView::leaveEvent(event); 01072 if (d->hoveredIndex.isValid()) { 01073 viewport()->update(visualRect(d->hoveredIndex)); 01074 d->hoveredIndex = QModelIndex(); 01075 } 01076 if (d->categoryDrawerV3 && d->hoveredBlock->height != -1) { 01077 const QModelIndex categoryIndex = d->proxyModel->index(d->hoveredBlock->firstIndex.row(), d->proxyModel->sortColumn(), rootIndex()); 01078 const QStyleOptionViewItemV4 option = d->blockRect(categoryIndex); 01079 d->categoryDrawerV3->mouseLeft(categoryIndex, option.rect); 01080 *d->hoveredBlock = Private::Block(); 01081 d->hoveredCategory = QString(); 01082 viewport()->update(option.rect); 01083 } 01084 } 01085 01086 void KCategorizedView::startDrag(Qt::DropActions supportedActions) 01087 { 01088 QListView::startDrag(supportedActions); 01089 } 01090 01091 void KCategorizedView::dragMoveEvent(QDragMoveEvent *event) 01092 { 01093 QListView::dragMoveEvent(event); 01094 d->hoveredIndex = indexAt(event->pos()); 01095 } 01096 01097 void KCategorizedView::dragEnterEvent(QDragEnterEvent *event) 01098 { 01099 QListView::dragEnterEvent(event); 01100 } 01101 01102 void KCategorizedView::dragLeaveEvent(QDragLeaveEvent *event) 01103 { 01104 QListView::dragLeaveEvent(event); 01105 } 01106 01107 void KCategorizedView::dropEvent(QDropEvent *event) 01108 { 01109 QListView::dropEvent(event); 01110 } 01111 01112 //TODO: improve se we take into account collapsed blocks 01113 //TODO: take into account when there is no grid and no uniformItemSizes 01114 QModelIndex KCategorizedView::moveCursor(CursorAction cursorAction, 01115 Qt::KeyboardModifiers modifiers) 01116 { 01117 if (!d->isCategorized()) { 01118 return QListView::moveCursor(cursorAction, modifiers); 01119 } 01120 01121 const QModelIndex current = currentIndex(); 01122 const QRect currentRect = visualRect(current); 01123 if (!current.isValid()) { 01124 const int rowCount = d->proxyModel->rowCount(rootIndex()); 01125 if (!rowCount) { 01126 return QModelIndex(); 01127 } 01128 return d->proxyModel->index(0, modelColumn(), rootIndex()); 01129 } 01130 01131 switch (cursorAction) { 01132 case MoveLeft: { 01133 if (!current.row()) { 01134 return QModelIndex(); 01135 } 01136 const QModelIndex previous = d->proxyModel->index(current.row() - 1, modelColumn(), rootIndex()); 01137 const QRect previousRect = visualRect(previous); 01138 if (previousRect.top() == currentRect.top()) { 01139 return previous; 01140 } 01141 01142 return QModelIndex(); 01143 } 01144 case MoveRight: { 01145 if (current.row() == d->proxyModel->rowCount() - 1) { 01146 return QModelIndex(); 01147 } 01148 const QModelIndex next = d->proxyModel->index(current.row() + 1, modelColumn(), rootIndex()); 01149 const QRect nextRect = visualRect(next); 01150 if (nextRect.top() == currentRect.top()) { 01151 return next; 01152 } 01153 01154 return QModelIndex(); 01155 } 01156 case MoveDown: { 01157 if (d->hasGrid() || uniformItemSizes()) { 01158 const QModelIndex current = currentIndex(); 01159 const QSize itemSize = d->hasGrid() ? gridSize() 01160 : sizeHintForIndex(current); 01161 const Private::Block &block = d->blocks[d->categoryForIndex(current)]; 01162 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1); 01163 const bool canMove = current.row() + maxItemsPerRow < block.firstIndex.row() + 01164 block.items.count(); 01165 01166 if (canMove) { 01167 return d->proxyModel->index(current.row() + maxItemsPerRow, modelColumn(), rootIndex()); 01168 } 01169 01170 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow; 01171 const QModelIndex nextIndex = d->proxyModel->index(block.firstIndex.row() + block.items.count(), modelColumn(), rootIndex()); 01172 01173 if (!nextIndex.isValid()) { 01174 return QModelIndex(); 01175 } 01176 01177 const Private::Block &nextBlock = d->blocks[d->categoryForIndex(nextIndex)]; 01178 01179 if (nextBlock.items.count() <= currentRelativePos) { 01180 return QModelIndex(); 01181 } 01182 01183 if (currentRelativePos < (block.items.count() % maxItemsPerRow)) { 01184 return d->proxyModel->index(nextBlock.firstIndex.row() + currentRelativePos, modelColumn(), rootIndex()); 01185 } 01186 01187 return QModelIndex(); 01188 } 01189 } 01190 case MoveUp: { 01191 if (d->hasGrid() || uniformItemSizes()) { 01192 const QModelIndex current = currentIndex(); 01193 const QSize itemSize = d->hasGrid() ? gridSize() 01194 : sizeHintForIndex(current); 01195 const Private::Block &block = d->blocks[d->categoryForIndex(current)]; 01196 const int maxItemsPerRow = qMax(d->viewportWidth() / itemSize.width(), 1); 01197 const bool canMove = current.row() - maxItemsPerRow >= block.firstIndex.row(); 01198 01199 if (canMove) { 01200 return d->proxyModel->index(current.row() - maxItemsPerRow, modelColumn(), rootIndex()); 01201 } 01202 01203 const int currentRelativePos = (current.row() - block.firstIndex.row()) % maxItemsPerRow; 01204 const QModelIndex prevIndex = d->proxyModel->index(block.firstIndex.row() - 1, modelColumn(), rootIndex()); 01205 01206 if (!prevIndex.isValid()) { 01207 return QModelIndex(); 01208 } 01209 01210 const Private::Block &prevBlock = d->blocks[d->categoryForIndex(prevIndex)]; 01211 01212 if (prevBlock.items.count() <= currentRelativePos) { 01213 return QModelIndex(); 01214 } 01215 01216 const int remainder = prevBlock.items.count() % maxItemsPerRow; 01217 if (currentRelativePos < remainder) { 01218 return d->proxyModel->index(prevBlock.firstIndex.row() + prevBlock.items.count() - remainder + currentRelativePos, modelColumn(), rootIndex()); 01219 } 01220 01221 return QModelIndex(); 01222 } 01223 } 01224 default: 01225 break; 01226 } 01227 01228 return QModelIndex(); 01229 } 01230 01231 void KCategorizedView::rowsAboutToBeRemoved(const QModelIndex &parent, 01232 int start, 01233 int end) 01234 { 01235 if (!d->isCategorized()) { 01236 QListView::rowsAboutToBeRemoved(parent, start, end); 01237 return; 01238 } 01239 01240 *d->hoveredBlock = Private::Block(); 01241 d->hoveredCategory = QString(); 01242 01243 if (end - start + 1 == d->proxyModel->rowCount()) { 01244 d->blocks.clear(); 01245 QListView::rowsAboutToBeRemoved(parent, start, end); 01246 return; 01247 } 01248 01249 // Removal feels a bit more complicated than insertion. Basically we can consider there are 01250 // 3 different cases when going to remove items. (*) represents an item, Items between ([) and 01251 // (]) are the ones which are marked for removal. 01252 // 01253 // - 1st case: 01254 // ... * * * * * * [ * * * ... 01255 // 01256 // The items marked for removal are the last part of this category. No need to mark any item 01257 // of this category as in quarantine, because no special offset will be pushed to items at 01258 // the right because of any changes (since the removed items are those on the right most part 01259 // of the category). 01260 // 01261 // - 2nd case: 01262 // ... * * * * * * ] * * * ... 01263 // 01264 // The items marked for removal are the first part of this category. We have to mark as in 01265 // quarantine all items in this category. Absolutely all. All items will have to be moved to 01266 // the left (or moving up, because rows got a different offset). 01267 // 01268 // - 3rd case: 01269 // ... * * [ * * * * ] * * ... 01270 // 01271 // The items marked for removal are in between of this category. We have to mark as in 01272 // quarantine only those items that are at the right of the end of the removal interval, 01273 // (starting on "]"). 01274 // 01275 // It hasn't been explicitly said, but when we remove, we have to mark all blocks that are 01276 // located under the top most affected category as in quarantine (the block itself, as a whole), 01277 // because such a change can force it to have a different offset (note that items themselves 01278 // contain relative positions to the block, so marking the block as in quarantine is enough). 01279 // 01280 // Also note that removal implicitly means that we have to update correctly firstIndex of each 01281 // block, and in general keep updated the internal information of elements. 01282 01283 QStringList listOfCategoriesMarkedForRemoval; 01284 01285 QString lastCategory; 01286 int alreadyRemoved = 0; 01287 for (int i = start; i <= end; ++i) { 01288 const QModelIndex index = d->proxyModel->index(i, modelColumn(), parent); 01289 01290 Q_ASSERT(index.isValid()); 01291 01292 const QString category = d->categoryForIndex(index); 01293 01294 if (lastCategory != category) { 01295 lastCategory = category; 01296 alreadyRemoved = 0; 01297 } 01298 01299 Private::Block &block = d->blocks[category]; 01300 block.items.removeAt(i - block.firstIndex.row() - alreadyRemoved); 01301 ++alreadyRemoved; 01302 01303 if (!block.items.count()) { 01304 listOfCategoriesMarkedForRemoval << category; 01305 } 01306 01307 block.height = -1; 01308 01309 viewport()->update(); 01310 } 01311 01312 //BEGIN: update the items that are in quarantine in affected categories 01313 { 01314 const QModelIndex lastIndex = d->proxyModel->index(end, modelColumn(), parent); 01315 const QString category = d->categoryForIndex(lastIndex); 01316 Private::Block &block = d->blocks[category]; 01317 if (block.items.count() && start <= block.firstIndex.row() && end >= block.firstIndex.row()) { 01318 block.firstIndex = d->proxyModel->index(end + 1, modelColumn(), parent); 01319 } 01320 block.quarantineStart = block.firstIndex; 01321 } 01322 //END: update the items that are in quarantine in affected categories 01323 01324 Q_FOREACH (const QString &category, listOfCategoriesMarkedForRemoval) { 01325 d->blocks.remove(category); 01326 } 01327 01328 //BEGIN: mark as in quarantine those categories that are under the affected ones 01329 { 01330 //BEGIN: order for marking as alternate those blocks that are alternate 01331 QList<Private::Block> blockList = d->blocks.values(); 01332 qSort(blockList.begin(), blockList.end(), Private::Block::lessThan); 01333 QList<int> firstIndexesRows; 01334 foreach (const Private::Block &block, blockList) { 01335 firstIndexesRows << block.firstIndex.row(); 01336 } 01337 //END: order for marking as alternate those blocks that are alternate 01338 for (QHash<QString, Private::Block>::Iterator it = d->blocks.begin(); it != d->blocks.end(); ++it) { 01339 Private::Block &block = *it; 01340 if (block.firstIndex.row() > start) { 01341 block.outOfQuarantine = false; 01342 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2; 01343 } else if (block.firstIndex.row() == start) { 01344 block.alternate = firstIndexesRows.indexOf(block.firstIndex.row()) % 2; 01345 } 01346 } 01347 } 01348 //END: mark as in quarantine those categories that are under the affected ones 01349 01350 QListView::rowsAboutToBeRemoved(parent, start, end); 01351 } 01352 01353 void KCategorizedView::updateGeometries() 01354 { 01355 const int oldVerticalOffset = verticalOffset(); 01356 01357 QListView::updateGeometries(); 01358 01359 if (!d->isCategorized()) { 01360 return; 01361 } 01362 01363 const int rowCount = d->proxyModel->rowCount(); 01364 if (!rowCount) { 01365 verticalScrollBar()->setRange(0, 0); 01366 return; 01367 } 01368 01369 const QModelIndex lastIndex = d->proxyModel->index(rowCount - 1, modelColumn(), rootIndex()); 01370 Q_ASSERT(lastIndex.isValid()); 01371 QRect lastItemRect = visualRect(lastIndex); 01372 01373 if (d->hasGrid()) { 01374 lastItemRect.setSize(lastItemRect.size().expandedTo(gridSize())); 01375 } else { 01376 if (uniformItemSizes()) { 01377 QSize itemSize = sizeHintForIndex(lastIndex); 01378 itemSize.setHeight(itemSize.height() + spacing()); 01379 lastItemRect.setSize(itemSize); 01380 } else { 01381 QSize itemSize = sizeHintForIndex(lastIndex); 01382 const QString category = d->categoryForIndex(lastIndex); 01383 itemSize.setHeight(d->highestElementInLastRow(d->blocks[category]) + spacing()); 01384 lastItemRect.setSize(itemSize); 01385 } 01386 } 01387 01388 const int bottomRange = lastItemRect.bottomRight().y() + verticalOffset() - viewport()->height(); 01389 01390 if (verticalScrollMode() == ScrollPerItem) { 01391 verticalScrollBar()->setSingleStep(lastItemRect.height()); 01392 const int rowsPerPage = qMax(viewport()->height() / lastItemRect.height(), 1); 01393 verticalScrollBar()->setPageStep(rowsPerPage * lastItemRect.height()); 01394 } 01395 01396 verticalScrollBar()->setRange(0, bottomRange); 01397 verticalScrollBar()->setValue(oldVerticalOffset); 01398 01399 //TODO: also consider working with the horizontal scroll bar. since at this level I am not still 01400 // supporting "top to bottom" flow, there is no real problem. If I support that someday 01401 // (think how to draw categories), we would have to take care of the horizontal scroll bar too. 01402 // In theory, as KCategorizedView has been designed, there is no need of horizontal scroll bar. 01403 horizontalScrollBar()->setRange(0, 0); 01404 } 01405 01406 void KCategorizedView::currentChanged(const QModelIndex ¤t, 01407 const QModelIndex &previous) 01408 { 01409 QListView::currentChanged(current, previous); 01410 } 01411 01412 void KCategorizedView::dataChanged(const QModelIndex &topLeft, 01413 const QModelIndex &bottomRight) 01414 { 01415 QListView::dataChanged(topLeft, bottomRight); 01416 if (!d->isCategorized()) { 01417 return; 01418 } 01419 01420 *d->hoveredBlock = Private::Block(); 01421 d->hoveredCategory = QString(); 01422 01423 //BEGIN: since the model changed data, we need to reconsider item sizes 01424 int i = topLeft.row(); 01425 int indexToCheck = i; 01426 QModelIndex categoryIndex; 01427 QString category; 01428 Private::Block *block; 01429 while (i <= bottomRight.row()) { 01430 const QModelIndex currIndex = d->proxyModel->index(i, modelColumn(), rootIndex()); 01431 if (i == indexToCheck) { 01432 categoryIndex = d->proxyModel->index(i, d->proxyModel->sortColumn(), rootIndex()); 01433 category = categoryIndex.data(KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString(); 01434 block = &d->blocks[category]; 01435 block->quarantineStart = currIndex; 01436 indexToCheck = block->firstIndex.row() + block->items.count(); 01437 } 01438 visualRect(currIndex); 01439 ++i; 01440 } 01441 //END: since the model changed data, we need to reconsider item sizes 01442 } 01443 01444 void KCategorizedView::rowsInserted(const QModelIndex &parent, 01445 int start, 01446 int end) 01447 { 01448 QListView::rowsInserted(parent, start, end); 01449 if (!d->isCategorized()) { 01450 return; 01451 } 01452 01453 *d->hoveredBlock = Private::Block(); 01454 d->hoveredCategory = QString(); 01455 d->rowsInserted(parent, start, end); 01456 } 01457 01458 #ifndef KDE_NO_DEPRECATED 01459 void KCategorizedView::rowsInsertedArtifficial(const QModelIndex &parent, 01460 int start, 01461 int end) 01462 { 01463 Q_UNUSED(parent); 01464 Q_UNUSED(start); 01465 Q_UNUSED(end); 01466 } 01467 #endif 01468 01469 #ifndef KDE_NO_DEPRECATED 01470 void KCategorizedView::rowsRemoved(const QModelIndex &parent, 01471 int start, 01472 int end) 01473 { 01474 Q_UNUSED(parent); 01475 Q_UNUSED(start); 01476 Q_UNUSED(end); 01477 } 01478 #endif 01479 01480 void KCategorizedView::slotLayoutChanged() 01481 { 01482 if (!d->isCategorized()) { 01483 return; 01484 } 01485 01486 d->blocks.clear(); 01487 *d->hoveredBlock = Private::Block(); 01488 d->hoveredCategory = QString(); 01489 if (d->proxyModel->rowCount()) { 01490 d->rowsInserted(rootIndex(), 0, d->proxyModel->rowCount() - 1); 01491 } 01492 } 01493 01494 //END: Public part 01495 01496 #include "kcategorizedview.moc"
KDE 4.7 API Reference