Merge lp:~mzanetti/reminders-app/shoot into lp:reminders-app
- shoot
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Michael Zanetti |
Approved revision: | 39 |
Merged at revision: | 50 |
Proposed branch: | lp:~mzanetti/reminders-app/shoot |
Merge into: | lp:reminders-app |
Prerequisite: | lp:~mzanetti/reminders-app/cleanup |
Diff against target: |
409 lines (+235/-19) 10 files modified
CMakeLists.txt (+2/-2) src/app/CMakeLists.txt (+4/-1) src/app/camerahelper.cpp (+52/-0) src/app/camerahelper.h (+39/-0) src/app/main.cpp (+21/-12) src/app/qml/ui/CameraPage.qml (+70/-0) src/app/qml/ui/EditNotePage.qml (+4/-0) src/plugin/Evernote/note.cpp (+11/-0) src/plugin/Evernote/resource.cpp (+21/-4) src/plugin/Evernote/utils/enmldocument.cpp (+11/-0) |
To merge this branch: | bzr merge lp:~mzanetti/reminders-app/shoot |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Phone Apps Jenkins Bot | continuous-integration | Approve | |
Riccardo Padovani | Approve | ||
David Planella | Pending | ||
Review via email:
|
This proposal supersedes a proposal from 2014-01-24.
Commit message
Add feature to take a picture with the camera and attach it to the note.
Description of the change
Note: This changes the main.cpp to be mandatory. The CameraHelper class really doesn't belong to the evernote plugin and with the current Ubuntu SDK it's impossible to use the QML Camera element with QML only.
Because of this I added the CameraHelper to the main.cpp which in turn requires us to use the built reminders binary to launch instead of qmlscene.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : Posted in a previous version of this proposal | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
David Planella (dpm) wrote : Posted in a previous version of this proposal | # |
The branch worked nicely, but I noticed a couple of things:
1) The camera pictures on the device (Nexus 4) are offset 90 degrees counterclockwise
2) It uses the default save location of the Camera component (e.g. /home/phablet/
I'm not sure it's related to this change, but I've noticed the same thing I did when I tested a previous branch:
- After creating a note and taking a shot, once saved and back on the notes list, the "add note" button in the toolbar apparently no longer works. However, it does work: but it takes about 30 seconds to react. I saw this in the debug output, but I'm not sure it's related:
Got a transport exception: Could not refill buffer . Trying to reestablish connection...
Thrift: Mon Jan 27 09:08:53 2014 SSL_shutdown: error code: 0
I'll try to reproduce and file a bug properly. I just wanted to note that with the recent branches (from Content Hub onwards) have brought up this issue.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : Posted in a previous version of this proposal | # |
FAILED: Continuous integration, rev:34
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 38. By Michael Zanetti
-
debug--
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:38
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 39. By Michael Zanetti
-
return something in CameraHelper:
:save()
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
PASSED: Continuous integration, rev:39
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Riccardo Padovani (rpadovani) wrote : | # |
Works well, I can add take without problems
> 1) The camera pictures on the device (Nexus 4) are offset 90 degrees counterclockwise
Not anymore
> - After creating a note and taking a shot, once saved and back on the notes list, the "add note" button in the
> toolbar apparently no longer works. However, it does work: but it takes about 30 seconds to react. I saw this in the > debug output, but I'm not sure it's related:
Confirm that add note button doesn't work for ~30 sec, but I haven't error messages on my terminal
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michael Zanetti (mzanetti) wrote : | # |
> Confirm that add note button doesn't work for ~30 sec, but I haven't error
> messages on my terminal
This happens if we're waiting for a response from the evernote server and it takes so long. Given that we don't support offline note editing with later syncing, the current code needs to wait for a confirmation when creating a new note. This should obviously change in the future, but out of the scope of this merge.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) : | # |
Preview Diff
1 | === modified file 'CMakeLists.txt' |
2 | --- CMakeLists.txt 2014-01-28 12:18:59 +0000 |
3 | +++ CMakeLists.txt 2014-01-28 12:18:59 +0000 |
4 | @@ -33,11 +33,11 @@ |
5 | set(CMAKE_INSTALL_PREFIX /) |
6 | set(CMAKE_INSTALL_BINDIR /) |
7 | set(DATA_DIR /) |
8 | - set(EXEC "qmlscene $@ -I .${QT_IMPORTS_DIR} qml/${MAIN_QML}") |
9 | + set(EXEC "./reminders -I .${QT_IMPORTS_DIR}") |
10 | set(DESKTOP_DIR ${DATA_DIR}) |
11 | else(CLICK_MODE) |
12 | set(DATA_DIR ${CMAKE_INSTALL_DATADIR}/${APP_HARDCODE}) |
13 | - set(EXEC "qmlscene $@ ${CMAKE_INSTALL_PREFIX}/${DATA_DIR}/qml/${MAIN_QML}") |
14 | + set(EXEC "reminders") |
15 | set(ICON "${CMAKE_INSTALL_PREFIX}/${DATA_DIR}/${ICON}") |
16 | set(DESKTOP_DIR ${CMAKE_INSTALL_DATADIR}/applications) |
17 | endif(CLICK_MODE) |
18 | |
19 | === modified file 'src/app/CMakeLists.txt' |
20 | --- src/app/CMakeLists.txt 2014-01-28 12:18:59 +0000 |
21 | +++ src/app/CMakeLists.txt 2014-01-28 12:18:59 +0000 |
22 | @@ -2,6 +2,7 @@ |
23 | |
24 | set(reminders_SRCS |
25 | main.cpp |
26 | + camerahelper.cpp |
27 | ${QML_SRCS} |
28 | ) |
29 | |
30 | @@ -15,6 +16,8 @@ |
31 | ) |
32 | |
33 | install(DIRECTORY qml DESTINATION ${DATA_DIR}) |
34 | -if(NOT CLICK_MODE) |
35 | +if(CLICK_MODE) |
36 | + install(TARGETS reminders DESTINATION ${DATA_DIR}) |
37 | +else() |
38 | install(TARGETS reminders RUNTIME DESTINATION bin) |
39 | endif() |
40 | |
41 | === added file 'src/app/camerahelper.cpp' |
42 | --- src/app/camerahelper.cpp 1970-01-01 00:00:00 +0000 |
43 | +++ src/app/camerahelper.cpp 2014-01-28 12:18:59 +0000 |
44 | @@ -0,0 +1,52 @@ |
45 | +/* |
46 | + * Copyright: 2013 Canonical, Ltd |
47 | + * |
48 | + * This file is part of reminders |
49 | + * |
50 | + * reminders is free software: you can redistribute it and/or modify |
51 | + * it under the terms of the GNU General Public License as published by |
52 | + * the Free Software Foundation, either version 3 of the License, or |
53 | + * (at your option) any later version. |
54 | + * |
55 | + * reminders is distributed in the hope that it will be useful, |
56 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
57 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
58 | + * GNU General Public License for more details. |
59 | + * |
60 | + * You should have received a copy of the GNU General Public License |
61 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
62 | + * |
63 | + * Authors: Michael Zanetti <michael.zanetti@canonical.com> |
64 | + */ |
65 | + |
66 | +#include "camerahelper.h" |
67 | + |
68 | +#include <QStandardPaths> |
69 | +#include <QCoreApplication> |
70 | +#include <QImage> |
71 | +#include <QTransform> |
72 | + |
73 | +CameraHelper::CameraHelper(QObject *parent): |
74 | + QObject(parent) |
75 | +{ |
76 | + |
77 | +} |
78 | + |
79 | +QString CameraHelper::importLocation() const |
80 | +{ |
81 | + QString homePath = QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first(); |
82 | + QString appName = QCoreApplication::applicationName(); |
83 | + return homePath + "/.cache/" + appName + '/'; |
84 | +} |
85 | + |
86 | +bool CameraHelper::rotate(const QString &imageFile, int angle) |
87 | +{ |
88 | + QImage image; |
89 | + if (!image.load(imageFile)) { |
90 | + return false; |
91 | + } |
92 | + QTransform transform; |
93 | + transform.rotate(angle); |
94 | + image = image.transformed(transform); |
95 | + return image.save(imageFile); |
96 | +} |
97 | |
98 | === added file 'src/app/camerahelper.h' |
99 | --- src/app/camerahelper.h 1970-01-01 00:00:00 +0000 |
100 | +++ src/app/camerahelper.h 2014-01-28 12:18:59 +0000 |
101 | @@ -0,0 +1,39 @@ |
102 | +/* |
103 | + * Copyright: 2013 Canonical, Ltd |
104 | + * |
105 | + * This file is part of reminders |
106 | + * |
107 | + * reminders is free software: you can redistribute it and/or modify |
108 | + * it under the terms of the GNU General Public License as published by |
109 | + * the Free Software Foundation, either version 3 of the License, or |
110 | + * (at your option) any later version. |
111 | + * |
112 | + * reminders is distributed in the hope that it will be useful, |
113 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
114 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
115 | + * GNU General Public License for more details. |
116 | + * |
117 | + * You should have received a copy of the GNU General Public License |
118 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
119 | + * |
120 | + * Authors: Michael Zanetti <michael.zanetti@canonical.com> |
121 | + */ |
122 | + |
123 | +#ifndef CAMERAHELPER_H |
124 | +#define CAMERAHELPER_H |
125 | + |
126 | +#include <QObject> |
127 | + |
128 | +class CameraHelper: public QObject |
129 | +{ |
130 | + Q_OBJECT |
131 | + Q_PROPERTY(QString importLocation READ importLocation CONSTANT) |
132 | +public: |
133 | + CameraHelper(QObject *parent = 0); |
134 | + |
135 | + QString importLocation() const; |
136 | + |
137 | + Q_INVOKABLE bool rotate(const QString &imageFile, int angle); |
138 | +}; |
139 | + |
140 | +#endif |
141 | |
142 | === modified file 'src/app/main.cpp' |
143 | --- src/app/main.cpp 2014-01-28 12:18:59 +0000 |
144 | +++ src/app/main.cpp 2014-01-28 12:18:59 +0000 |
145 | @@ -19,34 +19,43 @@ |
146 | * Authors: Michael Zanetti <michael.zanetti@canonical.com> |
147 | */ |
148 | |
149 | +#include "camerahelper.h" |
150 | + |
151 | #include <QtGui/QGuiApplication> |
152 | #include <QtQuick/QQuickView> |
153 | #include <QtQml/QtQml> |
154 | |
155 | #include <QDebug> |
156 | |
157 | -/* |
158 | - * This is just a minimalistic main to fire up our own qml scene which has the |
159 | - * import path for the plugin preconfigured. This is just used for easier |
160 | - * development and while we can ship this, we could also run the app ourselves |
161 | - * with: |
162 | - * qmlscene -I /path/to/plugin/ reminders.qml |
163 | - */ |
164 | - |
165 | int main(int argc, char *argv[]) |
166 | { |
167 | - |
168 | - // Do the same as qmlscene does |
169 | QGuiApplication a(argc, argv); |
170 | QQuickView view; |
171 | view.setResizeMode(QQuickView::SizeRootObjectToView); |
172 | |
173 | - // Additionally add the -I ../plugin to load the plugin |
174 | + // Set up import paths |
175 | QStringList importPathList = view.engine()->importPathList(); |
176 | importPathList.append(QDir::currentPath() + "/../plugin/"); |
177 | + |
178 | + QStringList args = a.arguments(); |
179 | + for (int i = 0; i < args.count(); i++) { |
180 | + if (args.at(i) == "-I" && args.count() > i + 1) { |
181 | + QString addedPath = args.at(i+1); |
182 | + if (addedPath.startsWith('.')) { |
183 | + addedPath = addedPath.right(addedPath.length() - 1); |
184 | + addedPath.prepend(QDir::currentPath()); |
185 | + } |
186 | + importPathList.append(addedPath); |
187 | + } |
188 | + } |
189 | + |
190 | view.engine()->setImportPathList(importPathList); |
191 | |
192 | - // and directly load the qml file |
193 | + // Set up camera helper |
194 | + CameraHelper helper; |
195 | + view.engine()->rootContext()->setContextProperty("cameraHelper", &helper); |
196 | + |
197 | + // load the qml file |
198 | view.setSource(QUrl::fromLocalFile("qml/reminders.qml")); |
199 | |
200 | view.show(); |
201 | |
202 | === added file 'src/app/qml/ui/CameraPage.qml' |
203 | --- src/app/qml/ui/CameraPage.qml 1970-01-01 00:00:00 +0000 |
204 | +++ src/app/qml/ui/CameraPage.qml 2014-01-28 12:18:59 +0000 |
205 | @@ -0,0 +1,70 @@ |
206 | +/* |
207 | + * Copyright: 2013 Canonical, Ltd |
208 | + * |
209 | + * This file is part of reminders-app |
210 | + * |
211 | + * reminders-app is free software: you can redistribute it and/or modify |
212 | + * it under the terms of the GNU General Public License as published by |
213 | + * the Free Software Foundation; version 3. |
214 | + * |
215 | + * reminders-app is distributed in the hope that it will be useful, |
216 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
217 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
218 | + * GNU General Public License for more details. |
219 | + * |
220 | + * You should have received a copy of the GNU General Public License |
221 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
222 | + */ |
223 | + |
224 | +import QtQuick 2.0 |
225 | +import Ubuntu.Components 0.1 |
226 | +import QtMultimedia 5.0 |
227 | +import QtQuick.Window 2.0 |
228 | +import Evernote 0.1 |
229 | + |
230 | +Page { |
231 | + id: root |
232 | + property var note |
233 | + property int position |
234 | + |
235 | + tools: ToolbarItems { |
236 | + locked: true |
237 | + opened: true |
238 | + ToolbarButton { |
239 | + text: "Shoot" |
240 | + iconName: "camera-symbolic" |
241 | + onTriggered: camera.imageCapture.captureToLocation(cameraHelper.importLocation); |
242 | + } |
243 | + } |
244 | + |
245 | + Camera { |
246 | + id: camera |
247 | + flash.mode: Camera.FlashTorch |
248 | + focus.focusMode: Camera.FocusContinuous |
249 | + focus.focusPointMode: Camera.FocusPointAuto |
250 | + |
251 | + imageCapture { |
252 | + |
253 | + onImageSaved: { |
254 | + if (videoOutput.orientation != 0) { |
255 | + cameraHelper.rotate(path, -videoOutput.orientation) |
256 | + } |
257 | + |
258 | + root.note.attachFile(root.position, path) |
259 | + print("got image", path) |
260 | + pagestack.pop(); |
261 | + } |
262 | + } |
263 | + } |
264 | + |
265 | + VideoOutput { |
266 | + id: videoOutput |
267 | + anchors { |
268 | + fill: parent |
269 | + } |
270 | + fillMode: Image.PreserveAspectCrop |
271 | + orientation: Screen.primaryOrientation === Qt.PortraitOrientation ? -90 : 0 |
272 | + source: camera |
273 | + focus: visible |
274 | + } |
275 | +} |
276 | |
277 | === modified file 'src/app/qml/ui/EditNotePage.qml' |
278 | --- src/app/qml/ui/EditNotePage.qml 2014-01-28 12:18:59 +0000 |
279 | +++ src/app/qml/ui/EditNotePage.qml 2014-01-28 12:18:59 +0000 |
280 | @@ -172,6 +172,10 @@ |
281 | MouseArea { |
282 | anchors.fill: parent |
283 | onClicked: { |
284 | + priv.insertPosition = noteTextArea.cursorPosition; |
285 | + note.richTextContent = noteTextArea.text; |
286 | + |
287 | + pagestack.push(Qt.resolvedUrl("CameraPage.qml"), {note: root.note, position: priv.insertPosition}) |
288 | } |
289 | } |
290 | } |
291 | |
292 | === modified file 'src/plugin/Evernote/note.cpp' |
293 | --- src/plugin/Evernote/note.cpp 2014-01-28 12:18:59 +0000 |
294 | +++ src/plugin/Evernote/note.cpp 2014-01-28 12:18:59 +0000 |
295 | @@ -242,9 +242,20 @@ |
296 | |
297 | void Note::attachFile(int position, const QUrl &fileName) |
298 | { |
299 | + QFile importedFile(fileName.toString()); |
300 | + if (!importedFile.exists()) { |
301 | + return; |
302 | + } |
303 | + |
304 | + qDebug() << "attaching file" << position << fileName; |
305 | Resource *resource = addResource(fileName.path()); |
306 | m_content.attachFile(position, fileName.path(), resource->hash(), resource->type()); |
307 | emit contentChanged(); |
308 | + |
309 | + // Cleanup imported file. |
310 | + // TODO: If the app should be extended to allow attaching other files, and we somehow |
311 | + // can browse to unconfined files, this needs to be made conditional to not delete those files! |
312 | + importedFile.remove(); |
313 | } |
314 | |
315 | Note *Note::clone() |
316 | |
317 | === modified file 'src/plugin/Evernote/resource.cpp' |
318 | --- src/plugin/Evernote/resource.cpp 2014-01-28 12:18:59 +0000 |
319 | +++ src/plugin/Evernote/resource.cpp 2014-01-28 12:18:59 +0000 |
320 | @@ -24,6 +24,7 @@ |
321 | #include <QStandardPaths> |
322 | #include <QDebug> |
323 | #include <QCryptographicHash> |
324 | +#include <QFileInfo> |
325 | |
326 | Resource::Resource(const QByteArray &data, const QString &hash, const QString &fileName, const QString &type, QObject *parent): |
327 | m_hash(hash), |
328 | @@ -45,18 +46,18 @@ |
329 | } |
330 | } |
331 | |
332 | -Resource::Resource(const QString &path, QObject *parent): |
333 | - m_filePath(path) |
334 | +Resource::Resource(const QString &path, QObject *parent) |
335 | { |
336 | + |
337 | QFile file(path); |
338 | if (!file.open(QFile::ReadOnly)) { |
339 | qWarning() << "Cannot open file for reading..."; |
340 | return; |
341 | } |
342 | - |
343 | QByteArray fileContent = file.readAll(); |
344 | - m_hash = QCryptographicHash::hash(fileContent, QCryptographicHash::Md5).toHex(); |
345 | + file.close(); |
346 | |
347 | + m_hash = QCryptographicHash::hash(fileContent, QCryptographicHash::Md5).toHex(); |
348 | m_fileName = path.split('/').last(); |
349 | if (m_fileName.endsWith(".png")) { |
350 | m_type = "image/png"; |
351 | @@ -65,6 +66,22 @@ |
352 | } else { |
353 | qWarning() << "cannot determine mime type of file" << m_fileName; |
354 | } |
355 | + |
356 | + m_filePath = QStandardPaths::standardLocations(QStandardPaths::CacheLocation).first() + "/" + m_hash + "." + m_type.split('/').last(); |
357 | + |
358 | + QFile copy(m_filePath); |
359 | + if (!copy.exists()) { |
360 | + |
361 | + if (!copy.open(QFile::WriteOnly)) { |
362 | + qWarning() << "error writing file" << m_filePath; |
363 | + return; |
364 | + } |
365 | + copy.write(fileContent); |
366 | + copy.close(); |
367 | + } |
368 | + |
369 | + |
370 | + qDebug() << "created resource" << m_hash; |
371 | } |
372 | |
373 | QString Resource::hash() const |
374 | |
375 | === modified file 'src/plugin/Evernote/utils/enmldocument.cpp' |
376 | --- src/plugin/Evernote/utils/enmldocument.cpp 2014-01-28 12:18:59 +0000 |
377 | +++ src/plugin/Evernote/utils/enmldocument.cpp 2014-01-28 12:18:59 +0000 |
378 | @@ -379,6 +379,7 @@ |
379 | writer.writeDTD("<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">"); |
380 | |
381 | int textPos = 0; |
382 | + bool inserted = false; |
383 | |
384 | while (!reader.atEnd() && !reader.hasError()) { |
385 | QXmlStreamReader::TokenType token = reader.readNext(); |
386 | @@ -397,6 +398,7 @@ |
387 | writer.writeAttribute("hash", hash); |
388 | writer.writeAttribute("type", type); |
389 | writer.writeEndElement(); |
390 | + inserted = true; |
391 | |
392 | writer.writeCharacters(textString.right(textString.length() - (position - textPos))); |
393 | } else { |
394 | @@ -405,6 +407,15 @@ |
395 | textPos += textString.length(); |
396 | } |
397 | if (token == QXmlStreamReader::EndElement) { |
398 | + |
399 | + // The above logic would fail on an empty note |
400 | + if (reader.name() == "en-note" && !inserted) { |
401 | + writer.writeStartElement("en-media"); |
402 | + writer.writeAttribute("hash", hash); |
403 | + writer.writeAttribute("type", type); |
404 | + writer.writeEndElement(); |
405 | + } |
406 | + |
407 | writer.writeEndElement(); |
408 | } |
409 | } |
PASSED: Continuous integration, rev:33 91.189. 93.70:8080/ job/reminders- app-ci/ 75/ 91.189. 93.70:8080/ job/generic- mediumtests- trusty/ 793 91.189. 93.70:8080/ job/reminders- app-saucy- amd64-ci/ 75 91.189. 93.70:8080/ job/reminders- app-trusty- amd64-ci/ 75
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild: 91.189. 93.70:8080/ job/reminders- app-ci/ 75/rebuild
http://