~ubuntu-branches/ubuntu/utopic/kde-workspace/utopic-proposed

« back to all changes in this revision

Viewing changes to plasma/desktop/applets/kickoff/ui/flipscrollview.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Michał Zając
  • Date: 2011-07-09 08:31:15 UTC
  • Revision ID: james.westby@ubuntu.com-20110709083115-ohyxn6z93mily9fc
Tags: upstream-4.6.90
Import upstream version 4.6.90

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
    Copyright 2007 Robert Knight <robertknight@gmail.com>
 
3
 
 
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.
 
8
 
 
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.
 
13
 
 
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.
 
18
*/
 
19
 
 
20
// Own
 
21
#include "ui/flipscrollview.h"
 
22
 
 
23
// Qt
 
24
#include <QMouseEvent>
 
25
#include <QPainter>
 
26
#include <QScrollBar>
 
27
#include <QStack>
 
28
#include <QTimeLine>
 
29
 
 
30
// KDE
 
31
#include <KDebug>
 
32
#include <KGlobalSettings>
 
33
#include <KIconLoader>
 
34
#include <KColorScheme>
 
35
 
 
36
#include "ui/itemdelegate.h"
 
37
 
 
38
using namespace Kickoff;
 
39
 
 
40
class FlipScrollView::Private
 
41
{
 
42
public:
 
43
    Private(FlipScrollView *view)
 
44
            : q(view)
 
45
            , flipAnimTimeLine(new QTimeLine())
 
46
            , animLeftToRight(true)
 
47
            , itemHeight(-1) {
 
48
    }
 
49
 
 
50
    ~Private()
 
51
    {
 
52
        delete flipAnimTimeLine;
 
53
    }
 
54
 
 
55
    QModelIndex currentRoot() const
 
56
    {
 
57
        if (currentRootIndex.isValid()) {
 
58
            return currentRootIndex;
 
59
        } else {
 
60
            return q->rootIndex();
 
61
        }
 
62
    }
 
63
 
 
64
    QModelIndex previousRoot() const
 
65
    {
 
66
        if (previousRootIndices.isEmpty()) {
 
67
            return QModelIndex();
 
68
        }
 
69
        return previousRootIndices.top();
 
70
    }
 
71
 
 
72
    void setCurrentRoot(const QModelIndex& index)
 
73
    {
 
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);
 
84
        } else {
 
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>();
 
92
            //}
 
93
            currentRootIndex = index;
 
94
            updateScrollBarRange();
 
95
            q->verticalScrollBar()->setValue(previousVerticalOffsets.pop());
 
96
        }
 
97
 
 
98
        emit q->currentRootChanged(index);
 
99
 
 
100
        if (q->viewOptions().direction == Qt::RightToLeft) {
 
101
            animLeftToRight = !animLeftToRight;
 
102
        }
 
103
 
 
104
        flipAnimTimeLine->setCurrentTime(0);
 
105
        q->update();
 
106
    }
 
107
 
 
108
    int previousVerticalOffset()
 
109
    {
 
110
        return previousVerticalOffsets.isEmpty() ? 0 : previousVerticalOffsets.top();
 
111
    }
 
112
 
 
113
    int treeDepth(const QModelIndex& headerIndex) const
 
114
    {
 
115
        int depth = 0;
 
116
        QModelIndex index = headerIndex;
 
117
        while (index.isValid()) {
 
118
            index = index.parent();
 
119
            depth++;
 
120
        }
 
121
        return depth;
 
122
    }
 
123
 
 
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);
 
129
 
 
130
        return path;
 
131
    }
 
132
 
 
133
    void updateScrollBarRange()
 
134
    {
 
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);
 
141
    }
 
142
 
 
143
    FlipScrollView * const q;
 
144
    QPersistentModelIndex hoveredIndex;
 
145
    QPersistentModelIndex watchedIndexForDrag;
 
146
 
 
147
    QTimeLine *flipAnimTimeLine;
 
148
    bool animLeftToRight;
 
149
 
 
150
    int itemHeight;
 
151
    static const int FLIP_ANIM_DURATION = 200;
 
152
 
 
153
private:
 
154
    QPersistentModelIndex currentRootIndex;
 
155
    QStack<QPersistentModelIndex> previousRootIndices;
 
156
    QStack<int> previousVerticalOffsets;
 
157
};
 
158
 
 
159
FlipScrollView::FlipScrollView(QWidget *parent)
 
160
        : QAbstractItemView(parent)
 
161
        , d(new Private(this))
 
162
{
 
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);
 
169
    setAutoScroll(true);
 
170
    QPalette viewPalette(palette());
 
171
    viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
 
172
    setPalette(viewPalette);
 
173
    setAutoFillBackground(true);
 
174
}
 
175
FlipScrollView::~FlipScrollView()
 
176
{
 
177
    delete d;
 
178
}
 
179
 
 
180
void FlipScrollView::setCurrentRoot(const QModelIndex &index)
 
181
{
 
182
    d->setCurrentRoot(index);
 
183
}
 
184
 
 
185
void FlipScrollView::viewRoot()
 
186
{
 
187
    QModelIndex index;
 
188
    while (d->currentRoot().isValid()) {
 
189
        index = d->currentRoot();
 
190
        d->setCurrentRoot(d->currentRoot().parent());
 
191
        setCurrentIndex(index);
 
192
    }
 
193
    update(d->hoveredIndex);
 
194
    d->hoveredIndex = index;
 
195
}
 
196
 
 
197
QModelIndex FlipScrollView::indexAt(const QPoint& point) const
 
198
{
 
199
    const int items = model()->rowCount(d->currentRoot());
 
200
    const int rowIndex = (point.y() + verticalOffset()) / itemHeight();
 
201
 
 
202
    if (rowIndex < items) {
 
203
        return model()->index(rowIndex, 0, d->currentRoot());
 
204
    } else {
 
205
        return QModelIndex();
 
206
    }
 
207
}
 
208
 
 
209
int FlipScrollView::itemHeight() const
 
210
{
 
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();
 
215
    }
 
216
 
 
217
    return d->itemHeight;
 
218
}
 
219
 
 
220
void FlipScrollView::scrollTo(const QModelIndex& index, ScrollHint hint)
 
221
{
 
222
    if (!index.isValid()) {
 
223
        return;
 
224
    }
 
225
 
 
226
    const QRect itemRect = visualRect(index);
 
227
    if (itemRect.isValid() && hint == EnsureVisible) {
 
228
        if (itemRect.top() < 0) {
 
229
            verticalScrollBar()->setValue(verticalScrollBar()->value() +
 
230
                                          itemRect.top());
 
231
        } else if (itemRect.bottom() > height()) {
 
232
            verticalScrollBar()->setValue(verticalScrollBar()->value() +
 
233
                                          (itemRect.bottom() - height()));
 
234
        }
 
235
        update(itemRect);
 
236
    }
 
237
}
 
238
 
 
239
bool FlipScrollView::isIndexHidden(const QModelIndex&) const
 
240
{
 
241
    return false;
 
242
}
 
243
 
 
244
QRect FlipScrollView::visualRect(const QModelIndex& index) const
 
245
{
 
246
    const int leftOffset = ItemDelegate::ITEM_LEFT_MARGIN;
 
247
 
 
248
    if (index.parent() != d->currentRoot() &&
 
249
            index.parent() != d->previousRoot() &&
 
250
            index.parent() != (QModelIndex)d->hoveredIndex) {
 
251
        return QRect();
 
252
    }
 
253
 
 
254
    const bool parentIsPreviousRoot = d->previousRoot().isValid() && index.parent() == d->previousRoot();
 
255
    if (parentIsPreviousRoot && d->flipAnimTimeLine->state() == QTimeLine::NotRunning) {
 
256
        return QRect();
 
257
    }
 
258
 
 
259
    const int scrollBarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
 
260
    QRect itemRect(leftOffset, index.row() * itemHeight() - verticalOffset() ,
 
261
                   width() - leftOffset - scrollBarWidth, itemHeight());
 
262
 
 
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);
 
267
        } else {
 
268
            itemRect.translate(-itemRect.width() * (1 - timeValue), 0);
 
269
        }
 
270
    } else {
 
271
        if (d->animLeftToRight) {
 
272
            itemRect.translate((-timeValue*itemRect.width()), 0);
 
273
        } else {
 
274
            itemRect.translate((timeValue*itemRect.width()), 0);
 
275
        }
 
276
    }
 
277
 
 
278
    return itemRect;
 
279
}
 
280
 
 
281
int FlipScrollView::horizontalOffset() const
 
282
{
 
283
    return 0;
 
284
}
 
285
 
 
286
int FlipScrollView::verticalOffset() const
 
287
{
 
288
    return verticalScrollBar()->value();
 
289
}
 
290
 
 
291
QRegion FlipScrollView::visualRegionForSelection(const QItemSelection& selection) const
 
292
{
 
293
    QRegion region;
 
294
    foreach(const QModelIndex& index , selection.indexes()) {
 
295
        region |= visualRect(index);
 
296
    }
 
297
    return region;
 
298
}
 
299
QModelIndex FlipScrollView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers)
 
