mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-25 11:22:52 +00:00
567 lines
17 KiB
C++
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 <QKeyEvent>
|
|
#include <QMouseEvent>
|
|
#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"
|