1/****************************************************************************
2**
3** Copyright (C) 2017 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the examples of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:BSD$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** BSD License Usage
18** Alternatively, you may use this file under the terms of the BSD license
19** as follows:
20**
21** "Redistribution and use in source and binary forms, with or without
22** modification, are permitted provided that the following conditions are
23** met:
24** * Redistributions of source code must retain the above copyright
25** notice, this list of conditions and the following disclaimer.
26** * Redistributions in binary form must reproduce the above copyright
27** notice, this list of conditions and the following disclaimer in
28** the documentation and/or other materials provided with the
29** distribution.
30** * Neither the name of The Qt Company Ltd nor the names of its
31** contributors may be used to endorse or promote products derived
32** from this software without specific prior written permission.
33**
34**
35** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
46**
47** $QT_END_LICENSE$
48**
49****************************************************************************/
50
51#include "environment.h"
52#include "qcontext2dcanvas.h"
53#include "context2d.h"
54#include <QScriptValueIterator>
55#include <QDateTime>
56
57struct FakeDomEvent
58{
59 enum KeyCodes {
60 DOM_VK_UNDEFINED = 0x0,
61 DOM_VK_RIGHT_ALT = 0x12,
62 DOM_VK_LEFT_ALT = 0x12,
63 DOM_VK_LEFT_CONTROL = 0x11,
64 DOM_VK_RIGHT_CONTROL = 0x11,
65 DOM_VK_LEFT_SHIFT = 0x10,
66 DOM_VK_RIGHT_SHIFT = 0x10,
67 DOM_VK_META = 0x9D,
68 DOM_VK_BACK_SPACE = 0x08,
69 DOM_VK_CAPS_LOCK = 0x14,
70 DOM_VK_DELETE = 0x7F,
71 DOM_VK_END = 0x23,
72 DOM_VK_ENTER = 0x0D,
73 DOM_VK_ESCAPE = 0x1B,
74 DOM_VK_HOME = 0x24,
75 DOM_VK_NUM_LOCK = 0x90,
76 DOM_VK_PAUSE = 0x13,
77 DOM_VK_PRINTSCREEN = 0x9A,
78 DOM_VK_SCROLL_LOCK = 0x91,
79 DOM_VK_SPACE = 0x20,
80 DOM_VK_TAB = 0x09,
81 DOM_VK_LEFT = 0x25,
82 DOM_VK_RIGHT = 0x27,
83 DOM_VK_UP = 0x26,
84 DOM_VK_DOWN = 0x28,
85 DOM_VK_PAGE_DOWN = 0x22,
86 DOM_VK_PAGE_UP = 0x21,
87 DOM_VK_F1 = 0x70,
88 DOM_VK_F2 = 0x71,
89 DOM_VK_F3 = 0x72,
90 DOM_VK_F4 = 0x73,
91 DOM_VK_F5 = 0x74,
92 DOM_VK_F6 = 0x75,
93 DOM_VK_F7 = 0x76,
94 DOM_VK_F8 = 0x77,
95 DOM_VK_F9 = 0x78,
96 DOM_VK_F10 = 0x79,
97 DOM_VK_F11 = 0x7A,
98 DOM_VK_F12 = 0x7B,
99 DOM_VK_F13 = 0xF000,
100 DOM_VK_F14 = 0xF001,
101 DOM_VK_F15 = 0xF002,
102 DOM_VK_F16 = 0xF003,
103 DOM_VK_F17 = 0xF004,
104 DOM_VK_F18 = 0xF005,
105 DOM_VK_F19 = 0xF006,
106 DOM_VK_F20 = 0xF007,
107 DOM_VK_F21 = 0xF008,
108 DOM_VK_F22 = 0xF009,
109 DOM_VK_F23 = 0xF00A,
110 DOM_VK_F24 = 0xF00B
111 };
112
113 static int qtToDomKey(int keyCode);
114};
115
116int FakeDomEvent::qtToDomKey(int keyCode)
117{
118 switch (keyCode) {
119 case Qt::Key_Backspace:
120 return DOM_VK_BACK_SPACE;
121 case Qt::Key_Enter:
122 return DOM_VK_ENTER;
123 case Qt::Key_Return:
124 return DOM_VK_ENTER;
125 case Qt::Key_NumLock:
126 return DOM_VK_NUM_LOCK;
127 case Qt::Key_Alt:
128 return DOM_VK_RIGHT_ALT;
129 case Qt::Key_Control:
130 return DOM_VK_LEFT_CONTROL;
131 case Qt::Key_Shift:
132 return DOM_VK_LEFT_SHIFT;
133 case Qt::Key_Meta:
134 return DOM_VK_META;
135 case Qt::Key_CapsLock:
136 return DOM_VK_CAPS_LOCK;
137 case Qt::Key_Delete:
138 return DOM_VK_DELETE;
139 case Qt::Key_End:
140 return DOM_VK_END;
141 case Qt::Key_Escape:
142 return DOM_VK_ESCAPE;
143 case Qt::Key_Home:
144 return DOM_VK_HOME;
145 case Qt::Key_Pause:
146 return DOM_VK_PAUSE;
147 case Qt::Key_Print:
148 return DOM_VK_PRINTSCREEN;
149 case Qt::Key_ScrollLock:
150 return DOM_VK_SCROLL_LOCK;
151 case Qt::Key_Left:
152 return DOM_VK_LEFT;
153 case Qt::Key_Right:
154 return DOM_VK_RIGHT;
155 case Qt::Key_Up:
156 return DOM_VK_UP;
157 case Qt::Key_Down:
158 return DOM_VK_DOWN;
159 case Qt::Key_PageDown:
160 return DOM_VK_PAGE_DOWN;
161 case Qt::Key_PageUp:
162 return DOM_VK_PAGE_UP;
163 case Qt::Key_F1:
164 return DOM_VK_F1;
165 case Qt::Key_F2:
166 return DOM_VK_F2;
167 case Qt::Key_F3:
168 return DOM_VK_F3;
169 case Qt::Key_F4:
170 return DOM_VK_F4;
171 case Qt::Key_F5:
172 return DOM_VK_F5;
173 case Qt::Key_F6:
174 return DOM_VK_F6;
175 case Qt::Key_F7:
176 return DOM_VK_F7;
177 case Qt::Key_F8:
178 return DOM_VK_F8;
179 case Qt::Key_F9:
180 return DOM_VK_F9;
181 case Qt::Key_F10:
182 return DOM_VK_F10;
183 case Qt::Key_F11:
184 return DOM_VK_F11;
185 case Qt::Key_F12:
186 return DOM_VK_F12;
187 case Qt::Key_F13:
188 return DOM_VK_F13;
189 case Qt::Key_F14:
190 return DOM_VK_F14;
191 case Qt::Key_F15:
192 return DOM_VK_F15;
193 case Qt::Key_F16:
194 return DOM_VK_F16;
195 case Qt::Key_F17:
196 return DOM_VK_F17;
197 case Qt::Key_F18:
198 return DOM_VK_F18;
199 case Qt::Key_F19:
200 return DOM_VK_F19;
201 case Qt::Key_F20:
202 return DOM_VK_F20;
203 case Qt::Key_F21:
204 return DOM_VK_F21;
205 case Qt::Key_F22:
206 return DOM_VK_F22;
207 case Qt::Key_F23:
208 return DOM_VK_F23;
209 case Qt::Key_F24:
210 return DOM_VK_F24;
211 }
212 return keyCode;
213}
214
215//! [0]
216Environment::Environment(QObject *parent)
217 : QObject(parent)
218{
219 m_engine = new QScriptEngine(this);
220
221 m_document = m_engine->newQObject(
222 object: new Document(this), ownership: QScriptEngine::QtOwnership,
223 options: QScriptEngine::ExcludeSuperClassContents);
224
225 CanvasGradientPrototype::setup(m_engine);
226
227 m_originalGlobalObject = m_engine->globalObject();
228 reset();
229}
230//! [0]
231
232Environment::~Environment()
233{
234}
235
236QScriptEngine *Environment::engine() const
237{
238 return m_engine;
239}
240
241QScriptValue Environment::document() const
242{
243 return m_document;
244}
245
246int Environment::setTimeout(const QScriptValue &expression, int delay)
247{
248 if (expression.isString() || expression.isFunction()) {
249 int timerId = startTimer(interval: delay);
250 m_timeoutHash.insert(akey: timerId, avalue: expression);
251 return timerId;
252 }
253 return -1;
254}
255
256void Environment::clearTimeout(int timerId)
257{
258 killTimer(id: timerId);
259 m_timeoutHash.remove(akey: timerId);
260}
261
262//! [1]
263int Environment::setInterval(const QScriptValue &expression, int delay)
264{
265 if (expression.isString() || expression.isFunction()) {
266 int timerId = startTimer(interval: delay);
267 m_intervalHash.insert(akey: timerId, avalue: expression);
268 return timerId;
269 }
270 return -1;
271}
272
273void Environment::clearInterval(int timerId)
274{
275 killTimer(id: timerId);
276 m_intervalHash.remove(akey: timerId);
277}
278
279void Environment::timerEvent(QTimerEvent *event)
280{
281 int id = event->timerId();
282 QScriptValue expression = m_intervalHash.value(akey: id);
283 if (!expression.isValid()) {
284 expression = m_timeoutHash.value(akey: id);
285 if (expression.isValid())
286 killTimer(id);
287 }
288 if (expression.isString()) {
289 evaluate(code: expression.toString());
290 } else if (expression.isFunction()) {
291 expression.call();
292 }
293 maybeEmitScriptError();
294}
295//! [1]
296
297//! [5]
298void Environment::addCanvas(QContext2DCanvas *canvas)
299{
300 m_canvases.append(t: canvas);
301}
302
303QContext2DCanvas *Environment::canvasByName(const QString &name) const
304{
305 for (int i = 0; i < m_canvases.size(); ++i) {
306 QContext2DCanvas *canvas = m_canvases.at(i);
307 if (canvas->objectName() == name)
308 return canvas;
309 }
310 return 0;
311}
312//! [5]
313
314QList<QContext2DCanvas*> Environment::canvases() const
315{
316 return m_canvases;
317}
318
319void Environment::reset()
320{
321 if (m_engine->isEvaluating())
322 m_engine->abortEvaluation();
323
324 {
325 QHash<int, QScriptValue>::const_iterator it;
326 for (it = m_intervalHash.constBegin(); it != m_intervalHash.constEnd(); ++it)
327 killTimer(id: it.key());
328 m_intervalHash.clear();
329 for (it = m_timeoutHash.constBegin(); it != m_timeoutHash.constEnd(); ++it)
330 killTimer(id: it.key());
331 m_timeoutHash.clear();
332 }
333
334 for (int i = 0; i < m_canvases.size(); ++i)
335 m_canvases.at(i)->reset();
336
337 QScriptValue self = m_engine->newQObject(
338 object: this, ownership: QScriptEngine::QtOwnership,
339 options: QScriptEngine::ExcludeSuperClassContents);
340
341 {
342 QScriptValueIterator it(m_originalGlobalObject);
343 while (it.hasNext()) {
344 it.next();
345 self.setProperty(name: it.scriptName(), value: it.value(), flags: it.flags());
346 }
347 }
348
349 self.setProperty(name: "self", value: self);
350 self.setProperty(name: "window", value: self);
351
352 QScriptValue navigator = m_engine->newObject();
353 navigator.setProperty(name: "appCodeName", value: "context2d");
354 navigator.setProperty(name: "appMinorVersion", value: 1);
355 navigator.setProperty(name: "appVersion", value: 1);
356 navigator.setProperty(name: "browserLanguage", value: "en_US");
357 navigator.setProperty(name: "cookieEnabled", value: false);
358 navigator.setProperty(name: "cpuClass", value: "i686");
359 navigator.setProperty(name: "onLine", value: false);
360 navigator.setProperty(name: "platform", value: "bogus OS");
361 navigator.setProperty(name: "systemLanguage", value: "en_US");
362 navigator.setProperty(name: "userAgent", value: "Context2D/1.1");
363 navigator.setProperty(name: "userLanguage", value: "en_US");
364 self.setProperty(name: "navigator", value: navigator);
365
366 m_engine->setGlobalObject(self);
367
368 m_engine->collectGarbage();
369}
370
371QScriptValue Environment::evaluate(const QString &code, const QString &fileName)
372{
373 return m_engine->evaluate(program: code, fileName);
374}
375
376bool Environment::hasIntervalTimers() const
377{
378 return !m_intervalHash.isEmpty();
379}
380
381// This is used by the Context2D Qt Script benchmark.
382void Environment::triggerTimers()
383{
384 for (int x = 0; x < 2; ++x) {
385 QList<int> timerIds = x ? m_intervalHash.keys() : m_timeoutHash.keys();
386 for (int i = 0; i < timerIds.size(); ++i) {
387 QTimerEvent fakeEvent(timerIds.at(i));
388 timerEvent(event: &fakeEvent);
389 }
390 }
391}
392
393//! [2]
394QScriptValue Environment::toWrapper(QObject *object)
395{
396 return m_engine->newQObject(object, ownership: QScriptEngine::QtOwnership,
397 options: QScriptEngine::PreferExistingWrapperObject
398 | QScriptEngine::ExcludeSuperClassContents);
399}
400//! [2]
401
402//! [3]
403void Environment::handleEvent(QContext2DCanvas *canvas, QMouseEvent *e)
404{
405 QString type;
406 switch (e->type()) {
407 case QEvent::MouseButtonPress:
408 type = "mousedown"; break;
409 case QEvent::MouseButtonRelease:
410 type = "mouseup"; break;
411 case QEvent::MouseMove:
412 type = "mousemove"; break;
413 default: break;
414 }
415 if (type.isEmpty())
416 return;
417
418 QScriptValue handlerObject;
419 QScriptValue handler = eventHandler(canvas, type, who: &handlerObject);
420 if (!handler.isFunction())
421 return;
422
423 QScriptValue scriptEvent = newFakeDomEvent(type, target: toWrapper(object: canvas));
424 // MouseEvent
425 scriptEvent.setProperty(name: "screenX", value: e->globalX(), flags: QScriptValue::ReadOnly);
426 scriptEvent.setProperty(name: "screenY", value: e->globalY(), flags: QScriptValue::ReadOnly);
427 scriptEvent.setProperty(name: "clientX", value: e->x(), flags: QScriptValue::ReadOnly);
428 scriptEvent.setProperty(name: "clientY", value: e->y(), flags: QScriptValue::ReadOnly);
429 scriptEvent.setProperty(name: "layerX", value: e->x(), flags: QScriptValue::ReadOnly);
430 scriptEvent.setProperty(name: "layerY", value: e->y(), flags: QScriptValue::ReadOnly);
431 scriptEvent.setProperty(name: "pageX", value: e->x(), flags: QScriptValue::ReadOnly);
432 scriptEvent.setProperty(name: "pageY", value: e->y(), flags: QScriptValue::ReadOnly);
433 scriptEvent.setProperty(name: "altKey", value: (e->modifiers() & Qt::AltModifier) != 0,
434 flags: QScriptValue::ReadOnly);
435 scriptEvent.setProperty(name: "ctrlKey", value: (e->modifiers() & Qt::ControlModifier) != 0,
436 flags: QScriptValue::ReadOnly);
437 scriptEvent.setProperty(name: "metaKey", value: (e->modifiers() & Qt::MetaModifier) != 0,
438 flags: QScriptValue::ReadOnly);
439 scriptEvent.setProperty(name: "shiftKey", value: (e->modifiers() & Qt::ShiftModifier) != 0,
440 flags: QScriptValue::ReadOnly);
441 int button = 0;
442 if (e->button() == Qt::RightButton)
443 button = 2;
444 else if (e->button() == Qt::MidButton)
445 button = 1;
446 scriptEvent.setProperty(name: "button", value: button);
447 scriptEvent.setProperty(name: "relatedTarget", value: m_engine->nullValue(),
448 flags: QScriptValue::ReadOnly);
449 handler.call(thisObject: handlerObject, args: QScriptValueList() << scriptEvent);
450 maybeEmitScriptError();
451}
452//! [3]
453
454void Environment::handleEvent(QContext2DCanvas *canvas, QKeyEvent *e)
455{
456 QString type;
457 switch (e->type()) {
458 case QEvent::KeyPress:
459 type = "keydown"; break;
460 case QEvent::KeyRelease:
461 type = "keyup"; break;
462 default: break;
463 }
464 if (type.isEmpty())
465 return;
466
467 QScriptValue handlerObject;
468 QScriptValue handler = eventHandler(canvas, type, who: &handlerObject);
469 if (!handler.isFunction())
470 return;
471
472 QScriptValue scriptEvent = newFakeDomEvent(type, target: toWrapper(object: canvas));
473 // KeyEvent
474 scriptEvent.setProperty(name: "isChar", value: !e->text().isEmpty());
475 scriptEvent.setProperty(name: "charCode", value: e->text());
476 scriptEvent.setProperty(name: "keyCode", value: FakeDomEvent::qtToDomKey(keyCode: e->key()));
477 scriptEvent.setProperty(name: "which", value: e->key());
478
479 handler.call(thisObject: handlerObject, args: QScriptValueList() << scriptEvent);
480 maybeEmitScriptError();
481}
482
483QScriptValue Environment::eventHandler(QContext2DCanvas *canvas, const QString &type,
484 QScriptValue *who)
485{
486 QString handlerName = "on" + type;
487 QScriptValue obj = toWrapper(object: canvas);
488 QScriptValue handler = obj.property(name: handlerName);
489 if (!handler.isValid()) {
490 obj = m_document;
491 handler = obj.property(name: handlerName);
492 }
493 if (who && handler.isFunction())
494 *who = obj;
495 return handler;
496}
497
498//! [4]
499QScriptValue Environment::newFakeDomEvent(const QString &type, const QScriptValue &target)
500{
501 QScriptValue e = m_engine->newObject();
502 // Event
503 e.setProperty(name: "type", value: type, flags: QScriptValue::ReadOnly);
504 e.setProperty(name: "bubbles", value: true, flags: QScriptValue::ReadOnly);
505 e.setProperty(name: "cancelable", value: false, flags: QScriptValue::ReadOnly);
506 e.setProperty(name: "target", value: target, flags: QScriptValue::ReadOnly);
507 e.setProperty(name: "currentTarget", value: target, flags: QScriptValue::ReadOnly);
508 e.setProperty(name: "eventPhase", value: 3); // bubbling
509 e.setProperty(name: "timeStamp", value: static_cast<int>(QDateTime::currentDateTime().toSecsSinceEpoch()));
510 // UIEvent
511 e.setProperty(name: "detail", value: 0, flags: QScriptValue::ReadOnly);
512 e.setProperty(name: "view", value: m_engine->globalObject(), flags: QScriptValue::ReadOnly);
513 return e;
514}
515//! [4]
516
517void Environment::maybeEmitScriptError()
518{
519 if (m_engine->hasUncaughtException())
520 emit scriptError(error: m_engine->uncaughtException());
521}
522
523
524Document::Document(Environment *env)
525 : QObject(env)
526{
527}
528
529Document::~Document()
530{
531}
532
533QScriptValue Document::getElementById(const QString &id) const
534{
535 Environment *env = qobject_cast<Environment*>(object: parent());
536 QContext2DCanvas *canvas = env->canvasByName(name: id);
537 if (!canvas)
538 return QScriptValue();
539 return env->toWrapper(object: canvas);
540}
541
542QScriptValue Document::getElementsByTagName(const QString &name) const
543{
544 if (name != "canvas")
545 return QScriptValue();
546 Environment *env = qobject_cast<Environment*>(object: parent());
547 QList<QContext2DCanvas*> list = env->canvases();
548 QScriptValue result = env->engine()->newArray(length: list.size());
549 for (int i = 0; i < list.size(); ++i)
550 result.setProperty(arrayIndex: i, value: env->toWrapper(object: list.at(i)));
551 return result;
552}
553
554void Document::addEventListener(const QString &type, const QScriptValue &listener,
555 bool useCapture)
556{
557 Q_UNUSED(useCapture);
558 if (listener.isFunction()) {
559 Environment *env = qobject_cast<Environment*>(object: parent());
560 QScriptValue self = env->toWrapper(object: this);
561 self.setProperty(name: "on" + type, value: listener);
562 }
563}
564
565
566QColor colorFromString(const QString &name);
567
568CanvasGradientPrototype::CanvasGradientPrototype(QObject *parent)
569 : QObject(parent)
570{
571}
572
573void CanvasGradientPrototype::addColorStop(qreal offset, const QString &color)
574{
575 CanvasGradient *self = qscriptvalue_cast<CanvasGradient*>(value: thisObject());
576 if (!self || (self->value.type() == QGradient::NoGradient))
577 return;
578 self->value.setColorAt(pos: offset, color: colorFromString(name: color));
579}
580
581void CanvasGradientPrototype::setup(QScriptEngine *engine)
582{
583 CanvasGradientPrototype *proto = new CanvasGradientPrototype();
584 engine->setDefaultPrototype(metaTypeId: qMetaTypeId<CanvasGradient>(),
585 prototype: engine->newQObject(object: proto, ownership: QScriptEngine::ScriptOwnership,
586 options: QScriptEngine::ExcludeSuperClassContents));
587}
588

source code of qtscript/examples/script/context2d/environment.cpp