Merge lp:camera-app/staging into lp:camera-app

Proposed by Florian Boucault
Status: Merged
Approved by: Florian Boucault
Approved revision: 686
Merged at revision: 643
Proposed branch: lp:camera-app/staging
Merge into: lp:camera-app
Diff against target: 1655 lines (+587/-221)
25 files modified
GalleryView.qml (+4/-4)
GalleryViewHeader.qml (+3/-0)
MimeTypeMapper.js (+1/-1)
OptionButton.qml (+2/-2)
PhotogridView.qml (+42/-6)
SlideshowView.qml (+16/-6)
UnableShareDialog.qml (+34/-0)
ViewFinderOverlay.qml (+16/-9)
ViewFinderOverlayLoader.qml (+3/-1)
ViewFinderView.qml (+14/-15)
camera-app.qml (+41/-18)
debian/control (+3/-0)
tests/autopilot/camera_app/emulators/main_window.py (+77/-9)
tests/autopilot/camera_app/emulators/panel.py (+8/-2)
tests/autopilot/camera_app/tests/__init__.py (+14/-8)
tests/autopilot/camera_app/tests/test_capture.py (+67/-20)
tests/autopilot/camera_app/tests/test_diskspace.py (+4/-7)
tests/autopilot/camera_app/tests/test_flash.py (+12/-13)
tests/autopilot/camera_app/tests/test_focus.py (+17/-18)
tests/autopilot/camera_app/tests/test_gallery_view.py (+118/-41)
tests/autopilot/camera_app/tests/test_options.py (+0/-8)
tests/autopilot/camera_app/tests/test_photo_editor.py (+2/-22)
tests/autopilot/camera_app/tests/test_zoom.py (+0/-10)
tests/unittests/CMakeLists.txt (+1/-1)
tests/unittests/tst_PhotogridView.qml (+88/-0)
To merge this branch: bzr merge lp:camera-app/staging
Reviewer Review Type Date Requested Status
Ubuntu Phablet Team Pending
Review via email: mp+288646@code.launchpad.net

Commit message

