Improve kinetic scrolling on ScrollWidget

As previously discussed with this approach we can actually properly intercept events from child items. Furthermore now we properly "steal" events which cause a flick (it's important for child items to properly act on mouseReleaseEvents and not on mousePressEvents as some like to do, since it's the release events that cause a flick) so items don't get clicks when flicked. The physics and especially the overshoot behavior is a lot better in this code as well.
Review http://reviewboard.kde.org/r/3312/


svn path=/trunk/KDE/kdelibs/; revision=1104841
This commit is contained in:
Zack Rusin 2010-03-18 18:14:39 +00:00
parent 34ca7fdba5
commit 82702e7043
2 changed files with 640 additions and 34 deletions

View File

@ -25,6 +25,9 @@
#include <QApplication> #include <QApplication>
#include <QWidget> #include <QWidget>
#include <QTimer> #include <QTimer>
#include <QTime>
#include <QPropertyAnimation>
#include <QSequentialAnimationGroup>
//KDE //KDE
#include <kmimetype.h> #include <kmimetype.h>
@ -38,6 +41,39 @@
#include <plasma/animator.h> #include <plasma/animator.h>
#include <plasma/svg.h> #include <plasma/svg.h>
#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 namespace Plasma
{ {
@ -50,8 +86,7 @@ public:
bottomBorder(0), bottomBorder(0),
leftBorder(0), leftBorder(0),
rightBorder(0), rightBorder(0),
dragging(false), dragging(false)
animId(0)
{ {
} }
@ -62,6 +97,7 @@ public:
void commonConstructor() void commonConstructor()
{ {
q->setFocusPolicy(Qt::StrongFocus); q->setFocusPolicy(Qt::StrongFocus);
q->setFiltersChildEvents(true);
layout = new QGraphicsGridLayout(q); layout = new QGraphicsGridLayout(q);
q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
layout->setContentsMargins(1, 1, 1, 1); layout->setContentsMargins(1, 1, 1, 1);
@ -90,6 +126,20 @@ public:
horizontalScrollBar->nativeWidget()->setMinimum(0); horizontalScrollBar->nativeWidget()->setMinimum(0);
horizontalScrollBar->nativeWidget()->setMaximum(100); horizontalScrollBar->nativeWidget()->setMaximum(100);
QObject::connect(horizontalScrollBar, SIGNAL(valueChanged(int)), q, SLOT(horizontalScroll(int))); 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() void adjustScrollbars()
@ -233,6 +283,158 @@ public:
scrollingWidget->setFlag(QGraphicsItem::ItemClipsChildrenToShape, clip); 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 = "<<v <<", target = "<< target
<<", maxDist = "<<maxDistance;
qDebug()<<"duration = "<<duration<<" secs, ("
<< (duration * 1000) <<" msecs)";
qDebug()<<"startY = "<<startY;
qDebug()<<"endY = "<<endY;
qDebug()<<"overshoot = "<<overShootDistance(v, size);
qDebug()<<"avg velocity = "<< ((endY-startY)/duration);
#endif
anim->setStartValue(startY);
anim->setEndValue(endY);
anim->setDuration(duration * 1000);
anim->start();
} else {
if (anim == flickAnimationX)
fixupX();
else
fixupY();
}
}
void flickX(qreal velocity)
{
flick(flickAnimationX, velocity, widget.data()->x(), minXExtent(), maxXExtent(),
q->viewportGeometry().width());
}
void flickY(qreal velocity)
{
flick(flickAnimationY, velocity, widget.data()->y(),minYExtent(), maxYExtent(),
q->viewportGeometry().height());
}
void fixup(QAnimationGroup *group,
QPropertyAnimation *start, QPropertyAnimation *end,
qreal val, qreal minExtent, qreal maxExtent)
{
if (val > minExtent || maxExtent > minExtent) {
if (!qFuzzyCompare(val, minExtent)) {
if (FixupDuration) {
qreal dist = minExtent - val;
start->setStartValue(val);
start->setEndValue(minExtent - dist/2);
end->setStartValue(minExtent - dist/2);
end->setEndValue(minExtent);
start->setDuration(FixupDuration/4);
end->setDuration(3*FixupDuration/4);
group->start();
} else {
QObject *obj = start->targetObject();
obj->setProperty(start->propertyName(), minExtent);
}
}
} else if (val < maxExtent) {
if (FixupDuration) {
qreal dist = maxExtent - val;
start->setStartValue(val);
start->setEndValue(maxExtent - dist/2);
end->setStartValue(maxExtent - dist/2);
end->setEndValue(maxExtent);
start->setDuration(FixupDuration/4);
end->setDuration(3*FixupDuration/4);
group->start();
} else {
QObject *obj = start->targetObject();
obj->setProperty(start->propertyName(), maxExtent);
}
}
}
void fixupX()
{
fixup(fixupAnimation.groupX, fixupAnimation.startX, fixupAnimation.endX,
widget.data()->x(), minXExtent(), maxXExtent());
}
void fixupY()
{
fixup(fixupAnimation.groupY, fixupAnimation.startY, fixupAnimation.endY,
widget.data()->y(), minYExtent(), maxYExtent());
}
void makeRectVisible() void makeRectVisible()
{ {
if (!widget) { if (!widget) {
@ -264,9 +466,7 @@ public:
delta.setX(viewRect.right() - mappedRect.right()); delta.setX(viewRect.right() - mappedRect.right());
} }
animId = Animator::self()->moveItem( animateMoveTo(widget.data()->pos() + delta);
widget.data(), Plasma::Animator::SlideOutMovement,
(widget.data()->pos() + delta).toPoint());
} }
void makeItemVisible(QGraphicsItem *itemToBeVisible) void makeItemVisible(QGraphicsItem *itemToBeVisible)
@ -293,11 +493,329 @@ public:
dragHandles.remove(static_cast<QGraphicsWidget *>(destroyed)); dragHandles.remove(static_cast<QGraphicsWidget *>(destroyed));
} }
void scrollStateChanged(QGraphicsWidget *widget, QAbstractAnimation::State newState, void stopAnimations()
QAbstractAnimation::State oldState)
{ {
if (widget == q) { flickAnimationX->stop();
emit q->scrollStateChanged(newState, oldState); flickAnimationY->stop();
fixupAnimation.groupX->stop();
fixupAnimation.groupY->stop();
}
void handleMousePressEvent(QGraphicsSceneMouseEvent *event)
{
lastPos = QPoint();
lastPosTime = QTime::currentTime();
pressPos = event->scenePos();
pressScrollPos = -q->scrollPosition();
pressTime = QTime::currentTime();
velocity = QPointF();
stopAnimations();
}
void handleMouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (lastPosTime.isNull())
return;
bool rejectY = false;
bool rejectX = false;
bool moved = false;
if (canYFlick()) {
int dy = int(event->scenePos().y() - pressPos.y());
if (qAbs(dy) > QApplication::startDragDistance() || elapsed(pressTime) > 200) {
qreal newY = dy + pressScrollPos.y();
const qreal minY = minYExtent();
const qreal maxY = maxYExtent();
if (newY > minY)
newY = minY + (newY - minY) / 2;
if (newY < maxY && maxY - minY <= 0)
newY = maxY + (newY - maxY) / 2;
if (!hasOvershoot && (newY > minY || newY < maxY)) {
if (newY > minY)
newY = minY;
else if (newY < maxY)
newY = maxY;
else
rejectY = true;
}
if (!rejectY && stealEvent) {
widget.data()->setY(qRound(newY));
moved = true;
}
if (qAbs(dy) > QApplication::startDragDistance())
stealEvent = true;
}
}
if (canXFlick()) {
int dx = int(event->scenePos().x() - pressPos.x());
if (qAbs(dx) > QApplication::startDragDistance() || elapsed(pressTime) > 200) {
qreal newX = dx + pressScrollPos.x();
const qreal minX = minXExtent();
const qreal maxX = maxXExtent();
if (newX > minX)
newX = minX + (newX - minX) / 2;
if (newX < maxX && maxX - minX <= 0)
newX = maxX + (newX - maxX) / 2;
if (!hasOvershoot && (newX > minX || newX < maxX)) {
if (newX > minX)
newX = minX;
else if (newX < maxX)
newX = maxX;
else
rejectX = true;
}
if (!rejectX && stealEvent) {
widget.data()->setX(qRound(newX));
moved = true;
}
if (qAbs(dx) > QApplication::startDragDistance())
stealEvent = true;
}
}
if (!lastPos.isNull()) {
qreal msecs = qreal(restart(lastPosTime));
qreal elapsed = msecs / 1000.;
#if IGNORE_SUSPICIOUS_MOVES
if (msecs > 3) {
#endif
if (elapsed <= 0)
elapsed = 1;
if (canYFlick()) {
qreal diff = event->scenePos().y() - lastPos.y();
// average to reduce the effect of spurious moves
velocity.setY( velocity.y() + (diff / elapsed) );
velocity.setY( velocity.y() / 2 );
}
if (canXFlick()) {
qreal diff = event->scenePos().x() - lastPos.x();
// average to reduce the effect of spurious moves
velocity.setX( velocity.x() + (diff / elapsed) );
velocity.setX( velocity.x() / 2 );
}
#if IGNORE_SUSPICIOUS_MOVES
}
#endif
}
if (rejectX) velocity.setX(0);
if (rejectY) velocity.setY(0);
lastPos = event->scenePos();
}
void handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
stealEvent = false;
if (lastPosTime.isNull())
return;
if (elapsed(lastPosTime) > 100) {
// if we drag then pause before release we should not cause a flick.
velocity = QPointF();
}
if (qAbs(velocity.y()) > 10 &&
qAbs(event->scenePos().y() - pressPos.y()) > FlickThreshold) {
qreal vVelocity = velocity.y();
// Minimum velocity to avoid annoyingly slow flicks.
if (qAbs(vVelocity) < MinimumFlickVelocity)
vVelocity = vVelocity < 0 ? -MinimumFlickVelocity : MinimumFlickVelocity;
flickY(vVelocity);
} else {
fixupY();
}
if (qAbs(velocity.x()) > 10 &&
qAbs(event->scenePos().x() - pressPos.x()) > FlickThreshold) {
qreal hVelocity = velocity.x();
// Minimum velocity to avoid annoyingly slow flicks.
if (qAbs(hVelocity) < MinimumFlickVelocity)
hVelocity = hVelocity < 0 ? -MinimumFlickVelocity : MinimumFlickVelocity;
flickX(hVelocity);
} else {
fixupX();
}
lastPosTime = QTime();
}
qreal minXExtent() const
{
if (alignment & Qt::AlignLeft)
return 0;
else {
qreal vWidth = q->viewportGeometry().width();
qreal cWidth = q->contentsSize().width();
if (cWidth < vWidth) {
if (alignment & Qt::AlignRight)
return vWidth - cWidth;
else if (alignment & Qt::AlignHCenter)
return vWidth / 2 - cWidth / 2;
}
}
return 0;
}
qreal maxXExtent() const
{
return q->viewportGeometry().width() -
q->contentsSize().width();
}
qreal minYExtent() const
{
if (alignment & Qt::AlignTop)
return 0;
else {
qreal vHeight = q->viewportGeometry().height();
qreal cHeight = q->contentsSize().height();
if (cHeight < vHeight) {
if (alignment & Qt::AlignBottom)
return vHeight - cHeight;
else if (alignment & Qt::AlignVCenter)
return vHeight / 2 - cHeight / 2;
}
}
return 0;
}
qreal maxYExtent() const
{
return q->viewportGeometry().height() -
q->contentsSize().height();
}
bool canXFlick() const
{
return !qFuzzyCompare(q->contentsSize().width() + borderSize,
q->viewportGeometry().width());
}
bool canYFlick() const
{
return !qFuzzyCompare(q->contentsSize().height() + borderSize,
q->viewportGeometry().height());
}
int elapsed(const QTime &t) const
{
int n = t.msecsTo(QTime::currentTime());
if (n < 0) // passed midnight
n += 86400 * 1000;
return n;
}
int restart(QTime &t) const
{
QTime time = QTime::currentTime();
int n = t.msecsTo(time);
if (n < 0) // passed midnight
n += 86400*1000;
t = time;
return n;
}
void createFlickAnimations()
{
if (widget.data()) {
flickAnimationX = new QPropertyAnimation(widget.data(),
"x", widget.data());
flickAnimationY = new QPropertyAnimation(widget.data(),
"y", widget.data());
QObject::connect(flickAnimationX, SIGNAL(finished()),
q, SLOT(fixupX()));
QObject::connect(flickAnimationY, SIGNAL(finished()),
q, SLOT(fixupY()));
QObject::connect(flickAnimationX,
SIGNAL(stateChanged(QAbstractAnimation::State,
QAbstractAnimation::State)),
q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
QAbstractAnimation::State)));
QObject::connect(flickAnimationY,
SIGNAL(stateChanged(QAbstractAnimation::State,
QAbstractAnimation::State)),
q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
QAbstractAnimation::State)));
flickAnimationX->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; QPointF dragHandleClicked;
QSet<QGraphicsWidget *>dragHandles; QSet<QGraphicsWidget *>dragHandles;
bool dragging; bool dragging;
int animId;
QTimer *adjustScrollbarsTimer; QTimer *adjustScrollbarsTimer;
static const int borderSize = 4; 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) void ScrollWidget::setWidget(QGraphicsWidget *widget)
{ {
if (d->widget && d->widget.data() != widget) { if (d->widget && d->widget.data() != widget) {
d->deleteFlickAnimations();
d->widget.data()->removeEventFilter(this); d->widget.data()->removeEventFilter(this);
delete d->widget.data(); delete d->widget.data();
} }
d->widget = widget; d->widget = widget;
Plasma::Animator::self()->registerScrollingManager(this); d->createFlickAnimations();
connect(Plasma::Animator::self(),
SIGNAL(scrollStateChanged(QGraphicsWidget *, QAbstractAnimation::State,
QAbstractAnimation::State)), this,
SLOT(scrollStateChanged(QGraphicsWidget *, QAbstractAnimation::State,
QAbstractAnimation::State)));
//it's not good it's setting a size policy here, but it's done to be retrocompatible with older applications //it's not good it's setting a size policy here, but it's done to be retrocompatible with older applications
if (widget) { if (widget) {
connect(widget, SIGNAL(xChanged()), this, SLOT(setScrollX()));
connect(widget, SIGNAL(yChanged()), this, SLOT(setScrollY()));
widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
widget->setParentItem(d->scrollingWidget); widget->setParentItem(d->scrollingWidget);
widget->setPos(QPoint(0,0)); widget->setPos(d->minXExtent(), d->minYExtent());
widget->installEventFilter(this); widget->installEventFilter(this);
d->adjustScrollbarsTimer->start(200); d->adjustScrollbarsTimer->start(200);
} }
@ -547,35 +1084,37 @@ void ScrollWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
return; return;
} }
if (d->animId) { d->handleMouseMoveEvent(event);
Animator::self()->stopItemMovement(d->animId); event->accept();
}
event->ignore(); return QGraphicsWidget::mouseMoveEvent(event);
} }
void ScrollWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) void ScrollWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
{ {
if (!(event->buttons() & Qt::LeftButton)) { if (!d->widget) {
event->ignore(); return;
} }
if (d->animId) { d->handleMousePressEvent(event);
Animator::self()->stopItemMovement(d->animId); event->accept();
}
} }
void ScrollWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) void ScrollWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{ {
event->ignore(); if (!d->widget) {
return;
}
d->handleMouseReleaseEvent(event);
event->accept();
} }
void ScrollWidget::wheelEvent(QGraphicsSceneWheelEvent *) void ScrollWidget::wheelEvent(QGraphicsSceneWheelEvent *)
{ {
if (d->animId) { if (!d->widget) {
Animator::self()->stopItemMovement(d->animId); return;
} }
} }
bool ScrollWidget::eventFilter(QObject *watched, QEvent *event) bool ScrollWidget::eventFilter(QObject *watched, QEvent *event)
@ -599,12 +1138,13 @@ bool ScrollWidget::eventFilter(QObject *watched, QEvent *event)
event->type() == QEvent::GraphicsSceneMouseRelease) { event->type() == QEvent::GraphicsSceneMouseRelease) {
QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(event); QGraphicsSceneMouseEvent *me = static_cast<QGraphicsSceneMouseEvent *>(event);
qreal dragDistance = (d->dragHandleClicked - me->scenePos()).manhattanLength();
if (event->type() == QEvent::GraphicsSceneMousePress) { if (event->type() == QEvent::GraphicsSceneMousePress) {
d->dragHandleClicked = me->scenePos(); 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) { if (scene() && event->type() != QEvent::GraphicsSceneMouseMove) {
scene()->sendEvent(this, event); scene()->sendEvent(this, event);
@ -643,7 +1183,57 @@ QSizeF ScrollWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) con
return hint; 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 = " <<i<<", "
<<QTime::currentTime().toString(QString::fromLatin1("hh:mm:ss.zzz"));
#endif
switch (e->type()) {
case QEvent::GraphicsSceneMousePress:
d->handleMousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(e));
break;
case QEvent::GraphicsSceneMouseMove:
d->handleMouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(e));
break;
case QEvent::GraphicsSceneMouseRelease:
d->handleMouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(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 } // namespace Plasma
#include <scrollwidget.moc> #include <scrollwidget.moc>

View File

@ -50,6 +50,7 @@ class PLASMA_EXPORT ScrollWidget : public QGraphicsWidget
Q_PROPERTY(QSizeF contentsSize READ contentsSize) Q_PROPERTY(QSizeF contentsSize READ contentsSize)
Q_PROPERTY(QRectF viewportGeometry READ viewportGeometry) Q_PROPERTY(QRectF viewportGeometry READ viewportGeometry)
Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet) Q_PROPERTY(QString styleSheet READ styleSheet WRITE setStyleSheet)
Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment)
public: public:
@ -79,6 +80,18 @@ public:
*/ */
QGraphicsWidget *widget() const; 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 * Sets the horizontal scrollbar policy
* *
@ -196,6 +209,7 @@ protected:
bool eventFilter(QObject *watched, QEvent *event); bool eventFilter(QObject *watched, QEvent *event);
void focusInEvent(QFocusEvent *event); void focusInEvent(QFocusEvent *event);
QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint) const; QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint) const;
bool sceneEventFilter(QGraphicsItem *i, QEvent *e);
private: private:
ScrollWidgetPrivate * const d; ScrollWidgetPrivate * const d;
@ -206,8 +220,10 @@ private:
Q_PRIVATE_SLOT(d, void makeItemVisible()) Q_PRIVATE_SLOT(d, void makeItemVisible())
Q_PRIVATE_SLOT(d, void cleanupDragHandles(QObject *destroyed)) Q_PRIVATE_SLOT(d, void cleanupDragHandles(QObject *destroyed))
Q_PRIVATE_SLOT(d, void adjustScrollbars()) Q_PRIVATE_SLOT(d, void adjustScrollbars())
Q_PRIVATE_SLOT(d, void scrollStateChanged(QGraphicsWidget *, QAbstractAnimation::State, Q_PRIVATE_SLOT(d, void fixupX())
QAbstractAnimation::State)) Q_PRIVATE_SLOT(d, void fixupY())
Q_PRIVATE_SLOT(d, void setScrollX())
Q_PRIVATE_SLOT(d, void setScrollY())
friend class ScrollWidgetPrivate; friend class ScrollWidgetPrivate;
}; };