rhodes 5.5.0.3 → 5.5.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -0
  3. data/lib/commonAPI/coreapi/ext/platform/android/src/com/rho/notification/NotificationSingleton.java +20 -8
  4. data/lib/commonAPI/coreapi/ext/platform/iphone/cpp_based_impl/SystemImpl.mm +1 -2
  5. data/lib/commonAPI/coreapi/ext/system.xml +1 -1
  6. data/platform/android/build/android.rake +1 -0
  7. data/platform/iphone/Classes/AppManager/AppManager.m +44 -3
  8. data/platform/iphone/Classes/NativeView/RhoNativeViewManager.mm +4 -3
  9. data/platform/iphone/Classes/NativeView/RhoNativeViewManagerOC.h +1 -1
  10. data/platform/iphone/Classes/RhoMainView.h +8 -2
  11. data/platform/iphone/Classes/RhoUIWebView.h +75 -0
  12. data/platform/iphone/Classes/RhoUIWebView.m +142 -0
  13. data/platform/iphone/Classes/RhoWKWebView.h +87 -0
  14. data/platform/iphone/Classes/RhoWKWebView.m +187 -0
  15. data/platform/iphone/Classes/RhoWebView.h +72 -0
  16. data/platform/iphone/Classes/RhoWebViewFabrique.h +35 -0
  17. data/platform/iphone/Classes/RhoWebViewFabrique.m +87 -0
  18. data/platform/iphone/Classes/Rhodes.m +17 -6
  19. data/platform/iphone/Classes/Signature.old/SignatureDelegate.m +11 -4
  20. data/platform/iphone/Classes/Signature/SignatureDelegate.m +3 -4
  21. data/platform/iphone/Classes/SimpleMainView.h +7 -5
  22. data/platform/iphone/Classes/SimpleMainView.m +174 -179
  23. data/platform/iphone/Classes/SplitView/RightViewController.h +2 -2
  24. data/platform/iphone/Classes/SplitView/RightViewController.m +9 -10
  25. data/platform/iphone/Classes/SplitView/SplittedMainView.h +2 -3
  26. data/platform/iphone/Classes/SplitView/SplittedMainView.m +10 -7
  27. data/platform/iphone/Classes/TabbedMainView.h +2 -1
  28. data/platform/iphone/Classes/TabbedMainView.m +13 -10
  29. data/platform/iphone/Classes/URLProtocol/CRhoURLProtocol.m +21 -5
  30. data/platform/iphone/Classes/WebView.m +1 -1
  31. data/platform/iphone/RhoAppBaseLib/RhoAppBaseLib.xcodeproj/project.pbxproj +40 -3
  32. data/platform/iphone/RhoLib/RhoLib.xcodeproj/project.pbxproj +4 -0
  33. data/platform/iphone/rbuild/iphone.rake +51 -0
  34. data/platform/shared/common/RhoNativeViewManager.h +9 -9
  35. data/platform/shared/common/RhodesApp.cpp +13 -1
  36. data/platform/shared/net/HttpServer.cpp +12 -2
  37. data/platform/shared/qt/rhodes/ExternalWebView.ui +11 -2
  38. data/platform/shared/qt/rhodes/QtMainWindow.cpp +9 -7
  39. data/platform/shared/qt/rhodes/QtMainWindow.ui +13 -4
  40. data/platform/shared/qt/rhodes/qkineticscroller.cpp +1245 -0
  41. data/platform/shared/qt/rhodes/qkineticscroller.h +165 -0
  42. data/platform/shared/qt/rhodes/qkineticscroller_p.h +168 -0
  43. data/platform/shared/qt/rhodes/qtflickgesture.cpp +696 -0
  44. data/platform/shared/qt/rhodes/qtflickgesture_p.h +107 -0
  45. data/platform/shared/qt/rhodes/qtscroller.cpp +2080 -0
  46. data/platform/shared/qt/rhodes/qtscroller.h +138 -0
  47. data/platform/shared/qt/rhodes/qtscroller_p.h +205 -0
  48. data/platform/shared/qt/rhodes/qtscrollerfilter.cpp +350 -0
  49. data/platform/shared/qt/rhodes/qtscrollerfilter_p.h +110 -0
  50. data/platform/shared/qt/rhodes/qtscrollerproperties.cpp +412 -0
  51. data/platform/shared/qt/rhodes/qtscrollerproperties.h +135 -0
  52. data/platform/shared/qt/rhodes/qtscrollerproperties_p.h +90 -0
  53. data/platform/shared/qt/rhodes/qtscrollevent.cpp +190 -0
  54. data/platform/shared/qt/rhodes/qtscrollevent.h +100 -0
  55. data/platform/shared/qt/rhodes/qtscrollevent_p.h +33 -0
  56. data/platform/shared/qt/rhodes/qwebviewkineticscroller.cpp +347 -0
  57. data/platform/shared/qt/rhodes/qwebviewkineticscroller.h +90 -0
  58. data/platform/shared/qt/rhodes/qwebviewselectionsuppressor.h +113 -0
  59. data/platform/shared/qt/rhodes/rhodes.pro +19 -0
  60. data/res/generators/rhogen.rb +307 -15
  61. data/res/generators/templates/application/app/Settings/err_sync.erb +12 -6
  62. data/res/generators/templates/application/app/Settings/home.erb +32 -17
  63. data/res/generators/templates/application/app/Settings/index.erb +55 -26
  64. data/res/generators/templates/application/app/Settings/javascript_index.html +111 -0
  65. data/res/generators/templates/application/app/Settings/javascript_login.html +65 -0
  66. data/res/generators/templates/application/app/Settings/login.erb +25 -19
  67. data/res/generators/templates/application/app/Settings/reset.erb +18 -9
  68. data/res/generators/templates/application/app/Settings/wait.erb +10 -7
  69. data/res/generators/templates/application/app/index.erb +32 -20
  70. data/res/generators/templates/application/app/javascript_index.html +66 -0
  71. data/res/generators/templates/application/app/javascript_index.js +250 -0
  72. data/res/generators/templates/application/app/layout.erb +12 -67
  73. data/res/generators/templates/application/javascript_build.yml +41 -0
  74. data/res/generators/templates/application/javascript_rhoconfig.txt +123 -0
  75. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap-theme.css +587 -0
  76. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap-theme.css.map +1 -0
  77. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap-theme.min.css +6 -0
  78. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap-theme.min.css.map +1 -0
  79. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap.css +6757 -0
  80. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap.css.map +1 -0
  81. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap.min.css +6 -0
  82. data/res/generators/templates/application/public/bootstrap-3.3.7/css/bootstrap.min.css.map +1 -0
  83. data/res/generators/templates/application/public/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.eot +0 -0
  84. data/res/generators/templates/application/public/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.svg +288 -0
  85. data/res/generators/templates/application/public/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.ttf +0 -0
  86. data/res/generators/templates/application/public/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff +0 -0
  87. data/res/generators/templates/application/public/bootstrap-3.3.7/fonts/glyphicons-halflings-regular.woff2 +0 -0
  88. data/res/generators/templates/application/public/bootstrap-3.3.7/js/bootstrap.js +2377 -0
  89. data/res/generators/templates/application/public/bootstrap-3.3.7/js/bootstrap.min.js +7 -0
  90. data/res/generators/templates/application/public/bootstrap-3.3.7/js/npm.js +13 -0
  91. data/res/generators/templates/application/public/css/style.css +3 -0
  92. data/res/generators/templates/application/public/jquery/jquery-3.1.1.min.js +4 -0
  93. data/res/generators/templates/application/public/jquery/jquery-3.1.1.min.map +1 -0
  94. data/res/generators/templates/application/rhoconfig.txt +16 -0
  95. data/res/generators/templates/iphone_project/Bremen7.xcodeproj/project.pbxproj +4 -0
  96. data/res/generators/templates/model/edit.erb +22 -21
  97. data/res/generators/templates/model/index.erb +24 -22
  98. data/res/generators/templates/model/javascript_edit.html +65 -0
  99. data/res/generators/templates/model/javascript_index.html +56 -0
  100. data/res/generators/templates/model/javascript_index.js +83 -0
  101. data/res/generators/templates/model/javascript_model.js +16 -0
  102. data/res/generators/templates/model/javascript_new.html +64 -0
  103. data/res/generators/templates/model/javascript_show.html +66 -0
  104. data/res/generators/templates/model/new.erb +22 -19
  105. data/res/generators/templates/model/show.erb +22 -14
  106. data/res/prebuild_base_app/app/index.erb +31 -18
  107. data/res/prebuild_base_app/app/layout.erb +11 -56
  108. data/version +1 -1
  109. metadata +59 -24
  110. data/res/generators/templates/application/public/css/android.css +0 -418
  111. data/res/generators/templates/application/public/css/iphone.css +0 -378
  112. data/res/generators/templates/application/public/css/jqmobile-patch.css +0 -62
  113. data/res/generators/templates/application/public/css/re_webkit.css +0 -736
  114. data/res/generators/templates/application/public/css/re_webkit_flat.css +0 -753
  115. data/res/generators/templates/application/public/css/windows_mobile.css +0 -327
  116. data/res/generators/templates/application/public/jqmobile/images/ajax-loader.gif +0 -0
  117. data/res/generators/templates/application/public/jqmobile/images/icon-search-black.png +0 -0
  118. data/res/generators/templates/application/public/jqmobile/images/icons-18-black.png +0 -0
  119. data/res/generators/templates/application/public/jqmobile/images/icons-18-white.png +0 -0
  120. data/res/generators/templates/application/public/jqmobile/images/icons-36-black.png +0 -0
  121. data/res/generators/templates/application/public/jqmobile/images/icons-36-white.png +0 -0
  122. data/res/generators/templates/application/public/jqmobile/jquery.mobile-1.4.5.min.css +0 -3
  123. data/res/generators/templates/application/public/jqmobile/jquery.mobile-1.4.5.min.js +0 -10
  124. data/res/generators/templates/application/public/jqmobile/jquery.mobile-1.4.5.min.map +0 -1
  125. data/res/generators/templates/application/public/jqmobile/jquery.mobile.structure-1.4.5.min.css +0 -3
  126. data/res/generators/templates/application/public/jqmobile/jquery.mobile.theme-1.4.5.min.css +0 -3
  127. data/res/generators/templates/application/public/jquery/jquery-1.9.1.min.js +0 -5
  128. data/res/generators/templates/application/public/jquery/jquery-1.9.1.min.map +0 -1
  129. data/res/generators/templates/application/public/js/application.js +0 -1
  130. data/res/generators/templates/application/public/js/jqmobile-patch.js +0 -466
  131. data/res/generators/templates/application/public/js/syncengine.js +0 -504
