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

KDE's Doxygen guidelines are available online.

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-4.8.3 API Reference

Skip menu "kdelibs-4.8.3 API Reference"
  • 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
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal