/**************************************************************************** ** ** Copyright (C) 2011 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 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{} element. It can be used to draw graphics via scripting. A good resource for learning more about the HTML \c{} 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{} element is typically used in a web browser. The following HTML snippet defines a canvas of size 400x400 pixels with id \c{mycanvas}: \code Fallback content goes here. \endcode To draw on the canvas, we must first obtain a reference to the DOM element corresponding to the \c{} tag and then call the element's getContext() function. The resulting object implements the Context2D API that we use to draw. \code \endcode When the page is rendered by a browser that supports the \c{} 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. */