Merge lp:~mzanetti/reminders-app/add-image-support into lp:reminders-app
- add-image-support
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | David Planella |
Approved revision: | 29 |
Merged at revision: | 22 |
Proposed branch: | lp:~mzanetti/reminders-app/add-image-support |
Merge into: | lp:reminders-app |
Prerequisite: | lp:~mzanetti/reminders-app/notesdelegate |
Diff against target: |
926 lines (+446/-82) 19 files modified
src/app/qml/components/NotesDelegate.qml (+9/-1) src/app/qml/images/unchecked.svg (+46/-0) src/app/qml/ui/NotePage.qml (+2/-2) src/app/qml/ui/NotesPage.qml (+2/-1) src/app/qml/ui/SearchNotesPage.qml (+1/-1) src/plugin/Evernote/Evernote.pro (+7/-5) src/plugin/Evernote/evernoteplugin.cpp (+7/-1) src/plugin/Evernote/evernoteplugin.h (+2/-1) src/plugin/Evernote/jobs/evernotejob.cpp (+1/-1) src/plugin/Evernote/jobs/fetchnotejob.cpp (+1/-1) src/plugin/Evernote/jobs/savenotejob.cpp (+2/-2) src/plugin/Evernote/note.cpp (+51/-14) src/plugin/Evernote/note.h (+18/-6) src/plugin/Evernote/notesstore.cpp (+26/-7) src/plugin/Evernote/notesstore.h (+5/-1) src/plugin/Evernote/resourceimageprovider.cpp (+42/-0) src/plugin/Evernote/resourceimageprovider.h (+14/-0) src/plugin/Evernote/utils/enmldocument.cpp (+189/-29) src/plugin/Evernote/utils/enmldocument.h (+21/-9) |
To merge this branch: | bzr merge lp:~mzanetti/reminders-app/add-image-support |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Phone Apps Jenkins Bot | continuous-integration | Approve | |
David Planella | Approve | ||
Alan Pope πΊπ§π± π¦ (community) | Needs Fixing | ||
Review via email: mp+199031@code.launchpad.net |
Commit message
added support for image resources and greatly improve editor compatibility
Description of the change
This adds support for fetching resources (e.g. images) with notes. To make use of that I've improved the editor's compatibility to enml. We can now display in-text images and TODOs (aka. checkboxes), support tables and most of the rich text.
This screenshot shows the current state: http://
The TODOs are a bit of a problem because we can't embed checkbox controls into the TextEdit. We can tho (and that's what I'm doing in this merge) replace the checkboxes with images of checkboxes. The downside is that they aren't interactive. Maybe we can find some way to figure the image's position and put an overlaying MouseArea on top to toggle them but a first glance at it tells me that it's gonna be really tricky. Another idea would be to put an entry "manage TODOs" into the toolbar and open a sheet containing only the todos and real checkboxes there. Changing the checkboxes in there would then update the image in the text. Let's ask design for their opinion on this.
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
Alan Pope πΊπ§π± π¦ (popey) wrote : | # |
I get no image on device.
http://
Alan Pope πΊπ§π± π¦ (popey) : | # |
Michael Zanetti (mzanetti) wrote : | # |
> I get no image on device.
>
> http://
> http://
Just tried again. Works fine here. Can you provide more details please? Is there any debug output hinting to an error or the like?
David Planella (dpm) wrote : | # |
This worked for me:
David Planella (dpm) : | # |
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
David Planella (dpm) wrote : | # |
Hm, looking at Jenkins' output, it shows "bzr: ERROR: Conflicts from merge", but I don't see any conflicts coming from the MP in LP.
http://
David Planella (dpm) wrote : | # |
Ah, trying to merge manually shows the actual conflicts:
RM src/plugin/
RM src/plugin/
Text conflict in src/plugin/
Text conflict in src/plugin/
2 conflicts encountered.
- 29. By Michael Zanetti
-
merge trunk
Michael Zanetti (mzanetti) wrote : | # |
conflicts resolved
David Planella (dpm) wrote : | # |
Works well after fixing the conflicts!
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) : | # |
Preview Diff
1 | === modified file 'src/app/qml/components/NotesDelegate.qml' |
2 | --- src/app/qml/components/NotesDelegate.qml 2013-12-14 00:07:30 +0000 |
3 | +++ src/app/qml/components/NotesDelegate.qml 2014-01-10 09:05:41 +0000 |
4 | @@ -27,6 +27,7 @@ |
5 | property string title |
6 | property date creationDate |
7 | property string content |
8 | + property string resource |
9 | |
10 | Column { |
11 | id: contentColumn |
12 | @@ -36,7 +37,7 @@ |
13 | topMargin: units.gu(1) |
14 | left: parent.left |
15 | leftMargin: units.gu(2) |
16 | - right: parent.right |
17 | + right: resourceImage.left |
18 | rightMargin: units.gu(2) |
19 | } |
20 | Label { |
21 | @@ -53,4 +54,11 @@ |
22 | maximumLineCount: 2 |
23 | } |
24 | } |
25 | + |
26 | + Image { |
27 | + id: resourceImage |
28 | + anchors { top: parent.top; right: parent.right; bottom: parent.bottom } |
29 | + source: root.resource |
30 | + sourceSize.height: height |
31 | + } |
32 | } |
33 | |
34 | === added directory 'src/app/qml/images' |
35 | === added file 'src/app/qml/images/unchecked.svg' |
36 | --- src/app/qml/images/unchecked.svg 1970-01-01 00:00:00 +0000 |
37 | +++ src/app/qml/images/unchecked.svg 2014-01-10 09:05:41 +0000 |
38 | @@ -0,0 +1,46 @@ |
39 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
40 | +<!-- Created with Inkscape (http://www.inkscape.org/) --> |
41 | + |
42 | +<svg |
43 | + xmlns:dc="http://purl.org/dc/elements/1.1/" |
44 | + xmlns:cc="http://creativecommons.org/ns#" |
45 | + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
46 | + xmlns:svg="http://www.w3.org/2000/svg" |
47 | + xmlns="http://www.w3.org/2000/svg" |
48 | + version="1.1" |
49 | + width="90" |
50 | + height="90" |
51 | + id="svg3140"> |
52 | + <defs |
53 | + id="defs12" /> |
54 | + <metadata |
55 | + id="metadata3145"> |
56 | + <rdf:RDF> |
57 | + <cc:Work |
58 | + rdf:about=""> |
59 | + <dc:format>image/svg+xml</dc:format> |
60 | + <dc:type |
61 | + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> |
62 | + <dc:title></dc:title> |
63 | + </cc:Work> |
64 | + </rdf:RDF> |
65 | + </metadata> |
66 | + <rect |
67 | + width="90" |
68 | + height="90" |
69 | + x="0" |
70 | + y="-1.4648437e-05" |
71 | + id="rect4198" |
72 | + style="color:#000000;fill:none" /> |
73 | + <path |
74 | + d="m 6,24 v 42 c 0,18 3,18 30,18 h 18 c 27,0 30,0 30,-18 V 24 C 84,6 81,6 54,6 H 36 C 9,6 6,6 6,24 z M 36,12 h 18 c 24,0 24,0 24,12 v 42 c 0,12 0,12 -24,12 H 36 C 12,78 12,78 12,66 V 24 C 12,12 12,12 36,12 z" |
75 | + id="path3759" |
76 | + style="fill:#808080" /> |
77 | + <rect |
78 | + width="90" |
79 | + height="90" |
80 | + x="0" |
81 | + y="1.7382999e-05" |
82 | + id="rect3045" |
83 | + style="color:#000000;fill:none" /> |
84 | +</svg> |
85 | |
86 | === modified file 'src/app/qml/ui/NotePage.qml' |
87 | --- src/app/qml/ui/NotePage.qml 2013-12-13 23:59:53 +0000 |
88 | +++ src/app/qml/ui/NotePage.qml 2014-01-10 09:05:41 +0000 |
89 | @@ -33,7 +33,7 @@ |
90 | width: parent.width |
91 | text: "save" |
92 | onClicked: { |
93 | - note.content = noteTextArea.text |
94 | + note.htmlContent = noteTextArea.text |
95 | note.save(); |
96 | } |
97 | } |
98 | @@ -44,7 +44,7 @@ |
99 | height: parent.height - y |
100 | |
101 | textFormat: TextEdit.RichText |
102 | - text: note.content |
103 | + text: note.htmlContent |
104 | } |
105 | } |
106 | } |
107 | |
108 | === modified file 'src/app/qml/ui/NotesPage.qml' |
109 | --- src/app/qml/ui/NotesPage.qml 2013-12-13 23:39:24 +0000 |
110 | +++ src/app/qml/ui/NotesPage.qml 2014-01-10 09:05:41 +0000 |
111 | @@ -70,7 +70,8 @@ |
112 | delegate: NotesDelegate { |
113 | title: model.title |
114 | creationDate: model.created |
115 | - content: NotesStore.note(model.guid).plaintextContent |
116 | + content: model.plaintextContent |
117 | + resource: model.resources.length > 0 ? model.resources[0] : "" |
118 | |
119 | onClicked: { |
120 | pageStack.push(Qt.resolvedUrl("NotePage.qml"), {note: NotesStore.note(guid)}) |
121 | |
122 | === modified file 'src/app/qml/ui/SearchNotesPage.qml' |
123 | --- src/app/qml/ui/SearchNotesPage.qml 2013-12-13 23:39:24 +0000 |
124 | +++ src/app/qml/ui/SearchNotesPage.qml 2014-01-10 09:05:41 +0000 |
125 | @@ -68,7 +68,7 @@ |
126 | delegate: NotesDelegate { |
127 | title: model.title |
128 | creationDate: model.created |
129 | - content: NotesStore.note(model.guid).plaintextContent |
130 | + content: model.plaintextContent |
131 | |
132 | onClicked: { |
133 | pageStack.push(Qt.resolvedUrl("NotePage.qml"), {note: NotesStore.note(guid)}) |
134 | |
135 | === modified file 'src/plugin/Evernote/Evernote.pro' |
136 | --- src/plugin/Evernote/Evernote.pro 2013-12-12 22:41:35 +0000 |
137 | +++ src/plugin/Evernote/Evernote.pro 2014-01-10 09:05:41 +0000 |
138 | @@ -1,7 +1,7 @@ |
139 | TARGET=evernoteplugin |
140 | TEMPLATE=lib |
141 | CONFIG = qt plugin |
142 | -QT += qml gui xml |
143 | +QT += qml gui xml quick |
144 | QMAKE_CXXFLAGS += -std=c++0x -fPIC |
145 | |
146 | INCLUDEPATH += ../../../3rdParty/evernote-sdk-cpp/src/ ../../../3rdParty/libthrift |
147 | @@ -23,13 +23,14 @@ |
148 | jobs/evernotejob.cpp \ |
149 | jobs/savenotejob.cpp \ |
150 | jobs/deletenotejob.cpp \ |
151 | - utils/html2enmlconverter.cpp \ |
152 | evernoteconnection.cpp \ |
153 | jobs/userstorejob.cpp \ |
154 | jobs/notesstorejob.cpp \ |
155 | jobs/fetchusernamejob.cpp \ |
156 | jobs/createnotebookjob.cpp \ |
157 | - jobs/expungenotebookjob.cpp |
158 | + jobs/expungenotebookjob.cpp \ |
159 | + resourceimageprovider.cpp \ |
160 | + utils/enmldocument.cpp |
161 | |
162 | HEADERS += evernoteplugin.h \ |
163 | notesstore.h \ |
164 | @@ -45,13 +46,14 @@ |
165 | jobs/evernotejob.h \ |
166 | jobs/savenotejob.h \ |
167 | jobs/deletenotejob.h \ |
168 | - utils/html2enmlconverter.h \ |
169 | evernoteconnection.h \ |
170 | jobs/userstorejob.h \ |
171 | jobs/notesstorejob.h \ |
172 | jobs/fetchusernamejob.h \ |
173 | jobs/createnotebookjob.h \ |
174 | - jobs/expungenotebookjob.h |
175 | + jobs/expungenotebookjob.h \ |
176 | + resourceimageprovider.h \ |
177 | + utils/enmldocument.h |
178 | |
179 | message(building in $$OUT_PWD) |
180 | LIBS += -L$$OUT_PWD/../../../3rdParty/evernote-sdk-cpp/ -L$$OUT_PWD/../../../3rdParty/libthrift/ -levernote-sdk-cpp -llibthrift -lssl -lcrypto |
181 | |
182 | === modified file 'src/plugin/Evernote/evernoteplugin.cpp' |
183 | --- src/plugin/Evernote/evernoteplugin.cpp 2013-11-28 00:39:33 +0000 |
184 | +++ src/plugin/Evernote/evernoteplugin.cpp 2014-01-10 09:05:41 +0000 |
185 | @@ -26,6 +26,7 @@ |
186 | #include "notes.h" |
187 | #include "notebooks.h" |
188 | #include "note.h" |
189 | +#include "resourceimageprovider.h" |
190 | |
191 | #include <QtQml> |
192 | |
193 | @@ -44,7 +45,7 @@ |
194 | return EvernoteConnection::instance(); |
195 | } |
196 | |
197 | -void FitBitPlugin::registerTypes(const char *uri) |
198 | +void EvernotePlugin::registerTypes(const char *uri) |
199 | { |
200 | qmlRegisterSingletonType<UserStore>("Evernote", 0, 1, "UserStore", userStoreProvider); |
201 | qmlRegisterSingletonType<NotesStore>("Evernote", 0, 1, "NotesStore", notesStoreProvider); |
202 | @@ -54,3 +55,8 @@ |
203 | qmlRegisterType<Notebooks>("Evernote", 0, 1, "Notebooks"); |
204 | qmlRegisterUncreatableType<Note>("Evernote", 0, 1, "Note", "Cannot create Notes in QML. Use NotesStore.createNote() instead."); |
205 | } |
206 | + |
207 | +void EvernotePlugin::initializeEngine(QQmlEngine *engine, const char *uri) |
208 | +{ |
209 | + engine->addImageProvider("resource", new ResourceImageProvider); |
210 | +} |
211 | |
212 | === modified file 'src/plugin/Evernote/evernoteplugin.h' |
213 | --- src/plugin/Evernote/evernoteplugin.h 2013-11-21 23:30:15 +0000 |
214 | +++ src/plugin/Evernote/evernoteplugin.h 2014-01-10 09:05:41 +0000 |
215 | @@ -23,12 +23,13 @@ |
216 | |
217 | #include <QQmlExtensionPlugin> |
218 | |
219 | -class FitBitPlugin : public QQmlExtensionPlugin |
220 | +class EvernotePlugin : public QQmlExtensionPlugin |
221 | { |
222 | Q_OBJECT |
223 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface") |
224 | public: |
225 | void registerTypes(const char *uri); |
226 | + void initializeEngine(QQmlEngine *engine, const char *uri); |
227 | }; |
228 | |
229 | #endif // FITBITPLUGIN_H |
230 | |
231 | === modified file 'src/plugin/Evernote/jobs/evernotejob.cpp' |
232 | --- src/plugin/Evernote/jobs/evernotejob.cpp 2013-12-12 22:41:35 +0000 |
233 | +++ src/plugin/Evernote/jobs/evernotejob.cpp 2014-01-10 09:05:41 +0000 |
234 | @@ -83,7 +83,7 @@ |
235 | |
236 | |
237 | } catch (const evernote::edam::EDAMUserException &e) { |
238 | - qWarning() << "EDAMUserException" << e.what(); |
239 | + qWarning() << "EDAMUserException" << e.what() << e.errorCode; |
240 | emitJobDone(EvernoteConnection::ErrorCodeUserException, e.what()); |
241 | } catch (const evernote::edam::EDAMSystemException &e) { |
242 | qWarning() << "EDAMSystemException" << e.what(); |
243 | |
244 | === modified file 'src/plugin/Evernote/jobs/fetchnotejob.cpp' |
245 | --- src/plugin/Evernote/jobs/fetchnotejob.cpp 2013-12-13 23:59:53 +0000 |
246 | +++ src/plugin/Evernote/jobs/fetchnotejob.cpp 2014-01-10 09:05:41 +0000 |
247 | @@ -28,7 +28,7 @@ |
248 | |
249 | void FetchNoteJob::startJob() |
250 | { |
251 | - client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), true, false, false, false); |
252 | + client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), true, true, false, false); |
253 | } |
254 | |
255 | void FetchNoteJob::emitJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage) |
256 | |
257 | === modified file 'src/plugin/Evernote/jobs/savenotejob.cpp' |
258 | --- src/plugin/Evernote/jobs/savenotejob.cpp 2013-12-12 21:36:17 +0000 |
259 | +++ src/plugin/Evernote/jobs/savenotejob.cpp 2014-01-10 09:05:41 +0000 |
260 | @@ -42,9 +42,9 @@ |
261 | note.__isset.title = true; |
262 | note.notebookGuid = m_note->notebookGuid().toStdString(); |
263 | note.__isset.notebookGuid = true; |
264 | - note.content = m_note->content().toStdString(); |
265 | + note.content = m_note->enmlContent().toStdString(); |
266 | note.__isset.content = true; |
267 | - note.contentLength = m_note->content().length(); |
268 | + note.contentLength = m_note->enmlContent().length(); |
269 | |
270 | note.__isset.attributes = true; |
271 | note.attributes.reminderOrder = m_note->reminderOrder(); |
272 | |
273 | === modified file 'src/plugin/Evernote/note.cpp' |
274 | --- src/plugin/Evernote/note.cpp 2013-12-13 23:39:24 +0000 |
275 | +++ src/plugin/Evernote/note.cpp 2014-01-10 09:05:41 +0000 |
276 | @@ -21,9 +21,10 @@ |
277 | #include "note.h" |
278 | |
279 | #include "notesstore.h" |
280 | -#include "utils/html2enmlconverter.h" |
281 | |
282 | #include <QDateTime> |
283 | +#include <QUrl> |
284 | +#include <QUrlQuery> |
285 | #include <QDebug> |
286 | |
287 | Note::Note(const QString &guid, const QDateTime &created, QObject *parent) : |
288 | @@ -70,24 +71,35 @@ |
289 | } |
290 | } |
291 | |
292 | -QString Note::content() const |
293 | -{ |
294 | - return m_content; |
295 | -} |
296 | - |
297 | -void Note::setContent(const QString &content) |
298 | -{ |
299 | - if (m_content != content) { |
300 | - m_content = content; |
301 | - m_plaintextContent = Html2EnmlConverter::enml2plaintext(content); |
302 | - qDebug() << "plaintext content is" << m_plaintextContent; |
303 | +QString Note::enmlContent() const |
304 | +{ |
305 | + return m_content.enml(); |
306 | +} |
307 | + |
308 | +void Note::setEnmlContent(const QString &enmlContent) |
309 | +{ |
310 | + if (m_content.enml() != enmlContent) { |
311 | + m_content.setEnml(enmlContent); |
312 | + emit contentChanged(); |
313 | + } |
314 | +} |
315 | + |
316 | +QString Note::htmlContent() const |
317 | +{ |
318 | + return m_content.html(m_guid); |
319 | +} |
320 | + |
321 | +void Note::setHtmlContent(const QString &htmlContent) |
322 | +{ |
323 | + if (m_content.html(m_guid) != htmlContent) { |
324 | + m_content.setHtml(htmlContent); |
325 | emit contentChanged(); |
326 | } |
327 | } |
328 | |
329 | QString Note::plaintextContent() const |
330 | { |
331 | - return m_plaintextContent; |
332 | + return m_content.plaintext(); |
333 | } |
334 | |
335 | bool Note::reminder() const |
336 | @@ -171,12 +183,37 @@ |
337 | } |
338 | } |
339 | |
340 | +QStringList Note::resources() const |
341 | +{ |
342 | + QList<QString> ret; |
343 | + foreach (const QString &hash, m_resources.keys()) { |
344 | + QUrl url("image://resource/" + m_resourceTypes.value(hash)); |
345 | + QUrlQuery arguments; |
346 | + arguments.addQueryItem("noteGuid", m_guid); |
347 | + arguments.addQueryItem("hash", hash.toLocal8Bit().toHex()); |
348 | + url.setQuery(arguments); |
349 | + ret << url.toString(); |
350 | + } |
351 | + return ret; |
352 | +} |
353 | + |
354 | +QImage Note::resource(const QString &hash) |
355 | +{ |
356 | + return m_resources.value(hash); |
357 | +} |
358 | + |
359 | +void Note::addResource(const QString &hash, const QImage &image, const QString &type) |
360 | +{ |
361 | + m_resources.insert(hash, image); |
362 | + m_resourceTypes.insert(hash, type); |
363 | +} |
364 | + |
365 | Note *Note::clone() |
366 | { |
367 | Note *note = new Note(m_guid, m_created); |
368 | note->setNotebookGuid(m_notebookGuid); |
369 | note->setTitle(m_title); |
370 | - note->setContent(m_content); |
371 | + note->setEnmlContent(m_content.enml()); |
372 | note->setReminderOrder(m_reminderOrder); |
373 | note->setReminderTime(m_reminderTime); |
374 | note->setReminderDoneTime(m_reminderDoneTime); |
375 | |
376 | === modified file 'src/plugin/Evernote/note.h' |
377 | --- src/plugin/Evernote/note.h 2013-12-29 14:10:30 +0000 |
378 | +++ src/plugin/Evernote/note.h 2014-01-10 09:05:41 +0000 |
379 | @@ -21,9 +21,12 @@ |
380 | #ifndef NOTE_H |
381 | #define NOTE_H |
382 | |
383 | +#include "utils/enmldocument.h" |
384 | + |
385 | #include <QObject> |
386 | #include <QDateTime> |
387 | #include <QStringList> |
388 | +#include <QImage> |
389 | |
390 | class Note : public QObject |
391 | { |
392 | @@ -34,8 +37,10 @@ |
393 | Q_PROPERTY(QString notebookGuid READ notebookGuid WRITE setNotebookGuid NOTIFY notebookGuidChanged) |
394 | Q_PROPERTY(QDateTime created READ created CONSTANT) |
395 | Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) |
396 | - Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged) |
397 | + Q_PROPERTY(QString htmlContent READ htmlContent WRITE setHtmlContent NOTIFY contentChanged) |
398 | + Q_PROPERTY(QString enmlContent READ enmlContent WRITE setEnmlContent NOTIFY contentChanged) |
399 | Q_PROPERTY(QString plaintextContent READ plaintextContent NOTIFY contentChanged) |
400 | + Q_PROPERTY(QList<QString> resources READ resources NOTIFY contentChanged) |
401 | Q_PROPERTY(bool reminder READ reminder WRITE setReminder NOTIFY reminderChanged) |
402 | Q_PROPERTY(QDateTime reminderTime READ reminderTime WRITE setReminderTime NOTIFY reminderTimeChanged) |
403 | Q_PROPERTY(bool reminderDone READ reminderDone WRITE setReminderDone NOTIFY reminderDoneChanged) |
404 | @@ -56,11 +61,13 @@ |
405 | QString title() const; |
406 | void setTitle(const QString &title); |
407 | |
408 | - QString content() const; |
409 | - void setContent(const QString &content); |
410 | + QString enmlContent() const; |
411 | + void setEnmlContent(const QString &enmlContent); |
412 | + |
413 | + QString htmlContent() const; |
414 | + void setHtmlContent(const QString &htmlContent); |
415 | |
416 | QString plaintextContent() const; |
417 | - void setPlaintextContent(const QString &plaintextContent); |
418 | |
419 | // This is the QML representation as we don't want to deal with timestamps there. |
420 | // setting it to false will reset the reminderOrder to 0, setting it to true will |
421 | @@ -86,6 +93,10 @@ |
422 | bool isSearchResult() const; |
423 | void setIsSearchResult(bool isSearchResult); |
424 | |
425 | + QStringList resources() const; |
426 | + QImage resource(const QString &hash); |
427 | + void addResource(const QString &hash, const QImage &image, const QString &type); |
428 | + |
429 | Note* clone(); |
430 | |
431 | public slots: |
432 | @@ -106,12 +117,13 @@ |
433 | QString m_notebookGuid; |
434 | QDateTime m_created; |
435 | QString m_title; |
436 | - QString m_content; |
437 | - QString m_plaintextContent; |
438 | + EnmlDocument m_content; |
439 | qint64 m_reminderOrder; |
440 | QDateTime m_reminderTime; |
441 | QDateTime m_reminderDoneTime; |
442 | bool m_isSearchResult; |
443 | + QHash<QString, QImage> m_resources; |
444 | + QHash<QString, QString> m_resourceTypes; |
445 | }; |
446 | |
447 | #endif // NOTE_H |
448 | |
449 | === modified file 'src/plugin/Evernote/notesstore.cpp' |
450 | --- src/plugin/Evernote/notesstore.cpp 2013-12-13 23:59:53 +0000 |
451 | +++ src/plugin/Evernote/notesstore.cpp 2014-01-10 09:05:41 +0000 |
452 | @@ -23,7 +23,7 @@ |
453 | #include "notebooks.h" |
454 | #include "notebook.h" |
455 | #include "note.h" |
456 | -#include "utils/html2enmlconverter.h" |
457 | +#include "utils/enmldocument.h" |
458 | |
459 | #include "jobs/fetchnotesjob.h" |
460 | #include "jobs/fetchnotebooksjob.h" |
461 | @@ -34,6 +34,7 @@ |
462 | #include "jobs/createnotebookjob.h" |
463 | #include "jobs/expungenotebookjob.h" |
464 | |
465 | +#include <QImage> |
466 | #include <QDebug> |
467 | |
468 | NotesStore* NotesStore::s_instance = 0; |
469 | @@ -84,6 +85,14 @@ |
470 | return m_notes.at(index.row())->reminderDone(); |
471 | case RoleReminderDoneTime: |
472 | return m_notes.at(index.row())->reminderDoneTime(); |
473 | + case RoleEnmlContent: |
474 | + return m_notes.at(index.row())->enmlContent(); |
475 | + case RoleHtmlContent: |
476 | + return m_notes.at(index.row())->htmlContent(); |
477 | + case RolePlaintextContent: |
478 | + return m_notes.at(index.row())->plaintextContent(); |
479 | + case RoleResources: |
480 | + return m_notes.at(index.row())->resources(); |
481 | } |
482 | return QVariant(); |
483 | } |
484 | @@ -99,6 +108,10 @@ |
485 | roles.insert(RoleReminderTime, "reminderTime"); |
486 | roles.insert(RoleReminderDone, "reminderDone"); |
487 | roles.insert(RoleReminderDoneTime, "reminderDoneTime"); |
488 | + roles.insert(RoleEnmlContent, "enmlContent"); |
489 | + roles.insert(RoleHtmlContent, "htmlContent"); |
490 | + roles.insert(RolePlaintextContent, "plaintextContent"); |
491 | + roles.insert(RoleResources, "resources"); |
492 | return roles; |
493 | } |
494 | |
495 | @@ -209,7 +222,17 @@ |
496 | Note *note = m_notesHash.value(QString::fromStdString(result.guid)); |
497 | note->setNotebookGuid(QString::fromStdString(result.notebookGuid)); |
498 | note->setTitle(QString::fromStdString(result.title)); |
499 | - note->setContent(QString::fromStdString(result.content)); |
500 | + |
501 | + // Resources need to be set before the content because otherwise the image provider won't find them when the content is updated in the ui |
502 | + for (int i = 0; i < result.resources.size(); ++i) { |
503 | + evernote::edam::Resource resource = result.resources.at(i); |
504 | + if (QString::fromStdString(resource.mime).startsWith("image/")) { |
505 | + QImage image = QImage::fromData((const uchar*)resource.data.body.data(), resource.data.size); |
506 | + note->addResource(QString::fromStdString(resource.data.bodyHash), image, QString::fromStdString(resource.mime)); |
507 | + } |
508 | + } |
509 | + |
510 | + note->setEnmlContent(QString::fromStdString(result.content)); |
511 | note->setReminderOrder(result.attributes.reminderOrder); |
512 | QDateTime reminderDoneTime; |
513 | if (result.attributes.reminderDoneTime > 0) { |
514 | @@ -274,7 +297,7 @@ |
515 | Note *note = new Note(guid, created, this); |
516 | note->setNotebookGuid(QString::fromStdString(result.notebookGuid)); |
517 | note->setTitle(QString::fromStdString(result.title)); |
518 | - note->setContent(QString::fromStdString(result.content)); |
519 | + note->setEnmlContent(QString::fromStdString(result.content)); |
520 | |
521 | beginInsertRows(QModelIndex(), m_notes.count(), m_notes.count()); |
522 | m_notesHash.insert(note->guid(), note); |
523 | @@ -287,10 +310,6 @@ |
524 | void NotesStore::saveNote(const QString &guid) |
525 | { |
526 | Note *note = m_notesHash.value(guid); |
527 | - |
528 | - QString enml = Html2EnmlConverter::html2enml(note->content()); |
529 | - note->setContent(enml); |
530 | - |
531 | SaveNoteJob *job = new SaveNoteJob(note, this); |
532 | connect(job, &SaveNoteJob::jobDone, this, &NotesStore::saveNoteJobDone); |
533 | EvernoteConnection::instance()->enqueue(job); |
534 | |
535 | === modified file 'src/plugin/Evernote/notesstore.h' |
536 | --- src/plugin/Evernote/notesstore.h 2013-12-18 20:57:07 +0000 |
537 | +++ src/plugin/Evernote/notesstore.h 2014-01-10 09:05:41 +0000 |
538 | @@ -57,7 +57,11 @@ |
539 | RoleReminderTime, |
540 | RoleReminderDone, |
541 | RoleReminderDoneTime, |
542 | - RoleIsSearchResult |
543 | + RoleIsSearchResult, |
544 | + RoleEnmlContent, |
545 | + RoleHtmlContent, |
546 | + RolePlaintextContent, |
547 | + RoleResources |
548 | }; |
549 | |
550 | ~NotesStore(); |
551 | |
552 | === added file 'src/plugin/Evernote/resourceimageprovider.cpp' |
553 | --- src/plugin/Evernote/resourceimageprovider.cpp 1970-01-01 00:00:00 +0000 |
554 | +++ src/plugin/Evernote/resourceimageprovider.cpp 2014-01-10 09:05:41 +0000 |
555 | @@ -0,0 +1,42 @@ |
556 | +#include "resourceimageprovider.h" |
557 | + |
558 | +#include <notesstore.h> |
559 | +#include <note.h> |
560 | + |
561 | +#include <QUrlQuery> |
562 | +#include <QDebug> |
563 | + |
564 | +ResourceImageProvider::ResourceImageProvider(): |
565 | + QQuickImageProvider(QQuickImageProvider::Image) |
566 | +{ |
567 | + |
568 | +} |
569 | + |
570 | +QImage ResourceImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) |
571 | +{ |
572 | + QUrlQuery arguments(id.split('?').last()); |
573 | + QString noteGuid = arguments.queryItemValue("noteGuid"); |
574 | + QString resourceHash = arguments.queryItemValue("hash"); |
575 | + Note *note = NotesStore::instance()->note(noteGuid); |
576 | + if (!note) { |
577 | + qWarning() << "Unable to find note for resource:" << id; |
578 | + return QImage(); |
579 | + } |
580 | + |
581 | + QByteArray binHash = QByteArray::fromHex(resourceHash.toLatin1()); |
582 | + |
583 | + QImage image = NotesStore::instance()->note(noteGuid)->resource(binHash); |
584 | + *size = image.size(); |
585 | + |
586 | + if (requestedSize.isValid()) { |
587 | + if (requestedSize.height() > 0 && requestedSize.width() > 0) { |
588 | + image = image.scaled(requestedSize); |
589 | + } else if (requestedSize.height() > 0) { |
590 | + image = image.scaledToHeight(requestedSize.height()); |
591 | + } else { |
592 | + image = image.scaledToWidth(requestedSize.width()); |
593 | + } |
594 | + *size = requestedSize; |
595 | + } |
596 | + return image; |
597 | +} |
598 | |
599 | === added file 'src/plugin/Evernote/resourceimageprovider.h' |
600 | --- src/plugin/Evernote/resourceimageprovider.h 1970-01-01 00:00:00 +0000 |
601 | +++ src/plugin/Evernote/resourceimageprovider.h 2014-01-10 09:05:41 +0000 |
602 | @@ -0,0 +1,14 @@ |
603 | +#ifndef RESOURCEIMAGEPROVIDER_H |
604 | +#define RESOURCEIMAGEPROVIDER_H |
605 | + |
606 | +#include <QQuickImageProvider> |
607 | + |
608 | +class ResourceImageProvider : public QQuickImageProvider |
609 | +{ |
610 | +public: |
611 | + explicit ResourceImageProvider(); |
612 | + |
613 | + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); |
614 | +}; |
615 | + |
616 | +#endif // RESOURCEIMAGEPROVIDER_H |
617 | |
618 | === renamed file 'src/plugin/Evernote/utils/html2enmlconverter.cpp' => 'src/plugin/Evernote/utils/enmldocument.cpp' |
619 | --- src/plugin/Evernote/utils/html2enmlconverter.cpp 2013-12-29 14:10:30 +0000 |
620 | +++ src/plugin/Evernote/utils/enmldocument.cpp 2014-01-10 09:05:41 +0000 |
621 | @@ -18,32 +18,165 @@ |
622 | * Authors: Michael Zanetti <michael.zanetti@canonical.com> |
623 | */ |
624 | |
625 | -#include "html2enmlconverter.h" |
626 | +#include "enmldocument.h" |
627 | |
628 | #include <QXmlStreamReader> |
629 | #include <QXmlStreamWriter> |
630 | #include <QStringList> |
631 | - |
632 | -// Taken from http://xml.evernote.com/pub/enml2.dtd |
633 | -QStringList supportedTags = QStringList() << "a" << "abbr" << "acronym" << "address" << "area" << "b" << "bdo" << "big" << |
634 | - "blockquote" << "br" << "caption" << "center" << "cite" << "code" << "col" << |
635 | - "colgroup" << "dd" << "del" << "dfn" << "div" << "dl" << "dt" << "em" << |
636 | - "en-crypt" << "en-media" << "en-todo" << "font" << "h1" << "h2" << "h3" << |
637 | - "h4" << "h5" << "h6" << "hr" << "i" << "img" << "ins" << "kbd" << "li" << |
638 | - "map" << "ol" << "p" << "pre" << "q" << "s" << "samp" << "small" << "span" << |
639 | - "strike" << "strong" << "sub" << "sup" << "table" << "tbody" << "td" << |
640 | - "tfoot" << "th" << "thead" << "tr" << "tt" << "u" << "ul" << "var"; |
641 | - |
642 | - |
643 | -Html2EnmlConverter::Html2EnmlConverter() |
644 | -{ |
645 | -} |
646 | - |
647 | -QString Html2EnmlConverter::html2enml(const QString &html) |
648 | -{ |
649 | - // output |
650 | - QString evml; |
651 | - QXmlStreamWriter writer(&evml); |
652 | +#include <QUrl> |
653 | +#include <QUrlQuery> |
654 | +#include <QDebug> |
655 | + |
656 | +// ENML spec: http://xml.evernote.com/pub/enml2.dtd |
657 | +// QML supported HTML subset: http://qt-project.org/doc/qt-5.0/qtgui/richtext-html-subset.html |
658 | + |
659 | +// This is the list of common tags between enml and html. We can just copy those over as they are |
660 | +QStringList EnmlDocument::s_commonTags = QStringList() |
661 | + << "a" << "abbr" << "acronym" << "address" << "area" << "b" << "bdo" << "big" |
662 | + << "blockquote" << "br" << "caption" << "center" << "cite" << "code" << "col" |
663 | + << "colgroup" << "dd" << "del" << "dfn" << "div" << "dl" << "dt" << "em" |
664 | + << "en-crypt" << "en-todo" << "font" << "h1" << "h2" << "h3" << "h4" << "h5" |
665 | + << "h6" << "hr" << "i" << "ins" << "kbd" << "li" << "map" << "ol" |
666 | + << "p" << "pre" << "q" << "s" << "samp" << "small" << "span" << "strike" |
667 | + << "strong" << "sub" << "sup" << "table" << "tbody" << "td" << "tfoot" |
668 | + << "th" << "thead" << "tr" << "tt" << "u" << "ul" << "var"; |
669 | + |
670 | +// QML tends to generate more attributes than neccessary and Evernote's web editor gets confused by it. |
671 | +// Let's blacklist adding attributes to given tags. |
672 | +QStringList EnmlDocument::s_argumentBlackListTags = QStringList() |
673 | + << "ul" << "li" << "ol"; |
674 | + |
675 | +EnmlDocument::EnmlDocument(const QString &enml): |
676 | + m_enml(enml) |
677 | +{ |
678 | +} |
679 | + |
680 | +QString EnmlDocument::enml() const |
681 | +{ |
682 | + return m_enml; |
683 | +} |
684 | + |
685 | +void EnmlDocument::setEnml(const QString &enml) |
686 | +{ |
687 | + m_enml = enml; |
688 | +} |
689 | + |
690 | +QString EnmlDocument::html(const QString ¬eGuid) const |
691 | +{ |
692 | + // output |
693 | + QString html; |
694 | + QXmlStreamWriter writer(&html); |
695 | + writer.writeStartDocument(); |
696 | + |
697 | + // input |
698 | + QXmlStreamReader reader(m_enml); |
699 | + |
700 | + // state |
701 | + bool isBody = false; |
702 | + |
703 | + while (!reader.atEnd() && !reader.hasError()) { |
704 | + QXmlStreamReader::TokenType token = reader.readNext(); |
705 | + if(token == QXmlStreamReader::StartDocument) { |
706 | + continue; |
707 | + } |
708 | + |
709 | + // Handle start elements |
710 | + if(token == QXmlStreamReader::StartElement) { |
711 | + // skip everything if body hasn't started yet |
712 | + if (!isBody) { |
713 | + if (reader.name() == "en-note") { |
714 | + writer.writeStartElement("body"); |
715 | + isBody = true; |
716 | + } |
717 | + continue; |
718 | + } |
719 | + // Write supported start elements to output (including attributes) |
720 | + if (s_commonTags.contains(reader.name().toString())) { |
721 | + writer.writeStartElement(reader.name().toString()); |
722 | + |
723 | + writer.writeAttributes(reader.attributes()); |
724 | + |
725 | + // Fix paragraph alignment (text-align -> align) |
726 | + if (reader.name() == "p") { |
727 | + foreach (const QXmlStreamAttribute &attribute, reader.attributes()) { |
728 | + if (attribute.name() == "style" && attribute.value().contains("text-align")) { |
729 | + QString style = attribute.value().toString(); |
730 | + QString textAlign = style.split("text-align: ").at(1).split(';').first(); |
731 | + writer.writeAttribute("align", textAlign); |
732 | + break; |
733 | + } |
734 | + } |
735 | + } |
736 | + } |
737 | + |
738 | + // Convert images |
739 | + // TODO: what to do with music files etc? |
740 | + if (reader.name() == "en-media") { |
741 | + writer.writeStartElement("img"); |
742 | + QUrl url("image://resource/" + reader.attributes().value("type").toString()); |
743 | + QUrlQuery arguments; |
744 | + arguments.addQueryItem("noteGuid", noteGuid); |
745 | + arguments.addQueryItem("hash", reader.attributes().value("hash").toString()); |
746 | + url.setQuery(arguments); |
747 | + writer.writeAttribute("src", url.toString()); |
748 | + } |
749 | + |
750 | + // Convert todo checkboxes |
751 | + if (reader.name() == "en-todo") { |
752 | + bool checked = false; |
753 | + foreach(const QXmlStreamAttribute &attr, reader.attributes().toList()) { |
754 | + if (attr.name() == "checked" && attr.value() == "true") { |
755 | + checked = true; |
756 | + } |
757 | + } |
758 | + |
759 | + writer.writeStartElement("img"); |
760 | + writer.writeAttribute("src", checked ? "image://theme/select" : "image://theme/help"); |
761 | + } |
762 | + |
763 | + // We can't just copy over img tags with s_commonTags, because we generate img tags on our own. |
764 | + // Lets copy them manually |
765 | + if (reader.name() == "img") { |
766 | + writer.writeStartElement("img"); |
767 | + writer.writeAttributes(reader.attributes()); |
768 | + } |
769 | + |
770 | + } |
771 | + |
772 | + // Write *all* normal text inside <body> </body> to output |
773 | + if (isBody && token == QXmlStreamReader::Characters) { |
774 | + writer.writeCharacters(reader.text().toString()); |
775 | + } |
776 | + |
777 | + // handle end elements |
778 | + if (token == QXmlStreamReader::EndElement) { |
779 | + |
780 | + // skip everything after body |
781 | + if (reader.name() == "en-note") { |
782 | + writer.writeEndElement(); |
783 | + isBody = false; |
784 | + break; |
785 | + } |
786 | + |
787 | + // Write closing tags for supported elements |
788 | + if (s_commonTags.contains(reader.name().toString()) |
789 | + || reader.name() == "en-media" |
790 | + || reader.name() == "en-todo" |
791 | + || reader.name() == "img") { |
792 | + writer.writeEndElement(); |
793 | + } |
794 | + } |
795 | + } |
796 | + |
797 | + writer.writeEndDocument(); |
798 | + return html; |
799 | +} |
800 | + |
801 | +void EnmlDocument::setHtml(const QString &html) |
802 | +{ |
803 | + // output |
804 | + m_enml.clear(); |
805 | + QXmlStreamWriter writer(&m_enml); |
806 | writer.writeStartDocument(); |
807 | writer.writeDTD("<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">"); |
808 | |
809 | @@ -69,13 +202,38 @@ |
810 | } |
811 | continue; |
812 | } |
813 | + |
814 | // Write supported start elements to output (including attributes) |
815 | - if (supportedTags.contains(reader.name().toString())) { |
816 | + if (s_commonTags.contains(reader.name().toString())) { |
817 | writer.writeStartElement(reader.name().toString()); |
818 | - writer.writeAttributes(reader.attributes()); |
819 | + if (!s_argumentBlackListTags.contains(reader.name().toString())) { |
820 | + writer.writeAttributes(reader.attributes()); |
821 | + } |
822 | + |
823 | + } |
824 | + |
825 | + if (reader.name() == "img") { |
826 | + QUrl imageUrl = QUrl(reader.attributes().value("src").toString()); |
827 | + if (imageUrl.authority() == "resource") { |
828 | + QString type = imageUrl.path().remove(QRegExp("^/")); |
829 | + |
830 | + QUrlQuery arguments(imageUrl.query()); |
831 | + QString hash = arguments.queryItemValue("hash"); |
832 | + |
833 | + writer.writeStartElement("en-media"); |
834 | + writer.writeAttribute("hash", hash); |
835 | + writer.writeAttribute("type", type); |
836 | + } else if (imageUrl.authority() == "theme") { |
837 | + writer.writeStartElement("en-todo"); |
838 | + writer.writeAttribute("checked", imageUrl.path() == "/select" ? "true" : "false"); |
839 | + } else { |
840 | + writer.writeStartElement("img"); |
841 | + writer.writeAttributes(reader.attributes()); |
842 | + } |
843 | } |
844 | } |
845 | |
846 | + |
847 | // Write *all* normal text inside <body> </body> to output |
848 | if (isBody && token == QXmlStreamReader::Characters) { |
849 | writer.writeCharacters(reader.text().toString()); |
850 | @@ -92,24 +250,26 @@ |
851 | } |
852 | |
853 | // Write closing tags for supported elements |
854 | - if (supportedTags.contains(reader.name().toString())) { |
855 | + if (s_commonTags.contains(reader.name().toString())) { |
856 | + writer.writeEndElement(); |
857 | + } |
858 | + |
859 | + if (reader.name() == "img") { |
860 | writer.writeEndElement(); |
861 | } |
862 | } |
863 | } |
864 | |
865 | writer.writeEndDocument(); |
866 | - |
867 | - return evml; |
868 | } |
869 | |
870 | -QString Html2EnmlConverter::enml2plaintext(const QString &enml) |
871 | +QString EnmlDocument::plaintext() const |
872 | { |
873 | // output |
874 | QString plaintext; |
875 | |
876 | // input |
877 | - QXmlStreamReader reader(enml); |
878 | + QXmlStreamReader reader(m_enml); |
879 | |
880 | while (!reader.atEnd() && !reader.hasError()) { |
881 | QXmlStreamReader::TokenType token = reader.readNext(); |
882 | |
883 | === renamed file 'src/plugin/Evernote/utils/html2enmlconverter.h' => 'src/plugin/Evernote/utils/enmldocument.h' |
884 | --- src/plugin/Evernote/utils/html2enmlconverter.h 2013-12-29 14:10:30 +0000 |
885 | +++ src/plugin/Evernote/utils/enmldocument.h 2014-01-10 09:05:41 +0000 |
886 | @@ -18,19 +18,31 @@ |
887 | * Authors: Michael Zanetti <michael.zanetti@canonical.com> |
888 | */ |
889 | |
890 | -#ifndef HTML2ENMLCONVERTER_H |
891 | -#define HTML2ENMLCONVERTER_H |
892 | +#ifndef ENMLDOCUMENT_H |
893 | +#define ENMLDOCUMENT_H |
894 | |
895 | #include <QString> |
896 | |
897 | -class Html2EnmlConverter |
898 | +class EnmlDocument |
899 | { |
900 | public: |
901 | - Html2EnmlConverter(); |
902 | - |
903 | - static QString html2enml(const QString &html); |
904 | - |
905 | - static QString enml2plaintext(const QString &enml); |
906 | + EnmlDocument(const QString &enml = QString()); |
907 | + |
908 | + QString enml() const; |
909 | + void setEnml(const QString &enml); |
910 | + |
911 | + // noteGuid is required to convert en-media tags to urls for image provider |
912 | + QString html(const QString ¬eGuid) const; |
913 | + void setHtml(const QString &html); |
914 | + |
915 | + QString plaintext() const; |
916 | + |
917 | +private: |
918 | + QString m_enml; |
919 | + |
920 | + static QStringList s_commonTags; |
921 | + static QStringList s_argumentBlackListTags; |
922 | + |
923 | }; |
924 | |
925 | -#endif // HTML2ENMLCONVERTER_H |
926 | +#endif // ENMLDOCUMENT_H |
PASSED: Continuous integration, rev:28 91.189. 93.70:8080/ job/reminders- app-ci/ 41/ 91.189. 93.70:8080/ job/generic- mediumtests- trusty/ 451 91.189. 93.70:8080/ job/reminders- app-saucy- amd64-ci/ 41 91.189. 93.70:8080/ job/reminders- app-trusty- amd64-ci/ 41
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/ 41/rebuild
http://