2 * Copyright (C) 2008 David Greaves <david@dgreaves.com>
4 * This software is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2.1 of
7 * the License, or (at your option) any later version.
9 * This software is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this software; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 //#define DEBUG_SHOPPER 1
21 #include "QFingerScrollArea.h"
22 #include "shopper.h" // automake, i8n, gettext
24 #include <QApplication>
25 #include <QDesktopWidget>
27 #include <QMouseEvent>
28 #include <QTimerEvent>
29 #include <QChildEvent>
32 const int QFingerScrollArea::SENSITIVITY
= 20; // pixels before we scroll
33 //const double QFingerScrollArea::SCALE = 0.1; // sensitivity to movement
34 const int QFingerScrollArea::KINETIC_REFRESH
= 100; // ms
35 const int QFingerScrollArea::VSCALE
= 100; // velocity scaling to ensure not lost in int rounding
36 const double QFingerScrollArea::DECEL
= 0.75; // velocity reduction factor/kinetic refresh
37 const int QFingerScrollArea::DECEL_DURATION
= 1000; // ms
38 const int QFingerScrollArea::DEBOUNCE
= 200; // after a mouseRelease we disallow MousePress for this long
40 // QFingerScrollArea a QScrollArea that responds to fingers
41 QFingerScrollArea::QFingerScrollArea(QWidget
* parent
) :
48 recently_scrolling(false),
49 scrolling_setup(false),
73 max_x
= qApp
->desktop()->screenGeometry().width();
74 max_y
= qApp
->desktop()->screenGeometry().height();
77 ////////////////////////////////////////////////////////////////
79 void QFingerScrollArea::setScrollConstraints(bool AllowHoriz
, bool AllowVert
)
81 allowVert
= AllowVert
;
82 allowHoriz
= AllowHoriz
;
83 if (!(allowHoriz
or allowVert
)) allowVert
= true; // Don't
85 void QFingerScrollArea::setScrollPaper(bool _scrollPaper
)
87 scrollPaper
= _scrollPaper
;
89 void QFingerScrollArea::setKinetic(bool _kinetic
)
93 ////////////////////////////////////////////////////////////////
94 // Finger scroll implementation - movement
95 void QFingerScrollArea::mousePressEvent ( QMouseEvent
* event
)
97 if (event
->button() == Qt::LeftButton
){
100 QScrollArea::mousePressEvent(event
);
102 void QFingerScrollArea::setupEvent ( QMouseEvent
* event
)
104 start_y
= event
->globalY();
105 scroll_start_y
= verticalScrollBar()->value();
106 scroll_range_y
= verticalScrollBar()->maximum() - verticalScrollBar()->minimum();
108 start_x
= event
->globalX();
109 scroll_start_x
= horizontalScrollBar()->value();
110 scroll_range_x
= horizontalScrollBar()->maximum() - horizontalScrollBar()->minimum();
113 scale_x
= widget()->width()-width();
114 scale_y
= widget()->height()-height();
123 scrolling_setup
= true;
124 DEBUG("scroll setup " <<start_x
<<":"<<start_y
);
126 void QFingerScrollArea::mouseMoveEvent ( QMouseEvent
* event
)
128 // We should get a mousePressedEvent - but we don't always...
129 if (!scrolling_setup
) {
133 if (abs(start_x
- event
->globalX()) > SENSITIVITY
or
134 abs(start_y
- event
->globalY()) > SENSITIVITY
) {
135 if ((allowVert
and scroll_range_y
) or
136 (allowHoriz
and scroll_range_x
))
140 QScrollArea::mouseMoveEvent(event
);
143 curr_y
= event
->globalY();
144 curr_x
= event
->globalX();
146 if (allowVert
and scroll_range_y
) {
147 // (curr_y - start_y) = delta_pixels
148 // scroll_start / scroll_range = fraction_value_of_scrollbar
149 // fraction_value_of_scrollbar * scale_y = pixels above widget base
150 // pixels above widget base + delta_pixels = new_fraction_value_of_scrollbar * scale
151 // new_fraction_value_of_scrollbar = new_value_of_scrollbar / scroll_range
152 // v2 = ((scroll_start / scroll_range) * scale + (curr - start))/scale)*scroll_range
154 // if scale is the scrollarea height then moving will match the scrollbar
156 // if scale is the child widget height then moving will scroll pixel by pixel
159 // DEBUG(scroll_start_y<<" + (("<<start_y<<" - "<<curr_y<<") * "<<scroll_range_y<<")/"<<scale_y);
160 int v_y
= scroll_start_y
+ direction
* ((start_y
- curr_y
) * scroll_range_y
)/scale_y
;
161 verticalScrollBar()->setValue(v_y
);
163 if (allowHoriz
and scroll_range_x
) {
164 int v_x
= scroll_start_x
+ direction
* ((start_x
- curr_x
) * scroll_range_x
)/scale_x
;
165 verticalScrollBar()->setValue(v_x
);
167 curr_time
= event_time
.elapsed();
168 DEBUG("scroll move " <<curr_x
<<":"<<curr_y
<<" time:"<<curr_time
);
170 if (curr_time
> last_ev_time
){
171 if (last_ev_time
){ // first time round we set v to zero
173 vel_x
= (last_x
- curr_x
)*VSCALE
/ (curr_time
- last_ev_time
);
175 vel_y
= (last_y
- curr_y
)*VSCALE
/ (curr_time
- last_ev_time
);
176 DEBUG("Velocity last_y:"<<last_y
<< " curr_y:"<<curr_y
<<" curr_time:"<<curr_time
<<" last_ev_time:"<<last_ev_time
<< " Velocity: "<<vel_y
);
181 // Store the last values
182 last_ev_time
= curr_time
;
188 void QFingerScrollArea::mouseReleaseEvent ( QMouseEvent
* event
)
191 if (scrolling
and event
->button() == Qt::LeftButton
) {
193 if (kinetic
and last_ev_time
) { // Only do velocity if we had a last_ev_time
194 curr_time
= event_time
.elapsed();
196 int y
= event
->globalY();
197 int x
= event
->globalX();
199 DEBUG("scroll stop " <<x
<<":"<<y
<<" time:"<<curr_time
);
202 kineticTimer
=startTimer(KINETIC_REFRESH
);
205 scrolling_setup
= false;
206 recently_scrolling
= true;
207 debounceTimer
= startTimer(DEBOUNCE
);
210 QScrollArea::mouseReleaseEvent(event
);
212 ////////////////////////////////////////////////////////////////
214 void QFingerScrollArea::timerEvent(QTimerEvent
*event
)
216 if (event
->timerId() == debounceTimer
) {
217 recently_scrolling
= false;
218 killTimer(debounceTimer
);
223 if (event
->timerId() == kineticTimer
) {
225 int v_y
= verticalScrollBar()->value() +
226 direction
* vel_y1
* KINETIC_REFRESH
/VSCALE
* scroll_range_y
/ scale_y
;
227 verticalScrollBar()->setValue(v_y
);
232 int v_x
= horizontalScrollBar()->value() +
233 direction
* vel_x1
* KINETIC_REFRESH
/VSCALE
* scroll_range_x
/ scale_x
;
234 horizontalScrollBar()->setValue(v_x
);
239 if (scrolling
or kinetic_cycles
> DECEL_DURATION
/KINETIC_REFRESH
or
240 (vel_x1
== 0 and vel_y1
==0)) {
241 killTimer(kineticTimer
);
246 ////////////////////////////////////////////////////////////////
247 // Child management - event filtering
248 // Handle events down the hierarchy : FIX: as children are added, register them
249 void QFingerScrollArea::childEvent ( QChildEvent
* event
)
251 DEBUG("childEvent is a " << event
->type());
252 if (event
->type() == QEvent::ChildAdded
or
253 event
->type() == 70) { // FIX is this a Qt bug? 70 is ChildInserted which is deprecated in 4.4
254 registerChildrenForFingerScrolling(event
->child());
256 if (event
->removed()) {
257 deregisterChildrenForFingerScrolling(event
->child());
261 void QFingerScrollArea::registerChildrenForFingerScrolling(QObject
*top
)
263 top
->installEventFilter(this);
264 DEBUG("registered a " << top
->metaObject()->className());
266 QList
<QObject
*> children
= top
->findChildren
<QObject
*>();
267 foreach (obj
, children
){
268 DEBUG("registered a " << obj
->metaObject()->className());
269 obj
->installEventFilter(this);
272 void QFingerScrollArea::deregisterChildrenForFingerScrolling(QObject
*top
)
274 top
->removeEventFilter(this);
275 DEBUG("deregistered a " << top
->metaObject()->className());
277 QList
<QObject
*> children
= top
->findChildren
<QObject
*>();
278 foreach (obj
, children
){
279 obj
->removeEventFilter(this);
280 DEBUG("deregistered a " << obj
->metaObject()->className());
284 bool QFingerScrollArea::eventFilter(QObject
*obj
, QEvent
*event
)
286 switch (event
->type()) {
287 case QEvent::MouseButtonPress
:
288 if (recently_scrolling
) {
291 if (static_cast<QMouseEvent
*>(event
)->button() == Qt::LeftButton
){
292 setupEvent ( static_cast<QMouseEvent
*>(event
));
295 case QEvent::MouseButtonRelease
:
297 mouseReleaseEvent(static_cast<QMouseEvent
*>(event
));
300 if (recently_scrolling
) {
304 case QEvent::MouseMove
:
306 mouseMoveEvent(static_cast<QMouseEvent
*>(event
));
310 case QEvent::ChildAdded
:
311 registerChildrenForFingerScrolling(static_cast<QChildEvent
*>(event
)->child());
313 case QEvent::ChildRemoved
:
314 deregisterChildrenForFingerScrolling(static_cast<QChildEvent
*>(event
)->child());
319 // standard event processing
320 return QScrollArea::eventFilter(obj
, event
);