New release:
- Only accept manual focus on points inside the viewfinder.
- viewFinderView.captureMode can now be set before the camera is ready. Take advantage of that to fix bug #1545123
- Disable controls and prevent navigation while a delayed/timed shoot is ongoing.
- Display a stock icon as the video thumbnail when thumbnailer fails (which it will do now if it can't process a video)
- Allow sharing multiple files, except if they are mixed content
- Only use full screen in staged mode.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'GalleryView.qml'
2--- GalleryView.qml 2016-02-23 11:46:52 +0000
3+++ GalleryView.qml 2016-03-23 15:42:41 +0000
4@@ -16,7 +16,7 @@
5
6 import QtQuick 2.4
7 import Ubuntu.Components 1.3
8-import Ubuntu.Content 0.1
9+import Ubuntu.Content 1.3
10 import Ubuntu.Thumbnailer 0.1
11 import CameraApp 0.1
12 import "MimeTypeMapper.js" as MimeTypeMapper
13@@ -35,7 +35,7 @@
14 StorageLocations.removableStorageVideosLocation]
15 typeFilters: !main.contentExportMode ? [ "image", "video" ]
16 : [MimeTypeMapper.contentTypeToMimeType(main.transferContentType)]
17- singleSelectionOnly: main.transfer.selectionType === ContentTransfer.Single
18+ singleSelectionOnly: main.contentExportMode && main.transfer.selectionType === ContentTransfer.Single
19 }
20
21 property bool gridMode: main.contentExportMode
22@@ -58,7 +58,6 @@
23
24 function exitUserSelectionMode() {
25 model.clearSelection();
26- model.singleSelectionOnly = true;
27 userSelectionMode = false;
28 }
29
30@@ -76,6 +75,7 @@
31 model: galleryView.model
32 visible: opacity != 0.0
33 inView: galleryView.inView && galleryView.currentView == slideshowView
34+ focus: inView
35 inSelectionMode: main.contentExportMode || galleryView.userSelectionMode
36 onToggleSelection: model.toggleSelected(currentIndex)
37 onToggleHeader: header.toggle();
38@@ -89,6 +89,7 @@
39 model: galleryView.model
40 visible: opacity != 0.0
41 inView: galleryView.inView && galleryView.currentView == photogridView
42+ focus: inView
43 inSelectionMode: main.contentExportMode || galleryView.userSelectionMode
44 onPhotoClicked: {
45 slideshowView.showPhotoAtIndex(index);
46@@ -97,7 +98,6 @@
47 onPhotoPressAndHold: {
48 if (!galleryView.userSelectionMode) {
49 galleryView.userSelectionMode = true;
50- model.singleSelectionOnly = false;
51 model.toggleSelected(index);
52 }
53 }
54
55=== modified file 'GalleryViewHeader.qml'
56--- GalleryViewHeader.qml 2016-02-25 13:01:05 +0000
57+++ GalleryViewHeader.qml 2016-03-23 15:42:41 +0000
58@@ -153,6 +153,7 @@
59
60 Item {
61 id: actionsDrawer
62+ objectName: "actionsDrawer"
63
64 anchors {
65 top: parent.bottom
66@@ -170,6 +171,8 @@
67 }
68
69 property bool opened: false
70+ property bool fullyOpened: actionsColumn.y == 0
71+ property bool fullyClosed: actionsColumn.y == -actionsColumn.height
72 property list<Action> actions
73
74 onOpenedChanged: {
75
76=== modified file 'MimeTypeMapper.js'
77--- MimeTypeMapper.js 2014-07-31 18:41:17 +0000
78+++ MimeTypeMapper.js 2016-03-23 15:42:41 +0000
79@@ -17,7 +17,7 @@
80 */
81
82 .pragma library
83-.import Ubuntu.Content 0.1 as UbuntuContent
84+.import Ubuntu.Content 1.3 as UbuntuContent
85
86 function startsWith(string, prefix) {
87 return string.indexOf(prefix) === 0;
88
89=== modified file 'OptionButton.qml'
90--- OptionButton.qml 2015-11-24 15:44:58 +0000
91+++ OptionButton.qml 2016-03-23 15:42:41 +0000
92@@ -26,8 +26,8 @@
93 iconName: !model.get(model.selectedIndex).icon ? model.icon : model.get(model.selectedIndex).icon
94 iconSource: (model && model.iconSource) ? model.iconSource : ""
95 on: model.isToggle ? model.get(model.selectedIndex).value : true
96- enabled: model.available
97+ enabled: model.visible && model.available
98 label: model.label
99- visible: model.visible
100+ visible: model.visible && model.available
101 automaticOrientation: false
102 }
103
104=== modified file 'PhotogridView.qml'
105--- PhotogridView.qml 2016-02-23 11:46:52 +0000
106+++ PhotogridView.qml 2016-03-23 15:42:41 +0000
107@@ -18,11 +18,11 @@
108 import Ubuntu.Components 1.3
109 import Ubuntu.Components.Popups 1.3
110 import Ubuntu.Thumbnailer 0.1
111-import Ubuntu.Content 0.1
112+import Ubuntu.Content 1.3
113 import CameraApp 0.1
114 import "MimeTypeMapper.js" as MimeTypeMapper
115
116-Item {
117+FocusScope {
118 id: photogridView
119
120 property var model
121@@ -40,11 +40,14 @@
122 Action {
123 text: i18n.tr("Share")
124 iconName: "share"
125- enabled: model.selectedFiles.length <= 1
126+ enabled: model.selectedFiles.length > 0
127 onTriggered: {
128- if (model.selectedFiles.length > 0) {
129- var dialog = PopupUtils.open(sharePopoverComponent)
130- dialog.parent = photogridView
131+ // Display a warning message if we are attempting to share mixed
132+ // content, as the framework does not properly support this
133+ if (selectionContainsMixedMedia()) {
134+ PopupUtils.open(unableShareDialogComponent).parent = photogridView;
135+ } else {
136+ PopupUtils.open(sharePopoverComponent).parent = photogridView;
137 }
138 }
139 },
140@@ -60,6 +63,19 @@
141 }
142 ]
143
144+ function selectionContainsMixedMedia() {
145+ var selection = model.selectedFiles;
146+ var lastType = model.get(selection[0], "fileType");
147+ for (var i = 1; i < selection.length; i++) {
148+ var type = model.get(selection[i], "fileType");
149+ if (type !== lastType) {
150+ return true;
151+ }
152+ lastType = type;
153+ }
154+ return false;
155+ }
156+
157 function showPhotoAtIndex(index) {
158 gridView.positionViewAtIndex(index, GridView.Center);
159 }
160@@ -139,6 +155,16 @@
161 visible: isVideo
162 }
163
164+ Icon {
165+ objectName: "thumbnailLoadingErrorIcon"
166+ anchors.centerIn: parent
167+ width: units.gu(6)
168+ height: width
169+ name: cellDelegate.isVideo ? "stock_video" : "stock_image"
170+ color: "white"
171+ opacity: thumbnail.status == Image.Error ? 1.0 : 0.0
172+ }
173+
174 MouseArea {
175 anchors.fill: parent
176 onClicked: photogridView.photoClicked(index)
177@@ -160,6 +186,7 @@
178 visible: inSelectionMode
179
180 Icon {
181+ objectName: "mediaItemCheckBox"
182 anchors.centerIn: parent
183 width: parent.width * 0.8
184 height: parent.height * 0.8
185@@ -225,4 +252,13 @@
186 onVisibleChanged: photogridView.toggleHeader()
187 }
188 }
189+
190+ Component {
191+ id: unableShareDialogComponent
192+ UnableShareDialog {
193+ objectName: "unableShareDialog"
194+ onVisibleChanged: photogridView.toggleHeader()
195+ }
196+ }
197+
198 }
199
200=== modified file 'SlideshowView.qml'
201--- SlideshowView.qml 2016-02-23 11:46:52 +0000
202+++ SlideshowView.qml 2016-03-23 15:42:41 +0000
203@@ -18,12 +18,12 @@
204 import Ubuntu.Components 1.3
205 import Ubuntu.Components.ListItems 1.3 as ListItems
206 import Ubuntu.Components.Popups 1.3
207-import Ubuntu.Content 0.1
208+import Ubuntu.Content 1.3
209 import Ubuntu.Thumbnailer 0.1
210 import CameraApp 0.1
211 import "MimeTypeMapper.js" as MimeTypeMapper
212
213-Item {
214+FocusScope {
215 id: slideshowView
216
217 property var model
218@@ -111,14 +111,12 @@
219
220 anchors.fill: parent
221 model: slideshowView.model
222+ focus: true
223 orientation: ListView.Horizontal
224 boundsBehavior: Flickable.StopAtBounds
225 cacheBuffer: width
226 highlightRangeMode: ListView.StrictlyEnforceRange
227- // FIXME: this disables the animation introduced by highlightRangeMode
228- // happening setting currentIndex; it is necessary at least because we
229- // were hitting https://bugreports.qt-project.org/browse/QTBUG-41035
230- highlightMoveDuration: 0
231+ highlightMoveDuration: UbuntuAnimation.FastDuration
232 snapMode: ListView.SnapOneItem
233 onCountChanged: {
234 // currentIndex is -1 by default and stays so until manually set to something else
235@@ -140,6 +138,7 @@
236 }
237 delegate: Item {
238 id: delegate
239+ objectName: "mediaItem" + index
240 property bool pinchInProgress: zoomPinchArea.active
241 property string url: fileURL
242 property bool isSelected: selected
243@@ -183,6 +182,7 @@
244 property real maximumZoom: 3.0
245 property bool active: false
246 property var center
247+ enabled: !media.isVideo
248
249 onPinchStarted: {
250 active = true;
251@@ -258,6 +258,16 @@
252 }
253 fillMode: Image.PreserveAspectFit
254 }
255+
256+ Icon {
257+ objectName: "thumbnailLoadingErrorIcon"
258+ anchors.centerIn: parent
259+ width: units.gu(30)
260+ height: width
261+ name: media.isVideo ? "stock_video" : "stock_image"
262+ color: "white"
263+ opacity: image.status == Image.Error ? 1.0 : 0.0
264+ }
265 }
266
267 Icon {
268
269=== added file 'UnableShareDialog.qml'
270--- UnableShareDialog.qml 1970-01-01 00:00:00 +0000
271+++ UnableShareDialog.qml 2016-03-23 15:42:41 +0000
272@@ -0,0 +1,34 @@
273+/*
274+ * Copyright (C) 2016 Canonical Ltd
275+ *
276+ * This program is free software: you can redistribute it and/or modify
277+ * it under the terms of the GNU General Public License version 3 as
278+ * published by the Free Software Foundation.
279+ *
280+ * This program is distributed in the hope that it will be useful,
281+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
282+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
283+ * GNU General Public License for more details.
284+ *
285+ * You should have received a copy of the GNU General Public License
286+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
287+ */
288+
289+import QtQuick 2.4
290+import Ubuntu.Components 1.3
291+import Ubuntu.Components.Popups 1.3
292+
293+Dialog {
294+ id: dialog
295+ objectName: "unableShareDialog"
296+
297+ title: i18n.tr("Unable to share")
298+ text: i18n.tr("Unable to share photos and videos at the same time")
299+
300+ Button {
301+ objectName: "unableShareDialogOk"
302+ text: i18n.tr("Ok")
303+ color: UbuntuColors.orange
304+ onClicked: PopupUtils.close(dialog);
305+ }
306+}
307
308=== modified file 'ViewFinderOverlay.qml'
309--- ViewFinderOverlay.qml 2016-02-23 11:46:52 +0000
310+++ ViewFinderOverlay.qml 2016-03-23 15:42:41 +0000
311@@ -33,6 +33,7 @@
312 property var controls: controls
313 property var settings: settings
314 property bool readyForCapture
315+ property int sensorOrientation
316
317 function showFocusRing(x, y) {
318 focusRing.center = Qt.point(x, y);
319@@ -289,6 +290,7 @@
320 id: bottomEdgeClose
321 anchors.fill: parent
322 onClicked: optionsOverlayClose()
323+ enabled: !camera.timedCaptureInProgress
324 }
325
326 OrientationHelper {
327@@ -305,8 +307,9 @@
328 height: optionsOverlayLoader.height
329 onOpenedChanged: optionsOverlayLoader.item.closeValueSelector()
330 enabled: camera.videoRecorder.recorderState == CameraRecorder.StoppedState
331- && !camera.photoCaptureInProgress
332+ && !camera.photoCaptureInProgress && !camera.timedCaptureInProgress
333 opacity: enabled ? 1.0 : 0.3
334+ property bool ready: optionsOverlayLoader.status == Loader.Ready
335
336 /* At startup, opened is false and 'bottomEdge.height' is 0 until
337 optionsOverlayLoader has finished loading. When that happens
338@@ -652,13 +655,15 @@
339 enabled: visible
340
341 function timedShoot(secs) {
342+ camera.timedCaptureInProgress = true;
343 timedShootFeedback.start();
344 shootingTimer.remainingSecs = secs;
345 shootingTimer.start();
346 }
347
348 function cancelTimedShoot() {
349- if (shootingTimer.running) {
350+ if (camera.timedCaptureInProgress) {
351+ camera.timedCaptureInProgress = false;
352 shootingTimer.stop();
353 timedShootFeedback.stop();
354 }
355@@ -692,9 +697,8 @@
356 break;
357 }
358
359- if (Screen.primaryOrientation == Qt.PortraitOrientation) {
360- orientation += 90;
361- }
362+ // account for the orientation of the sensor
363+ orientation -= viewFinderOverlay.sensorOrientation;
364
365 if (camera.captureMode == Camera.CaptureVideo) {
366 if (main.contentExportMode) {
367@@ -774,6 +778,7 @@
368 onTriggered: {
369 if (remainingSecs == 0) {
370 running = false;
371+ camera.timedCaptureInProgress = false;
372 controls.shoot();
373 timedShootFeedback.stop();
374 } else {
375@@ -819,7 +824,7 @@
376 iconName: (camera.captureMode == Camera.CaptureStillImage) ? "camcorder" : "camera-symbolic"
377 onClicked: controls.changeRecordMode()
378 enabled: camera.videoRecorder.recorderState == CameraRecorder.StoppedState && !main.contentExportMode
379- && !camera.photoCaptureInProgress
380+ && !camera.photoCaptureInProgress && !camera.timedCaptureInProgress
381 }
382
383 ShootButton {
384@@ -833,6 +838,7 @@
385 }
386
387 enabled: viewFinderOverlay.readyForCapture && !storageMonitor.diskSpaceCriticallyLow
388+ && !camera.timedCaptureInProgress
389 state: (camera.captureMode == Camera.CaptureVideo) ?
390 ((camera.videoRecorder.recorderState == CameraRecorder.StoppedState) ? "record_off" : "record_on") :
391 "camera"
392@@ -869,7 +875,7 @@
393 }
394
395 enabled: !camera.switchInProgress && camera.videoRecorder.recorderState == CameraRecorder.StoppedState
396- && !camera.photoCaptureInProgress
397+ && !camera.photoCaptureInProgress && !camera.timedCaptureInProgress
398 iconName: "camera-flip"
399 onClicked: controls.switchCamera()
400 }
401@@ -892,7 +898,7 @@
402 property real maximumScale: 3.0
403 property bool active: false
404
405- enabled: !camera.photoCaptureInProgress
406+ enabled: !camera.photoCaptureInProgress && !camera.timedCaptureInProgress
407 onPinchStarted: {
408 active = true;
409 initialZoom = zoomControl.value;
410@@ -910,6 +916,7 @@
411
412 MouseArea {
413 id: manualFocusMouseArea
414+ objectName: "manualFocusMouseArea"
415 anchors {
416 fill: parent
417 // Pinch gestures need more clearance at the edges of the screen, but
418@@ -918,7 +925,7 @@
419 rightMargin: -bottomEdgeIndicators.height
420 }
421 enabled: camera.focus.isFocusPointModeSupported(Camera.FocusPointCustom) &&
422- !camera.photoCaptureInProgress
423+ !camera.photoCaptureInProgress && !camera.timedCaptureInProgress
424 onClicked: {
425 camera.manualFocus(mouse.x, mouse.y);
426 mouse.accepted = false;
427
428=== modified file 'ViewFinderOverlayLoader.qml'
429--- ViewFinderOverlayLoader.qml 2016-02-23 11:46:52 +0000
430+++ ViewFinderOverlayLoader.qml 2016-03-23 15:42:41 +0000
431@@ -25,6 +25,7 @@
432 property var controls: loader.item ? loader.item.controls : null
433 property var settings: loader.item.settings
434 property bool readyForCapture
435+ property int sensorOrientation
436
437 function showFocusRing(x, y) {
438 loader.item.showFocusRing(x, y);
439@@ -37,7 +38,8 @@
440 asynchronous: true
441 Component.onCompleted: {
442 loader.setSource("ViewFinderOverlay.qml", { "camera": loader.camera,
443- "readyForCapture": Qt.binding(function() { return loader.readyForCapture})
444+ "readyForCapture": Qt.binding(function() { return loader.readyForCapture}),
445+ "sensorOrientation": Qt.binding(function () {return loader.sensorOrientation})
446 });
447 }
448 }
449
450=== modified file 'ViewFinderView.qml'
451--- ViewFinderView.qml 2016-02-23 11:46:52 +0000
452+++ ViewFinderView.qml 2016-03-23 15:42:41 +0000
453@@ -21,9 +21,9 @@
454 import QtMultimedia 5.0
455 import CameraApp 0.1
456 import QtGraphicalEffects 1.0
457-import Ubuntu.Content 0.1
458+import Ubuntu.Content 1.3
459
460-Item {
461+FocusScope {
462 id: viewFinderView
463
464 property bool overlayVisible: true
465@@ -60,11 +60,15 @@
466 property bool failedToConnect: false
467
468 function manualFocus(x, y) {
469- viewFinderOverlay.showFocusRing(x, y);
470- autoFocusTimer.restart();
471- focus.focusMode = Camera.FocusAuto;
472- focus.customFocusPoint = viewFinder.mapPointToSourceNormalized(Qt.point(x, y));
473- focus.focusPointMode = Camera.FocusPointCustom;
474+ var normalizedPoint = viewFinder.mapPointToSourceNormalized(Qt.point(x, y - viewFinder.y));
475+ if (normalizedPoint.x >= 0.0 && normalizedPoint.x <= 1.0 &&
476+ normalizedPoint.y >= 0.0 && normalizedPoint.y <= 1.0) {
477+ viewFinderOverlay.showFocusRing(x, y);
478+ autoFocusTimer.restart();
479+ focus.focusMode = Camera.FocusAuto;
480+ focus.customFocusPoint = normalizedPoint;
481+ focus.focusPointMode = Camera.FocusPointCustom;
482+ }
483 }
484
485 function autoFocus() {
486@@ -96,6 +100,7 @@
487 property alias maximumZoom: camera.maximumDigitalZoom
488 property bool switchInProgress: false
489 property bool photoCaptureInProgress: false
490+ property bool timedCaptureInProgress: false
491
492 onPhotoCaptureInProgressChanged: {
493 if (main.contentExportMode && camera.photoCaptureInProgress) {
494@@ -110,14 +115,6 @@
495 if (photoRollHint.necessary && !main.transfer) photoRollHint.enable();
496 camera.photoCaptureInProgress = false;
497 }
498-
499- if (main.transfer) {
500- if (main.transfer.contentType === ContentType.Videos) {
501- viewFinderView.captureMode = Camera.CaptureVideo;
502- } else {
503- viewFinderView.captureMode = Camera.CaptureStillImage;
504- }
505- }
506 }
507 }
508
509@@ -265,6 +262,7 @@
510 // Set orientation only at startup because later on Screen.primaryOrientation
511 // may change.
512 orientation = Screen.primaryOrientation === Qt.PortraitOrientation ? -90 : 0;
513+ viewFinderOverlay.sensorOrientation = orientation;
514 }
515
516 transform: Rotation {
517@@ -421,6 +419,7 @@
518
519 PhotoRollHint {
520 id: photoRollHint
521+ objectName: "photoRollHint"
522 anchors.fill: parent
523 visible: enabled
524
525
526=== modified file 'camera-app.qml'
527--- camera-app.qml 2016-02-23 11:46:52 +0000
528+++ camera-app.qml 2016-03-23 15:42:41 +0000
529@@ -20,7 +20,7 @@
530 import Ubuntu.Components 1.3
531 import Ubuntu.Unity.Action 1.1 as UnityActions
532 import UserMetrics 0.1
533-import Ubuntu.Content 0.1
534+import Ubuntu.Content 1.3
535 import CameraApp 0.1
536
537 Window {
538@@ -30,6 +30,26 @@
539 height: units.gu(80)
540 color: "black"
541 title: "Camera"
542+ // special flag only supported by Unity8/MIR so far that hides the shell's
543+ // top panel in Staged mode
544+ flags: Qt.Window | 0x00800000
545+
546+ property int preFullScreenVisibility
547+
548+ function toggleFullScreen() {
549+ if (main.visibility != Window.FullScreen) {
550+ preFullScreenVisibility = main.visibility;
551+ main.visibility = Window.FullScreen;
552+ } else {
553+ main.visibility = preFullScreenVisibility;
554+ }
555+ }
556+
557+ function exitFullScreen() {
558+ if (main.visibility == Window.FullScreen) {
559+ main.visibility = preFullScreenVisibility;
560+ }
561+ }
562
563 UnityActions.ActionManager {
564 actions: [
565@@ -63,11 +83,7 @@
566
567 Component.onCompleted: {
568 i18n.domain = "camera-app";
569- if (!application.desktopMode) {
570- main.showFullScreen();
571- } else {
572- main.show();
573- }
574+ main.show();
575 }
576
577
578@@ -78,6 +94,15 @@
579 flickableDirection: state == "PORTRAIT" ? Flickable.HorizontalFlick : Flickable.VerticalFlick
580 boundsBehavior: Flickable.StopAtBounds
581
582+ Keys.onPressed: {
583+ if (event.key == Qt.Key_F11) {
584+ main.toggleFullScreen();
585+ event.accepted = true;
586+ }
587+ }
588+ Keys.onEscapePressed: main.exitFullScreen()
589+
590+
591 property real panesMargin: units.gu(1)
592 property real ratio
593 property int orientationAngle: Screen.angleBetween(Screen.primaryOrientation, Screen.orientation)
594@@ -135,7 +160,9 @@
595 }
596 }
597 ]
598- interactive: !viewFinderView.touchAcquired && !galleryView.touchAcquired && !viewFinderView.camera.photoCaptureInProgress
599+ interactive: !viewFinderView.touchAcquired && !galleryView.touchAcquired
600+ && !viewFinderView.camera.photoCaptureInProgress
601+ && !viewFinderView.camera.timedCaptureInProgress
602
603 Component.onCompleted: {
604 // FIXME: workaround for qtubuntu not returning values depending on the grid unit definition
605@@ -256,6 +283,7 @@
606 height: viewSwitcher.height
607 overlayVisible: !viewSwitcher.moving && !viewSwitcher.flicking
608 inView: viewSwitcher.ratio < 0.5
609+ focus: !galleryView.focus
610 opacity: inView ? 1.0 : 0.0
611 onPhotoTaken: {
612 galleryView.prependMediaToModel(filePath);
613@@ -273,6 +301,7 @@
614 width: viewSwitcher.width
615 height: viewSwitcher.height
616 inView: viewSwitcher.ratio > 0.0
617+ focus: inView
618 onExit: viewSwitcher.switchToViewFinder()
619 opacity: inView ? 1.0 : 0.0
620 }
621@@ -280,7 +309,7 @@
622
623 property bool contentExportMode: transfer !== null
624 property var transfer: null
625- property var transferContentType: ContentType.Pictures
626+ property var transferContentType: transfer ? transfer.contentType : "image"
627
628 function exportContent(urls) {
629 if (!main.transfer) return;
630@@ -312,16 +341,10 @@
631 onExportRequested: {
632 viewSwitcher.switchToViewFinder();
633
634- // The exportRequested event can arrive before or after the
635- // app is active, but setting the recording type before the
636- // capture becomes ready does not have any effect.
637- // See camera.imageCapture.onReadyChanged for the other case.
638- if (viewFinderView.camera.imageCapture.ready) {
639- if (transfer.contentType === ContentType.Videos) {
640- viewFinderView.captureMode = Camera.CaptureVideo;
641- } else {
642- viewFinderView.captureMode = Camera.CaptureStillImage;
643- }
644+ if (transfer.contentType === ContentType.Videos) {
645+ viewFinderView.captureMode = Camera.CaptureVideo;
646+ } else {
647+ viewFinderView.captureMode = Camera.CaptureStillImage;
648 }
649 main.transfer = transfer;
650 }
651
652=== modified file 'debian/control'
653--- debian/control 2015-07-31 02:23:54 +0000
654+++ debian/control 2016-03-23 15:42:41 +0000
655@@ -13,11 +13,14 @@
656 qtbase5-dev,
657 qtdeclarative5-dev,
658 qml-module-qtquick2,
659+ qml-module-qtpositioning,
660 qml-module-qttest,
661 qtdeclarative5-ubuntu-ui-toolkit-plugin,
662 qtdeclarative5-unity-action-plugin (>= 1.1.0),
663 qtdeclarative5-usermetrics0.1,
664 qtdeclarative5-ubuntu-content1,
665+ qtdeclarative5-ubuntu-thumbnailer0.1,
666+ qtdeclarative5-ubuntu-ui-extras0.2,
667 qtmultimedia5-dev,
668 libusermetricsinput1-dev,
669 gettext,
670
671=== modified file 'tests/autopilot/camera_app/emulators/main_window.py'
672--- tests/autopilot/camera_app/emulators/main_window.py 2015-11-25 17:00:31 +0000
673+++ tests/autopilot/camera_app/emulators/main_window.py 2016-03-23 15:42:41 +0000
674@@ -36,10 +36,29 @@
675 """Returns the gallery view"""
676 return self.app.wait_select_single("GalleryView")
677
678+ def get_media(self, index=0):
679+ """Returns media at index in the currently loaded view in gallery"""
680+ gallery = self.get_gallery()
681+ view = gallery.select_single("SlideshowView")
682+ if not view.visible:
683+ view = gallery.select_single("PhotogridView")
684+
685+ return view.wait_select_single(objectName="mediaItem" + str(index))
686+
687+ def get_broken_media_icon(self, index=0):
688+ """Returns the broken media icon"""
689+ media = self.get_media(index)
690+ return media.wait_select_single(objectName="thumbnailLoadingErrorIcon")
691+
692 def get_no_media_hint(self):
693 """Returns the Item representing the hint that no media is available"""
694 return self.app.wait_select_single(objectName="noMediaHint")
695
696+ def get_focus_mouse_area(self):
697+ """Returns the focus mouse area"""
698+ return self.app.wait_select_single("QQuickMouseArea",
699+ objectName="manualFocusMouseArea")
700+
701 def get_focus_ring(self):
702 """Returns the focus ring of the camera"""
703 return self.app.wait_select_single("FocusRing")
704@@ -49,9 +68,9 @@
705 return self.app.wait_select_single("ShootButton")
706
707 def get_photo_roll_hint(self):
708- """Returns the layer that serves at hinting to the existence of the
709- photo roll"""
710- return self.app.wait_select_single("PhotoRollHint")
711+ """Returns the photo roll hint"""
712+ return self.app.wait_select_single("PhotoRollHint",
713+ objectName="photoRollHint")
714
715 def get_record_control(self):
716 """Returns the button that toggles between photo and video recording"""
717@@ -63,8 +82,12 @@
718 in settingsProperty
719 """
720 optionButtons = self.app.select_many("OptionButton")
721- return next(button for button in optionButtons
722- if button.settingsProperty == settingsProperty)
723+ optionButton = next(button for button in optionButtons
724+ if button.settingsProperty == settingsProperty)
725+ if optionButton.visible:
726+ return optionButton
727+ else:
728+ return None
729
730 def get_flash_button(self):
731 """Returns the flash control button of the camera"""
732@@ -90,6 +113,10 @@
733 """Returns the video resolution button of the camera"""
734 return self.get_option_button("videoResolution")
735
736+ def get_timer_delay_button(self):
737+ """Returns the timer delay option button of the camera"""
738+ return self.get_option_button("selfTimerDelay")
739+
740 def get_stop_watch(self):
741 """Returns the stop watch when using the record button of the camera"""
742 return self.app.wait_select_single("StopWatch")
743@@ -139,30 +166,71 @@
744 except:
745 return None
746
747+ def open_actions_drawer(self, gallery):
748+ """Opens action drawer of gallery"""
749+ actionsDrawerButton = gallery.wait_select_single(
750+ "IconButton",
751+ objectName="additionalActionsButton")
752+ self.app.pointing_device.move_to_object(actionsDrawerButton)
753+ self.app.pointing_device.click()
754+ actionsDrawer = gallery.wait_select_single("QQuickItem",
755+ objectName="actionsDrawer")
756+ actionsDrawer.fullyOpened.wait_for(True)
757+
758+ def close_actions_drawer(self, gallery):
759+ """Closes action drawer of gallery"""
760+ actionsDrawerButton = gallery.wait_select_single(
761+ "IconButton",
762+ objectName="additionalActionsButton")
763+ self.app.pointing_device.move_to_object(actionsDrawerButton)
764+ self.app.pointing_device.click()
765+ actionsDrawer = gallery.wait_select_single("QQuickItem",
766+ objectName="actionsDrawer")
767+ actionsDrawer.fullyClosed.wait_for(True)
768+
769 def swipe_to_gallery(self, testCase):
770 view_switcher = self.get_view_switcher()
771+ viewfinder = self.get_viewfinder()
772+ view_switcher.interactive.wait_for(True)
773+ view_switcher.enabled.wait_for(True)
774+ view_switcher.settling.wait_for(False)
775+ view_switcher.switching.wait_for(False)
776+ viewfinder.inView.wait_for(True)
777 x, y = view_switcher.x, view_switcher.y
778 w, h = view_switcher.width, view_switcher.height
779
780 tx = x + (w // 2)
781 ty = y + (h // 2)
782
783- self.app.pointing_device.drag(tx, ty, x, ty, rate=1)
784- viewfinder = self.get_viewfinder()
785+ # FIXME: a rate higher than 1 does not always make view_switcher move
786+ self.app.pointing_device.drag(tx, ty, x, ty, rate=1,
787+ time_between_events=0.0001)
788+
789 testCase.assertThat(viewfinder.inView, Eventually(Equals(False)))
790+ view_switcher.settling.wait_for(False)
791+ view_switcher.switching.wait_for(False)
792
793 def swipe_to_viewfinder(self, testCase):
794 view_switcher = self.get_view_switcher()
795+ viewfinder = self.get_viewfinder()
796+ view_switcher.interactive.wait_for(True)
797+ view_switcher.enabled.wait_for(True)
798+ view_switcher.settling.wait_for(False)
799+ view_switcher.switching.wait_for(False)
800+ viewfinder.inView.wait_for(False)
801 x, y = view_switcher.x, view_switcher.y
802 w, h = view_switcher.width, view_switcher.height
803
804 tx = x + (w // 2)
805 ty = y + (h // 2)
806
807+ # FIXME: a rate higher than 1 does not always make view_switcher move
808 self.app.pointing_device.drag(
809- tx, ty, (tx + view_switcher.width // 2), ty, rate=1)
810- viewfinder = self.get_viewfinder()
811+ tx, ty, (tx + view_switcher.width // 2), ty, rate=1,
812+ time_between_events=0.0001)
813 testCase.assertThat(viewfinder.inView, Eventually(Equals(True)))
814+ view_switcher.settling.wait_for(False)
815+ view_switcher.switching.wait_for(False)
816
817 def switch_cameras(self):
818 # Swap cameras and wait for camera to settle
819
820=== modified file 'tests/autopilot/camera_app/emulators/panel.py'
821--- tests/autopilot/camera_app/emulators/panel.py 2015-12-01 09:03:34 +0000
822+++ tests/autopilot/camera_app/emulators/panel.py 2016-03-23 15:42:41 +0000
823@@ -24,6 +24,7 @@
824 :return: The panel.
825
826 """
827+ self.ready.wait_for(True)
828 self.animating.wait_for(False)
829 if not self.opened:
830 self._drag_to_open()
831@@ -38,11 +39,14 @@
832 start_y = y + self.height - 1
833 stop_y = y
834
835- self.pointing_device.drag(line_x, start_y, line_x, stop_y)
836+ # FIXME: a rate higher than 1 does not always make panel move
837+ self.pointing_device.drag(line_x, start_y, line_x, stop_y, rate=1,
838+ time_between_events=0.0001)
839
840 @autopilot_logging.log_action(logger.info)
841 def close(self):
842 """Close the panel if it's opened."""
843+ self.ready.wait_for(True)
844 self.animating.wait_for(False)
845 if self.opened:
846 self._drag_to_close()
847@@ -55,4 +59,6 @@
848 start_y = y
849 stop_y = y + self.height - 1
850
851- self.pointing_device.drag(line_x, start_y, line_x, stop_y)
852+ # FIXME: a rate higher than 1 does not always make panel move
853+ self.pointing_device.drag(line_x, start_y, line_x, stop_y, rate=1,
854+ time_between_events=0.0001)
855
856=== modified file 'tests/autopilot/camera_app/tests/__init__.py'
857--- tests/autopilot/camera_app/tests/__init__.py 2015-05-15 07:29:16 +0000
858+++ tests/autopilot/camera_app/tests/__init__.py 2016-03-23 15:42:41 +0000
859@@ -8,7 +8,6 @@
860 """Camera-app autopilot tests."""
861
862 import os
863-import time
864 import shutil
865 from pkg_resources import resource_filename
866
867@@ -42,6 +41,12 @@
868 sample_dir = resource_filename('camera_app', 'data')
869
870 def setUp(self):
871+ # Remove configuration file
872+ config_file = os.path.expanduser(
873+ "~/.config/com.ubuntu.camera/com.ubuntu.camera.conf")
874+ if os.path.exists(config_file):
875+ os.remove(config_file)
876+
877 self.pointing_device = Pointer(self.input_device_class.create())
878 super(CameraAppTestCase, self).setUp()
879 if os.path.exists(self.local_location):
880@@ -51,11 +56,7 @@
881 else:
882 self.launch_click_installed()
883
884- # wait and sleep as workaround for bug #1373039. To
885- # make sure large components get loaded asynchronously on start-up
886- # -- Chris Gagnon 11-17-2014
887 self.main_window.get_qml_view().visible.wait_for(True)
888- time.sleep(5)
889
890 def launch_test_local(self):
891 self.app = self.launch_test_application(
892@@ -107,6 +108,11 @@
893 shutil.copyfile(os.path.join(self.sample_dir, "sample.jpg"),
894 os.path.join(self.pictures_dir, "sample.jpg"))
895
896- def add_sample_video(self):
897- shutil.copyfile(os.path.join(self.sample_dir, "sample.mp4"),
898- os.path.join(self.videos_dir, "sample.mp4"))
899+ def add_sample_video(self, broken=False):
900+ if broken:
901+ path = os.path.join(self.videos_dir, "sample_broken.mp4")
902+ with open(path, "w") as video:
903+ video.write("I AM NOT A VIDEO")
904+ else:
905+ shutil.copyfile(os.path.join(self.sample_dir, "sample.mp4"),
906+ os.path.join(self.videos_dir, "sample.mp4"))
907
908=== modified file 'tests/autopilot/camera_app/tests/test_capture.py'
909--- tests/autopilot/camera_app/tests/test_capture.py 2015-12-01 09:03:34 +0000
910+++ tests/autopilot/camera_app/tests/test_capture.py 2016-03-23 15:42:41 +0000
911@@ -17,6 +17,7 @@
912 import unittest
913 import time
914 import os
915+import glob
916
917
918 class TestCapture(CameraAppTestCase):
919@@ -25,23 +26,10 @@
920 """ This is needed to wait for the application to start.
921 In the testfarm, the application may take some time to show up."""
922 def setUp(self):
923- # Remove configuration file where knowledge of the photo roll hint's
924- # necessity is stored
925- config_file = os.path.expanduser(
926- "~/.config/com.ubuntu.camera/com.ubuntu.camera.conf")
927- if os.path.exists(config_file):
928- os.remove(config_file)
929-
930 super(TestCapture, self).setUp()
931-
932- self.assertThat(
933- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
934 self.pictures_dir = os.path.expanduser("~/Pictures/com.ubuntu.camera")
935 self.videos_dir = os.path.expanduser("~/Videos/com.ubuntu.camera")
936
937- def tearDown(self):
938- super(TestCapture, self).tearDown()
939-
940 """Test taking a picture"""
941 def test_take_picture(self):
942 exposure_button = self.main_window.get_exposure_button()
943@@ -83,6 +71,62 @@
944 # check that the camera is able to capture another photo
945 self.assertThat(exposure_button.enabled, Eventually(Equals(True)))
946
947+ """Test taking a picture with a timer set"""
948+ def test_take_picture_with_timer(self):
949+ delay = 5
950+ self.enable_timer("%s seconds" % str(delay))
951+
952+ # start timed shoot
953+ shoot_button = self.main_window.get_exposure_button()
954+ self.assertThat(shoot_button.enabled, Eventually(Equals(True)))
955+ self.pointing_device.move_to_object(shoot_button)
956+ self.pointing_device.click()
957+
958+ switch_cameras_button = self.main_window.get_swap_camera_button()
959+ record_mode_button = self.main_window.get_record_control()
960+ view_switcher = self.main_window.get_view_switcher()
961+
962+ # controls and navigation should be disabled at this point
963+ self.assertThat(shoot_button.enabled,
964+ Eventually(Equals(True)))
965+ self.assertThat(switch_cameras_button.enabled,
966+ Eventually(Equals(True)))
967+ self.assertThat(record_mode_button.enabled,
968+ Eventually(Equals(True)))
969+ self.assertThat(view_switcher.interactive,
970+ Eventually(Equals(True)))
971+
972+ # after the delay controls and navigation should be re-enabled
973+ self.assertThat(shoot_button.enabled,
974+ Eventually(Equals(True), timeout=delay))
975+ self.assertThat(switch_cameras_button.enabled,
976+ Eventually(Equals(True), timeout=delay))
977+ self.assertThat(record_mode_button.enabled,
978+ Eventually(Equals(True), timeout=delay))
979+ self.assertThat(view_switcher.interactive,
980+ Eventually(Equals(True), timeout=delay))
981+
982+ def enable_timer(self, label_value):
983+ # open bottom edge
984+ bottom_edge = self.main_window.get_bottom_edge()
985+ bottom_edge.open()
986+
987+ # open video resolution option value selector showing the possible
988+ # values
989+ timer_delay_button = self.main_window.get_timer_delay_button()
990+ self.pointing_device.move_to_object(timer_delay_button)
991+ self.pointing_device.click()
992+ option_value_selector = self.main_window.get_option_value_selector()
993+ self.assertThat(
994+ option_value_selector.visible, Eventually(Equals(True)))
995+
996+ # select a 5 seconds delay
997+ option = self.main_window.get_option_value_button(label_value)
998+ self.pointing_device.move_to_object(option)
999+ self.pointing_device.click()
1000+
1001+ bottom_edge.close()
1002+
1003 def test_record_video(self):
1004 """Test clicking on the record control.
1005
1006@@ -208,7 +252,7 @@
1007 def get_first_picture(self, timeout=10):
1008 pictures = []
1009 for i in range(0, timeout):
1010- pictures = os.listdir(self.pictures_dir)
1011+ pictures = glob.glob(os.path.join(self.pictures_dir, "*.jpg"))
1012 if len(pictures) != 0:
1013 break
1014 time.sleep(1)
1015@@ -236,9 +280,11 @@
1016 return quality
1017
1018 def dismiss_first_photo_hint(self):
1019- # Swipe to photo roll and back to viewfinder
1020- self.main_window.swipe_to_gallery(self)
1021- self.main_window.swipe_to_viewfinder(self)
1022+ photo_roll_hint = self.main_window.get_photo_roll_hint()
1023+ if photo_roll_hint.enabled:
1024+ # Swipe to photo roll and back to viewfinder
1025+ self.main_window.swipe_to_gallery(self)
1026+ self.main_window.swipe_to_viewfinder(self)
1027
1028 def set_compression_quality(self, quality="Normal Quality"):
1029 # open bottom edge
1030@@ -276,9 +322,10 @@
1031 # switch cameras and select the last resolution for the current camera
1032 self.main_window.switch_cameras()
1033 resolutions = self.get_available_video_resolutions()
1034- expected_resolution = resolutions[-1]
1035- self.assertThat(expected_resolution, NotEquals(initial_resolution))
1036- self.set_video_resolution(expected_resolution)
1037+ if len(resolutions) > 1:
1038+ expected_resolution = resolutions[-1]
1039+ self.assertThat(expected_resolution, NotEquals(initial_resolution))
1040+ self.set_video_resolution(expected_resolution)
1041
1042 # switch back to the initial camera and record a video
1043 self.main_window.switch_cameras()
1044
1045=== modified file 'tests/autopilot/camera_app/tests/test_diskspace.py'
1046--- tests/autopilot/camera_app/tests/test_diskspace.py 2015-04-29 15:56:44 +0000
1047+++ tests/autopilot/camera_app/tests/test_diskspace.py 2016-03-23 15:42:41 +0000
1048@@ -61,9 +61,6 @@
1049 # threshold as they all expect a normal situation at the start
1050 self.assertThat(self.diskSpaceAvailable(), GreaterThan(LOW_THRESHOLD))
1051
1052- self.assertThat(
1053- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1054-
1055 def tearDown(self):
1056 super(TestCameraDiskSpace, self).tearDown()
1057 os.remove(self.diskFiller) if os.path.exists(self.diskFiller) else None
1058@@ -75,8 +72,8 @@
1059 exposure_button = self.main_window.get_exposure_button()
1060 no_space_hint = self.main_window.get_no_space_hint()
1061
1062- self.assertThat(exposure_button.enabled, Equals(True))
1063- self.assertThat(no_space_hint.visible, Equals(False))
1064+ self.assertThat(exposure_button.enabled, Eventually(Equals(True)))
1065+ self.assertThat(no_space_hint.visible, Eventually(Equals(False)))
1066
1067 self.setFreeSpaceTo(CRITICAL_THRESHOLD - MEGABYTE)
1068 self.assertThat(
1069@@ -89,8 +86,8 @@
1070 self.assertThat(
1071 self.diskSpaceAvailable(), GreaterThan(CRITICAL_THRESHOLD))
1072
1073- self.assertThat(exposure_button.enabled, Equals(True))
1074- self.assertThat(no_space_hint.visible, Equals(False))
1075+ self.assertThat(exposure_button.enabled, Eventually(Equals(True)))
1076+ self.assertThat(no_space_hint.visible, Eventually(Equals(False)))
1077
1078 def test_low_disk(self):
1079 """Verify proper behavior when disk space becomes low"""
1080
1081=== modified file 'tests/autopilot/camera_app/tests/test_flash.py'
1082--- tests/autopilot/camera_app/tests/test_flash.py 2015-07-03 13:27:48 +0000
1083+++ tests/autopilot/camera_app/tests/test_flash.py 2016-03-23 15:42:41 +0000
1084@@ -16,21 +16,14 @@
1085 class TestCameraFlash(CameraAppTestCase):
1086 """Tests the flash"""
1087
1088- """ This is needed to wait for the application to start.
1089- In the testfarm, the application may take some time to show up."""
1090- def setUp(self):
1091- super(TestCameraFlash, self).setUp()
1092- self.assertThat(
1093- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1094-
1095- def tearDown(self):
1096- super(TestCameraFlash, self).tearDown()
1097-
1098 """Test that flash modes activate properly"""
1099 def test_cycle_flash(self):
1100 bottom_edge = self.main_window.get_bottom_edge()
1101 bottom_edge.open()
1102 flash_button = self.main_window.get_flash_button()
1103+ if not flash_button:
1104+ return
1105+
1106 option_value_selector = self.main_window.get_option_value_selector()
1107
1108 # open option value selector showing the possible values
1109@@ -67,10 +60,13 @@
1110 bottom_edge = self.main_window.get_bottom_edge()
1111 bottom_edge.open()
1112 flash_button = self.main_window.get_video_flash_button()
1113+ if not flash_button:
1114+ return
1115+
1116 option_value_selector = self.main_window.get_option_value_selector()
1117
1118 # ensure initial state
1119- self.assertThat(flash_button.iconName, Equals("torch-off"))
1120+ self.assertThat(flash_button.iconName, Eventually(Equals("torch-off")))
1121
1122 # open option value selector showing the possible values
1123 self.pointing_device.move_to_object(flash_button)
1124@@ -83,19 +79,22 @@
1125 option = self.main_window.get_option_value_button("On")
1126 self.pointing_device.move_to_object(option)
1127 self.pointing_device.click()
1128- self.assertThat(flash_button.iconName, Equals("torch-on"))
1129+ self.assertThat(flash_button.iconName, Eventually(Equals("torch-on")))
1130
1131 # set flash to "off"
1132 option = self.main_window.get_option_value_button("Off")
1133 self.pointing_device.move_to_object(option)
1134 self.pointing_device.click()
1135- self.assertThat(flash_button.iconName, Equals("torch-off"))
1136+ self.assertThat(flash_button.iconName, Eventually(Equals("torch-off")))
1137
1138 """Test that flash and hdr modes are mutually exclusive"""
1139 def test_flash_hdr_mutually_exclusive(self):
1140 bottom_edge = self.main_window.get_bottom_edge()
1141 bottom_edge.open()
1142 flash_button = self.main_window.get_flash_button()
1143+ if not flash_button:
1144+ return
1145+
1146 hdr_button = self.main_window.get_hdr_button()
1147 option_value_selector = self.main_window.get_option_value_selector()
1148
1149
1150=== modified file 'tests/autopilot/camera_app/tests/test_focus.py'
1151--- tests/autopilot/camera_app/tests/test_focus.py 2016-02-26 08:52:37 +0000
1152+++ tests/autopilot/camera_app/tests/test_focus.py 2016-03-23 15:42:41 +0000
1153@@ -18,26 +18,19 @@
1154 class TestFocus(CameraAppTestCase):
1155 """Tests the focus"""
1156
1157- """ This is needed to wait for the application to start.
1158- In the testfarm, the application may take some time to show up."""
1159- def setUp(self):
1160- super(TestFocus, self).setUp()
1161- self.assertThat(
1162- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1163-
1164- def tearDown(self):
1165- super(TestFocus, self).tearDown()
1166-
1167 def verify_focus_ring_after_click_at(self, ring, x, y):
1168 # The focus ring should be invisible in the beginning
1169 self.assertThat(ring.opacity, Eventually(Equals(0.0)))
1170
1171+ focus_mouse_area = self.main_window.get_focus_mouse_area()
1172+ self.assertThat(focus_mouse_area.enabled, Eventually(Equals(True)))
1173+
1174 # Click in the designated spot
1175 self.pointing_device.move(x, y)
1176 self.pointing_device.click()
1177
1178 # The focus ring sould be visible now
1179- self.assertThat(ring.opacity, Eventually(GreaterThan(0.5)))
1180+ self.assertThat(ring.opacity, Eventually(GreaterThan(0.1)))
1181
1182 # After some seconds the focus ring should fade out
1183 self.assertThat(ring.opacity, Eventually(Equals(0.0)))
1184@@ -45,19 +38,22 @@
1185 """Test focusing in an area where we know the picture is"""
1186 @unittest.skipIf(model() == 'Galaxy Nexus', 'Unusable with Mir on maguro')
1187 def test_focus_valid_and_disappear(self):
1188+ geometry = self.main_window.get_viewfinder_geometry()
1189 focus_ring = self.main_window.get_focus_ring()
1190- feed = self.main_window.get_viewfinder_geometry()
1191 switch_cameras = self.main_window.get_swap_camera_button()
1192 exposure_button = self.main_window.get_exposure_button()
1193
1194 # Click in the center of the viewfinder area
1195- mid_x, mid_y = self.get_center(feed)
1196+ mid_x, mid_y = self.get_center(geometry)
1197 self.verify_focus_ring_after_click_at(focus_ring, mid_x, mid_y)
1198
1199 # Then try on the side edges and top edge to verify they
1200 # are focusable too
1201- self.verify_focus_ring_after_click_at(focus_ring, 1, mid_y)
1202- self.verify_focus_ring_after_click_at(focus_ring, feed.width - 1,
1203+ self.verify_focus_ring_after_click_at(focus_ring,
1204+ geometry.globalRect.x + 1, mid_y)
1205+ self.verify_focus_ring_after_click_at(focus_ring,
1206+ geometry.globalRect.x +
1207+ geometry.globalRect.width - 1,
1208 mid_y)
1209 self.verify_focus_ring_after_click_at(focus_ring, mid_x, 1)
1210
1211@@ -69,10 +65,13 @@
1212 # Click in the center of the viewfinder area
1213 self.verify_focus_ring_after_click_at(focus_ring, mid_x, mid_y)
1214
1215- # Then try on the side edges and top edge to verify they
1216+ # Then try on the left, right and above the center to verify they
1217 # are focusable too
1218- self.verify_focus_ring_after_click_at(focus_ring, 1, mid_y)
1219- self.verify_focus_ring_after_click_at(focus_ring, feed.width - 1,
1220+ self.verify_focus_ring_after_click_at(focus_ring,
1221+ geometry.globalRect.x + 1, mid_y)
1222+ self.verify_focus_ring_after_click_at(focus_ring,
1223+ geometry.globalRect.x +
1224+ geometry.globalRect.width - 1,
1225 mid_y)
1226 self.verify_focus_ring_after_click_at(focus_ring, mid_x, 1)
1227
1228
1229=== modified file 'tests/autopilot/camera_app/tests/test_gallery_view.py'
1230--- tests/autopilot/camera_app/tests/test_gallery_view.py 2015-04-29 15:56:44 +0000
1231+++ tests/autopilot/camera_app/tests/test_gallery_view.py 2016-03-23 15:42:41 +0000
1232@@ -7,7 +7,7 @@
1233
1234 """Tests for the Camera App zoom"""
1235
1236-from testtools.matchers import Equals
1237+from testtools.matchers import Equals, NotEquals
1238 from autopilot.matchers import Eventually
1239
1240 from camera_app.tests import CameraAppTestCase
1241@@ -35,16 +35,19 @@
1242 self.assertThat(slideshow_view.visible, Eventually(Equals(False)))
1243 self.assertThat(photogrid_view.visible, Eventually(Equals(True)))
1244
1245- def select_first_photo(self):
1246- # select the first photo
1247- gallery = self.main_window.get_gallery()
1248- photo = gallery.wait_select_single(objectName="mediaItem0")
1249- self.pointing_device.move_to_object(photo)
1250-
1251- # do a long press to enter Multiselection mode
1252- self.pointing_device.press()
1253- sleep(1)
1254- self.pointing_device.release()
1255+ def select_media(self, index=0):
1256+ media = self.main_window.get_media(index)
1257+ checkbox = media.wait_select_single(objectName="mediaItemCheckBox")
1258+
1259+ self.pointing_device.move_to_object(checkbox)
1260+
1261+ if checkbox.visible:
1262+ self.click()
1263+ else:
1264+ # do a long press to enter Multiselection mode
1265+ self.pointing_device.press()
1266+ sleep(1)
1267+ self.pointing_device.release()
1268
1269
1270 class TestCameraGalleryView(CameraAppTestCase, TestCameraGalleryViewMixin):
1271@@ -53,11 +56,6 @@
1272 def setUp(self):
1273 self.delete_all_media()
1274 super(TestCameraGalleryView, self).setUp()
1275- self.assertThat(
1276- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1277-
1278- def tearDown(self):
1279- super(TestCameraGalleryView, self).tearDown()
1280
1281 """Tests swiping to the gallery and pressing the back button"""
1282 def test_swipe_to_gallery(self):
1283@@ -112,26 +110,55 @@
1284 def setUp(self):
1285 self.delete_all_media()
1286 self.add_sample_video()
1287-
1288 super(TestCameraGalleryViewWithVideo, self).setUp()
1289- self.assertThat(
1290- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1291-
1292- def tearDown(self):
1293- super(TestCameraGalleryViewWithVideo, self).tearDown()
1294
1295 """Tests the thumnails for video load correctly in slideshow view"""
1296 def test_video_thumbnails(self):
1297 viewfinder = self.main_window.get_viewfinder()
1298 gallery = self.main_window.get_gallery()
1299-
1300- self.main_window.swipe_to_gallery(self)
1301-
1302- self.assertThat(viewfinder.inView, Eventually(Equals(False)))
1303- self.assertThat(gallery.inView, Eventually(Equals(True)))
1304-
1305- spinner = gallery.wait_select_single("ActivityIndicator")
1306- self.assertThat(spinner.running, Eventually(Equals(False)))
1307+ self.main_window.swipe_to_gallery(self)
1308+
1309+ self.assertThat(viewfinder.inView, Eventually(Equals(False)))
1310+ self.assertThat(gallery.inView, Eventually(Equals(True)))
1311+
1312+ spinner = gallery.wait_select_single("ActivityIndicator")
1313+ self.assertThat(spinner.running, Eventually(Equals(False)))
1314+
1315+ thumb_error = self.main_window.get_broken_media_icon()
1316+ self.assertThat(thumb_error.opacity, Eventually(Equals(0.0)))
1317+
1318+ self.move_from_slideshow_to_photogrid()
1319+ thumb_error = self.main_window.get_broken_media_icon()
1320+ self.assertThat(thumb_error.opacity, Eventually(Equals(0.0)))
1321+
1322+
1323+class TestCameraGalleryViewWithBrokenVideo(
1324+ TestCameraGalleryViewMixin, CameraAppTestCase):
1325+ """Tests the camera gallery view with a broken video already present"""
1326+
1327+ def setUp(self):
1328+ self.delete_all_media()
1329+ self.add_sample_video(broken=True)
1330+ super(TestCameraGalleryViewWithBrokenVideo, self).setUp()
1331+
1332+ """Tests the placeholder thumnails for broken video loads correctly"""
1333+ def test_video_thumbnails(self):
1334+ viewfinder = self.main_window.get_viewfinder()
1335+ gallery = self.main_window.get_gallery()
1336+
1337+ self.main_window.swipe_to_gallery(self)
1338+ self.assertThat(viewfinder.inView, Eventually(Equals(False)))
1339+ self.assertThat(gallery.inView, Eventually(Equals(True)))
1340+
1341+ spinner = gallery.wait_select_single("ActivityIndicator")
1342+ self.assertThat(spinner.running, Eventually(Equals(False)))
1343+
1344+ thumb_error = self.main_window.get_broken_media_icon()
1345+ self.assertThat(thumb_error.opacity, Eventually(NotEquals(0.0)))
1346+
1347+ self.move_from_slideshow_to_photogrid()
1348+ thumb_error = self.main_window.get_broken_media_icon()
1349+ self.assertThat(thumb_error.opacity, Eventually(NotEquals(0.0)))
1350
1351
1352 class TestCameraGalleryViewWithPhoto(
1353@@ -141,25 +168,17 @@
1354 def setUp(self):
1355 self.delete_all_media()
1356 self.add_sample_photo()
1357-
1358 super(TestCameraGalleryViewWithPhoto, self).setUp()
1359- self.assertThat(
1360- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1361-
1362- def tearDown(self):
1363- super(TestCameraGalleryViewWithPhoto, self).tearDown()
1364
1365 """Test deleting photo from multiselection"""
1366 def test_delete_photo_from_multiselection(self):
1367 self.main_window.swipe_to_gallery(self)
1368 self.move_from_slideshow_to_photogrid()
1369- self.select_first_photo()
1370+ self.select_media()
1371
1372 # open actions drawer
1373 gallery = self.main_window.get_gallery()
1374- opt = gallery.wait_select_single(objectName="additionalActionsButton")
1375- self.pointing_device.move_to_object(opt)
1376- self.pointing_device.click()
1377+ self.main_window.open_actions_drawer(gallery)
1378
1379 # click delete action button
1380 delete = gallery.wait_select_single(objectName="actionButtonDelete")
1381@@ -178,7 +197,7 @@
1382 def test_multiselection_mode(self):
1383 self.main_window.swipe_to_gallery(self)
1384 self.move_from_slideshow_to_photogrid()
1385- self.select_first_photo()
1386+ self.select_media()
1387
1388 # exit the multiselection mode
1389 gallery = self.main_window.get_gallery()
1390@@ -191,3 +210,61 @@
1391
1392 self.assertThat(slideshow_view.visible, Eventually(Equals(False)))
1393 self.assertThat(photogrid_view.visible, Eventually(Equals(True)))
1394+
1395+
1396+class TestCameraGalleryViewWithPhotosAndVideo(
1397+ TestCameraGalleryViewMixin, CameraAppTestCase):
1398+ """Tests the camera gallery view with two photos and a video"""
1399+
1400+ def setUp(self):
1401+ self.delete_all_media()
1402+ self.add_sample_photo()
1403+ self.add_sample_video()
1404+ super(TestCameraGalleryViewWithPhotosAndVideo, self).setUp()
1405+
1406+ def verify_share_state(self, expectedState, close=True):
1407+ gallery = self.main_window.get_gallery()
1408+ self.main_window.open_actions_drawer(gallery)
1409+
1410+ # verify expected state
1411+ share = gallery.wait_select_single(objectName="actionButtonShare")
1412+ self.assertThat(share.enabled, Eventually(Equals(expectedState)))
1413+
1414+ if (close):
1415+ self.main_window.close_actions_drawer(gallery)
1416+ else:
1417+ return share
1418+
1419+ """Tests share button enable or disabled correctly in multiselection"""
1420+ def test_multiselection_share_enabled(self):
1421+ self.main_window.swipe_to_gallery(self)
1422+ self.move_from_slideshow_to_photogrid()
1423+
1424+ # Verify options button disabled until we select something
1425+ gallery = self.main_window.get_gallery()
1426+ opt = gallery.wait_select_single(objectName="additionalActionsButton")
1427+ self.assertThat(opt.visible, Eventually(Equals(False)))
1428+
1429+ # Verify that if we select one photo options and share are enabled
1430+ self.select_media(0)
1431+ self.assertThat(opt.visible, Eventually(Equals(True)))
1432+ self.verify_share_state(True)
1433+
1434+ # Verify that it stays enabled with mixed media selected
1435+ self.select_media(1)
1436+ self.verify_share_state(True)
1437+
1438+ """Tests sharing with mixed media generates a warning dialog"""
1439+ def test_no_share_mixed_media(self):
1440+ self.main_window.swipe_to_gallery(self)
1441+ self.move_from_slideshow_to_photogrid()
1442+
1443+ self.select_media(0)
1444+ self.select_media(1)
1445+ share = self.verify_share_state(True, close=False)
1446+
1447+ self.pointing_device.move_to_object(share)
1448+ self.pointing_device.click()
1449+
1450+ gallery = self.main_window.get_gallery()
1451+ gallery.wait_select_single(objectName="unableShareDialog")
1452
1453=== modified file 'tests/autopilot/camera_app/tests/test_options.py'
1454--- tests/autopilot/camera_app/tests/test_options.py 2016-02-25 13:42:01 +0000
1455+++ tests/autopilot/camera_app/tests/test_options.py 2016-03-23 15:42:41 +0000
1456@@ -16,14 +16,6 @@
1457 class TestCameraOptions(CameraAppTestCase):
1458 """Tests the options overlay"""
1459
1460- """ This is needed to wait for the application to start.
1461- In the testfarm, the application may take some time to show up."""
1462- def setUp(self):
1463- super(TestCameraOptions, self).setUp()
1464- # FIXME: this should be in parent class
1465- self.assertThat(
1466- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1467-
1468 """Test that the options overlay closes properly by tapping"""
1469 def test_overlay_tap_to_close(self):
1470 bottom_edge = self.main_window.get_bottom_edge()
1471
1472=== modified file 'tests/autopilot/camera_app/tests/test_photo_editor.py'
1473--- tests/autopilot/camera_app/tests/test_photo_editor.py 2015-07-07 11:09:05 +0000
1474+++ tests/autopilot/camera_app/tests/test_photo_editor.py 2016-03-23 15:42:41 +0000
1475@@ -20,13 +20,7 @@
1476 def setUp(self):
1477 self.delete_all_media()
1478 self.add_sample_photo()
1479-
1480 super(TestCameraPhotoEditorWithPhoto, self).setUp()
1481- self.assertThat(
1482- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1483-
1484- def tearDown(self):
1485- super(TestCameraPhotoEditorWithPhoto, self).tearDown()
1486
1487 """Tests editor opening and closing correctly for pictures"""
1488 def test_editor_appears(self):
1489@@ -37,11 +31,7 @@
1490 self.main_window.swipe_to_gallery(self)
1491
1492 self.assertThat(gallery.inView, Eventually(Equals(True)))
1493-
1494- # open actions drawer
1495- opt = gallery.wait_select_single(objectName="additionalActionsButton")
1496- self.pointing_device.move_to_object(opt)
1497- self.pointing_device.click()
1498+ self.main_window.open_actions_drawer(gallery)
1499
1500 # If the editor button is not there when in the gallery view, then
1501 # we are not on a system that has the UI extras package installed or
1502@@ -77,13 +67,7 @@
1503 def setUp(self):
1504 self.delete_all_media()
1505 self.add_sample_video()
1506-
1507 super(TestCameraPhotoEditorWithVideo, self).setUp()
1508- self.assertThat(
1509- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1510-
1511- def tearDown(self):
1512- super(TestCameraPhotoEditorWithVideo, self).tearDown()
1513
1514 """Tests editor not being available for videos"""
1515 def test_editor_not_on_videos(self):
1516@@ -95,11 +79,7 @@
1517 self.main_window.swipe_to_gallery(self)
1518
1519 self.assertThat(gallery.inView, Eventually(Equals(True)))
1520-
1521- # open actions drawer
1522- opt = gallery.wait_select_single(objectName="additionalActionsButton")
1523- self.pointing_device.move_to_object(opt)
1524- self.pointing_device.click()
1525+ self.main_window.open_actions_drawer(gallery)
1526
1527 # If the editor button is not there when in the gallery view, then
1528 # we are not on a system that has the UI extras package installed or
1529
1530=== modified file 'tests/autopilot/camera_app/tests/test_zoom.py'
1531--- tests/autopilot/camera_app/tests/test_zoom.py 2016-01-11 17:07:21 +0000
1532+++ tests/autopilot/camera_app/tests/test_zoom.py 2016-03-23 15:42:41 +0000
1533@@ -19,16 +19,6 @@
1534 class TestCameraZoom(CameraAppTestCase):
1535 """Tests the main camera features"""
1536
1537- """ This is needed to wait for the application to start.
1538- In the testfarm, the application may take some time to show up."""
1539- def setUp(self):
1540- super(TestCameraZoom, self).setUp()
1541- self.assertThat(
1542- self.main_window.get_qml_view().visible, Eventually(Equals(True)))
1543-
1544- def tearDown(self):
1545- super(TestCameraZoom, self).tearDown()
1546-
1547 def activate_zoom(self):
1548 viewfinder = self.main_window.get_viewfinder_geometry()
1549 viewfinder_center = self.get_center(viewfinder)
1550
1551=== modified file 'tests/unittests/CMakeLists.txt'
1552--- tests/unittests/CMakeLists.txt 2016-02-26 15:24:03 +0000
1553+++ tests/unittests/CMakeLists.txt 2016-03-23 15:42:41 +0000
1554@@ -7,7 +7,7 @@
1555 add_executable(tst_QmlTests tst_QmlTests.cpp)
1556 qt5_use_modules(tst_QmlTests Core Qml Quick Test QuickTest)
1557 target_link_libraries(tst_QmlTests ${TPL_QT5_LIBRARIES})
1558-add_test(tst_QmlTests ${XVFB_RUN_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst_QmlTests -import ${CMAKE_SOURCE_DIR})
1559+add_test(tst_QmlTests ${XVFB_RUN_CMD} ${CMAKE_CURRENT_BINARY_DIR}/tst_QmlTests -import ${CMAKE_SOURCE_DIR} -import ${CMAKE_BINARY_DIR})
1560
1561 # copy qml test files to build dir
1562 file(GLOB qmlTestFiles RELATIVE ${CMAKE_SOURCE_DIR}/tests/unittests/ *qml)
1563
1564=== added file 'tests/unittests/tst_PhotogridView.qml'
1565--- tests/unittests/tst_PhotogridView.qml 1970-01-01 00:00:00 +0000
1566+++ tests/unittests/tst_PhotogridView.qml 2016-03-23 15:42:41 +0000
1567@@ -0,0 +1,88 @@
1568+/*
1569+ * Copyright 2016 Canonical Ltd.
1570+ *
1571+ * This program is free software; you can redistribute it and/or modify
1572+ * it under the terms of the GNU General Public License as published by
1573+ * the Free Software Foundation; version 3.
1574+ *
1575+ * This program is distributed in the hope that it will be useful,
1576+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1577+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1578+ * GNU General Public License for more details.
1579+ *
1580+ * You should have received a copy of the GNU General Public License
1581+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1582+ *
1583+ */
1584+
1585+import QtQuick 2.4
1586+import QtTest 1.0
1587+import "../../"
1588+import "../../.." //Needed for out of source build
1589+
1590+TestCase {
1591+ name: "PhotogridView"
1592+
1593+ function test_mixedMediaSelection_data() {
1594+ return [
1595+ { // one item only
1596+ isMixedMedia: false,
1597+ listItems: [
1598+ { fileType: "image", selected: true, fileURL: "" }
1599+ ]
1600+ },
1601+ { // mixed media but only non-mixed selected
1602+ isMixedMedia: false,
1603+ listItems: [
1604+ { fileType: "video", selected: false, fileURL: "" },
1605+ { fileType: "image", selected: true, fileURL: "" },
1606+ { fileType: "image", selected: true, fileURL: "" }
1607+ ]
1608+ },
1609+ { // mixed media
1610+ isMixedMedia: true,
1611+ listItems: [
1612+ { fileType: "video", selected: true, fileURL: "" },
1613+ { fileType: "image", selected: true, fileURL: "" },
1614+ { fileType: "image", selected: true, fileURL: "" }
1615+ ]
1616+ },
1617+ ];
1618+ }
1619+
1620+ function test_mixedMediaSelection(data) {
1621+ list.clear()
1622+ list.data = data.listItems;
1623+ for (var i = 0; i < data.listItems.length; i++) {
1624+ list.append(data.listItems[i]);
1625+ }
1626+ list.updateSelectedFiles();
1627+ grid.model = list
1628+ compare(grid.selectionContainsMixedMedia(), data.isMixedMedia, "Mixed media not detected correctly")
1629+ }
1630+
1631+ ListModel {
1632+ id: list
1633+ property var data
1634+ property var selectedFiles: []
1635+ function updateSelectedFiles() {
1636+ // need to re-assign entire list due to the way list properties work in QML
1637+ var selected = [];
1638+ for (var i = 0; i < list.count; i++) {
1639+ if (list.data[i].selected) selected.push(i);
1640+ }
1641+ list.selectedFiles = selected;
1642+ }
1643+ function get(i, key) {
1644+ return list.data[i][key];
1645+ }
1646+ }
1647+
1648+ PhotogridView {
1649+ id: grid
1650+ width: 600
1651+ height: 800
1652+ inView: true
1653+ inSelectionMode: true
1654+ }
1655+}

Subscribers

People subscribed via source and target branches