diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-01-01 13:37:19 +0100 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2017-01-02 00:31:41 +0100 |
commit | ba7128b30850594c7efb258d1794e377eede364a (patch) | |
tree | f805d511f6d12b0799c05b414054db8b8635bfc9 | |
parent | 431c257dd29e2e3d8878db200f0de4d452bffe92 (diff) | |
download | kube-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.
-rw-r--r-- | components/package/contents/ui/FocusComposer.qml | 61 | ||||
-rw-r--r-- | docs/design.md | 4 | ||||
-rw-r--r-- | framework/actions/CMakeLists.txt | 4 | ||||
-rw-r--r-- | framework/actions/actionbroker.cpp | 5 | ||||
-rw-r--r-- | framework/actions/actionbroker.h | 1 | ||||
-rw-r--r-- | framework/actions/actionhandler.cpp | 17 | ||||
-rw-r--r-- | framework/actions/actionhandler.h | 55 | ||||
-rw-r--r-- | framework/actions/context.cpp | 34 | ||||
-rw-r--r-- | framework/actions/context.h | 27 | ||||
-rw-r--r-- | framework/actions/tests/CMakeLists.txt | 6 | ||||
-rw-r--r-- | framework/actions/tests/actiontest.cpp | 102 | ||||
-rw-r--r-- | framework/domain/CMakeLists.txt | 3 | ||||
-rw-r--r-- | framework/domain/actions/sinkactions.cpp | 74 | ||||
-rw-r--r-- | framework/domain/completer.cpp | 26 | ||||
-rw-r--r-- | framework/domain/completer.h | 40 | ||||
-rw-r--r-- | framework/domain/composercontroller.cpp | 268 | ||||
-rw-r--r-- | framework/domain/composercontroller.h | 118 | ||||
-rw-r--r-- | framework/domain/controller.cpp | 55 | ||||
-rw-r--r-- | framework/domain/controller.h | 75 | ||||
-rw-r--r-- | framework/domain/selector.cpp | 26 | ||||
-rw-r--r-- | framework/domain/selector.h | 50 |
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 | |||
23 | import org.kde.kirigami 1.0 as Kirigami | 23 | import org.kde.kirigami 1.0 as Kirigami |
24 | 24 | ||
25 | import org.kube.framework.domain 1.0 as KubeFramework | 25 | import org.kube.framework.domain 1.0 as KubeFramework |
26 | import org.kube.framework.actions 1.0 as KubeAction | ||
27 | 26 | ||
28 | Controls2.Popup { | 27 | Controls2.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 |
95 | A 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. | 95 | A 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 | ||
97 | An 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. | 97 | An 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. |
100 | It 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. | 100 | It 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, | |||
102 | A simple criteria could be the currently selected account. | 102 | A simple criteria could be the currently selected account. |
103 | 103 | ||
104 | #### Automatic action discovery | 104 | #### Automatic action discovery |
105 | While 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. | 105 | While 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 |
108 | Actions 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. | 108 | Actions 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 | ||
10 | add_library(actionplugin SHARED ${SRCS}) | 10 | add_library(actionplugin SHARED ${SRCS}) |
11 | 11 | ||
12 | target_link_libraries(actionplugin KF5::Async) | 12 | target_link_libraries(actionplugin KF5::Async sink) |
13 | qt5_use_modules(actionplugin Core Quick Qml) | 13 | qt5_use_modules(actionplugin Core Quick Qml) |
14 | 14 | ||
15 | install(TARGETS actionplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) | 15 | install(TARGETS actionplugin DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) |
16 | install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) | 16 | install(FILES qmldir DESTINATION ${QML_INSTALL_DIR}/org/kube/framework/actions) |
17 | |||
18 | add_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 | |||
98 | void 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 | ||
40 | Q_SIGNALS: | 41 | Q_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 | ||
34 | ActionHandler::~ActionHandler() | ||
35 | { | ||
36 | ActionBroker::instance().unregisterHandler(mActionId, this); | ||
37 | } | ||
38 | |||
34 | bool ActionHandler::isActionReady(Context *context) | 39 | bool ActionHandler::isActionReady(Context *context) |
35 | { | 40 | { |
36 | if (context) { | 41 | if (context) { |
@@ -67,6 +72,8 @@ ActionResult ActionHandler::execute(Context *context) | |||
67 | 72 | ||
68 | void ActionHandler::setActionId(const QByteArray &actionId) | 73 | void 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 | ||
86 | void ActionHandler::setRequiredProperties(const QSet<QByteArray> &requiredProperties) | ||
87 | { | ||
88 | mRequiredProperties = requiredProperties; | ||
89 | } | ||
90 | |||
91 | QSet<QByteArray> ActionHandler::requiredProperties() const | ||
92 | { | ||
93 | return mRequiredProperties; | ||
94 | } | ||
95 | |||
79 | 96 | ||
80 | ActionHandlerHelper::ActionHandlerHelper(const Handler &handler) | 97 | ActionHandlerHelper::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 | ||
28 | namespace Kube { | 29 | namespace Kube { |
29 | class Context; | ||
30 | 30 | ||
31 | class ActionHandler : public QObject | 31 | class ActionHandler : public QObject |
32 | { | 32 | { |
@@ -35,6 +35,7 @@ class ActionHandler : public QObject | |||
35 | 35 | ||
36 | public: | 36 | public: |
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 | |||
46 | private: | 50 | private: |
47 | QByteArray mActionId; | 51 | QByteArray mActionId; |
52 | QSet<QByteArray> mRequiredProperties; | ||
53 | }; | ||
54 | |||
55 | template <typename ContextType> | ||
56 | class ActionHandlerBase : public ActionHandler | ||
57 | { | ||
58 | public: | ||
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 | } | ||
86 | protected: | ||
87 | |||
88 | virtual bool isActionReady(ContextType &) { return true; } | ||
89 | virtual KAsync::Job<void> execute(ContextType &) = 0; | ||
48 | }; | 90 | }; |
49 | 91 | ||
50 | class ActionHandlerHelper : public ActionHandler | 92 | class ActionHandlerHelper : public ActionHandler |
51 | { | 93 | { |
52 | Q_OBJECT | ||
53 | public: | 94 | public: |
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; |
65 | private: | 106 | private: |
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 | ||
32 | Context::Context(const Context &other) | ||
33 | : QObject() | ||
34 | { | ||
35 | *this = other; | ||
36 | } | ||
37 | |||
38 | Context &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 | |||
32 | void Context::clear() | 46 | void 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 | ||
58 | QSet<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 | |||
44 | QDebug operator<<(QDebug dbg, const Kube::Context &context) | 72 | QDebug 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 | |||
87 | QDebug 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 | ||
35 | namespace Kube { | 38 | namespace Kube { |
@@ -38,13 +41,27 @@ class Context : public QObject { | |||
38 | Q_OBJECT | 41 | Q_OBJECT |
39 | public: | 42 | public: |
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 | |||
55 | class ContextWrapper { | ||
56 | public: | ||
57 | ContextWrapper(Context &c) : context{c} {} | ||
58 | Context &context; | ||
43 | }; | 59 | }; |
44 | 60 | ||
45 | } | 61 | } |
46 | 62 | ||
47 | QDebug operator<<(QDebug dbg, const Kube::Context &); | 63 | QDebug operator<<(QDebug dbg, const Kube::Context &); |
64 | QDebug operator<<(QDebug dbg, const Kube::ContextWrapper &); | ||
48 | 65 | ||
49 | Q_DECLARE_METATYPE(Kube::Context*); | 66 | Q_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 @@ | |||
1 | include_directories(${CMAKE_CURRENT_BINARY_DIR}) | ||
2 | cmake_policy(SET CMP0063 NEW) | ||
3 | add_executable(actiontest actiontest.cpp) | ||
4 | add_test(actiontest sinkactiontest) | ||
5 | qt5_use_modules(actiontest Core Test) | ||
6 | target_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 | |||
11 | SINK_DEBUG_AREA("actiontest") | ||
12 | |||
13 | class HandlerContext : public Kube::Context { | ||
14 | Q_OBJECT | ||
15 | KUBE_CONTEXT_PROPERTY(QString, Property1, property1) | ||
16 | KUBE_CONTEXT_PROPERTY(QString, Property2, property2) | ||
17 | }; | ||
18 | |||
19 | class 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 | |||
27 | class Handler : public Kube::ActionHandlerBase<HandlerContextWrapper> | ||
28 | { | ||
29 | public: | ||
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 | |||
49 | class 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 | |||
55 | class Context2 : public Kube::ContextWrapper { | ||
56 | using Kube::ContextWrapper::ContextWrapper; | ||
57 | KUBE_CONTEXTWRAPPER_PROPERTY(QByteArray, Property2, property2) | ||
58 | }; | ||
59 | |||
60 | |||
61 | class ActionTest : public QObject | ||
62 | { | ||
63 | Q_OBJECT | ||
64 | private 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 | |||
101 | QTEST_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 | ) |
24 | find_package(KF5 REQUIRED COMPONENTS Package) | 27 | find_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 | ||
81 | class FolderContext : public Kube::ContextWrapper { | ||
82 | using Kube::ContextWrapper::ContextWrapper; | ||
83 | KUBE_CONTEXTWRAPPER_PROPERTY(Sink::ApplicationDomain::Folder::Ptr, Folder, folder) | ||
84 | }; | ||
85 | |||
81 | static ActionHandlerHelper synchronizeHandler("org.kde.kube.actions.synchronize", | 86 | static 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 | ||
113 | static 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 | |||
141 | static 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 | |||
23 | Completer::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 | |||
25 | class Completer : public QObject { | ||
26 | Q_OBJECT | ||
27 | Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT) | ||
28 | Q_PROPERTY (QString searchString WRITE setSearchString READ searchString) | ||
29 | |||
30 | public: | ||
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 | |||
36 | private: | ||
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 | ||
41 | SINK_DEBUG_AREA("composercontroller"); | 38 | SINK_DEBUG_AREA("composercontroller"); |
42 | 39 | ||
43 | Q_DECLARE_METATYPE(KMime::Types::Mailbox) | ||
44 | |||
45 | ComposerController::ComposerController(QObject *parent) : QObject(parent) | ||
46 | { | ||
47 | QQmlEngine::setObjectOwnership(&mContext, QQmlEngine::CppOwnership); | ||
48 | } | ||
49 | |||
50 | |||
51 | Kube::Context* ComposerController::mailContext() | ||
52 | { | ||
53 | return &mContext; | ||
54 | } | ||
55 | |||
56 | class RecipientCompleter : public Completer { | ||
57 | public: | ||
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 | |||
68 | Completer *ComposerController::recipientCompleter() const | ||
69 | { | ||
70 | static auto selector = new RecipientCompleter(); | ||
71 | QQmlEngine::setObjectOwnership(selector, QQmlEngine::CppOwnership); | ||
72 | return selector; | ||
73 | } | ||
74 | |||
75 | class IdentitySelector : public Selector { | 40 | class IdentitySelector : public Selector { |
76 | public: | 41 | public: |
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 | } |
100 | private: | 65 | private: |
101 | ComposerContext &mContext; | 66 | ComposerController &mController; |
102 | }; | 67 | }; |
103 | 68 | ||
69 | class RecipientCompleter : public Completer { | ||
70 | public: | ||
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 | |||
82 | ComposerController::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 | |||
105 | Completer *ComposerController::recipientCompleter() const | ||
106 | { | ||
107 | return mRecipientCompleter.data(); | ||
108 | } | ||
109 | |||
104 | Selector *ComposerController::identitySelector() const | 110 | Selector *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 | ||
111 | void ComposerController::setMessage(const KMime::Message::Ptr &msg) | 115 | void 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 | ||
120 | void ComposerController::loadMessage(const QVariant &message, bool loadAsDraft) | 124 | void 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 | ||
162 | void ComposerController::clear() | 168 | Kube::ControllerAction* ComposerController::saveAsDraftAction() |
169 | { | ||
170 | return mSaveAsDraftAction.data(); | ||
171 | } | ||
172 | |||
173 | Kube::ControllerAction* ComposerController::sendAction() | ||
163 | { | 174 | { |
164 | mContext.clear(); | 175 | return mSendAction.data(); |
165 | } | 176 | } |
166 | 177 | ||
178 | KMime::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 | ||
168 | Kube::ActionHandler *ComposerController::messageHandler() | 205 | void 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 | }, | 211 | void 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 | ||
204 | Kube::Action* ComposerController::saveAsDraftAction() | 242 | void 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 | ||
215 | Kube::Action* ComposerController::sendAction() | 247 | void 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 | ||
33 | namespace KMime { | 33 | inline bool operator !=(const KMime::Types::Mailbox &l, const KMime::Types::Mailbox &r) |
34 | class Message; | 34 | { |
35 | return !(l.prettyAddress() == r.prettyAddress()); | ||
35 | } | 36 | } |
36 | 37 | ||
37 | class ComposerContext : public Kube::Context { | 38 | Q_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 | |||
47 | class 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 | ||
52 | public: | 40 | namespace KMime { |
53 | Completer(QAbstractItemModel *model) : mModel{model} | 41 | class 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 | |||
61 | private: | ||
62 | QAbstractItemModel *mModel = nullptr; | ||
63 | QString mSearchString; | ||
64 | }; | ||
65 | 43 | ||
66 | /** | 44 | class ComposerController : public Kube::Controller |
67 | * Exposes a model and maintains a current index selection. | 45 | { |
68 | */ | ||
69 | class 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 | |||
74 | public: | ||
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 |
91 | private: | 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 | ||
96 | class 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 | ||
107 | public: | 70 | public: |
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 | |||
120 | public slots: | ||
121 | void clear(); | ||
122 | 80 | ||
123 | signals: | 81 | private slots: |
124 | void done(); | 82 | void updateSendAction(); |
83 | void send(); | ||
84 | void updateSaveAsDraftAction(); | ||
85 | void saveAsDraft(); | ||
125 | 86 | ||
126 | private: | 87 | private: |
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 | |||
24 | using namespace Kube; | ||
25 | |||
26 | ControllerAction::ControllerAction() | ||
27 | : QObject() | ||
28 | { | ||
29 | QQmlEngine::setObjectOwnership(this, QQmlEngine::CppOwnership); | ||
30 | } | ||
31 | |||
32 | void ControllerAction::execute() | ||
33 | { | ||
34 | emit triggered(); | ||
35 | } | ||
36 | |||
37 | void 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 | |||
49 | void 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 | |||
38 | namespace Kube { | ||
39 | |||
40 | class ControllerAction : public QObject { | ||
41 | Q_OBJECT | ||
42 | Q_PROPERTY(bool enabled MEMBER mEnabled NOTIFY enabledChanged) | ||
43 | public: | ||
44 | ControllerAction(); | ||
45 | ~ControllerAction() = default; | ||
46 | |||
47 | Q_INVOKABLE void execute(); | ||
48 | void setEnabled(bool enabled) { setProperty("enabled", enabled); } | ||
49 | |||
50 | signals: | ||
51 | void enabledChanged(); | ||
52 | void triggered(); | ||
53 | |||
54 | private: | ||
55 | bool mEnabled = true; | ||
56 | }; | ||
57 | |||
58 | class Controller : public QObject { | ||
59 | Q_OBJECT | ||
60 | public: | ||
61 | Controller() = default; | ||
62 | virtual ~Controller() = default; | ||
63 | |||
64 | public slots: | ||
65 | void clear(); | ||
66 | |||
67 | signals: | ||
68 | void done(); | ||
69 | void error(); | ||
70 | |||
71 | protected: | ||
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 | |||
23 | Selector::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 | */ | ||
27 | class Selector : public QObject { | ||
28 | Q_OBJECT | ||
29 | Q_PROPERTY (int currentIndex READ currentIndex WRITE setCurrentIndex) | ||
30 | Q_PROPERTY (QAbstractItemModel* model READ model CONSTANT) | ||
31 | |||
32 | public: | ||
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; | ||
46 | private: | ||
47 | QAbstractItemModel *mModel = nullptr; | ||
48 | int mCurrentIndex = 0; | ||
49 | }; | ||
50 | |||