• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KDEUI

kcategorizedview.cpp

Go to the documentation of this file.
00001 
00021 #include "kcategorizedview.h"
00022 #include "kcategorizedview_p.h"
00023 
00024 #include <math.h> // trunc on C99 compliant systems
00025 #include <kdefakes.h> // trunc for not C99 compliant systems
00026 
00027 #include <QPainter>
00028 #include <QScrollBar>
00029 #include <QPaintEvent>
00030 
00031 #include "kcategorydrawer.h"
00032 #include "kcategorizedsortfilterproxymodel.h"
00033 
00034 // By defining DOLPHIN_DRAGANDDROP the custom drag and drop implementation of
00035 // KCategorizedView is bypassed to have a consistent drag and drop look for all
00036 // views. Hopefully transparent pixmaps for drag objects will be supported in
00037 // Qt 4.4, so that this workaround can be skipped.
00038 #define DOLPHIN_DRAGANDDROP
00039 
00040 KCategorizedView::Private::Private(KCategorizedView *listView)
00041     : listView(listView)
00042     , categoryDrawer(0)
00043     , biggestItemSize(QSize(0, 0))
00044     , mouseButtonPressed(false)
00045     , rightMouseButtonPressed(false)
00046     , isDragging(false)
00047     , dragLeftViewport(false)
00048     , proxyModel(0)
00049 {
00050 }
00051 
00052 KCategorizedView::Private::~Private()
00053 {
00054 }
00055 
00056 const QModelIndexList &KCategorizedView::Private::intersectionSet(const QRect &rect)
00057 {
00058     QModelIndex index;
00059     QRect indexVisualRect;
00060 
00061     intersectedIndexes.clear();
00062 
00063     int itemHeight;
00064 
00065     if (listView->gridSize().isEmpty())
00066     {
00067         itemHeight = biggestItemSize.height();
00068     }
00069     else
00070     {
00071         itemHeight = listView->gridSize().height();
00072     }
00073 
00074     // Lets find out where we should start
00075     int top = proxyModel->rowCount() - 1;
00076     int bottom = 0;
00077     int middle = (top + bottom) / 2;
00078     while (bottom <= top)
00079     {
00080         middle = (top + bottom) / 2;
00081 
00082         index = proxyModel->index(middle, 0);
00083         indexVisualRect = visualRect(index);
00084         // We need the whole height (not only the visualRect). This will help us to update
00085         // all needed indexes correctly (ereslibre)
00086         indexVisualRect.setHeight(indexVisualRect.height() + (itemHeight - indexVisualRect.height()));
00087 
00088         if (qMax(indexVisualRect.topLeft().y(),
00089                  indexVisualRect.bottomRight().y()) < qMin(rect.topLeft().y(),
00090                                                            rect.bottomRight().y()))
00091         {
00092             bottom = middle + 1;
00093         }
00094         else
00095         {
00096             top = middle - 1;
00097         }
00098     }
00099 
00100     for (int i = middle; i < proxyModel->rowCount(); i++)
00101     {
00102         index = proxyModel->index(i, 0);
00103         indexVisualRect = visualRect(index);
00104 
00105         if (rect.intersects(indexVisualRect))
00106             intersectedIndexes.append(index);
00107 
00108         // If we passed next item, stop searching for hits
00109         if (qMax(rect.bottomRight().y(), rect.topLeft().y()) <
00110                                                   qMin(indexVisualRect.topLeft().y(),
00111                                                        indexVisualRect.bottomRight().y()))
00112             break;
00113     }
00114 
00115     return intersectedIndexes;
00116 }
00117 
00118 QRect KCategorizedView::Private::visualRectInViewport(const QModelIndex &index) const
00119 {
00120     if (!index.isValid())
00121         return QRect();
00122 
00123     QString curCategory = elementsInfo[index.row()].category;
00124 
00125     QRect retRect;
00126 
00127     if (listView->flow() == QListView::LeftToRight)
00128     {
00129         if (listView->layoutDirection() == Qt::LeftToRight)
00130         {
00131             retRect = QRect(listView->spacing(), listView->spacing() * 2 +
00132                             categoryDrawer->categoryHeight(index, listView->viewOptions()), 0, 0);
00133         }
00134         else
00135         {
00136             retRect = QRect(listView->viewport()->width() - listView->spacing(), listView->spacing() * 2 +
00137                             categoryDrawer->categoryHeight(index, listView->viewOptions()), 0, 0);
00138         }
00139     }
00140     else
00141     {
00142         retRect = QRect(listView->spacing(), listView->spacing() * 2 +
00143                         categoryDrawer->categoryHeight(index, listView->viewOptions()), 0, 0);
00144     }
00145 
00146     int viewportWidth = listView->viewport()->width() - listView->spacing();
00147 
00148     int itemHeight;
00149     int itemWidth;
00150 
00151     if (listView->gridSize().isEmpty() && (listView->flow() == QListView::LeftToRight))
00152     {
00153         itemHeight = biggestItemSize.height();
00154         itemWidth = biggestItemSize.width();
00155     }
00156     else if (listView->flow() == QListView::LeftToRight)
00157     {
00158         itemHeight = listView->gridSize().height();
00159         itemWidth = listView->gridSize().width();
00160     }
00161     else if (listView->gridSize().isEmpty() && (listView->flow() == QListView::TopToBottom))
00162     {
00163         itemHeight = biggestItemSize.height();
00164         itemWidth = listView->viewport()->width() - listView->spacing() * 2;
00165     }
00166     else
00167     {
00168         itemHeight = listView->gridSize().height();
00169         itemWidth = listView->gridSize().width() - listView->spacing() * 2;
00170     }
00171 
00172     int itemWidthPlusSeparation = listView->spacing() + itemWidth;
00173     if (!itemWidthPlusSeparation)
00174         itemWidthPlusSeparation++;
00175     int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
00176     if (!elementsPerRow)
00177         elementsPerRow++;
00178 
00179     int column;
00180     int row;
00181 
00182     if (listView->flow() == QListView::LeftToRight)
00183     {
00184         column = elementsInfo[index.row()].relativeOffsetToCategory % elementsPerRow;
00185         row = elementsInfo[index.row()].relativeOffsetToCategory / elementsPerRow;
00186 
00187         if (listView->layoutDirection() == Qt::LeftToRight)
00188         {
00189             retRect.setLeft(retRect.left() + column * listView->spacing() +
00190                             column * itemWidth);
00191         }
00192         else
00193         {
00194             retRect.setLeft(retRect.right() - column * listView->spacing() -
00195                             column * itemWidth - itemWidth);
00196 
00197             retRect.setRight(retRect.right() - column * listView->spacing() -
00198                             column * itemWidth);
00199         }
00200     }
00201     else
00202     {
00203         elementsPerRow = 1;
00204         column = elementsInfo[index.row()].relativeOffsetToCategory % elementsPerRow;
00205         row = elementsInfo[index.row()].relativeOffsetToCategory / elementsPerRow;
00206     }
00207 
00208     foreach (const QString &category, categories)
00209     {
00210         if (category == curCategory)
00211             break;
00212 
00213         float rows = (float) ((float) categoriesIndexes[category].count() /
00214                               (float) elementsPerRow);
00215 
00216         int rowsInt = categoriesIndexes[category].count() / elementsPerRow;
00217 
00218         if (rows - trunc(rows)) rowsInt++;
00219 
00220         retRect.setTop(retRect.top() +
00221                        (rowsInt * itemHeight) +
00222                        categoryDrawer->categoryHeight(index, listView->viewOptions()) +
00223                        listView->spacing() * 2);
00224 
00225         if (listView->gridSize().isEmpty())
00226         {
00227             retRect.setTop(retRect.top() +
00228                            (rowsInt * listView->spacing()));
00229         }
00230     }
00231 
00232     if (listView->gridSize().isEmpty())
00233     {
00234         retRect.setTop(retRect.top() + row * listView->spacing() +
00235                        (row * itemHeight));
00236     }
00237     else
00238     {
00239         retRect.setTop(retRect.top() + (row * itemHeight));
00240     }
00241 
00242     retRect.setWidth(itemWidth);
00243 
00244     QModelIndex heightIndex = proxyModel->index(index.row(), 0);
00245     if (listView->gridSize().isEmpty())
00246     {
00247         retRect.setHeight(listView->sizeHintForIndex(heightIndex).height());
00248     }
00249     else
00250     {
00251         const QSize sizeHint = listView->sizeHintForIndex(heightIndex);
00252         if (sizeHint.width() < itemWidth) {
00253             retRect.setWidth(sizeHint.width());
00254             retRect.moveLeft(retRect.left() + (itemWidth - sizeHint.width()) / 2);
00255         }
00256         retRect.setHeight(qMin(sizeHint.height(), listView->gridSize().height()));
00257     }
00258 
00259     return retRect;
00260 }
00261 
00262 QRect KCategorizedView::Private::visualCategoryRectInViewport(const QString &category) const
00263 {
00264     QRect retRect(listView->spacing(),
00265                   listView->spacing(),
00266                   listView->viewport()->width() - listView->spacing() * 2,
00267                   0);
00268 
00269     if (!proxyModel || !categoryDrawer || !proxyModel->isCategorizedModel() || !proxyModel->rowCount() || !categories.contains(category))
00270         return QRect();
00271 
00272     QModelIndex index = proxyModel->index(0, 0, QModelIndex());
00273 
00274     int viewportWidth = listView->viewport()->width() - listView->spacing();
00275 
00276     int itemHeight;
00277     int itemWidth;
00278 
00279     if (listView->gridSize().isEmpty())
00280     {
00281         itemHeight = biggestItemSize.height();
00282         itemWidth = biggestItemSize.width();
00283     }
00284     else
00285     {
00286         itemHeight = listView->gridSize().height();
00287         itemWidth = listView->gridSize().width();
00288     }
00289 
00290     int itemWidthPlusSeparation = listView->spacing() + itemWidth;
00291     int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
00292 
00293     if (!elementsPerRow)
00294         elementsPerRow++;
00295 
00296     if (listView->flow() == QListView::TopToBottom)
00297     {
00298         elementsPerRow = 1;
00299     }
00300 
00301     foreach (const QString &itCategory, categories)
00302     {
00303         if (itCategory == category)
00304             break;
00305 
00306         float rows = (float) ((float) categoriesIndexes[itCategory].count() /
00307                               (float) elementsPerRow);
00308         int rowsInt = categoriesIndexes[itCategory].count() / elementsPerRow;
00309 
00310         if (rows - trunc(rows)) rowsInt++;
00311 
00312         retRect.setTop(retRect.top() +
00313                        (rowsInt * itemHeight) +
00314                        categoryDrawer->categoryHeight(index, listView->viewOptions()) +
00315                        listView->spacing() * 2);
00316 
00317         if (listView->gridSize().isEmpty())
00318         {
00319             retRect.setTop(retRect.top() +
00320                            (rowsInt * listView->spacing()));
00321         }
00322     }
00323 
00324     retRect.setHeight(categoryDrawer->categoryHeight(index, listView->viewOptions()));
00325 
00326     return retRect;
00327 }
00328 
00329 // We're sure elementsPosition doesn't contain index
00330 const QRect &KCategorizedView::Private::cacheIndex(const QModelIndex &index)
00331 {
00332     QRect rect = visualRectInViewport(index);
00333     QHash<int, QRect>::iterator it = elementsPosition.insert(index.row(), rect);
00334 
00335     return *it;
00336 }
00337 
00338 // We're sure categoriesPosition doesn't contain category
00339 const QRect &KCategorizedView::Private::cacheCategory(const QString &category)
00340 {
00341     QRect rect = visualCategoryRectInViewport(category);
00342     QHash<QString, QRect>::iterator it = categoriesPosition.insert(category, rect);
00343 
00344     return *it;
00345 }
00346 
00347 const QRect &KCategorizedView::Private::cachedRectIndex(const QModelIndex &index)
00348 {
00349     QHash<int, QRect>::const_iterator it = elementsPosition.constFind(index.row());
00350     if (it != elementsPosition.constEnd()) // If we have it cached
00351     {                                        // return it
00352         return *it;
00353     }
00354     else                                     // Otherwise, cache it
00355     {                                        // and return it
00356         return cacheIndex(index);
00357     }
00358 }
00359 
00360 const QRect &KCategorizedView::Private::cachedRectCategory(const QString &category)
00361 {
00362     QHash<QString, QRect>::const_iterator it = categoriesPosition.constFind(category);
00363     if (it != categoriesPosition.constEnd()) // If we have it cached
00364     {                                                // return it
00365         return *it;
00366     }
00367     else                                            // Otherwise, cache it and
00368     {                                               // return it
00369         return cacheCategory(category);
00370     }
00371 }
00372 
00373 QRect KCategorizedView::Private::visualRect(const QModelIndex &index)
00374 {
00375     QRect retRect = cachedRectIndex(index);
00376     int dx = -listView->horizontalOffset();
00377     int dy = -listView->verticalOffset();
00378     retRect.adjust(dx, dy, dx, dy);
00379 
00380     return retRect;
00381 }
00382 
00383 QRect KCategorizedView::Private::categoryVisualRect(const QString &category)
00384 {
00385     QRect retRect = cachedRectCategory(category);
00386     int dx = -listView->horizontalOffset();
00387     int dy = -listView->verticalOffset();
00388     retRect.adjust(dx, dy, dx, dy);
00389 
00390     return retRect;
00391 }
00392 
00393 void KCategorizedView::Private::drawNewCategory(const QModelIndex &index,
00394                                                 int sortRole,
00395                                                 const QStyleOption &option,
00396                                                 QPainter *painter)
00397 {
00398     if (!index.isValid())
00399     {
00400         return;
00401     }
00402 
00403     QStyleOption optionCopy = option;
00404     const QString category = proxyModel->data(index, KCategorizedSortFilterProxyModel::CategoryDisplayRole).toString();
00405 
00406     optionCopy.state &= ~QStyle::State_Selected;
00407 
00408     if ((listView->selectionMode() != SingleSelection) && (listView->selectionMode() != NoSelection)) {
00409         if ((category == hoveredCategory) && !mouseButtonPressed)
00410         {
00411             optionCopy.state |= QStyle::State_MouseOver;
00412         }
00413         else if ((category == hoveredCategory) && mouseButtonPressed)
00414         {
00415             QPoint initialPressPosition = listView->viewport()->mapFromGlobal(QCursor::pos());
00416             initialPressPosition.setY(initialPressPosition.y() + listView->verticalOffset());
00417             initialPressPosition.setX(initialPressPosition.x() + listView->horizontalOffset());
00418 
00419             if (initialPressPosition == this->initialPressPosition)
00420             {
00421                 optionCopy.state |= QStyle::State_Selected;
00422             }
00423         }
00424     }
00425 
00426     categoryDrawer->drawCategory(index,
00427                                  sortRole,
00428                                  optionCopy,
00429                                  painter);
00430 }
00431 
00432 
00433 void KCategorizedView::Private::updateScrollbars()
00434 {
00435     // find the last index in the last category
00436     QModelIndex lastIndex = categoriesIndexes.isEmpty() ? QModelIndex() : categoriesIndexes[categories.last()].last();
00437 
00438     int lastItemBottom = cachedRectIndex(lastIndex).top() +
00439                          listView->spacing() + (listView->gridSize().isEmpty() ? biggestItemSize.height() : listView->gridSize().height()) - listView->viewport()->height();
00440 
00441     listView->horizontalScrollBar()->setRange(0, 0);
00442 
00443     if (listView->verticalScrollMode() == QAbstractItemView::ScrollPerItem)
00444     {
00445         listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
00446     }
00447 
00448     if (listView->horizontalScrollMode() == QAbstractItemView::ScrollPerItem)
00449     {
00450         listView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
00451     }
00452 
00453     listView->verticalScrollBar()->setSingleStep(listView->viewport()->height() / 10);
00454     listView->verticalScrollBar()->setPageStep(listView->viewport()->height());
00455     listView->verticalScrollBar()->setRange(0, lastItemBottom);
00456 }
00457 
00458 void KCategorizedView::Private::drawDraggedItems(QPainter *painter)
00459 {
00460     QStyleOptionViewItemV4 option = listView->viewOptions();
00461     option.state &= ~QStyle::State_MouseOver;
00462     foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
00463     {
00464         const int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
00465         const int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
00466 
00467         option.rect = visualRect(index);
00468         option.rect.adjust(dx, dy, dx, dy);
00469 
00470         if (option.rect.intersects(listView->viewport()->rect()))
00471         {
00472             listView->itemDelegate(index)->paint(painter, option, index);
00473         }
00474     }
00475 }
00476 
00477 void KCategorizedView::Private::layoutChanged(bool forceItemReload)
00478 {
00479     if (proxyModel && categoryDrawer && proxyModel->isCategorizedModel() &&
00480         ((forceItemReload ||
00481           (modelSortRole != proxyModel->sortRole()) ||
00482           (modelSortColumn != proxyModel->sortColumn()) ||
00483           (modelSortOrder != proxyModel->sortOrder()) ||
00484           (modelLastRowCount != proxyModel->rowCount()) ||
00485           (modelCategorized != proxyModel->isCategorizedModel()))))
00486     {
00487         // Force the view to update all elements
00488         listView->rowsInsertedArtifficial(QModelIndex(), 0, proxyModel->rowCount() - 1);
00489 
00490         if (!forceItemReload)
00491         {
00492             modelSortRole = proxyModel->sortRole();
00493             modelSortColumn = proxyModel->sortColumn();
00494             modelSortOrder = proxyModel->sortOrder();
00495             modelLastRowCount = proxyModel->rowCount();
00496             modelCategorized = proxyModel->isCategorizedModel();
00497         }
00498     }
00499 
00500     if (proxyModel && categoryDrawer && proxyModel->isCategorizedModel())
00501     {
00502         updateScrollbars();
00503     }
00504 }
00505 
00506 void KCategorizedView::Private::drawDraggedItems()
00507 {
00508     QRect rectToUpdate;
00509     QRect currentRect;
00510     foreach (const QModelIndex &index, listView->selectionModel()->selectedIndexes())
00511     {
00512         int dx = mousePosition.x() - initialPressPosition.x() + listView->horizontalOffset();
00513         int dy = mousePosition.y() - initialPressPosition.y() + listView->verticalOffset();
00514 
00515         currentRect = visualRect(index);
00516         currentRect.adjust(dx, dy, dx, dy);
00517 
00518         if (currentRect.intersects(listView->viewport()->rect()))
00519         {
00520             rectToUpdate = rectToUpdate.united(currentRect);
00521         }
00522     }
00523 
00524     listView->viewport()->update(lastDraggedItemsRect.united(rectToUpdate));
00525 
00526     lastDraggedItemsRect = rectToUpdate;
00527 }
00528 
00529 
00530 //==============================================================================
00531 
00532 
00533 KCategorizedView::KCategorizedView(QWidget *parent)
00534     : QListView(parent)
00535     , d(new Private(this))
00536 {
00537 }
00538 
00539 KCategorizedView::~KCategorizedView()
00540 {
00541     delete d;
00542 }
00543 
00544 void KCategorizedView::setGridSize(const QSize &size)
00545 {
00546     QListView::setGridSize(size);
00547 
00548     d->layoutChanged(true);
00549 }
00550 
00551 void KCategorizedView::setModel(QAbstractItemModel *model)
00552 {
00553     d->lastSelection = QItemSelection();
00554     d->forcedSelectionPosition = 0;
00555     d->elementsInfo.clear();
00556     d->elementsPosition.clear();
00557     d->categoriesIndexes.clear();
00558     d->categoriesPosition.clear();
00559     d->categories.clear();
00560     d->intersectedIndexes.clear();
00561     d->modelIndexList.clear();
00562     d->hovered = QModelIndex();
00563     d->mouseButtonPressed = false;
00564     d->rightMouseButtonPressed = false;
00565 
00566     if (d->proxyModel)
00567     {
00568         QObject::disconnect(d->proxyModel,
00569                             SIGNAL(layoutChanged()),
00570                             this, SLOT(slotLayoutChanged()));
00571 
00572         QObject::disconnect(d->proxyModel,
00573                             SIGNAL(rowsRemoved(QModelIndex,int,int)),
00574                             this, SLOT(rowsRemoved(QModelIndex,int,int)));
00575     }
00576 
00577     QListView::setModel(model);
00578 
00579     d->proxyModel = dynamic_cast<KCategorizedSortFilterProxyModel*>(model);
00580 
00581     if (d->proxyModel)
00582     {
00583         d->modelSortRole = d->proxyModel->sortRole();
00584         d->modelSortColumn = d->proxyModel->sortColumn();
00585         d->modelSortOrder = d->proxyModel->sortOrder();
00586         d->modelLastRowCount = d->proxyModel->rowCount();
00587         d->modelCategorized = d->proxyModel->isCategorizedModel();
00588 
00589         QObject::connect(d->proxyModel,
00590                          SIGNAL(layoutChanged()),
00591                          this, SLOT(slotLayoutChanged()));
00592 
00593         QObject::connect(d->proxyModel,
00594                          SIGNAL(rowsRemoved(QModelIndex,int,int)),
00595                          this, SLOT(rowsRemoved(QModelIndex,int,int)));
00596 
00597         if (d->proxyModel->rowCount())
00598         {
00599             d->layoutChanged(true);
00600         }
00601     }
00602     else
00603     {
00604         d->modelCategorized = false;
00605     }
00606 }
00607 
00608 QRect KCategorizedView::visualRect(const QModelIndex &index) const
00609 {
00610     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
00611     {
00612         return QListView::visualRect(index);
00613     }
00614 
00615     if (!qobject_cast<const QSortFilterProxyModel*>(index.model()))
00616     {
00617         return d->visualRect(d->proxyModel->mapFromSource(index));
00618     }
00619 
00620     return d->visualRect(index);
00621 }
00622 
00623 KCategoryDrawer *KCategorizedView::categoryDrawer() const
00624 {
00625     return d->categoryDrawer;
00626 }
00627 
00628 void KCategorizedView::setCategoryDrawer(KCategoryDrawer *categoryDrawer)
00629 {
00630     d->lastSelection = QItemSelection();
00631     d->forcedSelectionPosition = 0;
00632     d->elementsInfo.clear();
00633     d->elementsPosition.clear();
00634     d->categoriesIndexes.clear();
00635     d->categoriesPosition.clear();
00636     d->categories.clear();
00637     d->intersectedIndexes.clear();
00638     d->modelIndexList.clear();
00639     d->hovered = QModelIndex();
00640     d->mouseButtonPressed = false;
00641     d->rightMouseButtonPressed = false;
00642 
00643     if (!categoryDrawer && d->proxyModel)
00644     {
00645         QObject::disconnect(d->proxyModel,
00646                             SIGNAL(layoutChanged()),
00647                             this, SLOT(slotLayoutChanged()));
00648 
00649         QObject::disconnect(d->proxyModel,
00650                             SIGNAL(rowsRemoved(QModelIndex,int,int)),
00651                             this, SLOT(rowsRemoved(QModelIndex,int,int)));
00652     }
00653     else if (categoryDrawer && d->proxyModel)
00654     {
00655         QObject::connect(d->proxyModel,
00656                          SIGNAL(layoutChanged()),
00657                          this, SLOT(slotLayoutChanged()));
00658 
00659         QObject::connect(d->proxyModel,
00660                          SIGNAL(rowsRemoved(QModelIndex,int,int)),
00661                          this, SLOT(rowsRemoved(QModelIndex,int,int)));
00662     }
00663 
00664     d->categoryDrawer = categoryDrawer;
00665 
00666     if (categoryDrawer)
00667     {
00668         if (d->proxyModel)
00669         {
00670             if (d->proxyModel->rowCount())
00671             {
00672                 d->layoutChanged(true);
00673             }
00674         }
00675     }
00676     else
00677     {
00678         updateGeometries();
00679     }
00680 }
00681 
00682 QModelIndex KCategorizedView::indexAt(const QPoint &point) const
00683 {
00684     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
00685     {
00686         return QListView::indexAt(point);
00687     }
00688 
00689     QModelIndex index;
00690 
00691     const QModelIndexList item = d->intersectionSet(QRect(point, point));
00692 
00693     if (item.count() == 1)
00694     {
00695         index = item[0];
00696     }
00697 
00698     return index;
00699 }
00700 
00701 void KCategorizedView::reset()
00702 {
00703     QListView::reset();
00704 
00705     d->lastSelection = QItemSelection();
00706     d->forcedSelectionPosition = 0;
00707     d->elementsInfo.clear();
00708     d->elementsPosition.clear();
00709     d->categoriesIndexes.clear();
00710     d->categoriesPosition.clear();
00711     d->categories.clear();
00712     d->intersectedIndexes.clear();
00713     d->modelIndexList.clear();
00714     d->hovered = QModelIndex();
00715     d->biggestItemSize = QSize(0, 0);
00716     d->mouseButtonPressed = false;
00717     d->rightMouseButtonPressed = false;
00718 }
00719 
00720 void KCategorizedView::paintEvent(QPaintEvent *event)
00721 {
00722     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
00723     {
00724         QListView::paintEvent(event);
00725         return;
00726     }
00727 
00728     bool alternatingRows = alternatingRowColors();
00729 
00730     QStyleOptionViewItemV4 option = viewOptions();
00731     option.widget = this;
00732     if (wordWrap())
00733     {
00734         option.features |= QStyleOptionViewItemV4::WrapText;
00735     }
00736 
00737     QPainter painter(viewport());
00738     QRect area = event->rect();
00739     const bool focus = (hasFocus() || viewport()->hasFocus()) &&
00740                         currentIndex().isValid();
00741     const QStyle::State state = option.state;
00742     const bool enabled = (state & QStyle::State_Enabled) != 0;
00743 
00744     painter.save();
00745 
00746     QModelIndexList dirtyIndexes = d->intersectionSet(area);
00747     bool alternate = false;
00748     if (dirtyIndexes.count())
00749     {
00750         alternate = dirtyIndexes[0].row() % 2;
00751     }
00752     foreach (const QModelIndex &index, dirtyIndexes)
00753     {
00754         if (alternatingRows && alternate)
00755         {
00756             option.features |= QStyleOptionViewItemV4::Alternate;
00757             alternate = false;
00758         }
00759         else if (alternatingRows)
00760         {
00761             option.features &= ~QStyleOptionViewItemV4::Alternate;
00762             alternate = true;
00763         }
00764         option.state = state;
00765         option.rect = visualRect(index);
00766 
00767         if (selectionModel() && selectionModel()->isSelected(index))
00768         {
00769             option.state |= QStyle::State_Selected;
00770         }
00771 
00772         if (enabled)
00773         {
00774             QPalette::ColorGroup cg;
00775             if ((d->proxyModel->flags(index) & Qt::ItemIsEnabled) == 0)
00776             {
00777                 option.state &= ~QStyle::State_Enabled;
00778                 cg = QPalette::Disabled;
00779             }
00780             else
00781             {
00782                 cg = QPalette::Normal;
00783             }
00784             option.palette.setCurrentColorGroup(cg);
00785         }
00786 
00787         if (focus && currentIndex() == index)
00788         {
00789             option.state |= QStyle::State_HasFocus;
00790             if (this->state() == EditingState)
00791                 option.state |= QStyle::State_Editing;
00792         }
00793 
00794         // we are only interested to give the mouse over feedback when no
00795         // dragging is happening (ereslibre)
00796         if ((index == d->hovered) && !d->mouseButtonPressed &&
00797             (this->state() == QAbstractItemView::NoState))
00798             option.state |= QStyle::State_MouseOver;
00799         else
00800             option.state &= ~QStyle::State_MouseOver;
00801 
00802         itemDelegate(index)->paint(&painter, option, index);
00803     }
00804 
00805     // Redraw categories
00806     QStyleOptionViewItemV4 otherOption;
00807     bool intersectedInThePast = false;
00808     foreach (const QString &category, d->categories)
00809     {
00810         otherOption = option;
00811         otherOption.rect = d->categoryVisualRect(category);
00812         otherOption.state &= ~QStyle::State_MouseOver;
00813 
00814         if (otherOption.rect.intersects(area))
00815         {
00816             intersectedInThePast = true;
00817 
00818             QModelIndex indexToDraw = d->proxyModel->index(d->categoriesIndexes[category][0].row(), d->proxyModel->sortColumn());
00819 
00820             d->drawNewCategory(indexToDraw,
00821                                d->proxyModel->sortRole(), otherOption, &painter);
00822         }
00823         else if (intersectedInThePast)
00824         {
00825             break; // the visible area has been finished, we don't need to keep asking, the rest won't intersect
00826                 // this is doable because we know that categories are correctly ordered on the list
00827         }
00828     }
00829 
00830     if ((selectionMode() != SingleSelection) && (selectionMode() != NoSelection))
00831     {
00832         if (d->mouseButtonPressed && !d->isDragging)
00833         {
00834             QPoint start, end, initialPressPosition;
00835 
00836             initialPressPosition = d->initialPressPosition;
00837 
00838             initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
00839             initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
00840 
00841             if (d->initialPressPosition.x() > d->mousePosition.x() ||
00842                 d->initialPressPosition.y() > d->mousePosition.y())
00843             {
00844                 start = d->mousePosition;
00845                 end = initialPressPosition;
00846             }
00847             else
00848             {
00849                 start = initialPressPosition;
00850                 end = d->mousePosition;
00851             }
00852 
00853             QStyleOptionRubberBand yetAnotherOption;
00854             yetAnotherOption.initFrom(this);
00855             yetAnotherOption.shape = QRubberBand::Rectangle;
00856             yetAnotherOption.opaque = false;
00857             yetAnotherOption.rect = QRect(start, end).intersected(viewport()->rect().adjusted(-16, -16, 16, 16));
00858             painter.save();
00859             style()->drawControl(QStyle::CE_RubberBand, &yetAnotherOption, &painter);
00860             painter.restore();
00861         }
00862     }
00863 
00864     if (d->isDragging && !d->dragLeftViewport)
00865     {
00866         painter.setOpacity(0.5);
00867         d->drawDraggedItems(&painter);
00868     }
00869 
00870     painter.restore();
00871 }
00872 
00873 void KCategorizedView::resizeEvent(QResizeEvent *event)
00874 {
00875     QListView::resizeEvent(event);
00876 
00877     // Clear the items positions cache
00878     d->elementsPosition.clear();
00879     d->categoriesPosition.clear();
00880     d->forcedSelectionPosition = 0;
00881 
00882     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
00883     {
00884         return;
00885     }
00886 
00887     d->updateScrollbars();
00888 }
00889 
00890 void KCategorizedView::setSelection(const QRect &rect,
00891                                     QItemSelectionModel::SelectionFlags flags)
00892 {
00893     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
00894     {
00895         QListView::setSelection(rect, flags);
00896         return;
00897     }
00898 
00899     if (!flags)
00900         return;
00901 
00902     if (flags & QItemSelectionModel::Clear)
00903     {
00904         selectionModel()->clear();
00905         d->lastSelection.clear();
00906     }
00907 
00908     QModelIndexList dirtyIndexes = d->intersectionSet(rect);
00909 
00910     // no items affected, just leave
00911     if (!dirtyIndexes.count())
00912     {
00913         selectionModel()->select(d->lastSelection, QItemSelectionModel::SelectCurrent);
00914 
00915         return;
00916     }
00917 
00918     QModelIndex topLeft;
00919     QModelIndex bottomRight;
00920 
00921     if (d->mouseButtonPressed || d->rightMouseButtonPressed) // selection with click + drag
00922     {
00923         QItemSelection selection;
00924 
00925         QModelIndex prev = dirtyIndexes[0];
00926         QModelIndex first = prev;
00927         foreach (const QModelIndex &index, dirtyIndexes)
00928         {
00929             // we have a different interval. non-contiguous items
00930             if ((index.row() - prev.row()) > 1) {
00931                 selection << QItemSelectionRange(first, prev);
00932 
00933                 first = index;
00934             }
00935 
00936             prev = index;
00937         }
00938 
00939         selection << QItemSelectionRange(first, prev);
00940 
00941         if (flags & QItemSelectionModel::Current)
00942         {
00943             if (rect.topLeft() == rect.bottomRight())
00944             {
00945                 selectionModel()->setCurrentIndex(indexAt(rect.topLeft()), QItemSelectionModel::NoUpdate);
00946             }
00947 
00948             selection.merge(d->lastSelection, flags);
00949         }
00950         else
00951         {
00952             selection.merge(selectionModel()->selection(), flags);
00953 
00954             selectionModel()->select(selection, QItemSelectionModel::SelectCurrent);
00955 
00956             return;
00957         }
00958 
00959         selectionModel()->select(selection, flags);
00960     }
00961     else // selection with click + keyboard keys
00962     {
00963         QModelIndex topLeftIndex = indexAt(QPoint(rect.topLeft().x(),
00964                                                   rect.topLeft().y()));
00965         QModelIndex bottomRightIndex = indexAt(QPoint(rect.bottomRight().x(),
00966                                                       rect.bottomRight().y()));
00967 
00968         // keyboard selection comes "upside down". Let's normalize it
00969         if (topLeftIndex.row() > bottomRightIndex.row())
00970         {
00971             QModelIndex auxIndex = topLeftIndex;
00972             topLeftIndex = bottomRightIndex;
00973             bottomRightIndex = auxIndex;
00974         }
00975 
00976         int viewportWidth = viewport()->width() - spacing();
00977         int itemWidth;
00978 
00979         if (gridSize().isEmpty())
00980         {
00981             itemWidth = d->biggestItemSize.width();
00982         }
00983         else
00984         {
00985             itemWidth = gridSize().width();
00986         }
00987 
00988         int itemWidthPlusSeparation = spacing() + itemWidth;
00989         if (!itemWidthPlusSeparation)
00990             itemWidthPlusSeparation++;
00991         int elementsPerRow = viewportWidth / itemWidthPlusSeparation;
00992         if (!elementsPerRow)
00993             elementsPerRow++;
00994 
00995         QModelIndexList theoricDirty(dirtyIndexes);
00996         dirtyIndexes.clear();
00997         int first = model()->rowCount();
00998         int last = 0;
00999 
01000         foreach (const QModelIndex &index, theoricDirty)
01001         {
01002             if ((index.row() < first) &&
01003                 ((((topLeftIndex.row() / elementsPerRow) == (index.row() / elementsPerRow)) &&
01004                   ((topLeftIndex.row() % elementsPerRow) <= (index.row() % elementsPerRow))) ||
01005                  (topLeftIndex.row() / elementsPerRow) != (index.row() / elementsPerRow)))
01006             {
01007                 first = index.row();
01008                 topLeft = index;
01009             }
01010 
01011             if ((index.row() > last) &&
01012                 ((((bottomRightIndex.row() / elementsPerRow) == (index.row() / elementsPerRow)) &&
01013                   ((bottomRightIndex.row() % elementsPerRow) >= (index.row() % elementsPerRow))) ||
01014                  (bottomRightIndex.row() / elementsPerRow) != (index.row() / elementsPerRow)))
01015             {
01016                 last = index.row();
01017                 bottomRight = index;
01018             }
01019         }
01020 
01021         for (int i = first; i <= last; i++)
01022         {
01023             dirtyIndexes << model()->index(i, theoricDirty[0].column(), theoricDirty[0].parent());
01024         }
01025 
01026         QItemSelection selection(topLeft, bottomRight);
01027 
01028         selectionModel()->select(selection, flags);
01029     }
01030 }
01031 
01032 void KCategorizedView::mouseMoveEvent(QMouseEvent *event)
01033 {
01034     QListView::mouseMoveEvent(event);
01035 
01036     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
01037     {
01038         return;
01039     }
01040 
01041     const QModelIndexList item = d->intersectionSet(QRect(event->pos(), event->pos()));
01042 
01043     if (item.count() == 1)
01044     {
01045         d->hovered = item[0];
01046     }
01047     else
01048     {
01049         d->hovered = QModelIndex();
01050     }
01051 
01052     const QString previousHoveredCategory = d->hoveredCategory;
01053 
01054     d->mousePosition = event->pos();
01055     d->hoveredCategory.clear();
01056 
01057     // Redraw categories
01058     foreach (const QString &category, d->categories)
01059     {
01060         if (d->categoryVisualRect(category).intersects(QRect(event->pos(), event->pos())))
01061         {
01062             d->hoveredCategory = category;
01063             viewport()->update(d->categoryVisualRect(category));
01064         }
01065         else if ((category == previousHoveredCategory) &&
01066                  (!d->categoryVisualRect(previousHoveredCategory).intersects(QRect(event->pos(), event->pos()))))
01067         {
01068             viewport()->update(d->categoryVisualRect(category));
01069         }
01070     }
01071 
01072     QRect rect;
01073     if (d->mouseButtonPressed && !d->isDragging)
01074     {
01075         QPoint start, end, initialPressPosition;
01076 
01077         initialPressPosition = d->initialPressPosition;
01078 
01079         initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
01080         initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
01081 
01082         if (d->initialPressPosition.x() > d->mousePosition.x() ||
01083             d->initialPressPosition.y() > d->mousePosition.y())
01084         {
01085             start = d->mousePosition;
01086             end = initialPressPosition;
01087         }
01088         else
01089         {
01090             start = initialPressPosition;
01091             end = d->mousePosition;
01092         }
01093 
01094         rect = QRect(start, end).adjusted(-16, -16, 16, 16);
01095         rect = rect.united(QRect(start, end).adjusted(16, 16, -16, -16)).intersected(viewport()->rect());
01096 
01097         viewport()->update(rect);
01098     }
01099 }
01100 
01101 void KCategorizedView::mousePressEvent(QMouseEvent *event)
01102 {
01103     d->dragLeftViewport = false;
01104 
01105     if (event->button() == Qt::LeftButton)
01106     {
01107         d->mouseButtonPressed = true;
01108 
01109         d->initialPressPosition = event->pos();
01110         d->initialPressPosition.setY(d->initialPressPosition.y() +
01111                                                               verticalOffset());
01112         d->initialPressPosition.setX(d->initialPressPosition.x() +
01113                                                             horizontalOffset());
01114     }
01115     else if (event->button() == Qt::RightButton)
01116     {
01117         d->rightMouseButtonPressed = true;
01118     }
01119 
01120     QListView::mousePressEvent(event);
01121 
01122     if (selectionModel())
01123     {
01124         d->lastSelection = selectionModel()->selection();
01125     }
01126 
01127     viewport()->update(d->categoryVisualRect(d->hoveredCategory));
01128 }
01129 
01130 void KCategorizedView::mouseReleaseEvent(QMouseEvent *event)
01131 {
01132     d->mouseButtonPressed = false;
01133     d->rightMouseButtonPressed = false;
01134 
01135     QListView::mouseReleaseEvent(event);
01136 
01137     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
01138     {
01139         return;
01140     }
01141 
01142     QPoint initialPressPosition = viewport()->mapFromGlobal(QCursor::pos());
01143     initialPressPosition.setY(initialPressPosition.y() + verticalOffset());
01144     initialPressPosition.setX(initialPressPosition.x() + horizontalOffset());
01145 
01146     if ((selectionMode() != SingleSelection) && (selectionMode() != NoSelection) &&
01147         (initialPressPosition == d->initialPressPosition))
01148     {
01149         foreach(const QString &category, d->categories)
01150         {
01151             if (d->categoryVisualRect(category).contains(event->pos()) &&
01152                 selectionModel())
01153             {
01154                 QItemSelection selection = selectionModel()->selection();
01155                 QModelIndexList indexList = d->categoriesIndexes[category];
01156 
01157                 foreach (const QModelIndex &index, indexList)
01158                 {
01159                     QModelIndex selectIndex = index.model()->index(index.row(), 0);
01160 
01161                     selection << QItemSelectionRange(selectIndex);
01162                 }
01163 
01164                 selectionModel()->select(selection, QItemSelectionModel::SelectCurrent);
01165 
01166                 break;
01167             }
01168         }
01169     }
01170 
01171     QRect rect;
01172     if (!d->isDragging)
01173     {
01174         QPoint start, end, initialPressPosition;
01175 
01176         initialPressPosition = d->initialPressPosition;
01177 
01178         initialPressPosition.setY(initialPressPosition.y() - verticalOffset());
01179         initialPressPosition.setX(initialPressPosition.x() - horizontalOffset());
01180 
01181         if (d->initialPressPosition.x() > d->mousePosition.x() ||
01182             d->initialPressPosition.y() > d->mousePosition.y())
01183         {
01184             start = d->mousePosition;
01185             end = initialPressPosition;
01186         }
01187         else
01188         {
01189             start = initialPressPosition;
01190             end = d->mousePosition;
01191         }
01192 
01193         rect = QRect(start, end).adjusted(-16, -16, 16, 16);
01194         rect = rect.united(QRect(start, end).adjusted(16, 16, -16, -16)).intersected(viewport()->rect());
01195 
01196         viewport()->update(rect);
01197     }
01198 
01199     if (d->hovered.isValid())
01200         viewport()->update(visualRect(d->hovered));
01201     else if (!d->hoveredCategory.isEmpty())
01202         viewport()->update(d->categoryVisualRect(d->hoveredCategory));
01203 }
01204 
01205 void KCategorizedView::leaveEvent(QEvent *event)
01206 {
01207     d->hovered = QModelIndex();
01208     d->hoveredCategory.clear();
01209 
01210     QListView::leaveEvent(event);
01211 }
01212 
01213 void KCategorizedView::startDrag(Qt::DropActions supportedActions)
01214 {
01215     // FIXME: QAbstractItemView does far better here since it sets the
01216     //        pixmap of selected icons to the dragging cursor, but it sets a non
01217     //        ARGB window so it is no transparent. Use QAbstractItemView when
01218     //        this is fixed on Qt.
01219     // QAbstractItemView::startDrag(supportedActions);
01220 #if !defined(DOLPHIN_DRAGANDDROP)
01221     QListView::startDrag(supportedActions);
01222 #endif
01223 
01224     d->isDragging = false;
01225     d->mouseButtonPressed = false;
01226     d->rightMouseButtonPressed = false;
01227 
01228     viewport()->update(d->lastDraggedItemsRect);
01229 }
01230 
01231 void KCategorizedView::dragMoveEvent(QDragMoveEvent *event)
01232 {
01233     d->mousePosition = event->pos();
01234 
01235     if (d->mouseButtonPressed)
01236     {
01237         d->isDragging = true;
01238     }
01239     else
01240     {
01241         d->isDragging = false;
01242     }
01243 
01244     d->dragLeftViewport = false;
01245 
01246 #if defined(DOLPHIN_DRAGANDDROP)
01247     QAbstractItemView::dragMoveEvent(event);
01248 #else
01249     QListView::dragMoveEvent(event);
01250 #endif
01251 
01252     if (!d->proxyModel || !d->categoryDrawer || !d->proxyModel->isCategorizedModel())
01253     {
01254         return;