summaryrefslogtreecommitdiffstats
path: root/framework
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2017-01-01 13:37:19 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2017-01-02 00:31:41 +0100
commitba7128b30850594c7efb258d1794e377eede364a (patch)
treef805d511f6d12b0799c05b414054db8b8635bfc9 /framework
parent431c257dd29e2e3d8878db200f0de4d452bffe92 (diff)
downloadkube-ba7128b30850594c7efb258d1794e377eede364a.tar.gz
kube-ba7128b30850594c7efb258d1794e377eede364a.zip
Instead of using the action system we use controllers only.
It's simpler, and the action system was just too complex to use in a typesafe way.
Diffstat (limited to 'framework')
-rw-r--r--framework/actions/CMakeLists.txt4
-rw-r--r--framework/actions/actionbroker.cpp5
-rw-r--r--framework/actions/actionbroker.h1
-rw-r--r--framework/actions/actionhandler.cpp17
-rw-r--r--framework/actions/actionhandler.h55
-rw-r--r--framework/actions/context.cpp34
-rw-r--r--framework/actions/context.h27
-rw-r--r--framework/actions/tests/CMakeLists.txt6
-rw-r--r--framework/actions/tests/actiontest.cpp102
-rw-r--r--framework/domain/CMakeLists.txt3
-rw-r--r--framework/domain/actions/sinkactions.cpp74
-rw-r--r--framework/domain/completer.cpp26
-rw-r--r--framework/domain/completer.h40
-rw-r--r--framework/domain/composercontroller.cpp268
-rw-r--r--framework/domain/composercontroller.h118
-rw-r--r--framework/domain/controller.cpp55
-rw-r--r--framework/domain/controller.h75
-rw-r--r--framework/domain/selector.cpp26
-rw-r--r--framework/domain/selector.h50
19 files changed, 727 insertions, 259 deletions
diff --git a/framework/actions/CMakeLists.txt b/framework/actions/CMakeLists.txt
index 9cf0acd1..9fc43b9b 100644
--- a/framework/actions/CMakeLists.txt
+++ b/framework/actions/CMakeLists.txt
@@ -9,8 +9,10 @@ set(SRCS
9 9
10add_library(actionplugin SHARED ${SRCS}) 10add_library(actionplugin SHARED ${SRCS})
11 11
12target_link_libraries(actionplugin KF5::Async) 12target_link_libraries(actionplugin KF5::Async sink)
13qt5_use_modules(actionplugin Core Quick Qml) 13qt5_use_modules(actionplugin Core Quick Qml)
14 14
15install(TARGETS actionplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) 15install(TARGETS actionplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions)
16install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) 16install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions)
17
18add_subdirectory(tests)
diff --git a/framework/actions/actionbroker.cpp b/framework/actions/actionbroker.cpp
index 17145440..f6bfdd8e 100644
--- a/framework/actions/actionbroker.cpp
+++ b/framework/actions/actionbroker.cpp
@@ -94,3 +94,8 @@ void ActionBroker::registerHandler(const QByteArray &actionId, ActionHandler *ha
94{ 94{
95 mHandler.insert(actionId, handler); 95 mHandler.insert(actionId, handler);
96} 96}
97
98void ActionBroker::unregisterHandler(const QByteArray &actionId, ActionHandler *handler)
99{
100 mHandler.remove(actionId, handler);
101}
diff --git a/framework/actions/actionbroker.h b/framework/actions/actionbroker.h
index 84678c16..d893a3e7 100644
--- a/framework/actions/actionbroker.h
+++ b/framework/actions/actionbroker.h
@@ -36,6 +36,7 @@ public:
36 ActionResult executeAction(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler, const QList<QPointer<ActionHandler>> &postHandler); 36 ActionResult executeAction(const QByteArray &actionId, Context *context, const QList<QPointer<ActionHandler>> &preHandler, const QList<QPointer<ActionHandler>> &postHandler);
37 37
38 void registerHandler(const QByteArray &actionId, ActionHandler *handler); 38 void registerHandler(const QByteArray &actionId, ActionHandler *handler);
39 void unregisterHandler(const QByteArray &actionId, ActionHandler *handler);
39 40
40Q_SIGNALS: 41Q_SIGNALS:
41 void readyChanged(); 42 void readyChanged();
diff --git a/framework/actions/actionhandler.cpp b/framework/actions/actionhandler.cpp
index dc9edeca..eb7b3224 100644
--- a/framework/actions/actionhandler.cpp
+++ b/framework/actions/actionhandler.cpp
@@ -31,6 +31,11 @@ ActionHandler::ActionHandler(QObject *parent)
31 31
32} 32}
33 33
34ActionHandler::~ActionHandler()
35{
36 ActionBroker::instance().unregisterHandler(mActionId, this);
37}
38
34bool ActionHandler::isActionReady(Context *context) 39bool ActionHandler::isActionReady(Context *context)
35{ 40{
36 if (context) { 41 if (context) {
@@ -67,6 +72,8 @@ ActionResult ActionHandler::execute(Context *context)
67 72
68void ActionHandler::setActionId(const QByteArray &actionId) 73void ActionHandler::setActionId(const QByteArray &actionId)
69{ 74{
75 //Reassigning the id is not supported
76 Q_ASSERT(mActionId.isEmpty());
70 mActionId = actionId; 77 mActionId = actionId;
71 ActionBroker::instance().registerHandler(actionId, this); 78 ActionBroker::instance().registerHandler(actionId, this);
72} 79}
@@ -76,6 +83,16 @@ QByteArray ActionHandler::actionId() const
76 return mActionId; 83 return mActionId;
77} 84}
78 85
86void ActionHandler::setRequiredProperties(const QSet<QByteArray> &requiredProperties)
87{
88 mRequiredProperties = requiredProperties;
89}
90
91QSet<QByteArray> ActionHandler::requiredProperties() const
92{
93 return mRequiredProperties;
94}
95
79 96
80ActionHandlerHelper::ActionHandlerHelper(const Handler &handler) 97ActionHandlerHelper::ActionHandlerHelper(const Handler &handler)
81 : ActionHandler(nullptr), 98 : ActionHandler(nullptr),
diff --git a/framework/actions/actionhandler.h b/framework/actions/actionhandler.h
index 09ed13c6..5ccf0ac7 100644
--- a/framework/actions/actionhandler.h
+++ b/framework/actions/actionhandler.h
@@ -24,9 +24,9 @@
24#include <Async/Async> 24#include <Async/Async>
25 25
26#include "actionresult.h" 26#include "actionresult.h"
27#include "context.h"
27 28
28namespace Kube { 29namespace Kube {
29class Context;
30 30
31class ActionHandler : public QObject 31class ActionHandler : public QObject
32{ 32{
@@ -35,6 +35,7 @@ class ActionHandler : public QObject
35 35
36public: 36public:
37 ActionHandler(QObject *parent = 0); 37 ActionHandler(QObject *parent = 0);
38 virtual ~ActionHandler();
38 39
39 virtual bool isActionReady(Context *context); 40 virtual bool isActionReady(Context *context);
40 41
@@ -43,25 +44,65 @@ public:
43 void setActionId(const QByteArray &); 44 void setActionId(const QByteArray &);
44 QByteArray actionId() const; 45 QByteArray actionId() const;
45 46
47 void setRequiredProperties(const QSet<QByteArray> &requiredProperties);
48 QSet<QByteArray> requiredProperties() const;
49
46private: 50private:
47 QByteArray mActionId; 51 QByteArray mActionId;
52 QSet<QByteArray> mRequiredProperties;
53};
54
55template <typename ContextType>
56class ActionHandlerBase : public ActionHandler
57{
58public:
59 ActionHandlerBase(const QByteArray &actionId)
60 : ActionHandler{}
61 {
62 setActionId(actionId);
63 }
64
65 bool isActionReady(Context *c) Q_DECL_OVERRIDE
66 {
67 auto wrapper = ContextType{*c};
68 return isActionReady(wrapper);
69 }
70
71 ActionResult execute(Context *c) Q_DECL_OVERRIDE
72 {
73 ActionResult result;
74 auto wrapper = ContextType{*c};
75 execute(wrapper)
76 .template syncThen<void>([=](const KAsync::Error &error) {
77 auto modifyableResult = result;
78 if (error) {
79 qWarning() << "Job failed: " << error.errorCode << error.errorMessage;
80 modifyableResult.setError(1);
81 }
82 modifyableResult.setDone();
83 }).exec();
84 return result;
85 }
86protected:
87
88 virtual bool isActionReady(ContextType &) { return true; }
89 virtual KAsync::Job<void> execute(ContextType &) = 0;
48}; 90};
49 91
50class ActionHandlerHelper : public ActionHandler 92class ActionHandlerHelper : public ActionHandler
51{ 93{
52 Q_OBJECT
53public: 94public:
54 typedef std::function<bool(Context*)> IsReadyFunction; 95 typedef std::function<bool(Context *)> IsReadyFunction;
55 typedef std::function<void(Context*)> Handler; 96 typedef std::function<void(Context *)> Handler;
56 typedef std::function<KAsync::Job<void>(Context*)> JobHandler; 97 typedef std::function<KAsync::Job<void>(Context *)> JobHandler;
57 98
58 ActionHandlerHelper(const Handler &); 99 ActionHandlerHelper(const Handler &);
59 ActionHandlerHelper(const IsReadyFunction &, const Handler &); 100 ActionHandlerHelper(const IsReadyFunction &, const Handler &);
60 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const Handler &); 101 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const Handler &);
61 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const JobHandler &); 102 ActionHandlerHelper(const QByteArray &actionId, const IsReadyFunction &, const JobHandler &);
62 103
63 bool isActionReady(Context *context) Q_DECL_OVERRIDE; 104 bool isActionReady(Context *) Q_DECL_OVERRIDE;
64 ActionResult execute(Context *context) Q_DECL_OVERRIDE; 105 ActionResult execute(Context *) Q_DECL_OVERRIDE;
65private: 106private:
66 const IsReadyFunction isReadyFunction; 107 const IsReadyFunction isReadyFunction;
67 const Handler handlerFunction; 108 const Handler handlerFunction;
diff --git a/framework/actions/context.cpp b/framework/actions/context.cpp
index 8f370a0b..45b660a9 100644
--- a/framework/actions/context.cpp
+++ b/framework/actions/context.cpp
@@ -29,6 +29,20 @@ Context::Context(QObject *parent)
29 29
30} 30}
31 31
32Context::Context(const Context &other)
33 : QObject()
34{
35 *this = other;
36}
37
38Context &Context::operator=(const Context &other)
39{
40 for (const auto &p : other.availableProperties()) {
41 setProperty(p, other.property(p));
42 }
43 return *this;
44}
45
32void Context::clear() 46void Context::clear()
33{ 47{
34 auto meta = metaObject(); 48 auto meta = metaObject();
@@ -41,6 +55,20 @@ void Context::clear()
41 } 55 }
42} 56}
43 57
58QSet<QByteArray> Context::availableProperties() const
59{
60 QSet<QByteArray> names;
61 auto meta = metaObject();
62 for (auto i = meta->propertyOffset(); i < meta->propertyCount(); i++) {
63 auto property = meta->property(i);
64 names << property.name();
65 }
66 for (const auto &p : dynamicPropertyNames()) {
67 names << p;
68 }
69 return names;
70}
71
44QDebug operator<<(QDebug dbg, const Kube::Context &context) 72QDebug operator<<(QDebug dbg, const Kube::Context &context)
45{ 73{
46 dbg << "Kube::Context {\n"; 74 dbg << "Kube::Context {\n";
@@ -55,3 +83,9 @@ QDebug operator<<(QDebug dbg, const Kube::Context &context)
55 dbg << "\n}"; 83 dbg << "\n}";
56 return dbg; 84 return dbg;
57} 85}
86
87QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &context)
88{
89 dbg << context.context;
90 return dbg;
91}
diff --git a/framework/actions/context.h b/framework/actions/context.h
index 42ae3a93..4207fe12 100644
--- a/framework/actions/context.h
+++ b/framework/actions/context.h
@@ -19,17 +19,20 @@
19#pragma once 19#pragma once
20 20
21#include <QObject> 21#include <QObject>
22
23#define KUBE_CONTEXT_PROPERTY(TYPE, NAME, LOWERCASENAME) \ 22#define KUBE_CONTEXT_PROPERTY(TYPE, NAME, LOWERCASENAME) \
24 public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \ 23 public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \
24 Q_SIGNALS: void LOWERCASENAME##Changed(); \
25 private: TYPE m##NAME;
26
27#define KUBE_CONTEXTWRAPPER_PROPERTY(TYPE, NAME, LOWERCASENAME) \
28 public: \
25 struct NAME { \ 29 struct NAME { \
26 static constexpr const char *name = #LOWERCASENAME; \ 30 static constexpr const char *name = #LOWERCASENAME; \
27 typedef TYPE Type; \ 31 typedef TYPE Type; \
28 }; \ 32 }; \
29 void set##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \ 33 void set##NAME(const TYPE &value) { context.setProperty(NAME::name, QVariant::fromValue(value)); } \
30 TYPE get##NAME() const { return m##NAME; } \ 34 void clear##NAME() { context.setProperty(NAME::name, QVariant{}); } \
31 Q_SIGNALS: void LOWERCASENAME##Changed(); \ 35 TYPE get##NAME() const { return context.property(NAME::name).value<TYPE>(); } \
32 private: TYPE m##NAME;
33 36
34 37
35namespace Kube { 38namespace Kube {
@@ -38,13 +41,27 @@ class Context : public QObject {
38 Q_OBJECT 41 Q_OBJECT
39public: 42public:
40 Context(QObject *parent = 0); 43 Context(QObject *parent = 0);
44 Context(const Context &);
45
41 virtual ~Context(){}; 46 virtual ~Context(){};
47
48 Context &operator=(const Context &);
49
42 virtual void clear(); 50 virtual void clear();
51
52 QSet<QByteArray> availableProperties() const;
53};
54
55class ContextWrapper {
56public:
57 ContextWrapper(Context &c) : context{c} {}
58 Context &context;
43}; 59};
44 60
45} 61}
46 62
47QDebug operator<<(QDebug dbg, const Kube::Context &); 63QDebug operator<<(QDebug dbg, const Kube::Context &);
64QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &);
48 65
49Q_DECLARE_METATYPE(Kube::Context*); 66Q_DECLARE_METATYPE(Kube::Context*);
50 67
diff --git a/framework/actions/tests/CMakeLists.txt b/framework/actions/tests/CMakeLists.txt
new file mode 100644
index 00000000..af872a3b
--- /dev/null
+++ b/framework/actions/tests/CMakeLists.txt
@@ -0,0 +1,6 @@
1include_directories(${CMAKE_CURRENT_BINARY_DIR})
2cmake_policy(SET CMP0063 NEW)
3add_executable(actiontest actiontest.cpp)
4add_test(actiontest sinkactiontest)
5qt5_use_modules(actiontest Core Test)
6target_link_libraries(actiontest actionplugin)
diff --git a/framework/actions/tests/actiontest.cpp b/framework/actions/tests/actiontest.cpp
new file mode 100644
index 00000000..a4ec4432
--- /dev/null
+++ b/framework/actions/tests/actiontest.cpp
@@ -0,0 +1,102 @@
1#include <QTest>
2#include <QDebug>
3#include <QSignalSpy>
4
5#include <actions/action.h>
6#include <actions/context.h>
7#include <actions/actionhandler.h>
8
9#include <sink/log.h>
10
11SINK_DEBUG_AREA("actiontest")
12
13class HandlerContext : public Kube::Context {
14 Q_OBJECT
15 KUBE_CONTEXT_PROPERTY(QString, Property1, property1)
16 KUBE_CONTEXT_PROPERTY(QString, Property2, property2)
17};
18
19class HandlerContextWrapper : public Kube::ContextWrapper {
20 using Kube::ContextWrapper::ContextWrapper;
21 KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property1, property1)
22 KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property2, property2)
23};
24
25
26
27class Handler : public Kube::ActionHandlerBase<HandlerContextWrapper>
28{
29public:
30 Handler() : Kube::ActionHandlerBase<HandlerContextWrapper>{"org.kde.kube.test.action1"}
31 {}
32
33 //TODO default implementation checks that all defined properties are available in the context
34 // bool isReady() override {
35 // auto accountId = context->property("accountId").value<QByteArray>();
36 // return !accountId.isEmpty();
37 // }
38
39 KAsync::Job<void> execute(HandlerContextWrapper &context)
40 {
41 SinkLog() << "Executing action1";
42 SinkLog() << context;
43 executions.append(context.context);
44 return KAsync::null<void>();
45 }
46 mutable QList<Kube::Context> executions;
47};
48
49class Context1 : public Kube::ContextWrapper {
50 using Kube::ContextWrapper::ContextWrapper;
51 KUBE_CONTEXTWRAPPER_PROPERTY(QString, Property1, property1)
52 KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2)
53};
54
55class Context2 : public Kube::ContextWrapper {
56 using Kube::ContextWrapper::ContextWrapper;
57 KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2)
58};
59
60
61class ActionTest : public QObject
62{
63 Q_OBJECT
64private slots:
65
66 void initTestCase()
67 {
68 }
69
70 void testActionExecution()
71 {
72 Handler actionHandler;
73
74 HandlerContext context;
75 //Kube::Context context;
76 HandlerContextWrapper{context}.setProperty1(QString("property1"));
77 context.setProperty("property2", QVariant::fromValue(QString("property2")));
78 auto future = Kube::Action("org.kde.kube.test.action1", context).executeWithResult();
79
80 QTRY_VERIFY(future.isDone());
81 QVERIFY(!future.error());
82
83 QCOMPARE(actionHandler.executions.size(), 1);
84 QCOMPARE(actionHandler.executions.first().availableProperties().size(), 2);
85 }
86
87 void testContextCasting()
88 {
89 Kube::Context c;
90
91 Context1 context1{c};
92 context1.setProperty1("property1");
93 context1.setProperty2("property2");
94
95 auto context2 = Context2{c};
96 QCOMPARE(context2.getProperty2(), QByteArray("property2"));
97 }
98
99};
100
101QTEST_GUILESS_MAIN(ActionTest)
102#include "actiontest.moc"
diff --git a/framework/domain/CMakeLists.txt b/framework/domain/CMakeLists.txt
index 481d5908..bb522416 100644
--- a/framework/domain/CMakeLists.txt
+++ b/framework/domain/CMakeLists.txt
@@ -20,6 +20,9 @@ set(mailplugin_SRCS
20 identitiesmodel.cpp 20 identitiesmodel.cpp
21 recepientautocompletionmodel.cpp 21 recepientautocompletionmodel.cpp
22 settings/accountsettings.cpp 22 settings/accountsettings.cpp
23 selector.cpp
24 completer.cpp
25 controller.cpp
23) 26)
24find_package(KF5 REQUIRED COMPONENTS Package) 27find_package(KF5 REQUIRED COMPONENTS Package)
25 28
diff --git a/framework/domain/actions/sinkactions.cpp b/framework/domain/actions/sinkactions.cpp
index fd791a91..a2d4c02c 100644
--- a/framework/domain/actions/sinkactions.cpp
+++ b/framework/domain/actions/sinkactions.cpp
@@ -16,7 +16,7 @@
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA. 17 02110-1301, USA.
18*/ 18*/
19#include <actions/context.h> 19
20#include <actions/actionhandler.h> 20#include <actions/actionhandler.h>
21 21
22#include <KMime/Message> 22#include <KMime/Message>
@@ -78,12 +78,18 @@ static ActionHandlerHelper deleteHandler("org.kde.kube.actions.delete",
78 } 78 }
79); 79);
80 80
81class FolderContext : public Kube::ContextWrapper {
82 using Kube::ContextWrapper::ContextWrapper;
83 KUBE_CONTEXTWRAPPER_PROPERTY(Sink::ApplicationDomain::Folder::Ptr, Folder, folder)
84};
85
81static ActionHandlerHelper synchronizeHandler("org.kde.kube.actions.synchronize", 86static ActionHandlerHelper synchronizeHandler("org.kde.kube.actions.synchronize",
82 [](Context *context) -> bool { 87 [](Context *context) -> bool {
83 return true; 88 return true;
84 }, 89 },
85 [](Context *context) { 90 [](Context *context_) {
86 if (auto folder = context->property("folder").value<Folder::Ptr>()) { 91 auto context = FolderContext{*context_};
92 if (auto folder = context.getFolder()) {
87 SinkLog() << "Synchronizing folder " << folder->resourceInstanceIdentifier() << folder->identifier(); 93 SinkLog() << "Synchronizing folder " << folder->resourceInstanceIdentifier() << folder->identifier();
88 auto scope = SyncScope().resourceFilter(folder->resourceInstanceIdentifier()).filter<Mail::Folder>(QVariant::fromValue(folder->identifier())); 94 auto scope = SyncScope().resourceFilter(folder->resourceInstanceIdentifier()).filter<Mail::Folder>(QVariant::fromValue(folder->identifier()));
89 scope.setType<ApplicationDomain::Mail>(); 95 scope.setType<ApplicationDomain::Mail>();
@@ -110,65 +116,3 @@ static ActionHandlerHelper sendOutboxHandler("org.kde.kube.actions.sendOutbox",
110 }} 116 }}
111); 117);
112 118
113static ActionHandlerHelper sendMailHandler("org.kde.kube.actions.sendmail",
114 [](Context *context) -> bool {
115 auto accountId = context->property("accountId").value<QByteArray>();
116 return !accountId.isEmpty();
117 },
118 ActionHandlerHelper::JobHandler{[](Context *context) -> KAsync::Job<void> {
119 auto accountId = context->property("accountId").value<QByteArray>();
120 auto message = context->property("message").value<KMime::Message::Ptr>();
121 SinkLog() << "Sending a mail: " << *context;
122
123 Query query;
124 query.containsFilter<ApplicationDomain::SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::transport);
125 query.filter<SinkResource::Account>(accountId);
126 return Store::fetchAll<ApplicationDomain::SinkResource>(query)
127 .then<void, QList<ApplicationDomain::SinkResource::Ptr>>([=](const QList<ApplicationDomain::SinkResource::Ptr> &resources) -> KAsync::Job<void> {
128 if (!resources.isEmpty()) {
129 auto resourceId = resources[0]->identifier();
130 SinkTrace() << "Sending message via resource: " << resourceId;
131 Mail mail(resourceId);
132 mail.setBlobProperty("mimeMessage", message->encodedContent());
133 return Store::create(mail);
134 }
135 SinkWarning() << "Failed to find a mailtransport resource";
136 return KAsync::error<void>(0, "Failed to find a MailTransport resource.");
137 });
138 }}
139);
140
141static ActionHandlerHelper saveAsDraft("org.kde.kube.actions.save-as-draft",
142 [](Context *context) -> bool {
143 auto accountId = context->property("accountId").value<QByteArray>();
144 return !accountId.isEmpty();
145 },
146 ActionHandlerHelper::JobHandler([](Context *context) -> KAsync::Job<void> {
147 SinkLog() << "Executing the save-as-draft action";
148 SinkLog() << *context;
149 const auto accountId = context->property("accountId").value<QByteArray>();
150 const auto message = context->property("message").value<KMime::Message::Ptr>();
151 auto existingMail = context->property("existingMail").value<Mail>();
152 if (!message) {
153 SinkWarning() << "Failed to get the mail: " << context->property("mail");
154 return KAsync::error<void>(1, "Failed to get the mail: " + context->property("mail").toString());
155 }
156
157 if (existingMail.identifier().isEmpty()) {
158 Query query;
159 query.containsFilter<SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::drafts);
160 query.filter<SinkResource::Account>(accountId);
161 return Store::fetchOne<SinkResource>(query)
162 .then<void, SinkResource>([=](const SinkResource &resource) -> KAsync::Job<void> {
163 Mail mail(resource.identifier());
164 mail.setDraft(true);
165 mail.setMimeMessage(message->encodedContent());
166 return Store::create(mail);
167 });
168 } else {
169 SinkWarning() << "Modifying an existing mail" << existingMail.identifier();
170 existingMail.setMimeMessage(message->encodedContent());
171 return Store::modify(existingMail);
172 }
173 })
174);
diff --git a/framework/domain/completer.cpp b/framework/domain/completer.cpp
new file mode 100644
index 00000000..cacb4faa
--- /dev/null
+++ b/framework/domain/completer.cpp
@@ -0,0 +1,26 @@
1/*
2 Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#include "completer.h"
20
21#include <QQmlEngine>
22
23Completer::Completer(QAbstractItemModel *model) : mModel{model}
24{
25 QQmlEngine::setObjectOwnership(mModel, QQmlEngine::CppOwnership);
26}
diff --git a/framework/domain/completer.h b/framework/domain/completer.h
new file mode 100644
index 00000000..a672b809
--- /dev/null
+++ b/framework/domain/completer.h
@@ -0,0 +1,40 @@
1/*
2 Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#pragma once
20
21#include <QAbstractItemModel>
22#include <QString>
23#include <QQmlEngine>
24
25class Completer : public QObject {
26 Q_OBJECT
27 Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT)
28 Q_PROPERTY (QString searchString WRITE setSearchString READ searchString)
29
30public:
31 Completer(QAbstractItemModel *model);
32 QAbstractItemModel *model() { return mModel; }
33 virtual void setSearchString(const QString &s) { mSearchString = s; }
34 QString searchString() const { return mSearchString; }
35
36private:
37 QAbstractItemModel *mModel = nullptr;
38 QString mSearchString;
39};
40
diff --git a/framework/domain/composercontroller.cpp b/framework/domain/composercontroller.cpp
index 57d386c6..4ce356a9 100644
--- a/framework/domain/composercontroller.cpp
+++ b/framework/domain/composercontroller.cpp
@@ -19,9 +19,6 @@
19 19
20 20
21#include "composercontroller.h" 21#include "composercontroller.h"
22#include <actions/context.h>
23#include <actions/action.h>
24#include <actions/actionhandler.h>
25#include <settings/settings.h> 22#include <settings/settings.h>
26#include <KMime/Message> 23#include <KMime/Message>
27#include <KCodecs/KEmailAddress> 24#include <KCodecs/KEmailAddress>
@@ -40,41 +37,9 @@
40 37
41SINK_DEBUG_AREA("composercontroller"); 38SINK_DEBUG_AREA("composercontroller");
42 39
43Q_DECLARE_METATYPE(KMime::Types::Mailbox)
44
45ComposerController::ComposerController(QObject *parent) : QObject(parent)
46{
47 QQmlEngine::setObjectOwnership(&mContext, QQmlEngine::CppOwnership);
48}
49
50
51Kube::Context* ComposerController::mailContext()
52{
53 return &mContext;
54}
55
56class RecipientCompleter : public Completer {
57public:
58 RecipientCompleter() : Completer(new RecipientAutocompletionModel)
59 {
60 }
61
62 void setSearchString(const QString &s) {
63 static_cast<RecipientAutocompletionModel*>(model())->setFilter(s);
64 Completer::setSearchString(s);
65 }
66};
67
68Completer *ComposerController::recipientCompleter() const
69{
70 static auto selector = new RecipientCompleter();
71 QQmlEngine::setObjectOwnership(selector, QQmlEngine::CppOwnership);
72 return selector;
73}
74
75class IdentitySelector : public Selector { 40class IdentitySelector : public Selector {
76public: 41public:
77 IdentitySelector(ComposerContext &context) : Selector(new IdentitiesModel), mContext(context) 42 IdentitySelector(ComposerController &controller) : Selector(new IdentitiesModel), mController(controller)
78 { 43 {
79 } 44 }
80 45
@@ -88,33 +53,72 @@ public:
88 mb.setAddress(index.data(IdentitiesModel::Address).toString().toUtf8()); 53 mb.setAddress(index.data(IdentitiesModel::Address).toString().toUtf8());
89 SinkLog() << "Setting current identity: " << mb.prettyAddress() << "Account: " << currentAccountId; 54 SinkLog() << "Setting current identity: " << mb.prettyAddress() << "Account: " << currentAccountId;
90 55
91 mContext.setProperty("identity", QVariant::fromValue(mb)); 56 mController.setIdentity(mb);
92 mContext.setProperty("accountId", QVariant::fromValue(currentAccountId)); 57 mController.setAccountId(currentAccountId);
93 } else { 58 } else {
94 SinkWarning() << "No valid identity for index: " << index; 59 SinkWarning() << "No valid identity for index: " << index;
95 mContext.setProperty("identity", QVariant{}); 60 mController.clearIdentity();
96 mContext.setProperty("accountId", QVariant{}); 61 mController.clearAccountId();
97 } 62 }
98 63
99 } 64 }
100private: 65private:
101 ComposerContext &mContext; 66 ComposerController &mController;
102}; 67};
103 68
69class RecipientCompleter : public Completer {
70public:
71 RecipientCompleter() : Completer(new RecipientAutocompletionModel)
72 {
73 }
74
75 void setSearchString(const QString &s) {
76 static_cast<RecipientAutocompletionModel*>(model())->setFilter(s);
77 Completer::setSearchString(s);
78 }
79};
80
81
82ComposerController::ComposerController()
83 : Kube::Controller(),
84 mSendAction{new Kube::ControllerAction},
85 mSaveAsDraftAction{new Kube::ControllerAction},
86 mRecipientCompleter{new RecipientCompleter},
87 mIdentitySelector{new IdentitySelector{*this}}
88{
89 QObject::connect(mSaveAsDraftAction.data(), &Kube::ControllerAction::triggered, this, &ComposerController::saveAsDraft);
90 updateSaveAsDraftAction();
91 // mSendAction->monitorProperty<To>();
92 // mSendAction->monitorProperty<Send>([] (const QString &) -> bool{
93 // //validate
94 // });
95 // registerAction<ControllerAction>(&ComposerController::send);
96 // actionDepends<ControllerAction, To, Subject>();
97 // TODO do in constructor
98 QObject::connect(mSendAction.data(), &Kube::ControllerAction::triggered, this, &ComposerController::send);
99
100 QObject::connect(this, &ComposerController::toChanged, &ComposerController::updateSendAction);
101 QObject::connect(this, &ComposerController::subjectChanged, &ComposerController::updateSendAction);
102 updateSendAction();
103}
104
105Completer *ComposerController::recipientCompleter() const
106{
107 return mRecipientCompleter.data();
108}
109
104Selector *ComposerController::identitySelector() const 110Selector *ComposerController::identitySelector() const
105{ 111{
106 static auto selector = new IdentitySelector(*const_cast<ComposerContext*>(&mContext)); 112 return mIdentitySelector.data();
107 QQmlEngine::setObjectOwnership(selector, QQmlEngine::CppOwnership);
108 return selector;
109} 113}
110 114
111void ComposerController::setMessage(const KMime::Message::Ptr &msg) 115void ComposerController::setMessage(const KMime::Message::Ptr &msg)
112{ 116{
113 mContext.setTo(msg->to(true)->asUnicodeString()); 117 setTo(msg->to(true)->asUnicodeString());
114 mContext.setCc(msg->cc(true)->asUnicodeString()); 118 setCc(msg->cc(true)->asUnicodeString());
115 mContext.setSubject(msg->subject(true)->asUnicodeString()); 119 setSubject(msg->subject(true)->asUnicodeString());
116 mContext.setBody(msg->body()); 120 setBody(msg->body());
117 mContext.setProperty("existingMessage", QVariant::fromValue(msg)); 121 setExistingMessage(msg);
118} 122}
119 123
120void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) 124void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
@@ -122,18 +126,20 @@ void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft)
122 Sink::Query query(*message.value<Sink::ApplicationDomain::Mail::Ptr>()); 126 Sink::Query query(*message.value<Sink::ApplicationDomain::Mail::Ptr>());
123 query.request<Sink::ApplicationDomain::Mail::MimeMessage>(); 127 query.request<Sink::ApplicationDomain::Mail::MimeMessage>();
124 Sink::Store::fetchOne<Sink::ApplicationDomain::Mail>(query).syncThen<void, Sink::ApplicationDomain::Mail>([this, loadAsDraft](const Sink::ApplicationDomain::Mail &mail) { 128 Sink::Store::fetchOne<Sink::ApplicationDomain::Mail>(query).syncThen<void, Sink::ApplicationDomain::Mail>([this, loadAsDraft](const Sink::ApplicationDomain::Mail &mail) {
125 mContext.setProperty("existingMail", QVariant::fromValue(mail)); 129 setExistingMail(mail);
130
131 //TODO this should probably happen as reaction to the property being set.
126 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage()); 132 const auto mailData = KMime::CRLFtoLF(mail.getMimeMessage());
127 if (!mailData.isEmpty()) { 133 if (!mailData.isEmpty()) {
128 KMime::Message::Ptr mail(new KMime::Message); 134 KMime::Message::Ptr mail(new KMime::Message);
129 mail->setContent(mailData); 135 mail->setContent(mailData);
130 mail->parse(); 136 mail->parse();
131 if (loadAsDraft) { 137 if (loadAsDraft) {
138 setMessage(mail);
139 } else {
132 auto reply = MailTemplates::reply(mail); 140 auto reply = MailTemplates::reply(mail);
133 //We assume reply 141 //We assume reply
134 setMessage(reply); 142 setMessage(reply);
135 } else {
136 setMessage(mail);
137 } 143 }
138 } else { 144 } else {
139 qWarning() << "Retrieved empty message"; 145 qWarning() << "Retrieved empty message";
@@ -159,68 +165,122 @@ void applyAddresses(const QString &list, std::function<void(const QByteArray &,
159 } 165 }
160} 166}
161 167
162void ComposerController::clear() 168Kube::ControllerAction* ComposerController::saveAsDraftAction()
169{
170 return mSaveAsDraftAction.data();
171}
172
173Kube::ControllerAction* ComposerController::sendAction()
163{ 174{
164 mContext.clear(); 175 return mSendAction.data();
165} 176}
166 177
178KMime::Message::Ptr ComposerController::assembleMessage()
179{
180 auto mail = mExistingMessage;
181 if (!mail) {
182 mail = KMime::Message::Ptr::create();
183 }
184 applyAddresses(getTo(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
185 mail->to(true)->addAddress(addrSpec, displayName);
186 recordForAutocompletion(addrSpec, displayName);
187 });
188 applyAddresses(getCc(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
189 mail->cc(true)->addAddress(addrSpec, displayName);
190 recordForAutocompletion(addrSpec, displayName);
191 });
192 applyAddresses(getBcc(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
193 mail->bcc(true)->addAddress(addrSpec, displayName);
194 recordForAutocompletion(addrSpec, displayName);
195 });
196
197 mail->from(true)->addAddress(getIdentity());
198
199 mail->subject(true)->fromUnicodeString(getSubject(), "utf-8");
200 mail->setBody(getBody().toUtf8());
201 mail->assemble();
202 return mail;
203}
167 204
168Kube::ActionHandler *ComposerController::messageHandler() 205void ComposerController::updateSendAction()
169{ 206{
170 return new Kube::ActionHandlerHelper( 207 auto enabled = !getTo().isEmpty() && !getSubject().isEmpty();
171 [](Kube::Context *context) { 208 mSendAction->setEnabled(enabled);
172 auto identity = context->property("identity"); 209}
173 return identity.isValid(); 210
174 }, 211void ComposerController::send()
175 [this](Kube::Context *context) { 212{
176 auto mail = context->property("existingMessage").value<KMime::Message::Ptr>(); 213 // verify<To, Subject>()
177 if (!mail) { 214 // && verify<Subject>();
178 mail = KMime::Message::Ptr::create(); 215 auto message = assembleMessage();
216
217 auto accountId = getAccountId();
218 //SinkLog() << "Sending a mail: " << *this;
219 using namespace Sink;
220 using namespace Sink::ApplicationDomain;
221
222 Query query;
223 query.containsFilter<ApplicationDomain::SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::transport);
224 query.filter<SinkResource::Account>(accountId);
225 auto job = Store::fetchAll<ApplicationDomain::SinkResource>(query)
226 .then<void, QList<ApplicationDomain::SinkResource::Ptr>>([=](const QList<ApplicationDomain::SinkResource::Ptr> &resources) -> KAsync::Job<void> {
227 if (!resources.isEmpty()) {
228 auto resourceId = resources[0]->identifier();
229 SinkTrace() << "Sending message via resource: " << resourceId;
230 Mail mail(resourceId);
231 mail.setBlobProperty("mimeMessage", message->encodedContent());
232 return Store::create(mail);
179 } 233 }
180 applyAddresses(context->property(ComposerContext::To::name).toString(), [&](const QByteArray &addrSpec, const QByteArray &displayName) { 234 return KAsync::error<void>(0, "Failed to find a MailTransport resource.");
181 mail->to(true)->addAddress(addrSpec, displayName); 235 });
182 recordForAutocompletion(addrSpec, displayName); 236 run(job);
183 }); 237 job = job.syncThen<void>([&] {
184 applyAddresses(context->property(ComposerContext::Cc::name).toString(), [&](const QByteArray &addrSpec, const QByteArray &displayName) { 238 emit done();
185 mail->cc(true)->addAddress(addrSpec, displayName); 239 });
186 recordForAutocompletion(addrSpec, displayName);
187 });
188 applyAddresses(context->property(ComposerContext::Bcc::name).toString(), [&](const QByteArray &addrSpec, const QByteArray &displayName) {
189 mail->bcc(true)->addAddress(addrSpec, displayName);
190 recordForAutocompletion(addrSpec, displayName);
191 });
192
193 mail->from(true)->addAddress(context->property("identity").value<KMime::Types::Mailbox>());
194
195 mail->subject(true)->fromUnicodeString(context->property(ComposerContext::Subject::name).toString(), "utf-8");
196 mail->setBody(context->property(ComposerContext::Body::name).toString().toUtf8());
197 mail->assemble();
198
199 context->setProperty("message", QVariant::fromValue(mail));
200 }
201 );
202} 240}
203 241
204Kube::Action* ComposerController::saveAsDraftAction() 242void ComposerController::updateSaveAsDraftAction()
205{ 243{
206 auto action = new Kube::Action("org.kde.kube.actions.save-as-draft", mContext); 244 mSendAction->setEnabled(true);
207 action->addPreHandler(messageHandler());
208 action->addPostHandler(new Kube::ActionHandlerHelper(
209 [this](Kube::Context *context) {
210 emit done();
211 }));
212 return action;
213} 245}
214 246
215Kube::Action* ComposerController::sendAction() 247void ComposerController::saveAsDraft()
216{ 248{
217 auto action = new Kube::Action("org.kde.kube.actions.sendmail", mContext); 249 const auto accountId = getAccountId();
218 // action->addPreHandler(identityHandler()); 250 auto existingMail = getExistingMail();
219 action->addPreHandler(messageHandler()); 251
220 // action->addPreHandler(encryptionHandler()); 252 auto message = assembleMessage();
221 action->addPostHandler(new Kube::ActionHandlerHelper( 253 //FIXME this is something for the validation
222 [this](Kube::Context *context) { 254 if (!message) {
223 emit done(); 255 SinkWarning() << "Failed to get the mail: ";
224 })); 256 return;
225 return action; 257 // return KAsync::error<void>(1, "Failed to get the mail.");
258 }
259
260 using namespace Sink;
261 using namespace Sink::ApplicationDomain;
262
263 auto job = [&] {
264 if (existingMail.identifier().isEmpty()) {
265 Query query;
266 query.containsFilter<SinkResource::Capabilities>(ApplicationDomain::ResourceCapabilities::Mail::drafts);
267 query.filter<SinkResource::Account>(accountId);
268 return Store::fetchOne<SinkResource>(query)
269 .then<void, SinkResource>([=](const SinkResource &resource) -> KAsync::Job<void> {
270 Mail mail(resource.identifier());
271 mail.setDraft(true);
272 mail.setMimeMessage(message->encodedContent());
273 return Store::create(mail);
274 });
275 } else {
276 SinkWarning() << "Modifying an existing mail" << existingMail.identifier();
277 existingMail.setDraft(true);
278 existingMail.setMimeMessage(message->encodedContent());
279 return Store::modify(existingMail);
280 }
281 }();
282 job = job.syncThen<void>([&] {
283 emit done();
284 });
285 run(job);
226} 286}
diff --git a/framework/domain/composercontroller.h b/framework/domain/composercontroller.h
index 3e701ed1..c5046306 100644
--- a/framework/domain/composercontroller.h
+++ b/framework/domain/composercontroller.h
@@ -23,110 +23,74 @@
23#include <QString> 23#include <QString>
24#include <QStringList> 24#include <QStringList>
25#include <QVariant> 25#include <QVariant>
26#include <QQmlEngine>
27#include <QAbstractItemModel>
28#include <sink/applicationdomaintype.h> 26#include <sink/applicationdomaintype.h>
27#include <KMime/Message>
29 28
30#include <actions/context.h> 29#include "completer.h"
31#include <actions/action.h> 30#include "selector.h"
31#include "controller.h"
32 32
33namespace KMime { 33inline bool operator !=(const KMime::Types::Mailbox &l, const KMime::Types::Mailbox &r)
34class Message; 34{
35 return !(l.prettyAddress() == r.prettyAddress());
35} 36}
36 37
37class ComposerContext : public Kube::Context { 38Q_DECLARE_METATYPE(KMime::Types::Mailbox);
38 Q_OBJECT
39 KUBE_CONTEXT_PROPERTY(QString, To, to)
40 KUBE_CONTEXT_PROPERTY(QString, Cc, cc)
41 KUBE_CONTEXT_PROPERTY(QString, Bcc, bcc)
42 KUBE_CONTEXT_PROPERTY(QString, From, from)
43 KUBE_CONTEXT_PROPERTY(QString, Subject, subject)
44 KUBE_CONTEXT_PROPERTY(QString, Body, body)
45};
46
47class Completer : public QObject {
48 Q_OBJECT
49 Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT)
50 Q_PROPERTY (QString searchString WRITE setSearchString READ searchString)
51 39
52public: 40namespace KMime {
53 Completer(QAbstractItemModel *model) : mModel{model} 41class Message;
54 { 42}
55 QQmlEngine::setObjectOwnership(mModel, QQmlEngine::CppOwnership);
56 }
57 QAbstractItemModel *model() { return mModel; }
58 virtual void setSearchString(const QString &s) { mSearchString = s; }
59 QString searchString() const { return mSearchString; }
60
61private:
62 QAbstractItemModel *mModel = nullptr;
63 QString mSearchString;
64};
65 43
66/** 44class ComposerController : public Kube::Controller
67 * Exposes a model and maintains a current index selection. 45{
68 */
69class Selector : public QObject {
70 Q_OBJECT 46 Q_OBJECT
71 Q_PROPERTY (int currentIndex READ currentIndex WRITE setCurrentIndex)
72 Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT)
73
74public:
75 Selector(QAbstractItemModel *model) : mModel{model}
76 {
77 QQmlEngine::setObjectOwnership(mModel, QQmlEngine::CppOwnership);
78 }
79
80 virtual QAbstractItemModel *model() { return mModel; }
81
82 void setCurrentIndex(int i) {
83 mCurrentIndex = i;
84 Q_ASSERT(mModel);
85 setCurrent(mModel->index(mCurrentIndex, 0));
86 }
87 47
88 int currentIndex() { return mCurrentIndex; } 48 //Interface properties
49 KUBE_CONTROLLER_PROPERTY(QString, To, to)
50 KUBE_CONTROLLER_PROPERTY(QString, Cc, cc)
51 KUBE_CONTROLLER_PROPERTY(QString, Bcc, bcc)
52 KUBE_CONTROLLER_PROPERTY(QString, Subject, subject)
53 KUBE_CONTROLLER_PROPERTY(QString, Body, body)
89 54
90 virtual void setCurrent(const QModelIndex &) = 0; 55 //Set by identitySelector
91private: 56 KUBE_CONTROLLER_PROPERTY(KMime::Types::Mailbox, Identity, identity)
92 QAbstractItemModel *mModel = nullptr; 57 KUBE_CONTROLLER_PROPERTY(QByteArray, AccountId, accountId)
93 int mCurrentIndex = 0;
94};
95 58
96class ComposerController : public QObject 59 //Set by loadMessage
97{ 60 KUBE_CONTROLLER_PROPERTY(KMime::Message::Ptr, ExistingMessage, existingMessage)
98 Q_OBJECT 61 KUBE_CONTROLLER_PROPERTY(Sink::ApplicationDomain::Mail, ExistingMail, existingMail)
99 Q_PROPERTY (Kube::Context* mailContext READ mailContext CONSTANT)
100 62
101 Q_PROPERTY (Completer* recipientCompleter READ recipientCompleter CONSTANT) 63 Q_PROPERTY (Completer* recipientCompleter READ recipientCompleter CONSTANT)
102 Q_PROPERTY (Selector* identitySelector READ identitySelector CONSTANT) 64 Q_PROPERTY (Selector* identitySelector READ identitySelector CONSTANT)
65 //Q_PROPERTY (QValidator* subjectValidator READ subjectValidator CONSTANT)
103 66
104 Q_PROPERTY (Kube::Action* sendAction READ sendAction) 67 Q_PROPERTY (Kube::ControllerAction* sendAction READ sendAction CONSTANT)
105 Q_PROPERTY (Kube::Action* saveAsDraftAction READ saveAsDraftAction) 68 Q_PROPERTY (Kube::ControllerAction* saveAsDraftAction READ saveAsDraftAction CONSTANT)
106 69
107public: 70public:
108 explicit ComposerController(QObject *parent = Q_NULLPTR); 71 explicit ComposerController();
109
110 Kube::Context* mailContext();
111 72
112 Completer *recipientCompleter() const; 73 Completer *recipientCompleter() const;
113 Selector *identitySelector() const; 74 Selector *identitySelector() const;
114 75
115 Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft); 76 Q_INVOKABLE void loadMessage(const QVariant &draft, bool loadAsDraft);
116 77
117 Kube::Action* sendAction(); 78 Kube::ControllerAction* sendAction();
118 Kube::Action* saveAsDraftAction(); 79 Kube::ControllerAction* saveAsDraftAction();
119
120public slots:
121 void clear();
122 80
123signals: 81private slots:
124 void done(); 82 void updateSendAction();
83 void send();
84 void updateSaveAsDraftAction();
85 void saveAsDraft();
125 86
126private: 87private:
127 Kube::ActionHandler *messageHandler();
128 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName); 88 void recordForAutocompletion(const QByteArray &addrSpec, const QByteArray &displayName);
129 void setMessage(const QSharedPointer<KMime::Message> &msg); 89 void setMessage(const QSharedPointer<KMime::Message> &msg);
90 KMime::Message::Ptr assembleMessage();
130 91
131 ComposerContext mContext; 92 QScopedPointer<Kube::ControllerAction> mSendAction;
93 QScopedPointer<Kube::ControllerAction> mSaveAsDraftAction;
94 QScopedPointer<Completer> mRecipientCompleter;
95 QScopedPointer<Selector> mIdentitySelector;
132}; 96};
diff --git a/framework/domain/controller.cpp b/framework/domain/controller.cpp
new file mode 100644
index 00000000..fb971136
--- /dev/null
+++ b/framework/domain/controller.cpp
@@ -0,0 +1,55 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#include "controller.h"
20
21#include <QQmlEngine>
22#include <QMetaProperty>
23
24using namespace Kube;
25
26ControllerAction::ControllerAction()
27 : QObject()
28{
29 QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership);
30}
31
32void ControllerAction::execute()
33{
34 emit triggered();
35}
36
37void Controller::clear()
38{
39 auto meta = metaObject();
40 for (auto i = meta->propertyOffset(); i < meta->propertyCount(); i++) {
41 auto property = meta->property(i);
42 setProperty(property.name(), QVariant());
43 }
44 for (const auto &p : dynamicPropertyNames()) {
45 setProperty(p, QVariant());
46 }
47}
48
49void Controller::run(const KAsync::Job<void> &job)
50{
51 auto jobToExec = job;
52 //TODO handle error
53 //TODO attach a log context to the execution that we can gather from the job?
54 jobToExec.exec();
55}
diff --git a/framework/domain/controller.h b/framework/domain/controller.h
new file mode 100644
index 00000000..c152a588
--- /dev/null
+++ b/framework/domain/controller.h
@@ -0,0 +1,75 @@
1/*
2 Copyright (c) 2016 Christian Mollekopf <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#pragma once
20
21#include <QObject>
22#include <QVariant>
23#include <Async/Async>
24
25#define KUBE_CONTROLLER_PROPERTY(TYPE, NAME, LOWERCASENAME) \
26 public: Q_PROPERTY(TYPE LOWERCASENAME MEMBER m##NAME NOTIFY LOWERCASENAME##Changed) \
27 Q_SIGNALS: void LOWERCASENAME##Changed(); \
28 private: TYPE m##NAME; \
29 public: \
30 struct NAME { \
31 static constexpr const char *name = #LOWERCASENAME; \
32 typedef TYPE Type; \
33 }; \
34 void set##NAME(const TYPE &value) { setProperty(NAME::name, QVariant::fromValue(value)); } \
35 void clear##NAME() { setProperty(NAME::name, QVariant{}); } \
36 TYPE get##NAME() const { return m##NAME; } \
37
38namespace Kube {
39
40class ControllerAction : public QObject {
41 Q_OBJECT
42 Q_PROPERTY(bool enabled MEMBER mEnabled NOTIFY enabledChanged)
43public:
44 ControllerAction();
45 ~ControllerAction() = default;
46
47 Q_INVOKABLE void execute();
48 void setEnabled(bool enabled) { setProperty("enabled", enabled); }
49
50signals:
51 void enabledChanged();
52 void triggered();
53
54private:
55 bool mEnabled = true;
56};
57
58class Controller : public QObject {
59 Q_OBJECT
60public:
61 Controller() = default;
62 virtual ~Controller() = default;
63
64public slots:
65 void clear();
66
67signals:
68 void done();
69 void error();
70
71protected:
72 void run(const KAsync::Job<void> &job);
73};
74
75}
diff --git a/framework/domain/selector.cpp b/framework/domain/selector.cpp
new file mode 100644
index 00000000..ddb23744
--- /dev/null
+++ b/framework/domain/selector.cpp
@@ -0,0 +1,26 @@
1/*
2 Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#include "selector.h"
20
21#include <QQmlEngine>
22
23Selector::Selector(QAbstractItemModel *model) : mModel{model}
24{
25 QQmlEngine::setObjectOwnership(mModel, QQmlEngine::CppOwnership);
26}
diff --git a/framework/domain/selector.h b/framework/domain/selector.h
new file mode 100644
index 00000000..77c47ba7
--- /dev/null
+++ b/framework/domain/selector.h
@@ -0,0 +1,50 @@
1/*
2 Copyright (c) 2016 Christian Mollekofp <mollekopf@kolabsys.com>
3
4 This library is free software; you can redistribute it and/or modify it
5 under the terms of the GNU Library General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or (at your
7 option) any later version.
8
9 This library is distributed in the hope that it will be useful, but WITHOUT
10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12 License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to the
16 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 02110-1301, USA.
18*/
19#pragma once
20
21#include <QAbstractItemModel>
22#include <QString>
23
24/**
25 * Exposes a model and maintains a current index selection.
26 */
27class Selector : public QObject {
28 Q_OBJECT
29 Q_PROPERTY (int currentIndex READ currentIndex WRITE setCurrentIndex)
30 Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT)
31
32public:
33 Selector(QAbstractItemModel *model);
34
35 virtual QAbstractItemModel *model() { return mModel; }
36
37 void setCurrentIndex(int i) {
38 mCurrentIndex = i;
39 Q_ASSERT(mModel);
40 setCurrent(mModel->index(mCurrentIndex, 0));
41 }
42
43 int currentIndex() { return mCurrentIndex; }
44
45 virtual void setCurrent(const QModelIndex &) = 0;
46private:
47 QAbstractItemModel *mModel = nullptr;
48 int mCurrentIndex = 0;
49};
50