mirror of
https://bitbucket.org/smil3y/kde-workspace.git
synced 2025-02-24 19:02:51 +00:00
699 lines
21 KiB
C++
699 lines
21 KiB
C++
/*
|
|
* Copyright © 2008, 2009 Fredrik Höglund <fredrik@kde.org>
|
|
*
|
|
* The smooth scrolling code is based on the code in KHTMLView,
|
|
* Copyright © 2006-2008 Germain Garand <germain@ebooksfrance.org>
|
|
* Copyright © 2008 Allan Sandfeld Jensen <kde@carewolf.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.
|
|
*/
|
|
|
|
#include "abstractitemview.h"
|
|
#include "proxymodel.h"
|
|
#include "style.h"
|
|
|
|
#include <QItemSelectionModel>
|
|
#include <QPaintEngine>
|
|
|
|
#include <KDirModel>
|
|
#include <KFileItemDelegate>
|
|
#include <KGlobalSettings>
|
|
|
|
#include <Plasma/PaintUtils>
|
|
|
|
#include <limits.h>
|
|
|
|
static const int sSmoothScrollTime = 140;
|
|
static const int sSmoothScrollTick = 14;
|
|
|
|
|
|
AbstractItemView::AbstractItemView(QGraphicsWidget *parent)
|
|
: QGraphicsWidget(parent),
|
|
m_itemFrame(0),
|
|
m_delegate(0),
|
|
m_lastScrollValue(0),
|
|
m_viewScrolled(false),
|
|
m_dx(0),
|
|
m_ddx(0),
|
|
m_dddx(0),
|
|
m_rdx(0),
|
|
m_dy(0),
|
|
m_ddy(0),
|
|
m_dddy(0),
|
|
m_rdy(0),
|
|
m_smoothScrolling(false),
|
|
m_autoScrollSpeed(0),
|
|
m_drawShadows(true)
|
|
{
|
|
m_scrollBar = new Plasma::ScrollBar(this);
|
|
connect(m_scrollBar, SIGNAL(valueChanged(int)), SLOT(scrollBarValueChanged(int)));
|
|
connect(m_scrollBar->nativeWidget(), SIGNAL(actionTriggered(int)), SLOT(scrollBarActionTriggered(int)));
|
|
connect(m_scrollBar->nativeWidget(), SIGNAL(sliderReleased()), SLOT(scrollBarSliderReleased()));
|
|
|
|
m_itemFrame = new Plasma::FrameSvg(this);
|
|
m_itemFrame->setImagePath("widgets/viewitem");
|
|
m_itemFrame->setCacheAllRenderedFrames(true);
|
|
m_itemFrame->setElementPrefix("normal");
|
|
connect(m_itemFrame, SIGNAL(repaintNeeded()), this, SLOT(svgChanged()));
|
|
|
|
// This is a dummy widget that's never shown - it's just passed to
|
|
// KFileItemDelegate in the style options, so it will use the widget's
|
|
// style to draw the view item backgrounds.
|
|
m_styleWidget = new QWidget;
|
|
m_style = new FolderViewStyle;
|
|
m_styleWidget->setStyle(m_style);
|
|
|
|
const int size = style()->pixelMetric(QStyle::PM_LargeIconSize);
|
|
m_iconSize = QSize(size, size);
|
|
|
|
}
|
|
|
|
AbstractItemView::~AbstractItemView()
|
|
{
|
|
delete m_styleWidget;
|
|
delete m_style;
|
|
}
|
|
|
|
void AbstractItemView::setModel(QAbstractItemModel *model)
|
|
{
|
|
m_model = static_cast<ProxyModel*>(model);
|
|
m_dirModel = static_cast<KDirModel*>(m_model->sourceModel());
|
|
|
|
connect(m_model, SIGNAL(rowsInserted(QModelIndex,int,int)), SLOT(rowsInserted(QModelIndex,int,int)));
|
|
connect(m_model, SIGNAL(rowsRemoved(QModelIndex,int,int)), SLOT(rowsRemoved(QModelIndex,int,int)));
|
|
connect(m_model, SIGNAL(modelReset()), SLOT(modelReset()));
|
|
connect(m_model, SIGNAL(layoutChanged()), SLOT(layoutChanged()));
|
|
connect(m_model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), SLOT(dataChanged(QModelIndex,QModelIndex)));
|
|
}
|
|
|
|
QAbstractItemModel *AbstractItemView::model() const
|
|
{
|
|
return m_model;
|
|
}
|
|
|
|
void AbstractItemView::setSelectionModel(QItemSelectionModel *model)
|
|
{
|
|
m_selectionModel = model;
|
|
}
|
|
|
|
QItemSelectionModel *AbstractItemView::selectionModel() const
|
|
{
|
|
return m_selectionModel;
|
|
}
|
|
|
|
void AbstractItemView::setItemDelegate(KFileItemDelegate *delegate)
|
|
{
|
|
m_delegate = static_cast<KFileItemDelegate*>(delegate);
|
|
|
|
connect(m_delegate, SIGNAL(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)),
|
|
SLOT(closeEditor(QWidget*,QAbstractItemDelegate::EndEditHint)));
|
|
connect(m_delegate, SIGNAL(commitData(QWidget*)), SLOT(commitData(QWidget*)));
|
|
}
|
|
|
|
KFileItemDelegate *AbstractItemView::itemDelegate() const
|
|
{
|
|
return m_delegate;
|
|
}
|
|
|
|
void AbstractItemView::setIconSize(const QSize &iconSize)
|
|
{
|
|
m_iconSize = iconSize;
|
|
}
|
|
|
|
QSize AbstractItemView::iconSize() const
|
|
{
|
|
return m_iconSize;
|
|
}
|
|
|
|
void AbstractItemView::setDrawShadows(bool on)
|
|
{
|
|
if (m_drawShadows != on) {
|
|
m_drawShadows = on;
|
|
markAreaDirty(visibleArea());
|
|
update();
|
|
}
|
|
}
|
|
|
|
bool AbstractItemView::drawShadows() const
|
|
{
|
|
return m_drawShadows;
|
|
}
|
|
|
|
QScrollBar *AbstractItemView::verticalScrollBar() const
|
|
{
|
|
return m_scrollBar->nativeWidget();
|
|
}
|
|
|
|
QRect AbstractItemView::visibleArea() const
|
|
{
|
|
return mapToViewport(contentsRect()).toAlignedRect();
|
|
}
|
|
|
|
// Marks the given rect in viewport coordinates, as dirty and schedules a repaint.
|
|
void AbstractItemView::markAreaDirty(const QRect &rect)
|
|
{
|
|
if (!rect.isEmpty() && rect.intersects(visibleArea())) {
|
|
m_dirtyRegion += rect;
|
|
update(mapFromViewport(rect));
|
|
}
|
|
}
|
|
|
|
// This function scrolls the contents of the backbuffer the distance the scrollbar
|
|
// has moved since the last time this function was called.
|
|
QRect AbstractItemView::scrollBackBuffer()
|
|
{
|
|
const int value = m_scrollBar->value();
|
|
const int delta = m_lastScrollValue - value;
|
|
m_lastScrollValue = value;
|
|
|
|
if (qAbs(delta) >= m_pixmap.height()) {
|
|
return visibleArea();
|
|
}
|
|
|
|
QRect dirty = m_pixmap.rect();
|
|
|
|
return mapToViewport(dirty.translated(contentsRect().topLeft().toPoint())).toAlignedRect();
|
|
}
|
|
|
|
void AbstractItemView::prepareBackBuffer()
|
|
{
|
|
const QRect cr = contentsRect().toRect();
|
|
|
|
// Make sure the backbuffer pixmap has the same size as the content rect
|
|
if (m_pixmap.size() != cr.size()) {
|
|
QPixmap pixmap(cr.size());
|
|
pixmap.fill(Qt::transparent);
|
|
if (!m_pixmap.isNull()) {
|
|
// Static content optimization
|
|
{
|
|
QPainter p(&pixmap);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
p.drawPixmap(0, 0, m_pixmap);
|
|
}
|
|
QRegion region(pixmap.rect());
|
|
region -= m_pixmap.rect();
|
|
region.translate(0, m_scrollBar->value());
|
|
m_dirtyRegion |= region;
|
|
} else {
|
|
m_dirtyRegion = QRegion(visibleArea());
|
|
}
|
|
m_pixmap = pixmap;
|
|
}
|
|
|
|
if (m_viewScrolled) {
|
|
m_dirtyRegion += scrollBackBuffer();
|
|
m_viewScrolled = false;
|
|
}
|
|
}
|
|
|
|
// This function draws the backbuffer pixmap on the widget, and fades out the top
|
|
// and bottom if as needed.
|
|
void AbstractItemView::syncBackBuffer(QPainter *painter, const QRect &clipRect)
|
|
{
|
|
const QRect cr = contentsRect().toRect();
|
|
|
|
const int fadeHeight = 16;
|
|
const QRect topFadeRect(cr.x(), cr.y(), cr.width(), fadeHeight);
|
|
const QRect bottomFadeRect(cr.bottomLeft() - QPoint(0, fadeHeight), QSize(cr.width(), fadeHeight));
|
|
int scrollValue = m_scrollBar->value();
|
|
int maxValue = m_scrollBar->maximum();
|
|
|
|
// Draw the backbuffer on the widget
|
|
// =================================
|
|
if ((scrollValue > 0 && topFadeRect.intersects(clipRect)) ||
|
|
(scrollValue < maxValue && bottomFadeRect.intersects(clipRect)))
|
|
{
|
|
QPixmap pixmap = m_pixmap;
|
|
QPainter p(&pixmap);
|
|
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
|
|
// Fade out the top section of the pixmap if the scrollbar slider isn't at the top
|
|
if (scrollValue > 0 && topFadeRect.intersects(clipRect))
|
|
{
|
|
if (m_topFadeTile.isNull())
|
|
{
|
|
m_topFadeTile = QPixmap(256, fadeHeight);
|
|
m_topFadeTile.fill(Qt::transparent);
|
|
QLinearGradient g(0, 0, 0, fadeHeight);
|
|
g.setColorAt(0, Qt::transparent);
|
|
g.setColorAt(1, Qt::black);
|
|
QPainter p(&m_topFadeTile);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
p.fillRect(0, 0, 256, fadeHeight, g);
|
|
p.end();
|
|
}
|
|
p.drawTiledPixmap(0, 0, m_pixmap.width(), fadeHeight, m_topFadeTile);
|
|
}
|
|
|
|
// Fade out the bottom part of the pixmap if the scrollbar slider isn't at the bottom
|
|
if (scrollValue < maxValue && bottomFadeRect.intersects(clipRect))
|
|
{
|
|
if (m_topFadeTile.isNull())
|
|
{
|
|
m_bottomFadeTile = QPixmap(256, fadeHeight);
|
|
m_bottomFadeTile.fill(Qt::transparent);
|
|
QLinearGradient g(0, 0, 0, fadeHeight);
|
|
g.setColorAt(0, Qt::black);
|
|
g.setColorAt(1, Qt::transparent);
|
|
QPainter p(&m_bottomFadeTile);
|
|
p.setCompositionMode(QPainter::CompositionMode_Source);
|
|
p.fillRect(0, 0, 256, fadeHeight, g);
|
|
p.end();
|
|
}
|
|
p.drawTiledPixmap(0, m_pixmap.height() - fadeHeight, m_pixmap.width(), fadeHeight, m_bottomFadeTile);
|
|
}
|
|
p.end();
|
|
|
|
painter->drawPixmap(cr.topLeft(), pixmap);
|
|
}
|
|
else
|
|
{
|
|
painter->drawPixmap(cr.topLeft(), m_pixmap);
|
|
}
|
|
}
|
|
|
|
QSize AbstractItemView::doTextLayout(QTextLayout &layout, const QSize &constraints, Qt::Alignment alignment,
|
|
QTextOption::WrapMode wrapMode) const
|
|
{
|
|
QTextOption to;
|
|
to.setAlignment(alignment);
|
|
to.setTextDirection(layoutDirection());
|
|
to.setWrapMode(wrapMode);
|
|
layout.setTextOption(to);
|
|
|
|
QFontMetricsF fm = QFontMetricsF(layout.font());
|
|
|
|
QTextLine line;
|
|
qreal leading = fm.leading();
|
|
qreal widthUsed = 0;
|
|
qreal height = 0;
|
|
|
|
layout.beginLayout();
|
|
while ((line = layout.createLine()).isValid()) {
|
|
// Make the last line that will fit infinitely long.
|
|
// drawTextLayout() will handle this by fading the line out
|
|
// if it won't fit inside the constraints.
|
|
if (height + 2 * fm.lineSpacing() > constraints.height()) {
|
|
line.setLineWidth(INT_MAX);
|
|
if (line.naturalTextWidth() < constraints.width()) {
|
|
line.setLineWidth(constraints.width());
|
|
widthUsed = qMax(widthUsed, line.naturalTextWidth());
|
|
} else {
|
|
widthUsed = constraints.width();
|
|
}
|
|
} else {
|
|
line.setLineWidth(constraints.width());
|
|
widthUsed = qMax(widthUsed, line.naturalTextWidth());
|
|
}
|
|
line.setPosition(QPointF(0, height));
|
|
height += line.height() + leading;
|
|
}
|
|
layout.endLayout();
|
|
|
|
return QSize(widthUsed, height);
|
|
}
|
|
|
|
void AbstractItemView::drawTextLayout(QPainter *painter, const QTextLayout &layout, const QRect &rect) const
|
|
{
|
|
// Create the alpha gradient for the fade out effect
|
|
QLinearGradient alphaGradient(0, 0, 1, 0);
|
|
alphaGradient.setCoordinateMode(QGradient::ObjectBoundingMode);
|
|
if (layout.textOption().textDirection() == Qt::LeftToRight) {
|
|
alphaGradient.setColorAt(0, QColor(0, 0, 0, 255));
|
|
alphaGradient.setColorAt(1, QColor(0, 0, 0, 0));
|
|
} else {
|
|
alphaGradient.setColorAt(0, QColor(0, 0, 0, 0));
|
|
alphaGradient.setColorAt(1, QColor(0, 0, 0, 255));
|
|
}
|
|
|
|
QFontMetrics fm(layout.font());
|
|
|
|
QList<QRect> fadeRects;
|
|
QList<QRect> haloRects;
|
|
int fadeWidth = 30;
|
|
|
|
// Compute halo and fade rects
|
|
for (int i = 0; i < layout.lineCount(); i++) {
|
|
const QTextLine line = layout.lineAt(i);
|
|
const QRectF lr = line.naturalTextRect();
|
|
|
|
// Add a fade out rect to the list if the line is too long
|
|
if (lr.width() > rect.width()) {
|
|
int x = rect.width() - fadeWidth;
|
|
int y = lr.y();
|
|
QRect r = QStyle::visualRect(layout.textOption().textDirection(), QRect(QPoint(), rect.size()),
|
|
QRect(x, y, fadeWidth, int(lr.height())));
|
|
fadeRects.append(r);
|
|
}
|
|
|
|
haloRects.append((lr & rect.translated(-rect.topLeft())).toAlignedRect());
|
|
}
|
|
|
|
// Create a pixmap for the text
|
|
QPixmap pixmap(rect.size());
|
|
pixmap.fill(Qt::transparent);
|
|
|
|
QPainter p(&pixmap);
|
|
p.setPen(painter->pen());
|
|
|
|
int y = 0;
|
|
if (layout.textOption().alignment() & Qt::AlignVCenter) {
|
|
const int h = fm.height() * layout.lineCount();
|
|
if (h < pixmap.height()) {
|
|
y = (pixmap.height() - h) / 2;
|
|
}
|
|
}
|
|
p.translate(0, y);
|
|
|
|
// Draw each line in the layout
|
|
for (int i = 0; i < layout.lineCount(); i++)
|
|
{
|
|
const QTextLine line = layout.lineAt(i);
|
|
const QRectF tr = line.naturalTextRect();
|
|
|
|
if (tr.width() > rect.width()) {
|
|
if (layoutDirection() == Qt::LeftToRight) {
|
|
line.draw(&p, QPointF(-tr.x(), 0));
|
|
} else {
|
|
line.draw(&p, QPointF(rect.width() - tr.right(), 0));
|
|
}
|
|
} else {
|
|
line.draw(&p, QPointF());
|
|
}
|
|
}
|
|
|
|
// Reduce the alpha in each fade out rect using the alpha gradient
|
|
if (!fadeRects.isEmpty()) {
|
|
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
|
foreach (const QRect &rect, fadeRects) {
|
|
p.fillRect(rect, alphaGradient);
|
|
}
|
|
}
|
|
p.end();
|
|
|
|
if (drawShadows()) {
|
|
const QColor color = painter->pen().color();
|
|
if (qGray(color.rgb()) < 192) {
|
|
// Draw halos
|
|
painter->translate(0, y);
|
|
foreach (const QRect &haloRect, haloRects) {
|
|
Plasma::PaintUtils::drawHalo(painter, haloRect.translated(rect.topLeft()));
|
|
}
|
|
painter->translate(0, -y);
|
|
} else {
|
|
// Draw shadow
|
|
QImage shadow = pixmap.toImage();
|
|
Plasma::PaintUtils::shadowBlur(shadow, 2, Qt::black);
|
|
|
|
// Make the shadow twice as dark
|
|
quint32 * const pixels = reinterpret_cast<quint32*>(shadow.bits());
|
|
for (int i = 0; i < shadow.width() * shadow.height(); i++) {
|
|
pixels[i] = qMin(255, qAlpha(pixels[i]) * 2) << 24;
|
|
}
|
|
|
|
painter->drawImage(rect.topLeft() + QPoint(1, 1), shadow);
|
|
}
|
|
}
|
|
|
|
// Draw the text pixmap
|
|
painter->drawPixmap(rect.topLeft(), pixmap);
|
|
}
|
|
|
|
void AbstractItemView::rowsInserted(const QModelIndex &parent, int first, int last)
|
|
{
|
|
Q_UNUSED(parent)
|
|
Q_UNUSED(first)
|
|
Q_UNUSED(last)
|
|
}
|
|
|
|
void AbstractItemView::rowsRemoved(const QModelIndex &parent, int first, int last)
|
|
{
|
|
Q_UNUSED(parent)
|
|
Q_UNUSED(first)
|
|
Q_UNUSED(last)
|
|
}
|
|
|
|
void AbstractItemView::modelReset()
|
|
{
|
|
}
|
|
|
|
void AbstractItemView::layoutChanged()
|
|
{
|
|
}
|
|
|
|
void AbstractItemView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
|
|
{
|
|
Q_UNUSED(topLeft)
|
|
Q_UNUSED(bottomRight)
|
|
}
|
|
|
|
void AbstractItemView::commitData(QWidget *editor)
|
|
{
|
|
Q_UNUSED(editor)
|
|
}
|
|
|
|
void AbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
|
|
{
|
|
Q_UNUSED(editor)
|
|
Q_UNUSED(hint)
|
|
}
|
|
|
|
void AbstractItemView::svgChanged()
|
|
{
|
|
}
|
|
|
|
void AbstractItemView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint hint)
|
|
{
|
|
Q_UNUSED(hint)
|
|
|
|
const QRectF r = mapFromViewport(visualRect(index));
|
|
|
|
if (r.top() < 0) {
|
|
smoothScroll(0, r.top());
|
|
} else if (r.bottom() > geometry().height()) {
|
|
smoothScroll(0, r.bottom() - geometry().height());
|
|
}
|
|
}
|
|
|
|
void AbstractItemView::autoScroll(ScrollDirection direction, int pixelsPerSecond)
|
|
{
|
|
m_scrollDirection = direction;
|
|
m_autoScrollSetSpeed = direction == ScrollUp ? -pixelsPerSecond : pixelsPerSecond;
|
|
|
|
if (!m_autoScrollTimer.isActive()) {
|
|
m_autoScrollSpeed = 1;
|
|
m_autoScrollTime.restart();
|
|
m_autoScrollTimer.start(1000 / 30, this);
|
|
}
|
|
}
|
|
|
|
void AbstractItemView::stopAutoScrolling()
|
|
{
|
|
m_autoScrollSetSpeed = 0;
|
|
m_autoScrollTimer.stop();
|
|
}
|
|
|
|
void AbstractItemView::scrollBarValueChanged(int value)
|
|
{
|
|
Q_UNUSED(value)
|
|
|
|
m_viewScrolled = true;
|
|
update();
|
|
}
|
|
|
|
void AbstractItemView::scrollBarActionTriggered(int action)
|
|
{
|
|
switch (action)
|
|
{
|
|
case QAbstractSlider::SliderSingleStepAdd:
|
|
case QAbstractSlider::SliderSingleStepSub:
|
|
case QAbstractSlider::SliderPageStepAdd:
|
|
case QAbstractSlider::SliderPageStepSub:
|
|
stopAutoScrolling();
|
|
smoothScroll(0, m_scrollBar->nativeWidget()->sliderPosition() - m_scrollBar->value());
|
|
break;
|
|
|
|
case QAbstractSlider::SliderToMinimum:
|
|
case QAbstractSlider::SliderToMaximum:
|
|
stopAutoScrolling();
|
|
// Use a delayed call since the value won't propagate until after this function returns
|
|
QMetaObject::invokeMethod(this, "finishedScrolling", Qt::QueuedConnection);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void AbstractItemView::scrollBarSliderReleased()
|
|
{
|
|
finishedScrolling();
|
|
}
|
|
|
|
void AbstractItemView::finishedScrolling()
|
|
{
|
|
}
|
|
|
|
void AbstractItemView::timerEvent(QTimerEvent *event)
|
|
{
|
|
if (event->timerId() == m_smoothScrollTimer.timerId()) {
|
|
scrollTick();
|
|
} else if (event->timerId() == m_autoScrollTimer.timerId()) {
|
|
int step = qRound(m_autoScrollTime.elapsed() * (m_autoScrollSpeed / 1000.));
|
|
m_autoScrollTime.restart();
|
|
|
|
if (m_scrollDirection == ScrollUp && m_scrollBar->value() > m_scrollBar->minimum()) {
|
|
m_scrollBar->setValue(qMax(m_scrollBar->minimum(), m_scrollBar->value() + step));
|
|
} else if (m_scrollDirection == ScrollDown && m_scrollBar->value() < m_scrollBar->maximum()) {
|
|
m_scrollBar->setValue(qMin(m_scrollBar->maximum(), m_scrollBar->value() + step));
|
|
} else {
|
|
m_autoScrollSetSpeed = 0;
|
|
m_autoScrollSpeed = 0;
|
|
}
|
|
|
|
if (m_autoScrollSetSpeed > m_autoScrollSpeed) {
|
|
int delta;
|
|
if (m_autoScrollSpeed >= 0) {
|
|
delta = qBound(2, m_autoScrollSpeed * 2, 30);
|
|
} else {
|
|
delta = qBound(2, qAbs(m_autoScrollSpeed) / 2, 30);
|
|
}
|
|
m_autoScrollSpeed = qMin(m_autoScrollSpeed + delta, m_autoScrollSetSpeed);
|
|
} else if (m_autoScrollSetSpeed < m_autoScrollSpeed) {
|
|
int delta;
|
|
if (m_autoScrollSpeed >= 0) {
|
|
delta = qBound(2, m_autoScrollSpeed / 2, 30);
|
|
} else {
|
|
delta = qBound(2, qAbs(m_autoScrollSpeed * 2), 30);
|
|
}
|
|
m_autoScrollSpeed = qMax(m_autoScrollSetSpeed, m_autoScrollSpeed - delta);
|
|
}
|
|
|
|
if (m_autoScrollSpeed == 0 && m_autoScrollSetSpeed == 0) {
|
|
m_autoScrollTimer.stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void AbstractItemView::startScrolling()
|
|
{
|
|
m_smoothScrolling = true;
|
|
m_smoothScrollTimer.start(sSmoothScrollTick, this);
|
|
}
|
|
|
|
void AbstractItemView::stopScrolling()
|
|
{
|
|
m_smoothScrollTimer.stop();
|
|
m_dx = m_dy = 0;
|
|
m_ddx = m_ddy = 0;
|
|
m_rdx = m_rdy = 0;
|
|
m_dddx = m_dddy = 0;
|
|
m_smoothScrolling = false;
|
|
finishedScrolling();
|
|
}
|
|
|
|
void AbstractItemView::smoothScroll(int dx, int dy)
|
|
{
|
|
// full scroll is remaining scroll plus new scroll
|
|
m_dx = m_dx + dx;
|
|
m_dy = m_dy + dy;
|
|
|
|
if (m_dx == 0 && m_dy == 0) return;
|
|
|
|
int steps = sSmoothScrollTime/sSmoothScrollTick;
|
|
|
|
// average step size (stored in 1/16 px/step)
|
|
m_ddx = (m_dx*16)/(steps+1);
|
|
m_ddy = (m_dy*16)/(steps+1);
|
|
|
|
if (qAbs(m_ddx) < 64 && qAbs(m_ddy) < 64) {
|
|
// Don't move slower than average 4px/step in minimum one direction
|
|
if (m_ddx > 0) m_ddx = qMax(m_ddx, 64);
|
|
if (m_ddy > 0) m_ddy = qMax(m_ddy, 64);
|
|
if (m_ddx < 0) m_ddx = qMin(m_ddx, -64);
|
|
if (m_ddy < 0) m_ddy = qMin(m_ddy, -64);
|
|
// This means fewer than normal steps
|
|
steps = qMax(m_ddx ? (m_dx*16)/m_ddx : 0, m_ddy ? (m_dy*16)/m_ddy : 0);
|
|
if (steps < 1) steps = 1;
|
|
m_ddx = (m_dx*16)/(steps+1);
|
|
m_ddy = (m_dy*16)/(steps+1);
|
|
}
|
|
|
|
// step size starts at double average speed and ends at 0
|
|
m_ddx *= 2;
|
|
m_ddy *= 2;
|
|
|
|
// deacceleration speed
|
|
m_dddx = (m_ddx+1)/steps;
|
|
m_dddy = (m_ddy+1)/steps;
|
|
|
|
if (!m_smoothScrolling) {
|
|
startScrolling();
|
|
scrollTick();
|
|
}
|
|
m_smoothScrollStopwatch.start();
|
|
}
|
|
|
|
void AbstractItemView::scrollTick() {
|
|
if (m_dx == 0 && m_dy == 0) {
|
|
stopScrolling();
|
|
return;
|
|
}
|
|
|
|
// step size + remaining partial step
|
|
int tddx = m_ddx + m_rdx;
|
|
int tddy = m_ddy + m_rdy;
|
|
|
|
// don't go under 1px/step
|
|
if (tddx > 0 && tddx < 16) tddx = 16;
|
|
if (tddy > 0 && tddy < 16) tddy = 16;
|
|
if (tddx < 0 && tddx > -16) tddx = -16;
|
|
if (tddy < 0 && tddy > -16) tddy = -16;
|
|
|
|
// full pixel steps to scroll in this step
|
|
int ddx = tddx / 16;
|
|
int ddy = tddy / 16;
|
|
// remaining partial step (this is especially needed for 1.x sized steps)
|
|
m_rdx = tddx % 16;
|
|
m_rdy = tddy % 16;
|
|
|
|
// limit step to requested scrolling distance
|
|
if (qAbs(ddx) > qAbs(m_dx)) ddx = m_dx;
|
|
if (qAbs(ddy) > qAbs(m_dy)) ddy = m_dy;
|
|
|
|
// Don't stop if deaccelerated too fast
|
|
if (!ddx) ddx = m_dx;
|
|
if (!ddy) ddy = m_dy;
|
|
|
|
// update remaining scroll
|
|
m_dx -= ddx;
|
|
m_dy -= ddy;
|
|
|
|
m_scrollBar->setValue(m_scrollBar->value() + ddy);
|
|
|
|
// only consider decelerating if we aren't too far behind schedule
|
|
if (m_smoothScrollStopwatch.elapsed() < 2 * sSmoothScrollTick) {
|
|
// update scrolling speed
|
|
int dddx = m_dddx;
|
|
int dddy = m_dddy;
|
|
// don't change direction
|
|
if (abs(dddx) > abs(m_ddx)) dddx = m_ddx;
|
|
if (abs(dddy) > abs(m_ddy)) dddy = m_ddy;
|
|
|
|
m_ddx -= dddx;
|
|
m_ddy -= dddy;
|
|
}
|
|
m_smoothScrollStopwatch.start();
|
|
}
|
|
|
|
#include "moc_abstractitemview.cpp"
|