diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .*.swp .*.swo +.arcconfig diff --git a/lib/qt.js b/lib/qt.js --- a/lib/qt.js +++ b/lib/qt.js @@ -1607,6 +1607,8 @@ }; +function parse($TEXT, options) { + /* -----[ Parser (constants) ]----- */ var UNARY_PREFIX = makePredicate([ @@ -1655,8 +1657,6 @@ /* -----[ Parser ]----- */ -function parse($TEXT, options) { - options = defaults(options, { strict : false, filename : null, @@ -4509,6 +4509,12 @@ }, "name": function(src) { return bindout(tree, src); + }, + "string": function(src) { + return src; + }, + "num": function(src) { + return src; } }; @@ -7030,7 +7036,34 @@ Key_Play: 250, Key_Sleep: 95, Key_Zoom: 251, - Key_Cancel: 3 + Key_Cancel: 3, + // CursorShape + ArrowCursor: 0, + UpArrowCursor: 1, + CrossCursor: 2, + WaitCursor: 3, + IBeamCursor: 4, + SizeVerCursor: 5, + SizeHorCursor: 6, + SizeBDiagCursor: 7, + SizeFDiagCursor: 8, + SizeAllCursor: 9, + BlankCursor: 10, + SplitVCursor: 11, + SplitHCursor: 12, + PointingHandCursor: 13, + ForbiddenCursor: 14, + WhatsThisCursor: 15, + BusyCursor: 16, + OpenHandCursor: 17, + ClosedHandCursor: 18, + DragCopyCursor: 19, + DragMoveCursor: 20, + DragLinkCursor: 21, + LastCursor: 21, //DragLinkCursor, + BitmapCursor: 24, + CustomCursor: 25 + } /** @@ -7304,21 +7337,32 @@ if (meta.object.$class in constructors) { item = new constructors[meta.object.$class](meta); } else if (cTree = engine.loadComponent(meta.object.$class)) { - if (cTree.$children.length !== 1) + if (cTree.$children.length !== 1) { console.error("A QML component must only contain one root element!"); - var item = (new QMLComponent({ object: cTree, context: meta.context })).createObject(meta.parent); - + } + + var component = new QMLComponent({ object: cTree, context: meta.context }); + component.finalizeImports(); + + var item = component.createObject(meta.parent); + // Recall QMLBaseObject with the meta of the instance in order to get property // definitions, etc. from the instance QMLBaseObject.call(item, meta); + if (typeof item.dom != 'undefined') item.dom.className += " " + meta.object.$class + (meta.object.id ? " " + meta.object.id : ""); + var dProp; // Handle default properties } else { console.log("No constructor found for " + meta.object.$class); return; } + if (!global.qmlEngine.doc) { + global.qmlEngine.doc = item; + } + // id if (meta.object.id) meta.context[meta.object.id] = item; @@ -7557,7 +7601,7 @@ if (source != null) { global.qmlEngine = new QMLEngine(); - qmlEngine.loadFile(source); + qmlEngine.loadFile(source, null); qmlEngine.start(); break ; } @@ -7625,6 +7669,12 @@ this.getAttributes = function() { return (attributes); } } +registerQmlType({ + module: 'QtQuick', + name: 'QtObject', + versions: /.*/, + constructor: QMLBaseObject +}); // TODO function QMLColor(val) { @@ -7711,36 +7761,40 @@ if (!qrc.includesFile(file)) { var src = getUrlContents(file); - console.log('loading file', file); - qrc[file] = qmlparse(src); + if (src) { + console.log('Loading file [', file,']'); + qrc[file] = qmlparse(src); + }else { + console.log('Can nor load file [', file,']'); + } } } - + // Load file, parse and construct (.qml or .qml.js) - this.loadFile = function(file) { + this.loadFile = function(file, parentComponent) { var tree; basePath = this.pathFromFilepath(file); this.basePath = basePath; this.ensureFileIsLoadedInQrc(file); tree = convertToEngine(qrc[file]); - this.loadQMLTree(tree); + return this.loadQMLTree(tree, parentComponent); } // parse and construct qml this.loadQML = function(src) { - this.loadQMLTree(parseQML(src)); + this.loadQMLTree(parseQML(src), null); } - this.loadQMLTree = function(tree) { + this.loadQMLTree = function(tree, parentComponent) { engine = this; if (options.debugTree) { options.debugTree(tree); } // Create and initialize objects - var component = new QMLComponent({ object: tree, parent: null }); - doc = component.createObject(null); + var component = new QMLComponent({ object: tree, parent: parentComponent }); + doc = component.createObject(parentComponent); component.finalizeImports(); this.$initializePropertyBindings(); @@ -7750,10 +7804,12 @@ for (var i in this.completedSignals) { this.completedSignals[i](); } + + return component; } this.rootContext = function() { - return doc.$context; + return global.qmlEngine.doc.$context; } this.focusedElement = (function() { @@ -7901,7 +7957,9 @@ // Initialize property bindings for (var i = 0; i < this.bindedProperties.length; i++) { var property = this.bindedProperties[i]; - property.binding.compile(); + if (property.binding!=null) { + property.binding.compile(); + } property.update(); } this.bindedProperties = []; @@ -8090,7 +8148,8 @@ } QMLPositioner.slotChildrenChanged = function() { - for (var i = 0; i < this.children.length; i++) { + var c = this.children.length; + for (var i = 0; i < c; i++) { var child = this.children[i]; if (!child.widthChanged.isConnected(this, this.layoutChildren)) child.widthChanged.connect(this, this.layoutChildren); @@ -8349,6 +8408,8 @@ this.width = width; this.$updatingGeometry = false; + + if (this.parent != undefined) updateChildrenRect(this.parent); } function updateVGeometry(newVal, oldVal, propName) { @@ -8448,38 +8509,43 @@ this.height = height; this.$updatingGeometry = false; + + if (this.parent != undefined) updateChildrenRect(this.parent); } - - -function QMLButton(meta) { - this.dom = document.createElement("button"); - QMLItem.call(this, meta); - var self = this; - - this.dom.style.pointerEvents = "auto"; - this.dom.innerHTML = ""; - - createSimpleProperty("string", this, "text"); - this.clicked = Signal(); - - this.Component.completed.connect(this, function() { - this.implicitWidth = this.dom.firstChild.offsetWidth + 20; - this.implicitHeight = this.dom.firstChild.offsetHeight + 5; - }); - this.textChanged.connect(this, function(newVal) { - this.dom.firstChild.innerHTML = newVal; - //TODO: Replace those statically sized borders - this.implicitWidth = this.dom.firstChild.offsetWidth + 20; - this.implicitHeight = this.dom.firstChild.offsetHeight + 5; - }); - - this.dom.onclick = function(e) { - self.clicked(); - } +function updateChildrenRect(component){ + + var children = component !== undefined ? component.children : undefined + if ( children == undefined || children.length == 0 ) + return; + + var maxWidth = 0; + var maxHeight = 0; + var minX = children.length>0 ? children[0].x : 0; + var minY = children.length>0 ? children[0].y : 0; + var h=0; + var w=0; + + for(var i in children){ + var child = children[i]; + + h = child.$isUsingImplicitHeight !== 0 ? child.implicitHeight : child.height; + w = child.$isUsingImplicitWidth !== 0 ? child.implicitWidth : child.width; + + maxWidth = Math.max(maxWidth, child.x + w); + maxHeight = Math.max(maxHeight, child.y + h); + minX = Math.min(minX, child.x); + minY = Math.min(minX, child.y); + } + + component.childrenRect.x = minX; + component.childrenRect.y = minY; + component.childrenRect.width = maxWidth; + component.childrenRect.height = maxHeight; + } -registerQmlType('Button', QMLButton); + QMLComponent.prototype.createObject = function(parent, properties) { var oldState = engine.operationState; @@ -8517,11 +8583,15 @@ if (typeof qmlEngine.basePath != 'undefined') src = qmlEngine.basePath + src; - if (typeof qrc[src] != 'undefined') + + if (typeof qrc[src] != 'undefined') { js = qrc[src]; - else + } + else { js = jsparse(getUrlContents(src)); - var $context = qmlEngine.rootContext(); + } + + var $context = this.$context; $context[importDesc.alias] = {}; importJavascriptInContext(js, $context[importDesc.alias]); } @@ -8683,6 +8753,7 @@ this.$isUsingImplicitWidth = true; this.$isUsingImplicitHeight = true; + // anchors property this.anchors = new QObject(this); createSimpleProperty("real", this.anchors, "left"); createSimpleProperty("real", this.anchors, "right"); @@ -8714,6 +8785,14 @@ this.anchors.marginsChanged.connect(this, updateHGeometry); this.anchors.marginsChanged.connect(this, updateVGeometry); + // childrenRect property + this.childrenRect = new QObject(this); + createSimpleProperty("real", this.childrenRect, "x","rw"); //todo ro + createSimpleProperty("real", this.childrenRect, "y","rw"); // todo ro + createSimpleProperty("real", this.childrenRect, "width","rw"); // todo ro + createSimpleProperty("real", this.childrenRect, "height","rw"); // todo ro + + createSimpleProperty("list", this, "states"); createSimpleProperty("string", this, "state"); createSimpleProperty("list", this, "transitions"); @@ -8913,7 +8992,11 @@ this.transform = []; this.rotation = 0; this.scale = 1; - + this.childrenRect.x = 0; + this.childrenRect.y = 0; + this.childrenRect.width = 0; + this.childrenRect.height = 0; + // Init size of root element if (this.$parent === null && engine.rootElement == undefined) { window.onresize(); @@ -9294,6 +9377,35 @@ } }); +function QMLButton(meta) { + this.dom = document.createElement("button"); + QMLItem.call(this, meta); + var self = this; + + this.dom.style.pointerEvents = "auto"; + this.dom.innerHTML = ""; + + createSimpleProperty("string", this, "text"); + this.clicked = Signal(); + + this.Component.completed.connect(this, function() { + this.implicitWidth = this.dom.firstChild.offsetWidth + 20; + this.implicitHeight = this.dom.firstChild.offsetHeight + 5; + }); + this.textChanged.connect(this, function(newVal) { + this.dom.firstChild.innerHTML = newVal; + //TODO: Replace those statically sized borders + this.implicitWidth = this.dom.firstChild.offsetWidth + 20; + this.implicitHeight = this.dom.firstChild.offsetHeight + 5; + }); + + this.dom.onclick = function(e) { + self.clicked(); + } +} + +registerQmlType('Button', QMLButton); + registerQmlType({ module: 'QtQuick.Controls', name: 'CheckBox', @@ -9331,7 +9443,85 @@ }; } }); +registerQmlType({ + module: 'QtQuick', + name: 'ComboBox', + versions: /.*/, + constructor: QMLComboBox +}); +function QMLComboBox(meta) { + QMLItem.call(this, meta); + var self = this; + + this.dom.style.pointerEvents = "auto"; + this.name = "QMLComboBox"; + + createSimpleProperty("int", this, "count"); + createSimpleProperty("int", this, "currentIndex"); + createSimpleProperty("string", this, "currentText"); + createSimpleProperty("array", this, "menu"); + createSimpleProperty("array", this, "model"); + createSimpleProperty("bool", this, "pressed"); + + this.count = 0; + this.currentIndex = 0; + this.currentText = ""; + this.menu = []; + this.model = []; + this.pressed = false; + + var updateCB = function(){ + var head = ""; + var html = head; + + var model = self.model; + var count = model.length; + self.count = count; + + for (var i = 0; i < count; i++) { + var elt = model[i]; + //if (elt instanceof Array) { // TODO - optgroups? update model ! + // var count_i = elt.length; + // for (var j = 0; j < count_i; j++) + // html += ""; + //} + //else + html += ""; + } + html += tail; + return html; + }; + + this.accepted = Signal(); + this.activated = Signal([{type: "int", name: "index"}]); + + this.find = function(text) { + return self.model.indexOf(text) + }; + this.selectAll = function () {}; // TODO + this.textAt = function(index) { + return this.model[index]; + }; + + this.Component.completed.connect(this, function () { + this.dom.innerHTML = updateCB(); + var child = this.dom.firstChild; + this.implicitWidth = child.offsetWidth; + this.implicitHeight = child.offsetHeight; + }); + + this.modelChanged.connect(updateCB); + + this.dom.onclick = function (e) { + var index = self.dom.firstChild.selectedIndex; + self.currentIndex = index ; + self.currentText = self.model[index]; + self.accepted(); + self.activated(index); + }; +} registerQmlType({ module: 'QtQuick.Controls', name: 'TextArea', @@ -9557,15 +9747,35 @@ QMLColumn.prototype.layoutChildren = function() { var curPos = 0, maxWidth = 0; - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - if (!(child.visible && child.opacity && child.width && child.height)) + var children = []; + var child = undefined; + var QMLRepeater = getConstructor('QtQuick', '2.0', 'Repeater'); + var QMLListView = getConstructor('QtQuick', '2.0', 'ListView'); + + for (var key in this.children) { + child = this.children[key]; + + if ( child instanceof QMLRepeater || child instanceof QMLListView) { + children = children.concat(child.children); + } + else { + children.push(child); + } + } + + if (children.length == 0) return; + + for (var i = 0; i < children.length; i++) { + child = children[i]; + if (!(child.visible && child.opacity && child.width && child.height)) { continue; + } + maxWidth = child.width > maxWidth ? child.width : maxWidth; - child.y = curPos; curPos += child.height + this.spacing; } + this.implicitWidth = maxWidth; this.implicitHeight = curPos - this.spacing; // We want no spacing at the bottom side } @@ -9651,48 +9861,70 @@ this.layoutDirectionChanged.connect(this, this.layoutChildren); this.widthChanged.connect(this, this.layoutChildren); - this.flow = 0; - this.layoutDirection = 0; + this.flow = this.Flow.LeftToRight; + this.layoutDirection = 0 ; } QMLFlow.prototype.layoutChildren = function() { var curHPos = 0, curVPos = 0, rowSize = 0; - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; + var children = []; + var child = undefined; + var QMLRepeater = getConstructor('QtQuick', '2.0', 'Repeater'); + + for (var key in this.children){ + child = this.children[key]; + + if ( child instanceof QMLRepeater) { + children = children.concat(child.children); + } + else{ + children.push(child); + } + } + + if (children.length == 0) return; + + var flowWidth = this.$isUsingImplicitWidth ? this.implicitWidth : this.width; + var flowHeight = this.$isUsingImplicitHeight ? this.implicitHeight : this.height; + + for (var i = 0; i < children.length; i++) { + child = children[i]; if (!(child.visible && child.opacity && child.width && child.height)) continue; - if (this.flow == 0) { - if (curHPos + child.width > this.width) { - curHPos = 0; + if (this.flow == this.Flow.LeftToRight) { + if (curHPos + child.width > flowWidth) { + if (this.$isUsingImplicitWidth == false ) curHPos = 0; curVPos += rowSize + this.spacing; rowSize = 0; } rowSize = child.height > rowSize ? child.height : rowSize; child.x = this.layoutDirection == 1 - ? this.width - curHPos - child.width : curHPos; + ? flowWidth - curHPos - child.width : curHPos; child.y = curVPos; curHPos += child.width + this.spacing; } else { - if (curVPos + child.height > this.height) { - curVPos = 0; + if (curVPos + child.height > flowHeight) { + if (this.$isUsingImplicitHeight == false ) curVPos = 0; curHPos += rowSize + this.spacing; rowSize = 0; } rowSize = child.width > rowSize ? child.width : rowSize; child.x = this.layoutDirection == 1 - ? this.width - curHPos - child.width : curHPos; + ? flowWidth - curHPos - child.width : curHPos; child.y = curVPos; curVPos += child.height + this.spacing; } } - if (this.flow == 0) + + if (this.$isUsingImplicitHeight) this.implicitHeight = curVPos + rowSize; - else + + if (this.$isUsingImplicitWidth) this.implicitWidth = curHPos + rowSize; } @@ -10189,31 +10421,28 @@ }); -registerQmlType({ - module: 'QtQuick', - name: 'ListElement', - versions: /.*/, - constructor: function QMLListElement(meta) { +function QMLListElement(meta) { QMLBaseObject.call(this, meta); for (var i in meta.object) { if (i[0] != "$") { createSimpleProperty("variant", this, i); } } applyProperties(meta.object, this, this, this.$context); - } -}); +} registerQmlType({ module: 'QtQuick', - name: 'ListModel', + name: 'ListElement', versions: /.*/, - constructor: function QMLListModel(meta) { + constructor: QMLListElement +}); + +function QMLListModel(meta) { QMLBaseObject.call(this, meta); var self = this, firstItem = true; - var QMLListElement = getConstructor('QtQuick', '2.0', 'ListElement'); createSimpleProperty("int", this, "count"); createSimpleProperty("list", this, "$items"); @@ -10223,17 +10452,8 @@ this.count = 0; this.$itemsChanged.connect(this, function(newVal) { - if (firstItem) { - firstItem = false; - var roleNames = []; - var dict = newVal[0]; - for (var i in (dict instanceof QMLListElement) ? dict.$properties : dict) { - if (i != "index") - roleNames.push(i); - } - this.$model.setRoleNames(roleNames); - } this.count = this.$items.length; + updateRoleNames(newVal); }); this.$model.data = function(index, role) { @@ -10244,7 +10464,21 @@ } this.append = function(dict) { - this.insert(this.$items.length, dict); + var index = this.$items.length; + var c = 0; + + if (dict instanceof Array){ + for (var key in dict) { + this.$items.push(dict[key]); + c++; + } + }else { + this.$items.push(dict); + c=1; + } + + this.$itemsChanged(this.$items); + this.$model.rowsInserted(index, index + c); } this.clear = function() { this.$items = []; @@ -10257,7 +10491,7 @@ this.insert = function(index, dict) { this.$items.splice(index, 0, dict); this.$itemsChanged(this.$items); - this.$model.rowsInserted(index, index+1); + this.$model.rowsInserted(index, index + 1); } this.move = function(from, to, n) { var vals = this.$items.splice(from, n); @@ -10273,11 +10507,34 @@ } this.set = function(index, dict) { this.$items[index] = dict; + engine.$requestDraw(); } this.setProperty = function(index, property, value) { this.$items[index][property] = value; + engine.$requestDraw(); } - } + + function updateRoleNames(newVal){ + if (firstItem && newVal.length > 0 ) { + firstItem = false; + var roleNames = []; + var dict = newVal[0]; + + for (var i in (dict instanceof QMLListElement) ? dict.$properties : dict) { + if (i != "index") + roleNames.push(i); + } + + self.$model.setRoleNames(roleNames); + } + } +} + +registerQmlType({ + module: 'QtQuick', + name: 'ListModel', + versions: /.*/, + constructor: QMLListModel }); registerQmlType({ @@ -10322,6 +10579,144 @@ } }); +/** + * + * Loader is used to dynamically load QML components. + * + * Loader can load a QML file (using the source property) + * or a Component object (using the sourceComponent property). + * It is useful for delaying the creation of a component until + * it is required: for example, when a component should be created + * on demand, or when a component should not be created unnecessarily + * for performance reasons. + * + */ + +registerQmlType({ + module: 'QtQuick', + name: 'Loader', + versions: /.*/, + constructor: function(meta) { + QMLItem.call(this, meta); + + var self = this; + + createSimpleProperty('bool', this, 'active'); //totest + createSimpleProperty('bool', this, 'asynchronous'); //todo + createSimpleProperty('var', this, 'item'); + createSimpleProperty('real', this, 'progress'); //todo + createSimpleProperty('url', this, 'source'); + createSimpleProperty('Component', this, 'sourceComponent'); //totest + createSimpleProperty('enum', this, 'status'); + + + this.active = true; + this.asynchronous = false; + this.item = undefined; + this.progress = 0.0; + this.source = undefined; + this.sourceComponent = undefined; + this.status = 1; + this.sourceUrl = ''; + + this.loaded = Signal(); + + implicitWidth = this.item ? this.item.width : 0; + implicitHeight = this.item ? this.item.height : 0; + + this.activeChanged.connect( function(newVal) { + + if (self.active){ + if (self.source) + sourceChanged(); + else if (self.sourceComponent) + sourceComponentChanged(); + }else{ + unload(); + } + }); + + this.sourceChanged.connect( function(newVal) { + if (self.active == false )//|| (newVal == self.sourceUrl && self.item !== undefined) ) //todo + { + console.log( " Loader isn't active."); + return; + } + + unload(); + + if (self.source.length>0) { + var fileName = newVal.lastIndexOf(".qml") == newVal.length-4 ? newVal.substring(0, newVal.length-4) : ""; + + if ( fileName !== "" ) { + var tree = engine.loadComponent(fileName); + + var component = new QMLComponent({ object: tree, context: self , parent: self}); + var loadedComponent = component.createObject(self); + + loadedComponent.parent = self; + component.finalizeImports(); + + self.childrenChanged(); + + if (engine.operationState !== QMLOperationState.Init) { + // We don't call those on first creation, as they will be called + // by the regular creation-procedures at the right time. + engine.$initializePropertyBindings(); + callOnCompleted(loadedComponent); + } + + self.sourceComponent = loadedComponent; + self.sourceUrl = newVal; + } + } + + } ); + + this.sourceComponentChanged.connect(function(newVal) { + + if ( self.active == false ) { + return; + } + + self.item = newVal; + + if (self.item){ + if (!self.$isUsingImplicitWidth) { + self.item.width = self.width; + } + if (!self.$isUsingImplicitHeight) { + self.item.height = self.height; + } + + self.loaded(); + } + } ); + + function unload(){ + if (self.item){ + + self.item.$delete(); + self.item.parent = undefined; + self.item = undefined; + } + } + + function callOnCompleted(child) { + child.Component.completed(); + for (var i = 0; i < child.children.length; i++) + callOnCompleted(child.children[i]); + } + + this.setSource = function(url, options) { + this.sourceUrl = url; + this.props = options; + this.source = url; + } + + } +}); + registerQmlType({ module: 'QtQuick', name: 'MouseArea', @@ -10345,6 +10740,9 @@ createSimpleProperty("real", this, "mouseY"); createSimpleProperty("bool", this, "pressed"); createSimpleProperty("bool", this, "containsMouse"); + createSimpleProperty("enum", this, "cursorShape"); + + this.clicked = Signal([{type: "variant", name: "mouse"}]); this.entered = Signal(); this.exited = Signal(); @@ -10354,7 +10752,8 @@ this.enabled = true; this.hoverEnabled = false; this.containsMouse = false; - + this.cursorShape = Qt.ArrowCursor; + function eventToMouse(e) { return { accepted: true, @@ -10400,14 +10799,56 @@ self.containsMouse = false; self.exited(); } + this.dom.onmouseover = function(e) { + self.containsMouse = true; + self.dom.style.cursor = cursorShapeToCSS(); + self.entered(); + } + this.dom.onmouseout = function(e) { + self.containsMouse = false; + self.dom.style.cursor = "auto"; + self.exited(); + } this.dom.onmousemove = function(e) { if (self.enabled && (self.hoverEnabled || self.pressed)) { var mouse = eventToMouse(e); self.positionChanged(mouse); self.mouseX = mouse.x; self.mouseY = mouse.y; } } + + function cursorShapeToCSS(){ + var cursor = "auto"; + switch (self.cursorShape) { + case Qt.ArrowCursor: cursor = "default"; break; + case Qt.UpArrowCursor: cursor = "auto";break; + case Qt.CrossCursor: cursor = "crosshair";break; + case Qt.WaitCursor: cursor = "wait";break; + case Qt.IBeamCursor: cursor = "auto";break; + case Qt.SizeVerCursor: cursor = "auto";break; + case Qt.SizeHorCursor: cursor = "auto";break; + case Qt.SizeBDiagCursor: cursor = "auto";break; + case Qt.SizeFDiagCursor: cursor = "auto";break; + case Qt.SizeAllCursor: cursor = "auto";break; + case Qt.BlankCursor: cursor = "auto";break; + case Qt.SplitVCursor: cursor = "auto";break; + case Qt.SplitHCursor: cursor = "auto";break; + case Qt.PointingHandCursor: cursor = "pointer";break; + case Qt.ForbiddenCursor: cursor = "not-allowed";break; + case Qt.WhatsThisCursor: cursor = "auto";break; + case Qt.BusyCursor: cursor = "progress";break; + case Qt.OpenHandCursor: cursor = "auto";break; + case Qt.ClosedHandCursor: cursor = "move";break; + case Qt.DragCopyCursor: cursor = "auto";break; + case Qt.DragMoveCursor: cursor = "auto";break; + case Qt.DragLinkCursor: cursor = "auto";break; + case Qt.LastCursor: cursor = "auto";break; + case Qt.BitmapCursor: cursor = "auto";break; + case Qt.CustomCursor: cursor = "auto"; break; + } + return cursor; + } } }); @@ -10856,8 +11297,11 @@ constructor: function QMLRepeater(meta) { QMLItem.call(this, meta); var self = this; + var QMLListModel = getConstructor('QtQuick', '2.0', 'ListModel'); + this.parent = meta.parent; // TODO: some (all ?) of the components including Repeater needs to know own parent at creation time. Please consider this major change. + createSimpleProperty("Component", this, "delegate"); this.container = function() { return this.parent; } this.$defaultProperty = "delegate"; @@ -10869,8 +11313,8 @@ this.modelChanged.connect(applyModel); this.delegateChanged.connect(applyModel); + this.childrenChanged.connect - this.model = 0; this.count = 0; this.itemAt = function(index) { @@ -10883,20 +11327,31 @@ callOnCompleted(child.children[i]); } function insertChildren(startIndex, endIndex) { - for (var index = startIndex; index < endIndex; index++) { + var index = 0; + var model = self.model instanceof QMLListModel ? self.model.$model : self.model; + + for ( index = startIndex; index < endIndex; index++) { var newItem = self.delegate.createObject(self); + newItem.parent = self; // Repeater children + self.delegate.finalizeImports(); // To properly import JavaScript in the context of a component createSimpleProperty("int", newItem, "index"); - var model = self.model instanceof QMLListModel ? self.model.$model : self.model; - for (var i in model.roleNames) { - if (typeof newItem.$properties[model.roleNames[i]] == 'undefined') - createSimpleProperty("variant", newItem, model.roleNames[i]); - newItem.$properties[model.roleNames[i]].set(model.data(index, model.roleNames[i]), true, newItem, self.model.$context); + + if ( typeof model == "number" || model instanceof Array ) { + if (typeof newItem.$properties["modelData"] == 'undefined'){ + createSimpleProperty("variant", newItem, "modelData"); + } + + var value = model instanceof Array ? model[index] : typeof model == "number" ? index : "undefined"; + newItem.$properties["modelData"].set(value, true, newItem, model.$context); + } else { + for (var i in model.roleNames) { + if (typeof newItem.$properties[model.roleNames[i]] == 'undefined') + createSimpleProperty("variant", newItem, model.roleNames[i]); + newItem.$properties[model.roleNames[i]].set(model.data(index, model.roleNames[i]), true, newItem, self.model.$context); + } } - - self.container().children.splice(self.parent.children.indexOf(self) - self.$items.length + index, 0, newItem); - newItem.parent = self.container(); - self.container().childrenChanged(); + self.$items.splice(index, 0, newItem); newItem.index = index; @@ -10908,6 +11363,11 @@ callOnCompleted(newItem); } } + + if (index > 0) { + self.container().childrenChanged(); + } + for (var i = endIndex; i < self.$items.length; i++) self.$items[i].index = i; @@ -10917,7 +11377,9 @@ function applyModel() { if (!self.delegate) return; + var model = self.model instanceof QMLListModel ? self.model.$model : self.model; + if (model instanceof JSItemModel) { model.dataChanged.connect(function(startIndex, endIndex) { //TODO @@ -10950,9 +11412,14 @@ } else if (typeof model == "number") { removeChildren(0, self.$items.length); insertChildren(0, model); - } + } else if (model instanceof Array) { + removeChildren(0, self.$items.length); + insertChildren(0, model.length); + } + } + function removeChildren(startIndex, endIndex) { var removed = self.$items.splice(startIndex, endIndex - startIndex); for (var index in removed) { @@ -10968,7 +11435,6 @@ } } }); - registerQmlType({ module: 'QtQuick', name: 'Rotation', @@ -11024,25 +11490,48 @@ } QMLRow.prototype.layoutChildren = function() { - var curPos = 0, - maxHeight = 0, - // When layoutDirection is RightToLeft we need oposite order - i = this.layoutDirection == 1 ? this.children.length - 1 : 0, - endPoint = this.layoutDirection == 1 ? -1 : this.children.length, + var curPos = 0, maxHeight = 0; + var children = []; + var child = undefined; + var QMLRepeater = getConstructor('QtQuick', '2.0', 'Repeater'); + + for (var key in this.children){ + child = this.children[key]; + + if ( child instanceof QMLRepeater) { + children = children.concat(child.children); + } + else{ + children.push(child); + } + } + + if (children.length == 0) return; + + // When layoutDirection is RightToLeft we need oposite order + var i = this.layoutDirection == 1 ? children.length - 1 : 0, + endPoint = this.layoutDirection == 1 ? -1 : children.length, step = this.layoutDirection == 1 ? -1 : 1; + + var rowWidth = this.$isUsingImplicitWidth ? this.implicitWidth : this.width; + var rowHeight = this.$isUsingImplicitHeight ? this.implicitHeight : this.height; + for (; i !== endPoint; i += step) { - var child = this.children[i]; + child = children[i]; + if (!(child.visible && child.opacity && child.width && child.height)) continue; + maxHeight = child.height > maxHeight ? child.height : maxHeight; child.x = curPos; curPos += child.width + this.spacing; } + this.implicitHeight = maxHeight; this.implicitWidth = curPos - this.spacing; // We want no spacing at the right side + } - registerQmlType({ module: 'QtQuick', name: 'Scale', @@ -11498,22 +11987,26 @@ var self = this; - this.font = new getConstructor('QtQuick', '2.0', 'Font')(this); + var QMLFont = new getConstructor('QtQuick', '2.0', 'Font'); + this.font = new QMLFont(this); this.dom.innerHTML = "" this.dom.firstChild.style.pointerEvents = "auto"; // In some browsers text-inputs have a margin by default, which distorts // the positioning, so we need to manually set it to 0. this.dom.firstChild.style.margin = "0"; + this.dom.firstChild.style.padding = "0"; this.dom.firstChild.style.width = "100%"; + this.dom.firstChild.style.height = "100%"; this.setupFocusOnDom(this.dom.firstChild); createSimpleProperty("string", this, "text"); createSimpleProperty("int", this, "maximumLength"); createSimpleProperty("bool", this, "readOnly"); createSimpleProperty("var", this, "validator"); createSimpleProperty("enum", this, "echoMode"); + this.accepted = Signal(); this.readOnly = false; this.maximumLength = -1; @@ -11711,6 +12204,43 @@ }); registerQmlType({ + module: 'QtQuick', + name: 'Screen', + versions: /.*/, + constructor: QMLScreen +}); + +function QMLScreen(meta) { + QMLItem.call(this, meta); + var self = this; + + createSimpleProperty("int", this, "desktopAvailableHeight"); + createSimpleProperty("int", this, "desktopAvailableWidth"); + createSimpleProperty("real", this, "devicePixelRatio"); + createSimpleProperty("int", this, "height"); + createSimpleProperty("string", this, "name"); + createSimpleProperty("enum", this, "orientation"); + createSimpleProperty("enum", this, "orientationUpdateMask"); + createSimpleProperty("real", this, "pixelDensity"); + createSimpleProperty("enum", this, "primaryOrientation"); + createSimpleProperty("int", this, "width"); + + this.Component.completed.connect(this, updateSC); + + function updateSC() { + self.desktopAvailableHeight = window.outerHeight; + self.desktopAvailableWidth = window.outerWidth; + self.devicePixelRatio = window.devicePixelRatio; + self.height = window.innerHeight; + self.name = this.name; + self.orientation = Qt.PrimaryOrientation; + self.orientationUpdateMask = 0; + self.pixelDensity = 100.0; // TODO + self.primaryOrientation = Qt.PrimaryOrientation; + self.width = window.innerWidth; + } +} +registerQmlType({ module: 'QmlWeb', name: 'RestModel', versions: /.*/, diff --git a/readme_gulp b/readme_gulp new file mode 100644 --- /dev/null +++ b/readme_gulp @@ -0,0 +1,8 @@ + +To rebuild library run + +./node_modules/.bin/gulp qt + +or + +./node_modules/.bin/gulp qt.min diff --git a/src/qtcore/qml/AutoLoader.js b/src/qtcore/qml/AutoLoader.js --- a/src/qtcore/qml/AutoLoader.js +++ b/src/qtcore/qml/AutoLoader.js @@ -7,7 +7,7 @@ if (source != null) { global.qmlEngine = new QMLEngine(); - qmlEngine.loadFile(source); + qmlEngine.loadFile(source, null); qmlEngine.start(); break ; } diff --git a/src/qtcore/qml/QMLBaseObject.js b/src/qtcore/qml/QMLBaseObject.js --- a/src/qtcore/qml/QMLBaseObject.js +++ b/src/qtcore/qml/QMLBaseObject.js @@ -59,3 +59,9 @@ this.getAttributes = function() { return (attributes); } } +registerQmlType({ + module: 'QtQuick', + name: 'QtObject', + versions: /.*/, + constructor: QMLBaseObject +}); diff --git a/src/qtcore/qml/QMLEngine.js b/src/qtcore/qml/QMLEngine.js --- a/src/qtcore/qml/QMLEngine.js +++ b/src/qtcore/qml/QMLEngine.js @@ -77,36 +77,40 @@ if (!qrc.includesFile(file)) { var src = getUrlContents(file); - console.log('loading file', file); - qrc[file] = qmlparse(src); + if (src) { + console.log('Loading file [', file,']'); + qrc[file] = qmlparse(src); + }else { + console.log('Can nor load file [', file,']'); + } } } - + // Load file, parse and construct (.qml or .qml.js) - this.loadFile = function(file) { + this.loadFile = function(file, parentComponent) { var tree; basePath = this.pathFromFilepath(file); this.basePath = basePath; this.ensureFileIsLoadedInQrc(file); tree = convertToEngine(qrc[file]); - this.loadQMLTree(tree); + return this.loadQMLTree(tree, parentComponent); } // parse and construct qml this.loadQML = function(src) { - this.loadQMLTree(parseQML(src)); + this.loadQMLTree(parseQML(src), null); } - this.loadQMLTree = function(tree) { + this.loadQMLTree = function(tree, parentComponent) { engine = this; if (options.debugTree) { options.debugTree(tree); } // Create and initialize objects - var component = new QMLComponent({ object: tree, parent: null }); - doc = component.createObject(null); + var component = new QMLComponent({ object: tree, parent: parentComponent }); + doc = component.createObject(parentComponent); component.finalizeImports(); this.$initializePropertyBindings(); @@ -116,10 +120,12 @@ for (var i in this.completedSignals) { this.completedSignals[i](); } + + return component; } this.rootContext = function() { - return doc.$context; + return global.qmlEngine.doc.$context; } this.focusedElement = (function() { @@ -267,7 +273,9 @@ // Initialize property bindings for (var i = 0; i < this.bindedProperties.length; i++) { var property = this.bindedProperties[i]; - property.binding.compile(); + if (property.binding!=null) { + property.binding.compile(); + } property.update(); } this.bindedProperties = []; diff --git a/src/qtcore/qml/QMLPositioner.js b/src/qtcore/qml/QMLPositioner.js --- a/src/qtcore/qml/QMLPositioner.js +++ b/src/qtcore/qml/QMLPositioner.js @@ -10,7 +10,8 @@ } QMLPositioner.slotChildrenChanged = function() { - for (var i = 0; i < this.children.length; i++) { + var c = this.children.length; + for (var i = 0; i < c; i++) { var child = this.children[i]; if (!child.widthChanged.isConnected(this, this.layoutChildren)) child.widthChanged.connect(this, this.layoutChildren); diff --git a/src/qtcore/qml/UpdateGeometry.js b/src/qtcore/qml/UpdateGeometry.js --- a/src/qtcore/qml/UpdateGeometry.js +++ b/src/qtcore/qml/UpdateGeometry.js @@ -95,6 +95,8 @@ this.width = width; this.$updatingGeometry = false; + + if (this.parent != undefined) updateChildrenRect(this.parent); } function updateVGeometry(newVal, oldVal, propName) { @@ -194,6 +196,40 @@ this.height = height; this.$updatingGeometry = false; + + if (this.parent != undefined) updateChildrenRect(this.parent); +} + +function updateChildrenRect(component){ + + var children = component !== undefined ? component.children : undefined + if ( children == undefined || children.length == 0 ) + return; + + var maxWidth = 0; + var maxHeight = 0; + var minX = children.length>0 ? children[0].x : 0; + var minY = children.length>0 ? children[0].y : 0; + var h=0; + var w=0; + + for(var i in children){ + var child = children[i]; + + h = child.$isUsingImplicitHeight ? child.implicitHeight : child.height; + w = child.$isUsingImplicitWidth ? child.implicitWidth : child.width; + + maxWidth = Math.max(maxWidth, child.x + w); + maxHeight = Math.max(maxHeight, child.y + h); + minX = Math.min(minX, child.x); + minY = Math.min(minX, child.y); + } + + component.childrenRect.x = minX; + component.childrenRect.y = minY; + component.childrenRect.width = maxWidth; + component.childrenRect.height = maxHeight; + } diff --git a/src/qtcore/qml/elements/Component.js b/src/qtcore/qml/elements/Component.js --- a/src/qtcore/qml/elements/Component.js +++ b/src/qtcore/qml/elements/Component.js @@ -34,11 +34,15 @@ if (typeof qmlEngine.basePath != 'undefined') src = qmlEngine.basePath + src; - if (typeof qrc[src] != 'undefined') + + if (typeof qrc[src] != 'undefined') { js = qrc[src]; - else + } + else { js = jsparse(getUrlContents(src)); - var $context = qmlEngine.rootContext(); + } + + var $context = this.$context; $context[importDesc.alias] = {}; importJavascriptInContext(js, $context[importDesc.alias]); } diff --git a/src/qtcore/qml/elements/Item.js b/src/qtcore/qml/elements/Item.js --- a/src/qtcore/qml/elements/Item.js +++ b/src/qtcore/qml/elements/Item.js @@ -122,6 +122,7 @@ this.$isUsingImplicitWidth = true; this.$isUsingImplicitHeight = true; + // anchors property this.anchors = new QObject(this); createSimpleProperty("real", this.anchors, "left"); createSimpleProperty("real", this.anchors, "right"); @@ -153,6 +154,14 @@ this.anchors.marginsChanged.connect(this, updateHGeometry); this.anchors.marginsChanged.connect(this, updateVGeometry); + // childrenRect property + this.childrenRect = new QObject(this); + createSimpleProperty("real", this.childrenRect, "x","rw"); //todo ro + createSimpleProperty("real", this.childrenRect, "y","rw"); // todo ro + createSimpleProperty("real", this.childrenRect, "width","rw"); // todo ro + createSimpleProperty("real", this.childrenRect, "height","rw"); // todo ro + + createSimpleProperty("list", this, "states"); createSimpleProperty("string", this, "state"); createSimpleProperty("list", this, "transitions"); @@ -352,7 +361,11 @@ this.transform = []; this.rotation = 0; this.scale = 1; - + this.childrenRect.x = 0; + this.childrenRect.y = 0; + this.childrenRect.width = 0; + this.childrenRect.height = 0; + // Init size of root element if (this.$parent === null && engine.rootElement == undefined) { window.onresize(); diff --git a/src/qtcore/qml/elements/Button.js b/src/qtcore/qml/elements/QtQuick.Controls/Button.js rename from src/qtcore/qml/elements/Button.js rename to src/qtcore/qml/elements/QtQuick.Controls/Button.js diff --git a/src/qtcore/qml/elements/QtQuick.Controls/Checkbox.js b/src/qtcore/qml/elements/QtQuick.Controls/Checkbox.js --- a/src/qtcore/qml/elements/QtQuick.Controls/Checkbox.js +++ b/src/qtcore/qml/elements/QtQuick.Controls/Checkbox.js @@ -34,4 +34,4 @@ self.checked = this.checked; }; } -}); +}); \ No newline at end of file diff --git a/src/qtcore/qml/elements/QtQuick.Controls/ComboBox.js b/src/qtcore/qml/elements/QtQuick.Controls/ComboBox.js new file mode 100644 --- /dev/null +++ b/src/qtcore/qml/elements/QtQuick.Controls/ComboBox.js @@ -0,0 +1,79 @@ +registerQmlType({ + module: 'QtQuick', + name: 'ComboBox', + versions: /.*/, + constructor: QMLComboBox +}); + +function QMLComboBox(meta) { + QMLItem.call(this, meta); + var self = this; + + this.dom.style.pointerEvents = "auto"; + this.name = "QMLComboBox"; + + createSimpleProperty("int", this, "count"); + createSimpleProperty("int", this, "currentIndex"); + createSimpleProperty("string", this, "currentText"); + createSimpleProperty("array", this, "menu"); + createSimpleProperty("array", this, "model"); + createSimpleProperty("bool", this, "pressed"); + + this.count = 0; + this.currentIndex = 0; + this.currentText = ""; + this.menu = []; + this.model = []; + this.pressed = false; + + var updateCB = function(){ + var head = ""; + var html = head; + + var model = self.model; + var count = model.length; + self.count = count; + + for (var i = 0; i < count; i++) { + var elt = model[i]; + //if (elt instanceof Array) { // TODO - optgroups? update model ! + // var count_i = elt.length; + // for (var j = 0; j < count_i; j++) + // html += ""; + //} + //else + html += ""; + } + html += tail; + return html; + }; + + this.accepted = Signal(); + this.activated = Signal([{type: "int", name: "index"}]); + + this.find = function(text) { + return self.model.indexOf(text) + }; + this.selectAll = function () {}; // TODO + this.textAt = function(index) { + return this.model[index]; + }; + + this.Component.completed.connect(this, function () { + this.dom.innerHTML = updateCB(); + var child = this.dom.firstChild; + this.implicitWidth = child.offsetWidth; + this.implicitHeight = child.offsetHeight; + }); + + this.modelChanged.connect(updateCB); + + this.dom.onclick = function (e) { + var index = self.dom.firstChild.selectedIndex; + self.currentIndex = index ; + self.currentText = self.model[index]; + self.accepted(); + self.activated(index); + }; +} \ No newline at end of file diff --git a/src/qtcore/qml/elements/QtQuick.Window/Screen.js b/src/qtcore/qml/elements/QtQuick.Window/Screen.js new file mode 100644 --- /dev/null +++ b/src/qtcore/qml/elements/QtQuick.Window/Screen.js @@ -0,0 +1,37 @@ +registerQmlType({ + module: 'QtQuick', + name: 'Screen', + versions: /.*/, + constructor: QMLScreen +}); + +function QMLScreen(meta) { + QMLItem.call(this, meta); + var self = this; + + createSimpleProperty("int", this, "desktopAvailableHeight"); + createSimpleProperty("int", this, "desktopAvailableWidth"); + createSimpleProperty("real", this, "devicePixelRatio"); + createSimpleProperty("int", this, "height"); + createSimpleProperty("string", this, "name"); + createSimpleProperty("enum", this, "orientation"); + createSimpleProperty("enum", this, "orientationUpdateMask"); + createSimpleProperty("real", this, "pixelDensity"); + createSimpleProperty("enum", this, "primaryOrientation"); + createSimpleProperty("int", this, "width"); + + this.Component.completed.connect(this, updateSC); + + function updateSC() { + self.desktopAvailableHeight = window.outerHeight; + self.desktopAvailableWidth = window.outerWidth; + self.devicePixelRatio = window.devicePixelRatio; + self.height = window.innerHeight; + self.name = this.name; + self.orientation = Qt.PrimaryOrientation; + self.orientationUpdateMask = 0; + self.pixelDensity = 100.0; // TODO + self.primaryOrientation = Qt.PrimaryOrientation; + self.width = window.innerWidth; + } +} \ No newline at end of file diff --git a/src/qtcore/qml/elements/QtQuick/Column.js b/src/qtcore/qml/elements/QtQuick/Column.js --- a/src/qtcore/qml/elements/QtQuick/Column.js +++ b/src/qtcore/qml/elements/QtQuick/Column.js @@ -5,15 +5,35 @@ QMLColumn.prototype.layoutChildren = function() { var curPos = 0, maxWidth = 0; - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - if (!(child.visible && child.opacity && child.width && child.height)) + var children = []; + var child = undefined; + var QMLRepeater = getConstructor('QtQuick', '2.0', 'Repeater'); + var QMLListView = getConstructor('QtQuick', '2.0', 'ListView'); + + for (var key in this.children) { + child = this.children[key]; + + if ( child instanceof QMLRepeater || child instanceof QMLListView) { + children = children.concat(child.children); + } + else { + children.push(child); + } + } + + if (children.length == 0) return; + + for (var i = 0; i < children.length; i++) { + child = children[i]; + if (!(child.visible && child.opacity && child.width && child.height)) { continue; + } + maxWidth = child.width > maxWidth ? child.width : maxWidth; - child.y = curPos; curPos += child.height + this.spacing; } + this.implicitWidth = maxWidth; this.implicitHeight = curPos - this.spacing; // We want no spacing at the bottom side } diff --git a/src/qtcore/qml/elements/QtQuick/Flow.js b/src/qtcore/qml/elements/QtQuick/Flow.js --- a/src/qtcore/qml/elements/QtQuick/Flow.js +++ b/src/qtcore/qml/elements/QtQuick/Flow.js @@ -12,48 +12,70 @@ this.layoutDirectionChanged.connect(this, this.layoutChildren); this.widthChanged.connect(this, this.layoutChildren); - this.flow = 0; - this.layoutDirection = 0; + this.flow = this.Flow.LeftToRight; + this.layoutDirection = 0 ; } QMLFlow.prototype.layoutChildren = function() { var curHPos = 0, curVPos = 0, rowSize = 0; - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; + var children = []; + var child = undefined; + var QMLRepeater = getConstructor('QtQuick', '2.0', 'Repeater'); + + for (var key in this.children){ + child = this.children[key]; + + if ( child instanceof QMLRepeater) { + children = children.concat(child.children); + } + else{ + children.push(child); + } + } + + if (children.length == 0) return; + + var flowWidth = this.$isUsingImplicitWidth ? this.implicitWidth : this.width; + var flowHeight = this.$isUsingImplicitHeight ? this.implicitHeight : this.height; + + for (var i = 0; i < children.length; i++) { + child = children[i]; if (!(child.visible && child.opacity && child.width && child.height)) continue; - if (this.flow == 0) { - if (curHPos + child.width > this.width) { - curHPos = 0; + if (this.flow == this.Flow.LeftToRight) { + if (curHPos + child.width > flowWidth) { + if (this.$isUsingImplicitWidth == false ) curHPos = 0; curVPos += rowSize + this.spacing; rowSize = 0; } rowSize = child.height > rowSize ? child.height : rowSize; child.x = this.layoutDirection == 1 - ? this.width - curHPos - child.width : curHPos; + ? flowWidth - curHPos - child.width : curHPos; child.y = curVPos; curHPos += child.width + this.spacing; } else { - if (curVPos + child.height > this.height) { - curVPos = 0; + if (curVPos + child.height > flowHeight) { + if (this.$isUsingImplicitHeight == false ) curVPos = 0; curHPos += rowSize + this.spacing; rowSize = 0; } rowSize = child.width > rowSize ? child.width : rowSize; child.x = this.layoutDirection == 1 - ? this.width - curHPos - child.width : curHPos; + ? flowWidth - curHPos - child.width : curHPos; child.y = curVPos; curVPos += child.height + this.spacing; } } - if (this.flow == 0) + + if (this.$isUsingImplicitHeight) this.implicitHeight = curVPos + rowSize; - else + + if (this.$isUsingImplicitWidth) this.implicitWidth = curHPos + rowSize; } diff --git a/src/qtcore/qml/elements/QtQuick/ListElement.js b/src/qtcore/qml/elements/QtQuick/ListElement.js --- a/src/qtcore/qml/elements/QtQuick/ListElement.js +++ b/src/qtcore/qml/elements/QtQuick/ListElement.js @@ -1,15 +1,17 @@ -registerQmlType({ - module: 'QtQuick', - name: 'ListElement', - versions: /.*/, - constructor: function QMLListElement(meta) { +function QMLListElement(meta) { QMLBaseObject.call(this, meta); for (var i in meta.object) { if (i[0] != "$") { createSimpleProperty("variant", this, i); } } applyProperties(meta.object, this, this, this.$context); - } +} + +registerQmlType({ + module: 'QtQuick', + name: 'ListElement', + versions: /.*/, + constructor: QMLListElement }); diff --git a/src/qtcore/qml/elements/QtQuick/ListModel.js b/src/qtcore/qml/elements/QtQuick/ListModel.js --- a/src/qtcore/qml/elements/QtQuick/ListModel.js +++ b/src/qtcore/qml/elements/QtQuick/ListModel.js @@ -1,12 +1,7 @@ -registerQmlType({ - module: 'QtQuick', - name: 'ListModel', - versions: /.*/, - constructor: function QMLListModel(meta) { +function QMLListModel(meta) { QMLBaseObject.call(this, meta); var self = this, firstItem = true; - var QMLListElement = getConstructor('QtQuick', '2.0', 'ListElement'); createSimpleProperty("int", this, "count"); createSimpleProperty("list", this, "$items"); @@ -16,17 +11,8 @@ this.count = 0; this.$itemsChanged.connect(this, function(newVal) { - if (firstItem) { - firstItem = false; - var roleNames = []; - var dict = newVal[0]; - for (var i in (dict instanceof QMLListElement) ? dict.$properties : dict) { - if (i != "index") - roleNames.push(i); - } - this.$model.setRoleNames(roleNames); - } this.count = this.$items.length; + updateRoleNames(newVal); }); this.$model.data = function(index, role) { @@ -37,7 +23,21 @@ } this.append = function(dict) { - this.insert(this.$items.length, dict); + var index = this.$items.length; + var c = 0; + + if (dict instanceof Array){ + for (var key in dict) { + this.$items.push(dict[key]); + c++; + } + }else { + this.$items.push(dict); + c=1; + } + + this.$itemsChanged(this.$items); + this.$model.rowsInserted(index, index + c); } this.clear = function() { this.$items = []; @@ -50,7 +50,7 @@ this.insert = function(index, dict) { this.$items.splice(index, 0, dict); this.$itemsChanged(this.$items); - this.$model.rowsInserted(index, index+1); + this.$model.rowsInserted(index, index + 1); } this.move = function(from, to, n) { var vals = this.$items.splice(from, n); @@ -66,9 +66,32 @@ } this.set = function(index, dict) { this.$items[index] = dict; + engine.$requestDraw(); } this.setProperty = function(index, property, value) { this.$items[index][property] = value; + engine.$requestDraw(); } - } + + function updateRoleNames(newVal){ + if (firstItem && newVal.length > 0 ) { + firstItem = false; + var roleNames = []; + var dict = newVal[0]; + + for (var i in (dict instanceof QMLListElement) ? dict.$properties : dict) { + if (i != "index") + roleNames.push(i); + } + + self.$model.setRoleNames(roleNames); + } + } +} + +registerQmlType({ + module: 'QtQuick', + name: 'ListModel', + versions: /.*/, + constructor: QMLListModel }); diff --git a/src/qtcore/qml/elements/QtQuick/Loader.js b/src/qtcore/qml/elements/QtQuick/Loader.js new file mode 100644 --- /dev/null +++ b/src/qtcore/qml/elements/QtQuick/Loader.js @@ -0,0 +1,137 @@ +/** + * + * Loader is used to dynamically load QML components. + * + * Loader can load a QML file (using the source property) + * or a Component object (using the sourceComponent property). + * It is useful for delaying the creation of a component until + * it is required: for example, when a component should be created + * on demand, or when a component should not be created unnecessarily + * for performance reasons. + * + */ + +registerQmlType({ + module: 'QtQuick', + name: 'Loader', + versions: /.*/, + constructor: function(meta) { + QMLItem.call(this, meta); + + var self = this; + + createSimpleProperty('bool', this, 'active'); //totest + createSimpleProperty('bool', this, 'asynchronous'); //todo + createSimpleProperty('var', this, 'item'); + createSimpleProperty('real', this, 'progress'); //todo + createSimpleProperty('url', this, 'source'); + createSimpleProperty('Component', this, 'sourceComponent'); //totest + createSimpleProperty('enum', this, 'status'); + + + this.active = true; + this.asynchronous = false; + this.item = undefined; + this.progress = 0.0; + this.source = undefined; + this.sourceComponent = undefined; + this.status = 1; + this.sourceUrl = ''; + + this.loaded = Signal(); + + implicitWidth = this.item ? this.item.width : 0; + implicitHeight = this.item ? this.item.height : 0; + + this.activeChanged.connect( function(newVal) { + + if (self.active){ + if (self.source) + sourceChanged(); + else if (self.sourceComponent) + sourceComponentChanged(); + }else{ + unload(); + } + }); + + this.sourceChanged.connect( function(newVal) { + if (self.active == false )//|| (newVal == self.sourceUrl && self.item !== undefined) ) //todo + { + console.log( " Loader isn't active."); + return; + } + + unload(); + + if (self.source.length>0) { + var fileName = newVal.lastIndexOf(".qml") == newVal.length-4 ? newVal.substring(0, newVal.length-4) : ""; + + if ( fileName !== "" ) { + var tree = engine.loadComponent(fileName); + + var component = new QMLComponent({ object: tree, context: self , parent: self}); + var loadedComponent = component.createObject(self); + + loadedComponent.parent = self; + component.finalizeImports(); + + self.childrenChanged(); + + if (engine.operationState !== QMLOperationState.Init) { + // We don't call those on first creation, as they will be called + // by the regular creation-procedures at the right time. + engine.$initializePropertyBindings(); + callOnCompleted(loadedComponent); + } + + self.sourceComponent = loadedComponent; + self.sourceUrl = newVal; + } + } + + } ); + + this.sourceComponentChanged.connect(function(newVal) { + + if ( self.active == false ) { + return; + } + + self.item = newVal; + + if (self.item){ + if (!self.$isUsingImplicitWidth) { + self.item.width = self.width; + } + if (!self.$isUsingImplicitHeight) { + self.item.height = self.height; + } + + self.loaded(); + } + } ); + + function unload(){ + if (self.item){ + + self.item.$delete(); + self.item.parent = undefined; + self.item = undefined; + } + } + + function callOnCompleted(child) { + child.Component.completed(); + for (var i = 0; i < child.children.length; i++) + callOnCompleted(child.children[i]); + } + + this.setSource = function(url, options) { + this.sourceUrl = url; + this.props = options; + this.source = url; + } + + } +}); diff --git a/src/qtcore/qml/elements/QtQuick/MouseArea.js b/src/qtcore/qml/elements/QtQuick/MouseArea.js --- a/src/qtcore/qml/elements/QtQuick/MouseArea.js +++ b/src/qtcore/qml/elements/QtQuick/MouseArea.js @@ -21,6 +21,9 @@ createSimpleProperty("real", this, "mouseY"); createSimpleProperty("bool", this, "pressed"); createSimpleProperty("bool", this, "containsMouse"); + createSimpleProperty("enum", this, "cursorShape"); + + this.clicked = Signal([{type: "variant", name: "mouse"}]); this.entered = Signal(); this.exited = Signal(); @@ -30,7 +33,8 @@ this.enabled = true; this.hoverEnabled = false; this.containsMouse = false; - + this.cursorShape = Qt.ArrowCursor; + function eventToMouse(e) { return { accepted: true, @@ -76,13 +80,55 @@ self.containsMouse = false; self.exited(); } + this.dom.onmouseover = function(e) { + self.containsMouse = true; + self.dom.style.cursor = cursorShapeToCSS(); + self.entered(); + } + this.dom.onmouseout = function(e) { + self.containsMouse = false; + self.dom.style.cursor = "auto"; + self.exited(); + } this.dom.onmousemove = function(e) { if (self.enabled && (self.hoverEnabled || self.pressed)) { var mouse = eventToMouse(e); self.positionChanged(mouse); self.mouseX = mouse.x; self.mouseY = mouse.y; } } + + function cursorShapeToCSS(){ + var cursor = "auto"; + switch (self.cursorShape) { + case Qt.ArrowCursor: cursor = "default"; break; + case Qt.UpArrowCursor: cursor = "auto";break; + case Qt.CrossCursor: cursor = "crosshair";break; + case Qt.WaitCursor: cursor = "wait";break; + case Qt.IBeamCursor: cursor = "auto";break; + case Qt.SizeVerCursor: cursor = "auto";break; + case Qt.SizeHorCursor: cursor = "auto";break; + case Qt.SizeBDiagCursor: cursor = "auto";break; + case Qt.SizeFDiagCursor: cursor = "auto";break; + case Qt.SizeAllCursor: cursor = "auto";break; + case Qt.BlankCursor: cursor = "auto";break; + case Qt.SplitVCursor: cursor = "auto";break; + case Qt.SplitHCursor: cursor = "auto";break; + case Qt.PointingHandCursor: cursor = "pointer";break; + case Qt.ForbiddenCursor: cursor = "not-allowed";break; + case Qt.WhatsThisCursor: cursor = "auto";break; + case Qt.BusyCursor: cursor = "progress";break; + case Qt.OpenHandCursor: cursor = "auto";break; + case Qt.ClosedHandCursor: cursor = "move";break; + case Qt.DragCopyCursor: cursor = "auto";break; + case Qt.DragMoveCursor: cursor = "auto";break; + case Qt.DragLinkCursor: cursor = "auto";break; + case Qt.LastCursor: cursor = "auto";break; + case Qt.BitmapCursor: cursor = "auto";break; + case Qt.CustomCursor: cursor = "auto"; break; + } + return cursor; + } } }); diff --git a/src/qtcore/qml/elements/QtQuick/Repeater.js b/src/qtcore/qml/elements/QtQuick/Repeater.js --- a/src/qtcore/qml/elements/QtQuick/Repeater.js +++ b/src/qtcore/qml/elements/QtQuick/Repeater.js @@ -5,8 +5,11 @@ constructor: function QMLRepeater(meta) { QMLItem.call(this, meta); var self = this; + var QMLListModel = getConstructor('QtQuick', '2.0', 'ListModel'); + this.parent = meta.parent; // TODO: some (all ?) of the components including Repeater needs to know own parent at creation time. Please consider this major change. + createSimpleProperty("Component", this, "delegate"); this.container = function() { return this.parent; } this.$defaultProperty = "delegate"; @@ -18,8 +21,8 @@ this.modelChanged.connect(applyModel); this.delegateChanged.connect(applyModel); + this.childrenChanged.connect - this.model = 0; this.count = 0; this.itemAt = function(index) { @@ -32,20 +35,31 @@ callOnCompleted(child.children[i]); } function insertChildren(startIndex, endIndex) { - for (var index = startIndex; index < endIndex; index++) { + var index = 0; + var model = self.model instanceof QMLListModel ? self.model.$model : self.model; + + for ( index = startIndex; index < endIndex; index++) { var newItem = self.delegate.createObject(self); + newItem.parent = self; // Repeater children + self.delegate.finalizeImports(); // To properly import JavaScript in the context of a component createSimpleProperty("int", newItem, "index"); - var model = self.model instanceof QMLListModel ? self.model.$model : self.model; - for (var i in model.roleNames) { - if (typeof newItem.$properties[model.roleNames[i]] == 'undefined') - createSimpleProperty("variant", newItem, model.roleNames[i]); - newItem.$properties[model.roleNames[i]].set(model.data(index, model.roleNames[i]), true, newItem, self.model.$context); + + if ( typeof model == "number" || model instanceof Array ) { + if (typeof newItem.$properties["modelData"] == 'undefined'){ + createSimpleProperty("variant", newItem, "modelData"); + } + + var value = model instanceof Array ? model[index] : typeof model == "number" ? index : "undefined"; + newItem.$properties["modelData"].set(value, true, newItem, model.$context); + } else { + for (var i in model.roleNames) { + if (typeof newItem.$properties[model.roleNames[i]] == 'undefined') + createSimpleProperty("variant", newItem, model.roleNames[i]); + newItem.$properties[model.roleNames[i]].set(model.data(index, model.roleNames[i]), true, newItem, self.model.$context); + } } - - self.container().children.splice(self.parent.children.indexOf(self) - self.$items.length + index, 0, newItem); - newItem.parent = self.container(); - self.container().childrenChanged(); + self.$items.splice(index, 0, newItem); newItem.index = index; @@ -57,6 +71,11 @@ callOnCompleted(newItem); } } + + if (index > 0) { + self.container().childrenChanged(); + } + for (var i = endIndex; i < self.$items.length; i++) self.$items[i].index = i; @@ -66,7 +85,9 @@ function applyModel() { if (!self.delegate) return; + var model = self.model instanceof QMLListModel ? self.model.$model : self.model; + if (model instanceof JSItemModel) { model.dataChanged.connect(function(startIndex, endIndex) { //TODO @@ -99,9 +120,14 @@ } else if (typeof model == "number") { removeChildren(0, self.$items.length); insertChildren(0, model); - } + } else if (model instanceof Array) { + removeChildren(0, self.$items.length); + insertChildren(0, model.length); + } + } + function removeChildren(startIndex, endIndex) { var removed = self.$items.splice(startIndex, endIndex - startIndex); for (var index in removed) { @@ -116,4 +142,4 @@ removeChildProperties(child.children[i]) } } -}); +}); \ No newline at end of file diff --git a/src/qtcore/qml/elements/QtQuick/Row.js b/src/qtcore/qml/elements/QtQuick/Row.js --- a/src/qtcore/qml/elements/QtQuick/Row.js +++ b/src/qtcore/qml/elements/QtQuick/Row.js @@ -14,21 +14,45 @@ } QMLRow.prototype.layoutChildren = function() { - var curPos = 0, - maxHeight = 0, - // When layoutDirection is RightToLeft we need oposite order - i = this.layoutDirection == 1 ? this.children.length - 1 : 0, - endPoint = this.layoutDirection == 1 ? -1 : this.children.length, + var curPos = 0, maxHeight = 0; + var children = []; + var child = undefined; + var QMLRepeater = getConstructor('QtQuick', '2.0', 'Repeater'); + + for (var key in this.children){ + child = this.children[key]; + + if ( child instanceof QMLRepeater) { + children = children.concat(child.children); + } + else{ + children.push(child); + } + } + + if (children.length == 0) return; + + // When layoutDirection is RightToLeft we need oposite order + var i = this.layoutDirection == 1 ? children.length - 1 : 0, + endPoint = this.layoutDirection == 1 ? -1 : children.length, step = this.layoutDirection == 1 ? -1 : 1; + + var rowWidth = this.$isUsingImplicitWidth ? this.implicitWidth : this.width; + var rowHeight = this.$isUsingImplicitHeight ? this.implicitHeight : this.height; + for (; i !== endPoint; i += step) { - var child = this.children[i]; + child = children[i]; + if (!(child.visible && child.opacity && child.width && child.height)) continue; + maxHeight = child.height > maxHeight ? child.height : maxHeight; child.x = curPos; curPos += child.width + this.spacing; } + this.implicitHeight = maxHeight; this.implicitWidth = curPos - this.spacing; // We want no spacing at the right side -} + +} \ No newline at end of file diff --git a/src/qtcore/qml/elements/QtQuick/TextInput.js b/src/qtcore/qml/elements/QtQuick/TextInput.js --- a/src/qtcore/qml/elements/QtQuick/TextInput.js +++ b/src/qtcore/qml/elements/QtQuick/TextInput.js @@ -11,22 +11,26 @@ var self = this; - this.font = new getConstructor('QtQuick', '2.0', 'Font')(this); + var QMLFont = new getConstructor('QtQuick', '2.0', 'Font'); + this.font = new QMLFont(this); this.dom.innerHTML = "" this.dom.firstChild.style.pointerEvents = "auto"; // In some browsers text-inputs have a margin by default, which distorts // the positioning, so we need to manually set it to 0. this.dom.firstChild.style.margin = "0"; + this.dom.firstChild.style.padding = "0"; this.dom.firstChild.style.width = "100%"; + this.dom.firstChild.style.height = "100%"; this.setupFocusOnDom(this.dom.firstChild); createSimpleProperty("string", this, "text"); createSimpleProperty("int", this, "maximumLength"); createSimpleProperty("bool", this, "readOnly"); createSimpleProperty("var", this, "validator"); createSimpleProperty("enum", this, "echoMode"); + this.accepted = Signal(); this.readOnly = false; this.maximumLength = -1; diff --git a/src/qtcore/qml/lib/parser.js b/src/qtcore/qml/lib/parser.js --- a/src/qtcore/qml/lib/parser.js +++ b/src/qtcore/qml/lib/parser.js @@ -1680,6 +1680,12 @@ }, "name": function(src) { return bindout(tree, src); + }, + "string": function(src) { + return src; + }, + "num": function(src) { + return src; } }; diff --git a/src/qtcore/qml/qml.js b/src/qtcore/qml/qml.js --- a/src/qtcore/qml/qml.js +++ b/src/qtcore/qml/qml.js @@ -174,21 +174,32 @@ if (meta.object.$class in constructors) { item = new constructors[meta.object.$class](meta); } else if (cTree = engine.loadComponent(meta.object.$class)) { - if (cTree.$children.length !== 1) + if (cTree.$children.length !== 1) { console.error("A QML component must only contain one root element!"); - var item = (new QMLComponent({ object: cTree, context: meta.context })).createObject(meta.parent); - + } + + var component = new QMLComponent({ object: cTree, context: meta.context }); + component.finalizeImports(); + + var item = component.createObject(meta.parent); + // Recall QMLBaseObject with the meta of the instance in order to get property // definitions, etc. from the instance QMLBaseObject.call(item, meta); + if (typeof item.dom != 'undefined') item.dom.className += " " + meta.object.$class + (meta.object.id ? " " + meta.object.id : ""); + var dProp; // Handle default properties } else { console.log("No constructor found for " + meta.object.$class); return; } + if (!global.qmlEngine.doc) { + global.qmlEngine.doc = item; + } + // id if (meta.object.id) meta.context[meta.object.id] = item; diff --git a/src/qtcore/qt.js b/src/qtcore/qt.js --- a/src/qtcore/qt.js +++ b/src/qtcore/qt.js @@ -181,5 +181,32 @@ Key_Play: 250, Key_Sleep: 95, Key_Zoom: 251, - Key_Cancel: 3 + Key_Cancel: 3, + // CursorShape + ArrowCursor: 0, + UpArrowCursor: 1, + CrossCursor: 2, + WaitCursor: 3, + IBeamCursor: 4, + SizeVerCursor: 5, + SizeHorCursor: 6, + SizeBDiagCursor: 7, + SizeFDiagCursor: 8, + SizeAllCursor: 9, + BlankCursor: 10, + SplitVCursor: 11, + SplitHCursor: 12, + PointingHandCursor: 13, + ForbiddenCursor: 14, + WhatsThisCursor: 15, + BusyCursor: 16, + OpenHandCursor: 17, + ClosedHandCursor: 18, + DragCopyCursor: 19, + DragMoveCursor: 20, + DragLinkCursor: 21, + LastCursor: 21, //DragLinkCursor, + BitmapCursor: 24, + CustomCursor: 25 + } diff --git a/src/uglify/parse.js b/src/uglify/parse.js --- a/src/uglify/parse.js +++ b/src/uglify/parse.js @@ -576,6 +576,8 @@ }; +function parse($TEXT, options) { + /* -----[ Parser (constants) ]----- */ var UNARY_PREFIX = makePredicate([ @@ -624,8 +626,6 @@ /* -----[ Parser ]----- */ -function parse($TEXT, options) { - options = defaults(options, { strict : false, filename : null,