2
Copyright 2007 Robert Knight <robertknight@gmail.com>
4
This library is free software; you can redistribute it and/or
5
modify it under the terms of the GNU Library General Public
6
License as published by the Free Software Foundation; either
7
version 2 of the License, or (at your option) any later version.
9
This library is distributed in the hope that it will be useful,
10
but WITHOUT ANY WARRANTY; without even the implied warranty of
11
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
Library General Public License for more details.
14
You should have received a copy of the GNU Library General Public License
15
along with this library; see the file COPYING.LIB. If not, write to
16
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17
Boston, MA 02110-1301, USA.
21
#include "ui/flipscrollview.h"
24
#include <QMouseEvent>
32
#include <KGlobalSettings>
33
#include <KIconLoader>
34
#include <KColorScheme>
36
#include "ui/itemdelegate.h"
38
using namespace Kickoff;
40
class FlipScrollView::Private
43
Private(FlipScrollView *view)
45
, flipAnimTimeLine(new QTimeLine())
46
, animLeftToRight(true)
52
delete flipAnimTimeLine;
55
QModelIndex currentRoot() const
57
if (currentRootIndex.isValid()) {
58
return currentRootIndex;
60
return q->rootIndex();
64
QModelIndex previousRoot() const
66
if (previousRootIndices.isEmpty()) {
69
return previousRootIndices.top();
72
void setCurrentRoot(const QModelIndex& index)
74
if (previousRootIndices.isEmpty() || previousRootIndices.top() != index) {
75
// we're entering into a submenu
76
//kDebug() << "pushing" << currentRootIndex.data(Qt::DisplayRole).value<QString>();
77
animLeftToRight = true;
78
hoveredIndex = QModelIndex();
79
previousRootIndices.push(currentRootIndex);
80
currentRootIndex = index;
81
previousVerticalOffsets.append(q->verticalOffset());
82
updateScrollBarRange();
83
q->verticalScrollBar()->setValue(0);
85
// we're exiting to the parent menu
86
//kDebug() << "popping" << previousRootIndices.top().data(Qt::DisplayRole).value<QString>();
87
animLeftToRight = false;
88
hoveredIndex = currentRootIndex;
89
previousRootIndices.pop();
90
//if (!previousRootIndices.isEmpty()) {
91
// kDebug() << "now the previos root is" << previousRootIndices.top().data(Qt::DisplayRole).value<QString>();
93
currentRootIndex = index;
94
updateScrollBarRange();
95
q->verticalScrollBar()->setValue(previousVerticalOffsets.pop());
98
emit q->currentRootChanged(index);
100
if (q->viewOptions().direction == Qt::RightToLeft) {
101
animLeftToRight = !animLeftToRight;
104
flipAnimTimeLine->setCurrentTime(0);
108
int previousVerticalOffset()
110
return previousVerticalOffsets.isEmpty() ? 0 : previousVerticalOffsets.top();
113
int treeDepth(const QModelIndex& headerIndex) const
116
QModelIndex index = headerIndex;
117
while (index.isValid()) {
118
index = index.parent();
124
QPainterPath trianglePath(qreal width = 5, qreal height = 10) {
125
QPainterPath path(QPointF(-width / 2, 0.0));
126
path.lineTo(width, -height / 2);
127
path.lineTo(width, height / 2);
128
path.lineTo(-width / 2, 0.0);
133
void updateScrollBarRange()
135
int childCount = q->model()->rowCount(currentRootIndex);
136
int pageSize = q->height();
137
int itemH = q->sizeHintForIndex(q->model()->index(0, 0)).height();
138
q->verticalScrollBar()->setRange(0, (childCount * itemH) - pageSize);
139
q->verticalScrollBar()->setPageStep(pageSize);
140
q->verticalScrollBar()->setSingleStep(itemH);
143
FlipScrollView * const q;
144
QPersistentModelIndex hoveredIndex;
145
QPersistentModelIndex watchedIndexForDrag;
147
QTimeLine *flipAnimTimeLine;
148
bool animLeftToRight;
151
static const int FLIP_ANIM_DURATION = 200;
154
QPersistentModelIndex currentRootIndex;
155
QStack<QPersistentModelIndex> previousRootIndices;
156
QStack<int> previousVerticalOffsets;
159
FlipScrollView::FlipScrollView(QWidget *parent)
160
: QAbstractItemView(parent)
161
, d(new Private(this))
163
connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(openItem(QModelIndex)));
164
connect(d->flipAnimTimeLine, SIGNAL(valueChanged(qreal)), this, SLOT(updateFlipAnimation(qreal)));
165
d->flipAnimTimeLine->setDuration(Private::FLIP_ANIM_DURATION);
166
d->flipAnimTimeLine->setCurrentTime(Private::FLIP_ANIM_DURATION);
167
setIconSize(QSize(KIconLoader::SizeMedium, KIconLoader::SizeMedium));
168
setMouseTracking(true);
170
QPalette viewPalette(palette());
171
viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
172
setPalette(viewPalette);
173
setAutoFillBackground(true);
175
FlipScrollView::~FlipScrollView()
180
void FlipScrollView::setCurrentRoot(const QModelIndex &index)
182
d->setCurrentRoot(index);
185
void FlipScrollView::viewRoot()
188
while (d->currentRoot().isValid()) {
189
index = d->currentRoot();
190
d->setCurrentRoot(d->currentRoot().parent());
191
setCurrentIndex(index);
193
update(d->hoveredIndex);
194
d->hoveredIndex = index;
197
QModelIndex FlipScrollView::indexAt(const QPoint& point) const
199
const int items = model()->rowCount(d->currentRoot());
200
const int rowIndex = (point.y() + verticalOffset()) / itemHeight();
202
if (rowIndex < items) {
203
return model()->index(rowIndex, 0, d->currentRoot());
205
return QModelIndex();
209
int FlipScrollView::itemHeight() const
211
//TODO: reset on font change
212
if (d->itemHeight < 1) {
213
QModelIndex index = model()->index(0, 0, d->currentRoot());
214
d->itemHeight = sizeHintForIndex(index).height();
217
return d->itemHeight;
220
void FlipScrollView::scrollTo(const QModelIndex& index, ScrollHint hint)
222
if (!index.isValid()) {
226
const QRect itemRect = visualRect(index);
227
if (itemRect.isValid() && hint == EnsureVisible) {
228
if (itemRect.top() < 0) {
229
verticalScrollBar()->setValue(verticalScrollBar()->value() +
231
} else if (itemRect.bottom() > height()) {
232
verticalScrollBar()->setValue(verticalScrollBar()->value() +
233
(itemRect.bottom() - height()));
239
bool FlipScrollView::isIndexHidden(const QModelIndex&) const
244
QRect FlipScrollView::visualRect(const QModelIndex& index) const
246
const int leftOffset = ItemDelegate::ITEM_LEFT_MARGIN;
248
if (index.parent() != d->currentRoot() &&
249
index.parent() != d->previousRoot() &&
250
index.parent() != (QModelIndex)d->hoveredIndex) {
254
const bool parentIsPreviousRoot = d->previousRoot().isValid() && index.parent() == d->previousRoot();
255
if (parentIsPreviousRoot && d->flipAnimTimeLine->state() == QTimeLine::NotRunning) {
259
const int scrollBarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
260
QRect itemRect(leftOffset, index.row() * itemHeight() - verticalOffset() ,
261
width() - leftOffset - scrollBarWidth, itemHeight());
263
const qreal timeValue = d->flipAnimTimeLine->currentValue();
264
if (index.parent() == d->currentRoot()) {
265
if (d->animLeftToRight) {
266
itemRect.translate(itemRect.width() * (1 - timeValue), 0);
268
itemRect.translate(-itemRect.width() * (1 - timeValue), 0);
271
if (d->animLeftToRight) {
272
itemRect.translate((-timeValue*itemRect.width()), 0);
274
itemRect.translate((timeValue*itemRect.width()), 0);
281
int FlipScrollView::horizontalOffset() const
286
int FlipScrollView::verticalOffset() const
288
return verticalScrollBar()->value();
291
QRegion FlipScrollView::visualRegionForSelection(const QItemSelection& selection) const
294
foreach(const QModelIndex& index , selection.indexes()) {
295
region |= visualRect(index);
299
QModelIndex FlipScrollView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers)
301
QModelIndex index = currentIndex();
302
// kDebug() << "Moving cursor with current index" << index.data(Qt::DisplayRole);
303
switch (cursorAction) {
305
if (!currentIndex().isValid()) {
306
index = model()->index(model()->rowCount(d->currentRoot()) - 1, 0, d->currentRoot());
307
} else if (currentIndex().row() > 0) {
308
index = currentIndex().sibling(currentIndex().row() - 1,
309
currentIndex().column());
313
if (!currentIndex().isValid()) {
314
index = model()->index(0, 0, d->currentRoot());
315
} else if (currentIndex().row() <
316
model()->rowCount(currentIndex().parent()) - 1) {
317
index = currentIndex().sibling(currentIndex().row() + 1,
318
currentIndex().column());
322
if (d->currentRoot().isValid()) {
323
index = d->currentRoot();
324
d->setCurrentRoot(d->currentRoot().parent());
325
setCurrentIndex(index);
329
if (model()->hasChildren(currentIndex())) {
330
openItem(currentIndex());
331
// return the new current index set by openItem()
332
index = currentIndex();
340
// clear the hovered index
341
update(d->hoveredIndex);
342
d->hoveredIndex = index;
344
//kDebug() << "New index after move" << index.data(Qt::DisplayRole);
349
void FlipScrollView::setSelection(const QRect& rect , QItemSelectionModel::SelectionFlags flags)
351
QItemSelection selection;
352
selection.select(indexAt(rect.topLeft()), indexAt(rect.bottomRight()));
353
selectionModel()->select(selection, flags);
356
void FlipScrollView::openItem(const QModelIndex& index)
358
if (model()->canFetchMore(index)) {
359
model()->fetchMore(index);
362
const bool hasChildren = model()->hasChildren(index);
365
d->setCurrentRoot(index);
366
setCurrentIndex(model()->index(0, 0, index));
368
//TODO Emit a signal to open/execute the item
372
void FlipScrollView::resizeEvent(QResizeEvent*)
374
d->updateScrollBarRange();
377
void FlipScrollView::mousePressEvent(QMouseEvent *event)
379
d->watchedIndexForDrag = indexAt(event->pos());
380
QAbstractItemView::mousePressEvent(event);
383
void FlipScrollView::mouseReleaseEvent(QMouseEvent *event)
385
d->watchedIndexForDrag = QModelIndex();
386
QAbstractItemView::mouseReleaseEvent(event);
389
void FlipScrollView::mouseMoveEvent(QMouseEvent *event)
391
const QModelIndex itemUnderMouse = indexAt(event->pos());
392
if (itemUnderMouse != d->hoveredIndex) {
393
update(itemUnderMouse);
394
update(d->hoveredIndex);
396
d->hoveredIndex = itemUnderMouse;
397
setCurrentIndex(d->hoveredIndex);
400
QAbstractItemView::mouseMoveEvent(event);
403
void FlipScrollView::keyPressEvent(QKeyEvent *event)
405
if (event->key() == Qt::Key_Enter ||
406
event->key() == Qt::Key_Return) {
407
moveCursor(MoveRight, event->modifiers());
412
if (event->key() == Qt::Key_Escape &&
413
d->currentRoot().isValid()) {
414
moveCursor(MoveLeft, event->modifiers());
419
QAbstractItemView::keyPressEvent(event);
422
void FlipScrollView::leaveEvent(QEvent *event)
425
d->hoveredIndex = QModelIndex();
426
setCurrentIndex(QModelIndex());
429
void FlipScrollView::paintItems(QPainter &painter, QPaintEvent *event, QModelIndex &root)
431
const int rows = model()->rowCount(root);
432
//kDebug() << "painting" << rows << "items";
434
for (int i = 0; i < rows; ++i) {
435
QModelIndex index = model()->index(i, 0, root);
437
QStyleOptionViewItem option = viewOptions();
438
option.rect = visualRect(index);
440
// only draw items intersecting the region of the widget
442
if (!event->rect().intersects(option.rect)) {
446
if (selectionModel()->isSelected(index)) {
447
option.state |= QStyle::State_Selected;
450
if (index == d->hoveredIndex) {
451
option.state |= QStyle::State_MouseOver;
454
if (index == currentIndex()) {
455
option.state |= QStyle::State_HasFocus;
458
itemDelegate(index)->paint(&painter, option, index);
460
if (model()->hasChildren(index)) {
462
painter.setPen(Qt::NoPen);
463
// there is an assumption made here that the delegate will fill the background
464
// with the selected color or some similar color which contrasts well with the
465
// highlighted text color
466
if (option.state & QStyle::State_MouseOver) {
467
painter.setBrush(palette().highlight());
469
painter.setBrush(palette().text());
472
QRect triRect = option.rect;
473
QPainterPath tPath = d->trianglePath();
474
if (option.direction == Qt::LeftToRight) {
475
triRect.setLeft(triRect.right() - ItemDelegate::ITEM_RIGHT_MARGIN);
476
painter.translate(triRect.center().x() - 6, triRect.y() + (option.rect.height() / 2));
479
triRect.setRight(triRect.left() + ItemDelegate::ITEM_RIGHT_MARGIN);
480
painter.translate(triRect.center().x() + 6, triRect.y() + (option.rect.height() / 2));
484
if (option.direction == Qt::LeftToRight) {
488
painter.drawPath(tPath);
489
painter.resetTransform();
495
void FlipScrollView::paintEvent(QPaintEvent * event)
497
QPalette viewPalette(palette());
498
viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
499
setPalette(viewPalette);
500
setAutoFillBackground(true);
502
QPainter painter(viewport());
503
painter.setRenderHint(QPainter::Antialiasing);
506
QModelIndex currentRoot = d->currentRoot();
507
QModelIndex previousRoot = d->animLeftToRight ? d->previousRoot() : (QModelIndex)d->hoveredIndex;
508
//kDebug() << "current root is" << currentRoot.data(Qt::DisplayRole).value<QString>();
510
paintItems(painter, event, currentRoot);
512
const qreal timerValue = d->flipAnimTimeLine->currentValue();
514
if (timerValue < 1.0) {
515
//kDebug() << "previous root is" << previousRoot.data(Qt::DisplayRole).value<QString>();
516
paintItems(painter, event, previousRoot);
518
if (d->flipAnimTimeLine->state() != QTimeLine::Running) {
519
d->flipAnimTimeLine->start();
523
QRectF eventRect = event->rect();
526
QStyle::State state = 0;
527
if (currentRoot.isValid()) {
528
state |= QStyle::State_Enabled;
531
if (currentRoot.isValid() || previousRoot.isValid()) {
533
if (!previousRoot.isValid()) {
534
opacity = timerValue;
535
} else if (!currentRoot.isValid()) {
536
opacity = 1 - timerValue;
541
void FlipScrollView::startDrag(Qt::DropActions supportedActions)
543
kDebug() << "Starting UrlItemView drag with actions" << supportedActions;
545
if (!d->watchedIndexForDrag.isValid()) {
549
QDrag *drag = new QDrag(this);
550
QMimeData *mimeData = model()->mimeData(selectionModel()->selectedIndexes());
552
if (mimeData->text().isNull()) {
556
drag->setMimeData(mimeData);
558
QModelIndex idx = selectionModel()->selectedIndexes().first();
559
QIcon icon = idx.data(Qt::DecorationRole).value<QIcon>();
560
drag->setPixmap(icon.pixmap(IconSize(KIconLoader::Desktop)));
565
void FlipScrollView::updateFlipAnimation(qreal)
567
setDirtyRegion(rect());
570
#include "flipscrollview.moc"