@@ -0,0 +1,107 @@
1
+ /****************************************************************************
2
+ **
3
+ ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4
+ ** All rights reserved.
5
+ ** Contact: Nokia Corporation (qt-info@nokia.com)
6
+ **
7
+ ** This file is part of the QtGui module of the Qt Toolkit.
8
+ **
9
+ ** $QT_BEGIN_LICENSE:LGPL$
10
+ ** No Commercial Usage
11
+ ** This file contains pre-release code and may not be distributed.
12
+ ** You may use this file in accordance with the terms and conditions
13
+ ** contained in the Technology Preview License Agreement accompanying
14
+ ** this package.
15
+ **
16
+ ** GNU Lesser General Public License Usage
17
+ ** Alternatively, this file may be used under the terms of the GNU Lesser
18
+ ** General Public License version 2.1 as published by the Free Software
19
+ ** Foundation and appearing in the file LICENSE.LGPL included in the
20
+ ** packaging of this file. Please review the following information to
21
+ ** ensure the GNU Lesser General Public License version 2.1 requirements
22
+ ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
+ **
24
+ ** In addition, as a special exception, Nokia gives you certain additional
25
+ ** rights. These rights are described in the Nokia Qt LGPL Exception
26
+ ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
+ **
28
+ ** If you have questions regarding the use of this file, please contact
29
+ ** Nokia at qt-info@nokia.com.
30
+ **
31
+ **
32
+ **
33
+ **
34
+ **
35
+ **
36
+ **
37
+ **
38
+ ** $QT_END_LICENSE$
39
+ **
40
+ ****************************************************************************/
41
+
42
+ #ifndef QTFLICKGESTURE_P_H
43
+ #define QTFLICKGESTURE_P_H
44
+
45
+ //
46
+ // W A R N I N G
47
+ // -------------
48
+ //
49
+ // This file is not part of the Qt API. It exists for the convenience
50
+ // of other Qt classes. This header file may change from version to
51
+ // version without notice, or even be removed.
52
+ //
53
+ // We mean it.
54
+ //
55
+
56
+ #include "qobject.h"
57
+ #include "qpointer.h"
58
+ #include "qevent.h"
59
+ #include "qgesture.h"
60
+ #include "qgesturerecognizer.h"
61
+ #include "qtscroller.h"
62
+
63
+ #ifndef QT_NO_GESTURES
64
+
65
+ class PressDelayHandler;
66
+
67
+ class QtFlickGesture : public QGesture
68
+ {
69
+ Q_OBJECT
70
+
71
+ public:
72
+ QtFlickGesture(QObject *receiver, Qt::MouseButton button, QObject *parent = 0);
73
+ ~QtFlickGesture();
74
+
75
+ protected:
76
+ bool eventFilter(QObject *o, QEvent *e);
77
+
78
+ private:
79
+ QPointer<QObject> receiver;
80
+ QtScroller *receiverScroller;
81
+ Qt::MouseButton button; // NoButton == Touch
82
+ bool macIgnoreWheel;
83
+ // QWidget::mapFromGlobal is very expensive on X11, so we cache the global position of the widget
84
+ QPointer<QWidget> receiverWindow;
85
+ QPoint receiverWindowPos;
86
+
87
+ static PressDelayHandler *pressDelayHandler;
88
+
89
+ friend class QtFlickGestureRecognizer;
90
+ };
91
+
92
+ class QtFlickGestureRecognizer : public QGestureRecognizer
93
+ {
94
+ public:
95
+ QtFlickGestureRecognizer(Qt::MouseButton button);
96
+
97
+ QGesture *create(QObject *target);
98
+ QGestureRecognizer::Result recognize(QGesture *state, QObject *watched, QEvent *event);
99
+ void reset(QGesture *state);
100
+
101
+ private:
102
+ Qt::MouseButton button; // NoButton == Touch
103
+ };
104
+
105
+ #endif // QT_NO_GESTURES
106
+
107
+ #endif // QTFLICKGESTURE_P_H
@@ -0,0 +1,2080 @@
1
+ /****************************************************************************
2
+ **
3
+ ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4
+ ** All rights reserved.
5
+ ** Contact: Nokia Corporation (qt-info@nokia.com)
6
+ **
7
+ ** This file is part of the QtGui module of the Qt Toolkit.
8
+ **
9
+ ** $QT_BEGIN_LICENSE:LGPL$
10
+ ** No Commercial Usage
11
+ ** This file contains pre-release code and may not be distributed.
12
+ ** You may use this file in accordance with the terms and conditions
13
+ ** contained in the Technology Preview License Agreement accompanying
14
+ ** this package.
15
+ **
16
+ ** GNU Lesser General Public License Usage
17
+ ** Alternatively, this file may be used under the terms of the GNU Lesser
18
+ ** General Public License version 2.1 as published by the Free Software
19
+ ** Foundation and appearing in the file LICENSE.LGPL included in the
20
+ ** packaging of this file. Please review the following information to
21
+ ** ensure the GNU Lesser General Public License version 2.1 requirements
22
+ ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23
+ **
24
+ ** In addition, as a special exception, Nokia gives you certain additional
25
+ ** rights. These rights are described in the Nokia Qt LGPL Exception
26
+ ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27
+ **
28
+ ** If you have questions regarding the use of this file, please contact
29
+ ** Nokia at qt-info@nokia.com.
30
+ **
31
+ **
32
+ **
33
+ **
34
+ **
35
+ **
36
+ **
37
+ **
38
+ ** $QT_END_LICENSE$
39
+ **
40
+ ****************************************************************************/
41
+
42
+ #include "qevent.h"
43
+ #include "qwidget.h"
44
+ #include "qtscroller.h"
45
+ #include "qtflickgesture_p.h"
46
+ #include "qtscroller_p.h"
47
+ #include "qtscrollerproperties.h"
48
+ #include "qtscrollerproperties_p.h"
49
+
50
+ #include "math.h"
51
+
52
+ #include <QTime>
53
+ #include <QMap>
54
+ #include <QApplication>
55
+ #include <QAbstractScrollArea>
56
+ #include <QGraphicsObject>
57
+ #include <QGraphicsScene>
58
+ #include <QGraphicsView>
59
+ #include <QDesktopWidget>
60
+ #include <QtCore/qmath.h>
61
+ #include <QtGui/qevent.h>
62
+ #include <qnumeric.h>
63
+
64
+ // completely optional warning, but you can go nuts if you forget to set the scroll mode right
65
+ #define ITEMVIEW_SCROLLMODE_WARNING 1
66
+ #ifdef ITEMVIEW_SCROLLMODE_WARNING
67
+ # include <QAbstractItemView>
68
+ #endif
69
+
70
+ #include <QtDebug>
71
+
72
+ // vvv QScroller Solution only
73
+ #include "qtscrollerfilter_p.h"
74
+ #include "qtscrollevent.h"
75
+ // ^^^ QScroller Solution only
76
+
77
+ #if defined(Q_WS_X11)
78
+ # include <QX11Info>
79
+ # include <QLibrary>
80
+ #endif
81
+
82
+
83
+ bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);
84
+
85
+ //#define QSCROLLER_DEBUG
86
+
87
+ #ifdef QSCROLLER_DEBUG
88
+ # define qScrollerDebug qDebug
89
+ #else
90
+ # define qScrollerDebug while (false) qDebug
91
+ #endif
92
+
93
+ QDebug &operator<<(QDebug &dbg, const QtScrollerPrivate::ScrollSegment &s)
94
+ {
95
+ dbg << "\n Time: start:" << s.startTime << " duration:" << s.deltaTime << " stop progress:" << s.stopProgress;
96
+ dbg << "\n Pos: start:" << s.startPos << " delta:" << s.deltaPos << " stop:" << s.stopPos;
97
+ dbg << "\n Curve: type:" << s.curve.type() << "\n";
98
+ return dbg;
99
+ }
100
+
101
+
102
+ // a few helper operators to make the code below a lot more readable:
103
+ // otherwise a lot of ifs would have to be multi-line to check both the x
104
+ // and y coordinate separately.
105
+
106
+ // returns true only if the abs. value of BOTH x and y are <= f
107
+ inline bool operator<=(const QPointF &p, qreal f)
108
+ {
109
+ return (qAbs(p.x()) <= f) && (qAbs(p.y()) <= f);
110
+ }
111
+
112
+ // returns true only if the abs. value of BOTH x and y are < f
113
+ inline bool operator<(const QPointF &p, qreal f)
114
+ {
115
+ return (qAbs(p.x()) < f) && (qAbs(p.y()) < f);
116
+ }
117
+
118
+ // returns true if the abs. value of EITHER x or y are >= f
119
+ inline bool operator>=(const QPointF &p, qreal f)
120
+ {
121
+ return (qAbs(p.x()) >= f) || (qAbs(p.y()) >= f);
122
+ }
123
+
124
+ // returns true if the abs. value of EITHER x or y are > f
125
+ inline bool operator>(const QPointF &p, qreal f)
126
+ {
127
+ return (qAbs(p.x()) > f) || (qAbs(p.y()) > f);
128
+ }
129
+
130
+ // returns a new point with both coordinates having the abs. value of the original one
131
+ inline QPointF qAbs(const QPointF &p)
132
+ {
133
+ return QPointF(qAbs(p.x()), qAbs(p.y()));
134
+ }
135
+
136
+ // returns a new point with all components of p1 multiplied by the corresponding components of p2
137
+ inline QPointF operator*(const QPointF &p1, const QPointF &p2)
138
+ {
139
+ return QPointF(p1.x() * p2.x(), p1.y() * p2.y());
140
+ }
141
+
142
+ // returns a new point with all components of p1 divided by the corresponding components of p2
143
+ inline QPointF operator/(const QPointF &p1, const QPointF &p2)
144
+ {
145
+ return QPointF(p1.x() / p2.x(), p1.y() / p2.y());
146
+ }
147
+
148
+ inline QPointF clampToRect(const QPointF &p, const QRectF &rect)
149
+ {
150
+ qreal x = qBound(rect.left(), p.x(), rect.right());
151
+ qreal y = qBound(rect.top(), p.y(), rect.bottom());
152
+ return QPointF(x, y);
153
+ }
154
+
155
+ // returns -1, 0 or +1 according to r being <0, ==0 or >0
156
+ inline int qSign(qreal r)
157
+ {
158
+ return (r < 0) ? -1 : ((r > 0) ? 1 : 0);
159
+ }
160
+
161
+ // this version is not mathematically exact, but it just works for every
162
+ // easing curve type (even custom ones)
163
+
164
+ static qreal differentialForProgress(const QEasingCurve &curve, qreal pos)
165
+ {
166
+ const qreal dx = 0.01;
167
+ qreal left = (pos < qreal(0.5)) ? pos : pos - qreal(dx);
168
+ qreal right = (pos >= qreal(0.5)) ? pos : pos + qreal(dx);
169
+ qreal d = (curve.valueForProgress(right) - curve.valueForProgress(left)) / qreal(dx);
170
+
171
+ //qScrollerDebug() << "differentialForProgress(type: " << curve.type() << ", pos: " << pos << ") = " << d;
172
+
173
+ return d;
174
+ }
175
+
176
+ // this version is not mathematically exact, but it just works for every
177
+ // easing curve type (even custom ones)
178
+
179
+ static qreal progressForValue(const QEasingCurve &curve, qreal value)
180
+ {
181
+ if (curve.type() >= QEasingCurve::InElastic &&
182
+ curve.type() < QEasingCurve::Custom) {
183
+ qWarning("progressForValue(): QEasingCurves of type %d do not have an inverse, since they are not injective.", curve.type());
184
+ return value;
185
+ }
186
+ if (value < qreal(0) || value > qreal(1))
187
+ return value;
188
+
189
+ qreal progress = value, left(0), right(1);
190
+ for (int iterations = 6; iterations; --iterations) {
191
+ qreal v = curve.valueForProgress(progress);
192
+ if (v < value)
193
+ left = progress;
194
+ else if (v > value)
195
+ right = progress;
196
+ else
197
+ break;
198
+ progress = (left + right) / qreal(2);
199
+ }
200
+ return progress;
201
+ }
202
+
203
+
204
+ class QScrollTimer : public QAbstractAnimation
205
+ {
206
+ public:
207
+ QScrollTimer(QtScrollerPrivate *_d)
208
+ : d(_d), ignoreUpdate(false), skip(0)
209
+ { }
210
+
211
+ int duration() const
212
+ {
213
+ return -1;
214
+ }
215
+
216
+ void start()
217
+ {
218
+ // QAbstractAnimation::start() will immediately call
219
+ // updateCurrentTime(), but our state is not set correctly yet
220
+ ignoreUpdate = true;
221
+ QAbstractAnimation::start();
222
+ ignoreUpdate = false;
223
+ skip = 0;
224
+ }
225
+
226
+ protected:
227
+ void updateCurrentTime(int /*currentTime*/)
228
+ {
229
+ if (!ignoreUpdate) {
230
+ if (++skip >= d->frameRateSkip()) {
231
+ skip = 0;
232
+ d->timerTick();
233
+ }
234
+ }
235
+ }
236
+
237
+ private:
238
+ QtScrollerPrivate *d;
239
+ bool ignoreUpdate;
240
+ int skip;
241
+ };
242
+
243
+ /*!
244
+ \class QScroller
245
+ \brief The QScroller class enables kinetic scrolling for any scrolling widget or graphics item.
246
+ \class QtScroller
247
+ \brief The QtScroller class enables kinetic scrolling for any scrolling widget or graphics item.
248
+ \since 4.8
249
+
250
+ With kinetic scrolling, the user can push the widget in a given
251
+ direction and it will continue to scroll in this direction until it is
252
+ stopped either by the user or by friction. Aspects of inertia, friction
253
+ and other physical concepts can be changed in order to fine-tune an
254
+ intuitive user experience.
255
+
256
+ The QtScroller object is the object that stores the current position and
257
+ scrolling speed and takes care of updates.
258
+ QtScroller can be triggered by a flick gesture
259
+
260
+ \code
261
+ QWidget *w = ...;
262
+ QtScroller::grabGesture(w, QtScroller::LeftMouseButtonGesture);
263
+ \endcode
264
+
265
+ or directly like this:
266
+
267
+ \code
268
+ QWidget *w = ...;
269
+ QtScroller *scroller = QtScroller::scroller(w);
270
+ scroller->scrollTo(QPointF(100, 100));
271
+ \endcode
272
+
273
+ The scrolled QObjects receive a QScrollPrepareEvent whenever the scroller needs to
274
+ update its geometry information and a QScrollEvent whenever the content of the object should
275
+ actually be scrolled.
276
+
277
+ The scroller uses the global QAbstractAnimation timer to generate its QScrollEvents. This
278
+ can be changed with QtScrollerProperties::FrameRate on a per-QtScroller basis.
279
+
280
+ Several examples in the \c scroller examples directory show how QtScroller,
281
+ QScrollEvent and the scroller gesture can be used.
282
+
283
+ Even though this kinetic scroller has a large number of settings available via
284
+ QtScrollerProperties, we recommend that you leave them all at their default, platform optimized
285
+ values. Before changing them you can experiment with the \c plot example in
286
+ the \c scroller examples directory.
287
+
288
+ \sa QtScrollEvent, QtScrollPrepareEvent, QtScrollerProperties
289
+ */
290
+
291
+
292
+ QMap<QObject *, QtScroller *> QtScrollerPrivate::allScrollers;
293
+ QSet<QtScroller *> QtScrollerPrivate::activeScrollers;
294
+
295
+ /*!
296
+ Returns \c true if a QtScroller object was already created for \a target; \c false otherwise.
297
+
298
+ \sa scroller()
299
+ */
300
+ bool QtScroller::hasScroller(QObject *target)
301
+ {
302
+ return (QtScrollerPrivate::allScrollers.value(target));
303
+ }
304
+
305
+ /*!
306
+ Returns the scroller for the given \a target.
307
+ As long as the object exists this function will always return the same QtScroller instance.
308
+ If no QtScroller exists for the \a target, one will implicitly be created.
309
+ At no point more than one QtScroller will be active on an object.
310
+
311
+ \sa hasScroller(), target()
312
+ */
313
+ QtScroller *QtScroller::scroller(QObject *target)
314
+ {
315
+ if (!target) {
316
+ qWarning("QtScroller::scroller() was called with a null target.");
317
+ return 0;
318
+ }
319
+
320
+ if (QtScrollerPrivate::allScrollers.contains(target))
321
+ return QtScrollerPrivate::allScrollers.value(target);
322
+
323
+ QtScroller *s = new QtScroller(target);
324
+ QtScrollerPrivate::allScrollers.insert(target, s);
325
+ return s;
326
+ }
327
+
328
+ /*!
329
+ \overload
330
+ This is the const version of scroller().
331
+ */
332
+ const QtScroller *QtScroller::scroller(const QObject *target)
333
+ {
334
+ return scroller(const_cast<QObject*>(target));
335
+ }
336
+
337
+ /*!
338
+ Returns an application wide list of currently active QtScroller objects.
339
+ Active QtScroller objects are in a state() that is not QtScroller::Inactive.
340
+ This function is useful when writing your own gesture recognizer.
341
+ */
342
+ QList<QtScroller *> QtScroller::activeScrollers()
343
+ {
344
+ return QtScrollerPrivate::activeScrollers.toList();
345
+ }
346
+
347
+ /*!
348
+ Returns the target object of this scroller.
349
+ \sa hasScroller(), scroller()
350
+ */
351
+ QObject *QtScroller::target() const
352
+ {
353
+ Q_D(const QtScroller);
354
+ return d->target;
355
+ }
356
+
357
+ /*!
358
+ \fn QtScroller::scrollerPropertiesChanged(const QtScrollerProperties &newProperties);
359
+
360
+ QtScroller emits this signal whenever its scroller properties change.
361
+ \a newProperties are the new scroller properties.
362
+
363
+ \sa scrollerProperties
364
+ */
365
+
366
+
367
+ /*! \property QtScroller::scrollerProperties
368
+ \brief The scroller properties of this scroller.
369
+ The properties are used by the QtScroller to determine its scrolling behavior.
370
+ */
371
+ QtScrollerProperties QtScroller::scrollerProperties() const
372
+ {
373
+ Q_D(const QtScroller);
374
+ return d->properties;
375
+ }
376
+
377
+ void QtScroller::setScrollerProperties(const QtScrollerProperties &sp)
378
+ {
379
+ Q_D(QtScroller);
380
+ if (d->properties != sp) {
381
+ d->properties = sp;
382
+ emit scrollerPropertiesChanged(sp);
383
+
384
+ // we need to force the recalculation here, since the overshootPolicy may have changed and
385
+ // existing segments may include an overshoot animation.
386
+ d->recalcScrollingSegments(true);
387
+ }
388
+ }
389
+
390
+
391
+ /*!
392
+ Registers a custom scroll gesture recognizer, grabs it for the \a
393
+ target and returns the resulting gesture type. If \a scrollGestureType is
394
+ set to TouchGesture the gesture triggers on touch events. If it is set to
395
+ one of LeftMouseButtonGesture, RightMouseButtonGesture or
396
+ MiddleMouseButtonGesture it triggers on mouse events of the
397
+ corresponding button.
398
+
399
+ Only one scroll gesture can be active on a single object at the same
400
+ time. If you call this function twice on the same object, it will
401
+ ungrab the existing gesture before grabbing the new one.
402
+
403
+ \note To avoid unwanted side-effects, mouse events are consumed while
404
+ the gesture is triggered. Since the initial mouse press event is
405
+ not consumed, the gesture sends a fake mouse release event
406
+ at the global position \c{(INT_MIN, INT_MIN)}. This ensures that
407
+ internal states of the widget that received the original mouse press
408
+ are consistent.
409
+
410
+ \sa ungrabGesture, grabbedGesture
411
+ */
412
+ Qt::GestureType QtScroller::grabGesture(QObject *target, ScrollerGestureType scrollGestureType)
413
+ {
414
+ // ensure that a scroller for target is created
415
+ QtScroller *s = scroller(target);
416
+ if (!s)
417
+ return Qt::GestureType(0);
418
+
419
+ QtScrollerPrivate *sp = s->d_ptr;
420
+ if (sp->recognizer)
421
+ ungrabGesture(target); // ungrab the old gesture
422
+
423
+ Qt::MouseButton button;
424
+ switch (scrollGestureType) {
425
+ case LeftMouseButtonGesture : button = Qt::LeftButton; break;
426
+ case RightMouseButtonGesture : button = Qt::RightButton; break;
427
+ case MiddleMouseButtonGesture: button = Qt::MidButton; break;
428
+ default :
429
+ case TouchGesture : button = Qt::NoButton; break; // NoButton == Touch
430
+ }
431
+
432
+ sp->recognizer = new QtFlickGestureRecognizer(button);
433
+ sp->recognizerType = QGestureRecognizer::registerRecognizer(sp->recognizer);
434
+ if (target->isWidgetType()) {
435
+ QWidget *widget = static_cast<QWidget *>(target);
436
+ widget->grabGesture(sp->recognizerType);
437
+ if (scrollGestureType == TouchGesture)
438
+ widget->setAttribute(Qt::WA_AcceptTouchEvents);
439
+ #if ITEMVIEW_SCROLLMODE_WARNING
440
+ QAbstractItemView *view = 0;
441
+ if ((view = qobject_cast<QAbstractItemView *>(widget->parentWidget())) &&
442
+ (view->viewport() == widget)) {
443
+ if (view->verticalScrollMode() != QAbstractItemView::ScrollPerPixel)
444
+ qWarning("QtScroller::grabGesture() was called on a QAbstractItemView with verticaScrollMode not set to ScrollPerPixel");
445
+ if (view->horizontalScrollMode() != QAbstractItemView::ScrollPerPixel)
446
+ qWarning("QtScroller::grabGesture() was called on a QAbstractItemView with horizontalScrollMode not set to ScrollPerPixel");
447
+ }
448
+ #endif // ITEMVIEW_SCROLLMODE_WARNING
449
+ } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
450
+ if (scrollGestureType == TouchGesture)
451
+ go->setAcceptTouchEvents(true);
452
+ go->grabGesture(sp->recognizerType);
453
+ }
454
+
455
+ // vvv QScroller Solution only
456
+ QtScrollerFilter::instance()->add(target);
457
+ // ^^^ QScroller Solution only
458
+
459
+ return sp->recognizerType;
460
+ }
461
+
462
+ /*!
463
+ Returns the gesture type currently grabbed for the \a target or 0 if no
464
+ gesture is grabbed.
465
+
466
+ \sa grabGesture, ungrabGesture
467
+ */
468
+ Qt::GestureType QtScroller::grabbedGesture(QObject *target)
469
+ {
470
+ QtScroller *s = scroller(target);
471
+ if (s && s->d_ptr)
472
+ return s->d_ptr->recognizerType;
473
+ else
474
+ return Qt::GestureType(0);
475
+ }
476
+
477
+ /*!
478
+ Ungrabs the gesture for the \a target.
479
+ Does nothing if no gesture is grabbed.
480
+
481
+ \sa grabGesture, grabbedGesture
482
+ */
483
+ void QtScroller::ungrabGesture(QObject *target)
484
+ {
485
+ QtScroller *s = scroller(target);
486
+ if (!s)
487
+ return;
488
+
489
+ QtScrollerPrivate *sp = s->d_ptr;
490
+ if (!sp->recognizer)
491
+ return; // nothing to do
492
+
493
+ if (target->isWidgetType()) {
494
+ QWidget *widget = static_cast<QWidget *>(target);
495
+ widget->ungrabGesture(sp->recognizerType);
496
+
497
+ } else if (QGraphicsObject *go = qobject_cast<QGraphicsObject*>(target)) {
498
+ go->ungrabGesture(sp->recognizerType);
499
+ }
500
+
501
+ QGestureRecognizer::unregisterRecognizer(sp->recognizerType);
502
+ // do not delete the recognizer. The QGestureManager is doing this.
503
+ sp->recognizer = 0;
504
+
505
+ // vvv QScroller Solution only
506
+ QtScrollerFilter::instance()->remove(target);
507
+ // ^^^ QScroller Solution only
508
+ }
509
+
510
+ /*!
511
+ \internal
512
+ */
513
+ QtScroller::QtScroller(QObject *target)
514
+ : d_ptr(new QtScrollerPrivate(this, target))
515
+ {
516
+ Q_ASSERT(target); // you can't create a scroller without a target in any normal way
517
+ Q_D(QtScroller);
518
+ d->init();
519
+ }
520
+
521
+ /*!
522
+ \internal
523
+ */
524
+ QtScroller::~QtScroller()
525
+ {
526
+ Q_D(QtScroller);
527
+ QGestureRecognizer::unregisterRecognizer(d->recognizerType);
528
+ // do not delete the recognizer. The QGestureManager is doing this.
529
+ d->recognizer = 0;
530
+ QtScrollerPrivate::allScrollers.remove(d->target);
531
+ QtScrollerPrivate::activeScrollers.remove(this);
532
+
533
+ delete d_ptr;
534
+ }
535
+
536
+
537
+ /*!
538
+ \fn QtScroller::stateChanged(QtScroller::State newState);
539
+
540
+ QtScroller emits this signal whenever the state changes. \a newState is the new State.
541
+
542
+ \sa state
543
+ */
544
+
545
+ /*!
546
+ \property QtScroller::state
547
+ \brief the state of the scroller
548
+
549
+ \sa QtScroller::State
550
+ */
551
+ QtScroller::State QtScroller::state() const
552
+ {
553
+ Q_D(const QtScroller);
554
+ return d->state;
555
+ }
556
+
557
+ /*!
558
+ Stops the scroller and resets its state back to Inactive.
559
+ */
560
+ void QtScroller::stop()
561
+ {
562
+ Q_D(QtScroller);
563
+ if (d->state != Inactive) {
564
+ QPointF here = clampToRect(d->contentPosition, d->contentPosRange);
565
+ qreal snapX = d->nextSnapPos(here.x(), 0, Qt::Horizontal);
566
+ qreal snapY = d->nextSnapPos(here.y(), 0, Qt::Vertical);
567
+ QPointF snap = here;
568
+ if (!qIsNaN(snapX))
569
+ snap.setX(snapX);
570
+ if (!qIsNaN(snapY))
571
+ snap.setY(snapY);
572
+ d->contentPosition = snap;
573
+ d->overshootPosition = QPointF(0, 0);
574
+
575
+ d->setState(Inactive);
576
+ }
577
+ }
578
+
579
+ /*!
580
+ Returns the pixel per meter metric for the scrolled widget.
581
+
582
+ The value is reported for both the x and y axis separately by using a QPointF.
583
+
584
+ \note Please note that this value should be physically correct. The actual DPI settings
585
+ that Qt returns for the display may be reported wrongly on purpose by the underlying
586
+ windowing system, for example on Mac OS X or Maemo 5.
587
+ */
588
+ QPointF QtScroller::pixelPerMeter() const
589
+ {
590
+ Q_D(const QtScroller);
591
+ QPointF ppm = d->pixelPerMeter;
592
+
593
+ if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(d->target)) {
594
+ QTransform viewtr;
595
+ //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
596
+ if (go->scene() && !go->scene()->views().isEmpty())
597
+ viewtr = go->scene()->views().first()->viewportTransform();
598
+ QTransform tr = go->deviceTransform(viewtr);
599
+ if (tr.isScaling()) {
600
+ QPointF p0 = tr.map(QPointF(0, 0));
601
+ QPointF px = tr.map(QPointF(1, 0));
602
+ QPointF py = tr.map(QPointF(0, 1));
603
+ ppm.rx() /= QLineF(p0, px).length();
604
+ ppm.ry() /= QLineF(p0, py).length();
605
+ }
606
+ }
607
+ return ppm;
608
+ }
609
+
610
+ /*!
611
+ Returns the current scrolling velocity in meter per second when the state is Scrolling or Dragging.
612
+ Returns a zero velocity otherwise.
613
+
614
+ The velocity is reported for both the x and y axis separately by using a QPointF.
615
+
616
+ \sa pixelPerMeter()
617
+ */
618
+ QPointF QtScroller::velocity() const
619
+ {
620
+ Q_D(const QtScroller);
621
+ const QtScrollerPropertiesPrivate *sp = d->properties.d.data();
622
+
623
+ switch (state()) {
624
+ case Dragging:
625
+ return d->releaseVelocity;
626
+ case Scrolling: {
627
+ QPointF vel;
628
+ qint64 now = d->monotonicTimer.elapsed();
629
+
630
+ if (!d->xSegments.isEmpty()) {
631
+ const QtScrollerPrivate::ScrollSegment &s = d->xSegments.head();
632
+ qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
633
+ qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
634
+ vel.setX(v);
635
+ }
636
+
637
+ if (!d->ySegments.isEmpty()) {
638
+ const QtScrollerPrivate::ScrollSegment &s = d->ySegments.head();
639
+ qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
640
+ qreal v = qSign(s.deltaPos) * qreal(s.deltaTime) / qreal(1000) * sp->decelerationFactor * qreal(0.5) * differentialForProgress(s.curve, progress);
641
+ vel.setY(v);
642
+ }
643
+ return vel;
644
+ }
645
+ default:
646
+ return QPointF(0, 0);
647
+ }
648
+ }
649
+
650
+ /*!
651
+ Returns the estimated final position for the current scroll movement.
652
+ Returns the current position if the scroller state is not Scrolling.
653
+ The result is undefined when the scroller state is Inactive.
654
+
655
+ The target position is in pixel.
656
+
657
+ \sa pixelPerMeter(), scrollTo()
658
+ */
659
+ QPointF QtScroller::finalPosition() const
660
+ {
661
+ Q_D(const QtScroller);
662
+ return QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
663
+ d->scrollingSegmentsEndPos(Qt::Vertical));
664
+ }
665
+
666
+ /*!
667
+ Starts scrolling the widget so that point \a pos is at the top-left position in
668
+ the viewport.
669
+
670
+ The behaviour when scrolling outside the valid scroll area is undefined.
671
+ In this case the scroller might or might not overshoot.
672
+
673
+ The scrolling speed will be calculated so that the given position will
674
+ be reached after a platform-defined time span.
675
+
676
+ \a pos is given in viewport coordinates.
677
+
678
+ \sa ensureVisible()
679
+ */
680
+ void QtScroller::scrollTo(const QPointF &pos)
681
+ {
682
+ // we could make this adjustable via QtScrollerProperties
683
+ scrollTo(pos, 300);
684
+ }
685
+
686
+ /*! \overload
687
+
688
+ This version will reach its destination position in \a scrollTime milliseconds.
689
+ */
690
+ void QtScroller::scrollTo(const QPointF &pos, int scrollTime)
691
+ {
692
+ Q_D(QtScroller);
693
+
694
+ if (d->state == Pressed || d->state == Dragging )
695
+ return;
696
+
697
+ // no need to resend a prepare event if we are already scrolling
698
+ if (d->state == Inactive && !d->prepareScrolling(QPointF()))
699
+ return;
700
+
701
+ QPointF newpos = clampToRect(pos, d->contentPosRange);
702
+ qreal snapX = d->nextSnapPos(newpos.x(), 0, Qt::Horizontal);
703
+ qreal snapY = d->nextSnapPos(newpos.y(), 0, Qt::Vertical);
704
+ if (!qIsNaN(snapX))
705
+ newpos.setX(snapX);
706
+ if (!qIsNaN(snapY))
707
+ newpos.setY(snapY);
708
+
709
+ qScrollerDebug() << "QtScroller::scrollTo(req:" << pos << " [pix] / snap:" << newpos << ", " << scrollTime << " [ms])";
710
+
711
+ if (newpos == d->contentPosition + d->overshootPosition)
712
+ return;
713
+
714
+ QPointF vel = velocity();
715
+
716
+ if (scrollTime < 0)
717
+ scrollTime = 0;
718
+ qreal time = qreal(scrollTime) / 1000;
719
+
720
+ d->createScrollToSegments(vel.x(), time, newpos.x(), Qt::Horizontal, QtScrollerPrivate::ScrollTypeScrollTo);
721
+ d->createScrollToSegments(vel.y(), time, newpos.y(), Qt::Vertical, QtScrollerPrivate::ScrollTypeScrollTo);
722
+
723
+ if (!scrollTime)
724
+ d->setContentPositionHelperScrolling();
725
+ d->setState(scrollTime ? Scrolling : Inactive);
726
+ }
727
+
728
+ /*!
729
+ Starts scrolling so that the rectangle \a rect is visible inside the
730
+ viewport with additional margins specified in pixels by \a xmargin and \a ymargin around
731
+ the rect.
732
+
733
+ In cases where it is not possible to fit the rect plus margins inside the viewport the contents
734
+ are scrolled so that as much as possible is visible from \a rect.
735
+
736
+ The scrolling speed is calculated so that the given position is reached after a platform-defined
737
+ time span.
738
+
739
+ This function performs the actual scrolling by calling scrollTo().
740
+
741
+ \sa scrollTo
742
+ */
743
+ void QtScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin)
744
+ {
745
+ // we could make this adjustable via QtScrollerProperties
746
+ ensureVisible(rect, xmargin, ymargin, 1000);
747
+ }
748
+
749
+ /*! \overload
750
+
751
+ This version will reach its destination position in \a scrollTime milliseconds.
752
+ */
753
+ void QtScroller::ensureVisible(const QRectF &rect, qreal xmargin, qreal ymargin, int scrollTime)
754
+ {
755
+ Q_D(QtScroller);
756
+
757
+ if (d->state == Pressed || d->state == Dragging)
758
+ return;
759
+
760
+ if (d->state == Inactive && !d->prepareScrolling(QPointF()))
761
+ return;
762
+
763
+ // -- calculate the current pos (or the position after the current scroll)
764
+ QPointF startPos = d->contentPosition + d->overshootPosition;
765
+ startPos = QPointF(d->scrollingSegmentsEndPos(Qt::Horizontal),
766
+ d->scrollingSegmentsEndPos(Qt::Vertical));
767
+
768
+ QRectF marginRect(rect.x() - xmargin, rect.y() - ymargin,
769
+ rect.width() + 2 * xmargin, rect.height() + 2 * ymargin);
770
+
771
+ QSizeF visible = d->viewportSize;
772
+ QRectF visibleRect(startPos, visible);
773
+
774
+ qScrollerDebug() << "QtScroller::ensureVisible(" << rect << " [pix], " << xmargin << " [pix], " << ymargin << " [pix], " << scrollTime << "[ms])";
775
+ qScrollerDebug() << " --> content position:" << d->contentPosition;
776
+
777
+ if (visibleRect.contains(marginRect))
778
+ return;
779
+
780
+ QPointF newPos = startPos;
781
+
782
+ if (visibleRect.width() < rect.width()) {
783
+ // at least try to move the rect into view
784
+ if (rect.left() > visibleRect.left())
785
+ newPos.setX(rect.left());
786
+ else if (rect.right() < visibleRect.right())
787
+ newPos.setX(rect.right() - visible.width());
788
+
789
+ } else if (visibleRect.width() < marginRect.width()) {
790
+ newPos.setX(rect.center().x() - visibleRect.width() / 2);
791
+ } else if (marginRect.left() > visibleRect.left()) {
792
+ newPos.setX(marginRect.left());
793
+ } else if (marginRect.right() < visibleRect.right()) {
794
+ newPos.setX(marginRect.right() - visible.width());
795
+ }
796
+
797
+ if (visibleRect.height() < rect.height()) {
798
+ // at least try to move the rect into view
799
+ if (rect.top() > visibleRect.top())
800
+ newPos.setX(rect.top());
801
+ else if (rect.bottom() < visibleRect.bottom())
802
+ newPos.setX(rect.bottom() - visible.height());
803
+
804
+ } else if (visibleRect.height() < marginRect.height()) {
805
+ newPos.setY(rect.center().y() - visibleRect.height() / 2);
806
+ } else if (marginRect.top() > visibleRect.top()) {
807
+ newPos.setY(marginRect.top());
808
+ } else if (marginRect.bottom() < visibleRect.bottom()) {
809
+ newPos.setY(marginRect.bottom() - visible.height());
810
+ }
811
+
812
+ // clamp to maximum content position
813
+ newPos = clampToRect(newPos, d->contentPosRange);
814
+ if (newPos == startPos)
815
+ return;
816
+
817
+ scrollTo(newPos, scrollTime);
818
+ }
819
+
820
+ /*! This function resends the QScrollPrepareEvent.
821
+ Calling resendPrepareEvent triggers a QScrollPrepareEvent from the scroller.
822
+ This allows the receiver to re-set content position and content size while
823
+ scrolling.
824
+ Calling this function while in the Inactive state is useless as the prepare event
825
+ is sent again before scrolling starts.
826
+ */
827
+ void QtScroller::resendPrepareEvent()
828
+ {
829
+ Q_D(QtScroller);
830
+ d->prepareScrolling(d->pressPosition);
831
+ }
832
+
833
+ /*! Set the snap positions for the horizontal axis to a list of \a positions.
834
+ This overwrites all previously set snap positions and also a previously
835
+ set snapping interval.
836
+ Snapping can be deactivated by setting an empty list of positions.
837
+ */
838
+ void QtScroller::setSnapPositionsX(const QList<qreal> &positions)
839
+ {
840
+ Q_D(QtScroller);
841
+ d->snapPositionsX = positions;
842
+ d->snapIntervalX = 0.0;
843
+
844
+ d->recalcScrollingSegments();
845
+ }
846
+
847
+ /*! Set the snap positions for the horizontal axis to regular spaced intervals.
848
+ The first snap position is at \a first. The next at \a first + \a interval.
849
+ This can be used to implement a list header.
850
+ This overwrites all previously set snap positions and also a previously
851
+ set snapping interval.
852
+ Snapping can be deactivated by setting an interval of 0.0
853
+ */
854
+ void QtScroller::setSnapPositionsX(qreal first, qreal interval)
855
+ {
856
+ Q_D(QtScroller);
857
+ d->snapFirstX = first;
858
+ d->snapIntervalX = interval;
859
+ d->snapPositionsX.clear();
860
+
861
+ d->recalcScrollingSegments();
862
+ }
863
+
864
+ /*! Set the snap positions for the vertical axis to a list of \a positions.
865
+ This overwrites all previously set snap positions and also a previously
866
+ set snapping interval.
867
+ Snapping can be deactivated by setting an empty list of positions.
868
+ */
869
+ void QtScroller::setSnapPositionsY(const QList<qreal> &positions)
870
+ {
871
+ Q_D(QtScroller);
872
+ d->snapPositionsY = positions;
873
+ d->snapIntervalY = 0.0;
874
+
875
+ d->recalcScrollingSegments();
876
+ }
877
+
878
+ /*! Set the snap positions for the vertical axis to regular spaced intervals.
879
+ The first snap position is at \a first. The next at \a first + \a interval.
880
+ This overwrites all previously set snap positions and also a previously
881
+ set snapping interval.
882
+ Snapping can be deactivated by setting an interval of 0.0
883
+ */
884
+ void QtScroller::setSnapPositionsY(qreal first, qreal interval)
885
+ {
886
+ Q_D(QtScroller);
887
+ d->snapFirstY = first;
888
+ d->snapIntervalY = interval;
889
+ d->snapPositionsY.clear();
890
+
891
+ d->recalcScrollingSegments();
892
+ }
893
+
894
+
895
+
896
+ // -------------- private ------------
897
+
898
+ QtScrollerPrivate::QtScrollerPrivate(QtScroller *q, QObject *_target)
899
+ : target(_target)
900
+ , recognizer(0)
901
+ , recognizerType(Qt::CustomGesture)
902
+ , state(QtScroller::Inactive)
903
+ , firstScroll(true)
904
+ , pressTimestamp(0)
905
+ , lastTimestamp(0)
906
+ , snapFirstX(-1.0)
907
+ , snapIntervalX(0.0)
908
+ , snapFirstY(-1.0)
909
+ , snapIntervalY(0.0)
910
+ , scrollTimer(new QScrollTimer(this))
911
+ , q_ptr(q)
912
+ {
913
+ connect(target, SIGNAL(destroyed(QObject*)), this, SLOT(targetDestroyed()));
914
+ }
915
+
916
+ void QtScrollerPrivate::init()
917
+ {
918
+ setDpiFromWidget(0);
919
+ monotonicTimer.start();
920
+ }
921
+
922
+ void QtScrollerPrivate::sendEvent(QObject *o, QEvent *e)
923
+ {
924
+ qt_sendSpontaneousEvent(o, e);
925
+ }
926
+
927
+ const char *QtScrollerPrivate::stateName(QtScroller::State state)
928
+ {
929
+ switch (state) {
930
+ case QtScroller::Inactive: return "inactive";
931
+ case QtScroller::Pressed: return "pressed";
932
+ case QtScroller::Dragging: return "dragging";
933
+ case QtScroller::Scrolling: return "scrolling";
934
+ default: return "(invalid)";
935
+ }
936
+ }
937
+
938
+ const char *QtScrollerPrivate::inputName(QtScroller::Input input)
939
+ {
940
+ switch (input) {
941
+ case QtScroller::InputPress: return "press";
942
+ case QtScroller::InputMove: return "move";
943
+ case QtScroller::InputRelease: return "release";
944
+ default: return "(invalid)";
945
+ }
946
+ }
947
+
948
+ void QtScrollerPrivate::targetDestroyed()
949
+ {
950
+ scrollTimer->stop();
951
+ delete q_ptr;
952
+ }
953
+
954
+ void QtScrollerPrivate::timerTick()
955
+ {
956
+ struct timerevent {
957
+ QtScroller::State state;
958
+ typedef void (QtScrollerPrivate::*timerhandler_t)();
959
+ timerhandler_t handler;
960
+ };
961
+
962
+ timerevent timerevents[] = {
963
+ { QtScroller::Dragging, &QtScrollerPrivate::timerEventWhileDragging },
964
+ { QtScroller::Scrolling, &QtScrollerPrivate::timerEventWhileScrolling },
965
+ };
966
+
967
+ for (int i = 0; i < int(sizeof(timerevents) / sizeof(*timerevents)); ++i) {
968
+ timerevent *te = timerevents + i;
969
+
970
+ if (state == te->state) {
971
+ (this->*te->handler)();
972
+ return;
973
+ }
974
+ }
975
+
976
+ scrollTimer->stop();
977
+ }
978
+
979
+ /*!
980
+ This function is used by gesture recognizers to inform the scroller about a new input event.
981
+ The scroller changes its internal state() according to the input event and its attached
982
+ scroller properties. The scroller doesn't distinguish between the kind of input device the
983
+ event came from. Therefore the event needs to be split into the \a input type, a \a position and a
984
+ milli-second \a timestamp. The \a position needs to be in the target's coordinate system.
985
+
986
+ The return value is \c true if the event should be consumed by the calling filter or \c false
987
+ if the event should be forwarded to the control.
988
+
989
+ \note Using grabGesture() should be sufficient for most use cases.
990
+ */
991
+ bool QtScroller::handleInput(Input input, const QPointF &position, qint64 timestamp)
992
+ {
993
+ Q_D(QtScroller);
994
+
995
+ qScrollerDebug() << "QtScroller::handleInput(" << input << ", " << d->stateName(d->state) << ", " << position << ", " << timestamp << ")";
996
+ struct statechange {
997
+ State state;
998
+ Input input;
999
+ typedef bool (QtScrollerPrivate::*inputhandler_t)(const QPointF &position, qint64 timestamp);
1000
+ inputhandler_t handler;
1001
+ };
1002
+
1003
+ statechange statechanges[] = {
1004
+ { QtScroller::Inactive, InputPress, &QtScrollerPrivate::pressWhileInactive },
1005
+ { QtScroller::Pressed, InputMove, &QtScrollerPrivate::moveWhilePressed },
1006
+ { QtScroller::Pressed, InputRelease, &QtScrollerPrivate::releaseWhilePressed },
1007
+ { QtScroller::Dragging, InputMove, &QtScrollerPrivate::moveWhileDragging },
1008
+ { QtScroller::Dragging, InputRelease, &QtScrollerPrivate::releaseWhileDragging },
1009
+ { QtScroller::Scrolling, InputPress, &QtScrollerPrivate::pressWhileScrolling }
1010
+ };
1011
+
1012
+ for (int i = 0; i < int(sizeof(statechanges) / sizeof(*statechanges)); ++i) {
1013
+ statechange *sc = statechanges + i;
1014
+
1015
+ if (d->state == sc->state && input == sc->input)
1016
+ return (d->*sc->handler)(position - d->overshootPosition, timestamp);
1017
+ }
1018
+ return false;
1019
+ }
1020
+
1021
+ #if !defined(Q_WS_MAC)
1022
+ // the Mac version is implemented in qtscroller_mac.mm
1023
+
1024
+ QPointF QtScrollerPrivate::realDpi(int screen)
1025
+ {
1026
+ # ifdef Q_WS_MAEMO_5
1027
+ Q_UNUSED(screen);
1028
+
1029
+ // The DPI value is hardcoded to 96 on Maemo5:
1030
+ // https://projects.maemo.org/bugzilla/show_bug.cgi?id=152525
1031
+ // This value (260) is only correct for the N900 though, but
1032
+ // there's no way to get the real DPI at run time.
1033
+ return QPointF(260, 260);
1034
+
1035
+ # elif defined(Q_WS_X11) && !defined(QT_NO_XRANDR)
1036
+ // Avoid including the libXRandR header for a very simple struct
1037
+ struct structXRRScreenSize
1038
+ {
1039
+ int width, height;
1040
+ int mwidth, mheight;
1041
+ };
1042
+ typedef structXRRScreenSize *(*PtrXRRSizes)(Display *, int, int *);
1043
+ typedef int (*PtrXRRRootToScreen)(Display *, Qt::HANDLE);
1044
+ static PtrXRRSizes ptrXRRSizes = 0;
1045
+ static PtrXRRRootToScreen ptrXRRRootToScreen = 0;
1046
+ static bool resolvedXRR = false;
1047
+
1048
+ if (!resolvedXRR) {
1049
+ QLibrary xrandrLib(QLatin1String("Xrandr"), 2);
1050
+ if (!xrandrLib.load()) { // try without the version number
1051
+ xrandrLib.setFileName(QLatin1String("Xrandr"));
1052
+ xrandrLib.load();
1053
+ }
1054
+ if (xrandrLib.isLoaded()) {
1055
+ ptrXRRSizes = (PtrXRRSizes) xrandrLib.resolve("XRRSizes");
1056
+ ptrXRRRootToScreen = (PtrXRRRootToScreen) xrandrLib.resolve("XRRRootToScreen");
1057
+ }
1058
+ resolvedXRR = true;
1059
+ }
1060
+
1061
+ if (ptrXRRSizes && ptrXRRRootToScreen) {
1062
+ int nsizes = 0;
1063
+ // QDesktopWidget is based on Xinerama screens, which do not always
1064
+ // correspond to RandR screens: NVidia's TwinView e.g. will show up
1065
+ // as 2 screens in QDesktopWidget, but libXRandR will only see 1 screen.
1066
+ // (although with the combined size of the Xinerama screens).
1067
+ // Additionally, libXrandr will simply crash when calling XRRSizes
1068
+ // for (the non-existant) screen 1 in this scenario.
1069
+ Qt::HANDLE root = QX11Info::appRootWindow(screen == -1 ? QX11Info::appScreen() : screen);
1070
+ int randrscreen = (root != 0) ? ptrXRRRootToScreen(QX11Info::display(), root) : -1;
1071
+
1072
+ structXRRScreenSize *sizes = ptrXRRSizes(QX11Info::display(), randrscreen == -1 ? 0 : randrscreen, &nsizes);
1073
+
1074
+ if (nsizes > 0 && sizes && sizes->width && sizes->height && sizes->mwidth && sizes->mheight) {
1075
+ qScrollerDebug() << "XRandR DPI:" << QPointF(qreal(25.4) * qreal(sizes->width) / qreal(sizes->mwidth),
1076
+ qreal(25.4) * qreal(sizes->height) / qreal(sizes->mheight));
1077
+ return QPointF(qreal(25.4) * qreal(sizes->width) / qreal(sizes->mwidth),
1078
+ qreal(25.4) * qreal(sizes->height) / qreal(sizes->mheight));
1079
+ }
1080
+ }
1081
+ # endif
1082
+
1083
+ QWidget *w = QApplication::desktop()->screen(screen);
1084
+ return QPointF(w->physicalDpiX(), w->physicalDpiY());
1085
+ }
1086
+
1087
+ #endif // !Q_WS_MAC
1088
+
1089
+
1090
+ /*! \internal
1091
+ Returns the resolution of the used screen.
1092
+ */
1093
+ QPointF QtScrollerPrivate::dpi() const
1094
+ {
1095
+ return pixelPerMeter * qreal(0.0254);
1096
+ }
1097
+
1098
+ /*! \internal
1099
+ Sets the resolution used for scrolling.
1100
+ This resolution is only used by the kinetic scroller. If you change this
1101
+ then the scroller will behave quite different as a lot of the values are
1102
+ given in physical distances (millimeter).
1103
+ */
1104
+ void QtScrollerPrivate::setDpi(const QPointF &dpi)
1105
+ {
1106
+ pixelPerMeter = dpi / qreal(0.0254);
1107
+ }
1108
+
1109
+ /*! \internal
1110
+ Sets the dpi used for scrolling to the value of the widget.
1111
+ */
1112
+ void QtScrollerPrivate::setDpiFromWidget(QWidget *widget)
1113
+ {
1114
+ QDesktopWidget *dw = QApplication::desktop();
1115
+ setDpi(realDpi(widget ? dw->screenNumber(widget) : dw->primaryScreen()));
1116
+ }
1117
+
1118
+ /*! \internal
1119
+ Updates the velocity during dragging.
1120
+ Sets releaseVelocity.
1121
+ */
1122
+ void QtScrollerPrivate::updateVelocity(const QPointF &deltaPixelRaw, qint64 deltaTime)
1123
+ {
1124
+ if (deltaTime <= 0)
1125
+ return;
1126
+
1127
+ Q_Q(QtScroller);
1128
+ QPointF ppm = q->pixelPerMeter();
1129
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1130
+ QPointF deltaPixel = deltaPixelRaw;
1131
+
1132
+ qScrollerDebug() << "QtScroller::updateVelocity(" << deltaPixelRaw << " [delta pix], " << deltaTime << " [delta ms])";
1133
+
1134
+ // faster than 2.5mm/ms seems bogus (that would be a screen height in ~20 ms)
1135
+ if (((deltaPixelRaw / qreal(deltaTime)).manhattanLength() / ((ppm.x() + ppm.y()) / 2) * 1000) > qreal(2.5))
1136
+ deltaPixel = deltaPixelRaw * qreal(2.5) * ppm / 1000 / (deltaPixelRaw / qreal(deltaTime)).manhattanLength();
1137
+
1138
+ QPointF newv = -deltaPixel / qreal(deltaTime) * qreal(1000) / ppm;
1139
+ // around 95% of all updates are in the [1..50] ms range, so make sure
1140
+ // to scale the smoothing factor over that range: this way a 50ms update
1141
+ // will have full impact, while 5ms update will only have a 10% impact.
1142
+ qreal smoothing = sp->dragVelocitySmoothingFactor * qMin(qreal(deltaTime), qreal(50)) / qreal(50);
1143
+
1144
+ // only smooth if we already have a release velocity and only if the
1145
+ // user hasn't stopped to move his finger for more than 100ms
1146
+ if ((releaseVelocity != QPointF(0, 0)) && (deltaTime < 100)) {
1147
+ qScrollerDebug() << "SMOOTHED from " << newv << " to " << newv * smoothing + releaseVelocity * (qreal(1) - smoothing);
1148
+ // smooth x or y only if the new velocity is either 0 or at least in
1149
+ // the same direction of the release velocity
1150
+ if (!newv.x() || (qSign(releaseVelocity.x()) == qSign(newv.x())))
1151
+ newv.setX(newv.x() * smoothing + releaseVelocity.x() * (qreal(1) - smoothing));
1152
+ if (!newv.y() || (qSign(releaseVelocity.y()) == qSign(newv.y())))
1153
+ newv.setY(newv.y() * smoothing + releaseVelocity.y() * (qreal(1) - smoothing));
1154
+ } else
1155
+ qScrollerDebug() << "NO SMOOTHING to " << newv;
1156
+
1157
+ releaseVelocity.setX(qBound(-sp->maximumVelocity, newv.x(), sp->maximumVelocity));
1158
+ releaseVelocity.setY(qBound(-sp->maximumVelocity, newv.y(), sp->maximumVelocity));
1159
+
1160
+ qScrollerDebug() << " --> new velocity:" << releaseVelocity;
1161
+ }
1162
+
1163
+ void QtScrollerPrivate::pushSegment(ScrollType type, qreal deltaTime, qreal stopProgress, qreal startPos, qreal deltaPos, qreal stopPos, QEasingCurve::Type curve, Qt::Orientation orientation)
1164
+ {
1165
+ if (startPos == stopPos || deltaPos == 0)
1166
+ return;
1167
+
1168
+ ScrollSegment s;
1169
+ if (orientation == Qt::Horizontal && !xSegments.isEmpty())
1170
+ s.startTime = xSegments.last().startTime + xSegments.last().deltaTime * xSegments.last().stopProgress;
1171
+ else if (orientation == Qt::Vertical && !ySegments.isEmpty())
1172
+ s.startTime = ySegments.last().startTime + ySegments.last().deltaTime * ySegments.last().stopProgress;
1173
+ else
1174
+ s.startTime = monotonicTimer.elapsed();
1175
+
1176
+ s.startPos = startPos;
1177
+ s.deltaPos = deltaPos;
1178
+ s.stopPos = stopPos;
1179
+ s.deltaTime = deltaTime * 1000;
1180
+ s.stopProgress = stopProgress;
1181
+ s.curve.setType(curve);
1182
+ s.type = type;
1183
+
1184
+ if (orientation == Qt::Horizontal)
1185
+ xSegments.enqueue(s);
1186
+ else
1187
+ ySegments.enqueue(s);
1188
+
1189
+ qScrollerDebug() << "+++ Added a new ScrollSegment: " << s;
1190
+ }
1191
+
1192
+
1193
+ /*! \internal
1194
+ Clears the old segments and recalculates them if the current segments are not longer valid
1195
+ */
1196
+ void QtScrollerPrivate::recalcScrollingSegments(bool forceRecalc)
1197
+ {
1198
+ Q_Q(QtScroller);
1199
+ QPointF ppm = q->pixelPerMeter();
1200
+
1201
+ releaseVelocity = q->velocity();
1202
+
1203
+ if (forceRecalc || !scrollingSegmentsValid(Qt::Horizontal))
1204
+ createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal);
1205
+
1206
+ if (forceRecalc || !scrollingSegmentsValid(Qt::Vertical))
1207
+ createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical);
1208
+ }
1209
+
1210
+ /*! \internal
1211
+ Returns the end position after the current scroll has finished.
1212
+ */
1213
+ qreal QtScrollerPrivate::scrollingSegmentsEndPos(Qt::Orientation orientation) const
1214
+ {
1215
+ if (orientation == Qt::Horizontal) {
1216
+ if (xSegments.isEmpty())
1217
+ return contentPosition.x() + overshootPosition.x();
1218
+ else
1219
+ return xSegments.last().stopPos;
1220
+ } else {
1221
+ if (ySegments.isEmpty())
1222
+ return contentPosition.y() + overshootPosition.y();
1223
+ else
1224
+ return ySegments.last().stopPos;
1225
+ }
1226
+ }
1227
+
1228
+ /*! \internal
1229
+ Checks if the scroller segment end in a valid position.
1230
+ */
1231
+ bool QtScrollerPrivate::scrollingSegmentsValid(Qt::Orientation orientation)
1232
+ {
1233
+ QQueue<ScrollSegment> *segments;
1234
+ qreal minPos;
1235
+ qreal maxPos;
1236
+
1237
+ if (orientation == Qt::Horizontal) {
1238
+ segments = &xSegments;
1239
+ minPos = contentPosRange.left();
1240
+ maxPos = contentPosRange.right();
1241
+ } else {
1242
+ segments = &ySegments;
1243
+ minPos = contentPosRange.top();
1244
+ maxPos = contentPosRange.bottom();
1245
+ }
1246
+
1247
+ if (segments->isEmpty())
1248
+ return true;
1249
+
1250
+ const ScrollSegment &last = segments->last();
1251
+ qreal stopPos = last.stopPos;
1252
+
1253
+ if (last.type == ScrollTypeScrollTo)
1254
+ return true; // scrollTo is always valid
1255
+
1256
+ if (last.type == ScrollTypeOvershoot &&
1257
+ (stopPos != minPos && stopPos != maxPos))
1258
+ return false;
1259
+
1260
+ if (stopPos < minPos || stopPos > maxPos)
1261
+ return false;
1262
+
1263
+ if (stopPos == minPos || stopPos == maxPos) // the begin and the end of the list are always ok
1264
+ return true;
1265
+
1266
+ qreal nextSnap = nextSnapPos(stopPos, 0, orientation);
1267
+ if (!qIsNaN(nextSnap) && stopPos != nextSnap)
1268
+ return false;
1269
+
1270
+ return true;
1271
+ }
1272
+
1273
+ /*! \internal
1274
+ Creates the sections needed to scroll to the specific \a endPos to the segments queue.
1275
+ */
1276
+ void QtScrollerPrivate::createScrollToSegments(qreal v, qreal deltaTime, qreal endPos, Qt::Orientation orientation, ScrollType type)
1277
+ {
1278
+ Q_UNUSED(v);
1279
+
1280
+ if (orientation == Qt::Horizontal)
1281
+ xSegments.clear();
1282
+ else
1283
+ ySegments.clear();
1284
+
1285
+ qScrollerDebug() << "+++ createScrollToSegments: t:" << deltaTime << "ep:" << endPos << "o:" << int(orientation);
1286
+
1287
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1288
+
1289
+ qreal startPos = (orientation == Qt::Horizontal) ? contentPosition.x() + overshootPosition.x()
1290
+ : contentPosition.y() + overshootPosition.y();
1291
+ qreal deltaPos = (endPos - startPos) / 2;
1292
+
1293
+ pushSegment(type, deltaTime * qreal(0.3), qreal(1.0), startPos, deltaPos, startPos + deltaPos, QEasingCurve::InQuad, orientation);
1294
+ pushSegment(type, deltaTime * qreal(0.7), qreal(1.0), startPos + deltaPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
1295
+ }
1296
+
1297
+ /*! \internal
1298
+ */
1299
+ void QtScrollerPrivate::createScrollingSegments(qreal v, qreal startPos, qreal ppm, Qt::Orientation orientation)
1300
+ {
1301
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1302
+
1303
+ QtScrollerProperties::OvershootPolicy policy;
1304
+ qreal minPos;
1305
+ qreal maxPos;
1306
+ qreal viewSize;
1307
+
1308
+ if (orientation == Qt::Horizontal) {
1309
+ xSegments.clear();
1310
+ policy = sp->hOvershootPolicy;
1311
+ minPos = contentPosRange.left();
1312
+ maxPos = contentPosRange.right();
1313
+ viewSize = viewportSize.width();
1314
+ } else {
1315
+ ySegments.clear();
1316
+ policy = sp->vOvershootPolicy;
1317
+ minPos = contentPosRange.top();
1318
+ maxPos = contentPosRange.bottom();
1319
+ viewSize = viewportSize.height();
1320
+ }
1321
+
1322
+ bool alwaysOvershoot = (policy == QtScrollerProperties::OvershootAlwaysOn);
1323
+ bool noOvershoot = (policy == QtScrollerProperties::OvershootAlwaysOff) || !sp->overshootScrollDistanceFactor;
1324
+ bool canOvershoot = !noOvershoot && (alwaysOvershoot || maxPos);
1325
+
1326
+ qScrollerDebug() << "+++ createScrollingSegments: s:" << startPos << "maxPos:" << maxPos << "o:" << int(orientation);
1327
+
1328
+ qScrollerDebug() << "v = " << v << ", decelerationFactor = " << sp->decelerationFactor << ", curveType = " << sp->scrollingCurve.type();
1329
+
1330
+ // This is only correct for QEasingCurve::OutQuad (linear velocity,
1331
+ // constant deceleration), but the results look and feel ok for OutExpo
1332
+ // and OutSine as well
1333
+
1334
+ // v(t) = deltaTime * a * 0.5 * differentialForProgress(t / deltaTime)
1335
+ // v(0) = vrelease
1336
+ // v(deltaTime) = 0
1337
+ // deltaTime = (2 * vrelease) / (a * differntial(0))
1338
+
1339
+ // pos(t) = integrate(v(t)dt)
1340
+ // pos(t) = vrelease * t - 0.5 * a * t * t
1341
+ // pos(t) = deltaTime * a * 0.5 * progress(t / deltaTime) * deltaTime
1342
+ // deltaPos = pos(deltaTime)
1343
+
1344
+ qreal deltaTime = (qreal(2) * qAbs(v)) / (sp->decelerationFactor * differentialForProgress(sp->scrollingCurve, 0));
1345
+ qreal deltaPos = qSign(v) * deltaTime * deltaTime * qreal(0.5) * sp->decelerationFactor * ppm;
1346
+ qreal endPos = startPos + deltaPos;
1347
+
1348
+ qScrollerDebug() << " Real Delta:" << deltaPos;
1349
+
1350
+ // -- determine snap points
1351
+ qreal nextSnap = nextSnapPos(endPos, 0, orientation);
1352
+ qreal lowerSnapPos = nextSnapPos(startPos, -1, orientation);
1353
+ qreal higherSnapPos = nextSnapPos(startPos, 1, orientation);
1354
+
1355
+ qScrollerDebug() << " Real Delta:" << lowerSnapPos <<"-"<<nextSnap <<"-"<<higherSnapPos;
1356
+
1357
+ // - check if we can reach another snap point
1358
+ if (nextSnap > higherSnapPos || qIsNaN(higherSnapPos))
1359
+ higherSnapPos = nextSnap;
1360
+ if (nextSnap < lowerSnapPos || qIsNaN(lowerSnapPos))
1361
+ lowerSnapPos = nextSnap;
1362
+
1363
+ // -- check if are in overshoot and end in overshoot
1364
+ if ((startPos < minPos && endPos < minPos) ||
1365
+ (startPos > maxPos && endPos > maxPos)) {
1366
+ qreal stopPos = endPos < minPos ? minPos : maxPos;
1367
+ qreal oDeltaTime = sp->overshootScrollTime;
1368
+
1369
+ pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), startPos, stopPos - startPos, stopPos, sp->scrollingCurve.type(), orientation);
1370
+ return;
1371
+ }
1372
+
1373
+ if (qAbs(v) < sp->minimumVelocity) {
1374
+
1375
+ qScrollerDebug() << "### below minimum Vel" << orientation;
1376
+
1377
+ // - no snap points or already at one
1378
+ if (qIsNaN(nextSnap) || nextSnap == startPos)
1379
+ return; // nothing to do, no scrolling needed.
1380
+
1381
+ // - decide which point to use
1382
+
1383
+ qreal snapDistance = higherSnapPos - lowerSnapPos;
1384
+
1385
+ qreal pressDistance = (orientation == Qt::Horizontal) ?
1386
+ lastPosition.x() - pressPosition.x() :
1387
+ lastPosition.y() - pressPosition.y();
1388
+
1389
+ // if not dragged far enough, pick the next snap point.
1390
+ if (sp->snapPositionRatio == 0.0 || qAbs(pressDistance / sp->snapPositionRatio) > snapDistance)
1391
+ endPos = nextSnap;
1392
+ else if (pressDistance < 0.0)
1393
+ endPos = lowerSnapPos;
1394
+ else
1395
+ endPos = higherSnapPos;
1396
+
1397
+ deltaPos = endPos - startPos;
1398
+ qreal midPos = startPos + deltaPos * qreal(0.3);
1399
+ pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.3), qreal(1.0), startPos, midPos - startPos, midPos, QEasingCurve::InQuad, orientation);
1400
+ pushSegment(ScrollTypeFlick, sp->snapTime * qreal(0.7), qreal(1.0), midPos, endPos - midPos, endPos, sp->scrollingCurve.type(), orientation);
1401
+ return;
1402
+ }
1403
+
1404
+ // - go to the next snappoint if there is one
1405
+ if (v > 0 && !qIsNaN(higherSnapPos)) {
1406
+ // change the time in relation to the changed end position
1407
+ if (endPos - startPos)
1408
+ deltaTime *= qAbs((higherSnapPos - startPos) / (endPos - startPos));
1409
+ if (deltaTime > sp->snapTime)
1410
+ deltaTime = sp->snapTime;
1411
+ endPos = higherSnapPos;
1412
+
1413
+ } else if (v < 0 && !qIsNaN(lowerSnapPos)) {
1414
+ // change the time in relation to the changed end position
1415
+ if (endPos - startPos)
1416
+ deltaTime *= qAbs((lowerSnapPos - startPos) / (endPos - startPos));
1417
+ if (deltaTime > sp->snapTime)
1418
+ deltaTime = sp->snapTime;
1419
+ endPos = lowerSnapPos;
1420
+
1421
+ // -- check if we are overshooting
1422
+ } else if (endPos < minPos || endPos > maxPos) {
1423
+ qreal stopPos = endPos < minPos ? minPos : maxPos;
1424
+
1425
+ qScrollerDebug() << "Overshoot: delta:" << (stopPos - startPos);
1426
+
1427
+ qreal stopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos - startPos) / deltaPos));
1428
+
1429
+ if (!canOvershoot) {
1430
+ qScrollerDebug() << "Overshoot stopp:" << stopProgress;
1431
+
1432
+ pushSegment(ScrollTypeFlick, deltaTime, stopProgress, startPos, endPos, stopPos, sp->scrollingCurve.type(), orientation);
1433
+ } else {
1434
+ qreal oDeltaTime = sp->overshootScrollTime;
1435
+ qreal oStopProgress = qMin(stopProgress + oDeltaTime * qreal(0.3) / deltaTime, qreal(1));
1436
+ qreal oDistance = startPos + deltaPos * sp->scrollingCurve.valueForProgress(oStopProgress) - stopPos;
1437
+ qreal oMaxDistance = qSign(oDistance) * (viewSize * sp->overshootScrollDistanceFactor);
1438
+
1439
+ qScrollerDebug() << "1 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
1440
+
1441
+ if (qAbs(oDistance) > qAbs(oMaxDistance)) {
1442
+ oStopProgress = progressForValue(sp->scrollingCurve, qAbs((stopPos + oMaxDistance - startPos) / deltaPos));
1443
+ oDistance = oMaxDistance;
1444
+ qScrollerDebug() << "2 oDistance:" << oDistance << "Max:" << oMaxDistance << "stopP/oStopP" << stopProgress << oStopProgress;
1445
+ }
1446
+
1447
+ pushSegment(ScrollTypeFlick, deltaTime, oStopProgress, startPos, deltaPos, stopPos + oDistance, sp->scrollingCurve.type(), orientation);
1448
+ pushSegment(ScrollTypeOvershoot, oDeltaTime * qreal(0.7), qreal(1.0), stopPos + oDistance, -oDistance, stopPos, sp->scrollingCurve.type(), orientation);
1449
+ }
1450
+ return;
1451
+ }
1452
+
1453
+ pushSegment(ScrollTypeFlick, deltaTime, qreal(1.0), startPos, deltaPos, endPos, sp->scrollingCurve.type(), orientation);
1454
+ }
1455
+
1456
+
1457
+ /*! \internal
1458
+ Prepares scrolling by sending a QScrollPrepareEvent to the receiver widget.
1459
+ Returns true if the scrolling was accepted and a target was returned.
1460
+ */
1461
+ bool QtScrollerPrivate::prepareScrolling(const QPointF &position)
1462
+ {
1463
+ QtScrollPrepareEvent spe(position);
1464
+ spe.ignore();
1465
+ sendEvent(target, &spe);
1466
+
1467
+ qScrollerDebug() << "QScrollPrepareEvent returned from" << target << "with" << spe.isAccepted() << "mcp:" << spe.contentPosRange() << "cp:" << spe.contentPos();
1468
+ if (spe.isAccepted()) {
1469
+ QPointF oldContentPos = contentPosition + overshootPosition;
1470
+ QPointF contentDelta = spe.contentPos() - oldContentPos;
1471
+
1472
+ viewportSize = spe.viewportSize();
1473
+ contentPosRange = spe.contentPosRange();
1474
+ if (contentPosRange.width() < 0)
1475
+ contentPosRange.setWidth(0);
1476
+ if (contentPosRange.height() < 0)
1477
+ contentPosRange.setHeight(0);
1478
+ contentPosition = clampToRect(spe.contentPos(), contentPosRange);
1479
+ overshootPosition = spe.contentPos() - contentPosition;
1480
+
1481
+ // - check if the content position was moved
1482
+ if (contentDelta != QPointF(0, 0)) {
1483
+ // need to correct all segments
1484
+ for (int i = 0; i < xSegments.count(); i++)
1485
+ xSegments[i].startPos -= contentDelta.x();
1486
+
1487
+ for (int i = 0; i < ySegments.count(); i++)
1488
+ ySegments[i].startPos -= contentDelta.y();
1489
+ }
1490
+
1491
+ if (QWidget *w = qobject_cast<QWidget *>(target))
1492
+ setDpiFromWidget(w);
1493
+ if (QGraphicsObject *go = qobject_cast<QGraphicsObject *>(target)) {
1494
+ //TODO: the first view isn't really correct - maybe use an additional field in the prepare event?
1495
+ if (go->scene() && !go->scene()->views().isEmpty())
1496
+ setDpiFromWidget(go->scene()->views().first());
1497
+ }
1498
+
1499
+ if (state == QtScroller::Scrolling) {
1500
+ recalcScrollingSegments();
1501
+ }
1502
+ return true;
1503
+ }
1504
+
1505
+ return false;
1506
+ }
1507
+
1508
+ void QtScrollerPrivate::handleDrag(const QPointF &position, qint64 timestamp)
1509
+ {
1510
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1511
+
1512
+ QPointF deltaPixel = position - lastPosition;
1513
+ qint64 deltaTime = timestamp - lastTimestamp;
1514
+
1515
+ if (sp->axisLockThreshold) {
1516
+ int dx = qAbs(deltaPixel.x());
1517
+ int dy = qAbs(deltaPixel.y());
1518
+ if (dx || dy) {
1519
+ bool vertical = (dy > dx);
1520
+ qreal alpha = qreal(vertical ? dx : dy) / qreal(vertical ? dy : dx);
1521
+ //qScrollerDebug() << "QtScroller::handleDrag() -- axis lock:" << alpha << " / " << axisLockThreshold << "- isvertical:" << vertical << "- dx:" << dx << "- dy:" << dy;
1522
+ if (alpha <= sp->axisLockThreshold) {
1523
+ if (vertical)
1524
+ deltaPixel.setX(0);
1525
+ else
1526
+ deltaPixel.setY(0);
1527
+ }
1528
+ }
1529
+ }
1530
+
1531
+ // calculate velocity (if the user would release the mouse NOW)
1532
+ updateVelocity(deltaPixel, deltaTime);
1533
+
1534
+ // restrict velocity, if content is not scrollable
1535
+ QRectF max = contentPosRange;
1536
+ bool canScrollX = (max.width() > 0) || (sp->hOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn);
1537
+ bool canScrollY = (max.height() > 0) || (sp->vOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn);
1538
+
1539
+ if (!canScrollX) {
1540
+ deltaPixel.setX(0);
1541
+ releaseVelocity.setX(0);
1542
+ }
1543
+ if (!canScrollY) {
1544
+ deltaPixel.setY(0);
1545
+ releaseVelocity.setY(0);
1546
+ }
1547
+
1548
+ // if (firstDrag) {
1549
+ // // Do not delay the first drag
1550
+ // setContentPositionHelper(q->contentPosition() - overshootDistance - deltaPixel);
1551
+ // dragDistance = QPointF(0, 0);
1552
+ // } else {
1553
+ dragDistance += deltaPixel;
1554
+ // }
1555
+ //qScrollerDebug() << "######################" << deltaPixel << position.y() << lastPosition.y();
1556
+ if (canScrollX)
1557
+ lastPosition.setX(position.x());
1558
+ if (canScrollY)
1559
+ lastPosition.setY(position.y());
1560
+ lastTimestamp = timestamp;
1561
+ }
1562
+
1563
+ bool QtScrollerPrivate::pressWhileInactive(const QPointF &position, qint64 timestamp)
1564
+ {
1565
+ if (prepareScrolling(position)) {
1566
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1567
+
1568
+ if (!contentPosRange.isNull() ||
1569
+ (sp->hOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn) ||
1570
+ (sp->vOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn)) {
1571
+
1572
+ lastPosition = pressPosition = position;
1573
+ lastTimestamp = pressTimestamp = timestamp;
1574
+ setState(QtScroller::Pressed);
1575
+ }
1576
+ }
1577
+ return false;
1578
+ }
1579
+
1580
+ bool QtScrollerPrivate::releaseWhilePressed(const QPointF &, qint64)
1581
+ {
1582
+ if (overshootPosition != QPointF(0.0, 0.0)) {
1583
+ setState(QtScroller::Scrolling);
1584
+ return true;
1585
+ } else {
1586
+ setState(QtScroller::Inactive);
1587
+ return false;
1588
+ }
1589
+ }
1590
+
1591
+ bool QtScrollerPrivate::moveWhilePressed(const QPointF &position, qint64 timestamp)
1592
+ {
1593
+ Q_Q(QtScroller);
1594
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1595
+ QPointF ppm = q->pixelPerMeter();
1596
+
1597
+ QPointF deltaPixel = position - pressPosition;
1598
+
1599
+ bool moveAborted = false;
1600
+ bool moveStarted = (((deltaPixel / ppm).manhattanLength()) > sp->dragStartDistance);
1601
+
1602
+ // check the direction of the mouse drag and abort if it's too much in the wrong direction.
1603
+ if (moveStarted) {
1604
+ QRectF max = contentPosRange;
1605
+ bool canScrollX = (max.width() > 0);
1606
+ bool canScrollY = (max.height() > 0);
1607
+
1608
+ if (sp->hOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn)
1609
+ canScrollX = true;
1610
+ if (sp->vOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn)
1611
+ canScrollY = true;
1612
+
1613
+ if (qAbs(deltaPixel.x() / ppm.x()) < qAbs(deltaPixel.y() / ppm.y())) {
1614
+ if (!canScrollY)
1615
+ moveAborted = true;
1616
+ } else {
1617
+ if (!canScrollX)
1618
+ moveAborted = true;
1619
+ }
1620
+ }
1621
+
1622
+ if (moveAborted) {
1623
+ setState(QtScroller::Inactive);
1624
+ moveStarted = false;
1625
+
1626
+ } else if (moveStarted) {
1627
+ setState(QtScroller::Dragging);
1628
+
1629
+ // subtract the dragStartDistance
1630
+ deltaPixel = deltaPixel - deltaPixel * (sp->dragStartDistance / deltaPixel.manhattanLength());
1631
+
1632
+ if (deltaPixel != QPointF(0, 0)) {
1633
+ // handleDrag updates lastPosition, lastTimestamp and velocity
1634
+ handleDrag(pressPosition + deltaPixel, timestamp);
1635
+ }
1636
+ }
1637
+ return moveStarted;
1638
+ }
1639
+
1640
+ bool QtScrollerPrivate::moveWhileDragging(const QPointF &position, qint64 timestamp)
1641
+ {
1642
+ // handleDrag updates lastPosition, lastTimestamp and velocity
1643
+ handleDrag(position, timestamp);
1644
+ return true;
1645
+ }
1646
+
1647
+ void QtScrollerPrivate::timerEventWhileDragging()
1648
+ {
1649
+ if (dragDistance != QPointF(0, 0)) {
1650
+ qScrollerDebug() << "QtScroller::timerEventWhileDragging() -- dragDistance:" << dragDistance;
1651
+
1652
+ setContentPositionHelperDragging(-dragDistance);
1653
+ dragDistance = QPointF(0, 0);
1654
+ }
1655
+ }
1656
+
1657
+ bool QtScrollerPrivate::releaseWhileDragging(const QPointF &position, qint64 timestamp)
1658
+ {
1659
+ Q_Q(QtScroller);
1660
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1661
+
1662
+ // handleDrag updates lastPosition, lastTimestamp and velocity
1663
+ handleDrag(position, timestamp);
1664
+
1665
+ // check if we moved at all - this can happen if you stop a running
1666
+ // scroller with a press and release shortly afterwards
1667
+ QPointF deltaPixel = position - pressPosition;
1668
+ if (((deltaPixel / q->pixelPerMeter()).manhattanLength()) > sp->dragStartDistance) {
1669
+
1670
+ // handle accelerating flicks
1671
+ if ((oldVelocity != QPointF(0, 0)) && sp->acceleratingFlickMaximumTime &&
1672
+ ((timestamp - pressTimestamp) < qint64(sp->acceleratingFlickMaximumTime * 1000))) {
1673
+
1674
+ // - determine if the direction was changed
1675
+ int signX = 0, signY = 0;
1676
+ if (releaseVelocity.x())
1677
+ signX = (releaseVelocity.x() > 0) == (oldVelocity.x() > 0) ? 1 : -1;
1678
+ if (releaseVelocity.y())
1679
+ signY = (releaseVelocity.y() > 0) == (oldVelocity.y() > 0) ? 1 : -1;
1680
+
1681
+ if (signX > 0)
1682
+ releaseVelocity.setX(qBound(-sp->maximumVelocity,
1683
+ oldVelocity.x() * sp->acceleratingFlickSpeedupFactor,
1684
+ sp->maximumVelocity));
1685
+ if (signY > 0)
1686
+ releaseVelocity.setY(qBound(-sp->maximumVelocity,
1687
+ oldVelocity.y() * sp->acceleratingFlickSpeedupFactor,
1688
+ sp->maximumVelocity));
1689
+ }
1690
+ }
1691
+
1692
+ QPointF ppm = q->pixelPerMeter();
1693
+ createScrollingSegments(releaseVelocity.x(), contentPosition.x() + overshootPosition.x(), ppm.x(), Qt::Horizontal);
1694
+ createScrollingSegments(releaseVelocity.y(), contentPosition.y() + overshootPosition.y(), ppm.y(), Qt::Vertical);
1695
+
1696
+ qScrollerDebug() << "QtScroller::releaseWhileDragging() -- velocity:" << releaseVelocity << "-- minimum velocity:" << sp->minimumVelocity << "overshoot" << overshootPosition;
1697
+
1698
+ if (xSegments.isEmpty() && ySegments.isEmpty())
1699
+ setState(QtScroller::Inactive);
1700
+ else
1701
+ setState(QtScroller::Scrolling);
1702
+
1703
+ return true;
1704
+ }
1705
+
1706
+ void QtScrollerPrivate::timerEventWhileScrolling()
1707
+ {
1708
+ qScrollerDebug() << "QtScroller::timerEventWhileScrolling()";
1709
+
1710
+ setContentPositionHelperScrolling();
1711
+ if (xSegments.isEmpty() && ySegments.isEmpty())
1712
+ setState(QtScroller::Inactive);
1713
+ }
1714
+
1715
+ bool QtScrollerPrivate::pressWhileScrolling(const QPointF &position, qint64 timestamp)
1716
+ {
1717
+ Q_Q(QtScroller);
1718
+
1719
+ if ((q->velocity() <= properties.d->maximumClickThroughVelocity) &&
1720
+ (overshootPosition == QPointF(0.0, 0.0))) {
1721
+ setState(QtScroller::Inactive);
1722
+ return false;
1723
+ } else {
1724
+ lastPosition = pressPosition = position;
1725
+ lastTimestamp = pressTimestamp = timestamp;
1726
+ setState(QtScroller::Pressed);
1727
+ setState(QtScroller::Dragging);
1728
+ return true;
1729
+ }
1730
+ }
1731
+
1732
+ /*! \internal
1733
+ This function handles all state changes of the scroller.
1734
+ */
1735
+ void QtScrollerPrivate::setState(QtScroller::State newstate)
1736
+ {
1737
+ Q_Q(QtScroller);
1738
+ bool sendLastScroll = false;
1739
+
1740
+ if (state == newstate)
1741
+ return;
1742
+
1743
+ qScrollerDebug() << q << "QtScroller::setState(" << stateName(newstate) << ")";
1744
+
1745
+ switch (newstate) {
1746
+ case QtScroller::Inactive:
1747
+ scrollTimer->stop();
1748
+
1749
+ // send the last scroll event (but only after the current state change was finished)
1750
+ if (!firstScroll)
1751
+ sendLastScroll = true;
1752
+
1753
+ releaseVelocity = QPointF(0, 0);
1754
+ break;
1755
+
1756
+ case QtScroller::Pressed:
1757
+ scrollTimer->stop();
1758
+
1759
+ oldVelocity = releaseVelocity;
1760
+ releaseVelocity = QPointF(0, 0);
1761
+ break;
1762
+
1763
+ case QtScroller::Dragging:
1764
+ dragDistance = QPointF(0, 0);
1765
+ if (state == QtScroller::Pressed)
1766
+ scrollTimer->start();
1767
+ break;
1768
+
1769
+ case QtScroller::Scrolling:
1770
+ scrollTimer->start();
1771
+ break;
1772
+ }
1773
+
1774
+ qSwap(state, newstate);
1775
+
1776
+ if (sendLastScroll) {
1777
+ QtScrollEvent se(contentPosition, overshootPosition, QtScrollEvent::ScrollFinished);
1778
+ sendEvent(target, &se);
1779
+ firstScroll = true;
1780
+ }
1781
+ if (state == QtScroller::Dragging || state == QtScroller::Scrolling)
1782
+ activeScrollers.insert(q);
1783
+ else
1784
+ activeScrollers.remove(q);
1785
+ emit q->stateChanged(state);
1786
+ }
1787
+
1788
+
1789
+ /*! \internal
1790
+ Helps when setting the content position.
1791
+ It will try to move the content by the requested delta but stop in case
1792
+ when we are coming back from an overshoot or a scrollTo.
1793
+ It will also indicate a new overshooting condition by the overshootX and oversthootY flags.
1794
+
1795
+ In this cases it will reset the velocity variables and other flags.
1796
+
1797
+ Also keeps track of the current over-shooting value in overshootPosition.
1798
+
1799
+ \a deltaPos is the amount of pixels the current content position should be moved
1800
+ */
1801
+ void QtScrollerPrivate::setContentPositionHelperDragging(const QPointF &deltaPos)
1802
+ {
1803
+ Q_Q(QtScroller);
1804
+ QPointF ppm = q->pixelPerMeter();
1805
+ const QtScrollerPropertiesPrivate *sp = properties.d.data();
1806
+ QPointF v = q->velocity();
1807
+
1808
+ if (sp->overshootDragResistanceFactor)
1809
+ overshootPosition /= sp->overshootDragResistanceFactor;
1810
+
1811
+ QPointF oldPos = contentPosition + overshootPosition;
1812
+ QPointF newPos = oldPos + deltaPos;
1813
+
1814
+ qScrollerDebug() << "QtScroller::setContentPositionHelperDragging(" << deltaPos << " [pix])";
1815
+ qScrollerDebug() << " --> overshoot:" << overshootPosition << "- old pos:" << oldPos << "- new pos:" << newPos;
1816
+
1817
+ QPointF oldClampedPos = clampToRect(oldPos, contentPosRange);
1818
+ QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1819
+
1820
+ // --- handle overshooting and stop if the coordinate is going back inside the normal area
1821
+ bool alwaysOvershootX = (sp->hOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn);
1822
+ bool alwaysOvershootY = (sp->vOvershootPolicy == QtScrollerProperties::OvershootAlwaysOn);
1823
+ bool noOvershootX = (sp->hOvershootPolicy == QtScrollerProperties::OvershootAlwaysOff) ||
1824
+ ((state == QtScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1825
+ !sp->overshootDragDistanceFactor;
1826
+ bool noOvershootY = (sp->vOvershootPolicy == QtScrollerProperties::OvershootAlwaysOff) ||
1827
+ ((state == QtScroller::Dragging) && !sp->overshootDragResistanceFactor) ||
1828
+ !sp->overshootDragDistanceFactor;
1829
+ bool canOvershootX = !noOvershootX && (alwaysOvershootX || contentPosRange.width());
1830
+ bool canOvershootY = !noOvershootY && (alwaysOvershootY || contentPosRange.height());
1831
+
1832
+ qreal oldOvershootX = (canOvershootX) ? oldPos.x() - oldClampedPos.x() : 0;
1833
+ qreal oldOvershootY = (canOvershootY) ? oldPos.y() - oldClampedPos.y() : 0;
1834
+
1835
+ qreal newOvershootX = (canOvershootX) ? newPos.x() - newClampedPos.x() : 0;
1836
+ qreal newOvershootY = (canOvershootY) ? newPos.y() - newClampedPos.y() : 0;
1837
+
1838
+ qreal maxOvershootX = viewportSize.width() * sp->overshootDragDistanceFactor;
1839
+ qreal maxOvershootY = viewportSize.height() * sp->overshootDragDistanceFactor;
1840
+
1841
+ qScrollerDebug() << " --> noOs:" << noOvershootX << "drf:" << sp->overshootDragResistanceFactor << "mdf:" << sp->overshootScrollDistanceFactor << "ossP:"<<sp->hOvershootPolicy;
1842
+ qScrollerDebug() << " --> canOS:" << canOvershootX << "newOS:" << newOvershootX << "maxOS:" << maxOvershootX;
1843
+
1844
+ if (sp->overshootDragResistanceFactor) {
1845
+ oldOvershootX *= sp->overshootDragResistanceFactor;
1846
+ oldOvershootY *= sp->overshootDragResistanceFactor;
1847
+ newOvershootX *= sp->overshootDragResistanceFactor;
1848
+ newOvershootY *= sp->overshootDragResistanceFactor;
1849
+ }
1850
+
1851
+ // -- stop at the maximum overshoot distance
1852
+
1853
+ newOvershootX = qBound(-maxOvershootX, newOvershootX, maxOvershootX);
1854
+ newOvershootY = qBound(-maxOvershootY, newOvershootY, maxOvershootY);
1855
+
1856
+ overshootPosition.setX(newOvershootX);
1857
+ overshootPosition.setY(newOvershootY);
1858
+ contentPosition = newClampedPos;
1859
+
1860
+ QtScrollEvent se(contentPosition, overshootPosition, firstScroll ? QtScrollEvent::ScrollStarted : QtScrollEvent::ScrollUpdated);
1861
+ sendEvent(target, &se);
1862
+ firstScroll = false;
1863
+
1864
+ qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition <<
1865
+ "- overshoot x/y?:" << overshootPosition;
1866
+ }
1867
+
1868
+
1869
+ qreal QtScrollerPrivate::nextSegmentPosition(QQueue<ScrollSegment> &segments, qint64 now, qreal oldPos)
1870
+ {
1871
+ qreal pos = oldPos;
1872
+
1873
+ // check the X segments for new positions
1874
+ while (!segments.isEmpty()) {
1875
+ const ScrollSegment s = segments.head();
1876
+
1877
+ if ((s.startTime + s.deltaTime * s.stopProgress) <= now) {
1878
+ segments.dequeue();
1879
+ pos = s.stopPos;
1880
+ } else if (s.startTime <= now) {
1881
+ qreal progress = qreal(now - s.startTime) / qreal(s.deltaTime);
1882
+ pos = s.startPos + s.deltaPos * s.curve.valueForProgress(progress);
1883
+ if (s.deltaPos > 0 ? pos > s.stopPos : pos < s.stopPos) {
1884
+ segments.dequeue();
1885
+ pos = s.stopPos;
1886
+ } else {
1887
+ break;
1888
+ }
1889
+ } else {
1890
+ break;
1891
+ }
1892
+ }
1893
+ return pos;
1894
+ }
1895
+
1896
+ void QtScrollerPrivate::setContentPositionHelperScrolling()
1897
+ {
1898
+ qint64 now = monotonicTimer.elapsed();
1899
+ QPointF newPos = contentPosition + overshootPosition;
1900
+
1901
+ newPos.setX(nextSegmentPosition(xSegments, now, newPos.x()));
1902
+ newPos.setY(nextSegmentPosition(ySegments, now, newPos.y()));
1903
+
1904
+ // -- set the position and handle overshoot
1905
+ qScrollerDebug() << "QtScroller::setContentPositionHelperScrolling()";
1906
+ qScrollerDebug() << " --> overshoot:" << overshootPosition << "- new pos:" << newPos;
1907
+
1908
+ QPointF newClampedPos = clampToRect(newPos, contentPosRange);
1909
+
1910
+ overshootPosition = newPos - newClampedPos;
1911
+ contentPosition = newClampedPos;
1912
+
1913
+ QtScrollEvent se(contentPosition, overshootPosition, firstScroll ? QtScrollEvent::ScrollStarted : QtScrollEvent::ScrollUpdated);
1914
+ sendEvent(target, &se);
1915
+ firstScroll = false;
1916
+
1917
+ qScrollerDebug() << " --> new position:" << newClampedPos << "- new overshoot:" << overshootPosition;
1918
+ }
1919
+
1920
+ /*! \internal
1921
+ Returns the next snap point in direction.
1922
+ If \a direction >0 it will return the next snap point that is larger than the current position.
1923
+ If \a direction <0 it will return the next snap point that is smaller than the current position.
1924
+ If \a direction ==0 it will return the nearest snap point (or the current position if we are already
1925
+ on a snap point.
1926
+ Returns the nearest snap position or NaN if no such point could be found.
1927
+ */
1928
+ qreal QtScrollerPrivate::nextSnapPos(qreal p, int dir, Qt::Orientation orientation)
1929
+ {
1930
+ qreal bestSnapPos = Q_QNAN;
1931
+ qreal bestSnapPosDist = Q_INFINITY;
1932
+
1933
+ qreal minPos;
1934
+ qreal maxPos;
1935
+
1936
+ if (orientation == Qt::Horizontal) {
1937
+ minPos = contentPosRange.left();
1938
+ maxPos = contentPosRange.right();
1939
+ } else {
1940
+ minPos = contentPosRange.top();
1941
+ maxPos = contentPosRange.bottom();
1942
+ }
1943
+
1944
+ if (orientation == Qt::Horizontal) {
1945
+ // the snap points in the list
1946
+ foreach (qreal snapPos, snapPositionsX) {
1947
+ qreal snapPosDist = snapPos - p;
1948
+ if ((dir > 0 && snapPosDist < 0) ||
1949
+ (dir < 0 && snapPosDist > 0))
1950
+ continue; // wrong direction
1951
+ if (snapPos < minPos || snapPos > maxPos)
1952
+ continue; // invalid
1953
+
1954
+ if (qIsNaN(bestSnapPos) ||
1955
+ qAbs(snapPosDist) < bestSnapPosDist) {
1956
+ bestSnapPos = snapPos;
1957
+ bestSnapPosDist = qAbs(snapPosDist);
1958
+ }
1959
+ }
1960
+
1961
+ // the snap point interval
1962
+ if (snapIntervalX > 0.0) {
1963
+ qreal first = minPos + snapFirstX;
1964
+ qreal snapPos;
1965
+ if (dir > 0)
1966
+ snapPos = qCeil((p - first) / snapIntervalX) * snapIntervalX + first;
1967
+ else if (dir < 0)
1968
+ snapPos = qFloor((p - first) / snapIntervalX) * snapIntervalX + first;
1969
+ else if (p <= first)
1970
+ snapPos = first;
1971
+ else
1972
+ {
1973
+ qreal last = qFloor((maxPos - first) / snapIntervalX) * snapIntervalX + first;
1974
+ if (p >= last)
1975
+ snapPos = last;
1976
+ else
1977
+ snapPos = qRound((p - first) / snapIntervalX) * snapIntervalX + first;
1978
+ }
1979
+
1980
+ if (snapPos >= first && snapPos <= maxPos ) {
1981
+ qreal snapPosDist = snapPos - p;
1982
+
1983
+ if (qIsNaN(bestSnapPos) ||
1984
+ qAbs(snapPosDist) < bestSnapPosDist) {
1985
+ bestSnapPos = snapPos;
1986
+ bestSnapPosDist = qAbs(snapPosDist);
1987
+ }
1988
+ }
1989
+ }
1990
+
1991
+ } else { // (orientation == Qt::Vertical)
1992
+ // the snap points in the list
1993
+ foreach (qreal snapPos, snapPositionsY) {
1994
+ qreal snapPosDist = snapPos - p;
1995
+ if ((dir > 0 && snapPosDist < 0) ||
1996
+ (dir < 0 && snapPosDist > 0))
1997
+ continue; // wrong direction
1998
+ if (snapPos < minPos || snapPos > maxPos)
1999
+ continue; // invalid
2000
+
2001
+ if (qIsNaN(bestSnapPos) ||
2002
+ qAbs(snapPosDist) < bestSnapPosDist) {
2003
+ bestSnapPos = snapPos;
2004
+ bestSnapPosDist = qAbs(snapPosDist);
2005
+ }
2006
+ }
2007
+
2008
+ // the snap point interval
2009
+ if (snapIntervalY > 0.0) {
2010
+ qreal first = minPos + snapFirstY;
2011
+ qreal snapPos;
2012
+ if (dir > 0)
2013
+ snapPos = qCeil((p - first) / snapIntervalY) * snapIntervalY + first;
2014
+ else if (dir < 0)
2015
+ snapPos = qFloor((p - first) / snapIntervalY) * snapIntervalY + first;
2016
+ else if (p <= first)
2017
+ snapPos = first;
2018
+ else
2019
+ {
2020
+ qreal last = qFloor((maxPos - first) / snapIntervalY) * snapIntervalY + first;
2021
+ if (p >= last)
2022
+ snapPos = last;
2023
+ else
2024
+ snapPos = qRound((p - first) / snapIntervalY) * snapIntervalY + first;
2025
+ }
2026
+
2027
+ if (snapPos >= first && snapPos <= maxPos ) {
2028
+ qreal snapPosDist = snapPos - p;
2029
+
2030
+ if (qIsNaN(bestSnapPos) ||
2031
+ qAbs(snapPosDist) < bestSnapPosDist) {
2032
+ bestSnapPos = snapPos;
2033
+ bestSnapPosDist = qAbs(snapPosDist);
2034
+ }
2035
+ }
2036
+ }
2037
+ }
2038
+
2039
+ return bestSnapPos;
2040
+ }
2041
+
2042
+ /*!
2043
+ \enum QtScroller::State
2044
+
2045
+ This enum contains the different QtScroller states.
2046
+
2047
+ \value Inactive The scroller is not scrolling and nothing is pressed.
2048
+ \value Pressed A touch event was received or the mouse button was pressed but the scroll area is currently not dragged.
2049
+ \value Dragging The scroll area is currently following the touch point or mouse.
2050
+ \value Scrolling The scroll area is moving on it's own.
2051
+ */
2052
+
2053
+ /*!
2054
+ \enum QtScroller::ScrollerGestureType
2055
+
2056
+ This enum contains the different gesture types that are supported by the QtScroller gesture recognizer.
2057
+
2058
+ \value TouchGesture The gesture recognizer will only trigger on touch
2059
+ events. Specifically it will react on single touch points when using a
2060
+ touch screen and dual touch points when using a touchpad.
2061
+ \value LeftMouseButtonGesture The gesture recognizer will only trigger on left mouse button events.
2062
+ \value MiddleMouseButtonGesture The gesture recognizer will only trigger on middle mouse button events.
2063
+ \value RightMouseButtonGesture The gesture recognizer will only trigger on right mouse button events.
2064
+ */
2065
+
2066
+ /*!
2067
+ \enum QtScroller::Input
2068
+
2069
+ This enum contains an input device agnostic view of input events that are relevant for QtScroller.
2070
+
2071
+ \value InputPress The user pressed the input device (e.g. QEvent::MouseButtonPress,
2072
+ QEvent::GraphicsSceneMousePress, QEvent::TouchBegin)
2073
+
2074
+ \value InputMove The user moved the input device (e.g. QEvent::MouseMove,
2075
+ QEvent::GraphicsSceneMouseMove, QEvent::TouchUpdate)
2076
+
2077
+ \value InputRelease The user released the input device (e.g. QEvent::MouseButtonRelease,
2078
+ QEvent::GraphicsSceneMouseRelease, QEvent::TouchEnd)
2079
+
2080
+ */