]> cloud.milkyroute.net Git - dolphin.git/blobdiff - src/kitemviews/kitemlistcontainer.cpp
Implement smooth-scrolling for horizontal and vertical scrollbars
[dolphin.git] / src / kitemviews / kitemlistcontainer.cpp
index 09fb505d642ad6d8105dde61e849b0930a7ca864..926e13bf2763e410064a10ff45ab571a93db6308 100644 (file)
 #include "kitemlistcontainer.h"
 
 #include "kitemlistcontroller.h"
+#include "kitemlistsmoothscroller_p.h"
 #include "kitemlistview.h"
 #include "kitemmodelbase.h"
 
+#include <QApplication>
 #include <QGraphicsScene>
 #include <QGraphicsView>
+#include <QPropertyAnimation>
 #include <QScrollBar>
 #include <QStyle>
 
 #include <KDebug>
 
+/**
+ * Replaces the default viewport of KItemListContainer by a
+ * non-scrollable viewport. The scrolling is done in an optimized
+ * way by KItemListView internally.
+ */
 class KItemListContainerViewport : public QGraphicsView
 {
 public:
-    KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent)
-        : QGraphicsView(scene, parent)
-    {
-        setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-        setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-        setViewportMargins(0, 0, 0, 0);
-        setFrameShape(QFrame::NoFrame);
-    }
-
-    void scrollContentsBy(int dx, int dy)
-    {
-        Q_UNUSED(dx);
-        Q_UNUSED(dy);
-        // Do nothing. This prevents that e.g. the wheel-event
-        // results in a moving of the scene items.
-    }
+    KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent);
+protected:
+    virtual void wheelEvent(QWheelEvent* event);
 };
 
+KItemListContainerViewport::KItemListContainerViewport(QGraphicsScene* scene, QWidget* parent) :
+    QGraphicsView(scene, parent)
+{
+    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+    setViewportMargins(0, 0, 0, 0);
+    setFrameShape(QFrame::NoFrame);
+}
+
+void KItemListContainerViewport::wheelEvent(QWheelEvent* event)
+{
+    // Assure that the wheel-event gets forwarded to the parent
+    // and not handled at all by QGraphicsView.
+    event->ignore();
+}
+
+
+
 KItemListContainer::KItemListContainer(KItemListController* controller, QWidget* parent) :
     QAbstractScrollArea(parent),
