00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "ui/flipscrollview.h"
00022
00023
00024 #include <QMouseEvent>
00025 #include <QPainter>
00026 #include <QScrollBar>
00027 #include <QStack>
00028 #include <QTimeLine>
00029
00030
00031 #include <KDebug>
00032 #include <KGlobalSettings>
00033 #include <KIconLoader>
00034 #include <KColorScheme>
00035
00036 #include "ui/itemdelegate.h"
00037
00038 using namespace Kickoff;
00039
00040 class FlipScrollView::Private
00041 {
00042 public:
00043 Private(FlipScrollView *view)
00044 : q(view)
00045 , backArrowHover(false)
00046 , flipAnimTimeLine(new QTimeLine())
00047 , animLeftToRight(true)
00048 , itemHeight(-1)
00049 {
00050 }
00051 ~Private()
00052 {
00053 delete flipAnimTimeLine;
00054 }
00055
00056 QModelIndex currentRoot() const
00057 {
00058 if (currentRootIndex.isValid()) {
00059 return currentRootIndex;
00060 } else {
00061 return q->rootIndex();
00062 }
00063 }
00064 QModelIndex previousRoot() const
00065 {
00066 if (previousRootIndices.isEmpty()) {
00067 return QModelIndex();
00068 }
00069 return previousRootIndices.top();
00070 }
00071
00072 void setCurrentRoot(const QModelIndex& index)
00073 {
00074 if (previousRootIndices.isEmpty() || previousRootIndices.top() != index) {
00075
00076
00077 animLeftToRight = true;
00078 hoveredIndex = QModelIndex();
00079 previousRootIndices.push(currentRootIndex);
00080 currentRootIndex = index;
00081 previousVerticalOffsets.append(q->verticalOffset());
00082 updateScrollBarRange();
00083 q->verticalScrollBar()->setValue(0);
00084 } else {
00085
00086
00087 animLeftToRight = false;
00088 hoveredIndex = currentRootIndex;
00089 previousRootIndices.pop();
00090
00091
00092
00093 currentRootIndex = index;
00094 updateScrollBarRange();
00095 q->verticalScrollBar()->setValue(previousVerticalOffsets.pop());
00096 }
00097
00098 if (q->viewOptions().direction == Qt::RightToLeft) {
00099 animLeftToRight = !animLeftToRight;
00100 }
00101
00102 flipAnimTimeLine->setCurrentTime(0);
00103 q->update();
00104 }
00105
00106 int previousVerticalOffset()
00107 {
00108 return previousVerticalOffsets.isEmpty() ? 0 : previousVerticalOffsets.top();
00109 }
00110 int treeDepth(const QModelIndex& headerIndex) const
00111 {
00112 int depth = 0;
00113 QModelIndex index = headerIndex;
00114 while (index.isValid()) {
00115 index = index.parent();
00116 depth++;
00117 }
00118 return depth;
00119 }
00120
00121 QRect headerRect(const QModelIndex& headerIndex = QModelIndex()) const
00122 {
00123 Q_UNUSED(headerIndex)
00124 QFontMetrics fm(KGlobalSettings::smallestReadableFont());
00125 const int top = ItemDelegate::TOP_OFFSET - q->verticalScrollBar()->value();
00126 int minHeight = ItemDelegate::FIRST_HEADER_HEIGHT;
00127
00128 QRect rect(backArrowRect().right() + ItemDelegate::BACK_ARROW_SPACING, top,
00129 q->width() - backArrowRect().width() - ItemDelegate::BACK_ARROW_SPACING + 1,
00130 qMax(fm.height(), minHeight) + 4 + ItemDelegate::HEADER_BOTTOM_MARGIN);
00131
00132
00133 return rect;
00134 }
00135
00136 void drawHeader(QPainter *painter, const QRectF& rect,
00137 const QModelIndex& headerIndex, QStyleOptionViewItem options)
00138 {
00139 QFontMetrics fm(KGlobalSettings::smallestReadableFont());
00140 QModelIndex branchIndex = headerIndex;
00141
00142 painter->save();
00143 painter->setFont(KGlobalSettings::smallestReadableFont());
00144 painter->setPen(QPen(q->palette().text(),0));
00145
00146 QString currentText = i18n("All Applications");
00147 QString previousText;
00148 bool ltr = options.direction == Qt::LeftToRight;
00149 QString sep = ltr ? " > " : " < ";
00150 if (branchIndex.isValid()) {
00151 currentText = branchIndex.data(Qt::DisplayRole).value<QString>();
00152 branchIndex = branchIndex.parent();
00153
00154 while (branchIndex.isValid()) {
00155 previousText.append(branchIndex.data(Qt::DisplayRole).value<QString>()).append(sep);
00156 branchIndex = branchIndex.parent();
00157 }
00158 }
00159
00160 const qreal rightMargin = q->style()->pixelMetric(QStyle::PM_ScrollBarExtent) + 6;
00161 const qreal top = rect.bottom() - fm.height() - 1 - ItemDelegate::HEADER_BOTTOM_MARGIN;
00162 QRectF textRect(rect.left(), top, rect.width() - rightMargin, fm.height());
00163 painter->setPen(QPen(KColorScheme(QPalette::Active).foreground(KColorScheme::InactiveText), 1));
00164 painter->drawText(textRect, Qt::AlignRight | Qt::AlignVCenter, currentText);
00165
00166 if (!previousText.isEmpty()) {
00167 int textWidth = fm.width(currentText) + fm.width(' ');
00168 if (ltr) {
00169 textRect.adjust(0, 0, -textWidth, 0);
00170 } else {
00171 textRect.adjust(textWidth, 0, 0, 0);
00172 }
00173
00174 painter->drawText(textRect, Qt::AlignRight, previousText);
00175 }
00176
00177 painter->restore();
00178 }
00179
00180 void drawBackArrow(QPainter *painter,QStyle::State state)
00181 {
00182 painter->save();
00183 if (state & QStyle::State_MouseOver) {
00184 painter->setBrush(q->palette().highlight());
00185 } else {
00186 painter->setBrush(q->palette().mid());
00187 }
00188
00189 QRect rect = backArrowRect();
00190
00191
00192 painter->setPen(Qt::NoPen);
00193 painter->drawRect(rect);
00194
00195 painter->setPen(QPen(q->palette().dark(),0));
00196 painter->drawLine (backArrowRect().topRight()+QPointF(0.5,0),
00197 backArrowRect().bottomRight()+QPointF(0.5,0));
00198
00199
00200 if (state & QStyle::State_Enabled) {
00201 painter->setPen(Qt::NoPen);
00202
00203 if (state & QStyle::State_MouseOver) {
00204 painter->setBrush(q->palette().highlightedText());
00205 } else {
00206 painter->setBrush(q->palette().dark());
00207 }
00208 painter->translate(rect.center());
00209 if (painter->layoutDirection() == Qt::RightToLeft) {
00210 painter->rotate(180);
00211 }
00212 painter->drawPath(trianglePath());
00213 painter->resetTransform();
00214 }
00215 painter->restore();
00216 }
00217
00218 QPainterPath trianglePath(qreal width = 5,qreal height = 10)
00219 {
00220 QPainterPath path(QPointF(-width/2,0.0));
00221 path.lineTo(width,-height/2);
00222 path.lineTo(width,height/2);
00223 path.lineTo(-width/2,0.0);
00224
00225 return path;
00226 }
00227
00228 QRect backArrowRect() const
00229 {
00230 return QRect(0, 0, ItemDelegate::BACK_ARROW_WIDTH, q->height());
00231 }
00232
00233 void updateScrollBarRange()
00234 {
00235 int childCount = q->model()->rowCount(currentRootIndex);
00236 int pageSize = q->height();
00237 int headerHeight = headerRect(currentRoot()).height();
00238 int itemH = q->sizeHintForIndex(q->model()->index(0, 0)).height();
00239 q->verticalScrollBar()->setRange(0, (childCount * itemH) +
00240 headerHeight - pageSize);
00241 q->verticalScrollBar()->setPageStep(pageSize);
00242 q->verticalScrollBar()->setSingleStep(itemH);
00243 }
00244
00245 FlipScrollView * const q;
00246 bool backArrowHover;
00247 QPersistentModelIndex hoveredIndex;
00248 QPersistentModelIndex watchedIndexForDrag;
00249
00250 QTimeLine *flipAnimTimeLine;
00251 bool animLeftToRight;
00252
00253 int itemHeight;
00254 static const int FLIP_ANIM_DURATION = 200;
00255
00256 private:
00257 QPersistentModelIndex currentRootIndex;
00258 QStack<QPersistentModelIndex> previousRootIndices;
00259 QStack<int> previousVerticalOffsets;
00260 };
00261
00262 FlipScrollView::FlipScrollView(QWidget *parent)
00263 : QAbstractItemView(parent)
00264 , d(new Private(this))
00265 {
00266 connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(openItem(QModelIndex)));
00267 connect(d->flipAnimTimeLine, SIGNAL(valueChanged(qreal)), this, SLOT(updateFlipAnimation(qreal)));
00268 d->flipAnimTimeLine->setDuration(Private::FLIP_ANIM_DURATION);
00269 d->flipAnimTimeLine->setCurrentTime(Private::FLIP_ANIM_DURATION);
00270 setIconSize(QSize(KIconLoader::SizeMedium,KIconLoader::SizeMedium));
00271 setMouseTracking(true);
00272 setAutoScroll(true);
00273 }
00274 FlipScrollView::~FlipScrollView()
00275 {
00276 delete d;
00277 }
00278 void FlipScrollView::viewRoot()
00279 {
00280 QModelIndex index;
00281 while(d->currentRoot().isValid()) {
00282 index = d->currentRoot();
00283 d->setCurrentRoot(d->currentRoot().parent());
00284 setCurrentIndex(index);
00285 }
00286 update(d->hoveredIndex);
00287 d->hoveredIndex = index;
00288 }
00289
00290 QModelIndex FlipScrollView::indexAt(const QPoint& point) const
00291 {
00292 int topOffset = d->headerRect(d->currentRoot()).height() - verticalOffset();
00293 int items = model()->rowCount(d->currentRoot());
00294
00295 int rowIndex = (point.y() - topOffset) / itemHeight();
00296
00297 QRect itemRect = rect();
00298 itemRect.setTop(itemRect.top() + topOffset);
00299 itemRect.setLeft(d->backArrowRect().right() + ItemDelegate::BACK_ARROW_SPACING);
00300
00301 if (rowIndex < items && itemRect.contains(point)) {
00302 return model()->index(rowIndex,0,d->currentRoot());
00303 } else {
00304 return QModelIndex();
00305 }
00306 }
00307
00308 int FlipScrollView::itemHeight() const
00309 {
00310
00311 if (d->itemHeight < 1) {
00312 QModelIndex index = model()->index(0, 0, d->currentRoot());
00313 d->itemHeight = sizeHintForIndex(index).height();
00314 }
00315
00316 return d->itemHeight;
00317 }
00318
00319 void FlipScrollView::scrollTo(const QModelIndex& index , ScrollHint hint)
00320 {
00321 if (!index.isValid()) {
00322 return;
00323 }
00324
00325 QRect itemRect = visualRect(index);
00326 if (itemRect.isValid() && hint == EnsureVisible) {
00327 if (itemRect.top() < 0) {
00328 verticalScrollBar()->setValue(verticalScrollBar()->value() +
00329 itemRect.top());
00330 } else if (itemRect.bottom() > height()) {
00331 verticalScrollBar()->setValue(verticalScrollBar()->value() +
00332 (itemRect.bottom()-height()));
00333 }
00334 }
00335 }
00336
00337 bool FlipScrollView::isIndexHidden(const QModelIndex&) const
00338 {
00339 return false;
00340 }
00341
00342 QRect FlipScrollView::visualRect(const QModelIndex& index) const
00343 {
00344 int topOffset = d->headerRect(index.parent()).height();
00345 int leftOffset = d->backArrowRect().width() + ItemDelegate::BACK_ARROW_SPACING;
00346
00347 if (index.parent() != d->currentRoot() &&
00348 index.parent() != d->previousRoot() &&
00349 index.parent() != (QModelIndex)d->hoveredIndex) {
00350 return QRect();
00351 }
00352
00353 bool parentIsPreviousRoot = d->previousRoot().isValid() && index.parent() == d->previousRoot();
00354 if (parentIsPreviousRoot && d->flipAnimTimeLine->state() == QTimeLine::NotRunning) {
00355 return QRect();
00356 }
00357
00358 if (parentIsPreviousRoot) {
00359 topOffset -= d->previousVerticalOffset();
00360 } else {
00361 topOffset -= verticalOffset();
00362 }
00363
00364
00365
00366
00367
00368
00369
00370 int scrollBarWidth = verticalScrollBar()->isVisible() ?
00371 verticalScrollBar()->width() : 0;
00372 QRectF itemRect(leftOffset, topOffset + index.row() * itemHeight(),
00373 width() - leftOffset - scrollBarWidth - ItemDelegate::BACK_ARROW_SPACING,
00374 itemHeight());
00375
00376 const qreal timeValue = d->flipAnimTimeLine->currentValue();
00377 if ( index.parent() == d->currentRoot() ) {
00378 if (d->animLeftToRight) {
00379 itemRect.translate(itemRect.width() * (1 - timeValue),0);
00380 } else {
00381 itemRect.translate(-itemRect.width() * (1 - timeValue),0);
00382 }
00383 } else {
00384 if (d->animLeftToRight) {
00385 itemRect.translate((-timeValue*itemRect.width()),0);
00386 } else {
00387 itemRect.translate((timeValue*itemRect.width()),0);
00388 }
00389 }
00390 return itemRect.toRect();
00391 }
00392
00393 int FlipScrollView::horizontalOffset() const
00394 {
00395 return 0;
00396 }
00397
00398 int FlipScrollView::verticalOffset() const
00399 {
00400 return verticalScrollBar()->value();
00401 }
00402
00403 QRegion FlipScrollView::visualRegionForSelection(const QItemSelection& selection) const
00404 {
00405 QRegion region;
00406 foreach(const QModelIndex& index , selection.indexes()) {
00407 region |= visualRect(index);
00408 }
00409 return region;
00410 }
00411 QModelIndex FlipScrollView::moveCursor(CursorAction cursorAction,Qt::KeyboardModifiers)
00412 {
00413 QModelIndex index = currentIndex();
00414
00415 switch (cursorAction) {
00416 case MoveUp:
00417 if (!currentIndex().isValid()) {
00418 index = model()->index(model()->rowCount(d->currentRoot()) - 1, 0, d->currentRoot());
00419 } else if (currentIndex().row() > 0) {
00420 index = currentIndex().sibling(currentIndex().row()-1,
00421 currentIndex().column());
00422 }
00423 break;
00424 case MoveDown:
00425 if (!currentIndex().isValid()) {
00426 index = model()->index(0, 0, d->currentRoot());
00427 } else if (currentIndex().row() <
00428 model()->rowCount(currentIndex().parent())-1 ) {
00429 index = currentIndex().sibling(currentIndex().row()+1,
00430 currentIndex().column());
00431 }
00432 break;
00433 case MoveLeft:
00434 if (d->currentRoot().isValid()) {
00435 index = d->currentRoot();
00436 d->setCurrentRoot(d->currentRoot().parent());
00437 setCurrentIndex(index);
00438 }
00439 break;
00440 case MoveRight:
00441 if (model()->hasChildren(currentIndex())) {
00442 openItem(currentIndex());
00443
00444 index = currentIndex();
00445 }
00446 break;
00447 default:
00448
00449 break;
00450 }
00451
00452
00453 update(d->hoveredIndex);
00454 d->hoveredIndex = index;
00455
00456
00457
00458 return index;
00459 }
00460
00461 void FlipScrollView::setSelection(const QRect& rect , QItemSelectionModel::SelectionFlags flags)
00462 {
00463 QItemSelection selection;
00464 selection.select(indexAt(rect.topLeft()),indexAt(rect.bottomRight()));
00465 selectionModel()->select(selection,flags);
00466 }
00467
00468 void FlipScrollView::openItem(const QModelIndex& index)
00469 {
00470 if (model()->canFetchMore(index)) {
00471 model()->fetchMore(index);
00472 }
00473
00474 bool hasChildren = model()->hasChildren(index);
00475
00476 if (hasChildren) {
00477 d->setCurrentRoot(index);
00478 setCurrentIndex(model()->index(0,0,index));
00479 } else {
00480
00481 }
00482 }
00483
00484 void FlipScrollView::resizeEvent(QResizeEvent*)
00485 {
00486 d->updateScrollBarRange();
00487 }
00488
00489 void FlipScrollView::mousePressEvent(QMouseEvent *event)
00490 {
00491 d->watchedIndexForDrag = indexAt(event->pos());
00492 QAbstractItemView::mousePressEvent(event);
00493 }
00494
00495 void FlipScrollView::mouseReleaseEvent(QMouseEvent *event)
00496 {
00497 d->watchedIndexForDrag = QModelIndex();
00498
00499 if (d->backArrowRect().contains(event->pos()) && d->currentRoot().isValid()) {
00500
00501 d->setCurrentRoot(d->currentRoot().parent());
00502 setDirtyRegion(rect());
00503 } else {
00504 QAbstractItemView::mouseReleaseEvent(event);
00505 }
00506 }
00507
00508 void FlipScrollView::mouseMoveEvent(QMouseEvent *event)
00509 {
00510 bool mouseOverBackArrow = d->backArrowRect().contains(event->pos());
00511
00512 if (mouseOverBackArrow != d->backArrowHover) {
00513 d->backArrowHover = mouseOverBackArrow;
00514 setDirtyRegion(d->backArrowRect());
00515 } else {
00516 const QModelIndex itemUnderMouse = indexAt(event->pos());
00517 if (itemUnderMouse != d->hoveredIndex) {
00518 update(itemUnderMouse);
00519 update(d->hoveredIndex);
00520
00521 d->hoveredIndex = itemUnderMouse;
00522 setCurrentIndex(d->hoveredIndex);
00523 }
00524
00525 QAbstractItemView::mouseMoveEvent(event);
00526 }
00527 }
00528
00529 void FlipScrollView::keyPressEvent(QKeyEvent *event)
00530 {
00531 if (event->key() == Qt::Key_Enter ||
00532 event->key() == Qt::Key_Return) {
00533 moveCursor(MoveRight, event->modifiers());
00534 event->accept();
00535 return;
00536 }
00537
00538 if (event->key() == Qt::Key_Escape &&
00539 d->currentRoot().isValid()) {
00540 moveCursor(MoveLeft, event->modifiers());
00541 event->accept();
00542 return;
00543 }
00544
00545 QAbstractItemView::keyPressEvent(event);
00546 }
00547
00548 void FlipScrollView::leaveEvent(QEvent *event)
00549 {
00550 d->hoveredIndex = QModelIndex();
00551 setCurrentIndex(QModelIndex());
00552 }
00553
00554 void FlipScrollView::paintItems(QPainter &painter, QPaintEvent *event, QModelIndex &root)
00555 {
00556 const int rows = model()->rowCount(root);
00557
00558
00559 for (int i = 0; i < rows; ++i) {
00560 QModelIndex index = model()->index(i, 0, root);
00561
00562 QStyleOptionViewItem option = viewOptions();
00563 option.rect = visualRect(index);
00564
00565
00566
00567 if (!event->rect().intersects(option.rect)) {
00568 continue;
00569 }
00570
00571 if (selectionModel()->isSelected(index)) {
00572 option.state |= QStyle::State_Selected;
00573 }
00574
00575 if (index == d->hoveredIndex) {
00576 option.state |= QStyle::State_MouseOver;
00577 }
00578
00579 if (index == currentIndex()) {
00580 option.state |= QStyle::State_HasFocus;
00581 }
00582
00583 itemDelegate(index)->paint(&painter,option,index);
00584
00585 if (model()->hasChildren(index)) {
00586 painter.save();
00587 painter.setPen(Qt::NoPen);
00588
00589
00590
00591 if (option.state & QStyle::State_MouseOver) {
00592 painter.setBrush(palette().highlight());
00593 } else {
00594 painter.setBrush(palette().text());
00595 }
00596
00597 QRect triRect = option.rect;
00598 QPainterPath tPath = d->trianglePath();
00599 if (option.direction == Qt::LeftToRight) {
00600 triRect.setLeft(triRect.right() - ItemDelegate::ITEM_RIGHT_MARGIN);
00601 } else {
00602 triRect.setRight(triRect.left() + ItemDelegate::ITEM_RIGHT_MARGIN);
00603 }
00604
00605 painter.translate(triRect.center().x()-6, triRect.y() + (option.rect.height() / 2));
00606
00607 if (option.direction == Qt::LeftToRight) {
00608 painter.rotate(180);
00609 }
00610
00611 painter.drawPath(tPath);
00612 painter.resetTransform();
00613 painter.restore();
00614 }
00615 }
00616 }
00617
00618 void FlipScrollView::paintEvent(QPaintEvent * event)
00619 {
00620 QPainter painter(viewport());
00621 painter.setRenderHint(QPainter::Antialiasing);
00622
00623
00624 QModelIndex currentRoot = d->currentRoot();
00625 QModelIndex previousRoot = d->animLeftToRight ? d->previousRoot() : (QModelIndex)d->hoveredIndex;
00626
00627
00628 paintItems(painter, event, currentRoot);
00629
00630 const qreal timerValue = d->flipAnimTimeLine->currentValue();
00631
00632 if (timerValue < 1.0) {
00633
00634 paintItems(painter, event, previousRoot);
00635
00636 if (d->flipAnimTimeLine->state() != QTimeLine::Running) {
00637 d->flipAnimTimeLine->start();
00638 }
00639 }
00640
00641 QRectF eventRect = event->rect();
00642
00643
00644 QRectF headerRect = d->headerRect(currentRoot);
00645 if (d->animLeftToRight) {
00646 headerRect.translate(headerRect.width() * (1 - timerValue), 0);
00647 } else {
00648 headerRect.translate(-headerRect.width() * (1 - timerValue), 0);
00649 }
00650
00651 if (eventRect.intersects(headerRect)) {
00652 d->drawHeader(&painter, headerRect, currentRoot, viewOptions());
00653 }
00654
00655
00656 QRectF prevHeaderRect = d->headerRect(previousRoot);
00657 if (d->animLeftToRight) {
00658 prevHeaderRect.translate(-prevHeaderRect.width() * timerValue, 0);
00659 } else {
00660 prevHeaderRect.translate(prevHeaderRect.width() * timerValue, 0);
00661 }
00662
00663 if (eventRect.intersects(prevHeaderRect) && timerValue < 1.0) {
00664 d->drawHeader(&painter, prevHeaderRect, previousRoot, viewOptions());
00665 }
00666
00667
00668 QStyle::State state = 0;
00669 if (currentRoot.isValid()) {
00670 state |= QStyle::State_Enabled;
00671 }
00672
00673 if (d->backArrowHover) {
00674 state |= QStyle::State_MouseOver;
00675 }
00676
00677 if (currentRoot.isValid() || previousRoot.isValid()) {
00678 qreal opacity = 1.0;
00679 if (!previousRoot.isValid()) {
00680 opacity = timerValue;
00681 } else if (!currentRoot.isValid()) {
00682 opacity = 1-timerValue;
00683 }
00684
00685 painter.save();
00686 painter.setOpacity(opacity);
00687 d->drawBackArrow(&painter,state);
00688 painter.restore();
00689 }
00690 }
00691
00692 void FlipScrollView::startDrag(Qt::DropActions supportedActions)
00693 {
00694 kDebug() << "Starting UrlItemView drag with actions" << supportedActions;
00695
00696 if (!d->watchedIndexForDrag.isValid()) {
00697 return;
00698 }
00699
00700 QDrag *drag = new QDrag(this);
00701 QMimeData *mimeData = model()->mimeData(selectionModel()->selectedIndexes());
00702
00703 if (mimeData->text().isNull()) {
00704 return;
00705 }
00706
00707 drag->setMimeData(mimeData);
00708
00709 QModelIndex idx = selectionModel()->selectedIndexes().first();
00710 QIcon icon = idx.data(Qt::DecorationRole).value<QIcon>();
00711 drag->setPixmap(icon.pixmap(IconSize(KIconLoader::Desktop)));
00712
00713 Qt::DropAction dropAction = drag->exec();
00714 QAbstractItemView::startDrag(supportedActions);
00715 }
00716
00717 void FlipScrollView::updateFlipAnimation(qreal)
00718 {
00719 setDirtyRegion(rect());
00720 }
00721
00722 #include "flipscrollview.moc"