diff --git a/README.md b/README.md
index 91807eec8..4e852c718 100644
--- a/README.md
+++ b/README.md
@@ -38,10 +38,11 @@ Using one of the methods below, install the qmlweb JavaScript library:
* [Bower](http://bower.io/search/?q=qmlweb) — `bower install qmlweb`
* GitHub [releases](https://github.com/qmlweb/qmlweb/releases) —
`tar -xaf v0.2.0.tar.gz`
-* Manually (recommended if you cloned from git) — `npm install && npm run build`
+* Manually (recommended if you cloned from git) —
+`npm install && npm run build`
-Next, simply add `lib/qt.js` to the list of other JavaScript files in your app's
-HTML file:
+Next, simply add `lib/qt.js` to the list of other JavaScript files in
+your app's HTML file:
```HTML
diff --git a/docs/QMLEngine.md b/docs/QMLEngine.md
index 9ab6cbee9..0a2518003 100644
--- a/docs/QMLEngine.md
+++ b/docs/QMLEngine.md
@@ -66,8 +66,8 @@ base path extracted from the initially loaded file path.
### Implicit input
-* engine object function `importPathList()` - list of urls bases used for qmldir
- files lookup
+* engine object function `importPathList()` - list of urls bases used for
+ qmldir files lookup
### Additional implicit input/output
@@ -115,8 +115,8 @@ This hash then used by `qml.js::construct` method for computing component urls.
#### TODO
* We have to keep output in component scope, not in engine scope.
-* We have to add module "as"-names to component's names (which is possible after
- keeping imports in component scope).
+* We have to add module "as"-names to component's names (which is possible
+ after keeping imports in component scope).
* Determine how this stuff is related to `QmlWeb.loadImports`
* Check A1
* Make a complete picture of what going in with imports, including Component.js
diff --git a/docs/Signal.md b/docs/Signal.md
index 99cc935df..24f338300 100644
--- a/docs/Signal.md
+++ b/docs/Signal.md
@@ -29,5 +29,5 @@ methods from the `signal`.
## Class Method: Signal.signal(\[params\[, options\]\])
-Constructs a `Signal` instance with the given `params` and `options` and returns
-its `.signal` property.
+Constructs a `Signal` instance with the given `params` and `options` and
+returns its `.signal` property.
diff --git a/karma.conf.js b/karma.conf.js
index 1ff84d7b8..c4512d120 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -11,6 +11,7 @@ module.exports = function(config) {
"tests/common.js",
"tests/failingTests.js",
"tests/*/*.js",
+ "tests/*/**/test*.js",
{ pattern: "tests/*/**/qmldir", included: false },
{ pattern: "tests/*/**/qml/*.js", included: false },
{ pattern: "tests/*/**/*.qml", included: false },
@@ -22,6 +23,8 @@ module.exports = function(config) {
type: "lcov",
dir: "coverage/"
},
+ browserDisconnectTolerance: 5, // required for phantomjs in windows
+ browserNoActivityTimeout: 100000, // required for phantomjs in windows
customLaunchers: {
PhantomJSCustom: {
base: "PhantomJS",
diff --git a/src/engine/properties.js b/src/engine/properties.js
index bbd2d7feb..84e713a21 100644
--- a/src/engine/properties.js
+++ b/src/engine/properties.js
@@ -253,14 +253,14 @@ function connectSignal(item, signalName, value, objectScope, componentScope) {
value.src = `(
function(${params.join(", ")}) {
QmlWeb.executionContext = __executionContext;
- QmlWeb.engine.$oldBasePath = QmlWeb.engine.$basePath;
+ const bp = QmlWeb.engine.$basePath;
QmlWeb.engine.$basePath = "${componentScope.$basePath}";
try {
(function() {
${value.src}
})();
} finally {
- QmlWeb.engine.$basePath = QmlWeb.engine.$oldBasePath;
+ QmlWeb.engine.$basePath = bp;
}
}
)`;
diff --git a/src/modules/QtQuick.Controls/ComboBox.js b/src/modules/QtQuick.Controls/ComboBox.js
index f4846e891..7f43139e0 100644
--- a/src/modules/QtQuick.Controls/ComboBox.js
+++ b/src/modules/QtQuick.Controls/ComboBox.js
@@ -5,7 +5,7 @@ QmlWeb.registerQmlType({
baseClass: "QtQuick.Item",
properties: {
count: "int",
- currentIndex: "int",
+ currentIndex: { type: "int", initialValue: 0 }, // same in QtQuick.Controls
currentText: "string",
menu: { type: "array", initialValue: [] },
model: { type: "array", initialValue: [] },
@@ -22,8 +22,15 @@ QmlWeb.registerQmlType({
this.dom.style.pointerEvents = "auto";
this.name = "QMLComboBox";
+ // TODO change innerHTML to DOM
+ this.dom.innerHTML = "";
+ this.impl = this.dom.firstChild;
+
this.Component.completed.connect(this, this.Component$onCompleted);
this.modelChanged.connect(this, this.$onModelChanged);
+ this.currentIndexChanged.connect(this, this.$onCurrentIndexChanged);
+ this.heightChanged.connect(this, this.$onHeightChanged);
+ this.widthChanged.connect(this, this.$onWidthChanged);
this.dom.onclick = () => {
const index = this.dom.firstChild.selectedIndex;
@@ -43,29 +50,59 @@ QmlWeb.registerQmlType({
return this.model[index];
}
$updateImpl() {
- this.currentIndex = 0;
this.count = this.model.length;
- const entries = [];
- for (let i = 0; i < this.count; i++) {
- const elt = this.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
- entries.push(``);
+
+ const k = this.count; const m = this.model;
+
+ this.impl.options.length = k;
+ for (let i = 0; i < k; i++) {
+ this.impl.options[i] = new Option(m[i]);
}
- // TODO: remove innerHTML, port to DOM
- this.dom.innerHTML = ``;
- this.impl = this.dom.firstChild;
+
+ // should call this, because width()/heights() invoke updateV(H)Geometry,
+ // which in turn sets valid $useImplicitHeight flag
+ const h = this.height; const w = this.width;
+
+ this.implicitWidth = this.impl.offsetWidth;
+ this.implicitHeight = this.impl.offsetHeight;
+
+ this.$onHeightChanged(h);
+ this.$onWidthChanged(w);
+
+ this.impl.selectedIndex = this.currentIndex;
+ this.$updateCurrentText();
}
Component$onCompleted() {
this.$updateImpl();
- this.implicitWidth = this.impl.offsetWidth;
- this.implicitHeight = this.impl.offsetHeight;
}
$onModelChanged() {
this.$updateImpl();
}
+ $onCurrentIndexChanged() {
+ const i = this.currentIndex;
+ if (this.impl.selectedIndex !== i) {
+ this.impl.selectedIndex = i;
+ this.$updateCurrentText();
+ this.activated(i);
+ }
+ }
+ $updateCurrentText() {
+ if (typeof this.currentIndex === "undefined" || !this.model) {
+ this.currentText = undefined;
+ } else if (this.currentIndex >= 0 &&
+ this.currentIndex < this.model.length) {
+ this.currentText = this.model[ this.currentIndex ];
+ }
+ }
+ $onHeightChanged() {
+ if (this.height > 0 && this.impl
+ && this.height !== this.impl.offsetHeight) {
+ this.impl.style.height = `${this.height}px`;
+ }
+ }
+ $onWidthChanged() {
+ if (this.width > 0 && this.impl && this.width !== this.impl.offsetWidth) {
+ this.impl.style.width = `${this.width}px`;
+ }
+ }
});
diff --git a/src/modules/QtQuick.Controls/ScrollView.js b/src/modules/QtQuick.Controls/ScrollView.js
index e77ab1363..a8b5aa384 100644
--- a/src/modules/QtQuick.Controls/ScrollView.js
+++ b/src/modules/QtQuick.Controls/ScrollView.js
@@ -45,9 +45,13 @@ QmlWeb.registerQmlType({
this.viewport = undefined;
this.frameVisible = false;
this.highlightOnFocus = false;
+
this.verticalScrollBarPolicy = Qt.ScrollBarAsNeeded;
this.horizontalScrollBarPolicy = Qt.ScrollBarAsNeeded;
this.style = undefined;
+
+ this.$onVerticalScrollBarPolicyChanged(this.verticalScrollBarPolicy);
+ this.$onHorizontalScrollBarPolicyChanged(this.horizontalScrollBarPolicy);
}
$onContentItemChanged(newItem) {
if (typeof newItem !== undefined) {
diff --git a/src/modules/QtQuick/Column.js b/src/modules/QtQuick/Column.js
index 9e12011ba..2a7f3f1dd 100644
--- a/src/modules/QtQuick/Column.js
+++ b/src/modules/QtQuick/Column.js
@@ -8,7 +8,7 @@ QmlWeb.registerQmlType({
QmlWeb.callSuper(this, meta);
}
layoutChildren() {
- let curPos = 0;
+ let curPos = this.padding;
let maxWidth = 0;
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
@@ -16,11 +16,12 @@ QmlWeb.registerQmlType({
continue;
}
maxWidth = child.width > maxWidth ? child.width : maxWidth;
- child.y = curPos;
+ child.y = curPos + this.padding;
+ if (this.padding > 0) child.x = this.padding;
curPos += child.height + this.spacing;
}
- this.implicitWidth = maxWidth;
- this.implicitHeight = curPos - this.spacing;
+ this.implicitWidth = maxWidth + this.padding * 2;
+ this.implicitHeight = curPos - this.spacing + this.padding;
// We want no spacing at the bottom side
}
});
diff --git a/src/modules/QtQuick/Item.js b/src/modules/QtQuick/Item.js
index 7688b3453..884a5e352 100644
--- a/src/modules/QtQuick/Item.js
+++ b/src/modules/QtQuick/Item.js
@@ -314,7 +314,7 @@ QmlWeb.registerQmlType({
$onHeightChanged_(newVal) {
this.css.height = newVal ? `${newVal}px` : "auto";
}
- $onFocusChanged(newVal) {
+ $onFocusChanged_(newVal) {
if (newVal) {
if (this.dom.firstChild) {
this.dom.firstChild.focus();
@@ -365,6 +365,9 @@ QmlWeb.registerQmlType({
}
if (typeof this.z === "number") {
transform += ` translate3d(0, 0, ${this.z}px)`;
+ // should also consider z as zIndex for stacking order behaviour of qml
+ // see http://doc.qt.io/qt-5/qml-qtquick-item.html#z-prop
+ this.dom.style.zIndex = this.z;
}
this.dom.style.transform = transform;
this.dom.style.transformStyle = transformStyle;
diff --git a/src/modules/QtQuick/MouseArea.js b/src/modules/QtQuick/MouseArea.js
index 16df1b78b..2aa0e0c05 100644
--- a/src/modules/QtQuick/MouseArea.js
+++ b/src/modules/QtQuick/MouseArea.js
@@ -18,7 +18,8 @@ QmlWeb.registerQmlType({
clicked: [{ type: "variant", name: "mouse" }],
entered: [],
exited: [],
- positionChanged: [{ type: "variant", name: "mouse" }]
+ positionChanged: [{ type: "variant", name: "mouse" }],
+ wheel: [{ type: "variant", name: "wheel" }]
}
}, class {
constructor(meta) {
@@ -82,6 +83,9 @@ QmlWeb.registerQmlType({
if (!this.enabled || !this.hoverEnabled || this.pressed) return;
this.$handlePositionChanged(e);
});
+ this.dom.addEventListener("wheel", e => {
+ this.$handleWheel(e);
+ });
}
$onCursorShapeChanged() {
this.dom.style.cursor = this.$cursorShapeToCSS();
@@ -92,6 +96,18 @@ QmlWeb.registerQmlType({
this.mouseY = mouse.y;
this.positionChanged(mouse);
}
+ $handleWheel(e) {
+ const wheel = this.$eventToMouse(e);
+ wheel.angleDelta = { x: e.deltaX, y: e.deltaY };
+ wheel.accepted = false;
+
+ this.wheel(wheel);
+
+ if (wheel.accepted) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
$handleClick(e) {
const mouse = this.$eventToMouse(e);
if (this.enabled && this.acceptedButtons & mouse.button) {
diff --git a/src/modules/QtQuick/Positioner.js b/src/modules/QtQuick/Positioner.js
index 2ba1587d9..f4c407c59 100644
--- a/src/modules/QtQuick/Positioner.js
+++ b/src/modules/QtQuick/Positioner.js
@@ -4,7 +4,8 @@ QmlWeb.registerQmlType({
versions: /.*/,
baseClass: "Item",
properties: {
- spacing: "int"
+ spacing: "int",
+ padding: "int"
}
}, class {
constructor(meta) {
diff --git a/src/modules/QtQuick/Row.js b/src/modules/QtQuick/Row.js
index 646a2be43..a5b496c1d 100644
--- a/src/modules/QtQuick/Row.js
+++ b/src/modules/QtQuick/Row.js
@@ -14,7 +14,7 @@ QmlWeb.registerQmlType({
this.layoutChildren();
}
layoutChildren() {
- let curPos = 0;
+ let curPos = this.padding;
let maxHeight = 0;
// When layoutDirection is RightToLeft we need oposite order
let i = this.layoutDirection === 1 ? this.children.length - 1 : 0;
@@ -28,10 +28,12 @@ QmlWeb.registerQmlType({
maxHeight = child.height > maxHeight ? child.height : maxHeight;
child.x = curPos;
+ if (this.padding > 0) child.y = this.padding;
+
curPos += child.width + this.spacing;
}
- this.implicitHeight = maxHeight;
+ this.implicitHeight = maxHeight + this.padding * 2;
// We want no spacing at the right side
- this.implicitWidth = curPos - this.spacing;
+ this.implicitWidth = curPos - this.spacing + this.padding;
}
});
diff --git a/src/qtbase/Signal.js b/src/qtbase/Signal.js
index 082e4d72a..af1ae583f 100644
--- a/src/qtbase/Signal.js
+++ b/src/qtbase/Signal.js
@@ -113,11 +113,14 @@ class Signal {
}
static $execute(desc, args) {
+ // TODO: remove this try/catch in test env
try {
desc.slot.apply(desc.thisObj, args);
} catch (err) {
console.error("Signal slot error:", err.message, err,
- Function.prototype.toString.call(desc.slot)
+ desc.slot
+ ? Function.prototype.toString.call(desc.slot)
+ : "desc.slot is undefined!"
);
}
}
diff --git a/tests/QMLEngine/basepath/B.qml b/tests/QMLEngine/basepath/B.qml
new file mode 100644
index 000000000..bfec8d60c
--- /dev/null
+++ b/tests/QMLEngine/basepath/B.qml
@@ -0,0 +1,5 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.2
+
+Item {
+}
\ No newline at end of file
diff --git a/tests/QMLEngine/basepath/import/A.qml b/tests/QMLEngine/basepath/import/A.qml
new file mode 100644
index 000000000..6d7d445e0
--- /dev/null
+++ b/tests/QMLEngine/basepath/import/A.qml
@@ -0,0 +1,13 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.2
+
+Item {
+ property var a
+ property var b
+
+ onAChanged: foo1()
+ onBChanged: foo2()
+
+ function foo1() { b = a+1; }
+ function foo2() {}
+}
\ No newline at end of file
diff --git a/tests/QMLEngine/basepath/test.js b/tests/QMLEngine/basepath/test.js
new file mode 100644
index 000000000..8c52cf716
--- /dev/null
+++ b/tests/QMLEngine/basepath/test.js
@@ -0,0 +1,12 @@
+describe("QMLEngine.basepath", function() {
+ setupDivElement();
+ var webroot = "/base/tests/QMLEngine/basepath/";
+
+ // this checks the case of evalling properties during component init
+ // when this component is imported from another directory
+ // followed by loading of component in current directory
+ it("engine.$basePath is not corrupted after recursive properties eval",
+ function() {
+ var qml = loadQmlFile(webroot + "test.qml", this.div);
+ });
+});
diff --git a/tests/QMLEngine/basepath/test.qml b/tests/QMLEngine/basepath/test.qml
new file mode 100644
index 000000000..7bdc9bacd
--- /dev/null
+++ b/tests/QMLEngine/basepath/test.qml
@@ -0,0 +1,11 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.2
+import "import"
+
+Item {
+ A {
+ a:1
+ }
+ B {
+ }
+}
\ No newline at end of file
diff --git a/tests/QtQuick.Controls/ComboBox/test.js b/tests/QtQuick.Controls/ComboBox/test.js
new file mode 100644
index 000000000..7ede8daae
--- /dev/null
+++ b/tests/QtQuick.Controls/ComboBox/test.js
@@ -0,0 +1,60 @@
+describe("QtQuick.ComboBox", function() {
+ setupDivElement();
+ var webroot = "/base/tests/QtQuick.Controls/ComboBox/";
+
+ it("implicitWidth should change with model", function() {
+ var qml = loadQmlFile(webroot + "test.qml", this.div);
+
+ qml.model = ["hello i am a long string!"];
+ expect(qml.implicitWidth).toBeGreaterThan(100);
+
+ qml.model = ["hi!"];
+ expect(qml.implicitWidth).toBeLessThan(100);
+
+ expect(qml.width).toBe(qml.implicitWidth);
+ });
+
+ it("Can specify explicit width for ComboBox", function() {
+ var qml = loadQmlFile(webroot + "test.qml", this.div);
+
+ qml.width = 400;
+ qml.model = ["hello i am a long string!"];
+
+ expect(qml.width).toBe(400);
+
+ qml.width = 300;
+ expect(qml.width).toBe(300);
+ });
+
+ it("should auto-select first value", function() {
+ var qml = loadQmlFile(webroot + "test.qml", this.div);
+
+ qml.model = ["a", "b", "c"];
+
+ expect(qml.currentText).toBe("a");
+ expect(qml.currentIndex).toBe(0);
+ });
+
+ it("change of currentIndex should involve change of currentText", function() {
+ var qml = loadQmlFile(webroot + "test.qml", this.div);
+
+ qml.model = ["a", "b", "c"];
+ qml.currentIndex = 1;
+ expect(qml.currentText).toBe("b");
+ });
+
+ it("default value of currentIndex should be 0", function() {
+ var qml = loadQmlFile(webroot + "test.qml", this.div);
+
+ qml.model = ["a", "b", "c"];
+ expect(qml.currentText).toBe("a");
+ expect(qml.currentIndex).toBe(0);
+ });
+
+ it("initial value of currentIndex should be considered", function() {
+ var qml = loadQmlFile(webroot + "test_populated.qml", this.div);
+
+ expect(qml.currentText).toBe("b");
+ expect(qml.currentIndex).toBe(1);
+ });
+});
diff --git a/tests/QtQuick.Controls/ComboBox/test.qml b/tests/QtQuick.Controls/ComboBox/test.qml
new file mode 100644
index 000000000..a847e4dbe
--- /dev/null
+++ b/tests/QtQuick.Controls/ComboBox/test.qml
@@ -0,0 +1,5 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.2
+
+ComboBox {
+}
\ No newline at end of file
diff --git a/tests/QtQuick.Controls/ComboBox/test_populated.qml b/tests/QtQuick.Controls/ComboBox/test_populated.qml
new file mode 100644
index 000000000..e1e4cb3ac
--- /dev/null
+++ b/tests/QtQuick.Controls/ComboBox/test_populated.qml
@@ -0,0 +1,7 @@
+import QtQuick 2.0
+import QtQuick.Controls 1.2
+
+ComboBox {
+ currentIndex: 1
+ model: ["a","b","c"]
+}
\ No newline at end of file