diff --git a/widgets/scrollwidget.cpp b/widgets/scrollwidget.cpp index 5e8323b97..ecaf6a97c 100644 --- a/widgets/scrollwidget.cpp +++ b/widgets/scrollwidget.cpp @@ -25,6 +25,9 @@ #include #include #include +#include +#include +#include //KDE #include @@ -38,6 +41,39 @@ #include #include +#define DEBUG 0 + +/* + The flicking code is largely based on the behavior of + the flickable widget in QDeclerative so porting between + the two should preserve the behavior. + The code that figures out velocity could use some + improvements, in particular IGNORE_SUSPICIOUS_MOVES + is a hack that shouldn't be necessary. + */ + +//XXX fixme +// we use a timer between move events to figure out +// the velocity of a move, but sometimes we're getting move +// events with big positional changes with no break +// in between them, which causes us to compute +// huge velocities. this define just filters out +// events which come at insanly small time intervals. +// at some point we need to figure out how to do it properly +#define IGNORE_SUSPICIOUS_MOVES 1 + +// FlickThreshold determines how far the "mouse" must have moved +// before we perform a flick. +static const int FlickThreshold = 20; + + +static const qreal MinimumFlickVelocity = 200; +static const qreal MaxVelocity = 2000; + +// time it takes the widget to flick back to its +// bounds when overshot +static const qreal FixupDuration = 600; + namespace Plasma { @@ -50,8 +86,7 @@ public: bottomBorder(0), leftBorder(0), rightBorder(0), - dragging(false), - animId(0) + dragging(false) { } @@ -62,6 +97,7 @@ public: void commonConstructor() { q->setFocusPolicy(Qt::StrongFocus); + q->setFiltersChildEvents(true); layout = new QGraphicsGridLayout(q); q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); layout->setContentsMargins(1, 1, 1, 1); @@ -90,6 +126,20 @@ public: horizontalScrollBar->nativeWidget()->setMinimum(0); horizontalScrollBar->nativeWidget()->setMaximum(100); QObject::connect(horizontalScrollBar, SIGNAL(valueChanged(int)), q, SLOT(horizontalScroll(int))); + + flickAnimationX = 0; + flickAnimationY = 0; + fixupAnimation.groupX = 0; + fixupAnimation.startX = 0; + fixupAnimation.endX = 0; + fixupAnimation.groupY = 0; + fixupAnimation.startY = 0; + fixupAnimation.endY = 0; + directMoveAnimation = 0; + stealEvent = false; + hasOvershoot = true; + + alignment = Qt::AlignLeft | Qt::AlignTop; } void adjustScrollbars() @@ -233,6 +283,158 @@ public: scrollingWidget->setFlag(QGraphicsItem::ItemClipsChildrenToShape, clip); } + qreal overShootDistance(qreal velocity, qreal size) const + { + if (MaxVelocity <= 0) + return 0.0; + + velocity = qAbs(velocity); + if (velocity > MaxVelocity) + velocity = MaxVelocity; + qreal dist = size / 4 * velocity / MaxVelocity; + return dist; + } + + void animateMoveTo(const QPointF &pos) + { + qreal duration = 800; + QPointF start = widget.data()->pos(); + QSizeF threshold = q->viewportGeometry().size(); + QPointF diff = pos - start; + + //reduce if it's within the viewport + if (qAbs(diff.x()) < threshold.width() || + qAbs(diff.y()) < threshold.height()) + duration /= 2; + + directMoveAnimation->setStartValue(start); + directMoveAnimation->setEndValue(pos); + directMoveAnimation->setDuration(duration); + directMoveAnimation->start(); + } + + void flick(QPropertyAnimation *anim, + qreal velocity, + qreal val, + qreal minExtent, + qreal maxExtent, + qreal size) + { + qreal deceleration = 500; + qreal maxDistance = -1; + qreal target = 0; + // -ve velocity means list is moving up + if (velocity > 0) { + if (val < minExtent) + maxDistance = qAbs(minExtent - val + (hasOvershoot?overShootDistance(velocity,size):0)); + target = minExtent; + deceleration = -deceleration; + } else { + if (val > maxExtent) + maxDistance = qAbs(maxExtent - val) + (hasOvershoot?overShootDistance(velocity,size):0); + target = maxExtent; + } + if (maxDistance > 0) { + qreal v = velocity; + if (MaxVelocity != -1 && MaxVelocity < qAbs(v)) { + if (v < 0) + v = -MaxVelocity; + else + v = MaxVelocity; + } + qreal duration = qAbs(v / deceleration); + qreal diffY = v * duration + (0.5 * deceleration * duration * duration); + qreal startY = val; + qreal endY = startY + diffY; + + if (velocity > 0) { + if (endY > target) + endY = startY + maxDistance; + } else { + if (endY < target) + endY = startY - maxDistance; + } + duration = qAbs((endY-startY)/ (-v/2)); + +#if DEBUG + qDebug()<<"XXX velocity = "<setEasingCurve(QEasingCurve::OutCirc); + flickAnimationY->setEasingCurve(QEasingCurve::OutCirc); + + + fixupAnimation.groupX = new QSequentialAnimationGroup(widget.data()); + fixupAnimation.groupY = new QSequentialAnimationGroup(widget.data()); + fixupAnimation.startX = new QPropertyAnimation(widget.data(), + "x", widget.data()); + fixupAnimation.startY = new QPropertyAnimation(widget.data(), + "y", widget.data()); + fixupAnimation.endX = new QPropertyAnimation(widget.data(), + "x", widget.data()); + fixupAnimation.endY = new QPropertyAnimation(widget.data(), + "y", widget.data()); + fixupAnimation.groupX->addAnimation( + fixupAnimation.startX); + fixupAnimation.groupY->addAnimation( + fixupAnimation.startY); + fixupAnimation.groupX->addAnimation( + fixupAnimation.endX); + fixupAnimation.groupY->addAnimation( + fixupAnimation.endY); + + fixupAnimation.startX->setEasingCurve(QEasingCurve::InQuad); + fixupAnimation.endX->setEasingCurve(QEasingCurve::OutQuint); + fixupAnimation.startY->setEasingCurve(QEasingCurve::InQuad); + fixupAnimation.endY->setEasingCurve(QEasingCurve::OutQuint); + + QObject::connect(fixupAnimation.groupX, + SIGNAL(stateChanged(QAbstractAnimation::State, + QAbstractAnimation::State)), + q, SIGNAL(scrollStateChanged(QAbstractAnimation::State, + QAbstractAnimation::State))); + QObject::connect(fixupAnimation.groupY, + SIGNAL(stateChanged(QAbstractAnimation::State, + QAbstractAnimation::State)), + q, SIGNAL(scrollStateChanged(QAbstractAnimation::State, + QAbstractAnimation::State))); + + directMoveAnimation = new QPropertyAnimation(widget.data(), + "pos", + widget.data()); + QObject::connect(directMoveAnimation, SIGNAL(finished()), + q, SLOT(fixupX())); + QObject::connect(directMoveAnimation, SIGNAL(finished()), + q, SLOT(fixupY())); + QObject::connect(directMoveAnimation, + SIGNAL(stateChanged(QAbstractAnimation::State, + QAbstractAnimation::State)), + q, SIGNAL(scrollStateChanged(QAbstractAnimation::State, + QAbstractAnimation::State))); + directMoveAnimation->setEasingCurve(QEasingCurve::OutCirc); + } + } + void deleteFlickAnimations() + { + if (flickAnimationX) + flickAnimationX->stop(); + if (flickAnimationY) + flickAnimationY->stop(); + delete flickAnimationX; + delete flickAnimationY; + delete fixupAnimation.groupX; + delete fixupAnimation.groupY; + delete directMoveAnimation; + } + void setScrollX() + { + if (horizontalScrollBarPolicy != Qt::ScrollBarAlwaysOff) { + horizontalScrollBar->blockSignals(true); + horizontalScrollBar->setValue(-widget.data()->pos().x()/10.); + horizontalScrollBar->blockSignals(false); + } + } + void setScrollY() + { + if (verticalScrollBarPolicy != Qt::ScrollBarAlwaysOff) { + verticalScrollBar->blockSignals(true); + verticalScrollBar->setValue(-widget.data()->pos().y()/10.); + verticalScrollBar->blockSignals(false); } } @@ -320,9 +838,31 @@ public: QPointF dragHandleClicked; QSetdragHandles; bool dragging; - int animId; QTimer *adjustScrollbarsTimer; static const int borderSize = 4; + + QPointF pressPos; + QPointF pressScrollPos; + QPointF velocity; + QPointF lastPos; + QTime pressTime; + QTime lastPosTime; + QPropertyAnimation *flickAnimationX; + QPropertyAnimation *flickAnimationY; + struct { + QAnimationGroup *groupX; + QPropertyAnimation *startX; + QPropertyAnimation *endX; + + QAnimationGroup *groupY; + QPropertyAnimation *startY; + QPropertyAnimation *endY; + } fixupAnimation; + QPropertyAnimation *directMoveAnimation; + bool stealEvent; + bool hasOvershoot; + + Qt::Alignment alignment; }; @@ -348,23 +888,20 @@ ScrollWidget::~ScrollWidget() void ScrollWidget::setWidget(QGraphicsWidget *widget) { if (d->widget && d->widget.data() != widget) { + d->deleteFlickAnimations(); d->widget.data()->removeEventFilter(this); delete d->widget.data(); } d->widget = widget; - Plasma::Animator::self()->registerScrollingManager(this); - connect(Plasma::Animator::self(), - SIGNAL(scrollStateChanged(QGraphicsWidget *, QAbstractAnimation::State, - QAbstractAnimation::State)), this, - SLOT(scrollStateChanged(QGraphicsWidget *, QAbstractAnimation::State, - QAbstractAnimation::State))); + d->createFlickAnimations(); //it's not good it's setting a size policy here, but it's done to be retrocompatible with older applications - if (widget) { + connect(widget, SIGNAL(xChanged()), this, SLOT(setScrollX())); + connect(widget, SIGNAL(yChanged()), this, SLOT(setScrollY())); widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); widget->setParentItem(d->scrollingWidget); - widget->setPos(QPoint(0,0)); + widget->setPos(d->minXExtent(), d->minYExtent()); widget->installEventFilter(this); d->adjustScrollbarsTimer->start(200); } @@ -547,35 +1084,37 @@ void ScrollWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event) return; } - if (d->animId) { - Animator::self()->stopItemMovement(d->animId); - } + d->handleMouseMoveEvent(event); + event->accept(); - event->ignore(); + return QGraphicsWidget::mouseMoveEvent(event); } void ScrollWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) { - if (!(event->buttons() & Qt::LeftButton)) { - event->ignore(); + if (!d->widget) { + return; } - if (d->animId) { - Animator::self()->stopItemMovement(d->animId); - } + d->handleMousePressEvent(event); + event->accept(); } void ScrollWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { - event->ignore(); + if (!d->widget) { + return; + } + + d->handleMouseReleaseEvent(event); + event->accept(); } void ScrollWidget::wheelEvent(QGraphicsSceneWheelEvent *) { - if (d->animId) { - Animator::self()->stopItemMovement(d->animId); + if (!d->widget) { + return; } - } bool ScrollWidget::eventFilter(QObject *watched, QEvent *event) @@ -599,12 +1138,13 @@ bool ScrollWidget::eventFilter(QObject *watched, QEvent *event) event->type() == QEvent::GraphicsSceneMouseRelease) { QGraphicsSceneMouseEvent *me = static_cast(event); + qreal dragDistance = (d->dragHandleClicked - me->scenePos()).manhattanLength(); if (event->type() == QEvent::GraphicsSceneMousePress) { d->dragHandleClicked = me->scenePos(); } - d->dragging = (d->dragging | (d->dragHandleClicked.toPoint() - me->scenePos().toPoint()).manhattanLength() > (KGlobalSettings::dndEventDelay())); + d->dragging = (d->dragging || dragDistance > (KGlobalSettings::dndEventDelay())); if (scene() && event->type() != QEvent::GraphicsSceneMouseMove) { scene()->sendEvent(this, event); @@ -643,7 +1183,57 @@ QSizeF ScrollWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) con return hint; } + +bool ScrollWidget::sceneEventFilter(QGraphicsItem *i, QEvent *e) +{ + //only the scrolling widget and its children + if (!d->widget.data() || + (!d->widget.data()->isAncestorOf(i) && i != d->scrollingWidget)) + return false; + + bool stealThisEvent = d->stealEvent; + stealThisEvent &= (e->type() == QEvent::GraphicsSceneMousePress || + e->type() == QEvent::GraphicsSceneMouseMove || + e->type() == QEvent::GraphicsSceneMouseRelease); +#if DEBUG + qDebug()<<"sceneEventFilter = " <type()) { + case QEvent::GraphicsSceneMousePress: + d->handleMousePressEvent(static_cast(e)); + break; + case QEvent::GraphicsSceneMouseMove: + d->handleMouseMoveEvent(static_cast(e)); + break; + case QEvent::GraphicsSceneMouseRelease: + d->handleMouseReleaseEvent(static_cast(e)); + break; + default: + break; + } + if (stealThisEvent) + return true; + return QGraphicsWidget::sceneEventFilter(i, e); +} + +void Plasma::ScrollWidget::setAlignment(Qt::Alignment align) +{ + d->alignment = align; + if (d->widget.data() && + d->widget.data()->isVisible()) { + d->widget.data()->setPos(d->minXExtent(), + d->minYExtent()); + } +} + +Qt::Alignment Plasma::ScrollWidget::alignment() const +{ + return d->alignment; +} + } // namespace Plasma + #include diff --git a/widgets/scrollwidget.h b/widgets/scrollwidget.h index ada95b586..b5fec05c1 100644 --- a/widgets/scrollwidget.h +++ b/widgets/scrollwidget.h @@ -50,6 +50,7 @@ class PLASMA_EXPORT ScrollWidget : public QGraphicsWidget Q_PROPERTY(QSizeF contentsSize READ contentsSize) Q_PROPERTY(QRectF viewportGeometry READ viewportGeometry) Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment) public: @@ -79,6 +80,18 @@ public: */ QGraphicsWidget *widget() const; + /** + * Sets the alignment for the inner widget. + * It is only meaningful if the inner widget is smaller + * than the viewport. + */ + void setAlignment(Qt::Alignment align); + + /** + * @return currently set alignment for the inner widget + */ + Qt::Alignment alignment() const; + /** * Sets the horizontal scrollbar policy * @@ -196,6 +209,7 @@ protected: bool eventFilter(QObject *watched, QEvent *event); void focusInEvent(QFocusEvent *event); QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint) const; + bool sceneEventFilter(QGraphicsItem *i, QEvent *e); private: ScrollWidgetPrivate * const d; @@ -206,8 +220,10 @@ private: Q_PRIVATE_SLOT(d, void makeItemVisible()) Q_PRIVATE_SLOT(d, void cleanupDragHandles(QObject *destroyed)) Q_PRIVATE_SLOT(d, void adjustScrollbars()) - Q_PRIVATE_SLOT(d, void scrollStateChanged(QGraphicsWidget *, QAbstractAnimation::State, - QAbstractAnimation::State)) + Q_PRIVATE_SLOT(d, void fixupX()) + Q_PRIVATE_SLOT(d, void fixupY()) + Q_PRIVATE_SLOT(d, void setScrollX()) + Q_PRIVATE_SLOT(d, void setScrollY()) friend class ScrollWidgetPrivate; };