Changeset View
Changeset View
Standalone View
Standalone View
kcms/kded/package/contents/ui/main.qml
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright 2020 Kai Uwe Broulik <kde@broulik.de> | ||||
3 | * | ||||
4 | * This program is free software; you can redistribute it and/or | ||||
5 | * modify it under the terms of the GNU General Public License as | ||||
6 | * published by the Free Software Foundation; either version 2 of | ||||
7 | * the License or (at your option) version 3 or any later version | ||||
8 | * accepted by the membership of KDE e.V. (or its successor approved | ||||
9 | * by the membership of KDE e.V.), which shall act as a proxy | ||||
10 | * defined in Section 14 of version 3 of the license. | ||||
11 | * | ||||
12 | * This program is distributed in the hope that it will be useful, | ||||
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
15 | * GNU General Public License for more details. | ||||
16 | * | ||||
17 | * You should have received a copy of the GNU General Public License | ||||
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
19 | */ | ||||
20 | | ||||
21 | import QtQuick 2.6 | ||||
22 | import QtQuick.Layouts 1.1 | ||||
23 | import QtQuick.Controls 2.3 as QtControls | ||||
24 | import org.kde.kirigami 2.10 as Kirigami | ||||
25 | import org.kde.kcm 1.2 as KCM | ||||
26 | import org.kde.private.kcms.style 1.0 as Private | ||||
27 | | ||||
28 | KCM.ScrollViewKCM { | ||||
29 | id: root | ||||
30 | | ||||
31 | KCM.ConfigModule.quickHelp: i18n("<p>This module allows you to have an overview of all plugins of the | ||||
32 | KDE Daemon, also referred to as KDE Services. Generally, there are two types of service:</p> | ||||
33 | <ul><li>Services invoked at startup</li><li>Services called on demand</li></ul> | ||||
34 | <p>The latter are only listed for convenience. The startup services can be started and stopped. | ||||
35 | You can also define whether services should be loaded at startup.</p> | ||||
36 | <p><b>Use this with care: some services are vital for Plasma; do not deactivate services if you | ||||
37 | do not know what you are doing.</b></p>") | ||||
38 | | ||||
39 | // TODO immutable somehow? | ||||
40 | | ||||
41 | Binding { | ||||
42 | target: kcm.filteredModel | ||||
43 | property: "query" | ||||
44 | value: searchField.text | ||||
45 | } | ||||
46 | | ||||
47 | Binding { | ||||
48 | target: kcm.filteredModel | ||||
49 | property: "statusFilter" | ||||
50 | value: filterCombo.model[filterCombo.currentIndex].statusFilter | ||||
51 | } | ||||
52 | | ||||
53 | header: ColumnLayout { | ||||
54 | Kirigami.InlineMessage { | ||||
55 | Layout.fillWidth: true | ||||
56 | text: i18n("The KDE Service Manager (kded5) is currently not running. Make sure it is installed correctly."); | ||||
57 | type: Kirigami.MessageType.Error | ||||
58 | showCloseButton: false | ||||
59 | visible: !kcm.kdedRunning | ||||
60 | } | ||||
61 | | ||||
62 | Kirigami.InlineMessage { | ||||
63 | id: selfDisablingModulesHint | ||||
64 | Layout.fillWidth: true | ||||
65 | text: i18n("Some services disable themselves again when manually started if they are not useful in the current environment.") | ||||
66 | type: Kirigami.MessageType.Information | ||||
67 | showCloseButton: true | ||||
68 | visible: false | ||||
69 | } | ||||
70 | | ||||
71 | Kirigami.InlineMessage { | ||||
72 | id: errorMessage | ||||
73 | Layout.fillWidth: true | ||||
74 | | ||||
75 | type: Kirigami.MessageType.Error | ||||
76 | showCloseButton: true | ||||
77 | visible: false | ||||
78 | | ||||
79 | Connections { | ||||
80 | target: kcm | ||||
81 | onErrorMessage: { | ||||
82 | errorMessage.text = errorString; | ||||
83 | errorMessage.visible = true; | ||||
84 | } | ||||
85 | onShowSelfDisablingModulesHint: { | ||||
86 | selfDisablingModulesHint.visible = true; | ||||
87 | } | ||||
88 | } | ||||
89 | } | ||||
90 | | ||||
91 | RowLayout { | ||||
92 | Layout.fillWidth: true | ||||
93 | | ||||
94 | Kirigami.SearchField { | ||||
95 | id: searchField | ||||
96 | Layout.fillWidth: true | ||||
97 | } | ||||
98 | | ||||
99 | QtControls.ComboBox { | ||||
100 | id: filterCombo | ||||
101 | textRole: "text" | ||||
102 | model: [ | ||||
103 | {text: i18n("All Services"), statusFilter: Private.KCM.UnknownStatus}, | ||||
104 | {text: i18nc("List running services", "Running"), statusFilter: Private.KCM.Running}, | ||||
105 | {text: i18nc("List not running services", "Not Running"), statusFilter: Private.KCM.NotRunning} | ||||
106 | ] | ||||
107 | | ||||
108 | // HACK QQC2 doesn't support icons, so we just tamper with the desktop style ComboBox's background | ||||
109 | // and inject a nice little filter icon. | ||||
110 | Component.onCompleted: { | ||||
111 | if (!background || !background.hasOwnProperty("properties")) { | ||||
112 | // not a KQuickStyleItem | ||||
113 | return; | ||||
114 | } | ||||
115 | | ||||
116 | var props = background.properties || {}; | ||||
117 | | ||||
118 | background.properties = Qt.binding(function() { | ||||
119 | var newProps = props; | ||||
120 | newProps.currentIcon = "view-filter"; | ||||
121 | newProps.iconColor = Kirigami.Theme.textColor; | ||||
122 | return newProps; | ||||
123 | }); | ||||
124 | } | ||||
125 | } | ||||
126 | } | ||||
127 | } | ||||
128 | | ||||
129 | view: ListView { | ||||
130 | id: list | ||||
131 | clip: true | ||||
132 | activeFocusOnTab: true | ||||
133 | | ||||
134 | model: kcm.filteredModel | ||||
135 | | ||||
136 | section.property: "type" | ||||
137 | section.delegate: Kirigami.ListSectionHeader { | ||||
138 | width: list.width | ||||
139 | label: { | ||||
140 | switch (Number(section)) { | ||||
141 | case Private.KCM.AutostartType: return i18n("Startup Services"); | ||||
142 | case Private.KCM.OnDemandType: return i18n("Load-on-Demand Services"); | ||||
143 | } | ||||
144 | } | ||||
145 | } | ||||
146 | | ||||
147 | Component { | ||||
148 | id: listDelegateComponent | ||||
149 | | ||||
150 | Kirigami.AbstractListItem { | ||||
151 | id: delegate | ||||
152 | // FIXME why does the padding logic to dodge the ScrollBars not work here? | ||||
153 | text: model.display | ||||
154 | checkable: model.type !== Private.KCM.OnDemandType | ||||
155 | checked: model.autoloadEnabled === true | ||||
156 | hoverEnabled: checkable | ||||
157 | focusPolicy: Qt.ClickFocus | ||||
158 | Accessible.description: i18n("Toggle automatically loading this service on startup") | ||||
159 | onClicked: { | ||||
160 | if (checkable) { | ||||
161 | model.autoloadEnabled = !model.autoloadEnabled; | ||||
162 | } | ||||
163 | } | ||||
164 | | ||||
165 | Component.onCompleted: { | ||||
166 | // Checkable Kirigami.ListItem has blue background which we don't want | ||||
167 | // as we have a dedicated CheckBox. Still using those properties for accessibility. | ||||
168 | background.color = Qt.binding(function() { | ||||
169 | return delegate.highlighted || (delegate.supportsMouseEvents && delegate.pressed) | ||||
170 | ? delegate.activeBackgroundColor | ||||
171 | : delegate.backgroundColor | ||||
172 | }); | ||||
173 | } | ||||
174 | | ||||
175 | contentItem: RowLayout { | ||||
176 | QtControls.CheckBox { | ||||
177 | id: autoloadCheck | ||||
178 | // Keep focus on the delegate | ||||
179 | focusPolicy: Qt.NoFocus | ||||
180 | checked: delegate.checked | ||||
181 | visible: delegate.checkable | ||||
182 | onToggled: model.autoloadEnabled = !model.autoloadEnabled | ||||
183 | | ||||
184 | QtControls.ToolTip { | ||||
185 | text: delegate.Accessible.description | ||||
186 | } | ||||
187 | } | ||||
188 | | ||||
189 | ColumnLayout { | ||||
190 | Layout.fillWidth: true | ||||
191 | spacing: 0 | ||||
192 | | ||||
193 | QtControls.Label { | ||||
194 | id: displayLabel | ||||
195 | Layout.fillWidth: true | ||||
196 | text: delegate.text | ||||
197 | elide: Text.ElideRight | ||||
198 | textFormat: Text.PlainText | ||||
199 | color: (delegate.highlighted || (delegate.pressed && delegate.supportsMouseEvents)) ? delegate.activeTextColor : delegate.textColor | ||||
200 | } | ||||
201 | | ||||
202 | QtControls.Label { | ||||
203 | Layout.fillWidth: true | ||||
204 | text: model.description | ||||
205 | // FIXME do we have a descriptive label component? | ||||
206 | opacity: delegate.hovered ? 0.8 : 0.6 | ||||
207 | wrapMode: Text.WordWrap | ||||
208 | textFormat: Text.PlainText | ||||
209 | color: displayLabel.color | ||||
210 | } | ||||
211 | } | ||||
212 | | ||||
213 | QtControls.Label { | ||||
214 | id: statusLabel | ||||
215 | transformOrigin: Item.Right | ||||
216 | horizontalAlignment: Text.AlignRight | ||||
217 | opacity: model.status === Private.KCM.Running ? 1 : delegate.hovered ? 0.8 : 0.6 | ||||
218 | color: displayLabel.color | ||||
219 | text: { | ||||
220 | switch (model.status) { | ||||
221 | case Private.KCM.NotRunning: return i18n("Not running"); | ||||
222 | case Private.KCM.Running: return i18n("Running"); | ||||
223 | } | ||||
224 | return i18n("Unknown"); | ||||
225 | } | ||||
226 | | ||||
227 | readonly property int status: model.status | ||||
228 | onStatusChanged: { | ||||
229 | if (Kirigami.Units.longDuration > 1) { | ||||
230 | statusChangedAnimation.restart(); | ||||
231 | } | ||||
232 | } | ||||
233 | | ||||
234 | // FIXME use DelegateRecycler.onReused and stop animation when delegate gets reused | ||||
235 | // Otherwise label blinks as you scroll up and down the list :) | ||||
236 | SequentialAnimation { | ||||
237 | id: statusChangedAnimation | ||||
238 | PropertyAction { | ||||
239 | target: statusLabel | ||||
240 | property: "color" | ||||
241 | value: model.status === Private.KCM.Running ? Kirigami.Theme.positiveTextColor | ||||
242 | : Kirigami.Theme.negativeTextColor | ||||
243 | } | ||||
244 | NumberAnimation { | ||||
245 | target: statusLabel | ||||
246 | property: "scale" | ||||
247 | from: 1.5 | ||||
248 | to: 1 | ||||
249 | duration: Kirigami.Units.longDuration | ||||
250 | easing.type: Easing.InOutQuad | ||||
251 | } | ||||
252 | ColorAnimation { | ||||
253 | target: statusLabel | ||||
254 | property: "color" | ||||
255 | to: displayLabel.color | ||||
256 | duration: 2000 | ||||
257 | easing.type: Easing.InOutQuad | ||||
258 | } | ||||
259 | } | ||||
260 | } | ||||
261 | | ||||
262 | QtControls.Button { | ||||
263 | icon.name: model.status === Private.KCM.Running ? "media-playback-pause" : "media-playback-start" | ||||
264 | visible: model.type !== Private.KCM.OnDemandType | ||||
265 | onClicked: { | ||||
266 | selfDisablingModulesHint.visible = false; | ||||
267 | errorMessage.visible = false; | ||||
268 | | ||||
269 | if (model.status === Private.KCM.Running) { | ||||
270 | kcm.stopModule(model.moduleName); | ||||
271 | } else { | ||||
272 | kcm.startModule(model.moduleName); | ||||
273 | } | ||||
274 | } | ||||
275 | Accessible.name: model.status === Private.KCM.Running ? i18n("Stop Service") : i18n("Start Service") | ||||
276 | | ||||
277 | QtControls.ToolTip { | ||||
278 | text: parent.Accessible.name | ||||
279 | } | ||||
280 | } | ||||
281 | } | ||||
282 | } | ||||
283 | } | ||||
284 | | ||||
285 | delegate: Kirigami.DelegateRecycler { | ||||
286 | width: list.width | ||||
287 | sourceComponent: listDelegateComponent | ||||
288 | } | ||||
289 | } | ||||
290 | } |