[prev in list] [next in list] [prev in thread] [next in thread]
List: kopete-devel
Subject: [kopete-devel] [patch] smooth scrolling hack
From: Engin AYDOGAN <engin () bzzzt ! biz>
Date: 2005-03-31 0:34:07
Message-ID: 200503310334.07771.engin () bzzzt ! biz
[Download RAW message or body]
Hi there,
This hacks makes your contact list scrolling smooth.
--
Best regards,
Engin AYDOGAN
["kopete-smooth-scrolling-hack.patch" (text/x-diff)]
? kopete-smooth-scrolling-hack.patch
? libkopete/ui/addressbookselectorwidget_base.cpp
? libkopete/ui/addressbookselectorwidget_base.h
Index: kopete/contactlist/kopetelistview.cpp
===================================================================
RCS file: /home/kde/kdenetwork/kopete/kopete/contactlist/kopetelistview.cpp,v
retrieving revision 1.2
diff -u -3 -p -u -p -r1.2 kopetelistview.cpp
--- kopete/contactlist/kopetelistview.cpp 17 Nov 2004 23:44:25 -0000 1.2
+++ kopete/contactlist/kopetelistview.cpp 31 Mar 2005 12:23:36 -0000
@@ -21,6 +21,7 @@
#include "kopeteglobal.h"
#include "kopeteprefs.h"
+#include <qapplication.h>
#include <kdebug.h>
#include <qtimer.h>
@@ -93,6 +94,81 @@ struct ListView::Private
{
QTimer sortTimer;
std::auto_ptr<ToolTip> toolTip;
+ //! The status of smooth scrolling
+ bool smoothScrollingEnabled;
+ //! This will be the QTimer's ID which will be updating smooth scrolling animation.
+ int smoothScrollingTimer;
+ //! The time interval which the smooth scrolling will be updated.
+ int smoothScrollingTimerInterval;
+ //! This will be the target scroll bar position in the maxValue() minValue() \
boundary of the bar. + int targetScrollBarValue;
+ //! Meta current scroll bar value, this will be used to make precise calculation, \
note that data type is double + double metaScrollBarCurrentValue;
+ //! Acceleration constant
+ double scrollBarAccelerationConstant;
+ //! Scroll line step size to emulate
+ int smoothScrollingLineStep;
+ //! Scroll page step size to emulate
+ int smoothScrollingPageStep;
+ //! True if the slider is being pressed
+ bool scrollBarSliderPressed;
+ //! The mouse position where the slider dragging began
+ int scrollBarSliderDragStartY;
+ //! True when the mouse is pressed
+ bool mousePressed;
+ //! This will be used to save drag auto scroll status of the scrollview
+ // we will need to restore it later.
+ bool smoothScrollDragAutoScroll;
+ //! Auto scroll offset, the list will automatically start scrolling when the mouse \
gets this much pixel closer + int smoothAutoScrollOffset;
+ //! These are the state of scroll bar buttons, this are necessary if want \
continuous scrolling as long as we + // press to these buttons
+ bool scrollBarPrevLineUpPressed;
+ bool scrollBarPrevPagePressed;
+ bool scrollBarNextPagePressed;
+ bool scrollBarPrevLineBottomPressed;
+ bool scrollBarNextLinePressed;
+ //! Counter we'll use when waiting
+ int smoothScrollContinuousCounter;
+ //! The timer which will simulate continous play for line step buttons in \
scrollview + int continuousLinePressTimer;
+ //! How many timeouts should we wait before beginning continuous line step \
simulates + int continuousLinePressTimerWait;
+ //! Continuous press timer interval for next line buttton
+ int continuousLinePressTimerInterval;
+ //! The timer which will simulate continous page step in scrollview
+ int continuousPagePressTimer;
+ //! How many timeouts should we wait before beginning continuous page step \
simulates + int continuousPagePressTimerWait;
+ //! Continuous press timer interval for next page clicks
+ int continuousPagePressTimerInterval;
+ //! C-tor
+ Private()
+ : smoothScrollingEnabled(false),
+ smoothScrollingTimer(0),
+ smoothScrollingTimerInterval(30),
+ targetScrollBarValue(0),
+ metaScrollBarCurrentValue(0.0),
+ scrollBarAccelerationConstant(6.0),
+ smoothScrollingLineStep(0),
+ smoothScrollingPageStep(0),
+ scrollBarSliderPressed(false),
+ scrollBarSliderDragStartY(0),
+ mousePressed(false),
+ smoothScrollDragAutoScroll(false),
+ smoothAutoScrollOffset(60),
+ scrollBarPrevLineUpPressed(false),
+ scrollBarPrevPagePressed(false),
+ scrollBarNextPagePressed(false),
+ scrollBarPrevLineBottomPressed(false),
+ scrollBarNextLinePressed(false),
+ smoothScrollContinuousCounter(0),
+ continuousLinePressTimer(0),
+ continuousLinePressTimerWait(10),
+ continuousLinePressTimerInterval(40),
+ continuousPagePressTimer(0),
+ continuousPagePressTimerWait(2),
+ continuousPagePressTimerInterval(200) {}
};
ListView::ListView( QWidget *parent, const char *name )
@@ -125,6 +201,9 @@ ListView::ListView( QWidget *parent, con
// This is, of course, a nasty hack, but it works, so...
static_cast<ListView*>(viewport())->clearWFlags( WStaticContents );
static_cast<ListView*>(viewport())->setWFlags( WNoAutoErase );
+
+ // init smooth scrolling
+ setSmoothScrolling( true );
}
ListView::~ListView()
@@ -199,6 +278,308 @@ void ListView::delayedSort()
d->sortTimer.start( 500, true );
}
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Here begins the smooth scrolling stuff
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Bugs: * Scroll bar loses it's onMouseOver color when we drag the scroll bar and \
move the mouse outside +// * Next/Prev page areas don't get mouse clicks, some \
styles gives some feedback on such an event +// and that feedback is \
unintentionally avoided by this hack. Many many contraints caused this choice. \
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void ListView::setSmoothScrolling( bool b )
+{
+ if( d->smoothScrollingEnabled == b ) // Is setting changed?
+ return; // If no, just return
+ else // else
+ d->smoothScrollingEnabled = b; // update the setting
+
+ if( d->smoothScrollingEnabled ) // If enabled
+ {
+ // Intercept scrollbar's events
+ verticalScrollBar()->installEventFilter( this );
+ // Install the timer
+ d->smoothScrollingTimer = startTimer( d->smoothScrollingTimerInterval );
+ // If we want to enable smooth scrolling when item has changed with keypresses \
etc, we need this + connect( this, SIGNAL( currentChanged( QListViewItem* ) ), this, \
SLOT( slotCurrentChanged(QListViewItem*) ) ); + // Disable autoscroll, we will do it \
the smooth way. + d->smoothScrollDragAutoScroll = dragAutoScroll();
+ setDragAutoScroll( false );
+ // Init the timers to simulate continuous press
+ d->continuousLinePressTimer = startTimer( d->continuousLinePressTimerInterval );
+ d->continuousPagePressTimer = startTimer( d->continuousPagePressTimerInterval );
+
+ }
+ else // If disabled
+ {
+ // Uninstall the event interception from the scroll bar
+ verticalScrollBar()->removeEventFilter( this );
+ // Restore line/page step sizes
+ verticalScrollBar()->setLineStep( d->smoothScrollingLineStep );
+ // Kill the already started timer
+ killTimer( d->smoothScrollingTimer );
+ d->smoothScrollingTimer = 0;
+ // We don't need to list currentChanged anymore
+ disconnect( this, SIGNAL( currentChanged( QListViewItem* ) ), this, SLOT( \
slotCurrentChanged(QListViewItem*) ) ); + // Restore the autoscroll
+ setDragAutoScroll( d->smoothScrollDragAutoScroll );
+ // Kill the continuous press timers
+ killTimer( d->continuousLinePressTimer );
+ d->continuousLinePressTimer = 0;
+ killTimer( d->continuousPagePressTimer );
+ d->continuousPagePressTimer = 0;
+ }
+}
+
+bool ListView::smoothScrolling()
+{
+ return d->smoothScrollingEnabled;
+}
+
+void ListView::setSmoothScrollingTimerInterval( int i )
+{
+ d->smoothScrollingTimerInterval = i;
+}
+
+int ListView::smoothScrollingTimerInterval()
+{
+ return d->smoothScrollingTimerInterval;
+}
+
+void ListView::timerEvent( QTimerEvent *e )
+{
+ if( e->timerId() == d->smoothScrollingTimer )
+ { // This is a smooth scroll update
+ // Find how war we are away from the target scroll bar value and divide it by our \
constant (it can be both negative/positive) + double offset = static_cast<double>( ( \
d->targetScrollBarValue - d->metaScrollBarCurrentValue ) / \
d->scrollBarAccelerationConstant ); + // Add the offset to our meta current value, \
this is the desired precise value + d->metaScrollBarCurrentValue += offset;
+ // Cast it to integer and update the vertical scroll bar value
+ verticalScrollBar()->setValue( static_cast<int>( d->metaScrollBarCurrentValue ) );
+ }
+ else if( e->timerId() == d->continuousLinePressTimer )
+ {
+ // Check if any scrollbar buttons are being pressed right, if any, honor them
+ if( d->scrollBarPrevLineUpPressed || d->scrollBarPrevLineBottomPressed )
+ { // Check if the user has pressed for long enough to activate continuous mouse \
press effect + if( d->smoothScrollContinuousCounter++ > \
d->continuousLinePressTimerWait ) + {
+ d->targetScrollBarValue -= d->smoothScrollingLineStep;
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, \
verticalScrollBar()->minValue() ); + }
+ }
+ else if( d->scrollBarNextLinePressed )
+ {
+ if( d->smoothScrollContinuousCounter++ > d->continuousLinePressTimerWait )
+ {
+ d->targetScrollBarValue += d->smoothScrollingLineStep;
+ d->targetScrollBarValue = QMIN( d->targetScrollBarValue, \
verticalScrollBar()->maxValue() ); + }
+ }
+ }
+ else if( e->timerId() == d->continuousPagePressTimer )
+ {
+ if( d->scrollBarPrevPagePressed )
+ {
+ if( d->smoothScrollContinuousCounter++ > d->continuousPagePressTimerWait )
+ {
+ d->targetScrollBarValue -= d->smoothScrollingPageStep;
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, \
verticalScrollBar()->minValue() ); + }
+ }
+ else if( d->scrollBarNextPagePressed )
+ {
+ if( d->smoothScrollContinuousCounter++ > d->continuousPagePressTimerWait )
+ {
+ d->targetScrollBarValue += d->smoothScrollingPageStep;
+ d->targetScrollBarValue = QMIN( d->targetScrollBarValue, \
verticalScrollBar()->maxValue() ); + }
+ }
+ }
+}
+
+bool ListView::eventFilter( QObject *o, QEvent *e )
+{
+ if( o == verticalScrollBar() )
+ {
+ // Our scroll bar
+ QScrollBar *bar = static_cast<QScrollBar*>(o);
+ if( e->type() == QEvent::Wheel )
+ {
+ // OK, this is a wheel event, let's get our QWheelEvent in an unsafe way, due to \
a bug in RTTI of QT. + QWheelEvent *event = static_cast<QWheelEvent*>(e);
+ // Set new target value
+ d->targetScrollBarValue -= event->delta();
+ // Make sure it's in the boundaries of scroll bar
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, bar->minValue() );
+ d->targetScrollBarValue = QMIN( d->targetScrollBarValue, bar->maxValue() );
+ return true; // Ignore the event
+ }
+ else if( e->type() == QEvent::MouseButtonPress || e->type() == \
QEvent::MouseButtonDblClick ) + {
+ // We are intercepting all clicks and double clicks on the scrollbar. Unless we \
do so + // scroll bar immediatly goes to the point wherever user's click requests \
it to. + // Then smooth scroll begins, and animates the scrolling, but since the \
scrollbar + // goes to the destionation point for a very small amount of time at \
the very beginning of + // the click, this causes flickering. So we intercept each \
click, and make the scroll bar + // go to it's destination by smoothly.
+
+ //// Start masking the scrollbar so that we can detect where the mouse clicks on
+ // The slider handle's starting position.
+ int sliderStart = bar->sliderStart();
+ // The slider handle's ending position
+ int sliderEnd = sliderStart + bar->sliderRect().height();
+ // The slider handle's width
+ int width = bar->sliderRect().width();
+ // This is masking the upper previous line button
+ QRect prevLineUpper( 0, 0, width, 15 );
+ // This is masking the previous page, which is between the upper previous line \
button and the slider + QRect prevPage( 0, 15, width, sliderStart - 15 );
+ // This is masking the next page, which is between bottom previous line and the \
slider + QRect nextPage( 0, sliderEnd, width, bar->height() - sliderEnd - 30 );
+ // This is masking the bottom previous line button
+ QRect prevLineBottom( 0, bar->height() - 30, width, 15 );
+ // This is masking the next line button
+ QRect nextLine( 0, bar->height() - 15, width, 30 );
+
+ // Get page/line step sizes. You may ask, why we are not doing this in \
setSmoothScrolling + // the reason is, scroll bar might not be initialized at that \
moment. When we are receiving + // MouseButtonPress or such event, we're sure that \
it's initialized! + if( d->smoothScrollingLineStep == 0 && \
d->smoothScrollingPageStep == 0 ){ + d->smoothScrollingLineStep = bar->lineStep();
+ d->smoothScrollingPageStep = bar->pageStep();
+ // Set page/line steps of the scroll bar to zero, we'll emulate them, smoothly!
+ // If we don't set this to 0, when we pass the event to the button, the scollbar
+ // will scroll the list too.
+ verticalScrollBar()->setLineStep( 0 );
+ }
+
+ // OK, now we can understand which partion of the scroll bar is clicked, and do \
the requested thing + // animated. Then set the step sizes to zero, and pass the \
event to the slider, so that user can + // feel like he/she really pressed the \
buttons (on click color change). +
+ // Get our QMouseEvent so that we can have our relative mouse position
+ QMouseEvent *event = static_cast<QMouseEvent*>(e);
+
+ if( verticalScrollBar()->sliderRect().contains( event->pos() ) )// Click on the \
slider + {
+ d->scrollBarSliderDragStartY = event->y();
+ d->scrollBarSliderPressed = true;
+ }
+ else if( prevLineUpper.contains( event->pos() ) ) // Click on the upper previous \
line button + {
+ d->targetScrollBarValue -= d->smoothScrollingLineStep;
+ // Make sure if the targetScrollBarValue is in the scroll bar values range
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, \
verticalScrollBar()->minValue() ); + d->scrollBarPrevLineUpPressed = true;
+ return false; // pass the event to the scroll bar so the button gets "clicked"
+ }
+ else if( prevPage.contains( event->pos() ) ) // Click on the previous page area
+ {
+ d->targetScrollBarValue -= d->smoothScrollingPageStep;
+ // Make sure if the targetScrollBarValue is in the scroll bar values range
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, \
verticalScrollBar()->minValue() ); + d->scrollBarPrevPagePressed = true;
+ }
+ else if( nextPage.contains( event->pos() ) ) // Click on the next page area
+ {
+ d->targetScrollBarValue += d->smoothScrollingPageStep;
+ // Make sure if the targetScrollBarValue is in the scroll bar values range
+ d->targetScrollBarValue = QMIN( d->targetScrollBarValue, \
verticalScrollBar()->maxValue() ); + d->scrollBarNextPagePressed = true;
+ }
+ else if( prevLineBottom.contains( event->pos() ) ) // Click on the bottom \
previous line button + {
+ d->targetScrollBarValue -= d->smoothScrollingLineStep;
+ // Make sure if the targetScrollBarValue is in the scroll bar values range
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, \
verticalScrollBar()->minValue() ); + d->scrollBarPrevLineBottomPressed = true;
+ return false; // pass the event to the scroll bar so the button gets "clicked"
+ }
+ else if( nextLine.contains( event->pos() ) ) // Click on the next line button
+ {
+ d->targetScrollBarValue += d->smoothScrollingLineStep;
+ // Make sure if the targetScrollBarValue is in the scroll bar values range
+ d->targetScrollBarValue = QMIN( d->targetScrollBarValue, \
verticalScrollBar()->maxValue() ); + d->scrollBarNextLinePressed = true;
+ return false; // pass the event to the scroll bar so the button gets "clicked"
+ }
+ return true; // Now, ignore the event.
+ }
+ else if( e->type() == QEvent::MouseMove )
+ {
+ // Get our QMouseEvent so that we can have our relative mouse position
+ QMouseEvent *event = static_cast<QMouseEvent*>(e);
+ if( d->scrollBarSliderPressed )
+ {
+ // Mouse movement distance for this MouseMove event
+ double delta = event->y() - d->scrollBarSliderDragStartY;
+ // Update the drag start value so in the next MouseMove event we can calculate \
new movement distance + d->scrollBarSliderDragStartY = event->y();
+ // The length which we can move the mouse over the bar
+ double scale = bar->geometry().height() - bar->sliderRect().height() - 45;
+ // Scale it to scroll bar value
+ d->targetScrollBarValue += static_cast<int>( static_cast<double>( ( \
bar->maxValue() / scale ) * delta ) ); + }
+ }
+ else if( e->type() == QEvent::MouseButtonRelease )
+ {
+ // Reset waiting counter. This is used to wait before simulating continuous mouse \
press + d->smoothScrollContinuousCounter = 0;
+ // Mark all buttons as not pressed now
+ d->scrollBarSliderPressed = d->scrollBarPrevLineUpPressed = \
d->scrollBarPrevPagePressed = false; + d->scrollBarNextPagePressed = \
d->scrollBarPrevLineBottomPressed = d->scrollBarNextLinePressed = false; + // Make \
sure if the targetScrollBarValue is in the scroll bar values range \
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, bar->minValue() ); \
+ d->targetScrollBarValue = QMIN( d->targetScrollBarValue, bar->maxValue() ); \
+ return false; // Pass the release event to the scroll bar, which will put the \
buttons in off-state + }
+ else
+ {
+ return false; // Pass the event to the scroll bar
+ }
+ }
+ else
+ {
+ if( e->type() == QEvent::MouseButtonPress )
+ {
+ // Mark that we have pressed the button. This will prevent the list from \
scrolling when + // the current item has changed due to mouse click. It's fine when \
the keypresses cause + // it scroll, but not mouse.
+ d->mousePressed = true;
+ }
+ else if( e->type() == QEvent::MouseButtonRelease )
+ {
+ d->mousePressed = false;
+ }
+ else if( e->type() == QEvent::DragMove )
+ {
+ // OK, user is dragging something in the list
+ QDragMoveEvent *event = static_cast<QDragMoveEvent*>(e);
+ if( event->pos().y() < d->smoothAutoScrollOffset )
+ { // If he's too close to the upper edge, let's smootly scroll up
+ d->targetScrollBarValue -= ( d->smoothAutoScrollOffset - event->pos().y() ) * \
d->scrollBarAccelerationConstant / 3; + }
+ else if( event->pos().y() > ( visibleHeight() - d->smoothAutoScrollOffset ) )
+ { // If he's too close to the bottom edege, then let's smoothle scroll down
+ d->targetScrollBarValue += ( event->pos().y() - visibleHeight() + \
d->smoothAutoScrollOffset ) * d->scrollBarAccelerationConstant / 3; + }
+ // Make sure if the targetScrollBarValue is in the scroll bar values range
+ d->targetScrollBarValue = QMAX( d->targetScrollBarValue, \
verticalScrollBar()->minValue() ); + d->targetScrollBarValue = QMIN( \
d->targetScrollBarValue, verticalScrollBar()->maxValue() ); + }
+
+ return KListView::eventFilter( o, e ); // Pass the event to KListView
+ }
+}
+
+void ListView::slotCurrentChanged( QListViewItem *item )
+{
+ // If the current item changed due to mouse click then don't center it in the \
listview. Do this just for key presses. + if( d->mousePressed ){ d->mousePressed = \
false; return; } + d->targetScrollBarValue = itemPos(item) - \
static_cast<double>(visibleHeight()/2.0) + item->height(); +}
+
+
} // END namespace ListView
} // END namespace UI
} // END namespace Kopete
Index: kopete/contactlist/kopetelistview.h
===================================================================
RCS file: /home/kde/kdenetwork/kopete/kopete/contactlist/kopetelistview.h,v
retrieving revision 1.1
diff -u -3 -p -u -p -r1.1 kopetelistview.h
--- kopete/contactlist/kopetelistview.h 30 Jul 2004 15:10:30 -0000 1.1
+++ kopete/contactlist/kopetelistview.h 31 Mar 2005 12:23:37 -0000
@@ -48,6 +48,27 @@ public:
*/
void setShowTreeLines( bool bShowAsTree );
+ /**
+ * Sets the smooth scrolling.
+ */
+ void setSmoothScrolling( bool );
+
+ /**
+ * Gets the smooth scrolling status
+ */
+ bool smoothScrolling();
+
+ /**
+ * Sets the update interval of smooth scrolling animation.
+ * @param interval is the interval in ms.
+ */
+ void setSmoothScrollingTimerInterval( int interval );
+
+ /**
+ * Gets the current update interval.
+ */
+ int smoothScrollingTimerInterval();
+
public slots:
/**
* Calls QListView::sort()
@@ -56,11 +77,29 @@ public slots:
protected:
virtual void keyPressEvent( QKeyEvent *e );
+ /**
+ * Invoked on each timeout of a QTimer of this listview,
+ * This will manage the smooth scrolling animation.
+ */
+ virtual void timerEvent( QTimerEvent *e );
+
+ /**
+ * To make smooth scrolling work well, we need extensive event intercepting.
+ * This event filter is suppposed to achive that.
+ */
+ virtual bool eventFilter( QObject *o, QEvent *e );
private slots:
void slotContextMenu(KListView*,QListViewItem *item, const QPoint &point );
void slotDoubleClicked( QListViewItem *item );
-
+ /**
+ * To enable smooth scroll to focus on highlighted items when they are highlighted
+ * by a key press we use this slot. slotCurrentChanged is connected to the \
currentChanged + * signal, it's being invoked in every selection change. If the \
selection change was made + * by the mouse, then we don't do anything, since the \
item is on the viewable are already. + * Otherwise, we focus (bring it to the center \
of the list) smoothly. + */
+ void slotCurrentChanged( QListViewItem *item );
private:
struct Private;
Private *d;
_______________________________________________
kopete-devel mailing list
kopete-devel@kde.org
https://mail.kde.org/mailman/listinfo/kopete-devel
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic