Merge lp:~fboucault/camera-app/content_source into lp:camera-app
- content_source
- Merge into trunk
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 |
Related bugs: |
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.
Description of the change
Florian Boucault (fboucault) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:339
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Ugo Riboni (uriboni) wrote : | # |
Minor style and edge cases pointed out inline.
Runs fine, but not sure how to test the actual functionality.
- 340. By Florian Boucault
-
Test for QStandardPaths:
:standardLocati ons return value.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:340
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
- 341. By Florian Boucault
-
Do not reset mediaPath.
- 342. By Florian Boucault
-
Make sure everything else but the confirmation dialog is invisible.
Preview Diff
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 |
Missing polish and tests.