summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/package/contents/ui/FocusComposer.qml61
-rw-r--r--docs/design.md4
-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
21 files changed, 754 insertions, 297 deletions
diff --git a/components/package/contents/ui/FocusComposer.qml b/components/package/contents/ui/FocusComposer.qml
index 07fb08b6..902309a8 100644
--- a/components/package/contents/ui/FocusComposer.qml
+++ b/components/package/contents/ui/FocusComposer.qml
@@ -23,37 +23,26 @@ import QtQuick.Controls 2.0 as Controls2
23import org.kde.kirigami 1.0 as Kirigami 23import org.kde.kirigami 1.0 as Kirigami
24 24
25import org.kube.framework.domain 1.0 as KubeFramework 25import org.kube.framework.domain 1.0 as KubeFramework
26import org.kube.framework.actions 1.0 as KubeAction
27 26
28Controls2.Popup { 27Controls2.Popup {
29 id: root 28 id: root
30 29
31 //Controller 30 //Controller
32 KubeFramework.ComposerController { 31 KubeFramework.ComposerController {
33 id: composer 32 id: composerController
34 onDone: { 33 onDone: {
34 clear();
35 root.close() 35 root.close()
36 } 36 }
37 } 37 }
38 38
39 //context
40 property variant mailcontext: composer.mailContext
41
42 //actions 39 //actions
43 property variant sendAction: composer.sendAction 40 property variant sendAction: composerController.sendAction
44 property variant saveAsDraftAction: composer.saveAsDraftAction 41 property variant saveAsDraftAction: composerController.saveAsDraftAction
45 42
46 //BEGIN functions 43 //BEGIN functions
47 function loadMessage(message, loadAsDraft) { 44 function loadMessage(message, loadAsDraft) {
48 composer.loadMessage(message, loadAsDraft) 45 composerController.loadMessage(message, loadAsDraft)
49 }
50
51 function saveAsDraft() {
52 composer.saveAsDraft()
53 }
54
55 function clear() {
56 composer.clear();
57 } 46 }
58 //END functions 47 //END functions
59 48
@@ -91,14 +80,14 @@ Controls2.Popup {
91 80
92 Layout.fillWidth: true 81 Layout.fillWidth: true
93 82
94 text: mailcontext.to 83 text: composerController.to
95 onTextChanged: { 84 onTextChanged: {
96 mailcontext.to = text; 85 composerController.to = text;
97 } 86 }
98 87
99 model: composer.recipientCompleter.model 88 model: composerController.recipientCompleter.model
100 onSearchTermChanged: { 89 onSearchTermChanged: {
101 composer.recipientCompleter.searchString = searchTerm 90 composerController.recipientCompleter.searchString = searchTerm
102 } 91 }
103 } 92 }
104 93
@@ -116,15 +105,15 @@ Controls2.Popup {
116 105
117 visible: false 106 visible: false
118 107
119 text: mailcontext.cc 108 text: composerController.cc
120 109
121 onTextChanged: { 110 onTextChanged: {
122 mailcontext.cc = text; 111 composerController.cc = text;
123 } 112 }
124 113
125 model: composer.recipientCompleter.model 114 model: composerController.recipientCompleter.model
126 onSearchTermChanged: { 115 onSearchTermChanged: {
127 composer.recipientCompleter.searchString = searchTerm 116 composerController.recipientCompleter.searchString = searchTerm
128 } 117 }
129 } 118 }
130 119
@@ -141,15 +130,15 @@ Controls2.Popup {
141 130
142 visible : false 131 visible : false
143 132
144 text: mailcontext.bcc 133 text: composerController.bcc
145 134
146 onTextChanged: { 135 onTextChanged: {
147 mailcontext.bcc = text; 136 composerController.bcc = text;
148 } 137 }
149 138
150 model: composer.recipientCompleter.model 139 model: composerController.recipientCompleter.model
151 onSearchTermChanged: { 140 onSearchTermChanged: {
152 composer.recipientCompleter.searchString = searchTerm 141 composerController.recipientCompleter.searchString = searchTerm
153 } 142 }
154 } 143 }
155 144
@@ -161,13 +150,13 @@ Controls2.Popup {
161 150
162 Controls2.ComboBox { 151 Controls2.ComboBox {
163 id: identityCombo 152 id: identityCombo
164 model: composer.identitySelector.model 153 model: composerController.identitySelector.model
165 textRole: "displayName" 154 textRole: "displayName"
166 155
167 Layout.fillWidth: true 156 Layout.fillWidth: true
168 157
169 onCurrentIndexChanged: { 158 onCurrentIndexChanged: {
170 composer.identitySelector.currentIndex = currentIndex 159 composerController.identitySelector.currentIndex = currentIndex
171 } 160 }
172 } 161 }
173 162
@@ -201,20 +190,20 @@ Controls2.Popup {
201 190
202 placeholderText: "Enter Subject..." 191 placeholderText: "Enter Subject..."
203 192
204 text: mailcontext.subject 193 text: composerController.subject
205 194
206 onTextChanged: { 195 onTextChanged: {
207 mailcontext.subject = text; 196 composerController.subject = text;
208 } 197 }
209 } 198 }
210 199
211 Controls2.TextArea { 200 Controls2.TextArea {
212 id: content 201 id: content
213 202
214 text: mailcontext.body 203 text: composerController.body
215 204
216 onTextChanged: { 205 onTextChanged: {
217 mailcontext.body = text; 206 composerController.body = text;
218 } 207 }
219 208
220 Layout.fillWidth: true 209 Layout.fillWidth: true
@@ -242,7 +231,7 @@ Controls2.Popup {
242 Controls2.Button { 231 Controls2.Button {
243 text: "Save as Draft" 232 text: "Save as Draft"
244 233
245 enabled: saveAsDraftAction.ready 234 enabled: saveAsDraftAction.enabled
246 onClicked: { 235 onClicked: {
247 saveAsDraftAction.execute() 236 saveAsDraftAction.execute()
248 } 237 }
@@ -251,7 +240,7 @@ Controls2.Popup {
251 Controls2.Button { 240 Controls2.Button {
252 text: "Send" 241 text: "Send"
253 242
254 enabled: sendAction.ready 243 enabled: sendAction.enabled
255 onClicked: { 244 onClicked: {
256 sendAction.execute() 245 sendAction.execute()
257 } 246 }
diff --git a/docs/design.md b/docs/design.md
index 0120ecf5..3b37a5b2 100644
--- a/docs/design.md
+++ b/docs/design.md
@@ -94,7 +94,7 @@ The action can, through property-binding, reevaluate its ready state based on th
94#### Pre-action handler 94#### Pre-action handler
95A pre-action handler can be used to supply additional context information for the action to execute. This can be used to i.e. retrieve configuration information or resolve a user uid over ldap. 95A pre-action handler can be used to supply additional context information for the action to execute. This can be used to i.e. retrieve configuration information or resolve a user uid over ldap.
96 96
97An action can be executed if a set of available pre-action handlers plus the initially supplied informatin can complete the context so the target action-handler can be executed. 97An action can be executed if a set of available pre-action handlers plus the initially supplied information can complete the context so the target action-handler can be executed.
98 98
99#### Selecting action handlers out of candidates. 99#### Selecting action handlers out of candidates.
100It is possible that multiple action handlers are avialable for the same action, i.e. because different accounts supplied an action handler for the same action. In such a case it is necessary to select the right action handler based on the context. 100It is possible that multiple action handlers are avialable for the same action, i.e. because different accounts supplied an action handler for the same action. In such a case it is necessary to select the right action handler based on the context.
@@ -102,7 +102,7 @@ It is possible that multiple action handlers are avialable for the same action,
102A simple criteria could be the currently selected account. 102A simple criteria could be the currently selected account.
103 103
104#### Automatic action discovery 104#### Automatic action discovery
105While in many places explicit instantiation of actions is desirable, sometimes we may want to offer all available actions for a certain type. For this it should be possible to i.e. query for all actions that apply to a mail. That way it is possible to centrally add a new action that automatically becomes available everywhere. Note that this only works for actions that don't require an additional UI, since the components would have to embedd that somewhere. 105While in many places explicit instantiation of actions is desirable, sometimes we may want to offer all available actions for a certain type. For this it should be possible to i.e. query for all actions that apply to a mail. That way it is possible to centrally add a new action that automatically become available everywhere. Note that this only works for actions that don't require an additional UI, since the components would have to embed that somewhere.
106 106
107#### Implementation 107#### Implementation
108Actions are objects that provide the API, and that QML can instantiate directly with it's id. The C++ implementation looks up the action handler via a broker. 108Actions are objects that provide the API, and that QML can instantiate directly with it's id. The C++ implementation looks up the action handler via a broker.
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