2 * Copyright (C) 2008 David Greaves
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 "shopper.h" // automake, i8n, gettext
22 #include "QFingerScrollArea.h"
25 #define SCROLL_SENSITIVITY 20 // pixels before we scroll
26 #define SCROLL_SCALE 0.1 // sensitivity to movement
27 #define KINETIC_REFRESH 100 // ms
28 #define VSCALE 100 // velocity scaling to ensure not lost in int rounding
29 #define DECEL 0.75 // velocity reduction factor/kinetic refresh
30 #define DECEL_DURATION 1000 // ms
32 #define DEBOUNCE 200 // after a mouseRelease we disallow MousePress for this long
34 // QFingerScrollArea a QScrollArea that responds to fingers
35 QFingerScrollArea::QFingerScrollArea(QWidget
* parent
) :
42 recently_scrolling(false),
43 scrolling_setup(false),
67 max_x
= qApp
->desktop()->screenGeometry().width();
68 max_y
= qApp
->desktop()->screenGeometry().height();
71 ////////////////////////////////////////////////////////////////
73 void QFingerScrollArea::setScrollConstraints(bool AllowHoriz
, bool AllowVert
)
75 allowVert
= AllowVert
;
76 allowHoriz
= AllowHoriz
;
77 if (!(allowHoriz
or allowVert
)) allowVert
= true; // Don't
79 void QFingerScrollArea::setScrollPaper(bool _scrollPaper
)
81 scrollPaper
= _scrollPaper
;
83 void QFingerScrollArea::setKinetic(bool _kinetic
)
87 ////////////////////////////////////////////////////////////////
88 // Finger scroll implementation - movement
89 void QFingerScrollArea::mousePressEvent ( QMouseEvent
* event
)
91 if (event
->button() == Qt::LeftButton
){
94 QScrollArea::mousePressEvent(event
);
96 void QFingerScrollArea::setupEvent ( QMouseEvent
* event
)
98 start_y
= event
->globalY();
99 scroll_start_y
= verticalScrollBar()->value();
100 scroll_range_y
= verticalScrollBar()->maximum() - verticalScrollBar()->minimum();
102 start_x
= event
->globalX();
103 scroll_start_x
= horizontalScrollBar()->value();
104 scroll_range_x
= horizontalScrollBar()->maximum() - horizontalScrollBar()->minimum();
107 scale_x
= widget()->width()-width();
108 scale_y
= widget()->height()-height();
117 scrolling_setup
= true;
118 DEBUG("scroll setup " <<start_x
<<":"<<start_y
);
120 void QFingerScrollArea::mouseMoveEvent ( QMouseEvent
* event
)
122 // We should get a mousePressedEvent - but we don't always...
123 if (!scrolling_setup
) {
127 if (abs(start_x
- event
->globalX()) > SCROLL_SENSITIVITY
or
128 abs(start_y
- event
->globalY()) > SCROLL_SENSITIVITY
) {
129 if ((allowVert
and scroll_range_y
) or
130 (allowHoriz
and scroll_range_x
))
134 QScrollArea::mouseMoveEvent(event
);
137 curr_y
= event
->globalY();
138 curr_x
= event
->globalX();
140 if (allowVert
and scroll_range_y
) {
141 // (curr_y - start_y) = delta_pixels
142 // scroll_start / scroll_range = fraction_value_of_scrollbar
143 // fraction_value_of_scrollbar * scale_y = pixels above widget base
144 // pixels above widget base + delta_pixels = new_fraction_value_of_scrollbar * scale
145 // new_fraction_value_of_scrollbar = new_value_of_scrollbar / scroll_range
146 // v2 = ((scroll_start / scroll_range) * scale + (curr - start))/scale)*scroll_range
148 // if scale is the scrollarea height then moving will match the scrollbar
150 // if scale is the child widget height then moving will scroll pixel by pixel
153 // DEBUG(scroll_start_y<<" + (("<<start_y<<" - "<<curr_y<<") * "<<scroll_range_y<<")/"<<scale_y);
154 int v_y
= scroll_start_y
+ direction
* ((start_y
- curr_y
) * scroll_range_y
)/scale_y
;
155 verticalScrollBar()->setValue(v_y
);
157 if (allowHoriz
and scroll_range_x
) {
158 int v_x
= scroll_start_x
+ direction
* ((start_x
- curr_x
) * scroll_range_x
)/scale_x
;
159 verticalScrollBar()->setValue(v_x
);
161 curr_time
= event_time
.elapsed();
162 DEBUG("scroll move " <<curr_x
<<":"<<curr_y
<<" time:"<<curr_time
);
164 if (curr_time
> last_ev_time
){
165 if (last_ev_time
){ // first time round we set v to zero
167 vel_x
= (last_x
- curr_x
)*VSCALE
/ (curr_time
- last_ev_time
);
169 vel_y
= (last_y
- curr_y
)*VSCALE
/ (curr_time
- last_ev_time
);
170 DEBUG("Velocity last_y:"<<last_y
<< " curr_y:"<<curr_y
<<" curr_time:"<<curr_time
<<" last_ev_time:"<<last_ev_time
<< " Velocity: "<<vel_y
);
175 // Store the last values
176 last_ev_time
= curr_time
;
182 void QFingerScrollArea::mouseReleaseEvent ( QMouseEvent
* event
)
185 if (scrolling
and event
->button() == Qt::LeftButton
) {
187 if (kinetic
and last_ev_time
) { // Only do velocity if we had a last_ev_time
188 curr_time
= event_time
.elapsed();
190 int y
= event
->globalY();
191 int x
= event
->globalX();
193 DEBUG("scroll stop " <<x
<<":"<<y
<<" time:"<<curr_time
);
196 kineticTimer
=startTimer(KINETIC_REFRESH
);
199 scrolling_setup
= false;
200 recently_scrolling
= true;
201 debounceTimer
= startTimer(DEBOUNCE
);
204 QScrollArea::mouseReleaseEvent(event
);
206 ////////////////////////////////////////////////////////////////
208 void QFingerScrollArea::timerEvent(QTimerEvent
*event
)
210 if (event
->timerId() == debounceTimer
) {
211 recently_scrolling
= false;
212 killTimer(debounceTimer
);
217 if (event
->timerId() == kineticTimer
) {
219 int v_y
= verticalScrollBar()->value() +
220 direction
* vel_y1
* KINETIC_REFRESH
/VSCALE
* scroll_range_y
/ scale_y
;
221 verticalScrollBar()->setValue(v_y
);
226 int v_x
= horizontalScrollBar()->value() +
227 direction
* vel_x1
* KINETIC_REFRESH
/VSCALE
* scroll_range_x
/ scale_x
;
228 horizontalScrollBar()->setValue(v_x
);
233 if (scrolling
or kinetic_cycles
> DECEL_DURATION
/KINETIC_REFRESH
or
234 (vel_x1
== 0 and vel_y1
==0)) {
235 killTimer(kineticTimer
);
240 ////////////////////////////////////////////////////////////////
241 // Child management - event filtering
242 // Handle events down the hierarchy : FIX: as children are added, register them
243 void QFingerScrollArea::childEvent ( QChildEvent
* event
)
245 DEBUG("childEvent is a " << event
->type());
246 if (event
->type() == QEvent::ChildAdded
or
247 event
->type() == 70) { // FIX is this a Qt bug? 70 is ChildInserted which is deprecated in 4.4
248 registerChildrenForFingerScrolling(event
->child());
250 if (event
->removed()) {
251 deregisterChildrenForFingerScrolling(event
->child());
255 void QFingerScrollArea::registerChildrenForFingerScrolling(QObject
*top
)
257 top
->installEventFilter(this);
258 DEBUG("registered a " << top
->metaObject()->className());
260 QList
<QObject
*> children
= top
->findChildren
<QObject
*>();
261 foreach (obj
, children
){
262 DEBUG("registered a " << obj
->metaObject()->className());
263 obj
->installEventFilter(this);
266 void QFingerScrollArea::deregisterChildrenForFingerScrolling(QObject
*top
)
268 top
->removeEventFilter(this);
269 DEBUG("deregistered a " << top
->metaObject()->className());
271 QList
<QObject
*> children
= top
->findChildren
<QObject
*>();
272 foreach (obj
, children
){
273 obj
->removeEventFilter(this);
274 DEBUG("deregistered a " << obj
->metaObject()->className());
278 bool QFingerScrollArea::eventFilter(QObject
*obj
, QEvent
*event
)
280 switch (event
->type()) {
281 case QEvent::MouseButtonPress
:
282 if (recently_scrolling
) {
285 if (static_cast<QMouseEvent
*>(event
)->button() == Qt::LeftButton
){
286 setupEvent ( static_cast<QMouseEvent
*>(event
));
289 case QEvent::MouseButtonRelease
:
291 mouseReleaseEvent(static_cast<QMouseEvent
*>(event
));
294 if (recently_scrolling
) {
298 case QEvent::MouseMove
:
300 mouseMoveEvent(static_cast<QMouseEvent
*>(event
));
304 case QEvent::ChildAdded
:
305 registerChildrenForFingerScrolling(static_cast<QChildEvent
*>(event
)->child());
307 case QEvent::ChildRemoved
:
308 deregisterChildrenForFingerScrolling(static_cast<QChildEvent
*>(event
)->child());
313 // standard event processing
314 return QScrollArea::eventFilter(obj
, event
);