300
{
 
301
    QModelIndex index = currentIndex();
 
302
    // kDebug() << "Moving cursor with current index" << index.data(Qt::DisplayRole);
 
303
    switch (cursorAction) {
 
304
    case MoveUp:
 
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());
 
310
        }
 
311
        break;
 
312
    case MoveDown:
 
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());
 
319
        }
 
320
        break;
 
321
    case MoveLeft:
 
322
        if (d->currentRoot().isValid()) {
 
323
            index = d->currentRoot();
 
324
            d->setCurrentRoot(d->currentRoot().parent());
 
325
            setCurrentIndex(index);
 
326
        }
 
327
        break;
 
328
    case MoveRight:
 
329
        if (model()->hasChildren(currentIndex())) {
 
330
            openItem(currentIndex());
 
331
            // return the new current index set by openItem()
 
332
            index = currentIndex();
 
333
        }
 
334
        break;
 
335
    default:
 
336
        // Do nothing
 
337
        break;
 
338
    }
 
339
 
 
340
    // clear the hovered index
 
341
    update(d->hoveredIndex);
 
342
    d->hoveredIndex = index;
 
343
 
 
344
    //kDebug() << "New index after move" << index.data(Qt::DisplayRole);
 
345
 
 
346
    return index;
 
347
}
 
348
 
 
349
void FlipScrollView::setSelection(const QRect& rect , QItemSelectionModel::SelectionFlags flags)
 
350
{
 
351
    QItemSelection selection;
 
352
    selection.select(indexAt(rect.topLeft()), indexAt(rect.bottomRight()));
 
353
    selectionModel()->select(selection, flags);
 
354
}
 
355
 
 
356
void FlipScrollView::openItem(const QModelIndex& index)
 
357
{
 
358
    if (model()->canFetchMore(index)) {
 
359
        model()->fetchMore(index);
 
360
    }
 
361
 
 
362
    const bool hasChildren = model()->hasChildren(index);
 
363
 
 
364
    if (hasChildren) {
 
365
        d->setCurrentRoot(index);
 
366
        setCurrentIndex(model()->index(0, 0, index));
 
367
    } else {
 
368
        //TODO Emit a signal to open/execute the item
 
369
    }
 
370
}
 
371
 
 
372
void FlipScrollView::resizeEvent(QResizeEvent*)
 
373
{
 
374
    d->updateScrollBarRange();
 
375
}
 
376
 
 
377
void FlipScrollView::mousePressEvent(QMouseEvent *event)
 
378
{
 
379
    d->watchedIndexForDrag = indexAt(event->pos());
 
380
    QAbstractItemView::mousePressEvent(event);
 
381
}
 
382
 
 
383
void FlipScrollView::mouseReleaseEvent(QMouseEvent *event)
 
384
{
 
385
    d->watchedIndexForDrag = QModelIndex();
 
386
    QAbstractItemView::mouseReleaseEvent(event);
 
387
}
 
388
 
 
389
void FlipScrollView::mouseMoveEvent(QMouseEvent *event)
 
390
{
 
391
    const QModelIndex itemUnderMouse = indexAt(event->pos());
 
392
    if (itemUnderMouse != d->hoveredIndex) {
 
393
        update(itemUnderMouse);
 
394
        update(d->hoveredIndex);
 
395
 
 
396
        d->hoveredIndex = itemUnderMouse;
 
397
        setCurrentIndex(d->hoveredIndex);
 
398
    }
 
399
 
 
400
    QAbstractItemView::mouseMoveEvent(event);
 
401
}
 
402
 
 
403
void FlipScrollView::keyPressEvent(QKeyEvent *event)
 
404
{
 
405
    if (event->key() == Qt::Key_Enter ||
 
406
            event->key() == Qt::Key_Return) {
 
407
        moveCursor(MoveRight, event->modifiers());
 
408
        event->accept();
 
409
        return;
 
410
    }
 
411
 
 
412
    if (event->key() == Qt::Key_Escape &&
 
413
            d->currentRoot().isValid()) {
 
414
        moveCursor(MoveLeft, event->modifiers());
 
415
        event->accept();
 
416
        return;
 
417
    }
 
418
 
 
419
    QAbstractItemView::keyPressEvent(event);
 
420
}
 
421
 
 
422
void FlipScrollView::leaveEvent(QEvent *event)
 
423
{
 
424
    Q_UNUSED(event);
 
425
    d->hoveredIndex = QModelIndex();
 
426
    setCurrentIndex(QModelIndex());
 
427
}
 
428
 
 
429
void FlipScrollView::paintItems(QPainter &painter, QPaintEvent *event, QModelIndex &root)
 
