| /**************************************************************************** |
| ** |
| ** Copyright (C) 2017 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the examples of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:BSD$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** BSD License Usage |
| ** Alternatively, 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 <QScriptValueIterator> |
| #include <QDateTime> |
| |
| 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<QContext2DCanvas*> Environment::canvases() const |
| { |
| return m_canvases; |
| } |
| |
| void Environment::reset() |
| { |
| if (m_engine->isEvaluating()) |
| m_engine->abortEvaluation(); |
| |
| { |
| QHash<int, QScriptValue>::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<int> 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", static_cast<int>(QDateTime::currentDateTime().toSecsSinceEpoch())); |
| // 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<Environment*>(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<Environment*>(parent()); |
| QList<QContext2DCanvas*> 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<Environment*>(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<CanvasGradient*>(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<CanvasGradient>(), |
| engine->newQObject(proto, QScriptEngine::ScriptOwnership, |
| QScriptEngine::ExcludeSuperClassContents)); |
| } |