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