kde-workspace/plasma/applets/kickoff/ui/flipscrollview.cpp
Ivailo Monev efbde9e504 plasma: organize sub-directories
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2015-12-24 22:48:47 +02:00

567 lines
17 KiB
C++

/*
Copyright 2007 Robert Knight <robertknight@gmail.com>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
// Own
#include "ui/flipscrollview.h"
// Qt
#include <QCoreApplication>
#include <QtGui/qevent.h>
#include <QtGui/qevent.h>
#include <QPainter>
#include <QScrollBar>
#include <QStack>
#include <QTimeLine>
// KDE
#include <KDebug>
#include <KGlobalSettings>
#include <KIconLoader>
#include <KColorScheme>
#include "ui/itemdelegate.h"
using namespace Kickoff;
class FlipScrollView::Private
{
public:
Private(FlipScrollView *view)
: q(view)
, flipAnimTimeLine(new QTimeLine())
, animLeftToRight(true)
, itemHeight(-1) {
}
~Private()
{
delete flipAnimTimeLine;
}
QModelIndex currentRoot() const
{
if (currentRootIndex.isValid()) {
return currentRootIndex;
} else {
return q->rootIndex();
}
}
QModelIndex previousRoot() const
{
if (previousRootIndices.isEmpty()) {
return QModelIndex();
}
return previousRootIndices.top();
}
void setCurrentRoot(const QModelIndex& index)
{
if (previousRootIndices.isEmpty() || previousRootIndices.top() != index) {
// we're entering into a submenu
//kDebug() << "pushing" << currentRootIndex.data(Qt::DisplayRole).value<QString>();
animLeftToRight = true;
hoveredIndex = QModelIndex();
previousRootIndices.push(currentRootIndex);
currentRootIndex = index;
previousVerticalOffsets.append(q->verticalOffset());
updateScrollBarRange();
q->verticalScrollBar()->setValue(0);
} else {
// we're exiting to the parent menu
//kDebug() << "popping" << previousRootIndices.top().data(Qt::DisplayRole).value<QString>();
animLeftToRight = false;
hoveredIndex = currentRootIndex;
previousRootIndices.pop();
//if (!previousRootIndices.isEmpty()) {
// kDebug() << "now the previos root is" << previousRootIndices.top().data(Qt::DisplayRole).value<QString>();
//}
currentRootIndex = index;
updateScrollBarRange();
q->verticalScrollBar()->setValue(previousVerticalOffsets.pop());
}
emit q->currentRootChanged(index);
if (q->viewOptions().direction == Qt::RightToLeft) {
animLeftToRight = !animLeftToRight;
}
flipAnimTimeLine->setCurrentTime(0);
q->update();
}
int previousVerticalOffset()
{
return previousVerticalOffsets.isEmpty() ? 0 : previousVerticalOffsets.top();
}
int treeDepth(const QModelIndex& headerIndex) const
{
int depth = 0;
QModelIndex index = headerIndex;
while (index.isValid()) {
index = index.parent();
depth++;
}
return depth;
}
QPainterPath trianglePath(qreal width = 5, qreal height = 10) {
QPainterPath path(QPointF(-width / 2, 0.0));
path.lineTo(width, -height / 2);
path.lineTo(width, height / 2);
path.lineTo(-width / 2, 0.0);
return path;
}
void updateScrollBarRange()
{
int childCount = q->model()->rowCount(currentRootIndex);
int pageSize = q->height();
int itemH = q->sizeHintForIndex(q->model()->index(0, 0)).height();
q->verticalScrollBar()->setRange(0, (childCount * itemH) - pageSize);
q->verticalScrollBar()->setPageStep(pageSize);
q->verticalScrollBar()->setSingleStep(itemH);
}
FlipScrollView * const q;
QPersistentModelIndex hoveredIndex;
QPersistentModelIndex watchedIndexForDrag;
QTimeLine *flipAnimTimeLine;
bool animLeftToRight;
int itemHeight;
static const int FLIP_ANIM_DURATION = 200;
private:
QPersistentModelIndex currentRootIndex;
QStack<QPersistentModelIndex> previousRootIndices;
QStack<int> previousVerticalOffsets;
};
FlipScrollView::FlipScrollView(QWidget *parent)
: QAbstractItemView(parent)
, d(new Private(this))
{
connect(this, SIGNAL(clicked(QModelIndex)), this, SLOT(openItem(QModelIndex)));
connect(d->flipAnimTimeLine, SIGNAL(valueChanged(qreal)), this, SLOT(updateFlipAnimation(qreal)));
d->flipAnimTimeLine->setDuration(Private::FLIP_ANIM_DURATION);
d->flipAnimTimeLine->setCurrentTime(Private::FLIP_ANIM_DURATION);
setIconSize(QSize(KIconLoader::SizeMedium, KIconLoader::SizeMedium));
setMouseTracking(true);
setAutoScroll(true);
QPalette viewPalette(palette());
viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
setPalette(viewPalette);
setAutoFillBackground(true);
}
FlipScrollView::~FlipScrollView()
{
delete d;
}
void FlipScrollView::setCurrentRoot(const QModelIndex &index)
{
d->setCurrentRoot(index);
}
void FlipScrollView::viewRoot()
{
QModelIndex index;
while (d->currentRoot().isValid()) {
index = d->currentRoot();
d->setCurrentRoot(d->currentRoot().parent());
setCurrentIndex(index);
}
update(d->hoveredIndex);
d->hoveredIndex = index;
}
QModelIndex FlipScrollView::indexAt(const QPoint& point) const
{
const int items = model()->rowCount(d->currentRoot());
const int rowIndex = (point.y() + verticalOffset()) / itemHeight();
if (rowIndex < items) {
return model()->index(rowIndex, 0, d->currentRoot());
} else {
return QModelIndex();
}
}
int FlipScrollView::itemHeight() const
{
//TODO: reset on font change
if (d->itemHeight < 1) {
QModelIndex index = model()->index(0, 0, d->currentRoot());
d->itemHeight = sizeHintForIndex(index).height();
}
return d->itemHeight;
}
void FlipScrollView::scrollTo(const QModelIndex& index, ScrollHint hint)
{
if (!index.isValid()) {
return;
}
const QRect itemRect = visualRect(index);
if (itemRect.isValid() && hint == EnsureVisible) {
if (itemRect.top() < 0) {
verticalScrollBar()->setValue(verticalScrollBar()->value() +
itemRect.top());
} else if (itemRect.bottom() > height()) {
verticalScrollBar()->setValue(verticalScrollBar()->value() +
(itemRect.bottom() - height()));
}
update(itemRect);
}
}
bool FlipScrollView::isIndexHidden(const QModelIndex&) const
{
return false;
}
QRect FlipScrollView::visualRect(const QModelIndex& index) const
{
const int leftOffset = ItemDelegate::ITEM_LEFT_MARGIN;
if (index.parent() != d->currentRoot() &&
index.parent() != d->previousRoot() &&
index.parent() != (QModelIndex)d->hoveredIndex) {
return QRect();
}
const bool parentIsPreviousRoot = d->previousRoot().isValid() && index.parent() == d->previousRoot();
if (parentIsPreviousRoot && d->flipAnimTimeLine->state() == QTimeLine::NotRunning) {
return QRect();
}
const int scrollBarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->width() : 0;
QRect itemRect(leftOffset, index.row() * itemHeight() - verticalOffset() ,
width() - leftOffset - scrollBarWidth, itemHeight());
const qreal timeValue = d->flipAnimTimeLine->currentValue();
if (index.parent() == d->currentRoot()) {
if (d->animLeftToRight) {
itemRect.translate(itemRect.width() * (1 - timeValue), 0);
} else {
itemRect.translate(-itemRect.width() * (1 - timeValue), 0);
}
} else {
if (d->animLeftToRight) {
itemRect.translate((-timeValue*itemRect.width()), 0);
} else {
itemRect.translate((timeValue*itemRect.width()), 0);
}
}
return itemRect;
}
int FlipScrollView::horizontalOffset() const
{
return 0;
}
int FlipScrollView::verticalOffset() const
{
return verticalScrollBar()->value();
}
QRegion FlipScrollView::visualRegionForSelection(const QItemSelection& selection) const
{
QRegion region;
foreach(const QModelIndex& index , selection.indexes()) {
region |= visualRect(index);
}
return region;
}
QModelIndex FlipScrollView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers)
{
QModelIndex index = currentIndex();
// kDebug() << "Moving cursor with current index" << index.data(Qt::DisplayRole);
switch (cursorAction) {
case MoveUp:
if (!currentIndex().isValid()) {
index = model()->index(model()->rowCount(d->currentRoot()) - 1, 0, d->currentRoot());
} else if (currentIndex().row() > 0) {
index = currentIndex().sibling(currentIndex().row() - 1,
currentIndex().column());
}
break;
case MoveDown:
if (!currentIndex().isValid()) {
index = model()->index(0, 0, d->currentRoot());
} else if (currentIndex().row() <
model()->rowCount(currentIndex().parent()) - 1) {
index = currentIndex().sibling(currentIndex().row() + 1,
currentIndex().column());
}
break;
case MoveLeft:
if (d->currentRoot().isValid()) {
index = d->currentRoot();
d->setCurrentRoot(d->currentRoot().parent());
setCurrentIndex(index);
}
break;
case MoveRight:
if (model()->hasChildren(currentIndex())) {
openItem(currentIndex());
// return the new current index set by openItem()
index = currentIndex();
}
break;
default:
// Do nothing
break;
}
// clear the hovered index
update(d->hoveredIndex);
d->hoveredIndex = index;
//kDebug() << "New index after move" << index.data(Qt::DisplayRole);
return index;
}
void FlipScrollView::setSelection(const QRect& rect , QItemSelectionModel::SelectionFlags flags)
{
QItemSelection selection;
selection.select(indexAt(rect.topLeft()), indexAt(rect.bottomRight()));
selectionModel()->select(selection, flags);
}
void FlipScrollView::openItem(const QModelIndex& index)
{
if (model()->canFetchMore(index)) {
model()->fetchMore(index);
}
const bool hasChildren = model()->hasChildren(index);
if (hasChildren) {
d->setCurrentRoot(index);
setCurrentIndex(model()->index(0, 0, index));
} else {
//TODO Emit a signal to open/execute the item
}
}
void FlipScrollView::resizeEvent(QResizeEvent*)
{
d->updateScrollBarRange();
}
void FlipScrollView::mousePressEvent(QMouseEvent *event)
{
d->watchedIndexForDrag = indexAt(event->pos());
QAbstractItemView::mousePressEvent(event);
}
void FlipScrollView::mouseReleaseEvent(QMouseEvent *event)
{
d->watchedIndexForDrag = QModelIndex();
QAbstractItemView::mouseReleaseEvent(event);
}
void FlipScrollView::mouseMoveEvent(QMouseEvent *event)
{
const QModelIndex itemUnderMouse = indexAt(event->pos());
if (itemUnderMouse != d->hoveredIndex) {
update(itemUnderMouse);
update(d->hoveredIndex);
d->hoveredIndex = itemUnderMouse;
setCurrentIndex(d->hoveredIndex);
}
QAbstractItemView::mouseMoveEvent(event);
}
void FlipScrollView::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Enter ||
event->key() == Qt::Key_Return ||
event->key() == Qt::Key_Right) {
moveCursor(MoveRight, event->modifiers());
event->accept();
return;
}
if (event->key() == Qt::Key_Escape || event->key() == Qt::Key_Left) {
if (d->currentRoot().isValid()) {
moveCursor(MoveLeft, event->modifiers());
event->accept();
return;
} else {
// we are already in the leftmost column.
kDebug() << "we are in Left-Most column, processing Key_Left";
event->accept();
emit focusNextViewLeft();
return;
}
}
QAbstractItemView::keyPressEvent(event);
}
void FlipScrollView::leaveEvent(QEvent *event)
{
Q_UNUSED(event);
d->hoveredIndex = QModelIndex();
setCurrentIndex(QModelIndex());
}
void FlipScrollView::paintItems(QPainter &painter, QPaintEvent *event, QModelIndex &root)
{
const int rows = model()->rowCount(root);
//kDebug() << "painting" << rows << "items";
for (int i = 0; i < rows; ++i) {
QModelIndex index = model()->index(i, 0, root);
QStyleOptionViewItem option = viewOptions();
option.rect = visualRect(index);
// only draw items intersecting the region of the widget
// being updated
if (!event->rect().intersects(option.rect)) {
continue;
}
if (selectionModel()->isSelected(index)) {
option.state |= QStyle::State_Selected;
}
if (index == d->hoveredIndex) {
option.state |= QStyle::State_MouseOver;
}
if (index == currentIndex()) {
option.state |= QStyle::State_HasFocus;
}
itemDelegate(index)->paint(&painter, option, index);
if (model()->hasChildren(index)) {
painter.save();
painter.setPen(Qt::NoPen);
// there is an assumption made here that the delegate will fill the background
// with the selected color or some similar color which contrasts well with the
// highlighted text color
if (option.state & QStyle::State_MouseOver) {
painter.setBrush(palette().highlight());
} else {
painter.setBrush(palette().text());
}
QRect triRect = option.rect;
QPainterPath tPath = d->trianglePath();
if (option.direction == Qt::LeftToRight) {
triRect.setLeft(triRect.right() - ItemDelegate::ITEM_RIGHT_MARGIN);
painter.translate(triRect.center().x() - 6, triRect.y() + (option.rect.height() / 2));
} else {
triRect.setRight(triRect.left() + ItemDelegate::ITEM_RIGHT_MARGIN);
painter.translate(triRect.center().x() + 6, triRect.y() + (option.rect.height() / 2));
}
if (option.direction == Qt::LeftToRight) {
painter.rotate(180);
}
painter.drawPath(tPath);
painter.resetTransform();
painter.restore();
}
}
}
void FlipScrollView::paintEvent(QPaintEvent * event)
{
QPalette viewPalette(palette());
viewPalette.setColor(QPalette::Window, palette().color(QPalette::Active, QPalette::Base));
setPalette(viewPalette);
setAutoFillBackground(true);
QPainter painter(viewport());
painter.setRenderHint(QPainter::Antialiasing);
// draw items
QModelIndex currentRoot = d->currentRoot();
QModelIndex previousRoot = d->animLeftToRight ? d->previousRoot() : (QModelIndex)d->hoveredIndex;
//kDebug() << "current root is" << currentRoot.data(Qt::DisplayRole).value<QString>();
paintItems(painter, event, currentRoot);
const qreal timerValue = d->flipAnimTimeLine->currentValue();
if (timerValue < 1.0) {
//kDebug() << "previous root is" << previousRoot.data(Qt::DisplayRole).value<QString>();
paintItems(painter, event, previousRoot);
if (d->flipAnimTimeLine->state() != QTimeLine::Running) {
d->flipAnimTimeLine->start();
}
}
// draw navigation
QStyle::State state = 0;
if (currentRoot.isValid()) {
state |= QStyle::State_Enabled;
}
}
void FlipScrollView::startDrag(Qt::DropActions supportedActions)
{
kDebug() << "Starting UrlItemView drag with actions" << supportedActions;
if (!d->watchedIndexForDrag.isValid()) {
return;
}
QDrag *drag = new QDrag(this);
QMimeData *mimeData = model()->mimeData(selectionModel()->selectedIndexes());
if (mimeData->text().isNull()) {
return;
}
drag->setMimeData(mimeData);
QModelIndex idx = selectionModel()->selectedIndexes().first();
QIcon icon = idx.data(Qt::DecorationRole).value<QIcon>();
drag->setPixmap(icon.pixmap(IconSize(KIconLoader::Desktop)));
drag->exec();
}
void FlipScrollView::updateFlipAnimation(qreal)
{
setDirtyRegion(rect());
}
#include "moc_flipscrollview.cpp"