-    m_controller(controller)
+    m_controller(controller),
+    m_horizontalSmoothScroller(0),
+    m_verticalSmoothScroller(0)
 {
     Q_ASSERT(controller);
     controller->setParent(this);
@@ -65,7 +80,9 @@ KItemListContainer::KItemListContainer(KItemListController* controller, QWidget*
 
 KItemListContainer::KItemListContainer(QWidget* parent) :
     QAbstractScrollArea(parent),
-    m_controller(0)
+    m_controller(0),
+    m_horizontalSmoothScroller(0),
+    m_verticalSmoothScroller(0)
 {
     initialize();
 }
@@ -79,6 +96,20 @@ KItemListController* KItemListContainer::controller() const
     return m_controller;
 }
 
+void KItemListContainer::keyPressEvent(QKeyEvent* event)
+{
+    // TODO: We should find a better way to handle the key press events in the view.
+    // The reasons why we need this hack are:
+    // 1. Without reimplementing keyPressEvent() here, the event would not reach the QGraphicsView.
+    // 2. By default, the KItemListView does not have the keyboard focus in the QGraphicsScene, so
+    //    simply sending the event to the QGraphicsView which is the KItemListContainer's viewport
+    //    does not work.
+    KItemListView* view = m_controller->view();
+    if (view) {
+        QApplication::sendEvent(view, event);
+    }
+}
+
 void KItemListContainer::showEvent(QShowEvent* event)
 {
     QAbstractScrollArea::showEvent(event);
@@ -93,14 +124,41 @@ void KItemListContainer::resizeEvent(QResizeEvent* event)
 
 void KItemListContainer::scrollContentsBy(int dx, int dy)
 {
+    m_horizontalSmoothScroller->scrollContentsBy(dx);
+    m_verticalSmoothScroller->scrollContentsBy(dy);
+}
+
+void KItemListContainer::wheelEvent(QWheelEvent* event)
+{
+    if (event->modifiers().testFlag(Qt::ControlModifier)) {
+        event->ignore();
+        return;
+    }
+
     KItemListView* view = m_controller->view();
     if (!view) {
+        event->ignore();
         return;
     }
 
-    const qreal currentOffset = view->offset();
-    const qreal offsetDiff = (view->scrollOrientation() == Qt::Vertical) ? dy : dx;
-    view->setOffset(currentOffset - offsetDiff);
+    const bool scrollHorizontally = (event->orientation() == Qt::Horizontal) ||
+                                    (event->orientation() == Qt::Vertical && !verticalScrollBar()->isVisible());
+    KItemListSmoothScroller* smoothScroller = scrollHorizontally ?
+                                              m_horizontalSmoothScroller : m_verticalSmoothScroller;
+
+    const int numDegrees = event->delta() / 8;
+    const int numSteps = numDegrees / 15;
+
+    const QScrollBar* scrollBar = smoothScroller->scrollBar();
+    smoothScroller->scrollTo(scrollBar->value() - numSteps * scrollBar->pageStep());
+
+    event->accept();
+}
+
+void KItemListContainer::slotScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
+{
+    Q_UNUSED(previous);
+    updateSmoothScrollers(current);
 }
 
 void KItemListContainer::slotModelChanged(KItemModelBase* current, KItemModelBase* previous)
@@ -114,63 +172,144 @@ void KItemListContainer::slotViewChanged(KItemListView* current, KItemListView*
     QGraphicsScene* scene = static_cast<QGraphicsView*>(viewport())->scene();
     if (previous) {
         scene->removeItem(previous);
-        disconnect(previous, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars()));
-        disconnect(previous, SIGNAL(maximumOffsetChanged(int,int)), this, SLOT(updateScrollBars()));
+        disconnect(current, SIGNAL(scrollOrientationChanged(Qt::Orientation,Qt::Orientation)), this, SLOT(slotScrollOrientationChanged(Qt::Orientation,Qt::Orientation)));
+        disconnect(previous, SIGNAL(scrollOffsetChanged(qreal,qreal)),        this, SLOT(updateScrollOffsetScrollBar()));
+        disconnect(previous, SIGNAL(maximumScrollOffsetChanged(qreal,qreal)), this, SLOT(updateScrollOffsetScrollBar()));
+        disconnect(previous, SIGNAL(itemOffsetChanged(qreal,qreal)),          this, SLOT(updateItemOffsetScrollBar()));
+        disconnect(previous, SIGNAL(maximumItemOffsetChanged(qreal,qreal)),   this, SLOT(updateItemOffsetScrollBar()));
+        disconnect(previous, SIGNAL(scrollTo(qreal)),                         this, SLOT(scrollTo(qreal)));
+        m_horizontalSmoothScroller->setTargetObject(0);
+        m_verticalSmoothScroller->setTargetObject(0);
     }
     if (current) {
         scene->addItem(current);
-        connect(previous, SIGNAL(offsetChanged(int,int)), this, SLOT(updateScrollBars()));
-        connect(current, SIGNAL(maximumOffsetChanged(int,int)), this, SLOT(updateScrollBars()));
+        connect(current, SIGNAL(scrollOrientationChanged(Qt::Orientation,Qt::Orientation)), this, SLOT(slotScrollOrientationChanged(Qt::Orientation,Qt::Orientation)));
+        connect(current, SIGNAL(scrollOffsetChanged(qreal,qreal)),        this, SLOT(updateScrollOffsetScrollBar()));
+        connect(current, SIGNAL(maximumScrollOffsetChanged(qreal,qreal)), this, SLOT(updateScrollOffsetScrollBar()));
+        connect(current, SIGNAL(itemOffsetChanged(qreal,qreal)),          this, SLOT(updateItemOffsetScrollBar()));
+        connect(current, SIGNAL(maximumItemOffsetChanged(qreal,qreal)),   this, SLOT(updateItemOffsetScrollBar()));
+        connect(current, SIGNAL(scrollTo(qreal)),                         this, SLOT(scrollTo(qreal)));
+        m_horizontalSmoothScroller->setTargetObject(current);
+        m_verticalSmoothScroller->setTargetObject(current);
+        updateSmoothScrollers(current->scrollOrientation());
     }
 }
 
-void KItemListContainer::updateScrollBars()
+void KItemListContainer::scrollTo(qreal offset)
 {
-    const QSizeF size = m_controller->view()->size();
+    const KItemListView* view = m_controller->view();
+    if (view) {
+        if (view->scrollOrientation() == Qt::Vertical) {
+            m_verticalSmoothScroller->scrollTo(offset);
+        } else {
+            m_horizontalSmoothScroller->scrollTo(offset);
+        }
+    }
+}
+
+void KItemListContainer::updateScrollOffsetScrollBar()
+{
+    const KItemListView* view = m_controller->view();
+    if (!view) {
+        return;
+    }
 
-    if (m_controller->view()->scrollOrientation() == Qt::Vertical) {
-        QScrollBar* scrollBar = verticalScrollBar();
-        const int value = m_controller->view()->offset();
-        const int maximum = qMax(0, int(m_controller->view()->maximumOffset() - size.height()));
-        scrollBar->setPageStep(size.height());
-        scrollBar->setMinimum(0);
-        scrollBar->setMaximum(maximum);
-        scrollBar->setValue(value);
-        horizontalScrollBar()->setMaximum(0);
+    KItemListSmoothScroller* smoothScroller = 0;
+    QScrollBar* scrollOffsetScrollBar = 0;
+    int singleStep = 0;
+    int pageStep = 0;
+    if (view->scrollOrientation() == Qt::Vertical) {
+        smoothScroller = m_verticalSmoothScroller;
+        scrollOffsetScrollBar = verticalScrollBar();
+        singleStep = view->itemSize().height();
+        pageStep = view->size().height();
     } else {
-        QScrollBar* scrollBar = horizontalScrollBar();
-        const int value = m_controller->view()->offset();
-        const int maximum = qMax(0, int(m_controller->view()->maximumOffset() - size.width()));
-        scrollBar->setPageStep(size.width());
-        scrollBar->setMinimum(0);
-        scrollBar->setMaximum(maximum);
-        scrollBar->setValue(value);
-        verticalScrollBar()->setMaximum(0);
+        smoothScroller = m_horizontalSmoothScroller;
+        scrollOffsetScrollBar = horizontalScrollBar();
+        singleStep = view->itemSize().width();
+        pageStep = view->size().width();
+    }
+
+    const int value = view->scrollOffset();
+    const int maximum = qMax(0, int(view->maximumScrollOffset() - pageStep));
+    if (smoothScroller->requestScrollBarUpdate(maximum)) {
+        scrollOffsetScrollBar->setSingleStep(singleStep);
+        scrollOffsetScrollBar->setPageStep(pageStep);
+        scrollOffsetScrollBar->setMinimum(0);
+        scrollOffsetScrollBar->setMaximum(maximum);
+        scrollOffsetScrollBar->setValue(value);
     }
 }
 
-void KItemListContainer::updateGeometries()
+void KItemListContainer::updateItemOffsetScrollBar()
 {
-    QRect rect = geometry();
+    const KItemListView* view = m_controller->view();
+    if (!view) {
+        return;
+    }
 
-    int widthDec = frameWidth() * 2;
-    if (verticalScrollBar()->isVisible()) {
-        widthDec += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+    KItemListSmoothScroller* smoothScroller = 0;
+    QScrollBar* itemOffsetScrollBar = 0;
+    int singleStep = 0;
+    int pageStep = 0;
+    if (view->scrollOrientation() == Qt::Vertical) {
+        smoothScroller = m_horizontalSmoothScroller;
+        itemOffsetScrollBar = horizontalScrollBar();
+        singleStep = view->size().width() / 10;
+        pageStep = view->size().width();
+    } else {
+        smoothScroller = m_verticalSmoothScroller;
+        itemOffsetScrollBar = verticalScrollBar();
+        singleStep = view->size().height() / 10;
+        pageStep = view->size().height();
     }
 
-    int heightDec = frameWidth() * 2;
-    if (horizontalScrollBar()->isVisible()) {
-        heightDec += style()->pixelMetric(QStyle::PM_ScrollBarExtent);
+    const int value = view->itemOffset();
+    const int maximum = qMax(0, int(view->maximumItemOffset()) - pageStep);
+    if (smoothScroller->requestScrollBarUpdate(maximum)) {
+        itemOffsetScrollBar->setSingleStep(singleStep);
+        itemOffsetScrollBar->setPageStep(pageStep);
+        itemOffsetScrollBar->setMinimum(0);
+        itemOffsetScrollBar->setMaximum(maximum);
+        itemOffsetScrollBar->setValue(value);
     }
+}
+
+void KItemListContainer::updateGeometries()
+{
+    QRect rect = geometry();
+
+    const int widthDec = verticalScrollBar()->isVisible()
+                         ? frameWidth() + style()->pixelMetric(QStyle::PM_ScrollBarExtent)
+                         : frameWidth() * 2;
+
+    const int heightDec = horizontalScrollBar()->isVisible()
+                          ? frameWidth() + style()->pixelMetric(QStyle::PM_ScrollBarExtent)
+                          : frameWidth() * 2;
 
     rect.adjust(0, 0, -widthDec, -heightDec);
 
-    m_controller->view()->setGeometry(QRect(0, 0, rect.width(), rect.height()));
+    const QRectF newGeometry(0, 0, rect.width(), rect.height());
+    if (m_controller->view()->geometry() != newGeometry) {
+        m_controller->view()->setGeometry(newGeometry);
 
-    static_cast<KItemListContainerViewport*>(viewport())->scene()->setSceneRect(0, 0, rect.width(), rect.height());
-    static_cast<KItemListContainerViewport*>(viewport())->viewport()->setGeometry(QRect(0, 0, rect.width(), rect.height()));
+        static_cast<KItemListContainerViewport*>(viewport())->scene()->setSceneRect(0, 0, rect.width(), rect.height());
+        static_cast<KItemListContainerViewport*>(viewport())->viewport()->setGeometry(QRect(0, 0, rect.width(), rect.height()));
 
-    updateScrollBars();
+        updateScrollOffsetScrollBar();
+        updateItemOffsetScrollBar();
+    }
+}
+
+void KItemListContainer::updateSmoothScrollers(Qt::Orientation orientation)
+{
+    if (orientation == Qt::Vertical) {
+        m_verticalSmoothScroller->setPropertyName("scrollOffset");
+        m_horizontalSmoothScroller->setPropertyName("itemOffset");
+    } else {
+        m_horizontalSmoothScroller->setPropertyName("scrollOffset");
+        m_verticalSmoothScroller->setPropertyName("itemOffset");
+    }
 }
 
 void KItemListContainer::initialize()
@@ -186,6 +325,9 @@ void KItemListContainer::initialize()
 
     QGraphicsView* graphicsView = new KItemListContainerViewport(new QGraphicsScene(this), this);
     setViewport(graphicsView);
+
+    m_horizontalSmoothScroller = new KItemListSmoothScroller(horizontalScrollBar(), this);
+    m_verticalSmoothScroller = new KItemListSmoothScroller(verticalScrollBar(), this);
 }
 
 #include "kitemlistcontainer.moc"