Fix fatal mistake in last commit.
[qmlweb:qmlweb.git] / src / qtcore.js
1 /* @license
2
3   Copyright (c) 2011 Lauri Paimen <lauri@paimen.info>
4   Copyright (c) 2012 Anton Kreuzkamp <akreuzkamp@web.de>
5
6   Redistribution and use in source and binary forms, with or without
7   modification, are permitted provided that the following conditions
8   are met:
9
10       * Redistributions of source code must retain the above
11         copyright notice, this list of conditions and the following
12         disclaimer.
13
14       * Redistributions in binary form must reproduce the above
15         copyright notice, this list of conditions and the following
16         disclaimer in the documentation and/or other materials
17         provided with the distribution.
18
19   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
20   EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
23   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
24   OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
26   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
28   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
29   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30   SUCH DAMAGE.
31 */
32
33
34 /*
35  * QML engine and elements.
36  * 
37  * This is the main component of the project. It defines qml engine, elements
38  * and helpers for each.
39  *
40  * Exports:
41  *
42  * - QMLEngine(element, options) -- Returns new qml engine object, for which:
43  *   - loadFile(file) -- Load file to the engine (.qml or .qml.js atm)
44  *   - start() -- start the engine/application
45  *   - stop() -- stop the engine/application. Restarting is experimental.
46  *   element is HTMLCanvasElement and options are for debugging.
47  *   For further reference, see testpad and qml viewer applications.
48  */
49
50 (function() {
51
52 var QMLGlobalObject = {
53     Qt: {
54         rgba: function(r,g,b,a) {
55             var rgba = "rgba("
56                 + Math.round(r * 255) + ","
57                 + Math.round(g * 255) + ","
58                 + Math.round(b * 255) + ","
59                 + a + ")"
60             return rgba },
61         // Buttons masks
62         LeftButton: 1,
63         RightButton: 2,
64         MiddleButton: 4,
65         // Modifiers masks
66         NoModifier: 0,
67         ShiftModifier: 1,
68         ControlModifier: 2,
69         AltModifier: 4,
70         MetaModifier: 8,
71         KeypadModifier: 16 // Note: Not available in web
72
73         }
74     },
75     // Simple shortcuts to getter & setter functions, coolness with minifier
76     GETTER = "__defineGetter__",
77     SETTER = "__defineSetter__",
78     Undefined = undefined,
79     // This registry kind of implements weak-pointers in order to make
80     // garbage collecting possible
81     propertyUpdaters = [],
82     // Stack of Components/Files in whose context variable names are used
83     // Used to distribute the Component to all it's children without needing
84     // to pass it through all constructors.
85     // The last element in the Stack is the currently relevant context.
86     workingContext = [],
87     // Stack of properties that are currently are beeing evaluated. Used to
88     // get the information which property called a certain other property
89     // for evaluation and is thus dependant on it.
90     evaluatingProperties = [];
91
92 /**
93  * Inheritance helper
94  */
95 Object.create = function (o) {
96     function F() {}
97     F.prototype = o;
98     return new F();
99 };
100
101 // Helper. Ought to do absolutely nothing.
102 function noop(){};
103
104 // Helper to prevent some minimization cases. Ought to do "nothing".
105 function tilt() {arguments.length = 0};
106
107 // Helper to clone meta-objects for dynamic element creation
108 function cloneObject(obj) {
109     if (null == obj || typeof obj != "object")
110         return obj;
111     var copy = new obj.constructor();
112     for (var attr in obj) {
113         if (obj.hasOwnProperty(attr)) {
114             if (typeof obj[attr] == "object")
115                 copy[attr] = cloneObject(obj[attr]);
116             else
117                 copy[attr] = obj[attr];
118         }
119     }
120     return copy;
121 }
122
123 /**
124  * Helper function.
125  * Prints msg and values of object. Workaround when using getter functions as
126  * Chrome (at least) won't show property values for them.
127  * @param {String} msg Message
128  * @param {Object} obj Object to use (will be "printed", too)
129  * @param {Array} vals Values to list from the object.
130  */
131 function descr(msg, obj, vals) {
132     var str = msg + ": [" + obj.id + "] ",
133         i;
134     for (i = 0; i < vals.length; i++) {
135         str += vals[i] + "=" + obj[vals[i]] + " ";
136     }
137     console.log(str, obj);
138 }
139
140 /**
141  * QMLTransientValue.
142  * Value for setter can be given with this function.
143  * The difference is that no change signal is fired for setting the value.
144  * @param {any} val Value to be passed.
145  * @return {QMLTransientValue} special value for 
146  */
147 function QMLTransientValue(val) {
148     this.$val = val;
149 }
150
151 /**
152  * Evaluate binding.
153  * @param {Object} thisObj Object to be this
154  * @param {String} src Source code
155  * @param {Object} objectScope Scope for evaluation
156  * @param {Object} [globalScope] A second Scope for evaluation (both scopes properties will be directly accessible)
157  * @return {any} Resulting object.
158  */
159 function evalBinding(thisObj, src, objectScope, globalScope) {
160     var val;
161     // If "with" operator gets deprecated, you just have to create var of
162     // every property in objectScope and globalScope, assign the values, and run. That'll be quite
163     // slow :P
164     // todo: use thisObj.
165     //console.log("evalBinding objectScope, this, src: ", objectScope, thisObj, src);
166     (function() {
167         with(objectScope) {
168             if (globalScope) {
169                 with (globalScope) {
170                     val = eval(src);
171                 }
172             } else {
173                 val = eval(src);
174             }
175         }
176     })();
177     //console.log("    ->", val);
178     return val;
179 }
180
181 /**
182  * QML Object constructor.
183  * @param {Object} meta Meta information about the object
184  * @param {Object} parent Parent object for new object
185  * @return {Object} New qml object
186  */
187 function construct(meta, parent, engine) {
188     var constructors = {
189             MouseArea: QMLMouseArea,
190             Image: QMLImage,
191             BorderImage: QMLBorderImage,
192             Item: QMLItem,
193             Column: QMLItem, // todo
194             Row: QMLItem, // todo
195             Display: QMLItem, // todo
196             Text: QMLText,
197             Rectangle: QMLRectangle,
198             Repeater: QMLRepeater,
199             ListModel: QMLListModel,
200             ListElement: QMLListElement,
201             QMLDocument: QMLDocument,
202             Timer: QMLTimer,
203             SequentialAnimation: QMLSequentialAnimation,
204             NumberAnimation: QMLNumberAnimation,
205             TextInput: QMLTextInput,
206             Button: QMLButton
207         },
208         item,
209         cTree;
210         
211     if (meta.$class in constructors) {
212         item = new constructors[meta.$class](meta, parent, engine);
213         item.$$type = meta.$class; // Some debug info, don't depend on existence
214         item.$$meta = meta; // Some debug info, don't depend on existence
215         return item;
216     } else if (cTree = engine.loadComponent(meta.$class)) {
217         var component = construct(cTree, {}, engine);
218         item = component.$children[0];
219         //TODO: These $intern... properties are not nice. Find a better way.
220         item.$internChildren = component.$children[0].$children;
221         item.$internScope = component.$children[0].$scope;
222         meta.$componentMeta = cTree.$children[0];
223         if (cTree.$children[0].$defaultProperty) {
224             var bindSrc = "function $Qbc(newVal) {" + cTree.$children[0].$defaultProperty.src
225                             + " = newVal; };$Qbc";
226             item.$applyChild = evalBinding(item, bindSrc, item, item.$scope.getIdScope());
227         }
228         QMLBaseObject.call(item, meta, parent, engine);
229         item.$$type = meta.$class; // Some debug info, don't depend on existence
230         item.$$meta = meta; // Some debug info, don't depend on existence
231         return item;
232     } else {
233         console.log("No constructor found for " + meta.$class);
234     }
235 }
236
237 function createFunction(obj, funcName) {
238     var func;
239
240     function getter() {
241         return func;
242     }
243
244     function setter(newVal) {
245         if (!(newVal instanceof QMLBinding))
246             return;
247         var src;
248         if (newVal.src.search("function") == 0) {
249             // The src begins already with "function", so no need to put "function" around it
250             src = newVal.src + "; " + funcName;
251         } else {
252             // The src contains only the function body, so we need to put "function" around it
253             src = "var func = function() {"
254                     + newVal.src
255                     + "}; func";
256         }
257
258         func = evalBinding(null, src, obj, workingContext[workingContext.length-1].getIdScope());
259     }
260
261     setupGetterSetter(obj, funcName, getter, setter);
262 }
263
264 /**
265  * Create property getters and setters for object.
266  * @param {Object} obj Object for which gsetters will be set
267  * @param {String} propName Property name
268  * @param {Object} [options] Options that allow finetuning of the property
269  */
270 function createSimpleProperty(obj, propName, options) {
271     if (options == Undefined)
272         options = {};
273     var changeFuncName = 'on'
274                         + propName[0].toUpperCase()
275                         + propName.substr(1)
276                         + 'Changed',
277         binding,
278         objectScope = options.altParent || obj,
279         val,
280         dependantProperties = options.propDepList || [];
281
282     createFunction(obj, changeFuncName);
283
284     // Extended changesignal capabilities
285     obj["$" + changeFuncName] = [];
286
287     // Updater recalculates the value of a property if one of the
288     // dependencies changed
289     function update() {
290         if (binding) {
291             updaterIndex = propertyUpdaters.indexOf(update);
292             if (updaterIndex == -1) {
293                 propertyUpdaters.push(update);
294                 updaterIndex = propertyUpdaters.indexOf(update);
295                 objectScope.$ownPropertyUpdaters.push(updaterIndex);
296             }
297
298
299             val = binding();
300             if (obj[changeFuncName])
301                 obj[changeFuncName]();
302
303             // Trigger extended changesignal capabilities
304             for (i in obj["$" + changeFuncName]) {
305                 obj["$" + changeFuncName][i].call(objectScope, val, obj, propName);
306             }
307
308             if (!options.dontCallUpdaters) {
309                 for (i in dependantProperties) {
310                     if (propertyUpdaters[dependantProperties[i]] !== Undefined)
311                         propertyUpdaters[dependantProperties[i]].call(objectScope);
312                 }
313             }
314         }
315     }
316
317     var updaterIndex;
318
319     // Define getter
320     function getter() {
321         // Find out if this call to the getter is due to a property that is
322         // dependant on this one
323         if (evaluatingProperties.length !== 0) {
324             var item = evaluatingProperties[evaluatingProperties.length - 1];
325             if (evaluatingProperties.indexOf(updaterIndex) != -1)
326                 //TODO: Can this happen without having a binding loop?
327                 console.log("Probable binding loop detected!");
328             else if (dependantProperties.indexOf(item) == -1)
329                 dependantProperties.push(item);
330         }
331         return val;
332     };
333
334     // Define setter
335     function setter(newVal) {
336         var i;
337         //console.log("set", obj.id || obj, propName, newVal);
338         if (newVal instanceof QMLTransientValue) {
339             // TransientValue, don't fire signal handlers
340             val = newVal.$val;
341             binding = false;
342
343             // Trigger extended changesignal capabilities (for internal use)
344             for (i in obj["$" + changeFuncName]) {
345                 obj["$" + changeFuncName][i].call(objectScope, val, obj, propName);
346             }
347         } else if (newVal instanceof QMLBinding) {
348             updaterIndex = propertyUpdaters.indexOf(update);
349             if (updaterIndex == -1) {
350                 propertyUpdaters.push(update);
351                 updaterIndex = propertyUpdaters.indexOf(update);
352                 objectScope.$ownPropertyUpdaters.push(updaterIndex);
353             }
354
355             evaluatingProperties.push(updaterIndex);
356
357             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
358                 + "; return $Qbv;};$Qbc";
359             binding = evalBinding(null, bindSrc, objectScope, workingContext[workingContext.length-1].getIdScope());
360             val = binding();
361
362             evaluatingProperties.pop();
363
364             if (obj[changeFuncName])
365                 obj[changeFuncName]();
366
367             // Trigger extended changesignal capabilities
368             for (i in obj["$" + changeFuncName]) {
369                 obj["$" + changeFuncName][i].call(objectScope, val, obj, propName);
370             }
371         } else {
372             binding = false;
373
374             val = newVal;
375
376             if (obj[changeFuncName])
377                 obj[changeFuncName]();
378
379             // Trigger extended changesignal capabilities
380             for (i in obj["$" + changeFuncName]) {
381                 obj["$" + changeFuncName][i].call(objectScope, val, obj, propName);
382             }
383         }
384
385         if (!options.dontCallUpdaters) {
386             for (i in dependantProperties) {
387                 if (propertyUpdaters[dependantProperties[i]] !== Undefined)
388                     propertyUpdaters[dependantProperties[i]].call(objectScope);
389             }
390         }
391     };
392
393     setupGetterSetter(obj, propName, getter, setter);
394 }
395
396 /**
397  * Set up simple getter function for property
398  */
399 var setupGetter,
400     setupSetter,
401     setupGetterSetter;
402 (function() {
403
404 // todo: What's wrong with Object.defineProperty on some browsers?
405 // Object.defineProperty is the standard way to setup getters and setters.
406 // However, the following way to use Object.defineProperty don't work on some
407 // webkit-based browsers, namely Safari, iPad, iPhone and Nokia N9 browser.
408 // Chrome, firefox and opera still digest them fine.
409
410 // So, if the deprecated __defineGetter__ is available, use those, and if not
411 // use the standard Object.defineProperty (IE for example).
412
413     var useDefineProperty = !(Object[GETTER] && Object[SETTER]);
414
415     if (useDefineProperty) {
416
417         if (!Object.defineProperty) {
418             console.log("No __defineGetter__ or defineProperty available!");
419         }
420
421         setupGetter = function(obj, propName, func) {
422             Object.defineProperty(obj, propName,
423                 { get: func, configurable: true, enumerable: true } );
424         }
425         setupSetter = function(obj, propName, func) {
426             Object.defineProperty(obj, propName,
427                 { set: func, configurable: true, enumerable: false });
428         }
429         setupGetterSetter = function(obj, propName, getter, setter) {
430             Object.defineProperty(obj, propName,
431                 {get: getter, set: setter, configurable: true, enumerable: false });
432         }
433     } else {
434         setupGetter = function(obj, propName, func) {
435             obj[GETTER](propName, func);
436         }
437         setupSetter = function(obj, propName, func) {
438             obj[SETTER](propName, func);
439         }
440         setupGetterSetter = function(obj, propName, getter, setter) {
441             obj[GETTER](propName, getter);
442             obj[SETTER](propName, setter);
443         }
444     }
445
446 })();
447 /**
448  * Apply properties from meta to item. Skip values in skip.
449  * @param {Object} meta Source of properties
450  * @param {Object} item Target of property apply
451  * @param {Array} [skip] Array of property names to skip
452  */
453 function applyProperties(meta, item, skip) {
454     var i;
455     skip = skip || [];
456     for (i in meta) {
457         // skip if required
458         if (skip.indexOf(i) != -1) {
459             continue;
460         }
461         // skip global id's and internal values
462         if (i == "id" || i[0] == "$") {
463             continue;
464         }
465         // no property should begin with uppercase letter -- those indicate
466         // classes
467 //         if (i[0] == i[0].toUpperCase()) {
468 //             console.log(meta, "has", i, "-- bug?");
469 //             continue;
470 //         }
471         // Handle objects which are already defined in item differently
472         if (Object.prototype.toString.call(meta[i]) == '[object Object]') {
473             if (item[i] && !(meta[i] instanceof QMLBinding)) {
474                 // Apply properties one by one, otherwise apply at once
475                 // skip nothing
476                 applyProperties(meta[i], item[i]);
477                 continue;
478             }
479         }
480         item[i] = meta[i];
481     }
482 }
483
484 // ItemModel. EXPORTED.
485 JSItemModel = function() {
486     this.dataChangedCallbacks = [];
487     this.rowsInsertedCallbacks = [];
488     this.rowsMovedCallbacks = [];
489     this.rowsRemovedCallbacks = [];
490     this.modelResetCallbacks = [];
491     this.roleNames = [];
492
493     this.setRoleNames = function(names) {
494         this.roleNames = names;
495     }
496
497     this.emitDataChanged = function(startIndex, endIndex) {
498         for (var i in this.dataChangedCallbacks) {
499             this.dataChangedCallbacks[i](startIndex, endIndex);
500         }
501     }
502     this.emitRowsInserted = function(startIndex, endIndex) {
503         for (var i in this.rowsInsertedCallbacks) {
504             this.rowsInsertedCallbacks[i](startIndex, endIndex);
505         }
506     };
507     this.emitRowsMoved = function(sourceStartIndex, sourceEndIndex, destinationIndex) {
508         for (var i in this.rowsMovedCallbacks) {
509             this.rowsMovedCallbacks[i](sourceStartIndex, sourceEndIndex, destinationIndex);
510         }
511     };
512     this.emitRowsRemoved = function(startIndex, endIndex) {
513         for (var i in this.rowsRemovedCallbacks) {
514             this.rowsRemovedCallbacks[i](startIndex, endIndex);
515         }
516     };
517     this.emitModelReset = function() {
518         for (var i in this.modelResetCallbacks) {
519             this.modelResetCallbacks[i]();
520         }
521     };
522 }
523
524 // -----------------------------------------------------------------------------
525 // Stuff below defines QML things
526 // -----------------------------------------------------------------------------
527
528 // Helper
529 function unboundMethod() {
530     console.log("Unbound method for", this.$$type, this);
531 }
532
533 QMLRenderMode = {
534     Canvas: 0,
535     DOM: 1
536 }
537
538 // QML engine. EXPORTED.
539 QMLEngine = function (element, options) {
540 //----------Public Members----------
541     this.fps = 25;
542     this.$interval = Math.floor(1000 / this.fps); // Math.floor, causes bugs to timing?
543     this.running = false;
544
545     // Mouse Handling
546     this.mouseAreas = [];
547     this.oldMousePos = {x:0, y:0};
548
549     // List of available Components
550     this.components = {};
551
552     this.rootElement = element;
553     this.renderMode = element.nodeName == "CANVAS" ? QMLRenderMode.Canvas : QMLRenderMode.DOM;
554
555     // List of slots registered with Component.onCompleted
556     //TODO: Implement as real signals
557     this.completedSlots = [];
558
559
560 //----------Public Methods----------
561     // Start the engine
562     this.start = function()
563     {
564         var i;
565         if (!this.running) {
566             element.addEventListener("touchstart", touchHandler);
567             element.addEventListener("mousemove", mousemoveHandler);
568             this.running = true;
569             tickerId = setInterval(tick, this.$interval);
570             for (i = 0; i < whenStart.length; i++) {
571                 whenStart[i]();
572             }
573             this.$draw();
574         }
575     }
576
577     // Stop the engine
578     this.stop = function()
579     {
580         var i;
581         if (this.running) {
582             element.removeEventListener("touchstart", touchHandler);
583             element.removeEventListener("mousemove", mousemoveHandler);
584             this.running = false;
585             clearInterval(tickerId);
586             for (i = 0; i < whenStop.length; i++) {
587                 whenStop[i]();
588             }
589         }
590     }
591
592     // Load file, parse and construct (.qml or .qml.js)
593     this.loadFile = function(file) {
594         basePath = file.split("/");
595         basePath[basePath.length - 1] = "";
596         basePath = basePath.join("/");
597         var src = getUrlContents(file);
598         if (options.debugSrc) {
599             options.debugSrc(src);
600         }
601         this.loadQML(src);
602     }
603     // parse and construct qml
604     this.loadQML = function(src) {
605         var tree = parseQML(src);
606         if (options.debugTree) {
607             options.debugTree(tree);
608         }
609         doc = construct(tree, {}, this);
610         doc.$init();
611         for (var i in this.completedSlots) {
612             this.completedSlots[i]();
613         }
614     }
615
616     this.registerProperty = function(obj, propName)
617     {
618         var dependantProperties = [];
619         var value = obj[propName];
620
621         function getter() {
622             if (evaluatingProperties.length !== 0) {
623                 var item = evaluatingProperties[evaluatingProperties.length - 1];
624                 if (item[0] !== obj && dependantProperties.indexOf(item) == -1)
625                     dependantProperties.push(item);
626             }
627             return value;
628         }
629
630         function setter(newVal) {
631             value = newVal;
632
633             for (i in dependantProperties) {
634                 if (propertyUpdaters[dependantProperties[i]] !== Undefined)
635                     propertyUpdaters[dependantProperties[i]]();
636             }
637         }
638
639         setupGetterSetter(obj, propName, getter, setter);
640     }
641
642 //Intern
643
644     // Load file, parse and construct as Component (.qml)
645     this.loadComponent = function(name)
646     {
647         if (name in this.components)
648             return this.components[name];
649
650         var file = basePath + name + ".qml";
651
652         var src = getUrlContents(file);
653         if (src=="")
654             return undefined;
655         var tree = parseQML(src);
656         this.components[name] = tree;
657         return tree;
658     }
659
660     this.$getGlobalObj = function()
661     {
662         return globalObj;
663     }
664
665     this.$getTextMetrics = function(text, fontCss)
666     {
667         canvas.save();
668         canvas.font = fontCss;
669         var metrics = canvas.measureText(text);
670         canvas.restore();
671         return metrics;
672     }
673
674     this.$setBasePath = function(path)
675     {
676         basePath = path;
677     }
678
679     // Return a path to load the file
680     this.$resolvePath = function(file)
681     {
682         if (file == "" || file.indexOf("://") != -1 || file.indexOf("/") == 0) {
683             return file;
684         }
685         return basePath + file;
686     }
687
688     this.$registerStart = function(f)
689     {
690         whenStart.push(f);
691     }
692
693     this.$registerStop = function(f)
694     {
695         whenStop.push(f);
696     }
697
698     this.$addTicker = function(t)
699     {
700         tickers.push(t);
701     }
702
703     this.$removeTicker = function(t)
704     {
705         var index = tickers.indexOf(t);
706         if (index != -1) {
707             tickers.splice(index, 1);
708         }
709     }
710
711     this.size = function()
712     {
713         return { width: doc.getWidth(), height: doc.getHeight() };
714     }
715
716     // Requests draw in case something has probably changed.
717     this.$requestDraw = function()
718     {
719         isDirty = true;
720     }
721
722     // Performance measurements
723     this.$perfDraw = function(canvas)
724     {
725         doc.$draw(canvas);
726     }
727
728     this.$draw = function()
729     {
730         if (this.renderMode == QMLRenderMode.DOM)
731             return;
732         var time = new Date();
733
734         element.height = doc.height;
735         element.width = doc.width;
736
737         // Pixel-perfect size
738 //         canvasEl.style.height = canvasEl.height + "px";
739 //         canvasEl.style.width = canvasEl.width + "px";
740
741         doc.$draw(canvas);
742
743         if (options.drawStat) {
744             options.drawStat((new Date()).getTime() - time.getTime());
745         }
746     }
747
748
749 //----------Private Methods----------
750     // In JS we cannot easily access public members from
751     // private members so self acts as a bridge
752     var self = this;
753     
754     // Listen also to touchstart events on supporting devices
755     // Makes clicks more responsive (do not wait for click event anymore)
756     function touchHandler(e)
757     {
758         // preventDefault also disables pinching and scrolling while touching
759         // on qml application
760         e.preventDefault();
761         var at = {
762             layerX: e.touches[0].pageX - element.offsetLeft,
763             layerY: e.touches[0].pageY - element.offsetTop,
764             button: 1
765         }
766         element.onclick(at);
767
768     }
769
770     function mousemoveHandler(e)
771     {
772         var i;
773         for (i in self.mouseAreas) {
774             var l = self.mouseAreas[i];
775             if (l && l.onExited && l.hoverEnabled
776                   && (self.oldMousePos.x >= l.left
777                       && self.oldMousePos.x <= l.right
778                       && self.oldMousePos.y >= l.top
779                       && self.oldMousePos.y <= l.bottom)
780                   && !(e.pageX - element.offsetLeft >= l.left
781                        && e.pageX - element.offsetLeft <= l.right
782                        && e.pageY - element.offsetTop >= l.top
783                        && e.pageY - element.offsetTop <= l.bottom) )
784                 l.onExited();
785         }
786         for (i in self.mouseAreas) {
787             var l = self.mouseAreas[i];
788             if (l && l.onEntered && l.hoverEnabled
789                   && (e.pageX - element.offsetLeft >= l.left
790                       && e.pageX - element.offsetLeft <= l.right
791                       && e.pageY - element.offsetTop >= l.top
792                       && e.pageY - element.offsetTop <= l.bottom)
793                   && !(self.oldMousePos.x >= l.left
794                        && self.oldMousePos.x <= l.right
795                        && self.oldMousePos.y >= l.top
796                        && self.oldMousePos.y <= l.bottom))
797                 l.onEntered();
798         }
799         self.oldMousePos = { x: e.pageX - element.offsetLeft,
800                             y: e.pageY - element.offsetTop };
801     }
802
803     function tick()
804     {
805         var i,
806             now = (new Date).getTime(),
807             elapsed = now - lastTick;
808         lastTick = now;
809         for (i = 0; i < tickers.length; i++) {
810             tickers[i](now, elapsed);
811         }
812         if (isDirty) {
813             isDirty = false;
814             self.$draw();
815         }
816     }
817
818
819 //----------Private Members----------
820     // Target canvas
821     if (this.renderMode == QMLRenderMode.Canvas)
822         var canvas = element.getContext('2d');
823
824     var // Global Qt object
825         globalObj = Object.create(QMLGlobalObject),
826         // Root document of the engine
827         doc,
828         // Callbacks for stopping or starting the engine
829         whenStop = [],
830         whenStart = [],
831         // Ticker resource id and ticker callbacks
832         tickerId,
833         tickers = [],
834         lastTick = new Date().getTime(),
835         // isDirty tells if we should do redraw
836         isDirty = true,
837         // Base path of qml engine (used for resource loading)
838         basePath,
839         i;
840
841
842 //----------Construct----------
843
844     options = options || {};
845
846     if (options.debugConsole) {
847         // Replace QML-side console.log
848         globalObj.console = {};
849         globalObj.console.log = function() {
850             var args = Array.prototype.slice.call(arguments);
851             options.debugConsole.apply(Undefined, args);
852         };
853     }
854
855     // Register mousehandler for element
856     element.onclick = function(e) {
857         if (self.running) {
858             var i;
859             for (i in self.mouseAreas) {
860                 var l = self.mouseAreas[i];
861                 var mouse = {
862                     accepted: true,
863                     button: e.button == 0 ? QMLGlobalObject.Qt.LeftButton :
864                             e.button == 1 ? QMLGlobalObject.Qt.RightButton :
865                             e.button == 2 ? QMLGlobalObject.Qt.MiddleButton :
866                             0,
867                     modifiers: (e.ctrlKey * QMLGlobalObject.Qt.CtrlModifier)
868                             | (e.altKey * QMLGlobalObject.Qt.AltModifier)
869                             | (e.shiftKey * QMLGlobalObject.Qt.ShiftModifier)
870                             | (e.metaKey * QMLGlobalObject.Qt.MetaModifier),
871                     x: (e.offsetX || e.layerX) - l.left,
872                     y: (e.offsetY || e.layerY) - l.top
873                 };
874
875                 if (l.enabled
876                 && mouse.x >= 0 // equals: e.offsetX >= l.left
877                 && (e.offsetX || e.layerX) <= l.right
878                 && mouse.y >= 0 // equals: e.offsetY >= l.top
879                 && (e.offsetY || e.layerY) <= l.bottom) {
880                     // Dispatch mouse event
881                     l.mouse = mouse;
882                     l.onClicked();
883                     l.mouse = Undefined;
884                     self.$requestDraw();
885                     break;
886                 }
887             }
888         }
889     }
890 }
891
892 // Base object for all qml thingies
893 function QMLBaseObject(meta, parent, engine) {
894     var i,
895         prop,
896         self = this;
897
898     if (!this.$draw)
899         this.$draw = noop;
900     this.$scope = workingContext[workingContext.length-1];
901     if (!this.$ownPropertyUpdaters)
902         this.$ownPropertyUpdaters = [];
903
904     // parent
905     this.parent = parent;
906
907     // id
908     if (meta.id) {
909         this.id = meta.id;
910         this.$scope.defId(meta.id, this);
911     }
912
913     // children
914     this.$children = [];
915     function setChildren(childMeta) {
916         child = construct(childMeta, this, engine);
917         this.$children.push( child );
918     }
919     function getChildren() {
920         return this.$children;
921     }
922     setupGetterSetter(this, "children", getChildren, setChildren);
923
924     //defaultProperty
925     if (!this.$applyChild) {
926         this.$applyChild = function(newVal) {
927             this.children = newVal;
928         };
929     }
930
931     // properties
932     if (meta.$properties) {
933         for (i in meta.$properties) {
934             prop = meta.$properties[i];
935             if (prop.type == "alias") {
936                 // alias is reverse property, reverse getters and setters needed
937                 if (!(prop.value instanceof QMLBinding)) {
938                     console.log("Assumption failed: alias was not binding");
939                 }
940                 console.log("Aliases not yet supported");
941                 /* Aliases are not yet supported.
942                 Following code has never been executed.
943                 Left here for reference.
944
945                 this[GETTER](i, function() {
946                     return evalBinding(null, prop.value.src, this);
947                 });
948                 this[SETTER](i, function(val) {
949                     // val needs to be assigned to property/object/thingie
950                     // pointed by value.
951                     // todo: not sure how to do this by-the-book.
952
953                     // Way 1:
954                     // Inject value-to-be-assigned to scope and alter the
955                     // binding to assign the value. Then evaluate. Dirty hack?
956                     var scope = this,
957                         assignment = "(" + prop.value.src  + ") = $$$val";
958                     scope.$$$val = val;
959                     evalBinding(null, assignment, scope);
960
961                     // Way 2:
962                     // Evaluate binding to get the target object, then simply
963                     // assign. Didn't choose this as I'm afraid it wont work for
964                     // primitives.
965                     // var a = evalBinding(null,
966                     //                      prop.value.src, scope);
967                     // a = val;
968                     //
969
970                     });
971                 }
972                 */
973             } else {
974                 createSimpleProperty(this, i);
975                 this[i] = prop.value;
976             }
977         }
978     }
979
980     // todo: handle alias property assignments here?
981
982     // methods
983     function createMethod(item, name, method) {
984         // Trick: evaluate method with bindings to get pointer to
985         // function that can then be applied with arguments
986         // given to this function to do the job (and get the return
987         // values).
988         var func = evalBinding(null,
989                                method + ";" + name,
990                                item,
991                                workingContext[workingContext.length-1].getIdScope());
992         return function() {
993             return func.apply(null, arguments);
994         };
995     }
996     if (meta.$functions) {
997         for (i in meta.$functions) {
998             this[i] = createMethod(this, i, meta.$functions[i]);
999         }
1000     }
1001
1002     // signals
1003     if (meta.$signals) {
1004         for (i in meta.$signals) {
1005         
1006         }
1007     }
1008
1009     // Component.onCompleted
1010     this.Component = {};
1011     function addCompletedSlot(val) {
1012         if (!(val instanceof QMLBinding))
1013             return;
1014         var src = "var func = function() {"
1015                     + val.src
1016                     + "}; func";
1017
1018         func = evalBinding(null, src, self, self.$scope.getIdScope());
1019         self.Component.$onCompleted = func;
1020         engine.completedSlots.push(func);
1021     }
1022     function getCompletedSlot() {
1023         return self.Component.$onCompleted;
1024     }
1025     setupGetterSetter(this.Component, "onCompleted", getCompletedSlot, addCompletedSlot);
1026
1027     // Construct from meta, not from this!
1028     if (meta.$children) {
1029         for (i = 0; i < meta.$children.length; i++) {
1030             // This will call the setter of the defaultProperty
1031             // In case of the default property being children
1032             // (normal case) it will add a new child
1033             this.$applyChild(meta.$children[i]);
1034         }
1035     }
1036
1037     if (!this.$init)
1038         this.$init = [];
1039     this.$init[0] = function() {
1040         if (engine.renderMode == QMLRenderMode.DOM
1041             && self.$domElement !== Undefined && parent.$domElement) {
1042             parent.$domElement.appendChild(self.$domElement);
1043         }
1044
1045         // Apply property-values which are set inside the Component-definition
1046         if (meta.$componentMeta) {
1047             workingContext.push(self.$internScope);
1048             applyProperties(meta.$componentMeta, self);
1049             workingContext.pop();
1050         }
1051
1052         workingContext.push(self.$scope);
1053         applyProperties(meta, self);
1054         workingContext.pop();
1055
1056
1057         if (self.$internChildren != undefined) {
1058             for (var i in self.$internChildren) {
1059                 for (var j = self.$internChildren[i].$init.length - 1; j>=0; j--)
1060                     self.$internChildren[i].$init[j]();
1061             }
1062         } else {
1063             for (var i in self.$children) {
1064                 for (var j = self.$children[i].$init.length - 1; j>=0; j--)
1065                     self.$children[i].$init[j]();
1066             }
1067         }
1068     }
1069 }
1070
1071 // Item qml object
1072 function QMLItem(meta, parent, engine) {
1073     QMLBaseObject.call(this, meta, parent, engine);
1074     var child,
1075         o, i,
1076         self = this;
1077
1078     if (engine.renderMode == QMLRenderMode.DOM) {
1079         if (!this.$domElement)
1080             this.$domElement = document.createElement("div");
1081         this.$domElement.style.position = "absolute";
1082         this.$domElement.style.pointerEvents = "none";
1083         this.$domElement.className = meta.$class + (this.id ? " " + this.id : "");
1084     }
1085
1086     this.$geometry = {
1087         dependantProperties: [],
1088         left: 0,
1089         top: 0,
1090         update: function() {
1091             var updaterIndex = propertyUpdaters.indexOf(self.$geometry.update);
1092             if (updaterIndex == -1) {
1093                 propertyUpdaters.push(self.$geometry.update);
1094                 updaterIndex = propertyUpdaters.indexOf(self.$geometry.update);
1095                 self.$ownPropertyUpdaters.push(updaterIndex);
1096             }
1097
1098             evaluatingProperties.push(updaterIndex);
1099             if (self.$geometry.widthVal)
1100                 self.$geometry.width = self.$geometry.widthVal();
1101             if (self.$geometry.heightVal)
1102                 self.$geometry.height = self.$geometry.heightVal();
1103             if (self.$geometry.hVal)
1104                 self.$geometry.left = self.$geometry.hVal();
1105             if (self.$geometry.vVal)
1106                 self.$geometry.top = self.$geometry.vVal();
1107             evaluatingProperties.pop();
1108
1109             if (self.$geometry.geometryChanged) {
1110                 self.$geometry.geometryChanged.call(self);
1111             }
1112
1113             for (i in self.$geometry.dependantProperties) {
1114                 if (propertyUpdaters[self.$geometry.dependantProperties[i]] !== Undefined)
1115                     propertyUpdaters[self.$geometry.dependantProperties[i]]();
1116             }
1117             engine.$requestDraw();
1118         }
1119     }
1120
1121     // Anchors. Gah!
1122     // Create anchors object
1123     this.anchors = {};
1124
1125     function marginsSetter(val) {
1126         this.topMargin = val;
1127         this.bottomMargin = val;
1128         this.leftMargin = val;
1129         this.rightMargin = val;
1130     }
1131     setupSetter(this, 'margins', marginsSetter);
1132
1133     var geometryOptions = {
1134         altParent: this,
1135         propDepList: this.$geometry.dependantProperties,
1136         dontCallUpdaters: true
1137     };
1138
1139
1140     // Define anchor getters, returning absolute position
1141     // left, right, top, bottom, horizontalCenter, verticalCenter, baseline
1142     // todo: margins
1143     function leftGetter() {
1144         if (evaluatingProperties.length !== 0) {
1145             var updater = evaluatingProperties[evaluatingProperties.length - 1];
1146             if (updater !== propertyUpdaters.indexOf(self.$geometry.update)
1147                 && self.$geometry.dependantProperties.indexOf(updater) == -1)
1148                 self.$geometry.dependantProperties.push(updater);
1149         }
1150         return self.$geometry.left;
1151     }
1152     setupGetter(this, "left", leftGetter);
1153
1154     function rightGetter() {
1155         return self.left + self.width;
1156     }
1157     setupGetter(this, "right", rightGetter);
1158
1159     function topGetter() {
1160         if (evaluatingProperties.length !== 0) {
1161             var updater = evaluatingProperties[evaluatingProperties.length - 1];
1162             if (updater !== propertyUpdaters.indexOf(self.$geometry.update)
1163                 && self.$geometry.dependantProperties.indexOf(updater) == -1)
1164                 self.$geometry.dependantProperties.push(updater);
1165         }
1166         return self.$geometry.top;
1167     }
1168     setupGetter(this, "top", topGetter);
1169
1170     function bottomGetter() {
1171         return self.top + self.height;
1172     }
1173     setupGetter(self, "bottom", bottomGetter);
1174
1175     function hzGetter() {
1176         return self.left + self.width / 2;
1177     }
1178     setupGetter(this, "horizontalCenter", hzGetter);
1179
1180     function vzGetter() {
1181         return self.top + self.height / 2;
1182     }
1183     setupGetter(this, "verticalCenter", vzGetter);
1184
1185     function blGetter() {
1186         return self.top;
1187     }
1188     setupGetter(this, "baseline", blGetter);
1189
1190     // Assign values from meta
1191     function topSetter(newVal) {
1192         if (newVal instanceof QMLBinding) {
1193             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1194                     + "; return $Qbv;};$Qbc";
1195             self.$geometry.vVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1196         } else {
1197             self.$geometry.vVal = (function(val) { return function() {
1198                     return val;
1199                 }
1200             })(newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1201         }
1202         self.$geometry.update();
1203     }
1204     setupGetterSetter(this.anchors, "top", topGetter, topSetter, topSetter);
1205     function bottomSetter(newVal) {
1206         if (newVal instanceof QMLBinding) {
1207             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1208                     + "; return $Qbv - height;};$Qbc";
1209             self.$geometry.vVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1210         } else {
1211             self.$geometry.vVal = (function(obj, val) { return function() {
1212                     return val - obj.height;
1213                 }
1214             })(self, newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1215         }
1216         self.$geometry.update();
1217     }
1218     setupGetterSetter(this.anchors, "bottom", bottomGetter, bottomSetter);
1219     function leftSetter(newVal) {
1220         if (newVal instanceof QMLBinding) {
1221             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1222                     + "; return $Qbv;};$Qbc";
1223             self.$geometry.hVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1224         } else {
1225             self.$geometry.hVal = (function(val) { return function() {
1226                     return val;
1227                 }
1228             })(newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1229         }
1230         self.$geometry.update();
1231     }
1232     setupGetterSetter(this.anchors, "left", leftGetter, leftSetter);
1233     function rightSetter(newVal) {
1234         if (newVal instanceof QMLBinding) {
1235             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1236                     + "; return $Qbv - width;};$Qbc";
1237             self.$geometry.hVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1238         } else {
1239             self.$geometry.hVal = (function(obj, val) { return function() {
1240                     return val - obj.width;
1241                 }
1242             })(self, newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1243         }
1244         self.$geometry.update();
1245     }
1246     setupGetterSetter(this.anchors, "right", rightGetter, rightSetter);
1247     function hzSetter(newVal) {
1248         if (newVal instanceof QMLBinding) {
1249             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1250                     + "; return $Qbv - width / 2;};$Qbc";
1251             self.$geometry.hVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1252         } else {
1253             self.$geometry.hVal = (function(obj, val) { return function() {
1254                     return val - obj.width / 2;
1255                 }
1256             })(self, newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1257         }
1258         self.$geometry.update();
1259     }
1260     setupGetterSetter(this.anchors, "horizontalCenter", hzGetter, hzSetter);
1261     function vzSetter(newVal) {
1262         if (newVal instanceof QMLBinding) {
1263             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1264                     + "; return $Qbv - height / 2;};$Qbc";
1265             self.$geometry.vVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1266         } else {
1267             self.$geometry.vVal = (function(obj, val) { return function() {
1268                     return val - obj.height / 2;
1269                 }
1270             })(self, newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1271         }
1272         self.$geometry.update();
1273     }
1274     setupGetterSetter(this.anchors, "verticalCenter", vzGetter, vzSetter);
1275     function fillSetter(newVal) {
1276         var val = newVal.src;
1277         var hBindSrc = "function $Qbc() { var $Qbv = " + val
1278                 + "; return $Qbv.left;};$Qbc";
1279         self.$geometry.hVal = evalBinding(null, hBindSrc, self, workingContext[workingContext.length-1].getIdScope());
1280         var vBindSrc = "function $Qbc() { var $Qbv = " + val
1281                 + "; return $Qbv.top;};$Qbc";
1282         self.$geometry.vVal = evalBinding(null, vBindSrc, self, workingContext[workingContext.length-1].getIdScope());
1283         var widthBindSrc = "function $Qbc() { var $Qbv = " + val
1284                 + "; return $Qbv.width;};$Qbc";
1285         self.$geometry.widthVal = evalBinding(null, widthBindSrc, self, workingContext[workingContext.length-1].getIdScope());
1286         var heightBindSrc = "function $Qbc() { var $Qbv = " + val
1287                 + "; return $Qbv.height;};$Qbc";
1288         self.$geometry.heightVal = evalBinding(null, heightBindSrc, self, workingContext[workingContext.length-1].getIdScope());
1289         self.$geometry.update();
1290     }
1291     setupSetter(this.anchors, "fill", fillSetter);
1292     function centerInSetter(newVal) {
1293         var val = newVal.src;
1294         var hBindSrc = "function $Qbc() { var $Qbv = " + val
1295                 + "; return $Qbv.horizontalCenter - width / 2;};$Qbc";
1296         self.$geometry.hVal = evalBinding(null, hBindSrc, self, workingContext[workingContext.length-1].getIdScope());
1297         var vBindSrc = "function $Qbc() { var $Qbv = " + val
1298                 + "; return $Qbv.verticalCenter - height / 2;};$Qbc";
1299         self.$geometry.vVal = evalBinding(null, vBindSrc, self, workingContext[workingContext.length-1].getIdScope());
1300         self.$geometry.update();
1301     }
1302     setupSetter(this.anchors, "centerIn", centerInSetter);
1303
1304     function xGetter() {
1305         return self.left - self.parent.left;
1306     }
1307     function xSetter(newVal) {
1308         if (newVal instanceof QMLBinding) {
1309             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1310                     + "; return $Qbv + parent.left;};$Qbc";
1311             self.$geometry.hVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1312         } else {
1313             self.$geometry.hVal = (function(obj, val) { return function() {
1314                     return val + obj.parent.left;
1315                 }
1316             })(self, newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1317         }
1318         self.$geometry.update();
1319     }
1320     setupGetterSetter(this, "x", xGetter, xSetter);
1321     function yGetter() {
1322         return self.top - self.parent.top;
1323     }
1324     function ySetter(newVal) {
1325         if (newVal instanceof QMLBinding) {
1326             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1327                     + "; return $Qbv + parent.top;};$Qbc";
1328             self.$geometry.vVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1329         } else {
1330             self.$geometry.vVal = (function(obj, val) { return function() {
1331                     return val + obj.parent.top;
1332                 }
1333             })(self, newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1334         }
1335         self.$geometry.update();
1336     }
1337     setupGetterSetter(this, "y", yGetter, ySetter);
1338
1339     function widthGetter() {
1340         if (evaluatingProperties.length !== 0) {
1341             var updater = evaluatingProperties[evaluatingProperties.length - 1];
1342             if (updater !== propertyUpdaters.indexOf(self.$geometry.update)
1343                 && self.$geometry.dependantProperties.indexOf(updater) == -1)
1344                 self.$geometry.dependantProperties.push(updater);
1345         }
1346         return self.$geometry.width !== Undefined ? self.$geometry.width : self.implicitWidth;
1347     }
1348     function widthSetter(newVal) {
1349         if (newVal instanceof QMLBinding) {
1350             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1351                     + "; return $Qbv;};$Qbc";
1352             self.$geometry.widthVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1353         } else {
1354             self.$geometry.widthVal = (function(val) { return function() {
1355                     return val;
1356                 }
1357             })(newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1358         }
1359         self.$geometry.update();
1360     }
1361     setupGetterSetter(this, "width", widthGetter, widthSetter);
1362
1363     function heightGetter() {
1364         if (evaluatingProperties.length !== 0) {
1365             var updater = evaluatingProperties[evaluatingProperties.length - 1];
1366             if (updater !== propertyUpdaters.indexOf(self.$geometry.update)
1367                 && self.$geometry.dependantProperties.indexOf(updater) == -1)
1368                 self.$geometry.dependantProperties.push(updater);
1369         }
1370         return self.$geometry.height !== Undefined ? self.$geometry.height : self.implicitHeight;
1371     }
1372     function heightSetter(newVal) {
1373         if (newVal instanceof QMLBinding) {
1374             var bindSrc = "function $Qbc() { var $Qbv = " + newVal.src
1375                     + "; return $Qbv;};$Qbc";
1376             self.$geometry.heightVal = evalBinding(null, bindSrc, self, workingContext[workingContext.length-1].getIdScope());
1377         } else {
1378             self.$geometry.heightVal = (function(val) { return function() {
1379                     return val;
1380                 }
1381             })(newVal instanceof QMLTransientValue ? newVal.$val : newVal);
1382         }
1383         self.$geometry.update();
1384     }
1385     setupGetterSetter(this, "height", heightGetter, heightSetter);
1386
1387     createSimpleProperty(this, "implicitWidth");
1388     createSimpleProperty(this, "implicitHeight");
1389     createSimpleProperty(this, "rotation");
1390     createSimpleProperty(this, "spacing");
1391     createSimpleProperty(this, "visible");
1392     createSimpleProperty(this, "z");
1393
1394     if (engine.renderMode == QMLRenderMode.DOM) {
1395         this.$onRotationChanged.push(function(newVal) {
1396             this.$domElement.style.transform = "rotate(" + newVal + "deg)";
1397             this.$domElement.style.MozTransform = "rotate(" + newVal + "deg)";      //Firefox
1398             this.$domElement.style.webkitTransform = "rotate(" + newVal + "deg)";   //Chrome and Safari
1399             this.$domElement.style.OTransform = "rotate(" + newVal + "deg)";        //Opera
1400             this.$domElement.style.msTransform = "rotate(" + newVal + "deg)";       //IE
1401         });
1402         this.$onVisibleChanged.push(function(newVal) {
1403             this.$domElement.style.visibility = newVal ? "inherit" : "hidden";
1404         });
1405         this.$onZChanged.push(function(newVal) {
1406             this.$domElement.style.zIndex = newVal;
1407         });
1408         this.$geometry.geometryChanged = function() {
1409             var w = this.width,
1410                 h = this.height;
1411             this.$domElement.style.width = w ? w + "px" : "auto";
1412             this.$domElement.style.height = h ? h + "px" : "auto";
1413             this.$domElement.style.top = (this.$geometry.top-this.parent.top) + "px";
1414             this.$domElement.style.left = (this.$geometry.left-this.parent.left) + "px";
1415         }
1416     }
1417
1418     this.$init.push(function() {
1419         self.implicitHeight = 0;
1420         self.implicitWidth = 0;
1421         self.spacing = 0;
1422         self.x = 0;
1423         self.y = 0;
1424     });
1425
1426     this.$draw = function(c) {
1427         var i;
1428         if (this.visible !== false) { // Undefined means inherit, means true
1429             if (this.$drawItem ) {
1430                 var rotRad = (this.rotation || 0) / 180 * Math.PI,
1431                     rotOffsetX = Math.sin(rotRad) * this.width,
1432                     rotOffsetY = Math.sin(rotRad) * this.height;
1433                 c.save();
1434
1435                 // Handle rotation
1436                 // todo: implement transformOrigin
1437                 c.translate(this.left + rotOffsetX, this.top + rotOffsetY);
1438                 c.rotate(rotRad);
1439                 c.translate(-this.left, -this.top);
1440                 // Leave offset for drawing...
1441                 this.$drawItem(c);
1442                 c.translate(-rotOffsetX, -rotOffsetY);
1443                 c.restore();
1444             }
1445             if (this.$internChildren != undefined) {
1446                 for (i = 0; i < this.$internChildren.length; i++) {
1447                     if (this.$internChildren[i]
1448                         && this.$internChildren[i].$draw) {
1449                         this.$internChildren[i].$draw(c);
1450                     }
1451                 }
1452             } else {
1453                 for (i = 0; i < this.$children.length; i++) {
1454                     if (this.$children[i]
1455                         && this.$children[i].$draw) {
1456                         this.$children[i].$draw(c);
1457                     }
1458                 }
1459             }
1460         }
1461     }
1462 }
1463
1464 function QMLText(meta, parent, engine) {
1465     QMLItem.call(this, meta, parent, engine);
1466     var self = this;
1467
1468     if (engine.renderMode == QMLRenderMode.DOM) {
1469         // We create another span inside the text to distinguish the actual
1470         // (possibly html-formatted) text from child elements
1471         this.$domElement.innerHTML = "<div></div>";
1472         this.$domElement.style.pointerEvents = "auto";
1473         this.$domElement.firstChild.style.width = "100%";
1474         this.$domElement.firstChild.style.height = "100%";
1475     }
1476
1477     // Creates font css description
1478     function fontCss(font) {
1479         var css = "";
1480         css += font.italic ? "italic " : "normal ";
1481         css += font.capitalization == "smallcaps" ? "small-caps " : "normal ";
1482         // Canvas seems to only support bold yes or no
1483         css += (font.weight == self.Font.Bold
1484             || font.weight == self.Font.DemiBold
1485             || font.weight == self.Font.Black
1486             || font.bold) ? "bold " : "normal ";
1487         css += font.pixelSize !== Undefined
1488             ? font.pixelSize + "px "
1489             : (font.pointSize || 10) + "pt ";
1490         css += self.lineHeight !== Undefined ? self.lineHeight + "px " : " ";
1491         css += (font.family || "sans-serif") + " ";
1492         return css;
1493     }
1494
1495     this.Font = {
1496         // Capitalization
1497         MixedCase: "none",
1498         AllUppercase: "uppercase",
1499         AllLowercase: "lowercase",
1500         SmallCaps: "smallcaps",
1501         Capitalize: "capitalize",
1502         // Weight
1503         Light: "lighter",
1504         Normal: "normal",
1505         DemiBold: "600",
1506         Bold: "bold",
1507         Black: "bolder",
1508     }
1509
1510     this.Text = {
1511         // Wrap Mode
1512         NoWrap: 0,
1513         WordWrap: 1,
1514         WrapAnywhere: 2,
1515         Wrap: 3,
1516         // Horizontal-Alignment
1517         AlignLeft: "left",
1518         AlignRight: "right",
1519         AlignHCenter: "center",
1520         AlignJustify: "justify",
1521         // Style
1522         Normal: 0,
1523         Outline: 1,
1524         Raised: 2,
1525         Sunken: 3
1526     }
1527
1528     this.font = {};
1529     createSimpleProperty(this.font, "bold", { altParent: this });
1530     createSimpleProperty(this.font, "capitalization", { altParent: this });
1531     createSimpleProperty(this.font, "family", { altParent: this });
1532     createSimpleProperty(this.font, "italic", { altParent: this });
1533     createSimpleProperty(this.font, "letterSpacing", { altParent: this });
1534     createSimpleProperty(this.font, "pixelSize", { altParent: this });
1535     createSimpleProperty(this.font, "pointSize", { altParent: this });
1536     createSimpleProperty(this.font, "strikeout", { altParent: this });
1537     createSimpleProperty(this.font, "underline", { altParent: this });
1538     createSimpleProperty(this.font, "weight", { altParent: this });
1539     createSimpleProperty(this.font, "wordSpacing", { altParent: this });
1540
1541     createSimpleProperty(this, "color");
1542     createSimpleProperty(this, "text");
1543     createSimpleProperty(this, "lineHeight");
1544     createSimpleProperty(this, "wrapMode");
1545     createSimpleProperty(this, "horizontalAlignment");
1546     createSimpleProperty(this, "style");
1547     createSimpleProperty(this, "styleColor");
1548
1549     if (engine.renderMode == QMLRenderMode.DOM) {
1550         this.$onColorChanged.push(function(newVal) {
1551             this.$domElement.firstChild.style.color = newVal;
1552         });
1553         this.$onTextChanged.push(function(newVal) {
1554             this.$domElement.firstChild.innerHTML = newVal;
1555             this.$geometry.update();
1556         });
1557         this.font.$onPointSizeChanged.push(function(newVal) {
1558             this.$domElement.firstChild.style.fontSize = newVal + "pt";
1559             this.$geometry.update();
1560         });
1561         this.font.$onBoldChanged.push(function(newVal) {
1562             this.$domElement.firstChild.style.fontWeight =
1563                 this.font.weight !== Undefined ? this.font.weight :
1564                 newVal ? "bold" : "normal";
1565             this.$geometry.update();
1566         });
1567         this.font.$onCapitalizationChanged.push(function(newVal) {
1568             this.$domElement.firstChild.style.fontVariant =
1569                 newVal == "smallcaps" ? "small-caps" : "normal";
1570             newVal = newVal == "smallcaps" ? "none" : newVal;
1571             this.$domElement.firstChild.style.textTransform = newVal;
1572         });
1573         this.font.$onFamilyChanged.push(function(newVal) {
1574             this.$domElement.firstChild.style.fontFamily = newVal;
1575             this.$geometry.update();
1576         });
1577         this.font.$onItalicChanged.push(function(newVal) {
1578             this.$domElement.firstChild.style.fontStyle = newVal ? "italic" : "normal";
1579         });
1580         this.font.$onLetterSpacingChanged.push(function(newVal) {
1581             this.$domElement.firstChild.style.letterSpacing = newVal !== Undefined ? newVal + "px" : "";
1582         });
1583         this.font.$onPixelSizeChanged.push(function(newVal) {
1584             this.$domElement.firstChild.style.fontSize = newVal !== Undefined
1585                 ? newVal + "px "
1586                 : (this.font.pointSize || 10) + "pt";
1587             this.$geometry.update();
1588         });
1589         this.font.$onPointSizeChanged.push(function(newVal) {
1590             this.$domElement.firstChild.style.fontSize = this.font.pixelSize !== Undefined
1591                 ? this.font.pixelSize + "px "
1592                 : (newVal || 10) + "pt";
1593             this.$geometry.update();
1594         });
1595         this.font.$onStrikeoutChanged.push(function(newVal) {
1596             this.$domElement.firstChild.style.textDecoration = newVal
1597                 ? "line-through"
1598                 : this.font.underline
1599                 ? "underline"
1600                 : "normal";
1601         });
1602         this.font.$onUnderlineChanged.push(function(newVal) {
1603             this.$domElement.firstChild.style.textDecoration = this.font.strikeout
1604                 ? "line-through"
1605                 : newVal
1606                 ? "underline"
1607                 : "normal";
1608         });
1609         this.font.$onWeightChanged.push(function(newVal) {
1610             this.$domElement.firstChild.style.fontWeight =
1611                 newVal !== Undefined ? newVal :
1612                 this.font.bold ? "bold" : "normal";
1613         });
1614         this.font.$onWordSpacingChanged.push(function(newVal) {
1615             this.$domElement.firstChild.style.wordSpacing = newVal !== Undefined ? newVal + "px" : "";
1616         });
1617         this.$onLineHeightChanged.push(function(newVal) {
1618             this.$domElement.firstChild.style.lineHeight = newVal + "px";
1619         });
1620         this.$onWrapModeChanged.push(function(newVal) {
1621             switch (newVal) {
1622                 case 0:
1623                     this.$domElement.firstChild.style.whiteSpace = "pre";
1624                     break;
1625                 case 1:
1626                     this.$domElement.firstChild.style.whiteSpace = "pre-wrap";
1627                     break;
1628                 case 2:
1629                     this.$domElement.firstChild.style.whiteSpace = "pre-wrap";
1630                     this.$domElement.firstChild.style.wordBreak = "break-all";
1631                     break;
1632                 case 3:
1633                     this.$domElement.firstChild.style.whiteSpace = "pre-wrap";
1634                     this.$domElement.firstChild.style.wordWrap = "break-word";
1635             };
1636             // AlignJustify doesn't work with pre/pre-wrap, so we decide the
1637             // lesser of the two evils to be ignoring "\n"s inside the text.
1638             if (this.horizontalAlignment == "justify")
1639                 this.$domElement.firstChild.style.whiteSpace = "normal";
1640         });
1641         this.$onHorizontalAlignmentChanged.push(function(newVal) {
1642             this.$domElement.firstChild.style.textAlign = newVal;
1643             // AlignJustify doesn't work with pre/pre-wrap, so we decide the
1644             // lesser of the two evils to be ignoring "\n"s inside the text.
1645             if (newVal == "justify")
1646                 this.$domElement.firstChild.style.whiteSpace = "normal";
1647         });
1648         this.$onStyleChanged.push(function(newVal) {
1649             switch (newVal) {
1650                 case 0:
1651                     this.$domElement.firstChild.style.textShadow = "none";
1652                     break;
1653                 case 1:
1654                     var color = this.styleColor;
1655                     this.$domElement.firstChild.style.textShadow = "1px 0 0 " + color
1656                         + ", -1px 0 0 " + color
1657                         + ", 0 1px 0 " + color
1658                         + ", 0 -1px 0 " + color;
1659                     break;
1660                 case 2:
1661                     this.$domElement.firstChild.style.textShadow = "1px 1px 0 " + this.styleColor;
1662                     break;
1663                 case 3:
1664                     this.$domElement.firstChild.style.textShadow = "-1px -1px 0 " + this.styleColor;
1665             };
1666         });
1667         this.$onStyleColorChanged.push(function(newVal) {
1668             switch (this.style) {
1669                 case 0:
1670                     this.$domElement.firstChild.style.textShadow = "none";
1671                     break;
1672                 case 1:
1673                     this.$domElement.firstChild.style.textShadow = "1px 0 0 " + newVal
1674                         + ", -1px 0 0 " + newVal
1675                         + ", 0 1px 0 " + newVal
1676                         + ", 0 -1px 0 " + newVal;
1677                     break;
1678                 case 2:
1679                     this.$domElement.firstChild.style.textShadow = "1px 1px 0 " + newVal;
1680                     break;
1681                 case 3:
1682                     this.$domElement.firstChild.style.textShadow = "-1px -1px 0 " + newVal;
1683             };
1684         });
1685         this.$geometry.geometryChanged = function() {
1686             var w = this.$geometry.width,
1687                 h = this.$geometry.height;
1688             this.$domElement.style.width = w ? w + "px" : "auto";
1689             this.$domElement.style.height = h ? h + "px" : "auto";
1690             this.$domElement.style.top = (this.$geometry.top-this.parent.top) + "px";
1691             this.$domElement.style.left = (this.$geometry.left-this.parent.left) + "px";
1692         }
1693     } else {
1694         this.$onTextChanged.push(this.$geometry.update);
1695         this.font.$onFamilyChanged.push(this.$geometry.update);
1696         this.font.$onPointSizeChanged.push(this.$geometry.update);
1697     }
1698
1699     this.$init.push(function() {
1700         self.font.family = "sans-serif";
1701         self.font.pointSize = 10;
1702         self.wrapMode = self.Text.NoWrap;
1703         self.color = "black";
1704         self.text = "";
1705     });
1706
1707     // Define implicitHeight & implicitWidth
1708
1709     // Optimization: Remember last text
1710     // todo: Check for font size, family also
1711     var lastHText,
1712         lastH,
1713         lastHFont;
1714     function ihGetter(){
1715         if (evaluatingProperties.length !== 0) {
1716             var updater = evaluatingProperties[evaluatingProperties.length - 1];
1717             if (updater !== propertyUpdaters.indexOf(this.$geometry.update)
1718                 && this.$geometry.dependantProperties.indexOf(updater) == -1)
1719                 this.$geometry.dependantProperties.push(updater);
1720         }
1721
1722         // DOM
1723         if (engine.renderMode == QMLRenderMode.DOM) {
1724             return this.$domElement.offsetHeight;
1725         }
1726
1727         // Canvas
1728         // There is no height available in canvas element, figure out
1729         // other way
1730         var font = fontCss(this.font);
1731         if (lastHText == this.text && lastHFont == font) {
1732             return lastH;
1733         }
1734         var el = document.createElement("span"),
1735             height;
1736         el.style.font = font;
1737         el.innerText = this.text;
1738         document.body.appendChild(el);
1739         height = el.offsetHeight;
1740         document.body.removeChild(el);
1741         if (!height) {
1742             // Firefox doesn't support getting the height this way,
1743             // approximate from point size (full of win) :P
1744             if (this.font && this.font.pointSize) {
1745                 height = this.font.pointSize * 96 / 72;
1746             } else {
1747                 height = 10 * 96 / 72;
1748             }
1749
1750         }
1751         lastHText = this.text;
1752         lastHFont = font;
1753         lastH = height;
1754         return height;
1755     }
1756     setupGetter(this, "implicitHeight", ihGetter);
1757
1758     // Optimization: Remember last text
1759     // todo: Check for font size, family also
1760     var lastWText,
1761         lastW,
1762         lastWFont;
1763     function iwGetter() {
1764         if (evaluatingProperties.length !== 0) {
1765             var updater = evaluatingProperties[evaluatingProperties.length - 1];
1766             if (updater !== propertyUpdaters.indexOf(this.$geometry.update)
1767                 && this.$geometry.dependantProperties.indexOf(updater) == -1)
1768                 this.$geometry.dependantProperties.push(updater);
1769         }
1770
1771         var font = fontCss(this.font);
1772         if (lastWText == this.text && lastWFont == font) {
1773             return lastW;
1774         }
1775
1776         // DOM
1777         if (engine.renderMode == QMLRenderMode.DOM) {
1778             return this.$domElement.offsetWidth;
1779         }
1780
1781         // Canvas
1782         var width;
1783         width = engine.$getTextMetrics(this.text, font).width;
1784         lastWText = this.text;
1785         lastWFont = font;
1786         lastW = width;
1787         return width;
1788     }
1789     setupGetter(this, "implicitWidth", iwGetter);
1790
1791     this.$drawItem = function(c) {
1792         //descr("draw text", this, ["x", "y", "text",
1793         //                          "implicitWidth", "implicitHeight"]);
1794         c.save();
1795         c.font = fontCss(this.font);
1796         c.fillStyle = this.color;
1797         c.textAlign = "left";
1798         c.textBaseline = "top";
1799         c.fillText(this.text, this.left, this.top);
1800         c.restore();
1801     }
1802 }
1803
1804 function QMLRectangle(meta, parent, engine) {
1805     QMLItem.call(this, meta, parent, engine);
1806     var self = this;
1807
1808     createSimpleProperty(this, "color");
1809     this.border = {};
1810     createSimpleProperty(this.border, "color", { altParent: this });
1811     createSimpleProperty(this.border, "width", { altParent: this });
1812
1813     if (engine.renderMode == QMLRenderMode.DOM) {
1814         this.$onColorChanged.push(function(newVal) {
1815             this.$domElement.style.backgroundColor = newVal;
1816         });
1817         this.border.$onColorChanged.push(function(newVal) {
1818             this.$domElement.style.borderColor = newVal;
1819         });
1820         this.border.$onWidthChanged.push(function(newVal) {
1821             this.$domElement.style.borderWidth = newVal + "px";
1822             this.$domElement.style.borderStyle = newVal == 0 ? "none" : "solid";
1823             this.$geometry.update();
1824         });
1825     }
1826
1827     this.$init.push(function() {
1828         self.color = "white";
1829         self.border.color = "rgba(0,0,0,0)";
1830         self.border.width = 0;
1831     });
1832
1833     this.$drawItem = function(c) {
1834         //descr("draw rect", this, ["x", "y", "width", "height", "color"]);
1835         //descr("draw rect.border", this.border, ["color", "width"]);
1836
1837         c.save();
1838         c.fillStyle = this.color;
1839         c.fillRect(this.left, this.top, this.width, this.height);
1840         c.strokeStyle = this.border.color;
1841         c.lineWidth = this.border.width;
1842         c.strokeRect(this.left, this.top, this.width, this.height);
1843         c.restore();
1844     }
1845 }
1846
1847 function QMLRepeater(meta, parent, engine) {
1848     this.$applyChild = function(newVal) {
1849         this.delegate = newVal;
1850     }
1851
1852     QMLItem.call(this, meta, parent, engine);
1853     var self = this;
1854
1855     createSimpleProperty(this, "model");
1856     createSimpleProperty(this, "count");
1857     this.$completed = false;
1858
1859     this.$onModelChanged.push(function() {
1860         applyModel();
1861     });
1862
1863     this.$init.push(function() {
1864         self.model = 0;
1865         self.count = 0;
1866         self.$completed = true;
1867     });
1868
1869     function applyChildProperties(child) {
1870         createSimpleProperty(child, "index");
1871         child.index = new QMLBinding("parent.index");
1872         var model = self.model instanceof QMLListModel ? self.model.$model : self.model;
1873         for (var i in model.roleNames) {
1874             var func = (function(i) { return function() {
1875                     return model.data(child.index, model.roleNames[i]);
1876                     }
1877                 })(i);
1878             setupGetter(child, model.roleNames[i], func);
1879         }
1880         for (var i in child.$internChildren)
1881             applyChildProperties(child.$internChildren[i]);
1882         for (var i in child.$children)
1883             applyChildProperties(child.$children[i]);
1884     }
1885     function callOnCompleted(child) {
1886         if (child.Component.onCompleted)
1887             child.Component.onCompleted();
1888         for (var i in child.$internChildren)
1889             callOnCompleted(child.$internChildren[i]);
1890         for (var i in child.$children)
1891             callOnCompleted(child.$children[i]);
1892     }
1893     function insertChildren(startIndex, endIndex) {
1894         workingContext.push(self.$scope);
1895         for (var index = startIndex; index < endIndex; index++) {
1896             var newMeta = cloneObject(self.delegate);
1897             newMeta.id = newMeta.id + index;
1898             var newItem = construct(newMeta, self, engine);
1899
1900             if (engine.renderMode == QMLRenderMode.DOM)
1901                 newItem.$domElement.className += " " + self.delegate.id;
1902
1903             applyChildProperties(newItem);
1904             newItem.index = index;
1905             //TODO: Use parent's children, in order to make it completely transparent
1906             self.$children.splice(index, 0, newItem);
1907             if (self.$completed) {
1908                 // We don't call those on first creation, as they will be called
1909                 // by the regular creation-procedures at the right time.
1910                 for (var i = newItem.$init.length - 1; i>=0; i--)
1911                     newItem.$init[i]();
1912                 callOnCompleted(newItem);
1913             }
1914         }
1915         for (var i = endIndex; i < self.$children.length; i++) {
1916             self.$children[i].index = i;
1917         }
1918         workingContext.pop();
1919         self.count = self.$children.length;
1920     }
1921
1922     function applyModel() {
1923         var model = self.model instanceof QMLListModel ? self.model.$model : self.model;
1924         if (model instanceof JSItemModel) {
1925             model.dataChangedCallbacks.push(function(startIndex, endIndex) {
1926                 //TODO
1927             });
1928             model.rowsInsertedCallbacks.push(insertChildren);
1929             model.rowsMovedCallbacks.push(function(sourceStartIndex, sourceEndIndex, destinationIndex) {
1930                 var vals = self.$children.splice(sourceStartIndex, sourceEndIndex-sourceStartIndex);
1931                 for (var i = 0; i < vals.length; i++) {
1932                     self.$children.splice(destinationIndex + i, 0, vals[i]);
1933                 }
1934                 var smallestChangedIndex = sourceStartIndex < destinationIndex
1935                                         ? sourceStartIndex : destinationIndex;
1936                 for (var i = smallestChangedIndex; i < self.$children.length; i++) {
1937                     self.$children[i].index = i;
1938                 }
1939                 engine.$requestDraw();
1940             });
1941             model.rowsRemovedCallbacks.push(function(startIndex, endIndex) {
1942                 removeChildren(startIndex, endIndex);
1943                 for (var i = startIndex; i < self.$children.length; i++) {
1944                     self.$children[i].index = i;
1945                 }
1946                 self.count = self.$children.length;
1947                 engine.$requestDraw();
1948             });
1949             model.modelResetCallbacks.push(function() {
1950                 removeChildren(0, self.$children.length);
1951                 insertChildren(0, model.rowCount());
1952                 engine.$requestDraw();
1953             });
1954
1955             insertChildren(0, model.rowCount());
1956         } else if (typeof model == "number") {
1957             removeChildren(0, self.$children.length);
1958             insertChildren(0, model);
1959         }
1960     }
1961
1962     function removeChildren(startIndex, endIndex) {
1963         var removed = self.$children.splice(startIndex, endIndex - startIndex);
1964         for (var index in removed) {
1965             if (engine.renderMode == QMLRenderMode.DOM)
1966                 removed[index].parent.$domElement.removeChild(removed[index].$domElement);
1967             removeChildProperties(removed[index]);
1968         }
1969     }
1970     function removeChildProperties(child) {
1971         if (child.id)
1972             self.$scope.remId(child.id);
1973         for (var i in child.$ownPropertyUpdaters)
1974             propertyUpdaters[child.$ownPropertyUpdaters[i]] = undefined;
1975         for (var i in child.$children)
1976             removeChildProperties(child.$children[i])
1977         for (var i in child.$internChildren)
1978             removeChildProperties(child.$internChildren[i])
1979     }
1980 }
1981
1982 function QMLListModel(meta, parent, engine) {
1983     QMLBaseObject.call(this, meta, parent, engine);
1984     var self = this;
1985
1986     this.$model = new JSItemModel();
1987
1988     this.$model.data = function(index, role) {
1989         return self.$children[index][role];
1990     }
1991     this.$model.rowCount = function() {
1992         return self.$children.length;
1993     }
1994     var roleNames = [];
1995     for (var i in meta.$children[0]) {
1996         if (i != "id" && i != "index" && i[0] != "$")
1997             roleNames.push(i);
1998     }
1999     this.$model.setRoleNames(roleNames);
2000
2001     this.append = function(dict) {
2002         this.$children.push(dict);
2003         this.$model.emitRowsInserted(this.$children.length-1, this.$children.length);
2004     }
2005     this.clear = function() {
2006         this.$children = [];
2007         this.$model.emitModelReset();
2008     }
2009     this.get = function(index) {
2010         return this.$children[index];
2011     }
2012     this.insert = function(index, dict) {
2013         this.$children.splice(index, 0, dict);
2014         this.$model.emitRowsInserted(index, index+1);
2015     }
2016     this.move = function(from, to, n) {
2017         var vals = this.$children.splice(from, n);
2018         for (var i = 0; i < vals.length; i++) {
2019             this.$children.splice(to + i, 0, vals[i]);
2020         }
2021         this.$model.emitRowsMoved(from, from+n, to);
2022     }
2023     this.remove = function(index) {
2024         this.$children.splice(index, 1);
2025         this.$model.emitRowsRemoved(index, index+1);
2026     }
2027     this.set = function(index, dict) {
2028         this.$children[index] = dict;
2029         engine.$requestDraw();
2030     }
2031     this.setProperty = function(index, property, value) {
2032         this.$children[index][property] = value;
2033         engine.$requestDraw();
2034     }
2035 }
2036
2037 function QMLListElement(meta, parent, engine) {
2038     // QMLListElement can't have children and needs special handling of properties
2039     // thus we don't use QMLBaseObject for it
2040     var values = [];
2041
2042     for (i in meta) {
2043         if (i[0] != "$") {
2044             values[i] = meta[i];
2045             setupGetterSetter(this, i,
2046                 (function(name){
2047                     return function() {
2048                         return values[name];
2049                     }
2050                 })(i),
2051                 (function(name) {
2052                     return function(newVal) {
2053                         val = newVal;
2054                         parent.$model.emitDataChanged(this.index, this.index);
2055                     }
2056                 })(name)
2057             );
2058         }
2059     }
2060
2061     this.$init = [function() {
2062         applyProperties(meta, this);
2063     }];
2064 }
2065
2066 function QMLImage(meta, parent, engine) {
2067     QMLItem.call(this, meta, parent, engine);
2068     var img = new Image(),
2069         self = this;
2070
2071     if (engine.renderMode == QMLRenderMode.DOM) {
2072         img.style.width = "100%";
2073         img.style.height = "100%";
2074         img.style.position = "absolute";
2075         this.$domElement.appendChild(img);
2076     }
2077
2078     // Exports.
2079     this.Image = {
2080         // fillMode 
2081         Stretch: 1,
2082         PreserveAspectFit: 2,
2083         PreserveAspectCrop: 3,
2084         Tile: 4,
2085         TileVertically: 5,
2086         TileHorizontally: 6,
2087         // status
2088         Null: 1,
2089         Ready: 2,
2090         Loading: 3,
2091         Error: 4
2092     }
2093
2094     // no-op properties
2095     createSimpleProperty(this, "asynchronous");
2096     createSimpleProperty(this, "cache");
2097     createSimpleProperty(this, "smooth");
2098
2099     createSimpleProperty(this, "fillMode");
2100     createSimpleProperty(this, "mirror");
2101     createSimpleProperty(this, "progress");
2102     createSimpleProperty(this, "source");
2103     createSimpleProperty(this, "status");
2104
2105     this.sourceSize = {};
2106
2107     createSimpleProperty(this.sourceSize, "width", { altParent: this });
2108     createSimpleProperty(this.sourceSize, "height", { altParent: this });
2109
2110     this.$init.push(function() {
2111         self.asynchronous = true;
2112         self.cache = true;
2113         self.smooth = true;
2114         self.fillMode = self.Image.Stretch;
2115         self.mirror = false;
2116         self.progress = 0;
2117         self.source = "";
2118         self.status = self.Image.Null;
2119         self.sourceSize.width = 0;
2120         self.sourceSize.height = 0;
2121     });
2122
2123     // Actual size of image.
2124     // todo: bug; implicitWidth|height is not defined this way in docs
2125     function iwGetter() {
2126             return img.naturalWidth;
2127     }
2128     setupGetter(this, "implicitWidth", iwGetter);
2129
2130     function ihGetter() {
2131         return img.naturalHeight;
2132     }
2133     setupGetter(this, "implicitHeight", ihGetter);
2134
2135     // Bind status to img element
2136     img.onload = function() {
2137         self.progress = 1;
2138         self.status = self.Image.Ready;
2139         // todo: it is not right to set these
2140         self.sourceSize.width = img.naturalWidth;
2141         self.sourceSize.height = img.naturalHeight;
2142         self.$geometry.update();
2143     }
2144     img.onerror = function() {
2145         self.status = self.Image.Error;
2146     }
2147
2148     // Use extended changesignal capabilities to keep track of source
2149     this.$onSourceChanged.push(function(val) {
2150         self.progress = 0;
2151         self.status = self.Image.Loading;
2152         img.src = engine.$resolvePath(val);
2153     });
2154
2155     this.$drawItem = function(c) {
2156         //descr("draw image", this, ["left", "top", "width", "height", "source"]);
2157
2158         if (this.fillMode != this.Image.Stretch) {
2159             console.log("Images support only Image.Stretch fillMode currently");
2160         }
2161         if (this.status == this.Image.Ready) {
2162             c.save();
2163             c.drawImage(img, this.left, this.top, this.width, this.height);
2164             c.restore();
2165         } else {
2166             console.log("Waiting for image to load");
2167         }
2168     }
2169 }
2170
2171 function QMLBorderImage(meta, parent, engine) {
2172     QMLItem.call(this, meta, parent, engine);
2173     var self = this;
2174
2175     if (engine.renderMode == QMLRenderMode.Canvas)
2176         var img = new Image();
2177
2178     this.BorderImage = {
2179         // tileMode
2180         Stretch: "stretch",
2181         Repeat: "repeat",
2182         Round: "round",
2183         // status
2184         Null: 1,
2185         Ready: 2,
2186         Loading: 3,
2187         Error: 4
2188     }
2189
2190     createSimpleProperty(this, "source");
2191     createSimpleProperty(this, "status");
2192     this.border = {};
2193     createSimpleProperty(this.border, "left", { altParent: this });
2194     createSimpleProperty(this.border, "right", { altParent: this });
2195     createSimpleProperty(this.border, "top", { altParent: this });
2196     createSimpleProperty(this.border, "bottom", { altParent: this });
2197     createSimpleProperty(this, "horizontalTileMode");
2198     createSimpleProperty(this, "verticalTileMode");
2199
2200     this.$init.push(function() {
2201         self.source = "";
2202         self.status = self.BorderImage.Null
2203         self.border.left = 0;
2204         self.border.right = 0;
2205         self.border.top = 0;
2206         self.border.bottom = 0;
2207         self.horizontalTileMode = self.BorderImage.Stretch;
2208         self.verticalTileMode = self.BorderImage.Stretch;
2209     });
2210
2211     if (engine.renderMode == QMLRenderMode.DOM) {
2212         this.$onSourceChanged.push(function() {
2213             this.$domElement.style.borderImageSource = "url(" + engine.$resolvePath(this.source) + ")";
2214         });
2215         this.border.$onLeftChanged.push(updateBorder);
2216         this.border.$onRightChanged.push(updateBorder);
2217         this.border.$onTopChanged.push(updateBorder);
2218         this.border.$onBottomChanged.push(updateBorder);
2219         this.$onHorizontalTileModeChanged.push(updateBorder);
2220         this.$onVerticalTileModeChanged.push(updateBorder);
2221     } else {
2222         this.$onSourceChanged.push(function(val) {
2223             self.progress = 0;
2224             self.status = self.BorderImage.Loading;
2225             img.src = engine.$resolvePath(val);
2226         });
2227         img.onload = function() {
2228             self.progress = 1;
2229             self.status = self.BorderImage.Ready;
2230             engine.$requestDraw();
2231         }
2232         img.onerror = function() {
2233             self.status = self.BorderImage.Error;
2234         }
2235     }
2236
2237     function updateBorder() {
2238         this.$domElement.style.MozBorderImageSource = "url(" + engine.$resolvePath(this.source) + ")";
2239         this.$domElement.style.MozBorderImageSlice = this.border.top + " "
2240                                                 + this.border.right + " "
2241                                                 + this.border.bottom + " "
2242                                                 + this.border.left;
2243         this.$domElement.style.MozBorderImageRepeat = this.horizontalTileMode + " "
2244                                                     + this.verticalTileMode;
2245         this.$domElement.style.MozBorderImageWidth = this.border.top + " "
2246                                                 + this.border.right + " "
2247                                                 + this.border.bottom + " "
2248                                                 + this.border.left;
2249
2250         this.$domElement.style.webkitBorderImageSource = "url(" + engine.$resolvePath(this.source) + ")";
2251         this.$domElement.style.webkitBorderImageSlice = this.border.top + " "
2252                                                 + this.border.right + " "
2253                                                 + this.border.bottom + " "
2254                                                 + this.border.left;
2255         this.$domElement.style.webkitBorderImageRepeat = this.horizontalTileMode + " "
2256                                                     + this.verticalTileMode;
2257         this.$domElement.style.webkitBorderImageWidth = this.border.top + " "
2258                                                 + this.border.right + " "
2259                                                 + this.border.bottom + " "
2260                                                 + this.border.left;
2261
2262         this.$domElement.style.OBorderImageSource = "url(" + engine.$resolvePath(this.source) + ")";
2263         this.$domElement.style.OBorderImageSlice = this.border.top + " "
2264                                                 + this.border.right + " "
2265                                                 + this.border.bottom + " "
2266                                                 + this.border.left;
2267         this.$domElement.style.OBorderImageRepeat = this.horizontalTileMode + " "
2268                                                     + this.verticalTileMode;
2269         this.$domElement.style.OBorderImageWidth = this.border.top + "px "
2270                                                 + this.border.right + "px "
2271                                                 + this.border.bottom + "px "
2272                                                 + this.border.left + "px";
2273
2274         this.$domElement.style.borderImageSlice = this.border.top + " "
2275                                                 + this.border.right + " "
2276                                                 + this.border.bottom + " "
2277                                                 + this.border.left;
2278         this.$domElement.style.borderImageRepeat = this.horizontalTileMode + " "
2279                                                     + this.verticalTileMode;
2280         this.$domElement.style.borderImageWidth = this.border.top + "px "
2281                                                 + this.border.right + "px "
2282                                                 + this.border.bottom + "px "
2283                                                 + this.border.left + "px";
2284     }
2285
2286     this.$drawItem = function(c) {
2287         if (this.horizontalTileMode != this.BorderImage.Stretch || this.verticalTileMode != this.BorderImage.Stretch) {
2288             console.log("BorderImages support only BorderImage.Stretch tileMode currently with the canvas-backend.");
2289         }
2290         if (this.status == this.BorderImage.Ready) {
2291             c.save();
2292             c.drawImage(img, 0, 0, this.border.left, this.border.top,
2293                         this.left, this.top, this.border.left, this.border.top);
2294             c.drawImage(img, img.naturalWidth - this.border.right, 0,
2295                         this.border.right, this.border.top,
2296                         this.left + this.width - this.border.right, this.top,
2297                         this.border.right, this.border.top);
2298             c.drawImage(img, 0, img.naturalHeight - this.border.bottom,
2299                         this.border.left, this.border.bottom,
2300                         this.left, this.top + this.height - this.border.bottom,
2301                         this.border.left, this.border.bottom);
2302             c.drawImage(img, img.naturalWidth - this.border.right, img.naturalHeight - this.border.bottom,
2303                         this.border.right, this.border.bottom,
2304                         this.left + this.width - this.border.right,
2305                         this.top + this.height - this.border.bottom,
2306                         this.border.right, this.border.bottom);
2307
2308             c.drawImage(img, 0, this.border.top,
2309                         this.border.left, img.naturalHeight - this.border.bottom - this.border.top,
2310                         this.left, this.top + this.border.top,
2311                         this.border.left, this.height - this.border.bottom - this.border.top);
2312             c.drawImage(img, this.border.left, 0,
2313                         img.naturalWidth - this.border.right - this.border.left, this.border.top,
2314                         this.left + this.border.left, this.top,
2315                         this.width - this.border.right - this.border.left, this.border.top);
2316             c.drawImage(img, img.naturalWidth - this.border.right, this.border.top,
2317                         this.border.right, img.naturalHeight - this.border.bottom - this.border.top,
2318                         this.right - this.border.right, this.top + this.border.top,
2319                         this.border.right, this.height - this.border.bottom - this.border.top);
2320             c.drawImage(img, this.border.left, img.naturalHeight - this.border.bottom,
2321                         img.naturalWidth - this.border.right - this.border.left, this.border.bottom,
2322                         this.left + this.border.left, this.bottom - this.border.bottom,
2323                         this.width - this.border.right - this.border.left, this.border.bottom);
2324             c.restore();
2325         } else {
2326             console.log("Waiting for image to load");
2327         }
2328     }
2329 }
2330
2331 function QMLMouseArea(meta, parent, engine) {
2332     QMLItem.call(this, meta, parent, engine);
2333     var self = this;
2334
2335     if (engine.renderMode == QMLRenderMode.DOM)
2336         this.$domElement.style.pointerEvents = "all";
2337
2338     createSimpleProperty(this, "acceptedButtons");
2339     createSimpleProperty(this, "enabled");
2340     createSimpleProperty(this, "hoverEnabled");
2341     createFunction(this, "onClicked");
2342     createFunction(this, "onEntered");
2343     createFunction(this, "onExited");
2344     createSimpleProperty(this, "hovered");
2345
2346     this.$init.push(function() {
2347         self.acceptedButtons = QMLGlobalObject.Qt.LeftButton;
2348         self.enabled = true;
2349         self.hoverEnabled = false;
2350         self.hovered = false;
2351     });
2352
2353     if (engine.renderMode == QMLRenderMode.DOM) {
2354         this.$domElement.onclick = function(e) {
2355             var mouse = {
2356                 accepted: true,
2357                 button: e.button == 0 ? QMLGlobalObject.Qt.LeftButton :
2358                         e.button == 1 ? QMLGlobalObject.Qt.RightButton :
2359                         e.button == 2 ? QMLGlobalObject.Qt.MiddleButton :
2360                         0,
2361                 modifiers: (e.ctrlKey * QMLGlobalObject.Qt.CtrlModifier)
2362                         | (e.altKey * QMLGlobalObject.Qt.AltModifier)
2363                         | (e.shiftKey * QMLGlobalObject.Qt.ShiftModifier)
2364                         | (e.metaKey * QMLGlobalObject.Qt.MetaModifier),
2365                 x: (e.offsetX || e.layerX),
2366                 y: (e.offsetY || e.layerY)
2367             };
2368
2369             if (self.enabled) {
2370                 // Dispatch mouse event
2371                 self.mouse = mouse;
2372                 self.onClicked();
2373                 self.mouse = Undefined;
2374                 engine.$requestDraw();
2375             }
2376         }
2377         this.$domElement.onmouseover = function(e) {
2378             if (self.hoverEnabled) {
2379                 self.hovered = true;
2380                 if (self.onEntered)
2381                     self.onEntered();
2382             }
2383         }
2384         this.$domElement.onmouseout = function(e) {
2385             if (self.hoverEnabled) {
2386                 self.hovered = false;
2387                 if (self.onExited)
2388                 self.onExited();
2389             }
2390         }
2391     } else {
2392         engine.mouseAreas.push(this);
2393     }
2394 }
2395
2396 function QMLDocument(meta, parent, engine) {
2397
2398     var doc,
2399         // The only item in this document
2400         item,
2401         // id's in item scope
2402         ids = Object.create(engine.$getGlobalObj());
2403
2404     // todo: imports
2405
2406     if (meta.$children.length != 1) {
2407         console.log("QMLDocument: children.length != 1");
2408     }
2409
2410     // Build parent
2411     parent = {};
2412     parent.left = 0;
2413     parent.top = 0;
2414     parent.$domElement = engine.rootElement;
2415
2416     var scope = {
2417         // Get scope
2418         get: function() {
2419             return ids;
2420         },
2421         // Get base/id scope
2422         getIdScope: function() {
2423             return ids;
2424         },
2425         // Define id
2426         defId: function(name, obj) {
2427             if (ids[name]) {
2428                 console.log("QMLDocument: overriding " + name
2429                             + " with object", obj);
2430             }
2431             ids[name] = obj;
2432         },
2433         // Remove id
2434         remId: function(name) {
2435             ids[name] = undefined;
2436         }
2437     };
2438     workingContext.push(scope);
2439
2440     doc = new QMLItem(meta, parent, engine);
2441     item = doc.$children[0];
2442
2443     workingContext.pop();
2444
2445     function heightGetter() {
2446         return item.height; 
2447     }
2448     setupGetter(doc, "height", heightGetter);
2449
2450     function widthGetter() {
2451         return item.width;
2452     }
2453     setupGetter(doc, "width", widthGetter);
2454
2455
2456     doc.$draw = function(c) {
2457         c.save();
2458         c.fillStyle = "pink";
2459         c.fillRect(0, 0, c.canvas.width, c.canvas.height);
2460         c.restore();
2461         item.$draw(c);
2462     }
2463     doc.$init = function() {
2464         if (engine.renderMode == QMLRenderMode.DOM) {
2465             engine.rootElement.innerHTML = "";
2466             engine.rootElement.appendChild(doc.$domElement);
2467         }
2468         workingContext.push(scope);
2469         // The init-methods are called in reverse order for the $init
2470         // from QMLBaseObject, where explicitly-set-properties are applied,
2471         // needs to be called last.
2472         for (var i = item.$init.length - 1; i>=0; i--)
2473             item.$init[i]();
2474         workingContext.pop();
2475
2476         if (engine.renderMode == QMLRenderMode.DOM) {
2477             doc.$domElement.style.position = "relative";
2478             doc.$domElement.style.top = "0";
2479             doc.$domElement.style.left = "0";
2480             doc.$domElement.style.overflow = "hidden";
2481             doc.$domElement.style.width = item.width + "px";
2482             doc.$domElement.style.height = item.height + "px";
2483         }
2484     }
2485     // todo: legacy. remove
2486     doc.draw = doc.$draw;
2487     doc.getHeight = function() { return doc.height };
2488     doc.getWidth = function() { return doc.width };
2489
2490     return doc; // todo: return doc instead of item
2491
2492 }
2493
2494 function QMLTimer(meta, parent, engine) {
2495     QMLBaseObject.call(this, meta, parent, engine);
2496     var prevTrigger,
2497         self = this;
2498
2499     createSimpleProperty(this, "interval");
2500     createSimpleProperty(this, "repeat");
2501     createSimpleProperty(this, "running");
2502     createSimpleProperty(this, "triggeredOnStart");
2503
2504     this.$init.push(function() {
2505         self.interval = 1000;
2506         self.repeat = false;
2507         self.running = false;
2508         self.triggeredOnStart = false;
2509     });
2510
2511     // Create trigger as simple property. Reading the property triggers
2512     // the function!
2513     createFunction(this, "onTriggered");
2514
2515     engine.$addTicker(ticker);
2516     function ticker(now, elapsed) {
2517         if (self.running) {
2518             if (now - prevTrigger >= self.interval) {
2519                 prevTrigger = now;
2520                 trigger();
2521             }
2522         }
2523     }
2524
2525     this.start = function() {
2526         if (!this.running) {
2527             this.running = true;
2528             prevTrigger = (new Date).getTime();
2529             if (this.triggeredOnStart) {
2530                 trigger();
2531             }
2532         }
2533     }
2534     this.stop = function() {
2535         if (this.running) {
2536             this.running = false;
2537         }
2538     }
2539     this.restart = function() {
2540         this.stop();
2541         this.start();
2542     }
2543
2544     function trigger() {
2545         // Trigger this.
2546         self.onTriggered();
2547
2548         engine.$requestDraw();
2549     }
2550
2551     engine.$registerStart(function() {
2552         if (self.running) {
2553             self.running = false; // toggled back by self.start();
2554             self.start();
2555         }
2556     });
2557
2558     engine.$registerStop(function() {
2559         self.stop();
2560     });
2561 }
2562
2563 function QMLAnimation(meta, parent, engine) {
2564     QMLBaseObject.call(this, meta, parent, engine);
2565     var self = this;
2566
2567     // Exports
2568     this.Animation = {
2569         Infinite: -1
2570     };
2571
2572     createSimpleProperty(this, "alwaysRunToEnd");
2573     createSimpleProperty(this, "loops");
2574     createSimpleProperty(this, "paused");
2575     createSimpleProperty(this, "running");
2576
2577     this.$init.push(function() {
2578         self.alwaysRunToEnd = false;
2579         self.loops = 1;
2580         self.paused = false;
2581         self.running = false;
2582     });
2583
2584     // Methods
2585     this.restart = function() {
2586         this.stop();
2587         this.start();
2588     };
2589     // To be overridden
2590     this.complete = unboundMethod;
2591     this.pause = unboundMethod;
2592     this.resume = unboundMethod;
2593     this.start = unboundMethod;
2594     this.stop = unboundMethod;
2595 }
2596
2597 function QMLSequentialAnimation(meta, parent, engine) {
2598     QMLAnimation.call(this, meta, parent, engine);
2599     var curIndex,
2600         passedLoops,
2601         i,
2602         self = this;
2603
2604     function nextAnimation(proceed) {
2605
2606         var anim;
2607         if (self.running && !proceed) {
2608             curIndex++;
2609             if (curIndex < self.$children.length) {
2610                 anim = self.$children[curIndex];
2611                 console.log("nextAnimation", self, curIndex, anim);
2612                 descr("", anim, ["target"]);
2613                 anim.from = anim.target[anim.property];
2614                 anim.start();
2615             } else {
2616                 passedLoops++;
2617                 if (passedLoops >= self.loops) {
2618                     self.complete();
2619                 } else {
2620                     curIndex = -1;
2621                     nextAnimation();
2622                 }
2623             }
2624         }
2625     }
2626
2627     for (i = 0; i < this.$children.length; i++) {
2628         this.$children[i].$onRunningChanged.push(nextAnimation);
2629     }
2630     // $children is already constructed,
2631
2632
2633     this.start = function() {
2634         if (!this.running) {
2635             this.running = true;
2636             curIndex = -1;
2637             passedLoops = 0;
2638             nextAnimation();
2639         }
2640     }
2641     this.stop = function() {
2642         if (this.running) {
2643             this.running = false;
2644             if (curIndex < this.$children.length) {
2645                 this.$children[curIndex].stop();
2646             }
2647         }
2648     }
2649
2650     this.complete = function() {
2651         if (this.running) {
2652             if (curIndex < this.$children.length) {
2653                 // Stop current animation
2654                 this.$children[curIndex].stop();
2655             }
2656             this.running = false;
2657         }
2658     }
2659
2660     engine.$registerStart(function() {
2661         if (self.running) {
2662             self.running = false; // toggled back by start();
2663             self.start();
2664         }
2665     });
2666     engine.$registerStop(function() {
2667         self.stop();
2668     });
2669 };
2670
2671 function QMLPropertyAnimation(meta, parent, engine) {
2672     QMLAnimation.call(this, meta, parent, engine);
2673     var self = this;
2674
2675     // Exports
2676     this.Easing = {
2677         Linear: 1,
2678         InOutCubic: 2
2679         // TODO: rest and support for them.
2680     };
2681
2682     createSimpleProperty(this, "duration");
2683     this.easing = {};
2684     createSimpleProperty(this.easing, "type", { altParent: this });
2685     createSimpleProperty(this.easing, "amplitude", { altParent: this });
2686     createSimpleProperty(this.easing, "overshoot", { altParent: this });
2687     createSimpleProperty(this.easing, "period", { altParent: this });
2688     createSimpleProperty(this, "from");
2689     createSimpleProperty(this, "properties");
2690     createSimpleProperty(this, "property");
2691     createSimpleProperty(this, "target");
2692     createSimpleProperty(this, "targets");
2693     createSimpleProperty(this, "to");
2694
2695     this.$init.push(function() {
2696         self.duration = 250;
2697         self.easing.type = self.Easing.Linear;
2698         self.from = 0;
2699         self.properties = [];
2700         self.targets = [];
2701         self.to = 0;
2702     });
2703 }
2704
2705 function QMLNumberAnimation(meta, parent, engine) {
2706     QMLPropertyAnimation.call(this, meta, parent, engine);
2707     var tickStart,
2708         self = this;
2709
2710     engine.$addTicker(ticker);
2711
2712     function curve(place) {
2713         switch(self.easing.type) {
2714
2715          case self.Easing.InOutCubic:
2716             // todo: better estimate
2717             return 0.5 + Math.sin(place*Math.PI - Math.PI / 2) / 2
2718          default:
2719             console.log("Unsupported animation type: ", self.easing.type);
2720          case self.Easing.Linear:
2721             return place;
2722         }
2723     }
2724
2725     function ticker(now, elapsed) {
2726         if (self.running) {
2727             if (now > tickStart + self.duration) {
2728                 self.complete();
2729             } else {
2730                 var at = (now - tickStart) / self.duration,
2731                     value = curve(at) * (self.to - self.from) + self.from;
2732                 self.target[self.property] = new QMLTransientValue(value);
2733                 engine.$requestDraw();
2734             }
2735
2736         }
2737     }
2738
2739     // Methods
2740     this.start = function() {
2741         if (!this.running) {
2742             this.running = true;
2743             tickStart = (new Date).getTime();
2744         }
2745     }
2746
2747     this.stop = function() {
2748         if (this.running) {
2749             this.running = false;
2750         }
2751     }
2752
2753     this.complete = function() {
2754         if (this.running) {
2755             this.target[this.property] = this.to;
2756             this.stop();
2757             engine.$requestDraw();
2758         }
2759     }
2760 }
2761
2762
2763 //------------DOM-only-Elements------------
2764
2765 function QMLTextInput(meta, parent, engine) {
2766     QMLItem.call(this, meta, parent, engine);
2767
2768     if (engine.renderMode == QMLRenderMode.Canvas) {
2769         console.log("TextInput-type is only supported within the DOM-backend.");
2770         return;
2771     }
2772
2773     var self = this;
2774
2775     this.$domElement.innerHTML = "<input type=\"text\"/>"
2776     this.$domElement.firstChild.style.pointerEvents = "auto";
2777     // In some browsers text-inputs have a margin by default, which distorts
2778     // the positioning, so we need to manually set it to 0.
2779     this.$domElement.firstChild.style.margin = "0";
2780
2781     createSimpleProperty(this, "text", "");
2782     createFunction(this, "onAccepted");
2783
2784     function iwGetter() {
2785         return this.$domElement.firstChild.offsetWidth;
2786     }
2787     setupGetter(this, "implicitWidth", iwGetter);
2788
2789     function ihGetter() {
2790         return this.$domElement.firstChild.offsetHeight;
2791     }
2792     setupGetter(this, "implicitHeight", ihGetter);
2793
2794     this.$geometry.geometryChanged = function() {
2795         var w = this.width,
2796             h = this.height,
2797             d = this.$domElement.firstChild.offsetHeight
2798                 - window.getComputedStyle(this.$domElement.firstChild).height.slice(0,-2);
2799         this.$domElement.style.width = w + "px";
2800         this.$domElement.style.height = h + "px";
2801         this.$domElement.style.top = (this.$geometry.top-this.parent.top) + "px";
2802         this.$domElement.style.left = (this.$geometry.left-this.parent.left) + "px";
2803         // we need to subtract the width of the border and the padding so that
2804         // the text-input has the width we want
2805         if (this.$geometry.width !== Undefined)
2806             this.$domElement.firstChild.style.width = this.$geometry.width - d + "px";
2807         if (this.$geometry.height !== Undefined)
2808             this.$domElement.firstChild.style.height = this.$geometry.height - d + "px";
2809     }
2810
2811     this.$onTextChanged.push(function() {
2812         this.$domElement.firstChild.value = this.text;
2813     });
2814
2815     this.$domElement.firstChild.onkeydown = function(e) {
2816         if (e.keyCode == 13) //Enter pressed
2817             self.onAccepted();
2818     }
2819
2820     function updateValue(e) {
2821         if (self.text != self.$domElement.firstChild.value) {
2822             self.text = self.$domElement.firstChild.value;
2823         }
2824     }
2825
2826     this.$domElement.firstChild.oninput = updateValue;
2827     this.$domElement.firstChild.onpropertychanged = updateValue;
2828 }
2829
2830 function QMLButton(meta, parent, engine) {
2831     if (engine.renderMode == QMLRenderMode.Canvas) {
2832         console.log("Button-type is only supported within the DOM-backend. Use Rectangle + MouseArea instead.");
2833         QMLItem.call(this, meta, parent, engine);
2834         return;
2835     }
2836
2837     this.$domElement = document.createElement("button");
2838     QMLItem.call(this, meta, parent, engine);
2839     var self = this;
2840
2841     this.$domElement.style.pointerEvents = "auto";
2842     this.$domElement.innerHTML = "<span></span>";
2843
2844     createSimpleProperty(this, "text", "");
2845     createFunction(this, "onClicked");
2846
2847     this.$onTextChanged.push(function(newVal) {
2848         this.$domElement.firstChild.innerHTML = newVal;
2849     });
2850
2851     this.$domElement.onclick = function(e) {
2852         self.onClicked();
2853     }
2854 }
2855
2856 })();