1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qqmltimer_p.h" |
5 | |
6 | #include <QtCore/qcoreapplication.h> |
7 | #include "private/qpauseanimationjob_p.h" |
8 | #include <qdebug.h> |
9 | |
10 | #include <private/qobject_p.h> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | namespace { |
15 | const QEvent::Type QEvent_MaybeTick = QEvent::Type(QEvent::User + 1); |
16 | const QEvent::Type QEvent_Triggered = QEvent::Type(QEvent::User + 2); |
17 | } |
18 | |
19 | class QQmlTimerPrivate : public QObjectPrivate, public QAnimationJobChangeListener |
20 | { |
21 | Q_DECLARE_PUBLIC(QQmlTimer) |
22 | public: |
23 | QQmlTimerPrivate() |
24 | : running(false), repeating(false), triggeredOnStart(false) |
25 | , classBegun(false), componentComplete(false), firstTick(true), awaitingTick(false) {} |
26 | |
27 | void animationFinished(QAbstractAnimationJob *) override; |
28 | void animationCurrentLoopChanged(QAbstractAnimationJob *) override { maybeTick(); } |
29 | |
30 | void maybeTick() { |
31 | Q_Q(QQmlTimer); |
32 | if (!awaitingTick) { |
33 | awaitingTick = true; |
34 | QCoreApplication::postEvent(receiver: q, event: new QEvent(QEvent_MaybeTick)); |
35 | } |
36 | } |
37 | |
38 | int interval = 1000; |
39 | QPauseAnimationJob pause; |
40 | bool running : 1; |
41 | bool repeating : 1; |
42 | bool triggeredOnStart : 1; |
43 | bool classBegun : 1; |
44 | bool componentComplete : 1; |
45 | bool firstTick : 1; |
46 | bool awaitingTick : 1; |
47 | }; |
48 | |
49 | /*! |
50 | \qmltype Timer |
51 | \instantiates QQmlTimer |
52 | \inqmlmodule QtQml |
53 | \ingroup qtquick-interceptors |
54 | \brief Triggers a handler at a specified interval. |
55 | |
56 | A Timer can be used to trigger an action either once, or repeatedly |
57 | at a given interval. |
58 | |
59 | Here is a Timer that shows the current date and time, and updates |
60 | the text every 500 milliseconds. It uses the JavaScript \c Date |
61 | object to access the current time. |
62 | |
63 | \qml |
64 | import QtQuick 2.0 |
65 | |
66 | Item { |
67 | Timer { |
68 | interval: 500; running: true; repeat: true |
69 | onTriggered: time.text = Date().toString() |
70 | } |
71 | |
72 | Text { id: time } |
73 | } |
74 | \endqml |
75 | |
76 | The Timer type is synchronized with the animation timer. Since the animation |
77 | timer is usually set to 60fps, the resolution of Timer will be |
78 | at best 16ms. |
79 | |
80 | If the Timer is running and one of its properties is changed, the |
81 | elapsed time will be reset. For example, if a Timer with interval of |
82 | 1000ms has its \e repeat property changed 500ms after starting, the |
83 | elapsed time will be reset to 0, and the Timer will be triggered |
84 | 1000ms later. |
85 | |
86 | \sa {Qt Quick Demo - Clocks} |
87 | */ |
88 | |
89 | QQmlTimer::QQmlTimer(QObject *parent) |
90 | : QObject(*(new QQmlTimerPrivate), parent) |
91 | { |
92 | Q_D(QQmlTimer); |
93 | d->pause.addAnimationChangeListener(listener: d, QAbstractAnimationJob::Completion | QAbstractAnimationJob::CurrentLoop); |
94 | d->pause.setLoopCount(1); |
95 | d->pause.setDuration(d->interval); |
96 | } |
97 | |
98 | /*! |
99 | \qmlproperty int QtQml::Timer::interval |
100 | |
101 | Sets the \a interval between triggers, in milliseconds. |
102 | |
103 | The default interval is 1000 milliseconds. |
104 | */ |
105 | void QQmlTimer::setInterval(int interval) |
106 | { |
107 | Q_D(QQmlTimer); |
108 | if (interval != d->interval) { |
109 | d->interval = interval; |
110 | update(); |
111 | emit intervalChanged(); |
112 | } |
113 | } |
114 | |
115 | int QQmlTimer::interval() const |
116 | { |
117 | Q_D(const QQmlTimer); |
118 | return d->interval; |
119 | } |
120 | |
121 | /*! |
122 | \qmlproperty bool QtQml::Timer::running |
123 | |
124 | If set to true, starts the timer; otherwise stops the timer. |
125 | For a non-repeating timer, \a running is set to false after the |
126 | timer has been triggered. |
127 | |
128 | \a running defaults to false. |
129 | |
130 | \sa repeat |
131 | */ |
132 | bool QQmlTimer::isRunning() const |
133 | { |
134 | Q_D(const QQmlTimer); |
135 | return d->running; |
136 | } |
137 | |
138 | void QQmlTimer::setRunning(bool running) |
139 | { |
140 | Q_D(QQmlTimer); |
141 | if (d->running != running) { |
142 | d->running = running; |
143 | d->firstTick = true; |
144 | emit runningChanged(); |
145 | update(); |
146 | } |
147 | } |
148 | |
149 | /*! |
150 | \qmlproperty bool QtQml::Timer::repeat |
151 | |
152 | If \a repeat is true the timer is triggered repeatedly at the |
153 | specified interval; otherwise, the timer will trigger once at the |
154 | specified interval and then stop (i.e. running will be set to false). |
155 | |
156 | \a repeat defaults to false. |
157 | |
158 | \sa running |
159 | */ |
160 | bool QQmlTimer::isRepeating() const |
161 | { |
162 | Q_D(const QQmlTimer); |
163 | return d->repeating; |
164 | } |
165 | |
166 | void QQmlTimer::setRepeating(bool repeating) |
167 | { |
168 | Q_D(QQmlTimer); |
169 | if (repeating != d->repeating) { |
170 | d->repeating = repeating; |
171 | update(); |
172 | emit repeatChanged(); |
173 | } |
174 | } |
175 | |
176 | /*! |
177 | \qmlproperty bool QtQml::Timer::triggeredOnStart |
178 | |
179 | When a timer is started, the first trigger is usually after the specified |
180 | interval has elapsed. It is sometimes desirable to trigger immediately |
181 | when the timer is started; for example, to establish an initial |
182 | state. |
183 | |
184 | If \a triggeredOnStart is true, the timer is triggered immediately |
185 | when started, and subsequently at the specified interval. Note that if |
186 | \e repeat is set to false, the timer is triggered twice; once on start, |
187 | and again at the interval. |
188 | |
189 | \a triggeredOnStart defaults to false. |
190 | |
191 | \sa running |
192 | */ |
193 | bool QQmlTimer::triggeredOnStart() const |
194 | { |
195 | Q_D(const QQmlTimer); |
196 | return d->triggeredOnStart; |
197 | } |
198 | |
199 | void QQmlTimer::setTriggeredOnStart(bool triggeredOnStart) |
200 | { |
201 | Q_D(QQmlTimer); |
202 | if (d->triggeredOnStart != triggeredOnStart) { |
203 | d->triggeredOnStart = triggeredOnStart; |
204 | update(); |
205 | emit triggeredOnStartChanged(); |
206 | } |
207 | } |
208 | |
209 | /*! |
210 | \qmlmethod QtQml::Timer::start() |
211 | \brief Starts the timer |
212 | |
213 | If the timer is already running, calling this method has no effect. The |
214 | \c running property will be true following a call to \c start(). |
215 | */ |
216 | void QQmlTimer::start() |
217 | { |
218 | setRunning(true); |
219 | } |
220 | |
221 | /*! |
222 | \qmlmethod QtQml::Timer::stop() |
223 | \brief Stops the timer |
224 | |
225 | If the timer is not running, calling this method has no effect. The |
226 | \c running property will be false following a call to \c stop(). |
227 | */ |
228 | void QQmlTimer::stop() |
229 | { |
230 | setRunning(false); |
231 | } |
232 | |
233 | /*! |
234 | \qmlmethod QtQml::Timer::restart() |
235 | \brief Restarts the timer |
236 | |
237 | If the Timer is not running it will be started, otherwise it will be |
238 | stopped, reset to initial state and started. The \c running property |
239 | will be true following a call to \c restart(). |
240 | */ |
241 | void QQmlTimer::restart() |
242 | { |
243 | setRunning(false); |
244 | setRunning(true); |
245 | } |
246 | |
247 | void QQmlTimer::update() |
248 | { |
249 | Q_D(QQmlTimer); |
250 | if (d->classBegun && !d->componentComplete) |
251 | return; |
252 | d->pause.stop(); |
253 | if (d->running) { |
254 | d->pause.setCurrentTime(0); |
255 | d->pause.setLoopCount(d->repeating ? -1 : 1); |
256 | d->pause.setDuration(d->interval); |
257 | d->pause.start(); |
258 | if (d->triggeredOnStart && d->firstTick) |
259 | d->maybeTick(); |
260 | } |
261 | } |
262 | |
263 | void QQmlTimer::classBegin() |
264 | { |
265 | Q_D(QQmlTimer); |
266 | d->classBegun = true; |
267 | } |
268 | |
269 | void QQmlTimer::componentComplete() |
270 | { |
271 | Q_D(QQmlTimer); |
272 | d->componentComplete = true; |
273 | update(); |
274 | } |
275 | |
276 | /*! |
277 | \qmlsignal QtQml::Timer::triggered() |
278 | |
279 | This signal is emitted when the Timer times out. |
280 | */ |
281 | void QQmlTimer::ticked() |
282 | { |
283 | Q_D(QQmlTimer); |
284 | if (d->running && (d->pause.currentTime() > 0 || (d->triggeredOnStart && d->firstTick))) |
285 | emit triggered(); |
286 | d->firstTick = false; |
287 | } |
288 | |
289 | /*! |
290 | \internal |
291 | */ |
292 | bool QQmlTimer::event(QEvent *e) |
293 | { |
294 | Q_D(QQmlTimer); |
295 | if (e->type() == QEvent_MaybeTick) { |
296 | d->awaitingTick = false; |
297 | ticked(); |
298 | return true; |
299 | } else if (e->type() == QEvent_Triggered) { |
300 | if (d->running && d->pause.isStopped()) { |
301 | d->running = false; |
302 | emit triggered(); |
303 | emit runningChanged(); |
304 | } |
305 | return true; |
306 | } |
307 | return QObject::event(event: e); |
308 | } |
309 | |
310 | void QQmlTimerPrivate::animationFinished(QAbstractAnimationJob *) |
311 | { |
312 | Q_Q(QQmlTimer); |
313 | if (repeating || !running) |
314 | return; |
315 | firstTick = false; |
316 | QCoreApplication::postEvent(receiver: q, event: new QEvent(QEvent_Triggered)); |
317 | } |
318 | |
319 | QT_END_NAMESPACE |
320 | |
321 | #include "moc_qqmltimer_p.cpp" |
322 | |