Merge lp:~fboucault/camera-app/content_source into lp:camera-app

Proposed by Florian Boucault
Status: Merged
Approved by: Ugo Riboni
Approved revision: 340
Merged at revision: 341
Proposed branch: lp:~fboucault/camera-app/content_source
Merge into: lp:camera-app
Diff against target: 828 lines (+354/-56)
17 files modified
CMakeLists.txt (+2/-0)
CameraApp/foldersmodel.cpp (+73/-11)
CameraApp/foldersmodel.h (+19/-6)
GalleryView.qml (+23/-5)
GalleryViewHeader.qml (+14/-0)
MimeTypeMapper.js (+17/-6)
PhotogridView.qml (+8/-1)
Snapshot.qml (+4/-3)
ViewFinderExportConfirmation.qml (+94/-0)
ViewFinderOverlay.qml (+11/-17)
ViewFinderView.qml (+14/-2)
camera-app.qml (+42/-0)
camera-apparmor.json (+1/-1)
camera-contenthub.json (+5/-0)
cameraapplication.cpp (+22/-2)
cameraapplication.h (+2/-0)
manifest.json (+3/-2)
To merge this branch: bzr merge lp:~fboucault/camera-app/content_source
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve
Ugo Riboni (community) Needs Fixing
Review via email: mp+229250@code.launchpad.net

Commit message

Added export mode so that other apps can request pictures from the camera app.

To post a comment you must log in.
Revision history for this message
Florian Boucault (fboucault) wrote :

Missing polish and tests.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ugo Riboni (uriboni) wrote :

Minor style and edge cases pointed out inline.
Runs fine, but not sure how to test the actual functionality.

review: Needs Fixing
340. By Florian Boucault

Test for QStandardPaths::standardLocations return value.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
341. By Florian Boucault

Do not reset mediaPath.

342. By Florian Boucault

