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

KDEUI

kcategorizedview.cpp

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

KDEUI

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal