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
=== modified file 'src/app/qml/components/NotesDelegate.qml'
--- src/app/qml/components/NotesDelegate.qml 2013-12-14 00:07:30 +0000
+++ src/app/qml/components/NotesDelegate.qml 2014-01-10 09:05:41 +0000
@@ -27,6 +27,7 @@
27 property string title27 property string title
28 property date creationDate28 property date creationDate
29 property string content29 property string content
30 property string resource
3031
31 Column {32 Column {
32 id: contentColumn33 id: contentColumn
@@ -36,7 +37,7 @@
36 topMargin: units.gu(1)37 topMargin: units.gu(1)
37 left: parent.left38 left: parent.left
38 leftMargin: units.gu(2)39 leftMargin: units.gu(2)
39 right: parent.right40 right: resourceImage.left
40 rightMargin: units.gu(2)41 rightMargin: units.gu(2)
41 }42 }
42 Label {43 Label {
@@ -53,4 +54,11 @@
53 maximumLineCount: 254 maximumLineCount: 2
54 }55 }
55 }56 }
57
58 Image {
59 id: resourceImage
60 anchors { top: parent.top; right: parent.right; bottom: parent.bottom }
61 source: root.resource
62 sourceSize.height: height
63 }
56}64}
5765
=== added directory 'src/app/qml/images'
=== added file 'src/app/qml/images/unchecked.svg'
--- src/app/qml/images/unchecked.svg 1970-01-01 00:00:00 +0000
+++ src/app/qml/images/unchecked.svg 2014-01-10 09:05:41 +0000
@@ -0,0 +1,46 @@
1<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
4<svg
5 xmlns:dc="http://purl.org/dc/elements/1.1/"
6 xmlns:cc="http://creativecommons.org/ns#"
7 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8 xmlns:svg="http://www.w3.org/2000/svg"
9 xmlns="http://www.w3.org/2000/svg"
10 version="1.1"
11 width="90"
12 height="90"
13 id="svg3140">
14 <defs
15 id="defs12" />
16 <metadata
17 id="metadata3145">
18 <rdf:RDF>
19 <cc:Work
20 rdf:about="">
21 <dc:format>image/svg+xml</dc:format>
22 <dc:type
23 rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
24 <dc:title></dc:title>
25 </cc:Work>
26 </rdf:RDF>
27 </metadata>
28 <rect
29 width="90"
30 height="90"
31 x="0"
32 y="-1.4648437e-05"
33 id="rect4198"
34 style="color:#000000;fill:none" />
35 <path
36 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"
37 id="path3759"
38 style="fill:#808080" />
39 <rect
40 width="90"
41 height="90"
42 x="0"
43 y="1.7382999e-05"
44 id="rect3045"
45 style="color:#000000;fill:none" />
46</svg>
047
=== modified file 'src/app/qml/ui/NotePage.qml'
--- src/app/qml/ui/NotePage.qml 2013-12-13 23:59:53 +0000
+++ src/app/qml/ui/NotePage.qml 2014-01-10 09:05:41 +0000
@@ -33,7 +33,7 @@
33 width: parent.width33 width: parent.width
34 text: "save"34 text: "save"
35 onClicked: {35 onClicked: {
36 note.content = noteTextArea.text36 note.htmlContent = noteTextArea.text
37 note.save();37 note.save();
38 }38 }
39 }39 }
@@ -44,7 +44,7 @@
44 height: parent.height - y44 height: parent.height - y
4545
46 textFormat: TextEdit.RichText46 textFormat: TextEdit.RichText
47 text: note.content47 text: note.htmlContent
48 }48 }
49 }49 }
50}50}
5151
=== modified file 'src/app/qml/ui/NotesPage.qml'
--- src/app/qml/ui/NotesPage.qml 2013-12-13 23:39:24 +0000
+++ src/app/qml/ui/NotesPage.qml 2014-01-10 09:05:41 +0000
@@ -70,7 +70,8 @@
70 delegate: NotesDelegate {70 delegate: NotesDelegate {
71 title: model.title71 title: model.title
72 creationDate: model.created72 creationDate: model.created
73 content: NotesStore.note(model.guid).plaintextContent73 content: model.plaintextContent
74 resource: model.resources.length > 0 ? model.resources[0] : ""
7475
75 onClicked: {76 onClicked: {
76 pageStack.push(Qt.resolvedUrl("NotePage.qml"), {note: NotesStore.note(guid)})77 pageStack.push(Qt.resolvedUrl("NotePage.qml"), {note: NotesStore.note(guid)})
7778
=== modified file 'src/app/qml/ui/SearchNotesPage.qml'
--- src/app/qml/ui/SearchNotesPage.qml 2013-12-13 23:39:24 +0000
+++ src/app/qml/ui/SearchNotesPage.qml 2014-01-10 09:05:41 +0000
@@ -68,7 +68,7 @@
68 delegate: NotesDelegate {68 delegate: NotesDelegate {
69 title: model.title69 title: model.title
70 creationDate: model.created70 creationDate: model.created
71 content: NotesStore.note(model.guid).plaintextContent71 content: model.plaintextContent
7272
73 onClicked: {73 onClicked: {
74 pageStack.push(Qt.resolvedUrl("NotePage.qml"), {note: NotesStore.note(guid)})74 pageStack.push(Qt.resolvedUrl("NotePage.qml"), {note: NotesStore.note(guid)})
7575
=== modified file 'src/plugin/Evernote/Evernote.pro'
--- src/plugin/Evernote/Evernote.pro 2013-12-12 22:41:35 +0000
+++ src/plugin/Evernote/Evernote.pro 2014-01-10 09:05:41 +0000
@@ -1,7 +1,7 @@
1TARGET=evernoteplugin1TARGET=evernoteplugin
2TEMPLATE=lib2TEMPLATE=lib
3CONFIG = qt plugin3CONFIG = qt plugin
4QT += qml gui xml4QT += qml gui xml quick
5QMAKE_CXXFLAGS += -std=c++0x -fPIC5QMAKE_CXXFLAGS += -std=c++0x -fPIC
66
7INCLUDEPATH += ../../../3rdParty/evernote-sdk-cpp/src/ ../../../3rdParty/libthrift7INCLUDEPATH += ../../../3rdParty/evernote-sdk-cpp/src/ ../../../3rdParty/libthrift
@@ -23,13 +23,14 @@
23 jobs/evernotejob.cpp \23 jobs/evernotejob.cpp \
24 jobs/savenotejob.cpp \24 jobs/savenotejob.cpp \
25 jobs/deletenotejob.cpp \25 jobs/deletenotejob.cpp \
26 utils/html2enmlconverter.cpp \
27 evernoteconnection.cpp \26 evernoteconnection.cpp \
28 jobs/userstorejob.cpp \27 jobs/userstorejob.cpp \
29 jobs/notesstorejob.cpp \28 jobs/notesstorejob.cpp \
30 jobs/fetchusernamejob.cpp \29 jobs/fetchusernamejob.cpp \
31 jobs/createnotebookjob.cpp \30 jobs/createnotebookjob.cpp \
32 jobs/expungenotebookjob.cpp31 jobs/expungenotebookjob.cpp \
32 resourceimageprovider.cpp \
33 utils/enmldocument.cpp
3334
34HEADERS += evernoteplugin.h \35HEADERS += evernoteplugin.h \
35 notesstore.h \36 notesstore.h \
@@ -45,13 +46,14 @@
45 jobs/evernotejob.h \46 jobs/evernotejob.h \
46 jobs/savenotejob.h \47 jobs/savenotejob.h \
47 jobs/deletenotejob.h \48 jobs/deletenotejob.h \
48 utils/html2enmlconverter.h \
49 evernoteconnection.h \49 evernoteconnection.h \
50 jobs/userstorejob.h \50 jobs/userstorejob.h \
51 jobs/notesstorejob.h \51 jobs/notesstorejob.h \
52 jobs/fetchusernamejob.h \52 jobs/fetchusernamejob.h \
53 jobs/createnotebookjob.h \53 jobs/createnotebookjob.h \
54 jobs/expungenotebookjob.h54 jobs/expungenotebookjob.h \
55 resourceimageprovider.h \
56 utils/enmldocument.h
5557
56message(building in $$OUT_PWD)58message(building in $$OUT_PWD)
57LIBS += -L$$OUT_PWD/../../../3rdParty/evernote-sdk-cpp/ -L$$OUT_PWD/../../../3rdParty/libthrift/ -levernote-sdk-cpp -llibthrift -lssl -lcrypto59LIBS += -L$$OUT_PWD/../../../3rdParty/evernote-sdk-cpp/ -L$$OUT_PWD/../../../3rdParty/libthrift/ -levernote-sdk-cpp -llibthrift -lssl -lcrypto
5860
=== modified file 'src/plugin/Evernote/evernoteplugin.cpp'
--- src/plugin/Evernote/evernoteplugin.cpp 2013-11-28 00:39:33 +0000
+++ src/plugin/Evernote/evernoteplugin.cpp 2014-01-10 09:05:41 +0000
@@ -26,6 +26,7 @@
26#include "notes.h"26#include "notes.h"
27#include "notebooks.h"27#include "notebooks.h"
28#include "note.h"28#include "note.h"
29#include "resourceimageprovider.h"
2930
30#include <QtQml>31#include <QtQml>
3132
@@ -44,7 +45,7 @@
44 return EvernoteConnection::instance();45 return EvernoteConnection::instance();
45}46}
4647
47void FitBitPlugin::registerTypes(const char *uri)48void EvernotePlugin::registerTypes(const char *uri)
48{49{
49 qmlRegisterSingletonType<UserStore>("Evernote", 0, 1, "UserStore", userStoreProvider);50 qmlRegisterSingletonType<UserStore>("Evernote", 0, 1, "UserStore", userStoreProvider);
50 qmlRegisterSingletonType<NotesStore>("Evernote", 0, 1, "NotesStore", notesStoreProvider);51 qmlRegisterSingletonType<NotesStore>("Evernote", 0, 1, "NotesStore", notesStoreProvider);
@@ -54,3 +55,8 @@
54 qmlRegisterType<Notebooks>("Evernote", 0, 1, "Notebooks");55 qmlRegisterType<Notebooks>("Evernote", 0, 1, "Notebooks");
55 qmlRegisterUncreatableType<Note>("Evernote", 0, 1, "Note", "Cannot create Notes in QML. Use NotesStore.createNote() instead.");56 qmlRegisterUncreatableType<Note>("Evernote", 0, 1, "Note", "Cannot create Notes in QML. Use NotesStore.createNote() instead.");
56}57}
58
59void EvernotePlugin::initializeEngine(QQmlEngine *engine, const char *uri)
60{
61 engine->addImageProvider("resource", new ResourceImageProvider);
62}
5763
=== modified file 'src/plugin/Evernote/evernoteplugin.h'
--- src/plugin/Evernote/evernoteplugin.h 2013-11-21 23:30:15 +0000
+++ src/plugin/Evernote/evernoteplugin.h 2014-01-10 09:05:41 +0000
@@ -23,12 +23,13 @@
2323
24#include <QQmlExtensionPlugin>24#include <QQmlExtensionPlugin>
2525
26class FitBitPlugin : public QQmlExtensionPlugin26class EvernotePlugin : public QQmlExtensionPlugin
27{27{
28 Q_OBJECT28 Q_OBJECT
29 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")29 Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
30public: 30public:
31 void registerTypes(const char *uri);31 void registerTypes(const char *uri);
32 void initializeEngine(QQmlEngine *engine, const char *uri);
32};33};
3334
34#endif // FITBITPLUGIN_H35#endif // FITBITPLUGIN_H
3536
=== modified file 'src/plugin/Evernote/jobs/evernotejob.cpp'
--- src/plugin/Evernote/jobs/evernotejob.cpp 2013-12-12 22:41:35 +0000
+++ src/plugin/Evernote/jobs/evernotejob.cpp 2014-01-10 09:05:41 +0000
@@ -83,7 +83,7 @@
8383
8484
85 } catch (const evernote::edam::EDAMUserException &e) {85 } catch (const evernote::edam::EDAMUserException &e) {
86 qWarning() << "EDAMUserException" << e.what();86 qWarning() << "EDAMUserException" << e.what() << e.errorCode;
87 emitJobDone(EvernoteConnection::ErrorCodeUserException, e.what());87 emitJobDone(EvernoteConnection::ErrorCodeUserException, e.what());
88 } catch (const evernote::edam::EDAMSystemException &e) {88 } catch (const evernote::edam::EDAMSystemException &e) {
89 qWarning() << "EDAMSystemException" << e.what();89 qWarning() << "EDAMSystemException" << e.what();
9090
=== modified file 'src/plugin/Evernote/jobs/fetchnotejob.cpp'
--- src/plugin/Evernote/jobs/fetchnotejob.cpp 2013-12-13 23:59:53 +0000
+++ src/plugin/Evernote/jobs/fetchnotejob.cpp 2014-01-10 09:05:41 +0000
@@ -28,7 +28,7 @@
2828
29void FetchNoteJob::startJob()29void FetchNoteJob::startJob()
30{30{
31 client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), true, false, false, false);31 client()->getNote(m_result, token().toStdString(), m_guid.toStdString(), true, true, false, false);
32}32}
3333
34void FetchNoteJob::emitJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage)34void FetchNoteJob::emitJobDone(EvernoteConnection::ErrorCode errorCode, const QString &errorMessage)
3535
=== modified file 'src/plugin/Evernote/jobs/savenotejob.cpp'
--- src/plugin/Evernote/jobs/savenotejob.cpp 2013-12-12 21:36:17 +0000
+++ src/plugin/Evernote/jobs/savenotejob.cpp 2014-01-10 09:05:41 +0000
@@ -42,9 +42,9 @@
42 note.__isset.title = true;42 note.__isset.title = true;
43 note.notebookGuid = m_note->notebookGuid().toStdString();43 note.notebookGuid = m_note->notebookGuid().toStdString();
44 note.__isset.notebookGuid = true;44 note.__isset.notebookGuid = true;
45 note.content = m_note->content().toStdString();45 note.content = m_note->enmlContent().toStdString();
46 note.__isset.content = true;46 note.__isset.content = true;
47 note.contentLength = m_note->content().length();47 note.contentLength = m_note->enmlContent().length();
4848
49 note.__isset.attributes = true;49 note.__isset.attributes = true;
50 note.attributes.reminderOrder = m_note->reminderOrder();50 note.attributes.reminderOrder = m_note->reminderOrder();
5151
=== modified file 'src/plugin/Evernote/note.cpp'
--- src/plugin/Evernote/note.cpp 2013-12-13 23:39:24 +0000
+++ src/plugin/Evernote/note.cpp 2014-01-10 09:05:41 +0000
@@ -21,9 +21,10 @@
21#include "note.h"21#include "note.h"
2222
23#include "notesstore.h"23#include "notesstore.h"
24#include "utils/html2enmlconverter.h"
2524
26#include <QDateTime>25#include <QDateTime>
26#include <QUrl>
27#include <QUrlQuery>
27#include <QDebug>28#include <QDebug>
2829
29Note::Note(const QString &guid, const QDateTime &created, QObject *parent) :30Note::Note(const QString &guid, const QDateTime &created, QObject *parent) :
@@ -70,24 +71,35 @@
70 }71 }
71}72}
7273
73QString Note::content() const74QString Note::enmlContent() const
74{75{
75 return m_content;76 return m_content.enml();
76}77}
7778
78void Note::setContent(const QString &content)79void Note::setEnmlContent(const QString &enmlContent)
79{80{
80 if (m_content != content) {81 if (m_content.enml() != enmlContent) {
81 m_content = content;82 m_content.setEnml(enmlContent);
82 m_plaintextContent = Html2EnmlConverter::enml2plaintext(content);83 emit contentChanged();
83 qDebug() << "plaintext content is" << m_plaintextContent;84 }
85}
86
87QString Note::htmlContent() const
88{
89 return m_content.html(m_guid);
90}
91
92void Note::setHtmlContent(const QString &htmlContent)
93{
94 if (m_content.html(m_guid) != htmlContent) {
95 m_content.setHtml(htmlContent);
84 emit contentChanged();96 emit contentChanged();
85 }97 }
86}98}
8799
88QString Note::plaintextContent() const100QString Note::plaintextContent() const
89{101{
90 return m_plaintextContent;102 return m_content.plaintext();
91}103}
92104
93bool Note::reminder() const105bool Note::reminder() const
@@ -171,12 +183,37 @@
171 }183 }
172}184}
173185
186QStringList Note::resources() const
187{
188 QList<QString> ret;
189 foreach (const QString &hash, m_resources.keys()) {
190 QUrl url("image://resource/" + m_resourceTypes.value(hash));
191 QUrlQuery arguments;
192 arguments.addQueryItem("noteGuid", m_guid);
193 arguments.addQueryItem("hash", hash.toLocal8Bit().toHex());
194 url.setQuery(arguments);
195 ret << url.toString();
196 }
197 return ret;
198}
199
200QImage Note::resource(const QString &hash)
201{
202 return m_resources.value(hash);
203}
204
205void Note::addResource(const QString &hash, const QImage &image, const QString &type)
206{
207 m_resources.insert(hash, image);
208 m_resourceTypes.insert(hash, type);
209}
210
174Note *Note::clone()211Note *Note::clone()
175{212{
176 Note *note = new Note(m_guid, m_created);213 Note *note = new Note(m_guid, m_created);
177 note->setNotebookGuid(m_notebookGuid);214 note->setNotebookGuid(m_notebookGuid);
178 note->setTitle(m_title);215 note->setTitle(m_title);
179 note->setContent(m_content);216 note->setEnmlContent(m_content.enml());
180 note->setReminderOrder(m_reminderOrder);217 note->setReminderOrder(m_reminderOrder);
181 note->setReminderTime(m_reminderTime);218 note->setReminderTime(m_reminderTime);
182 note->setReminderDoneTime(m_reminderDoneTime);219 note->setReminderDoneTime(m_reminderDoneTime);
183220
=== modified file 'src/plugin/Evernote/note.h'
--- src/plugin/Evernote/note.h 2013-12-29 14:10:30 +0000
+++ src/plugin/Evernote/note.h 2014-01-10 09:05:41 +0000
@@ -21,9 +21,12 @@
21#ifndef NOTE_H21#ifndef NOTE_H
22#define NOTE_H22#define NOTE_H
2323
24#include "utils/enmldocument.h"
25
24#include <QObject>26#include <QObject>
25#include <QDateTime>27#include <QDateTime>
26#include <QStringList>28#include <QStringList>
29#include <QImage>
2730
28class Note : public QObject31class Note : public QObject
29{32{
@@ -34,8 +37,10 @@
34 Q_PROPERTY(QString notebookGuid READ notebookGuid WRITE setNotebookGuid NOTIFY notebookGuidChanged)37 Q_PROPERTY(QString notebookGuid READ notebookGuid WRITE setNotebookGuid NOTIFY notebookGuidChanged)
35 Q_PROPERTY(QDateTime created READ created CONSTANT)38 Q_PROPERTY(QDateTime created READ created CONSTANT)
36 Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)39 Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
37 Q_PROPERTY(QString content READ content WRITE setContent NOTIFY contentChanged)40 Q_PROPERTY(QString htmlContent READ htmlContent WRITE setHtmlContent NOTIFY contentChanged)
41 Q_PROPERTY(QString enmlContent READ enmlContent WRITE setEnmlContent NOTIFY contentChanged)
38 Q_PROPERTY(QString plaintextContent READ plaintextContent NOTIFY contentChanged)42 Q_PROPERTY(QString plaintextContent READ plaintextContent NOTIFY contentChanged)
43 Q_PROPERTY(QList<QString> resources READ resources NOTIFY contentChanged)
39 Q_PROPERTY(bool reminder READ reminder WRITE setReminder NOTIFY reminderChanged)44 Q_PROPERTY(bool reminder READ reminder WRITE setReminder NOTIFY reminderChanged)
40 Q_PROPERTY(QDateTime reminderTime READ reminderTime WRITE setReminderTime NOTIFY reminderTimeChanged)45 Q_PROPERTY(QDateTime reminderTime READ reminderTime WRITE setReminderTime NOTIFY reminderTimeChanged)
41 Q_PROPERTY(bool reminderDone READ reminderDone WRITE setReminderDone NOTIFY reminderDoneChanged)46 Q_PROPERTY(bool reminderDone READ reminderDone WRITE setReminderDone NOTIFY reminderDoneChanged)
@@ -56,11 +61,13 @@
56 QString title() const;61 QString title() const;
57 void setTitle(const QString &title);62 void setTitle(const QString &title);
5863
59 QString content() const;64 QString enmlContent() const;
60 void setContent(const QString &content);65 void setEnmlContent(const QString &enmlContent);
66
67 QString htmlContent() const;
68 void setHtmlContent(const QString &htmlContent);
6169
62 QString plaintextContent() const;70 QString plaintextContent() const;
63 void setPlaintextContent(const QString &plaintextContent);
6471
65 // This is the QML representation as we don't want to deal with timestamps there.72 // This is the QML representation as we don't want to deal with timestamps there.
66 // setting it to false will reset the reminderOrder to 0, setting it to true will73 // setting it to false will reset the reminderOrder to 0, setting it to true will
@@ -86,6 +93,10 @@
86 bool isSearchResult() const;93 bool isSearchResult() const;
87 void setIsSearchResult(bool isSearchResult);94 void setIsSearchResult(bool isSearchResult);
8895
96 QStringList resources() const;
97 QImage resource(const QString &hash);
98 void addResource(const QString &hash, const QImage &image, const QString &type);
99
89 Note* clone();100 Note* clone();
90101
91public slots:102public slots:
@@ -106,12 +117,13 @@
106 QString m_notebookGuid;117 QString m_notebookGuid;
107 QDateTime m_created;118 QDateTime m_created;
108 QString m_title;119 QString m_title;
109 QString m_content;120 EnmlDocument m_content;
110 QString m_plaintextContent;
111 qint64 m_reminderOrder;121 qint64 m_reminderOrder;
112 QDateTime m_reminderTime;122 QDateTime m_reminderTime;
113 QDateTime m_reminderDoneTime;123 QDateTime m_reminderDoneTime;
114 bool m_isSearchResult;124 bool m_isSearchResult;
125 QHash<QString, QImage> m_resources;
126 QHash<QString, QString> m_resourceTypes;
115};127};
116128
117#endif // NOTE_H129#endif // NOTE_H
118130
=== modified file 'src/plugin/Evernote/notesstore.cpp'
--- src/plugin/Evernote/notesstore.cpp 2013-12-13 23:59:53 +0000
+++ src/plugin/Evernote/notesstore.cpp 2014-01-10 09:05:41 +0000
@@ -23,7 +23,7 @@
23#include "notebooks.h"23#include "notebooks.h"
24#include "notebook.h"24#include "notebook.h"
25#include "note.h"25#include "note.h"
26#include "utils/html2enmlconverter.h"26#include "utils/enmldocument.h"
2727
28#include "jobs/fetchnotesjob.h"28#include "jobs/fetchnotesjob.h"
29#include "jobs/fetchnotebooksjob.h"29#include "jobs/fetchnotebooksjob.h"
@@ -34,6 +34,7 @@
34#include "jobs/createnotebookjob.h"34#include "jobs/createnotebookjob.h"
35#include "jobs/expungenotebookjob.h"35#include "jobs/expungenotebookjob.h"
3636
37#include <QImage>
37#include <QDebug>38#include <QDebug>
3839
39NotesStore* NotesStore::s_instance = 0;40NotesStore* NotesStore::s_instance = 0;
@@ -84,6 +85,14 @@
84 return m_notes.at(index.row())->reminderDone();85 return m_notes.at(index.row())->reminderDone();
85 case RoleReminderDoneTime:86 case RoleReminderDoneTime:
86 return m_notes.at(index.row())->reminderDoneTime();87 return m_notes.at(index.row())->reminderDoneTime();
88 case RoleEnmlContent:
89 return m_notes.at(index.row())->enmlContent();
90 case RoleHtmlContent:
91 return m_notes.at(index.row())->htmlContent();
92 case RolePlaintextContent:
93 return m_notes.at(index.row())->plaintextContent();
94 case RoleResources:
95 return m_notes.at(index.row())->resources();
87 }96 }
88 return QVariant();97 return QVariant();
89}98}
@@ -99,6 +108,10 @@
99 roles.insert(RoleReminderTime, "reminderTime");108 roles.insert(RoleReminderTime, "reminderTime");
100 roles.insert(RoleReminderDone, "reminderDone");109 roles.insert(RoleReminderDone, "reminderDone");
101 roles.insert(RoleReminderDoneTime, "reminderDoneTime");110 roles.insert(RoleReminderDoneTime, "reminderDoneTime");
111 roles.insert(RoleEnmlContent, "enmlContent");
112 roles.insert(RoleHtmlContent, "htmlContent");
113 roles.insert(RolePlaintextContent, "plaintextContent");
114 roles.insert(RoleResources, "resources");
102 return roles;115 return roles;
103}116}
104117
@@ -209,7 +222,17 @@
209 Note *note = m_notesHash.value(QString::fromStdString(result.guid));222 Note *note = m_notesHash.value(QString::fromStdString(result.guid));
210 note->setNotebookGuid(QString::fromStdString(result.notebookGuid));223 note->setNotebookGuid(QString::fromStdString(result.notebookGuid));
211 note->setTitle(QString::fromStdString(result.title));224 note->setTitle(QString::fromStdString(result.title));
212 note->setContent(QString::fromStdString(result.content));225
226 // 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
227 for (int i = 0; i < result.resources.size(); ++i) {
228 evernote::edam::Resource resource = result.resources.at(i);
229 if (QString::fromStdString(resource.mime).startsWith("image/")) {
230 QImage image = QImage::fromData((const uchar*)resource.data.body.data(), resource.data.size);
231 note->addResource(QString::fromStdString(resource.data.bodyHash), image, QString::fromStdString(resource.mime));
232 }
233 }
234
235 note->setEnmlContent(QString::fromStdString(result.content));
213 note->setReminderOrder(result.attributes.reminderOrder);236 note->setReminderOrder(result.attributes.reminderOrder);
214 QDateTime reminderDoneTime;237 QDateTime reminderDoneTime;
215 if (result.attributes.reminderDoneTime > 0) {238 if (result.attributes.reminderDoneTime > 0) {
@@ -274,7 +297,7 @@
274 Note *note = new Note(guid, created, this);297 Note *note = new Note(guid, created, this);
275 note->setNotebookGuid(QString::fromStdString(result.notebookGuid));298 note->setNotebookGuid(QString::fromStdString(result.notebookGuid));
276 note->setTitle(QString::fromStdString(result.title));299 note->setTitle(QString::fromStdString(result.title));
277 note->setContent(QString::fromStdString(result.content));300 note->setEnmlContent(QString::fromStdString(result.content));
278301
279 beginInsertRows(QModelIndex(), m_notes.count(), m_notes.count());302 beginInsertRows(QModelIndex(), m_notes.count(), m_notes.count());
280 m_notesHash.insert(note->guid(), note);303 m_notesHash.insert(note->guid(), note);
@@ -287,10 +310,6 @@
287void NotesStore::saveNote(const QString &guid)310void NotesStore::saveNote(const QString &guid)
288{311{
289 Note *note = m_notesHash.value(guid);312 Note *note = m_notesHash.value(guid);
290
291 QString enml = Html2EnmlConverter::html2enml(note->content());
292 note->setContent(enml);
293
294 SaveNoteJob *job = new SaveNoteJob(note, this);313 SaveNoteJob *job = new SaveNoteJob(note, this);
295 connect(job, &SaveNoteJob::jobDone, this, &NotesStore::saveNoteJobDone);314 connect(job, &SaveNoteJob::jobDone, this, &NotesStore::saveNoteJobDone);
296 EvernoteConnection::instance()->enqueue(job);315 EvernoteConnection::instance()->enqueue(job);
297316
=== modified file 'src/plugin/Evernote/notesstore.h'
--- src/plugin/Evernote/notesstore.h 2013-12-18 20:57:07 +0000
+++ src/plugin/Evernote/notesstore.h 2014-01-10 09:05:41 +0000
@@ -57,7 +57,11 @@
57 RoleReminderTime,57 RoleReminderTime,
58 RoleReminderDone,58 RoleReminderDone,
59 RoleReminderDoneTime,59 RoleReminderDoneTime,
60 RoleIsSearchResult60 RoleIsSearchResult,
61 RoleEnmlContent,
62 RoleHtmlContent,
63 RolePlaintextContent,
64 RoleResources
61 };65 };
6266
63 ~NotesStore();67 ~NotesStore();
6468
=== added file 'src/plugin/Evernote/resourceimageprovider.cpp'
--- src/plugin/Evernote/resourceimageprovider.cpp 1970-01-01 00:00:00 +0000
+++ src/plugin/Evernote/resourceimageprovider.cpp 2014-01-10 09:05:41 +0000
@@ -0,0 +1,42 @@
1#include "resourceimageprovider.h"
2
3#include <notesstore.h>
4#include <note.h>
5
6#include <QUrlQuery>
7#include <QDebug>
8
9ResourceImageProvider::ResourceImageProvider():
10 QQuickImageProvider(QQuickImageProvider::Image)
11{
12
13}
14
15QImage ResourceImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
16{
17 QUrlQuery arguments(id.split('?').last());
18 QString noteGuid = arguments.queryItemValue("noteGuid");
19 QString resourceHash = arguments.queryItemValue("hash");
20 Note *note = NotesStore::instance()->note(noteGuid);
21 if (!note) {
22 qWarning() << "Unable to find note for resource:" << id;
23 return QImage();
24 }
25
26 QByteArray binHash = QByteArray::fromHex(resourceHash.toLatin1());
27
28 QImage image = NotesStore::instance()->note(noteGuid)->resource(binHash);
29 *size = image.size();
30
31 if (requestedSize.isValid()) {
32 if (requestedSize.height() > 0 && requestedSize.width() > 0) {
33 image = image.scaled(requestedSize);
34 } else if (requestedSize.height() > 0) {
35 image = image.scaledToHeight(requestedSize.height());
36 } else {
37 image = image.scaledToWidth(requestedSize.width());
38 }
39 *size = requestedSize;
40 }
41 return image;
42}
043
=== added file 'src/plugin/Evernote/resourceimageprovider.h'
--- src/plugin/Evernote/resourceimageprovider.h 1970-01-01 00:00:00 +0000
+++ src/plugin/Evernote/resourceimageprovider.h 2014-01-10 09:05:41 +0000
@@ -0,0 +1,14 @@
1#ifndef RESOURCEIMAGEPROVIDER_H
2#define RESOURCEIMAGEPROVIDER_H
3
4#include <QQuickImageProvider>
5
6class ResourceImageProvider : public QQuickImageProvider
7{
8public:
9 explicit ResourceImageProvider();
10
11 QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize);
12};
13
14#endif // RESOURCEIMAGEPROVIDER_H
015
=== renamed file 'src/plugin/Evernote/utils/html2enmlconverter.cpp' => 'src/plugin/Evernote/utils/enmldocument.cpp'
--- src/plugin/Evernote/utils/html2enmlconverter.cpp 2013-12-29 14:10:30 +0000
+++ src/plugin/Evernote/utils/enmldocument.cpp 2014-01-10 09:05:41 +0000
@@ -18,32 +18,165 @@
18 * Authors: Michael Zanetti <michael.zanetti@canonical.com>18 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
19 */19 */
2020
21#include "html2enmlconverter.h"21#include "enmldocument.h"
2222
23#include <QXmlStreamReader>23#include <QXmlStreamReader>
24#include <QXmlStreamWriter>24#include <QXmlStreamWriter>
25#include <QStringList>25#include <QStringList>
2626#include <QUrl>
27// Taken from http://xml.evernote.com/pub/enml2.dtd27#include <QUrlQuery>
28QStringList supportedTags = QStringList() << "a" << "abbr" << "acronym" << "address" << "area" << "b" << "bdo" << "big" <<28#include <QDebug>
29 "blockquote" << "br" << "caption" << "center" << "cite" << "code" << "col" <<29
30 "colgroup" << "dd" << "del" << "dfn" << "div" << "dl" << "dt" << "em" <<30// ENML spec: http://xml.evernote.com/pub/enml2.dtd
31 "en-crypt" << "en-media" << "en-todo" << "font" << "h1" << "h2" << "h3" <<31// QML supported HTML subset: http://qt-project.org/doc/qt-5.0/qtgui/richtext-html-subset.html
32 "h4" << "h5" << "h6" << "hr" << "i" << "img" << "ins" << "kbd" << "li" <<32
33 "map" << "ol" << "p" << "pre" << "q" << "s" << "samp" << "small" << "span" <<33// This is the list of common tags between enml and html. We can just copy those over as they are
34 "strike" << "strong" << "sub" << "sup" << "table" << "tbody" << "td" <<34QStringList EnmlDocument::s_commonTags = QStringList()
35 "tfoot" << "th" << "thead" << "tr" << "tt" << "u" << "ul" << "var";35 << "a" << "abbr" << "acronym" << "address" << "area" << "b" << "bdo" << "big"
3636 << "blockquote" << "br" << "caption" << "center" << "cite" << "code" << "col"
3737 << "colgroup" << "dd" << "del" << "dfn" << "div" << "dl" << "dt" << "em"
38Html2EnmlConverter::Html2EnmlConverter()38 << "en-crypt" << "en-todo" << "font" << "h1" << "h2" << "h3" << "h4" << "h5"
39{39 << "h6" << "hr" << "i" << "ins" << "kbd" << "li" << "map" << "ol"
40}40 << "p" << "pre" << "q" << "s" << "samp" << "small" << "span" << "strike"
4141 << "strong" << "sub" << "sup" << "table" << "tbody" << "td" << "tfoot"
42QString Html2EnmlConverter::html2enml(const QString &html)42 << "th" << "thead" << "tr" << "tt" << "u" << "ul" << "var";
43{43
44 // output44// QML tends to generate more attributes than neccessary and Evernote's web editor gets confused by it.
45 QString evml;45// Let's blacklist adding attributes to given tags.
46 QXmlStreamWriter writer(&evml);46QStringList EnmlDocument::s_argumentBlackListTags = QStringList()
47 << "ul" << "li" << "ol";
48
49EnmlDocument::EnmlDocument(const QString &enml):
50 m_enml(enml)
51{
52}
53
54QString EnmlDocument::enml() const
55{
56 return m_enml;
57}
58
59void EnmlDocument::setEnml(const QString &enml)
60{
61 m_enml = enml;
62}
63
64QString EnmlDocument::html(const QString &noteGuid) const
65{
66 // output
67 QString html;
68 QXmlStreamWriter writer(&html);
69 writer.writeStartDocument();
70
71 // input
72 QXmlStreamReader reader(m_enml);
73
74 // state
75 bool isBody = false;
76
77 while (!reader.atEnd() && !reader.hasError()) {
78 QXmlStreamReader::TokenType token = reader.readNext();
79 if(token == QXmlStreamReader::StartDocument) {
80 continue;
81 }
82
83 // Handle start elements
84 if(token == QXmlStreamReader::StartElement) {
85 // skip everything if body hasn't started yet
86 if (!isBody) {
87 if (reader.name() == "en-note") {
88 writer.writeStartElement("body");
89 isBody = true;
90 }
91 continue;
92 }
93 // Write supported start elements to output (including attributes)
94 if (s_commonTags.contains(reader.name().toString())) {
95 writer.writeStartElement(reader.name().toString());
96
97 writer.writeAttributes(reader.attributes());
98
99 // Fix paragraph alignment (text-align -> align)
100 if (reader.name() == "p") {
101 foreach (const QXmlStreamAttribute &attribute, reader.attributes()) {
102 if (attribute.name() == "style" && attribute.value().contains("text-align")) {
103 QString style = attribute.value().toString();
104 QString textAlign = style.split("text-align: ").at(1).split(';').first();
105 writer.writeAttribute("align", textAlign);
106 break;
107 }
108 }
109 }
110 }
111
112 // Convert images
113 // TODO: what to do with music files etc?
114 if (reader.name() == "en-media") {
115 writer.writeStartElement("img");
116 QUrl url("image://resource/" + reader.attributes().value("type").toString());
117 QUrlQuery arguments;
118 arguments.addQueryItem("noteGuid", noteGuid);
119 arguments.addQueryItem("hash", reader.attributes().value("hash").toString());
120 url.setQuery(arguments);
121 writer.writeAttribute("src", url.toString());
122 }
123
124 // Convert todo checkboxes
125 if (reader.name() == "en-todo") {
126 bool checked = false;
127 foreach(const QXmlStreamAttribute &attr, reader.attributes().toList()) {
128 if (attr.name() == "checked" && attr.value() == "true") {
129 checked = true;
130 }
131 }
132
133 writer.writeStartElement("img");
134 writer.writeAttribute("src", checked ? "image://theme/select" : "image://theme/help");
135 }
136
137 // We can't just copy over img tags with s_commonTags, because we generate img tags on our own.
138 // Lets copy them manually
139 if (reader.name() == "img") {
140 writer.writeStartElement("img");
141 writer.writeAttributes(reader.attributes());
142 }
143
144 }
145
146 // Write *all* normal text inside <body> </body> to output
147 if (isBody && token == QXmlStreamReader::Characters) {
148 writer.writeCharacters(reader.text().toString());
149 }
150
151 // handle end elements
152 if (token == QXmlStreamReader::EndElement) {
153
154 // skip everything after body
155 if (reader.name() == "en-note") {
156 writer.writeEndElement();
157 isBody = false;
158 break;
159 }
160
161 // Write closing tags for supported elements
162 if (s_commonTags.contains(reader.name().toString())
163 || reader.name() == "en-media"
164 || reader.name() == "en-todo"
165 || reader.name() == "img") {
166 writer.writeEndElement();
167 }
168 }
169 }
170
171 writer.writeEndDocument();
172 return html;
173}
174
175void EnmlDocument::setHtml(const QString &html)
176{
177 // output
178 m_enml.clear();
179 QXmlStreamWriter writer(&m_enml);
47 writer.writeStartDocument();180 writer.writeStartDocument();
48 writer.writeDTD("<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">");181 writer.writeDTD("<!DOCTYPE en-note SYSTEM \"http://xml.evernote.com/pub/enml2.dtd\">");
49182
@@ -69,13 +202,38 @@
69 }202 }
70 continue;203 continue;
71 }204 }
205
72 // Write supported start elements to output (including attributes)206 // Write supported start elements to output (including attributes)
73 if (supportedTags.contains(reader.name().toString())) {207 if (s_commonTags.contains(reader.name().toString())) {
74 writer.writeStartElement(reader.name().toString());208 writer.writeStartElement(reader.name().toString());
75 writer.writeAttributes(reader.attributes());209 if (!s_argumentBlackListTags.contains(reader.name().toString())) {
210 writer.writeAttributes(reader.attributes());
211 }
212
213 }
214
215 if (reader.name() == "img") {
216 QUrl imageUrl = QUrl(reader.attributes().value("src").toString());
217 if (imageUrl.authority() == "resource") {
218 QString type = imageUrl.path().remove(QRegExp("^/"));
219
220 QUrlQuery arguments(imageUrl.query());
221 QString hash = arguments.queryItemValue("hash");
222
223 writer.writeStartElement("en-media");
224 writer.writeAttribute("hash", hash);
225 writer.writeAttribute("type", type);
226 } else if (imageUrl.authority() == "theme") {
227 writer.writeStartElement("en-todo");
228 writer.writeAttribute("checked", imageUrl.path() == "/select" ? "true" : "false");
229 } else {
230 writer.writeStartElement("img");
231 writer.writeAttributes(reader.attributes());
232 }
76 }233 }
77 }234 }
78235
236
79 // Write *all* normal text inside <body> </body> to output237 // Write *all* normal text inside <body> </body> to output
80 if (isBody && token == QXmlStreamReader::Characters) {238 if (isBody && token == QXmlStreamReader::Characters) {
81 writer.writeCharacters(reader.text().toString());239 writer.writeCharacters(reader.text().toString());
@@ -92,24 +250,26 @@
92 }250 }
93251
94 // Write closing tags for supported elements252 // Write closing tags for supported elements
95 if (supportedTags.contains(reader.name().toString())) {253 if (s_commonTags.contains(reader.name().toString())) {
254 writer.writeEndElement();
255 }
256
257 if (reader.name() == "img") {
96 writer.writeEndElement();258 writer.writeEndElement();
97 }259 }
98 }260 }
99 }261 }
100262
101 writer.writeEndDocument();263 writer.writeEndDocument();
102
103 return evml;
104}264}
105265
106QString Html2EnmlConverter::enml2plaintext(const QString &enml)266QString EnmlDocument::plaintext() const
107{267{
108 // output268 // output
109 QString plaintext;269 QString plaintext;
110270
111 // input271 // input
112 QXmlStreamReader reader(enml);272 QXmlStreamReader reader(m_enml);
113273
114 while (!reader.atEnd() && !reader.hasError()) {274 while (!reader.atEnd() && !reader.hasError()) {
115 QXmlStreamReader::TokenType token = reader.readNext();275 QXmlStreamReader::TokenType token = reader.readNext();
116276
=== renamed file 'src/plugin/Evernote/utils/html2enmlconverter.h' => 'src/plugin/Evernote/utils/enmldocument.h'
--- src/plugin/Evernote/utils/html2enmlconverter.h 2013-12-29 14:10:30 +0000
+++ src/plugin/Evernote/utils/enmldocument.h 2014-01-10 09:05:41 +0000
@@ -18,19 +18,31 @@
18 * Authors: Michael Zanetti <michael.zanetti@canonical.com>18 * Authors: Michael Zanetti <michael.zanetti@canonical.com>
19 */19 */
2020
21#ifndef HTML2ENMLCONVERTER_H21#ifndef ENMLDOCUMENT_H
22#define HTML2ENMLCONVERTER_H22#define ENMLDOCUMENT_H
2323
24#include <QString>24#include <QString>
2525
26class Html2EnmlConverter26class EnmlDocument
27{27{
28public:28public:
29 Html2EnmlConverter();29 EnmlDocument(const QString &enml = QString());
3030
31 static QString html2enml(const QString &html);31 QString enml() const;
3232 void setEnml(const QString &enml);
33 static QString enml2plaintext(const QString &enml);33
34 // noteGuid is required to convert en-media tags to urls for image provider
35 QString html(const QString &noteGuid) const;
36 void setHtml(const QString &html);
37
38 QString plaintext() const;
39
40private:
41 QString m_enml;
42
43 static QStringList s_commonTags;
44 static QStringList s_argumentBlackListTags;
45
34};46};
3547
36#endif // HTML2ENMLCONVERTER_H48#endif // ENMLDOCUMENT_H

Subscribers

People subscribed via source and target branches