on xenial help sign in
Move ZoomableImage to Components.

Add more unit tests.

commit2fc0157b91463e2108b20af554e318287205652a authorYing-Chun Liu <paul.liu@canonical.com> dateTue 18 Mar 2014, 22:31 (13 years ago) parent3b37881
5 files changed+296−112
qml/Components/ZoomableImage.qml+145 qml/Dash/Previews/PreviewZoomableImage.qml+5−110 tests/qmltests/CMakeLists.txt+1 tests/qmltests/Components/tst_ZoomableImage.qml+145 tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml−2
qml/Components/ZoomableImage.qmlnew file+145
@@ -0,0 +1,145 @@
1+/*
2+ * Copyright (C) 2014 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+import "../Components"
20+
21+/*! \brief Zoomable for image.
22+
23+ This widget shows image contained in source,
24+ can be zoomable accordingly with zoomable.
25+ */
26+
27+Item {
28+ id: root
29+ property alias source: image.source
30+ property var zoomable: false
31+ property alias state: image.state
32+ property alias scale: image.scale
33+
34+ Flickable {
35+ id: flickable
36+ objectName: "flickable"
37+ clip: true
38+ contentHeight: imageContainer.height
39+ contentWidth: imageContainer.width
40+ onHeightChanged: image.calculateSize()
41+ onWidthChanged: image.calculateSize()
42+ anchors.fill: parent
43+
44+ Item {
45+ id: imageContainer
46+ objectName: "imageContainer"
47+ width: Math.max(image.width * image.scale, flickable.width)
48+ height: Math.max(image.height * image.scale, flickable.height)
49+
50+ LazyImage {
51+ id: image
52+ objectName: "image"
53+ property real prevScale
54+ smooth: !flickable.movingVertically
55+ anchors.centerIn: parent
56+ fillMode: Image.PreserveAspectFit
57+
58+ function calculateSize() {
59+ image.scale = Math.min(flickable.width / image.width, flickable.height / image.height) * 0.98;
60+ pinchArea.minScale = image.scale;
61+ prevScale = Math.min(image.scale, 1);
62+ }
63+
64+ onScaleChanged: {
65+ if ((width * scale) > flickable.width) {
66+ var xoff = (flickable.width / 2 + flickable.contentX) * scale / prevScale;
67+ flickable.contentX = xoff - flickable.width / 2;
68+ }
69+ if ((height * scale) > flickable.height) {
70+ var yoff = (flickable.height / 2 + flickable.contentY) * scale / prevScale;
71+ flickable.contentY = yoff - flickable.height / 2;
72+ }
73+
74+ prevScale = scale;
75+ }
76+
77+ onStateChanged: {
78+ if (state == "ready") {
79+ image.width = flickable.width;
80+ image.height = flickable.height;
81+ calculateSize();
82+ }
83+ }
84+ }
85+ }
86+
87+ PinchArea {
88+ id: pinchArea
89+ objectName: "pinchArea"
90+ property real minScale: 1.0
91+ property real lastScale: 1.0
92+ anchors.fill: parent
93+ enabled: zoomable ? zoomable : false
94+
95+ pinch.target: image
96+ pinch.minimumScale: minScale
97+
98+ onPinchFinished: flickable.returnToBounds()
99+ }
100+
101+ MouseArea {
102+ id: mouseArea
103+ objectName: "mouseArea"
104+
105+ anchors.fill: parent
106+ property bool doubleClicked: false
107+ property bool swipeDone: false
108+ property int startX
109+ property int startY
110+ property real startScale: pinchArea.minScale
111+ enabled: zoomable ? zoomable : false
112+
113+ onWheel: {
114+ if (wheel.angleDelta.y > 0) {
115+ startScale = image.scale;
116+ image.scale = startScale + 0.1;
117+ } else if (wheel.angleDelta.y < 0) {
118+ startScale = image.scale;
119+ if (image.scale > 0.1) {
120+ image.scale = startScale - 0.1;
121+ }
122+ }
123+ wheel.accepted = true;
124+ }
125+
126+ onPressed: {
127+ startX = (mouse.x / image.scale);
128+ startY = (mouse.y / image.scale);
129+ startScale = image.scale;
130+ }
131+
132+ onReleased: {
133+ if (image.scale == startScale) {
134+ var deltaX = (mouse.x / image.scale) - startX;
135+ var deltaY = (mouse.y / image.scale) - startY;
136+
137+ if (image.scale == pinchArea.minScale &&
138+ (Math.abs(deltaX) > 50 || Math.abs(deltaY) > 50)) {
139+ swipeDone = true;
140+ }
141+ }
142+ }
143+ }
144+ }
145+}
qml/Dash/Previews/PreviewZoomableImage.qml+5−110
@@ -28,116 +28,11 @@ PreviewWidget {
2828 id: root
2929 implicitHeight: units.gu(22)
3030
31− Flickable {
32− id: flickable
33− objectName: "flickable"
34− clip: true
35− contentHeight: imageContainer.height
36− contentWidth: imageContainer.width
37− onHeightChanged: image.calculateSize()
38− onWidthChanged: image.calculateSize()
31+ ZoomableImage {
32+ id: image
33+ objectName: "image"
34+ source: widgetData["source"]
35+ zoomable: widgetData["zoomable"] ? widgetData["zoomable"] : false
3936 anchors.fill: parent
40−
41− Item {
42− id: imageContainer
43− objectName: "imageContainer"
44− width: Math.max(image.width * image.scale, flickable.width)
45− height: Math.max(image.height * image.scale, flickable.height)
46−
47− LazyImage {
48− id: image
49− objectName: "image"
50− property real prevScale
51− smooth: !flickable.movingVertically
52− anchors.centerIn: parent
53− fillMode: Image.PreserveAspectFit
54− source: widgetData["source"]
55−
56− function calculateSize() {
57− image.scale = Math.min(flickable.width / image.width, flickable.height / image.height) * 0.98;
58− pinchArea.minScale = image.scale;
59− prevScale = Math.min(image.scale, 1);
60− }
61−
62− onScaleChanged: {
63− if ((width * scale) > flickable.width) {
64− var xoff = (flickable.width / 2 + flickable.contentX) * scale / prevScale;
65− flickable.contentX = xoff - flickable.width / 2;
66− }
67− if ((height * scale) > flickable.height) {
68− var yoff = (flickable.height / 2 + flickable.contentY) * scale / prevScale;
69− flickable.contentY = yoff - flickable.height / 2;
70− }
71−
72− prevScale = scale;
73− }
74−
75− onStateChanged: {
76− if (state == "ready") {
77− image.width = flickable.width;
78− image.height = flickable.height;
79− calculateSize();
80− }
81− }
82− }
83− }
84−
85− PinchArea {
86− id: pinchArea
87− objectName: "pinchArea"
88− property real minScale: 1.0
89− property real lastScale: 1.0
90− anchors.fill: parent
91− enabled: widgetData["zoomable"] ? widgetData["zoomable"] : false
92−
93− pinch.target: image
94− pinch.minimumScale: minScale
95−
96− onPinchFinished: flickable.returnToBounds()
97− }
98−
99− MouseArea {
100− id: mouseArea
101− objectName: "mouseArea"
102−
103− anchors.fill: parent
104− property bool doubleClicked: false
105− property bool swipeDone: false
106− property int startX
107− property int startY
108− property real startScale: pinchArea.minScale
109− enabled: widgetData["zoomable"] ? widgetData["zoomable"] : false
110−
111− onWheel: {
112− if (wheel.angleDelta.y > 0) {
113− startScale = image.scale;
114− image.scale = startScale + 0.1;
115− } else if (wheel.angleDelta.y < 0) {
116− startScale = image.scale;
117− if (image.scale > 0.1) {
118− image.scale = startScale - 0.1;
119− }
120− }
121− wheel.accepted = true;
122− }
123−
124− onPressed: {
125− startX = (mouse.x / image.scale);
126− startY = (mouse.y / image.scale);
127− startScale = image.scale;
128− }
129−
130− onReleased: {
131− if (image.scale == startScale) {
132− var deltaX = (mouse.x / image.scale) - startX;
133− var deltaY = (mouse.y / image.scale) - startY;
134−
135− if (image.scale == pinchArea.minScale &&
136− (Math.abs(deltaX) > 50 || Math.abs(deltaY) > 50)) {
137− swipeDone = true;
138− }
139− }
140− }
141− }
14237 }
14338}
tests/qmltests/CMakeLists.txt+1
@@ -40,6 +40,7 @@ add_qml_test(Components Stage IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS}
4040 ${CMAKE_BINARY_DIR}/plugins)
4141add_qml_test(Components Tile)
4242add_qml_test(Components PageHeaderLabel)
43+add_qml_test(Components ZoomableImage)
4344add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4445add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4546add_qml_test(Dash DashPreview)
tests/qmltests/Components/tst_ZoomableImage.qmlnew file+145
@@ -0,0 +1,145 @@
1+/*
2+ * Copyright 2014 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import QtTest 1.0
19+import "../../../qml/Components"
20+import Unity.Test 0.1 as UT
21+
22+Rectangle {
23+ id: root
24+ width: units.gu(40)
25+ height: units.gu(80)
26+ color: "lightgrey"
27+
28+ property var widgetData0: {
29+ "source": "",
30+ "zoomable": false
31+ }
32+
33+ property var widgetData1: {
34+ "source": "../../../qml/graphics/phone_background.jpg",
35+ "zoomable": false
36+ }
37+
38+ property var widgetData2: {
39+ "source": "../../../qml/Dash/graphics/phone/screenshots/gallery@12.png",
40+ "zoomable": true
41+ }
42+
43+ ZoomableImage {
44+ id: zoomableImage
45+ width: parent.width
46+ anchors.fill: parent
47+ }
48+
49+ SignalSpy {
50+ id: signalSpy
51+ }
52+
53+ UT.UnityTestCase {
54+ name: "ZoomableImageTest"
55+ when: windowShown
56+
57+ function test_loadImage() {
58+ var image = findChild(zoomableImage, "image");
59+
60+ zoomableImage.source = widgetData0["source"];
61+ zoomableImage.zoomable = widgetData0["zoomable"];
62+ waitForRendering(zoomableImage);
63+ tryCompare(image, "state", "default");
64+
65+ signalSpy.signalName = "onStateChanged";
66+ signalSpy.target = image;
67+ signalSpy.clear();
68+
69+ zoomableImage.source = widgetData1["source"];
70+ zoomableImage.zoomable = widgetData1["zoomable"];
71+ waitForRendering(zoomableImage);
72+ tryCompare(image, "state", "ready");
73+ compare (signalSpy.count, 2);
74+ }
75+
76+ function test_zoomable_data() {
77+ return [ { source:widgetData2["source"],
78+ zoomable:false,
79+ answer1: true,
80+ answer2: false,
81+ answer3: true },
82+ { source:widgetData2["source"],
83+ zoomable:true,
84+ answer1: false,
85+ answer2: true,
86+ answer3: false }
87+ ]
88+ }
89+
90+ function test_zoomable(data) {
91+ var image = findChild(zoomableImage, "image");
92+
93+ signalSpy.signalName = "onScaleChanged";
94+ signalSpy.target = image;
95+ signalSpy.clear();
96+
97+ zoomableImage.source = data.source;
98+ zoomableImage.zoomable = data.zoomable;
99+ waitForRendering(zoomableImage);
100+
101+ tryCompare(image, "state", "ready");
102+ waitForRendering(image);
103+
104+ var event1 = touchEvent();
105+ var event2 = touchEvent();
106+ var x1Start = zoomableImage.width * 2 / 6;
107+ var y1Start = zoomableImage.height * 2 / 6;
108+ var x1End = zoomableImage.width * 1 / 6;
109+ var y1End = zoomableImage.height * 1 / 6;
110+ var x2Start = zoomableImage.width * 4 / 6;
111+ var y2Start = zoomableImage.height * 4 / 6;
112+ var x2End = zoomableImage.width * 5 / 6;
113+ var y2End = zoomableImage.height * 5 / 6;
114+
115+ var oldScale = image.scale;
116+
117+ mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
118+ mouseWheel(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2, 0, 10);
119+
120+ event1.press(0, x1Start, y1Start);
121+ event1.commit();
122+ event1.press(1, x2Start, y2Start);
123+ event1.commit();
124+
125+ for (var i = 0.0; i < 1.0; i += 0.02) {
126+ event1.move(0, x1Start + (x1End - x1Start) * i, y1Start + (y1End - y1Start) * i);
127+ event1.move(1, x2Start + (x2End - x2Start) * i, y2Start + (y2End - y2Start) * i);
128+ event1.commit();
129+ wait(30);
130+ }
131+
132+ event1.release(0, x1End, y1End);
133+ event1.commit();
134+ event1.release(1, x2End, y2End);
135+ event1.commit();
136+
137+ var newScale = image.scale;
138+
139+ compare(newScale == oldScale, data.answer1, "scale factor error");
140+ compare(newScale > oldScale, data.answer2, "scale factor error");
141+ compare(signalSpy.count == 0, data.answer3, "scale signal error");
142+ }
143+
144+ }
145+}
tests/qmltests/Dash/Previews/tst_PreviewZoomableImage.qml−2
@@ -89,8 +89,6 @@ Rectangle {
8989 mouseMove(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2);
9090 mouseWheel(zoomableImage, zoomableImage.width / 2, zoomableImage.height / 2, 0, 10);
9191
92− wait(300);
93−
9492 event1.press(0, x1Start, y1Start);
9593 event1.commit();
9694 event1.press(1, x2Start, y2Start);