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

KDEUI

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.5
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