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