430
{
 
431
    const int rows = model()->rowCount(root);
 
432
    //kDebug() << "painting" << rows << "items";
 
433
 
 
434
    for (int i = 0; i < rows; ++i) {
 
435
        QModelIndex index = model()->index(i, 0, root);
 
436
 
 
437
        QStyleOptionViewItem option = viewOptions();
 
438
        option.rect = visualRect(index);
 
439
 
 
440
        // only draw items intersecting the region of the widget
 
441
        // being updated
 
442
        if (!event->rect().intersects(option.rect)) {
 
443
            continue;
 
444
        }
 
445
 
 
446
        if (selectionModel()->isSelected(index)) {
 
447
            option.state |= QStyle::State_Selected;
 
448
        }
 
449
 
 
450
        if (index == d->hoveredIndex) {
 
451
            option.state |= QStyle::State_MouseOver;
 
452
        }
 
453
 
 
454
        if (index == currentIndex()) {
 
455
            option.state |= QStyle::State_HasFocus;
 
456
        }
 
457
 
 
458
        itemDelegate(index)->paint(&painter, option, index);
 
459
 
 
460
        if (model()->hasChildren(index)) {
 
461
            painter.save();
 
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());
 
468
            } else {
 
469
                painter.setBrush(palette().text());
 
470
            }
 
471
 
 
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));
 
477
 
 
478
            } else {
 
479
                triRect.setRight(triRect.left() + ItemDelegate::ITEM_RIGHT_MARGIN);
 
480
                painter.translate(triRect.center().x() + 6, triRect.y() + (option.rect.height() / 2));
 
481
            }
 
482
 
 
483
 
 
484
            if (option.direction == Qt::LeftToRight) {
 
485
                painter.rotate(180);
 
486
            }
 
487
 
 
488
            painter.drawPath(tPath);
 
489
            painter.resetTransform();
 
490
            painter.restore();
 
491
        }
 
492
    }
 
493
}
 
494
 
 
495
void FlipScrollView::paintEvent(QPaintEvent * event)
 
496
{
 
497
    QPalette viewPalette(palette());
 
498
    viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
 
499
    setPalette(viewPalette);
 
500
    setAutoFillBackground(true);
 
501
 
 
502
    QPainter painter(viewport());
 
503
    painter.setRenderHint(QPainter::Antialiasing);
 
504
 
 
505
    // draw items
 
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>();
 
509
 
 
510
    paintItems(painter, event, currentRoot);
 
511
 
 
512
    const qreal timerValue = d->flipAnimTimeLine->currentValue();
 
513
 
 
514
    if (timerValue < 1.0) {
 
515
        //kDebug() << "previous root is" << previousRoot.data(Qt::DisplayRole).value<QString>();
 
516
        paintItems(painter, event, previousRoot);
 
517
 
 
518
        if (d->flipAnimTimeLine->state() != QTimeLine::Running) {
 
519
            d->flipAnimTimeLine->start();
 
520
        }
 
521
    }
 
522
 
 
523
    QRectF eventRect = event->rect();
 
524
 
 
525
    // draw navigation
 
526
    QStyle::State state = 0;
 
527
    if (currentRoot.isValid()) {
 
528
        state |= QStyle::State_Enabled;
 
529
    }
 
530
 
 
531
    if (currentRoot.isValid() || previousRoot.isValid()) {
 
532
        qreal opacity = 1.0;
 
533
        if (!previousRoot.isValid()) {
 
534
            opacity = timerValue;
 
535
        } else if (!currentRoot.isValid()) {
 
536
            opacity = 1 - timerValue;
 
537
        }
 
538
    }
 
539
}
 
540
 
 
541
void FlipScrollView::startDrag(Qt::DropActions supportedActions)
 
542
{
 
543
    kDebug() << "Starting UrlItemView drag with actions" << supportedActions;
 
544
 
 
545
    if (!d->watchedIndexForDrag.isValid()) {
 
546
        return;
 
547
    }
 
548
 
 
549
    QDrag *drag = new QDrag(this);
 
550
    QMimeData *mimeData = model()->mimeData(selectionModel()->selectedIndexes());
 
551
 
 
552
    if (mimeData->text().isNull()) {
 
553
        return;
 
554
    }
 
555
 
 
556
    drag->setMimeData(mimeData);
 
557
 
 
558
    QModelIndex idx = selectionModel()->selectedIndexes().first();
 
559
    QIcon icon = idx.data(Qt::DecorationRole).value<QIcon>();
 
560
    drag->setPixmap(icon.pixmap(IconSize(KIconLoader::Desktop)));
 
561
 
 
562
    drag->exec();
 
563
}
 
564
 
 
565
void FlipScrollView::updateFlipAnimation(qreal)
 
566
{
 
567
    setDirtyRegion(rect());
 
568
}
 
569
 
 
570
#include "flipscrollview.moc"