/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** You may use this file under the terms of the BSD license as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "environment.h" #include "qcontext2dcanvas.h" #include "context2d.h" #include #include struct FakeDomEvent { enum KeyCodes { DOM_VK_UNDEFINED = 0x0, DOM_VK_RIGHT_ALT = 0x12, DOM_VK_LEFT_ALT = 0x12, DOM_VK_LEFT_CONTROL = 0x11, DOM_VK_RIGHT_CONTROL = 0x11, DOM_VK_LEFT_SHIFT = 0x10, DOM_VK_RIGHT_SHIFT = 0x10, DOM_VK_META = 0x9D, DOM_VK_BACK_SPACE = 0x08, DOM_VK_CAPS_LOCK = 0x14, DOM_VK_DELETE = 0x7F, DOM_VK_END = 0x23, DOM_VK_ENTER = 0x0D, DOM_VK_ESCAPE = 0x1B, DOM_VK_HOME = 0x24, DOM_VK_NUM_LOCK = 0x90, DOM_VK_PAUSE = 0x13, DOM_VK_PRINTSCREEN = 0x9A, DOM_VK_SCROLL_LOCK = 0x91, DOM_VK_SPACE = 0x20, DOM_VK_TAB = 0x09, DOM_VK_LEFT = 0x25, DOM_VK_RIGHT = 0x27, DOM_VK_UP = 0x26, DOM_VK_DOWN = 0x28, DOM_VK_PAGE_DOWN = 0x22, DOM_VK_PAGE_UP = 0x21, DOM_VK_F1 = 0x70, DOM_VK_F2 = 0x71, DOM_VK_F3 = 0x72, DOM_VK_F4 = 0x73, DOM_VK_F5 = 0x74, DOM_VK_F6 = 0x75, DOM_VK_F7 = 0x76, DOM_VK_F8 = 0x77, DOM_VK_F9 = 0x78, DOM_VK_F10 = 0x79, DOM_VK_F11 = 0x7A, DOM_VK_F12 = 0x7B, DOM_VK_F13 = 0xF000, DOM_VK_F14 = 0xF001, DOM_VK_F15 = 0xF002, DOM_VK_F16 = 0xF003, DOM_VK_F17 = 0xF004, DOM_VK_F18 = 0xF005, DOM_VK_F19 = 0xF006, DOM_VK_F20 = 0xF007, DOM_VK_F21 = 0xF008, DOM_VK_F22 = 0xF009, DOM_VK_F23 = 0xF00A, DOM_VK_F24 = 0xF00B }; static int qtToDomKey(int keyCode); }; int FakeDomEvent::qtToDomKey(int keyCode) { switch (keyCode) { case Qt::Key_Backspace: return DOM_VK_BACK_SPACE; case Qt::Key_Enter: return DOM_VK_ENTER; case Qt::Key_Return: return DOM_VK_ENTER; case Qt::Key_NumLock: return DOM_VK_NUM_LOCK; case Qt::Key_Alt: return DOM_VK_RIGHT_ALT; case Qt::Key_Control: return DOM_VK_LEFT_CONTROL; case Qt::Key_Shift: return DOM_VK_LEFT_SHIFT; case Qt::Key_Meta: return DOM_VK_META; case Qt::Key_CapsLock: return DOM_VK_CAPS_LOCK; case Qt::Key_Delete: return DOM_VK_DELETE; case Qt::Key_End: return DOM_VK_END; case Qt::Key_Escape: return DOM_VK_ESCAPE; case Qt::Key_Home: return DOM_VK_HOME; case Qt::Key_Pause: return DOM_VK_PAUSE; case Qt::Key_Print: return DOM_VK_PRINTSCREEN; case Qt::Key_ScrollLock: return DOM_VK_SCROLL_LOCK; case Qt::Key_Left: return DOM_VK_LEFT; case Qt::Key_Right: return DOM_VK_RIGHT; case Qt::Key_Up: return DOM_VK_UP; case Qt::Key_Down: return DOM_VK_DOWN; case Qt::Key_PageDown: return DOM_VK_PAGE_DOWN; case Qt::Key_PageUp: return DOM_VK_PAGE_UP; case Qt::Key_F1: return DOM_VK_F1; case Qt::Key_F2: return DOM_VK_F2; case Qt::Key_F3: return DOM_VK_F3; case Qt::Key_F4: return DOM_VK_F4; case Qt::Key_F5: return DOM_VK_F5; case Qt::Key_F6: return DOM_VK_F6; case Qt::Key_F7: return DOM_VK_F7; case Qt::Key_F8: return DOM_VK_F8; case Qt::Key_F9: return DOM_VK_F9; case Qt::Key_F10: return DOM_VK_F10; case Qt::Key_F11: return DOM_VK_F11; case Qt::Key_F12: return DOM_VK_F12; case Qt::Key_F13: return DOM_VK_F13; case Qt::Key_F14: return DOM_VK_F14; case Qt::Key_F15: return DOM_VK_F15; case Qt::Key_F16: return DOM_VK_F16; case Qt::Key_F17: return DOM_VK_F17; case Qt::Key_F18: return DOM_VK_F18; case Qt::Key_F19: return DOM_VK_F19; case Qt::Key_F20: return DOM_VK_F20; case Qt::Key_F21: return DOM_VK_F21; case Qt::Key_F22: return DOM_VK_F22; case Qt::Key_F23: return DOM_VK_F23; case Qt::Key_F24: return DOM_VK_F24; } return keyCode; } //! [0] Environment::Environment(QObject *parent) : QObject(parent) { m_engine = new QScriptEngine(this); m_document = m_engine->newQObject( new Document(this), QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassContents); CanvasGradientPrototype::setup(m_engine); m_originalGlobalObject = m_engine->globalObject(); reset(); } //! [0] Environment::~Environment() { } QScriptEngine *Environment::engine() const { return m_engine; } QScriptValue Environment::document() const { return m_document; } int Environment::setTimeout(const QScriptValue &expression, int delay) { if (expression.isString() || expression.isFunction()) { int timerId = startTimer(delay); m_timeoutHash.insert(timerId, expression); return timerId; } return -1; } void Environment::clearTimeout(int timerId) { killTimer(timerId); m_timeoutHash.remove(timerId); } //! [1] int Environment::setInterval(const QScriptValue &expression, int delay) { if (expression.isString() || expression.isFunction()) { int timerId = startTimer(delay); m_intervalHash.insert(timerId, expression); return timerId; } return -1; } void Environment::clearInterval(int timerId) { killTimer(timerId); m_intervalHash.remove(timerId); } void Environment::timerEvent(QTimerEvent *event) { int id = event->timerId(); QScriptValue expression = m_intervalHash.value(id); if (!expression.isValid()) { expression = m_timeoutHash.value(id); if (expression.isValid()) killTimer(id); } if (expression.isString()) { evaluate(expression.toString()); } else if (expression.isFunction()) { expression.call(); } maybeEmitScriptError(); } //! [1] //! [5] void Environment::addCanvas(QContext2DCanvas *canvas) { m_canvases.append(canvas); } QContext2DCanvas *Environment::canvasByName(const QString &name) const { for (int i = 0; i < m_canvases.size(); ++i) { QContext2DCanvas *canvas = m_canvases.at(i); if (canvas->objectName() == name) return canvas; } return 0; } //! [5] QList Environment::canvases() const { return m_canvases; } void Environment::reset() { if (m_engine->isEvaluating()) m_engine->abortEvaluation(); { QHash::const_iterator it; for (it = m_intervalHash.constBegin(); it != m_intervalHash.constEnd(); ++it) killTimer(it.key()); m_intervalHash.clear(); for (it = m_timeoutHash.constBegin(); it != m_timeoutHash.constEnd(); ++it) killTimer(it.key()); m_timeoutHash.clear(); } for (int i = 0; i < m_canvases.size(); ++i) m_canvases.at(i)->reset(); QScriptValue self = m_engine->newQObject( this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeSuperClassContents); { QScriptValueIterator it(m_originalGlobalObject); while (it.hasNext()) { it.next(); self.setProperty(it.scriptName(), it.value(), it.flags()); } } self.setProperty("self", self); self.setProperty("window", self); QScriptValue navigator = m_engine->newObject(); navigator.setProperty("appCodeName", "context2d"); navigator.setProperty("appMinorVersion", 1); navigator.setProperty("appVersion", 1); navigator.setProperty("browserLanguage", "en_US"); navigator.setProperty("cookieEnabled", false); navigator.setProperty("cpuClass", "i686"); navigator.setProperty("onLine", false); navigator.setProperty("platform", "bogus OS"); navigator.setProperty("systemLanguage", "en_US"); navigator.setProperty("userAgent", "Context2D/1.1"); navigator.setProperty("userLanguage", "en_US"); self.setProperty("navigator", navigator); m_engine->setGlobalObject(self); m_engine->collectGarbage(); } QScriptValue Environment::evaluate(const QString &code, const QString &fileName) { return m_engine->evaluate(code, fileName); } bool Environment::hasIntervalTimers() const { return !m_intervalHash.isEmpty(); } // This is used by the Context2D Qt Script benchmark. void Environment::triggerTimers() { for (int x = 0; x < 2; ++x) { QList timerIds = x ? m_intervalHash.keys() : m_timeoutHash.keys(); for (int i = 0; i < timerIds.size(); ++i) { QTimerEvent fakeEvent(timerIds.at(i)); timerEvent(&fakeEvent); } } } //! [2] QScriptValue Environment::toWrapper(QObject *object) { return m_engine->newQObject(object, QScriptEngine::QtOwnership, QScriptEngine::PreferExistingWrapperObject | QScriptEngine::ExcludeSuperClassContents); } //! [2] //! [3] void Environment::handleEvent(QContext2DCanvas *canvas, QMouseEvent *e) { QString type; switch (e->type()) { case QEvent::MouseButtonPress: type = "mousedown"; break; case QEvent::MouseButtonRelease: type = "mouseup"; break; case QEvent::MouseMove: type = "mousemove"; break; default: break; } if (type.isEmpty()) return; QScriptValue handlerObject; QScriptValue handler = eventHandler(canvas, type, &handlerObject); if (!handler.isFunction()) return; QScriptValue scriptEvent = newFakeDomEvent(type, toWrapper(canvas)); // MouseEvent scriptEvent.setProperty("screenX", e->globalX(), QScriptValue::ReadOnly); scriptEvent.setProperty("screenY", e->globalY(), QScriptValue::ReadOnly); scriptEvent.setProperty("clientX", e->x(), QScriptValue::ReadOnly); scriptEvent.setProperty("clientY", e->y(), QScriptValue::ReadOnly); scriptEvent.setProperty("layerX", e->x(), QScriptValue::ReadOnly); scriptEvent.setProperty("layerY", e->y(), QScriptValue::ReadOnly); scriptEvent.setProperty("pageX", e->x(), QScriptValue::ReadOnly); scriptEvent.setProperty("pageY", e->y(), QScriptValue::ReadOnly); scriptEvent.setProperty("altKey", (e->modifiers() & Qt::AltModifier) != 0, QScriptValue::ReadOnly); scriptEvent.setProperty("ctrlKey", (e->modifiers() & Qt::ControlModifier) != 0, QScriptValue::ReadOnly); scriptEvent.setProperty("metaKey", (e->modifiers() & Qt::MetaModifier) != 0, QScriptValue::ReadOnly); scriptEvent.setProperty("shiftKey", (e->modifiers() & Qt::ShiftModifier) != 0, QScriptValue::ReadOnly); int button = 0; if (e->button() == Qt::RightButton) button = 2; else if (e->button() == Qt::MidButton) button = 1; scriptEvent.setProperty("button", button); scriptEvent.setProperty("relatedTarget", m_engine->nullValue(), QScriptValue::ReadOnly); handler.call(handlerObject, QScriptValueList() << scriptEvent); maybeEmitScriptError(); } //! [3] void Environment::handleEvent(QContext2DCanvas *canvas, QKeyEvent *e) { QString type; switch (e->type()) { case QEvent::KeyPress: type = "keydown"; break; case QEvent::KeyRelease: type = "keyup"; break; default: break; } if (type.isEmpty()) return; QScriptValue handlerObject; QScriptValue handler = eventHandler(canvas, type, &handlerObject); if (!handler.isFunction()) return; QScriptValue scriptEvent = newFakeDomEvent(type, toWrapper(canvas)); // KeyEvent scriptEvent.setProperty("isChar", !e->text().isEmpty()); scriptEvent.setProperty("charCode", e->text()); scriptEvent.setProperty("keyCode", FakeDomEvent::qtToDomKey(e->key())); scriptEvent.setProperty("which", e->key()); handler.call(handlerObject, QScriptValueList() << scriptEvent); maybeEmitScriptError(); } QScriptValue Environment::eventHandler(QContext2DCanvas *canvas, const QString &type, QScriptValue *who) { QString handlerName = "on" + type; QScriptValue obj = toWrapper(canvas); QScriptValue handler = obj.property(handlerName); if (!handler.isValid()) { obj = m_document; handler = obj.property(handlerName); } if (who && handler.isFunction()) *who = obj; return handler; } //! [4] QScriptValue Environment::newFakeDomEvent(const QString &type, const QScriptValue &target) { QScriptValue e = m_engine->newObject(); // Event e.setProperty("type", type, QScriptValue::ReadOnly); e.setProperty("bubbles", true, QScriptValue::ReadOnly); e.setProperty("cancelable", false, QScriptValue::ReadOnly); e.setProperty("target", target, QScriptValue::ReadOnly); e.setProperty("currentTarget", target, QScriptValue::ReadOnly); e.setProperty("eventPhase", 3); // bubbling e.setProperty("timeStamp", QDateTime::currentDateTime().toTime_t()); // UIEvent e.setProperty("detail", 0, QScriptValue::ReadOnly); e.setProperty("view", m_engine->globalObject(), QScriptValue::ReadOnly); return e; } //! [4] void Environment::maybeEmitScriptError() { if (m_engine->hasUncaughtException()) emit scriptError(m_engine->uncaughtException()); } Document::Document(Environment *env) : QObject(env) { } Document::~Document() { } QScriptValue Document::getElementById(const QString &id) const { Environment *env = qobject_cast(parent()); QContext2DCanvas *canvas = env->canvasByName(id); if (!canvas) return QScriptValue(); return env->toWrapper(canvas); } QScriptValue Document::getElementsByTagName(const QString &name) const { if (name != "canvas") return QScriptValue(); Environment *env = qobject_cast(parent()); QList list = env->canvases(); QScriptValue result = env->engine()->newArray(list.size()); for (int i = 0; i < list.size(); ++i) result.setProperty(i, env->toWrapper(list.at(i))); return result; } void Document::addEventListener(const QString &type, const QScriptValue &listener, bool useCapture) { Q_UNUSED(useCapture); if (listener.isFunction()) { Environment *env = qobject_cast(parent()); QScriptValue self = env->toWrapper(this); self.setProperty("on" + type, listener); } } QColor colorFromString(const QString &name); CanvasGradientPrototype::CanvasGradientPrototype(QObject *parent) : QObject(parent) { } void CanvasGradientPrototype::addColorStop(qreal offset, const QString &color) { CanvasGradient *self = qscriptvalue_cast(thisObject()); if (!self || (self->value.type() == QGradient::NoGradient)) return; self->value.setColorAt(offset, colorFromString(color)); } void CanvasGradientPrototype::setup(QScriptEngine *engine) { CanvasGradientPrototype *proto = new CanvasGradientPrototype(); engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject(proto, QScriptEngine::ScriptOwnership, QScriptEngine::ExcludeSuperClassContents)); }