Make sure everything else but the confirmation dialog is invisible.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-07-29 10:06:21 +0000
3+++ CMakeLists.txt 2014-08-05 15:51:59 +0000
4@@ -120,6 +120,8 @@
5 DESTINATION ${CMAKE_INSTALL_PREFIX})
6 install(FILES camera-apparmor.json
7 DESTINATION ${CMAKE_INSTALL_PREFIX})
8+ install(FILES camera-contenthub.json
9+ DESTINATION ${CMAKE_INSTALL_PREFIX})
10
11 else(CLICK_MODE)
12 install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${DESKTOP_FILE}
13
14=== modified file 'CameraApp/foldersmodel.cpp'
15--- CameraApp/foldersmodel.cpp 2014-07-29 11:57:12 +0000
16+++ CameraApp/foldersmodel.cpp 2014-08-05 15:51:59 +0000
17@@ -20,7 +20,8 @@
18 #include <QtCore/QDateTime>
19
20 FoldersModel::FoldersModel(QObject *parent) :
21- QAbstractListModel(parent)
22+ QAbstractListModel(parent),
23+ m_singleSelectionOnly(true)
24 {
25 m_watcher = new QFileSystemWatcher(this);
26 connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString)));
27@@ -40,31 +41,60 @@
28 Q_EMIT foldersChanged();
29 }
30
31-QStringList FoldersModel::nameFilters() const
32+QStringList FoldersModel::typeFilters() const
33 {
34- return m_nameFilters;
35+ return m_typeFilters;
36 }
37
38-void FoldersModel::setNameFilters(const QStringList& nameFilters)
39+void FoldersModel::setTypeFilters(const QStringList& typeFilters)
40 {
41- m_nameFilters = nameFilters;
42+ m_typeFilters = typeFilters;
43 updateFileInfoList();
44- Q_EMIT nameFiltersChanged();
45-}
46+ Q_EMIT typeFiltersChanged();
47+}
48+
49+QList<int> FoldersModel::selectedFiles() const
50+{
51+ return m_selectedFiles.values();
52+}
53+
54+bool FoldersModel::singleSelectionOnly() const
55+{
56+ return m_singleSelectionOnly;
57+}
58+
59+void FoldersModel::setSingleSelectionOnly(bool singleSelectionOnly)
60+{
61+ if (singleSelectionOnly != m_singleSelectionOnly) {
62+ if (singleSelectionOnly && m_selectedFiles.count() > 1) {
63+ clearSelection();
64+ }
65+ m_singleSelectionOnly = singleSelectionOnly;
66+ Q_EMIT singleSelectionOnlyChanged();
67+ }
68+}
69+
70
71 void FoldersModel::updateFileInfoList()
72 {
73 m_fileInfoList.clear();
74 Q_FOREACH (QString folder, m_folders) {
75 QDir currentDir(folder);
76- QFileInfoList fileInfoList = currentDir.entryInfoList(m_nameFilters,
77- QDir::Files | QDir::Readable,
78+ QFileInfoList fileInfoList = currentDir.entryInfoList(QDir::Files | QDir::Readable,
79 QDir::Time | QDir::Reversed);
80 Q_FOREACH (QFileInfo fileInfo, fileInfoList) {
81- insertFileInfo(fileInfo);
82+ QString type = m_mimeDatabase.mimeTypeForFile(fileInfo).name();
83+ Q_FOREACH (QString filterType, m_typeFilters) {
84+ if (type.startsWith(filterType)) {
85+ insertFileInfo(fileInfo);
86+ break;
87+ }
88+ }
89 }
90 }
91 endResetModel();
92+ m_selectedFiles.clear();
93+ Q_EMIT selectedFilesChanged();
94 }
95
96 bool moreRecentThan(const QFileInfo& fileInfo1, const QFileInfo& fileInfo2)
97@@ -94,6 +124,7 @@
98 roles[FilePathRole] = "filePath";
99 roles[FileUrlRole] = "fileURL";
100 roles[FileTypeRole] = "fileType";
101+ roles[SelectedRole] = "selected";
102 return roles;
103 }
104
105@@ -120,7 +151,10 @@
106 return QUrl::fromLocalFile(item.filePath());
107 break;
108 case FileTypeRole:
109- return m_mimeDatabase.mimeTypeForFile(item.fileName()).name();
110+ return m_mimeDatabase.mimeTypeForFile(item).name();
111+ break;
112+ case SelectedRole:
113+ return m_selectedFiles.contains(index.row());
114 break;
115 default:
116 break;
117@@ -143,3 +177,31 @@
118 {
119 updateFileInfoList();
120 }
121+
122+void FoldersModel::toggleSelected(int row)
123+{
124+ if (m_selectedFiles.contains(row)) {
125+ m_selectedFiles.remove(row);
126+ } else {
127+ if (m_singleSelectionOnly) {
128+ int previouslySelected = m_selectedFiles.isEmpty() ? -1 : m_selectedFiles.values().first();
129+ if (previouslySelected != -1) {
130+ m_selectedFiles.remove(previouslySelected);
131+ Q_EMIT dataChanged(index(previouslySelected), index(previouslySelected));
132+ }
133+ }
134+ m_selectedFiles.insert(row);
135+ }
136+
137+ Q_EMIT dataChanged(index(row), index(row));
138+ Q_EMIT selectedFilesChanged();
139+}
140+
141+void FoldersModel::clearSelection()
142+{
143+ Q_FOREACH (int selectedFile, m_selectedFiles) {
144+ m_selectedFiles.remove(selectedFile);
145+ Q_EMIT dataChanged(index(selectedFile), index(selectedFile));
146+ }
147+ Q_EMIT selectedFilesChanged();
148+}
149
150=== modified file 'CameraApp/foldersmodel.h'
151--- CameraApp/foldersmodel.h 2014-07-29 11:57:12 +0000
152+++ CameraApp/foldersmodel.h 2014-08-05 15:51:59 +0000
153@@ -22,27 +22,34 @@
154 #include <QtCore/QFileInfo>
155 #include <QtCore/QFileSystemWatcher>
156 #include <QtCore/QMimeDatabase>
157+#include <QtCore/QSet>
158
159 class FoldersModel : public QAbstractListModel
160 {
161 Q_OBJECT
162 Q_PROPERTY (QStringList folders READ folders WRITE setFolders NOTIFY foldersChanged)
163- Q_PROPERTY (QStringList nameFilters READ nameFilters WRITE setNameFilters NOTIFY nameFiltersChanged)
164+ Q_PROPERTY (QStringList typeFilters READ typeFilters WRITE setTypeFilters NOTIFY typeFiltersChanged)
165+ Q_PROPERTY (QList<int> selectedFiles READ selectedFiles NOTIFY selectedFilesChanged)
166+ Q_PROPERTY (bool singleSelectionOnly READ singleSelectionOnly WRITE setSingleSelectionOnly NOTIFY singleSelectionOnlyChanged)
167
168 public:
169 enum Roles {
170 FileNameRole = Qt::UserRole + 1,
171 FilePathRole = Qt::UserRole + 2,
172 FileUrlRole = Qt::UserRole + 3,
173- FileTypeRole = Qt::UserRole + 4
174+ FileTypeRole = Qt::UserRole + 4,
175+ SelectedRole = Qt::UserRole + 5
176 };
177
178 explicit FoldersModel(QObject *parent = 0);
179
180 QStringList folders() const;
181 void setFolders(const QStringList& folders);
182- QStringList nameFilters() const;
183- void setNameFilters(const QStringList& nameFilters);
184+ QStringList typeFilters() const;
185+ void setTypeFilters(const QStringList& typeFilters);
186+ QList<int> selectedFiles() const;
187+ bool singleSelectionOnly() const;
188+ void setSingleSelectionOnly(bool singleSelectionOnly);
189
190 void updateFileInfoList();
191 void insertFileInfo(const QFileInfo& newFileInfo);
192@@ -51,20 +58,26 @@
193 QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
194 int rowCount(const QModelIndex& parent = QModelIndex()) const;
195 Q_INVOKABLE QVariant get(int row, QString role) const;
196+ Q_INVOKABLE void toggleSelected(int row);
197+ Q_INVOKABLE void clearSelection();
198
199 public Q_SLOTS:
200 void directoryChanged(const QString &directoryPath);
201
202 Q_SIGNALS:
203 void foldersChanged();
204- void nameFiltersChanged();
205+ void typeFiltersChanged();
206+ void selectedFilesChanged();
207+ void singleSelectionOnlyChanged();
208
209 private:
210 QStringList m_folders;
211- QStringList m_nameFilters;
212+ QStringList m_typeFilters;
213 QFileInfoList m_fileInfoList;
214 QFileSystemWatcher* m_watcher;
215 QMimeDatabase m_mimeDatabase;
216+ QSet<int> m_selectedFiles;
217+ bool m_singleSelectionOnly;
218 };
219
220 #endif // FOLDERSMODEL_H
221
222=== modified file 'GalleryView.qml'
223--- GalleryView.qml 2014-07-25 12:44:19 +0000
224+++ GalleryView.qml 2014-08-05 15:51:59 +0000
225@@ -16,7 +16,9 @@
226
227 import QtQuick 2.2
228 import Ubuntu.Components 1.0
229+import Ubuntu.Content 0.1
230 import CameraApp 0.1
231+import "MimeTypeMapper.js" as MimeTypeMapper
232
233 Item {
234 id: galleryView
235@@ -26,7 +28,9 @@
236 property Item currentView: state == "GRID" ? photogridView : slideshowView
237 property var model: FoldersModel {
238 folders: [application.picturesLocation, application.videosLocation]
239- nameFilters: [ "*.png", "*.jpg", "*.jpeg", "*.PNG", "*.JPG", "*.JPEG", "*.mp4" ]
240+ typeFilters: !main.contentExportMode ? [ "image", "video" ]
241+ : [MimeTypeMapper.contentTypeToMimeType(main.transferContentType)]
242+ singleSelectionOnly: main.transfer.selectionType === ContentTransfer.Single
243 }
244
245 property bool gridMode: false
246@@ -59,8 +63,12 @@
247 model: galleryView.model
248 visible: opacity != 0.0
249 onPhotoClicked: {
250- slideshowView.showPhotoAtIndex(index);
251- galleryView.gridMode = false;
252+ if (main.contentExportMode) {
253+ model.toggleSelected(index);
254+ } else {
255+ slideshowView.showPhotoAtIndex(index);
256+ galleryView.gridMode = false;
257+ }
258 }
259 }
260
261@@ -69,7 +77,8 @@
262 id: header
263 onExit: galleryView.exit()
264 actions: currentView.actions
265- gridMode: galleryView.gridMode
266+ gridMode: galleryView.gridMode || main.contentExportMode
267+ validationVisible: main.contentExportMode && model.selectedFiles.length > 0
268 onToggleViews: {
269 if (!galleryView.gridMode) {
270 // position grid view so that the current photo in slideshow view is visible
271@@ -78,6 +87,15 @@
272
273 galleryView.gridMode = !galleryView.gridMode
274 }
275+ onValidationClicked: {
276+ var selection = model.selectedFiles;
277+ var urls = [];
278+ for (var i=0; i<selection.length; i++) {
279+ urls.push(model.get(selection[i], "fileURL"));
280+ }
281+ model.clearSelection();
282+ main.exportContent(urls);
283+ }
284 }
285 }
286
287@@ -93,7 +111,7 @@
288 text: i18n.tr("No media available.")
289 }
290
291- state: galleryView.gridMode ? "GRID" : "SLIDESHOW"
292+ state: galleryView.gridMode || main.contentExportMode ? "GRID" : "SLIDESHOW"
293 states: [
294 State {
295 name: "SLIDESHOW"
296
297=== modified file 'GalleryViewHeader.qml'
298--- GalleryViewHeader.qml 2014-07-30 19:29:56 +0000
299+++ GalleryViewHeader.qml 2014-08-05 15:51:59 +0000
300@@ -34,8 +34,10 @@
301 property bool shown: true
302 property alias actions: actionsDrawer.actions
303 property bool gridMode: false
304+ property bool validationVisible
305 signal exit
306 signal toggleViews
307+ signal validationClicked
308
309 function show() {
310 shown = true;
311@@ -83,6 +85,7 @@
312 }
313 iconName: header.gridMode ? "stock_image" : "view-grid-symbolic"
314 onClicked: header.toggleViews()
315+ visible: !main.contentExportMode
316 }
317
318 IconButton {
319@@ -95,6 +98,17 @@
320 visible: actionsDrawer.actions.length > 0
321 onClicked: actionsDrawer.opened = !actionsDrawer.opened
322 }
323+
324+ IconButton {
325+ objectName: "validationButton"
326+ anchors {
327+ top: parent.top
328+ bottom: parent.bottom
329+ }
330+ iconName: "ok"
331+ onClicked: header.validationClicked()
332+ visible: header.validationVisible
333+ }
334 }
335
336 Item {
337
338=== modified file 'MimeTypeMapper.js'
339--- MimeTypeMapper.js 2014-07-29 11:57:12 +0000
340+++ MimeTypeMapper.js 2014-08-05 15:51:59 +0000
341@@ -16,22 +16,33 @@
342 * along with this program. If not, see <http://www.gnu.org/licenses/>.
343 */
344
345+.pragma library
346+.import Ubuntu.Content 0.1 as UbuntuContent
347+
348 function startsWith(string, prefix) {
349 return string.indexOf(prefix) === 0;
350 }
351
352 function mimeTypeToContentType(mimeType) {
353 if(startsWith(mimeType, "image")) {
354- return ContentType.Pictures;
355+ return UbuntuContent.ContentType.Pictures;
356 } else if(startsWith(mimeType, "audio")) {
357- return ContentType.Music;
358+ return UbuntuContent.ContentType.Music;
359 } else if(startsWith(mimeType, "video")) {
360- return ContentType.Videos;
361+ return UbuntuContent.ContentType.Videos;
362 } else if(startsWith(mimeType, "text/x-vcard")) {
363- return ContentType.Contacts;
364+ return UbuntuContent.ContentType.Contacts;
365 } else if(startsWith(mimeType, "text")) {
366- return ContentType.Documents;
367+ return UbuntuContent.ContentType.Documents;
368 } else {
369- return ContentType.Unknown;
370+ return UbuntuContent.ContentType.Unknown;
371+ }
372+}
373+
374+function contentTypeToMimeType(contentType) {
375+ if (contentType === UbuntuContent.ContentType.Pictures) {
376+ return "image";
377+ } else if (contentType === UbuntuContent.ContentType.Videos) {
378+ return "video";
379 }
380 }
381
382=== modified file 'PhotogridView.qml'
383--- PhotogridView.qml 2014-07-29 11:57:12 +0000
384+++ PhotogridView.qml 2014-08-05 15:51:59 +0000
385@@ -15,7 +15,7 @@
386 */
387
388 import QtQuick 2.2
389-import Ubuntu.Components 1.0
390+import Ubuntu.Components 1.1
391 import Ubuntu.Thumbnailer 0.1
392 import Ubuntu.Content 0.1
393 import "MimeTypeMapper.js" as MimeTypeMapper
394@@ -103,6 +103,13 @@
395 visible: isVideo
396 }
397
398+ Rectangle {
399+ anchors.fill: parent
400+ color: UbuntuColors.blue
401+ opacity: 0.4
402+ visible: selected
403+ }
404+
405 MouseArea {
406 anchors.fill: parent
407 onClicked: photogridView.photoClicked(index)
408
409=== modified file 'Snapshot.qml'
410--- Snapshot.qml 2014-06-04 14:48:22 +0000
411+++ Snapshot.qml 2014-08-05 15:51:59 +0000
412@@ -29,6 +29,8 @@
413 shoot.restart()
414 }
415
416+ visible: false
417+
418 Item {
419 id: container
420 anchors {
421@@ -36,7 +38,6 @@
422 bottom: parent.bottom
423 }
424 width: parent.width
425- visible: false
426
427 Image {
428 id: snapshot
429@@ -67,11 +68,11 @@
430 SequentialAnimation {
431 id: shoot
432
433- PropertyAction { target: container; property: "visible"; value: true }
434+ PropertyAction { target: snapshotRoot; property: "visible"; value: true }
435 PauseAnimation { duration: 150 }
436 XAnimator { target: container; to: container.width + shadow.width; duration: UbuntuAnimation.BriskDuration; easing: UbuntuAnimation.StandardEasing}
437 PropertyAction { target: snapshot; property: "source"; value: ""}
438- PropertyAction { target: container; property: "visible"; value: false }
439+ PropertyAction { target: snapshotRoot; property: "visible"; value: false }
440 PropertyAction { target: container; property: "x"; value: 0 }
441 }
442 }
443
444=== added file 'ViewFinderExportConfirmation.qml'
445--- ViewFinderExportConfirmation.qml 1970-01-01 00:00:00 +0000
446+++ ViewFinderExportConfirmation.qml 2014-08-05 15:51:59 +0000
447@@ -0,0 +1,94 @@
448+/*
449+ * Copyright 2014 Canonical Ltd.
450+ *
451+ * This program is free software; you can redistribute it and/or modify
452+ * it under the terms of the GNU General Public License as published by
453+ * the Free Software Foundation; version 3.
454+ *
455+ * This program is distributed in the hope that it will be useful,
456+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
457+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
458+ * GNU General Public License for more details.
459+ *
460+ * You should have received a copy of the GNU General Public License
461+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
462+ */
463+
464+import QtQuick 2.2
465+import Ubuntu.Components 1.1
466+
467+Item {
468+ id: viewFinderExportConfirmation
469+
470+ property string mediaPath
471+ property Snapshot snapshot
472+
473+ function confirmExport(path) {
474+ viewFinder.visible = false;
475+ viewFinderOverlay.visible = false;
476+ mediaPath = path;
477+ snapshot.visible = true;
478+ visible = true;
479+ }
480+
481+ function hide() {
482+ viewFinder.visible = true;
483+ viewFinderOverlay.visible = true;
484+ snapshot.source = "";
485+ snapshot.visible = false;
486+ visible = false;
487+ }
488+
489+ visible: false
490+
491+ CircleButton {
492+ id: retryButton
493+ objectName: "retryButton"
494+
495+ anchors {
496+ right: validateButton.left
497+ rightMargin: units.gu(7.5)
498+ bottom: parent.bottom
499+ bottomMargin: units.gu(6)
500+ }
501+
502+ iconName: "reload"
503+ onClicked: hide()
504+ }
505+
506+ CircleButton {
507+ id: validateButton
508+ objectName: "validateButton"
509+
510+ width: units.gu(8)
511+ anchors {
512+ bottom: parent.bottom
513+ bottomMargin: units.gu(5)
514+ horizontalCenter: parent.horizontalCenter
515+ }
516+
517+ iconName: "ok"
518+ onClicked: {
519+ hide();
520+ main.exportContent([mediaPath]);
521+ }
522+ }
523+
524+ CircleButton {
525+ id: cancelButton
526+ objectName: "cancelButton"
527+
528+ anchors {
529+ left: validateButton.right
530+ leftMargin: units.gu(7.5)
531+ bottom: parent.bottom
532+ bottomMargin: units.gu(6)
533+ }
534+
535+ iconName: "close"
536+ onClicked: {
537+ hide();
538+ main.cancelExport();
539+ }
540+ }
541+}
542
543=== modified file 'ViewFinderOverlay.qml'
544--- ViewFinderOverlay.qml 2014-07-30 19:29:39 +0000
545+++ ViewFinderOverlay.qml 2014-08-05 15:51:59 +0000
546@@ -288,8 +288,6 @@
547 enabled: visible
548
549 function shoot() {
550- camera.captureInProgress = true;
551-
552 var orientation = Screen.angleBetween(Screen.orientation, Screen.primaryOrientation);
553 if (Screen.primaryOrientation == Qt.PortraitOrientation) {
554 orientation += 90;
555@@ -304,7 +302,9 @@
556 // TODO: there's no event to tell us that the video has been successfully recorder or failed
557 }
558 } else {
559- shootFeedback.start();
560+ if (!main.contentExportMode) {
561+ shootFeedback.start();
562+ }
563 camera.imageCapture.setMetadata("Orientation", orientation);
564 var position = positionSource.position;
565 if (settings.gpsEnabled && positionSource.valid
566@@ -317,17 +317,12 @@
567 camera.imageCapture.setMetadata("GPSTimeStamp", position.timestamp);
568 camera.imageCapture.setMetadata("GPSProcessingMethod", "GPS");
569 }
570- camera.imageCapture.captureToLocation(application.picturesLocation);
571- }
572- }
573-
574- function completeCapture() {
575- viewFinderOverlay.visible = true;
576- // FIXME: no snapshot is available for videos
577- if (camera.captureMode != Camera.CaptureVideo) {
578- snapshot.startOutAnimation();
579- }
580- camera.captureInProgress = false;
581+ if (main.contentExportMode) {
582+ camera.imageCapture.captureToLocation(application.temporaryLocation);
583+ } else {
584+ camera.imageCapture.captureToLocation(application.picturesLocation);
585+ }
586+ }
587 }
588
589 function switchCamera() {
590@@ -361,9 +356,7 @@
591 target: camera.imageCapture
592 onReadyChanged: {
593 if (camera.imageCapture.ready) {
594- if (camera.captureInProgress) {
595- controls.completeCapture();
596- } else if (camera.switchInProgress) {
597+ if (camera.switchInProgress) {
598 controls.completeSwitch();
599 }
600 }
601@@ -383,6 +376,7 @@
602
603 iconName: (camera.captureMode == Camera.CaptureStillImage) ? "camcorder" : "camera-symbolic"
604 onClicked: controls.changeRecordMode()
605+ enabled: !main.contentExportMode
606 }
607
608 ShootButton {
609
610=== modified file 'ViewFinderView.qml'
611--- ViewFinderView.qml 2014-07-28 12:55:56 +0000
612+++ ViewFinderView.qml 2014-08-05 15:51:59 +0000
613@@ -28,6 +28,7 @@
614 property bool optionValueSelectorVisible: false
615 property bool touchAcquired: viewFinderOverlay.touchAcquired
616 property bool inView
617+ property alias captureMode: camera.captureMode
618 signal photoTaken
619 signal videoShot
620
621@@ -75,7 +76,6 @@
622 transparently based on the value. */
623 property alias currentZoom: camera.digitalZoom
624 property alias maximumZoom: camera.maximumDigitalZoom
625- property bool captureInProgress: false
626 property bool switchInProgress: false
627
628 imageCapture {
629@@ -86,6 +86,12 @@
630 snapshot.source = preview;
631 }
632 onImageSaved: {
633+ if (main.contentExportMode) {
634+ viewFinderExportConfirmation.confirmExport(path);
635+ } else {
636+ viewFinderOverlay.visible = true;
637+ snapshot.startOutAnimation();
638+ }
639 viewFinderView.photoTaken();
640 metricPhotos.increment();
641 console.log("Picture saved as " + path);
642@@ -96,7 +102,7 @@
643 onRecorderStateChanged: {
644 if (videoRecorder.recorderState === CameraRecorder.StoppedState) {
645 metricVideos.increment()
646- viewFinderOverlay.controls.completeCapture();
647+ viewFinderOverlay.visible = true;
648 viewFinderView.videoShot();
649 }
650 }
651@@ -284,4 +290,10 @@
652 geometry: viewFinderGeometry
653 deviceDefaultIsPortrait: Screen.primaryOrientation === Qt.PortraitOrientation
654 }
655+
656+ ViewFinderExportConfirmation {
657+ id: viewFinderExportConfirmation
658+ anchors.fill: parent
659+ snapshot: snapshot
660+ }
661 }
662
663=== modified file 'camera-app.qml'
664--- camera-app.qml 2014-07-28 12:55:56 +0000
665+++ camera-app.qml 2014-08-05 15:51:59 +0000
666@@ -16,9 +16,11 @@
667
668 import QtQuick 2.2
669 import QtQuick.Window 2.0
670+import QtMultimedia 5.0
671 import Ubuntu.Components 1.0
672 import Ubuntu.Unity.Action 1.1 as UnityActions
673 import UserMetrics 0.1
674+import Ubuntu.Content 0.1
675
676 Item {
677 id: main
678@@ -164,6 +166,46 @@
679 }
680 }
681
682+ property bool contentExportMode: transfer !== null
683+ property var transfer: null
684+ property var transferContentType: ContentType.Pictures
685+
686+ function exportContent(urls) {
687+ if (!main.transfer) return;
688+
689+ var item;
690+ var items = [];
691+ for (var i=0; i<urls.length; i++) {
692+ item = contentItemComponent.createObject(main.transfer, {"url": urls[i]});
693+ items.push(item);
694+ }
695+ main.transfer.items = items;
696+ main.transfer.state = ContentTransfer.Charged;
697+ main.transfer = null;
698+ }
699+
700+ function cancelExport() {
701+ main.transfer.state = ContentTransfer.Aborted;
702+ main.transfer = null;
703+ }
704+
705+ Component {
706+ id: contentItemComponent
707+ ContentItem {
708+ }
709+ }
710+
711+ Connections {
712+ target: ContentHub
713+ onExportRequested: {
714+ if (transferContentType === ContentType.Videos) {
715+ viewFinderView.captureMode = Camera.CaptureVideo;
716+ } else {
717+ viewFinderView.captureMode = Camera.CaptureStillImage;
718+ }
719+ main.transfer = transfer;
720+ }
721+ }
722
723 Metric {
724 id: metricPhotos
725
726=== modified file 'camera-apparmor.json'
727--- camera-apparmor.json 2014-07-17 11:03:53 +0000
728+++ camera-apparmor.json 2014-08-05 15:51:59 +0000
729@@ -11,4 +11,4 @@
730 "location"
731 ],
732 "policy_version": 1.2
733-}
734+}
735\ No newline at end of file
736
737=== added file 'camera-contenthub.json'
738--- camera-contenthub.json 1970-01-01 00:00:00 +0000
739+++ camera-contenthub.json 2014-08-05 15:51:59 +0000
740@@ -0,0 +1,5 @@
741+{
742+ "source": [
743+ "pictures"
744+ ]
745+}
746
747=== modified file 'cameraapplication.cpp'
748--- cameraapplication.cpp 2014-07-29 18:08:21 +0000
749+++ cameraapplication.cpp 2014-08-05 15:51:59 +0000
750@@ -110,7 +110,11 @@
751
752 QString CameraApplication::picturesLocation() const
753 {
754- QString location = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation).at(0) + "/" + QCoreApplication::applicationName();
755+ QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
756+ if (locations.isEmpty()) {
757+ return QString();
758+ }
759+ QString location = locations.at(0) + "/" + QCoreApplication::applicationName();
760 QDir dir;
761 dir.mkpath(location);
762 return location;
763@@ -118,7 +122,23 @@
764
765 QString CameraApplication::videosLocation() const
766 {
767- QString location = QStandardPaths::standardLocations(QStandardPaths::MoviesLocation).at(0) + "/" + QCoreApplication::applicationName();
768+ QStringList locations = QStandardPaths::standardLocations(QStandardPaths::MoviesLocation);
769+ if (locations.isEmpty()) {
770+ return QString();
771+ }
772+ QString location = locations.at(0) + "/" + QCoreApplication::applicationName();
773+ QDir dir;
774+ dir.mkpath(location);
775+ return location;
776+}
777+
778+QString CameraApplication::temporaryLocation() const
779+{
780+ QStringList locations = QStandardPaths::standardLocations(QStandardPaths::TempLocation);
781+ if (locations.isEmpty()) {
782+ return QString();
783+ }
784+ QString location = locations.at(0);
785 QDir dir;
786 dir.mkpath(location);
787 return location;
788
789=== modified file 'cameraapplication.h'
790--- cameraapplication.h 2014-07-02 15:43:31 +0000
791+++ cameraapplication.h 2014-08-05 15:51:59 +0000
792@@ -31,6 +31,7 @@
793 Q_PROPERTY(bool desktopMode READ isDesktopMode CONSTANT)
794 Q_PROPERTY(QString picturesLocation READ picturesLocation CONSTANT)
795 Q_PROPERTY(QString videosLocation READ videosLocation CONSTANT)
796+ Q_PROPERTY(QString temporaryLocation READ temporaryLocation CONSTANT)
797
798 public:
799 CameraApplication(int &argc, char **argv);
800@@ -39,6 +40,7 @@
801 bool isDesktopMode() const;
802 QString picturesLocation() const;
803 QString videosLocation() const;
804+ QString temporaryLocation() const;
805
806 private:
807 QScopedPointer<QQuickView> m_view;
808
809=== modified file 'manifest.json'
810--- manifest.json 2014-07-10 14:27:45 +0000
811+++ manifest.json 2014-08-05 15:51:59 +0000
812@@ -5,7 +5,8 @@
813 "hooks": {
814 "camera": {
815 "apparmor": "camera-apparmor.json",
816- "desktop": "@DESKTOP_FILE@"
817+ "desktop": "@DESKTOP_FILE@",
818+ "content-hub": "camera-contenthub.json"
819 }
820 },
821 "icon": "@CAMERA_ICON@",
822@@ -20,4 +21,4 @@
823 "x-test": {
824 "autopilot": "@AUTOPILOT_DIR@"
825 }
826-}
827+}
828\ No newline at end of file

Subscribers

People subscribed via source and target branches