1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
|
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the documentation of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL$
** GNU Free Documentation License
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms
** and conditions contained in a signed written agreement between you
** and Nokia.
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
/*!
\example script/context2d
\title Context2D Example
\brief This Qt Script example is an implementation of the Context2D API.
\image context2d-example.png
Context2D is part of the specification for the HTML \c{<canvas>}
element. It can be used to draw graphics via scripting. A good
resource for learning more about the HTML \c{<canvas>} element is
the \l{http://developer.mozilla.org/en/docs/HTML:Canvas}{Mozilla Developer Center}.
\section1 Using The HTML Canvas Element in a Web Browser
First, let's look at how the \c{<canvas>} element is typically
used in a web browser. The following HTML snippet defines a
canvas of size 400x400 pixels with id \c{mycanvas}:
\code
<canvas width="400" height="400" id="mycanvas">Fallback content goes here.</canvas>
\endcode
To draw on the canvas, we must first obtain a reference to the
DOM element corresponding to the \c{<canvas>} tag and then call
the element's getContext() function. The resulting object
implements the Context2D API that we use to draw.
\code
<script>
var canvas = document.getElementById("mycanvas");
var ctx = canvas.getContext("2d");
// Draw a face
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // Outer circle
ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // Mouth
ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // Left eye
ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // Right eye
ctx.stroke();
</script>
\endcode
When the page is rendered by a browser that supports the
\c{<canvas>} tag, this would be the result:
\image context2d-example-smileysmile.png
\section1 Using Qt Script to script a Canvas
The goal of this example is to be able to evaluate scripts
that use the Context2D API, and render the results. Basic
interaction (mouse, keyboard) should also be supported.
In other words, we want to present scripts with an execution
environment that very much resembles that of a web browser. Of
course, our environment is only a small subset of what a browser
provides; i.e. we don't provide a full DOM API, only what is
needed to run "self-contained" Context2D scripts (i.e. scripts
that don't depend on other parts of the DOM document).
Our "Context2D-browser" is set up through the following steps:
\list
\o Create an Environment.
\o Create a Context2D, and a QContext2DCanvas widget to render it.
\o Add the canvas object to the environment; this will enable
scripts to obtain a reference to it.
\o Evaluate scripts in the environment.
\endlist
Once a script has been evaluated, the application handles any
timer events and input events that occur subsequently
(i.e. forwards events to their associated script targets).
\section1 The Context2D Class
The "heart" of this example is the Context2D C++ class that implements
the drawing API. Its interface is defined in terms of properties
and slots. Note that this class isn't tied to Qt Script in any
way.
\snippet examples/script/context2d/context2d.h 0
The properties define various aspects of the Context2D
configuration.
\snippet examples/script/context2d/context2d.h 1
The slots define the operations that can be performed.
\snippet examples/script/context2d/context2d.h 2
The changed() signal is emitted when the contents of the drawing
area has changed, so that clients associated with the Context2D
object (i.e. the canvas widget that renders it) are notified.
\section2 Implementation
Conveniently enough, the concepts, data structures and operations
of the Context2D API map more or less directly to Qt's painting
API. Conceptually, all we have to do is initialize a QPainter
according to the Context2D properties, and use functions like
QPainter::strokePath() to do the painting. Painting is done on a
QImage.
\snippet examples/script/context2d/context2d.cpp 0
The property accessors and most of the slots manipulate the
internal Context2D state in some way. For the \c{lineCap}
property, Context2D uses a string representation; we therefore
have to map it from/to a Qt::PenCapStyle. The \c{lineJoin}
property is handled in the same fashion. All the property setters
also set a \e{dirty flag} for the property; this is used to
decide which aspects of the QPainter that need to be updated
before doing the next painting operation.
\snippet examples/script/context2d/context2d.cpp 3
The implementation of the \c{fillStyle} property is interesting,
since the value can be either a string or a \c{CanvasGradient}.
We handle this by having the property be of type QVariant,
and check the actual type of the value to see how to handle the
write.
\snippet examples/script/context2d/context2d.cpp 1
Context2D does not have a concept of a paint event; painting
operations can happen at any time. We would like to be efficient,
and not have to call QPainter::begin() and QPainter::end() for
every painting operation, since typically many painting operations
will follow in quick succession. The implementations of the
painting operations use a helper function, beginPainting(), that
activates the QPainter if it isn't active already, and updates
the state of the QPainter (brush, pen, etc.) so that it reflects
the current Context2D state.
\snippet examples/script/context2d/context2d.cpp 2
The implementation of each painting operation ends by calling
scheduleChange(), which will post a zero-timer event if one is
not already pending. When the application returns to the event
loop later (presumably after all the drawing operations have
finished), the timer will trigger, QPainter::end() will be
called, and the changed() signal is emitted with the new
image as argument. The net effect is that there will typically
be only a single (QPainter::begin(), QPainter::end()) pair
executed for the full sequence of painting operations.
\section1 The Canvas Widget
\snippet examples/script/context2d/qcontext2dcanvas.h 0
The QContext2DCanvas class provides a widget that renders
the contents of a Context2D object. It also provides a
minimal scripting API, most notably the getContext() function.
\snippet examples/script/context2d/qcontext2dcanvas.cpp 3
The constructor connects to the changed() signal of the
Context2D object, so that the widget can update itself
when it needs to do so. Mouse tracking is enabled so that
mouse move events will be received even when no mouse
buttons are depressed.
\snippet examples/script/context2d/qcontext2dcanvas.cpp 0
The getContext() function asks the environment to wrap the
Context2D object; the resulting proxy object makes the
Context2D API available to scripts.
\snippet examples/script/context2d/qcontext2dcanvas.cpp 1
The paintEvent() function simply paints the contents that
was last received from the Context2D object.
\snippet examples/script/context2d/qcontext2dcanvas.cpp 2
The canvas widget reimplements mouse and key event handlers, and
forwards these events to the scripting environment. The
environment will take care of delivering the event to the proper
script target, if any.
\section1 The Environment
\snippet examples/script/context2d/environment.h 0
The Environment class provides a scripting environment where a
Canvas C++ object can be registered, looked up by ID (name),
and where scripts can be evaluated. The environment has a
\c{document} property, just like the scripting environment of a
web browser, so that scripts can call
\c{document.getElementById()} to obtain a reference to a canvas.
\snippet examples/script/context2d/environment.h 1
The Environment class provides the timer attributes of the DOM
Window Object interface. This enables us to support scripts that
do animation, for example.
\snippet examples/script/context2d/environment.h 2
The scriptError() signal is emitted when evaluation of a script
causes a script exception. For example, if a mouse press handler
or timeout handler causes an exception, the environment's client(s)
will be notified of this and can report the error.
\snippet examples/script/context2d/environment.cpp 0
The constructor initializes the environment. First it creates
the QScriptEngine that will be used to evaluate scripts. It
creates the Document object that provides the getElementById()
function. Note that the QScriptEngine::ExcludeSuperClassContents
flag is specified to avoid the wrapper objects from exposing properties
and methods inherited from QObject. Next, the environment wraps
a pointer to \e{itself}; this is to prepare for setting this object
as the script engine's Global Object. The properties of the standard
Global Object are copied, so that these will also be available in
our custom Global Object. We also create two self-references to the
object; again, this is to provide a minimal level of compabilitity
with the scripting environment that web browsers provide.
\snippet examples/script/context2d/environment.cpp 5
The addCanvas() function adds the given canvas to the list of
registered canvas objects. The canvasByName() function looks up
a canvas by QObject::objectName(). This function is used to
implement the \c{document.getElementById()} script function.
\snippet examples/script/context2d/environment.cpp 1
The setInterval() and clearInterval() implementations use a QHash
to map from timer ID to the QScriptValue that holds the expression
to evaluate when the timer is triggered. A helper function,
maybeEmitScriptError(), is called after invoking the script handler;
it will emit the scriptError() signal if the script engine has an
uncaught exception.
\snippet examples/script/context2d/environment.cpp 2
The toWrapper() functions creates a QScriptValue that wraps the
given QObject. Note that the QScriptEngine::PreferExistingWrapperObject
flag is specified; this guarantees that a single, unique wrapper
object will be returned, even if toWrapper() is called several times
with the same argument. This is important, since it is possible that
a script can set new properties on the resulting wrapper object (e.g.
event handlers like \c{onmousedown}), and we want these to persist.
\snippet examples/script/context2d/environment.cpp 3
The handleEvent() function determines if there exists a handler
for the given event in the environment, and if so, invokes that
handler. Since the script expects a DOM event, the Qt C++ event
must be converted to a DOM event before it is passed to the
script. This mapping is relatively straightforward, but again,
we only implement a subset of the full DOM API; just enough to
get most scripts to work.
\snippet examples/script/context2d/environment.cpp 4
The newFakeDomEvent() function is a helper function that creates
a new script object and initializes it with default values for
the attributes defined in the DOM Event and DOM UIEvent
interfaces.
\snippet examples/script/context2d/environment.h 3
The Document class defines two slots that become available to
scripts: getElementById() and getElementsByTagName().
When the tag name is "canvas", getElementsByTagName() will
return a list of all canvas objects that are registered in
the environment.
\section1 The Application Window
\snippet examples/script/context2d/window.cpp 0
The Window constructor creates an Environment object and
connects to its scriptError() signal. It then creates a
Context2D object, and a QContext2DCanvas widget to hold it.
The canvas widget is given the name \c{tutorial}, and added to the
environment; scripts can access the canvas by e.g.
\c{document.getElementById('tutorial')}.
\snippet examples/script/context2d/window.cpp 1
The window contains a list widget that is populated with
available scripts (read from a \c{scripts/} folder).
\snippet examples/script/context2d/window.cpp 2
When an item is selected, the corresponding script is
evaluated in the environment.
\snippet examples/script/context2d/window.cpp 3
When the "Run in Debugger" button is clicked, the Qt Script debugger will
automatically be invoked when the first statement of the script is
reached. This enables the user to inspect the scripting environment and
control further execution of the script; e.g. he can single-step through
the script and/or set breakpoints. It is also possible to enter script
statements in the debugger's console widget, e.g. to perform custom
Context2D drawing operations, interactively.
\snippet examples/script/context2d/window.cpp 4
If the evaluation of a script causes an uncaught exception, the Qt Script
debugger will automatically be invoked; this enables the user to get an
idea of what went wrong.
*/
|