Merge lp:~mzanetti/reminders-app/add-image-support into lp:reminders-app

Proposed by Michael Zanetti
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
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://i.imgur.com/yeSEhiH.png

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.

To post a comment you must log in.
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Alan Pope 🍺🐧🐱 πŸ¦„ (popey) wrote :
Revision history for this message
Alan Pope 🍺🐧🐱 πŸ¦„ (popey) :
review: Needs Fixing
Revision history for this message
Michael Zanetti (mzanetti) wrote :

> I get no image on device.
>
> http://imgur.com/Ku8Z5GE vs
> http://popey.com/~alan/phablet/device-2013-12-29-140258.png

Just tried again. Works fine here. Can you provide more details please? Is there any debug output hinting to an error or the like?

Revision history for this message
David Planella (dpm) wrote :
Revision history for this message
David Planella (dpm) :
review: Approve
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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://91.189.93.70:8080/job/reminders-app-trusty-amd64-autolanding/18/console

Revision history for this message
David Planella (dpm) wrote :

Ah, trying to merge manually shows the actual conflicts:

RM src/plugin/Evernote/utils/html2enmlconverter.cpp => src/plugin/Evernote/utils/enmldocument.cpp
RM src/plugin/Evernote/utils/html2enmlconverter.h => src/plugin/Evernote/utils/enmldocument.h
Text conflict in src/plugin/Evernote/utils/enmldocument.cpp
Text conflict in src/plugin/Evernote/utils/enmldocument.h
2 conflicts encountered.

review: Needs Fixing
29. By Michael Zanetti

merge trunk

Revision history for this message
Michael Zanetti (mzanetti) wrote :

conflicts resolved

Revision history for this message
David Planella (dpm) wrote :

Works well after fixing the conflicts!

review: Approve
Revision history for this message
Ubuntu Phone Apps Jenkins Bot (ubuntu-phone-apps-jenkins-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 &noteGuid) 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 &noteGuid) 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

Subscribers

People subscribed via source and target branches