1591 lines
53 KiB
C++
1591 lines
53 KiB
C++
/*
|
|
* Copyright 2009 Marco Martin <notmart@gmail.com>
|
|
*
|
|
* This program 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, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this program; if not, write to the
|
|
* Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "scrollwidget.h"
|
|
|
|
#include <cmath>
|
|
|
|
//Qt
|
|
#include <QGraphicsSceneResizeEvent>
|
|
#include <QGraphicsGridLayout>
|
|
#include <QGraphicsScene>
|
|
#include <QApplication>
|
|
#include <QKeyEvent>
|
|
#include <QWidget>
|
|
#include <QTimer>
|
|
#include <QTime>
|
|
#include <QPropertyAnimation>
|
|
#include <QSequentialAnimationGroup>
|
|
|
|
#include <QLabel>
|
|
|
|
//KDE
|
|
#include <kdebug.h>
|
|
#include <kglobalsettings.h>
|
|
#include <kiconloader.h>
|
|
#include <ktextedit.h>
|
|
#include <ktextbrowser.h>
|
|
|
|
//Plasma
|
|
#include <plasma/widgets/scrollbar.h>
|
|
#include <plasma/widgets/svgwidget.h>
|
|
#include <plasma/widgets/label.h>
|
|
#include <plasma/widgets/textedit.h>
|
|
#include <plasma/widgets/textbrowser.h>
|
|
#include <plasma/animator.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
|
|
{
|
|
|
|
class ScrollWidgetPrivate
|
|
{
|
|
public:
|
|
enum Gesture {
|
|
GestureNone = 0,
|
|
GestureUndefined,
|
|
GestureScroll,
|
|
GestureZoom
|
|
};
|
|
|
|
ScrollWidgetPrivate(ScrollWidget *parent)
|
|
: q(parent),
|
|
topBorder(0),
|
|
bottomBorder(0),
|
|
leftBorder(0),
|
|
rightBorder(0),
|
|
dragging(false),
|
|
overflowBordersVisible(true),
|
|
multitouchGesture(GestureNone)
|
|
{
|
|
}
|
|
|
|
~ScrollWidgetPrivate()
|
|
{
|
|
}
|
|
|
|
void commonConstructor()
|
|
{
|
|
q->setFocusPolicy(Qt::StrongFocus);
|
|
q->setFiltersChildEvents(true);
|
|
layout = new QGraphicsGridLayout(q);
|
|
q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
scrollingWidget = new QGraphicsWidget(q);
|
|
scrollingWidget->setFlag(QGraphicsItem::ItemHasNoContents);
|
|
scrollingWidget->installEventFilter(q);
|
|
layout->addItem(scrollingWidget, 0, 0);
|
|
borderSvg = new Plasma::Svg(q);
|
|
borderSvg->setImagePath("widgets/scrollwidget");
|
|
|
|
adjustScrollbarsTimer = new QTimer(q);
|
|
adjustScrollbarsTimer->setSingleShot(true);
|
|
QObject::connect(adjustScrollbarsTimer, SIGNAL(timeout()), q, SLOT(adjustScrollbars()));
|
|
|
|
wheelTimer = new QTimer(q);
|
|
wheelTimer->setSingleShot(true);
|
|
|
|
verticalScrollBarPolicy = Qt::ScrollBarAsNeeded;
|
|
verticalScrollBar = new Plasma::ScrollBar(q);
|
|
verticalScrollBar->setFocusPolicy(Qt::NoFocus);
|
|
layout->addItem(verticalScrollBar, 0, 1);
|
|
verticalScrollBar->nativeWidget()->setMinimum(0);
|
|
verticalScrollBar->nativeWidget()->setMaximum(100);
|
|
QObject::connect(verticalScrollBar, SIGNAL(valueChanged(int)), q, SLOT(verticalScroll(int)));
|
|
|
|
horizontalScrollBarPolicy = Qt::ScrollBarAsNeeded;
|
|
horizontalScrollBar = new Plasma::ScrollBar(q);
|
|
verticalScrollBar->setFocusPolicy(Qt::NoFocus);
|
|
horizontalScrollBar->setOrientation(Qt::Horizontal);
|
|
layout->addItem(horizontalScrollBar, 1, 0);
|
|
horizontalScrollBar->nativeWidget()->setMinimum(0);
|
|
horizontalScrollBar->nativeWidget()->setMaximum(100);
|
|
QObject::connect(horizontalScrollBar, SIGNAL(valueChanged(int)), q, SLOT(horizontalScroll(int)));
|
|
|
|
layout->setColumnSpacing(0, 0);
|
|
layout->setColumnSpacing(1, 0);
|
|
layout->setRowSpacing(0, 0);
|
|
layout->setRowSpacing(1, 0);
|
|
|
|
flickAnimationX = 0;
|
|
flickAnimationY = 0;
|
|
fixupAnimation.groupX = 0;
|
|
fixupAnimation.startX = 0;
|
|
fixupAnimation.endX = 0;
|
|
fixupAnimation.groupY = 0;
|
|
fixupAnimation.startY = 0;
|
|
fixupAnimation.endY = 0;
|
|
fixupAnimation.snapX = 0;
|
|
fixupAnimation.snapY = 0;
|
|
directMoveAnimation = 0;
|
|
stealEvent = false;
|
|
hasOvershoot = true;
|
|
|
|
alignment = Qt::AlignLeft | Qt::AlignTop;
|
|
|
|
hasContentsProperty = false;
|
|
hasOffsetProperty = false;
|
|
hasXProperty = false;
|
|
hasYProperty = false;
|
|
}
|
|
|
|
void adjustScrollbars()
|
|
{
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
const bool verticalVisible = widget.data()->size().height() > q->size().height();
|
|
const bool horizontalVisible = widget.data()->size().width() > q->size().width();
|
|
|
|
verticalScrollBar->nativeWidget()->setMaximum(qMax(0, int((widget.data()->size().height() - scrollingWidget->size().height())/10)));
|
|
verticalScrollBar->nativeWidget()->setPageStep(int(scrollingWidget->size().height())/10);
|
|
|
|
if (verticalScrollBarPolicy == Qt::ScrollBarAlwaysOff ||
|
|
!verticalVisible) {
|
|
if (layout->count() > 2 && layout->itemAt(2) == verticalScrollBar) {
|
|
layout->removeAt(2);
|
|
} else if (layout->count() > 1 && layout->itemAt(1) == verticalScrollBar) {
|
|
layout->removeAt(1);
|
|
}
|
|
verticalScrollBar->hide();
|
|
} else if (!verticalScrollBar->isVisible()) {
|
|
layout->addItem(verticalScrollBar, 0, 1);
|
|
verticalScrollBar->show();
|
|
}
|
|
|
|
horizontalScrollBar->nativeWidget()->setMaximum(qMax(0, int((widget.data()->size().width() - scrollingWidget->size().width())/10)));
|
|
horizontalScrollBar->nativeWidget()->setPageStep(int(scrollingWidget->size().width())/10);
|
|
|
|
if (horizontalScrollBarPolicy == Qt::ScrollBarAlwaysOff ||
|
|
!horizontalVisible) {
|
|
if (layout->count() > 2 && layout->itemAt(2) == horizontalScrollBar) {
|
|
layout->removeAt(2);
|
|
} else if (layout->count() > 1 && layout->itemAt(1) == horizontalScrollBar) {
|
|
layout->removeAt(1);
|
|
}
|
|
horizontalScrollBar->hide();
|
|
} else if (!horizontalScrollBar->isVisible()) {
|
|
layout->addItem(horizontalScrollBar, 1, 0);
|
|
horizontalScrollBar->show();
|
|
}
|
|
|
|
if (widget && !topBorder && verticalVisible) {
|
|
topBorder = new Plasma::SvgWidget(q);
|
|
topBorder->setSvg(borderSvg);
|
|
topBorder->setElementID("border-top");
|
|
topBorder->setZValue(900);
|
|
topBorder->resize(topBorder->effectiveSizeHint(Qt::PreferredSize));
|
|
topBorder->setVisible(overflowBordersVisible);
|
|
|
|
bottomBorder = new Plasma::SvgWidget(q);
|
|
bottomBorder->setSvg(borderSvg);
|
|
bottomBorder->setElementID("border-bottom");
|
|
bottomBorder->setZValue(900);
|
|
bottomBorder->resize(bottomBorder->effectiveSizeHint(Qt::PreferredSize));
|
|
bottomBorder->setVisible(overflowBordersVisible);
|
|
} else if (topBorder && widget && !verticalVisible) {
|
|
//FIXME: in some cases topBorder->deleteLater() is deleteNever(), why?
|
|
topBorder->hide();
|
|
bottomBorder->hide();
|
|
topBorder->deleteLater();
|
|
bottomBorder->deleteLater();
|
|
topBorder = 0;
|
|
bottomBorder = 0;
|
|
}
|
|
|
|
|
|
if (widget && !leftBorder && horizontalVisible) {
|
|
leftBorder = new Plasma::SvgWidget(q);
|
|
leftBorder->setSvg(borderSvg);
|
|
leftBorder->setElementID("border-left");
|
|
leftBorder->setZValue(900);
|
|
leftBorder->resize(leftBorder->effectiveSizeHint(Qt::PreferredSize));
|
|
leftBorder->setVisible(overflowBordersVisible);
|
|
|
|
rightBorder = new Plasma::SvgWidget(q);
|
|
rightBorder->setSvg(borderSvg);
|
|
rightBorder->setElementID("border-right");
|
|
rightBorder->setZValue(900);
|
|
rightBorder->resize(rightBorder->effectiveSizeHint(Qt::PreferredSize));
|
|
rightBorder->setVisible(overflowBordersVisible);
|
|
} else if (leftBorder && widget && !horizontalVisible) {
|
|
leftBorder->hide();
|
|
rightBorder->hide();
|
|
leftBorder->deleteLater();
|
|
rightBorder->deleteLater();
|
|
leftBorder = 0;
|
|
rightBorder = 0;
|
|
}
|
|
|
|
layout->activate();
|
|
|
|
if (topBorder) {
|
|
topBorder->resize(q->size().width(), topBorder->size().height());
|
|
bottomBorder->resize(q->size().width(), bottomBorder->size().height());
|
|
bottomBorder->setPos(0, q->size().height() - topBorder->size().height());
|
|
}
|
|
if (leftBorder) {
|
|
leftBorder->resize(leftBorder->size().width(), q->size().height());
|
|
rightBorder->resize(rightBorder->size().width(), q->size().height());
|
|
rightBorder->setPos(q->size().width() - rightBorder->size().width(), 0);
|
|
}
|
|
|
|
QSizeF widgetSize = widget.data()->size();
|
|
if (widget.data()->sizePolicy().expandingDirections() & Qt::Horizontal) {
|
|
//keep a 1 pixel border
|
|
widgetSize.setWidth(scrollingWidget->size().width());
|
|
}
|
|
if (widget.data()->sizePolicy().expandingDirections() & Qt::Vertical) {
|
|
widgetSize.setHeight(scrollingWidget->size().height());
|
|
}
|
|
widget.data()->resize(widgetSize);
|
|
|
|
adjustClipping();
|
|
}
|
|
|
|
void verticalScroll(int value)
|
|
{
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
if (!dragging) {
|
|
widget.data()->setPos(QPoint(widget.data()->pos().x(), -value*10));
|
|
}
|
|
}
|
|
|
|
void horizontalScroll(int value)
|
|
{
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
if (!dragging) {
|
|
widget.data()->setPos(QPoint(-value*10, widget.data()->pos().y()));
|
|
}
|
|
}
|
|
|
|
void adjustClipping()
|
|
{
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
const bool clip = widget.data()->size().width() > scrollingWidget->size().width() || widget.data()->size().height() > scrollingWidget->size().height();
|
|
|
|
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 = q->scrollPosition();
|
|
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;
|
|
|
|
fixupAnimation.groupX->stop();
|
|
fixupAnimation.groupY->stop();
|
|
fixupAnimation.snapX->stop();
|
|
fixupAnimation.snapY->stop();
|
|
|
|
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 (hasYProperty) {
|
|
startY = -startY;
|
|
endY = -endY;
|
|
}
|
|
|
|
|
|
#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, widgetX(), minXExtent(), maxXExtent(),
|
|
q->viewportGeometry().width());
|
|
}
|
|
void flickY(qreal velocity)
|
|
{
|
|
flick(flickAnimationY, velocity, widgetY(),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) {
|
|
//TODO: we should consider the case where there is one axis available not the other
|
|
if (hasXProperty && hasYProperty) {
|
|
val = -val;
|
|
minExtent = -minExtent;
|
|
}
|
|
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) {
|
|
if (hasXProperty && hasYProperty) {
|
|
val = -val;
|
|
maxExtent = -maxExtent;
|
|
}
|
|
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);
|
|
}
|
|
} else if (end == fixupAnimation.endX && snapSize.width() > 1 &&
|
|
q->contentsSize().width() > q->viewportGeometry().width()) {
|
|
int target = snapSize.width() * round(val/snapSize.width());
|
|
fixupAnimation.snapX->setStartValue(val);
|
|
fixupAnimation.snapX->setEndValue(target);
|
|
fixupAnimation.snapX->setDuration(FixupDuration);
|
|
fixupAnimation.snapX->start();
|
|
} else if (end == fixupAnimation.endY && snapSize.height() > 1 &&
|
|
q->contentsSize().height() > q->viewportGeometry().height()) {
|
|
int target = snapSize.height() * round(val/snapSize.height());
|
|
fixupAnimation.snapY->setStartValue(val);
|
|
fixupAnimation.snapY->setEndValue(target);
|
|
fixupAnimation.snapY->setDuration(FixupDuration);
|
|
fixupAnimation.snapY->start();
|
|
}
|
|
}
|
|
void fixupX()
|
|
{
|
|
fixup(fixupAnimation.groupX, fixupAnimation.startX, fixupAnimation.endX,
|
|
widgetX(), minXExtent(), maxXExtent());
|
|
}
|
|
void fixupY()
|
|
{
|
|
fixup(fixupAnimation.groupY, fixupAnimation.startY, fixupAnimation.endY,
|
|
widgetY(), minYExtent(), maxYExtent());
|
|
}
|
|
|
|
void makeRectVisible()
|
|
{
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
QRectF viewRect = scrollingWidget->boundingRect();
|
|
//ensure the rect is not outside the widget bounding rect
|
|
QRectF mappedRect = QRectF(QPointF(qBound((qreal)0.0, rectToBeVisible.x(), widget.data()->size().width() - rectToBeVisible.width()),
|
|
qBound((qreal)0.0, rectToBeVisible.y(), widget.data()->size().height() - rectToBeVisible.height())),
|
|
rectToBeVisible.size());
|
|
mappedRect = widget.data()->mapToItem(scrollingWidget, mappedRect).boundingRect();
|
|
|
|
if (viewRect.contains(mappedRect)) {
|
|
return;
|
|
}
|
|
|
|
QPointF delta(0, 0);
|
|
|
|
if (mappedRect.top() < 0) {
|
|
delta.setY(-mappedRect.top());
|
|
} else if (mappedRect.bottom() > viewRect.bottom()) {
|
|
delta.setY(viewRect.bottom() - mappedRect.bottom());
|
|
}
|
|
|
|
if (mappedRect.left() < 0) {
|
|
delta.setX(-mappedRect.left());
|
|
} else if (mappedRect.right() > viewRect.right()) {
|
|
delta.setX(viewRect.right() - mappedRect.right());
|
|
}
|
|
|
|
animateMoveTo(q->scrollPosition() - delta);
|
|
}
|
|
|
|
void makeItemVisible(QGraphicsItem *itemToBeVisible)
|
|
{
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
|
|
QRectF rect(widget.data()->mapFromScene(itemToBeVisible->scenePos()), itemToBeVisible->boundingRect().size());
|
|
rectToBeVisible = rect;
|
|
|
|
makeRectVisible();
|
|
}
|
|
|
|
void makeItemVisible()
|
|
{
|
|
if (widgetToBeVisible) {
|
|
makeItemVisible(widgetToBeVisible.data());
|
|
}
|
|
}
|
|
|
|
void stopAnimations()
|
|
{
|
|
flickAnimationX->stop();
|
|
flickAnimationY->stop();
|
|
fixupAnimation.groupX->stop();
|
|
fixupAnimation.groupY->stop();
|
|
}
|
|
|
|
void setWidgetX(qreal x)
|
|
{
|
|
if (hasXProperty) {
|
|
widget.data()->setProperty("scrollPositionX", -x);
|
|
} else
|
|
widget.data()->setX(x);
|
|
}
|
|
void setWidgetY(qreal y)
|
|
{
|
|
if (hasYProperty) {
|
|
widget.data()->setProperty("scrollPositionY", -y);
|
|
} else
|
|
widget.data()->setY(y);
|
|
}
|
|
qreal widgetX() const
|
|
{
|
|
if (hasXProperty) {
|
|
return -widget.data()->property("scrollPositionX").toReal();
|
|
} else
|
|
return widget.data()->x();
|
|
}
|
|
qreal widgetY() const
|
|
{
|
|
if (hasYProperty) {
|
|
return -widget.data()->property("scrollPositionY").toReal();
|
|
} else
|
|
return widget.data()->y();
|
|
}
|
|
|
|
void handleKeyPressEvent(QKeyEvent *event)
|
|
{
|
|
if (!widget.data()) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
QPointF start = q->scrollPosition();
|
|
QPointF end = start;
|
|
|
|
qreal step = 100;
|
|
|
|
switch (event->key()) {
|
|
case Qt::Key_Left:
|
|
if (canXFlick()) {
|
|
end += QPointF(-step, 0);
|
|
}
|
|
break;
|
|
case Qt::Key_Right:
|
|
if (canXFlick()) {
|
|
end += QPointF(step, 0);
|
|
}
|
|
break;
|
|
case Qt::Key_Up:
|
|
if (canYFlick()) {
|
|
end += QPointF(0, -step);
|
|
}
|
|
break;
|
|
case Qt::Key_Down:
|
|
if (canYFlick()) {
|
|
end += QPointF(0, step);
|
|
}
|
|
break;
|
|
default:
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
fixupAnimation.groupX->stop();
|
|
fixupAnimation.groupY->stop();
|
|
fixupAnimation.snapX->stop();
|
|
fixupAnimation.snapY->stop();
|
|
directMoveAnimation->setStartValue(start);
|
|
directMoveAnimation->setEndValue(end);
|
|
directMoveAnimation->setDuration(200);
|
|
directMoveAnimation->start();
|
|
}
|
|
|
|
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) {
|
|
setWidgetY(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) {
|
|
setWidgetX(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();
|
|
}
|
|
|
|
void handleWheelEvent(QGraphicsSceneWheelEvent *event)
|
|
{
|
|
//only scroll when the animation is done, this avoids to receive too many events and getting mad when they arrive from a touchpad
|
|
if (!widget.data() || wheelTimer->isActive()) {
|
|
return;
|
|
}
|
|
|
|
QPointF start = q->scrollPosition();
|
|
QPointF end = start;
|
|
|
|
//At some point we should switch to
|
|
// step = QApplication::wheelScrollLines() *
|
|
// (event->delta()/120) *
|
|
// scrollBar->singleStep();
|
|
// which gives us exactly the number of lines to scroll but the issue
|
|
// is that at this point we don't have any clue what a "line" is and if
|
|
// we make it a pixel then scrolling by 3 (default) pixels will be
|
|
// very painful
|
|
qreal step = -event->delta()/3;
|
|
|
|
//ifthe widget can scroll in a single axis and the wheel is the other one, scroll the other one
|
|
Qt::Orientation orientation = event->orientation();
|
|
if (orientation == Qt::Vertical) {
|
|
if (!canYFlick() && canXFlick()) {
|
|
end += QPointF(step, 0);
|
|
} else if (canYFlick()) {
|
|
end += QPointF(0, step);
|
|
} else {
|
|
return;
|
|
}
|
|
} else {
|
|
if (canYFlick() && !canXFlick()) {
|
|
end += QPointF(0, step);
|
|
} else if (canXFlick()) {
|
|
end += QPointF(step, 0);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
fixupAnimation.groupX->stop();
|
|
fixupAnimation.groupY->stop();
|
|
fixupAnimation.snapX->stop();
|
|
fixupAnimation.snapY->stop();
|
|
directMoveAnimation->setStartValue(start);
|
|
directMoveAnimation->setEndValue(end);
|
|
directMoveAnimation->setDuration(200);
|
|
directMoveAnimation->start();
|
|
wheelTimer->start(50);
|
|
}
|
|
|
|
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
|
|
{
|
|
//make the thing feel quite "fixed" don't permit to flick when the contents size is less than the viewport
|
|
return q->contentsSize().width() > q->viewportGeometry().width();
|
|
}
|
|
|
|
bool canYFlick() const
|
|
{
|
|
return q->contentsSize().height() > 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()) {
|
|
QString xProp = QString::fromLatin1("x");
|
|
QString yProp = QString::fromLatin1("y");
|
|
|
|
if (hasXProperty)
|
|
xProp = QString::fromLatin1("scrollPositionX");
|
|
if (hasYProperty)
|
|
yProp = QString::fromLatin1("scrollPositionY");
|
|
|
|
flickAnimationX = new QPropertyAnimation(widget.data(),
|
|
xProp.toLatin1(), widget.data());
|
|
flickAnimationY = new QPropertyAnimation(widget.data(),
|
|
yProp.toLatin1(), 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(),
|
|
xProp.toLatin1(), widget.data());
|
|
fixupAnimation.startY = new QPropertyAnimation(widget.data(),
|
|
yProp.toLatin1(), widget.data());
|
|
fixupAnimation.endX = new QPropertyAnimation(widget.data(),
|
|
xProp.toLatin1(), widget.data());
|
|
fixupAnimation.endY = new QPropertyAnimation(widget.data(),
|
|
yProp.toLatin1(), 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);
|
|
|
|
fixupAnimation.snapX = new QPropertyAnimation(widget.data(),
|
|
xProp.toLatin1(), widget.data());
|
|
fixupAnimation.snapY = new QPropertyAnimation(widget.data(),
|
|
yProp.toLatin1(), widget.data());
|
|
fixupAnimation.snapX->setEasingCurve(QEasingCurve::InOutQuad);
|
|
fixupAnimation.snapY->setEasingCurve(QEasingCurve::InOutQuad);
|
|
|
|
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(q,
|
|
"scrollPosition",
|
|
q);
|
|
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;
|
|
delete fixupAnimation.snapX;
|
|
delete fixupAnimation.snapY;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
ScrollWidget *q;
|
|
QGraphicsWidget *scrollingWidget;
|
|
QWeakPointer<QGraphicsWidget> widget;
|
|
Plasma::Svg *borderSvg;
|
|
Plasma::SvgWidget *topBorder;
|
|
Plasma::SvgWidget *bottomBorder;
|
|
Plasma::SvgWidget *leftBorder;
|
|
Plasma::SvgWidget *rightBorder;
|
|
QGraphicsGridLayout *layout;
|
|
ScrollBar *verticalScrollBar;
|
|
Qt::ScrollBarPolicy verticalScrollBarPolicy;
|
|
ScrollBar *horizontalScrollBar;
|
|
Qt::ScrollBarPolicy horizontalScrollBarPolicy;
|
|
QString styleSheet;
|
|
QWeakPointer<QGraphicsWidget> widgetToBeVisible;
|
|
QRectF rectToBeVisible;
|
|
QPointF dragHandleClicked;
|
|
bool dragging;
|
|
QTimer *adjustScrollbarsTimer;
|
|
QTimer *wheelTimer;
|
|
|
|
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;
|
|
|
|
QPropertyAnimation *snapX;
|
|
QPropertyAnimation *snapY;
|
|
} fixupAnimation;
|
|
QPropertyAnimation *directMoveAnimation;
|
|
QSizeF snapSize;
|
|
bool stealEvent;
|
|
bool hasOvershoot;
|
|
bool overflowBordersVisible;
|
|
|
|
Qt::Alignment alignment;
|
|
|
|
Gesture multitouchGesture;
|
|
|
|
bool hasContentsProperty;
|
|
bool hasOffsetProperty;
|
|
bool hasXProperty;
|
|
bool hasYProperty;
|
|
};
|
|
|
|
|
|
ScrollWidget::ScrollWidget(QGraphicsItem *parent)
|
|
: QGraphicsWidget(parent),
|
|
d(new ScrollWidgetPrivate(this))
|
|
{
|
|
d->commonConstructor();
|
|
}
|
|
|
|
ScrollWidget::ScrollWidget(QGraphicsWidget *parent)
|
|
: QGraphicsWidget(parent),
|
|
d(new ScrollWidgetPrivate(this))
|
|
{
|
|
d->commonConstructor();
|
|
}
|
|
|
|
ScrollWidget::~ScrollWidget()
|
|
{
|
|
delete d;
|
|
}
|
|
|
|
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;
|
|
//it's not good it's setting a size policy here, but it's done to be retrocompatible with older applications
|
|
if (widget) {
|
|
d->hasContentsProperty = widget->property("contentsSize").isValid();
|
|
d->hasOffsetProperty = widget->property("scrollPosition").isValid();
|
|
d->hasXProperty = widget->property("scrollPositionX").isValid();
|
|
d->hasYProperty = widget->property("scrollPositionY").isValid();
|
|
d->createFlickAnimations();
|
|
|
|
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(d->minXExtent(), d->minYExtent());
|
|
widget->installEventFilter(this);
|
|
d->adjustScrollbarsTimer->start(200);
|
|
}
|
|
}
|
|
|
|
QGraphicsWidget *ScrollWidget::widget() const
|
|
{
|
|
return d->widget.data();
|
|
}
|
|
|
|
|
|
void ScrollWidget::setHorizontalScrollBarPolicy(const Qt::ScrollBarPolicy policy)
|
|
{
|
|
d->horizontalScrollBarPolicy = policy;
|
|
}
|
|
|
|
|
|
Qt::ScrollBarPolicy ScrollWidget::horizontalScrollBarPolicy() const
|
|
{
|
|
return d->horizontalScrollBarPolicy;
|
|
}
|
|
|
|
|
|
void ScrollWidget::setVerticalScrollBarPolicy(const Qt::ScrollBarPolicy policy)
|
|
{
|
|
d->verticalScrollBarPolicy = policy;
|
|
}
|
|
|
|
Qt::ScrollBarPolicy ScrollWidget::verticalScrollBarPolicy() const
|
|
{
|
|
return d->verticalScrollBarPolicy;
|
|
}
|
|
|
|
bool ScrollWidget::overflowBordersVisible() const
|
|
{
|
|
return d->overflowBordersVisible;
|
|
}
|
|
|
|
void ScrollWidget::setOverflowBordersVisible(const bool visible)
|
|
{
|
|
if (d->overflowBordersVisible == visible) {
|
|
return;
|
|
}
|
|
|
|
d->overflowBordersVisible = visible;
|
|
d->adjustScrollbars();
|
|
}
|
|
|
|
void ScrollWidget::ensureRectVisible(const QRectF &rect)
|
|
{
|
|
if (!d->widget) {
|
|
return;
|
|
}
|
|
|
|
d->rectToBeVisible = rect;
|
|
d->makeRectVisible();
|
|
}
|
|
|
|
void ScrollWidget::ensureItemVisible(QGraphicsItem *item)
|
|
{
|
|
if (!d->widget || !item) {
|
|
return;
|
|
}
|
|
|
|
QGraphicsItem *parentOfItem = item->parentItem();
|
|
while (parentOfItem != d->widget.data()) {
|
|
if (!parentOfItem) {
|
|
return;
|
|
}
|
|
|
|
parentOfItem = parentOfItem->parentItem();
|
|
}
|
|
|
|
//since we can't ensure it'll stay alive we can delay only if it's a qgraphicswidget
|
|
QGraphicsWidget *widget = qgraphicsitem_cast<QGraphicsWidget *>(item);
|
|
if (widget) {
|
|
d->widgetToBeVisible = widget;
|
|
|
|
// We need to wait for the parent item to resize...
|
|
QTimer::singleShot(0, this, SLOT(makeItemVisible()));
|
|
} else {
|
|
d->makeItemVisible(item);
|
|
}
|
|
}
|
|
|
|
QRectF ScrollWidget::viewportGeometry() const
|
|
{
|
|
QRectF result;
|
|
if (!d->widget) {
|
|
return result;
|
|
}
|
|
|
|
return d->scrollingWidget->boundingRect();
|
|
}
|
|
|
|
QSizeF ScrollWidget::contentsSize() const
|
|
{
|
|
if (d->widget) {
|
|
if (d->hasContentsProperty) {
|
|
QVariant var = d->widget.data()->property("contentsSize");
|
|
return var.toSizeF();
|
|
} else
|
|
return d->widget.data()->size();
|
|
}
|
|
return QSizeF();
|
|
}
|
|
|
|
void ScrollWidget::setScrollPosition(const QPointF &position)
|
|
{
|
|
if (d->widget) {
|
|
if (d->hasOffsetProperty)
|
|
d->widget.data()->setProperty("scrollPosition", position);
|
|
else
|
|
d->widget.data()->setPos(-position.toPoint());
|
|
}
|
|
}
|
|
|
|
QPointF ScrollWidget::scrollPosition() const
|
|
{
|
|
if (d->widget) {
|
|
if (d->hasOffsetProperty) {
|
|
QVariant var = d->widget.data()->property("scrollPosition");
|
|
return var.toPointF();
|
|
} else {
|
|
return -d->widget.data()->pos();
|
|
}
|
|
}
|
|
return QPointF();
|
|
}
|
|
|
|
void ScrollWidget::setSnapSize(const QSizeF &size)
|
|
{
|
|
d->snapSize = size;
|
|
}
|
|
|
|
QSizeF ScrollWidget::snapSize() const
|
|
{
|
|
return d->snapSize;
|
|
}
|
|
|
|
void ScrollWidget::setStyleSheet(const QString &styleSheet)
|
|
{
|
|
d->styleSheet = styleSheet;
|
|
d->verticalScrollBar->setStyleSheet(styleSheet);
|
|
d->horizontalScrollBar->setStyleSheet(styleSheet);
|
|
}
|
|
|
|
QString ScrollWidget::styleSheet() const
|
|
{
|
|
return d->styleSheet;
|
|
}
|
|
|
|
QWidget *ScrollWidget::nativeWidget() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void ScrollWidget::focusInEvent(QFocusEvent *event)
|
|
{
|
|
Q_UNUSED(event)
|
|
|
|
if (d->widget) {
|
|
d->widget.data()->setFocus();
|
|
}
|
|
}
|
|
|
|
|
|
void ScrollWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
|
{
|
|
if (!d->widget) {
|
|
QGraphicsWidget::resizeEvent(event);
|
|
return;
|
|
}
|
|
|
|
d->adjustScrollbarsTimer->start(200);
|
|
|
|
//if topBorder exists bottomBorder too
|
|
if (d->topBorder) {
|
|
d->topBorder->resize(event->newSize().width(), d->topBorder->size().height());
|
|
d->bottomBorder->resize(event->newSize().width(), d->bottomBorder->size().height());
|
|
d->bottomBorder->setPos(0, event->newSize().height() - d->bottomBorder->size().height());
|
|
}
|
|
if (d->leftBorder) {
|
|
d->leftBorder->resize(d->leftBorder->size().width(), event->newSize().height());
|
|
d->rightBorder->resize(d->rightBorder->size().width(), event->newSize().height());
|
|
d->rightBorder->setPos(event->newSize().width() - d->rightBorder->size().width(), 0);
|
|
}
|
|
|
|
QGraphicsWidget::resizeEvent(event);
|
|
}
|
|
|
|
void ScrollWidget::keyPressEvent(QKeyEvent *event)
|
|
{
|
|
d->handleKeyPressEvent(event);
|
|
}
|
|
|
|
void ScrollWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (!d->widget) {
|
|
return;
|
|
}
|
|
|
|
d->handleMouseMoveEvent(event);
|
|
event->accept();
|
|
|
|
return QGraphicsWidget::mouseMoveEvent(event);
|
|
}
|
|
|
|
void ScrollWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (!d->widget) {
|
|
return;
|
|
} else if (!d->canYFlick() && !d->canXFlick()) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
d->handleMousePressEvent(event);
|
|
|
|
if (event->button() == Qt::LeftButton) {
|
|
event->accept();
|
|
} else {
|
|
QGraphicsWidget::mousePressEvent(event);
|
|
}
|
|
}
|
|
|
|
void ScrollWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if (!d->widget) {
|
|
return;
|
|
}
|
|
|
|
d->handleMouseReleaseEvent(event);
|
|
event->accept();
|
|
}
|
|
|
|
void ScrollWidget::wheelEvent(QGraphicsSceneWheelEvent *event)
|
|
{
|
|
if (!d->widget) {
|
|
return;
|
|
} else if (!d->canYFlick() && !d->canXFlick()) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
d->handleWheelEvent(event);
|
|
event->accept();
|
|
}
|
|
|
|
bool ScrollWidget::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
if (!d->widget) {
|
|
return false;
|
|
}
|
|
|
|
if (watched == d->scrollingWidget && (event->type() == QEvent::GraphicsSceneResize ||
|
|
event->type() == QEvent::Move)) {
|
|
emit viewportGeometryChanged(viewportGeometry());
|
|
} else if (watched == d->widget.data() && event->type() == QEvent::GraphicsSceneResize) {
|
|
d->stopAnimations();
|
|
d->adjustScrollbarsTimer->start(200);
|
|
updateGeometry();
|
|
|
|
QPointF newPos = d->widget.data()->pos();
|
|
if (d->widget.data()->size().width() <= viewportGeometry().width()) {
|
|
newPos.setX(d->minXExtent());
|
|
}
|
|
if (d->widget.data()->size().height() <= viewportGeometry().height()) {
|
|
newPos.setY(d->minYExtent());
|
|
}
|
|
//check if the content is visible
|
|
if (d->widget.data()->geometry().right() < 0) {
|
|
newPos.setX(-d->widget.data()->geometry().width()+viewportGeometry().width());
|
|
}
|
|
if (d->widget.data()->geometry().bottom() < 0) {
|
|
newPos.setY(-d->widget.data()->geometry().height()+viewportGeometry().height());
|
|
}
|
|
d->widget.data()->setPos(newPos);
|
|
|
|
} else if (watched == d->widget.data() && event->type() == QEvent::GraphicsSceneMove) {
|
|
d->horizontalScrollBar->blockSignals(true);
|
|
d->verticalScrollBar->blockSignals(true);
|
|
d->horizontalScrollBar->setValue(-d->widget.data()->pos().x()/10);
|
|
d->verticalScrollBar->setValue(-d->widget.data()->pos().y()/10);
|
|
d->horizontalScrollBar->blockSignals(false);
|
|
d->verticalScrollBar->blockSignals(false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QSizeF ScrollWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const
|
|
{
|
|
if (!d->widget || which == Qt::MaximumSize) {
|
|
return QGraphicsWidget::sizeHint(which, constraint);
|
|
//FIXME: it should ake the minimum hint of the contained widget, but the result is in a ridiculously big widget
|
|
} else if (which == Qt::MinimumSize) {
|
|
return QSizeF(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous);
|
|
}
|
|
|
|
QSizeF hint = d->widget.data()->effectiveSizeHint(which, constraint);
|
|
if (d->horizontalScrollBar && d->horizontalScrollBar->isVisible()) {
|
|
hint += QSize(0, d->horizontalScrollBar->size().height());
|
|
}
|
|
if (d->verticalScrollBar && d->verticalScrollBar->isVisible()) {
|
|
hint += QSize(d->verticalScrollBar->size().width(), 0);
|
|
}
|
|
|
|
return hint;
|
|
}
|
|
|
|
|
|
bool ScrollWidget::sceneEventFilter(QGraphicsItem *i, QEvent *e)
|
|
{
|
|
//only the scrolling widget and its children
|
|
if (!d->widget.data() ||
|
|
(!d->scrollingWidget->isAncestorOf(i) && i != d->scrollingWidget) ||
|
|
i == d->horizontalScrollBar || i == d->verticalScrollBar) {
|
|
return false;
|
|
}
|
|
|
|
if (i->isWidget()) {
|
|
Plasma::Label *label = dynamic_cast<Plasma::Label *>(static_cast<QGraphicsWidget *>(i));
|
|
if (label && (label->nativeWidget()->textInteractionFlags() & Qt::TextSelectableByMouse)) {
|
|
return false;
|
|
}
|
|
|
|
Plasma::TextEdit *textEdit = dynamic_cast<Plasma::TextEdit *>(static_cast<QGraphicsWidget *>(i));
|
|
if (textEdit && (textEdit->nativeWidget()->textInteractionFlags() & Qt::TextSelectableByMouse)) {
|
|
return false;
|
|
}
|
|
|
|
Plasma::TextBrowser *textBrowser= dynamic_cast<Plasma::TextBrowser *>(static_cast<QGraphicsWidget *>(i));
|
|
if (textBrowser && (textBrowser->nativeWidget()->textInteractionFlags() & Qt::TextSelectableByMouse)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool stealThisEvent = d->stealEvent;
|
|
//still pass around mouse moves: try to make still possible to make items start a drag event. thi could be either necessary or annoying, let's see how it goes. (add QEvent::GraphicsSceneMouseMove to block them)
|
|
stealThisEvent &= (e->type() == QEvent::GraphicsSceneMousePress ||
|
|
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;
|
|
|
|
//Multitouch related events, we actually need only TouchUpdate
|
|
case QEvent::TouchUpdate: {
|
|
QList<QTouchEvent::TouchPoint> touchPoints = static_cast<QTouchEvent *>(e)->touchPoints();
|
|
if (touchPoints.count() == 2) {
|
|
const QTouchEvent::TouchPoint &touchPoint0 = touchPoints.first();
|
|
const QTouchEvent::TouchPoint &touchPoint1 = touchPoints.last();
|
|
const QLineF line0(touchPoint0.lastPos(), touchPoint1.lastPos());
|
|
const QLineF line1(touchPoint0.pos(), touchPoint1.pos());
|
|
const QLineF startLine(touchPoint0.startPos(), touchPoint1.startPos());
|
|
const QPointF point = line1.pointAt(0.5);
|
|
const QPointF lastPoint = line0.pointAt(0.5);
|
|
|
|
if (d->multitouchGesture == ScrollWidgetPrivate::GestureNone) {
|
|
d->multitouchGesture = ScrollWidgetPrivate::GestureUndefined;
|
|
}
|
|
if (d->multitouchGesture == ScrollWidgetPrivate::GestureUndefined) {
|
|
const int zoomDistance = qAbs(line1.length() - startLine.length());
|
|
const int dragDistance = (startLine.pointAt(0.5) - point).manhattanLength();
|
|
|
|
if (zoomDistance - dragDistance > 30) {
|
|
d->multitouchGesture = ScrollWidgetPrivate::GestureZoom;
|
|
} else if (dragDistance - zoomDistance > 30) {
|
|
d->multitouchGesture = ScrollWidgetPrivate::GestureScroll;
|
|
}
|
|
}
|
|
|
|
if (d->multitouchGesture == ScrollWidgetPrivate::GestureScroll) {
|
|
QGraphicsSceneMouseEvent fakeEvent;
|
|
fakeEvent.setPos(point);
|
|
fakeEvent.setLastPos(lastPoint);
|
|
d->handleMouseMoveEvent(&fakeEvent);
|
|
} else if (d->multitouchGesture == ScrollWidgetPrivate::GestureZoom) {
|
|
if (d->widget && d->widget.data()->property("zoomFactor").isValid()) {
|
|
qreal scaleFactor = 1;
|
|
if (line0.length() > 0) {
|
|
scaleFactor = line1.length() / line0.length();
|
|
}
|
|
|
|
qreal zoom = d->widget.data()->property("zoomFactor").toReal();
|
|
d->widget.data()->setProperty("zoomFactor", zoom * scaleFactor);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
void ScrollWidget::setOverShoot(bool enable)
|
|
{
|
|
d->hasOvershoot = enable;
|
|
}
|
|
|
|
bool ScrollWidget::hasOverShoot() const
|
|
{
|
|
return d->hasOvershoot;
|
|
}
|
|
|
|
} // namespace Plasma
|
|
|
|
|
|
|
|
|
|
|
|
#include "moc_scrollwidget.cpp"
|