summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2018-01-09 09:35:59 +0100
committerChristian Mollekopf <chrigi_1@fastmail.fm>2018-01-10 12:42:13 +0100
commit6d726bb10386b3d7f5481d41b735ec06cb2163ad (patch)
tree4d591b67b54c5a83f9f1d718a4576c8ccf05859b
parent2d9944bd0b5cd1dd202d9dc6318d612e1aca4241 (diff)
downloadkube-6d726bb10386b3d7f5481d41b735ec06cb2163ad.tar.gz
kube-6d726bb10386b3d7f5481d41b735ec06cb2163ad.zip
Install composer/converations/people as separate views and load them
dynamically.
-rw-r--r--CMakeLists.txt1
-rw-r--r--components/kube/qml/Kube.qml228
-rw-r--r--components/kube/qml/LoginView.qml52
-rw-r--r--components/kube/qml/ViewManager.qml84
-rw-r--r--framework/qml/LoginAccount.qml4
-rw-r--r--framework/src/extensionmodel.cpp63
-rw-r--r--framework/src/extensionmodel.h14
-rw-r--r--views/CMakeLists.txt11
-rw-r--r--views/accounts/metadata.json4
-rw-r--r--views/accounts/qml/View.qml (renamed from components/kube/qml/AccountsView.qml)0
-rw-r--r--views/accounts/tests/tst_accountsview.qml (renamed from components/kube/tests/tst_accountsview.qml)2
-rw-r--r--views/composer/metadata.json4
-rw-r--r--views/composer/qml/AddresseeListEditor.qml (renamed from components/kube/qml/AddresseeListEditor.qml)0
-rw-r--r--views/composer/qml/View.qml (renamed from components/kube/qml/ComposerView.qml)0
-rw-r--r--views/composer/tests/tst_composerview.qml (renamed from components/kube/tests/tst_composerview.qml)2
-rw-r--r--views/conversation/metadata.json4
-rw-r--r--views/conversation/qml/View.qml (renamed from components/kube/qml/MailView.qml)0
-rw-r--r--views/conversation/tests/tst_conversationview.qml (renamed from components/kube/tests/tst_mailview.qml)2
-rw-r--r--views/log/metadata.json4
-rw-r--r--views/log/qml/View.qml (renamed from components/kube/qml/LogView.qml)0
-rw-r--r--views/log/tests/tst_logview.qml (renamed from components/kube/tests/tst_logview.qml)2
-rw-r--r--views/people/metadata.json4
-rw-r--r--views/people/qml/View.qml (renamed from components/kube/qml/PeopleView.qml)0
-rw-r--r--views/people/tests/tst_peopleview.qml (renamed from components/kube/tests/tst_peopleview.qml)2
24 files changed, 254 insertions, 233 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7933a9e6..48120389 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,5 +36,6 @@ add_subdirectory(framework)
36add_subdirectory(components) 36add_subdirectory(components)
37add_subdirectory(icons) 37add_subdirectory(icons)
38add_subdirectory(applications) 38add_subdirectory(applications)
39add_subdirectory(views)
39add_subdirectory(accounts) 40add_subdirectory(accounts)
40add_subdirectory(tests) 41add_subdirectory(tests)
diff --git a/components/kube/qml/Kube.qml b/components/kube/qml/Kube.qml
index 9a31ba58..be3f7e34 100644
--- a/components/kube/qml/Kube.qml
+++ b/components/kube/qml/Kube.qml
@@ -86,35 +86,6 @@ Controls2.ApplicationWindow {
86 } 86 }
87 } 87 }
88 88
89 Kube.Listener {
90 filter: Kube.Messages.reply
91 onMessageReceived: {
92 kubeViews.openComposerWithMail(message.mail, false)
93 }
94 }
95
96 Kube.Listener {
97 filter: Kube.Messages.edit
98 onMessageReceived: {
99 kubeViews.openComposerWithMail(message.mail, true)
100 }
101 }
102
103 Kube.Listener {
104 filter: Kube.Messages.compose
105 onMessageReceived: kubeViews.openComposer(true, message.recipients)
106 }
107
108 Kube.Listener {
109 filter: Kube.Messages.requestLogin
110 onMessageReceived: kubeViews.setLoginView(message.accountId)
111 }
112
113 Kube.Listener {
114 filter: Kube.Messages.requestAccountsConfiguration
115 onMessageReceived: kubeViews.setAccountsView()
116 }
117
118 //BEGIN Shortcuts 89 //BEGIN Shortcuts
119 Shortcut { 90 Shortcut {
120 sequence: StandardKey.Quit 91 sequence: StandardKey.Quit
@@ -174,48 +145,20 @@ Controls2.ApplicationWindow {
174 145
175 spacing: Kube.Units.largeSpacing - Kube.Units.smallSpacing 146 spacing: Kube.Units.largeSpacing - Kube.Units.smallSpacing
176 147
177 Kube.IconButton {
178 id: composerButton
179 iconName: Kube.Icons.edit_inverted
180 onClicked: kubeViews.openComposer(false, [])
181 activeFocusOnTab: true
182 checkable: true
183 Controls2.ButtonGroup.group: viewButtonGroup
184 tooltip: qsTr("composer")
185 }
186
187 Kube.IconButton {
188 id: mailButton
189 iconName: Kube.Icons.mail_inverted
190 onClicked: kubeViews.setMailView()
191 activeFocusOnTab: true
192 checkable: true
193 checked: true
194 Controls2.ButtonGroup.group: viewButtonGroup
195 tooltip: qsTr("mails")
196 }
197
198 Kube.IconButton {
199 id: peopleButton
200 iconName: Kube.Icons.user_inverted
201 onClicked: kubeViews.setPeopleView()
202 activeFocusOnTab: true
203 checkable: true
204 Controls2.ButtonGroup.group: viewButtonGroup
205 tooltip: qsTr("people")
206 }
207 Repeater { 148 Repeater {
208 model: Kube.ExtensionModel {} 149 model: Kube.ExtensionModel {
150 id: extensionModel
151 sortOrder: ["composer", "conversation", "people"]
152 }
209 Kube.IconButton { 153 Kube.IconButton {
154 id: button
210 iconName: model.icon 155 iconName: model.icon
211 onClicked: { 156 onClicked: kubeViews.showView(model.name)
212 var component = Qt.createComponent(model.source)
213 kubeViews.pushView(component, {})
214 }
215 activeFocusOnTab: true 157 activeFocusOnTab: true
216 checkable: true 158 checkable: true
217 Controls2.ButtonGroup.group: viewButtonGroup 159 Controls2.ButtonGroup.group: viewButtonGroup
218 tooltip: model.tooltip 160 tooltip: model.tooltip
161 checked: kubeViews.currentViewName == model.name
219 } 162 }
220 } 163 }
221 } 164 }
@@ -240,10 +183,11 @@ Controls2.ApplicationWindow {
240 Kube.IconButton { 183 Kube.IconButton {
241 id: logButton 184 id: logButton
242 iconName: Kube.Icons.info_inverted 185 iconName: Kube.Icons.info_inverted
243 onClicked: kubeViews.setLogView() 186 onClicked: kubeViews.showView("log")
244 activeFocusOnTab: true 187 activeFocusOnTab: true
245 checkable: true 188 checkable: true
246 alert: logView.pendingError 189 alert: kubeViews.getView("log").pendingError
190 checked: kubeViews.currentViewName == "log"
247 Controls2.ButtonGroup.group: viewButtonGroup 191 Controls2.ButtonGroup.group: viewButtonGroup
248 tooltip: qsTr("logview") 192 tooltip: qsTr("logview")
249 } 193 }
@@ -251,141 +195,95 @@ Controls2.ApplicationWindow {
251 Kube.IconButton { 195 Kube.IconButton {
252 id: accountsButton 196 id: accountsButton
253 iconName: Kube.Icons.menu_inverted 197 iconName: Kube.Icons.menu_inverted
254 onClicked: kubeViews.setAccountsView() 198 onClicked: kubeViews.showView("accounts")
255 activeFocusOnTab: true 199 activeFocusOnTab: true
256 checkable: true 200 checkable: true
201 checked: kubeViews.currentViewName == "accounts"
257 Controls2.ButtonGroup.group: viewButtonGroup 202 Controls2.ButtonGroup.group: viewButtonGroup
258 tooltip: qsTr("settings") 203 tooltip: qsTr("settings")
259 } 204 }
260 } 205 }
261 } 206 }
262 Controls2.StackView { 207 ViewManager {
263 id: kubeViews 208 id: kubeViews
264
265 anchors { 209 anchors {
266 top: mainContent.top 210 top: mainContent.top
267 bottom: mainContent.bottom 211 bottom: mainContent.bottom
268 } 212 }
269 Layout.fillWidth: true 213 Layout.fillWidth: true
270 214
271 Kube.Listener { 215 extensionModel: extensionModel
272 filter: Kube.Messages.componentDone
273 onMessageReceived: {
274 //Return to the mailview if we try to pop everything off
275 if (kubeViews.depth == 1) {
276 kubeViews.setMailView()
277 } else {
278 kubeViews.pop(Controls2.StackView.Immediate)
279 }
280 }
281 }
282
283 onCurrentItemChanged: {
284 if (currentItem) {
285 currentItem.forceActiveFocus()
286 }
287 }
288 216
289 Component.onCompleted: { 217 Component.onCompleted: {
290 //Setup the initial item stack 218 dontFocus = true
291 if (!currentItem) { 219 showView("conversation")
292 setMailView() 220 if (startupCheck.noAccount) {
293 if (startupCheck.noAccount) { 221 showView("accounts")
294 setAccountsView()
295 }
296 } 222 }
223 dontFocus = false
297 } 224 }
298 225
299 ///Replace the current view (we can't go back to the old view, and we destroy the old view) 226 Kube.Listener {
300 function replaceView(view) { 227 filter: Kube.Messages.reply
301 if (currentItem != view) { 228 onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadAsDraft: false})
302 kubeViews.replace(null, view, {}, Controls2.StackView.Immediate)
303 }
304 }
305
306 ///Push a new view on the stack (the old view remains, and we can go back once done)
307 function pushView(view, properties) {
308 kubeViews.push(view, properties, Controls2.StackView.Immediate)
309 }
310
311 //TODO replacing here while a composer is open is destructive
312 function setPeopleView() {
313 replaceView(peopleView)
314 } 229 }
315 230
316 function setMailView() { 231 Kube.Listener {
317 replaceView(mailView) 232 filter: Kube.Messages.edit
233 onMessageReceived: kubeViews.replaceView("composer", {message: message.mail, loadAsDraft: true})
318 } 234 }
319 235
320 function setAccountsView() { 236 Kube.Listener {
321 pushView(accountsView, {}) 237 filter: Kube.Messages.compose
238 onMessageReceived: kubeViews.replaceView("composer", {newMessage: true, recipients: message.recipients})
322 } 239 }
323 240
324 function setLogView() { 241 Kube.Listener {
325 replaceView(logView) 242 filter: Kube.Messages.requestAccountsConfiguration
243 onMessageReceived: kubeViews.showView("accounts")
326 } 244 }
327 245
328 function setLoginView(account) { 246 Kube.Listener {
329 if (currentItem != loginView) { 247 filter: Kube.Messages.componentDone
330 pushView(loginView, {accountId: account}) 248 onMessageReceived: {
249 kubeViews.closeView()
331 } 250 }
332 } 251 }
333 252
334 function openComposer(newMessage, recipients) { 253 Kube.Listener {
335 pushView(composerView, {newMessage: newMessage, recipients: recipients}) 254 filter: Kube.Messages.requestLogin
336 } 255 onMessageReceived: {
337 256 loginView.createObject(kubeViews, {accountId: message.accountId})
338 function openComposerWithMail(mail, openAsDraft) {
339 pushView(composerView, {message: mail, loadAsDraft: openAsDraft})
340 }
341
342
343 //These items are not visible until pushed onto the stack, so we keep them in resources instead of items
344 resources: [
345 //Not components so we maintain state
346 MailView {
347 id: mailView
348 anchors.fill: parent
349 Controls2.StackView.onActivated: mailButton.checked = true
350 Controls2.StackView.onDeactivated: mailButton.checked = false
351 },
352 PeopleView {
353 id: peopleView
354 anchors.fill: parent
355 Controls2.StackView.onActivated: peopleButton.checked = true
356 Controls2.StackView.onDeactivated: peopleButton.checked = false
357 },
358 //Not a component because otherwise we can't log stuff
359 LogView {
360 id: logView
361 anchors.fill: parent
362 Controls2.StackView.onActivated: logButton.checked = true
363 Controls2.StackView.onDeactivated: logButton.checked = false
364 }
365 ]
366 //A component so it's always destroyed when we're done
367 Component {
368 id: composerView
369 ComposerView {
370 anchors.fill: parent
371 Controls2.StackView.onActivated: composerButton.checked = true
372 Controls2.StackView.onDeactivated: composerButton.checked = false
373 }
374 }
375 Component {
376 id: accountsView
377 AccountsView {
378 anchors.fill: parent
379 Controls2.StackView.onActivated: accountsButton.checked = true
380 Controls2.StackView.onDeactivated: accountsButton.checked = false
381 } 257 }
382 } 258 }
259
383 Component { 260 Component {
384 id: loginView 261 id: loginView
385 LoginView { 262 Kube.Popup {
386 anchors.fill: parent 263 id: popup
264 property alias accountId: login.accountId
265 visible: true
266 parent: Controls2.ApplicationWindow.overlay
267 height: app.height
268 width: app.width - app.sidebarWidth
269 x: app.sidebarWidth
270 y: 0
271 modal: true
272 closePolicy: Controls2.Popup.NoAutoClose
273 Kube.LoginAccount {
274 id: login
275 anchors {
276 fill: parent
277 bottomMargin: Kube.Units.largeSpacing
278 }
279 onDone: {
280 popup.close()
281 kubeViews.currentItem.forceActiveFocus()
282 }
283 }
387 } 284 }
388 } 285 }
286
389 } 287 }
390 } 288 }
391 //END Main content 289 //END Main content
diff --git a/components/kube/qml/LoginView.qml b/components/kube/qml/LoginView.qml
deleted file mode 100644
index dbbed11c..00000000
--- a/components/kube/qml/LoginView.qml
+++ /dev/null
@@ -1,52 +0,0 @@
1/*
2 * Copyright (C) 2017 Michael Bohlender, <michael.bohlender@kdemail.net>
3 * Copyright (C) 2017 Christian Mollekopf, <mollekopf@kolabsys.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20import QtQuick 2.4
21import QtQuick.Layouts 1.1
22import QtQuick.Controls 2.0
23import org.kube.framework 1.0 as Kube
24
25FocusScope {
26 id: root
27 property alias accountId: login.accountId
28 onActiveFocusChanged: {
29 if (activeFocus) {
30 popup.forceActiveFocus()
31 }
32 }
33
34 Kube.Popup {
35 id: popup
36 visible: true
37 parent: ApplicationWindow.overlay
38 height: app.height
39 width: app.width - app.sidebarWidth
40 x: app.sidebarWidth
41 y: 0
42 modal: true
43 closePolicy: Popup.NoAutoClose
44 Kube.LoginAccount {
45 id: login
46 anchors {
47 fill: parent
48 bottomMargin: Kube.Units.largeSpacing
49 }
50 }
51 }
52}
diff --git a/components/kube/qml/ViewManager.qml b/components/kube/qml/ViewManager.qml
new file mode 100644
index 00000000..6874107a
--- /dev/null
+++ b/components/kube/qml/ViewManager.qml
@@ -0,0 +1,84 @@
1/*
2 * Copyright (C) 2018 Christian Mollekopf, <mollekopf@kolabsys.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19
20import QtQuick 2.7
21import QtQuick.Controls 2.0
22/*
23* TODO
24* * Only replace composer if necessary (on reply, edit draft, ...)
25* * Shutdown procedure to save draft before destruction
26* * Separate logging from view and and make accessible to log (initialize()) call?
27*/
28StackView {
29 id: root
30 property string currentViewName: currentItem ? currentItem.objectName : ""
31 property variant extensionModel: null
32 property bool dontFocus: false
33
34 property var viewDict: new Object
35 function getView(name, replaceView) {
36 if (!replaceView && name in viewDict) {
37 var item = viewDict[name]
38 if (item) {
39 return item
40 }
41 }
42 var v = Qt.createComponent(extensionModel.findSource(name, "View.qml"))
43 v = v.createObject(root)
44 viewDict[name] = v
45 return v;
46 }
47
48 onCurrentItemChanged: {
49 if (currentItem && !dontFocus) {
50 currentItem.forceActiveFocus()
51 }
52 }
53
54 function showOrReplaceView(name, properties, replace) {
55 if (currentItem && currentItem.objectName == name) {
56 return
57 }
58 if (root.depth > 0) {
59 root.pop(StackView.Immediate)
60 }
61 var view = getView(name, replace)
62 var item = push(view, properties, StackView.Immediate)
63 item.parent = root
64 item.anchors.fill = root
65 item.objectName = name
66 }
67
68 function showView(name, properties) {
69 showOrReplaceView(name, properties, false)
70 }
71
72 function replaceView(name, properties) {
73 showOrReplaceView(name, properties, true)
74 }
75
76 function closeView() {
77 //The initial view remains
78 if (kubeViews.depth > 1) {
79 var item = kubeViews.pop(StackView.Immediate)
80 viewDict[item.objectName] = null
81 item.destroy()
82 }
83 }
84}
diff --git a/framework/qml/LoginAccount.qml b/framework/qml/LoginAccount.qml
index f02050ae..261746d5 100644
--- a/framework/qml/LoginAccount.qml
+++ b/framework/qml/LoginAccount.qml
@@ -26,10 +26,12 @@ Item {
26 property string accountId 26 property string accountId
27 property bool canRemove: true 27 property bool canRemove: true
28 28
29 signal done()
30
29 function login() { 31 function login() {
30 loader.item.login() 32 loader.item.login()
31 Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": loader.item.accountIdentifier}); 33 Kube.Fabric.postMessage(Kube.Messages.synchronize, {"accountId": loader.item.accountIdentifier});
32 Kube.Fabric.postMessage(Kube.Messages.componentDone, {}) 34 root.done()
33 } 35 }
34 36
35 Kube.AccountFactory { 37 Kube.AccountFactory {
diff --git a/framework/src/extensionmodel.cpp b/framework/src/extensionmodel.cpp
index f64a0e17..e3fab7d8 100644
--- a/framework/src/extensionmodel.cpp
+++ b/framework/src/extensionmodel.cpp
@@ -22,12 +22,17 @@
22#include <QDir> 22#include <QDir>
23#include <QDebug> 23#include <QDebug>
24#include <QTimer> 24#include <QTimer>
25#include <QJsonDocument>
26#include <QJsonObject>
25 27
26using namespace Kube; 28using namespace Kube;
27 29
28ExtensionModel::ExtensionModel(QObject *parent) 30ExtensionModel::ExtensionModel(QObject *parent)
29 : QSortFilterProxyModel(parent) 31 : QSortFilterProxyModel(parent)
30{ 32{
33 setDynamicSortFilter(true);
34 sort(0, Qt::DescendingOrder);
35 setFilterCaseSensitivity(Qt::CaseInsensitive);
31 QTimer::singleShot(0, this, &ExtensionModel::load); 36 QTimer::singleShot(0, this, &ExtensionModel::load);
32} 37}
33 38
@@ -36,8 +41,7 @@ QHash<int, QByteArray> ExtensionModel::roleNames() const
36 return { 41 return {
37 {Name, "name"}, 42 {Name, "name"},
38 {Tooltip, "tooltip"}, 43 {Tooltip, "tooltip"},
39 {Icon, "icon"}, 44 {Icon, "icon"}
40 {Source, "source"}
41 }; 45 };
42} 46}
43 47
@@ -48,21 +52,55 @@ void ExtensionModel::load()
48 auto engine = qmlEngine(this); 52 auto engine = qmlEngine(this);
49 Q_ASSERT(engine); 53 Q_ASSERT(engine);
50 for (const auto &path : engine->importPathList()) { 54 for (const auto &path : engine->importPathList()) {
51 QDir dir{path + "/org/kube/viewextensions"}; 55 QDir dir{path + "/org/kube/views"};
52 for (const auto &pluginName : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { 56 for (const auto &pluginName : dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) {
53 auto viewPath = dir.path() + pluginName + "/View.qml"; 57 const auto pluginPath = dir.path() + "/" + pluginName;
54 qWarning() << "Plugin path: " << dir.path() + pluginName + "/View.qml"; 58 mPaths.insert(pluginName, pluginPath);
55 auto item = new QStandardItem; 59 auto item = new QStandardItem;
56 item->setData(viewPath, Source);
57 item->setData(pluginName, Name); 60 item->setData(pluginName, Name);
58 item->setData(pluginName, Tooltip); 61 item->setData(pluginName, Tooltip);
59 item->setData("document-decrypt", Icon); 62 item->setData("kdocumentinfo-inverted", Icon);
63
64 if (QFileInfo::exists(pluginPath + "/metadata.json")) {
65 QFile file{pluginPath + "/metadata.json"};
66 file.open(QIODevice::ReadOnly);
67 auto json = QJsonDocument::fromJson(file.readAll());
68 auto map = json.object().toVariantMap();
69 item->setData(map.value("icon").toString(), Icon);
70 item->setData(map.value("tooltip").toString(), Tooltip);
71 if (map.value("hidden", false).toBool()) {
72 delete item;
73 continue;
74 }
75 }
76
60 model->appendRow(item); 77 model->appendRow(item);
61 } 78 }
62 } 79 }
63 setSourceModel(model); 80 setSourceModel(model);
64} 81}
65 82
83QString ExtensionModel::findSource(const QString &extensionName, const QString &sourceName)
84{
85 if (mPaths.isEmpty()) {
86 load();
87 }
88 return mPaths.value(extensionName) + "/" + sourceName;
89}
90
91void ExtensionModel::setSortOrder(const QVariantList &order)
92{
93 mSortOrder.clear();
94 for (const auto &e : order) {
95 mSortOrder << e.toString();
96 }
97}
98
99QVariantList ExtensionModel::sortOrder() const
100{
101 return {};
102}
103
66QVariant ExtensionModel::data(const QModelIndex &idx, int role) const 104QVariant ExtensionModel::data(const QModelIndex &idx, int role) const
67{ 105{
68 return QSortFilterProxyModel::data(idx, role); 106 return QSortFilterProxyModel::data(idx, role);
@@ -70,5 +108,14 @@ QVariant ExtensionModel::data(const QModelIndex &idx, int role) const
70 108
71bool ExtensionModel::lessThan(const QModelIndex &left, const QModelIndex &right) const 109bool ExtensionModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
72{ 110{
73 return QSortFilterProxyModel::lessThan(left, right); 111 auto leftIndex = mSortOrder.indexOf(left.data(Name).toString());
112 auto rightIndex = mSortOrder.indexOf(right.data(Name).toString());
113 if (leftIndex >= 0 && rightIndex >= 0) {
114 //Higher index is less than
115 return leftIndex > rightIndex;
116 }
117 if (leftIndex < 0 && rightIndex < 0) {
118 return QSortFilterProxyModel::lessThan(left, right);
119 }
120 return leftIndex < rightIndex;
74} 121}
diff --git a/framework/src/extensionmodel.h b/framework/src/extensionmodel.h
index 6e984005..8e5580f7 100644
--- a/framework/src/extensionmodel.h
+++ b/framework/src/extensionmodel.h
@@ -27,6 +27,8 @@ namespace Kube {
27class ExtensionModel : public QSortFilterProxyModel 27class ExtensionModel : public QSortFilterProxyModel
28{ 28{
29 Q_OBJECT 29 Q_OBJECT
30
31 Q_PROPERTY(QVariantList sortOrder WRITE setSortOrder READ sortOrder CONSTANT)
30public: 32public:
31 33
32 ExtensionModel(QObject *parent = Q_NULLPTR); 34 ExtensionModel(QObject *parent = Q_NULLPTR);
@@ -39,14 +41,22 @@ public:
39 enum Roles { 41 enum Roles {
40 Name = Qt::UserRole + 1, 42 Name = Qt::UserRole + 1,
41 Tooltip, 43 Tooltip,
42 Icon, 44 Icon
43 Source
44 }; 45 };
45 46
46 QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE; 47 QHash<int, QByteArray> roleNames() const Q_DECL_OVERRIDE;
47 48
49 void setSortOrder(const QVariantList &order);
50 QVariantList sortOrder() const;
51
52 Q_INVOKABLE QString findSource(const QString &extensionName, const QString &sourceName);
53
48private slots: 54private slots:
49 void load(); 55 void load();
56
57private:
58 QStringList mSortOrder;
59 QHash<QString, QString> mPaths;
50}; 60};
51 61
52} 62}
diff --git a/views/CMakeLists.txt b/views/CMakeLists.txt
new file mode 100644
index 00000000..4004e310
--- /dev/null
+++ b/views/CMakeLists.txt
@@ -0,0 +1,11 @@
1macro(install_view name)
2 install(DIRECTORY ${name}/qml/ DESTINATION ${QML_INSTALL_DIR}/org/kube/views/${name})
3 install(FILES ${name}/metadata.json DESTINATION ${QML_INSTALL_DIR}/org/kube/views/${name})
4 add_test(NAME viewtest-${name} COMMAND kubetestrunner WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${name}/tests)
5endmacro()
6
7install_view(composer)
8install_view(conversation)
9install_view(people)
10install_view(log)
11install_view(accounts)
diff --git a/views/accounts/metadata.json b/views/accounts/metadata.json
new file mode 100644
index 00000000..55091f97
--- /dev/null
+++ b/views/accounts/metadata.json
@@ -0,0 +1,4 @@
1{
2 "tooltip": "Account configuration.",
3 "hidden": true
4}
diff --git a/components/kube/qml/AccountsView.qml b/views/accounts/qml/View.qml
index 9b774907..9b774907 100644
--- a/components/kube/qml/AccountsView.qml
+++ b/views/accounts/qml/View.qml
diff --git a/components/kube/tests/tst_accountsview.qml b/views/accounts/tests/tst_accountsview.qml
index 428bb4cc..096ca9eb 100644
--- a/components/kube/tests/tst_accountsview.qml
+++ b/views/accounts/tests/tst_accountsview.qml
@@ -27,7 +27,7 @@ TestCase {
27 height: 400 27 height: 400
28 name: "AccountsView" 28 name: "AccountsView"
29 29
30 AccountsView { 30 View {
31 id: accountsView 31 id: accountsView
32 } 32 }
33 33
diff --git a/views/composer/metadata.json b/views/composer/metadata.json
new file mode 100644
index 00000000..c4654fba
--- /dev/null
+++ b/views/composer/metadata.json
@@ -0,0 +1,4 @@
1{
2 "icon": "document-edit-inverted",
3 "tooltip": "Compose new messages."
4}
diff --git a/components/kube/qml/AddresseeListEditor.qml b/views/composer/qml/AddresseeListEditor.qml
index 8f9862e7..8f9862e7 100644
--- a/components/kube/qml/AddresseeListEditor.qml
+++ b/views/composer/qml/AddresseeListEditor.qml
diff --git a/components/kube/qml/ComposerView.qml b/views/composer/qml/View.qml
index 1eb88bb4..1eb88bb4 100644
--- a/components/kube/qml/ComposerView.qml
+++ b/views/composer/qml/View.qml
diff --git a/components/kube/tests/tst_composerview.qml b/views/composer/tests/tst_composerview.qml
index 67db6ef6..eac391c1 100644
--- a/components/kube/tests/tst_composerview.qml
+++ b/views/composer/tests/tst_composerview.qml
@@ -32,7 +32,7 @@ TestCase {
32 32
33 Component { 33 Component {
34 id:composerComponent 34 id:composerComponent
35 ComposerView { 35 View {
36 focus: true 36 focus: true
37 } 37 }
38 } 38 }
diff --git a/views/conversation/metadata.json b/views/conversation/metadata.json
new file mode 100644
index 00000000..870ff2aa
--- /dev/null
+++ b/views/conversation/metadata.json
@@ -0,0 +1,4 @@
1{
2 "icon": "mail-message-inverted",
3 "tooltip": "Follow conversations."
4}
diff --git a/components/kube/qml/MailView.qml b/views/conversation/qml/View.qml
index 8b2b0caf..8b2b0caf 100644
--- a/components/kube/qml/MailView.qml
+++ b/views/conversation/qml/View.qml
diff --git a/components/kube/tests/tst_mailview.qml b/views/conversation/tests/tst_conversationview.qml
index f31d574d..467c049a 100644
--- a/components/kube/tests/tst_mailview.qml
+++ b/views/conversation/tests/tst_conversationview.qml
@@ -27,7 +27,7 @@ TestCase {
27 height: 400 27 height: 400
28 name: "MailView" 28 name: "MailView"
29 29
30 MailView { 30 View {
31 id: mailView 31 id: mailView
32 } 32 }
33 33
diff --git a/views/log/metadata.json b/views/log/metadata.json
new file mode 100644
index 00000000..1bcba1fb
--- /dev/null
+++ b/views/log/metadata.json
@@ -0,0 +1,4 @@
1{
2 "tooltip": "Log messages.",
3 "hidden": true
4}
diff --git a/components/kube/qml/LogView.qml b/views/log/qml/View.qml
index 4ae1a67c..4ae1a67c 100644
--- a/components/kube/qml/LogView.qml
+++ b/views/log/qml/View.qml
diff --git a/components/kube/tests/tst_logview.qml b/views/log/tests/tst_logview.qml
index d3326db1..a78d71cb 100644
--- a/components/kube/tests/tst_logview.qml
+++ b/views/log/tests/tst_logview.qml
@@ -30,7 +30,7 @@ TestCase {
30 height: 400 30 height: 400
31 name: "LogView" 31 name: "LogView"
32 32
33 LogView { 33 View {
34 id: logView 34 id: logView
35 } 35 }
36 36
diff --git a/views/people/metadata.json b/views/people/metadata.json
new file mode 100644
index 00000000..44296a7e
--- /dev/null
+++ b/views/people/metadata.json
@@ -0,0 +1,4 @@
1{
2 "icon": "im-user-inverted",
3 "tooltip": "Your contacts."
4}
diff --git a/components/kube/qml/PeopleView.qml b/views/people/qml/View.qml
index 3f1b9261..3f1b9261 100644
--- a/components/kube/qml/PeopleView.qml
+++ b/views/people/qml/View.qml
diff --git a/components/kube/tests/tst_peopleview.qml b/views/people/tests/tst_peopleview.qml
index e224b527..263247ad 100644
--- a/components/kube/tests/tst_peopleview.qml
+++ b/views/people/tests/tst_peopleview.qml
@@ -27,7 +27,7 @@ TestCase {
27 height: 400 27 height: 400
28 name: "PeopleView" 28 name: "PeopleView"
29 29
30 PeopleView { 30 View {
31 id: peopleView 31 id: peopleView
32 } 32 }
33 33