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