[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