Changeset View
Changeset View
Standalone View
Standalone View
applet/contents/ui/ConnectionItem.qml
1 | /* | 1 | /* | ||
---|---|---|---|---|---|
2 | Copyright 2013-2017 Jan Grulich <jgrulich@redhat.com> | 2 | Copyright 2013-2017 Jan Grulich <jgrulich@redhat.com> | ||
3 | Copyright 2020 Nate Graham <nate@kde.org> | ||||
3 | 4 | | |||
4 | This library is free software; you can redistribute it and/or | 5 | This library is free software; you can redistribute it and/or | ||
5 | modify it under the terms of the GNU Lesser General Public | 6 | modify it under the terms of the GNU Lesser General Public | ||
6 | License as published by the Free Software Foundation; either | 7 | License as published by the Free Software Foundation; either | ||
7 | version 2.1 of the License, or (at your option) version 3, or any | 8 | version 2.1 of the License, or (at your option) version 3, or any | ||
8 | later version accepted by the membership of KDE e.V. (or its | 9 | later version accepted by the membership of KDE e.V. (or its | ||
9 | successor approved by the membership of KDE e.V.), which shall | 10 | successor approved by the membership of KDE e.V.), which shall | ||
10 | act as a proxy defined in Section 6 of version 3 of the license. | 11 | act as a proxy defined in Section 6 of version 3 of the license. | ||
11 | 12 | | |||
12 | This library is distributed in the hope that it will be useful, | 13 | This library is distributed in the hope that it will be useful, | ||
13 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
15 | Lesser General Public License for more details. | 16 | Lesser General Public License for more details. | ||
16 | 17 | | |||
17 | You should have received a copy of the GNU Lesser General Public | 18 | You should have received a copy of the GNU Lesser General Public | ||
18 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | 19 | License along with this library. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | 20 | */ | ||
20 | 21 | | |||
21 | import QtQuick 2.2 | 22 | import QtQuick 2.12 | ||
22 | import QtQuick.Layouts 1.2 | 23 | import QtQuick.Layouts 1.2 | ||
23 | import QtQuick.Controls 2.4 as Controls | 24 | import QtQuick.Controls 2.12 | ||
25 | | ||||
24 | import org.kde.kcoreaddons 1.0 as KCoreAddons | 26 | import org.kde.kcoreaddons 1.0 as KCoreAddons | ||
25 | import org.kde.kquickcontrolsaddons 2.0 | 27 | import org.kde.kquickcontrolsaddons 2.0 | ||
26 | import org.kde.plasma.components 2.0 as PlasmaComponents | 28 | | ||
27 | import org.kde.plasma.core 2.0 as PlasmaCore | 29 | import org.kde.plasma.core 2.0 as PlasmaCore | ||
30 | import org.kde.plasma.components 2.0 as PlasmaComponents | ||||
31 | import org.kde.plasma.components 3.0 as PlasmaComponents3 | ||||
28 | import org.kde.plasma.networkmanagement 0.2 as PlasmaNM | 32 | import org.kde.plasma.networkmanagement 0.2 as PlasmaNM | ||
29 | 33 | | |||
30 | ListItem { | 34 | PlasmaComponents3.ExpandableListItem { | ||
31 | id: connectionItem | 35 | id: connectionItem | ||
32 | 36 | | |||
33 | property bool activating: ConnectionState == PlasmaNM.Enums.Activating | 37 | property bool activating: ConnectionState == PlasmaNM.Enums.Activating | ||
34 | property bool deactivated: ConnectionState === PlasmaNM.Enums.Deactivated | 38 | property bool deactivated: ConnectionState === PlasmaNM.Enums.Deactivated | ||
35 | property int baseHeight: Uuid ? connectionNameLabel.implicitHeight + connectionStatusLabel.implicitHeight + units.smallSpacing * 2 | | |||
36 | : stateChangeButton.implicitHeight + units.smallSpacing * 2 | | |||
37 | property bool expanded: visibleDetails || visiblePasswordDialog | | |||
38 | property bool passwordIsStatic: (SecurityType == PlasmaNM.Enums.StaticWep || SecurityType == PlasmaNM.Enums.WpaPsk || | 39 | property bool passwordIsStatic: (SecurityType == PlasmaNM.Enums.StaticWep || SecurityType == PlasmaNM.Enums.WpaPsk || | ||
39 | SecurityType == PlasmaNM.Enums.Wpa2Psk || SecurityType == PlasmaNM.Enums.SAE) | 40 | SecurityType == PlasmaNM.Enums.Wpa2Psk || SecurityType == PlasmaNM.Enums.SAE) | ||
40 | property bool predictableWirelessPassword: !Uuid && Type == PlasmaNM.Enums.Wireless && passwordIsStatic | 41 | property bool predictableWirelessPassword: !Uuid && Type == PlasmaNM.Enums.Wireless && passwordIsStatic | ||
41 | property bool showSpeed: plasmoid.expanded && | 42 | property bool showSpeed: plasmoid.expanded && | ||
42 | ConnectionState == PlasmaNM.Enums.Activated && | 43 | ConnectionState == PlasmaNM.Enums.Activated && | ||
43 | (Type == PlasmaNM.Enums.Wired || | 44 | (Type == PlasmaNM.Enums.Wired || | ||
44 | Type == PlasmaNM.Enums.Wireless || | 45 | Type == PlasmaNM.Enums.Wireless || | ||
45 | Type == PlasmaNM.Enums.Gsm || | 46 | Type == PlasmaNM.Enums.Gsm || | ||
46 | Type == PlasmaNM.Enums.Cdma) | 47 | Type == PlasmaNM.Enums.Cdma) | ||
47 | property bool visibleDetails: false | | |||
48 | property bool visiblePasswordDialog: false | | |||
49 | 48 | | |||
50 | property real rxBytes: 0 | 49 | property real rxBytes: 0 | ||
51 | property real txBytes: 0 | 50 | property real txBytes: 0 | ||
52 | 51 | | |||
53 | height: expanded ? baseHeight + expandableComponentLoader.height + units.smallSpacing * (ConnectionState == PlasmaNM.Enums.Active ? 1 : Uuid ? 2 : 1) | 52 | icon: model.ConnectionIcon | ||
54 | : baseHeight | 53 | title: model.ItemUniqueName | ||
55 | 54 | subtitle: itemText() | |||
56 | ColumnLayout { | 55 | iconUsesPlasmaSVG: true // We want the nice detailed network SVGs from the Plasma theme | ||
57 | id: mainColumn | 56 | isBusy: plasmoid.expanded && model.ConnectionState == PlasmaNM.Enums.Activating | ||
58 | anchors.fill: parent | 57 | isDefault: ConnectionState == PlasmaNM.Enums.Activated | ||
59 | 58 | allowStyledText: true // So we can colorize the up and down arrows | |||
60 | MouseArea { | 59 | defaultActionButtonAction: Action { | ||
61 | Layout.fillWidth: true | | |||
62 | Layout.preferredHeight: mainRow.height | | |||
63 | Layout.alignment: Qt.AlignTop | | |||
64 | acceptedButtons: Qt.LeftButton | Qt.RightButton | | |||
65 | hoverEnabled: true | | |||
66 | | ||||
67 | onEntered: { | | |||
68 | connectionView.currentVisibleButtonIndex = index | | |||
69 | connectionView.currentIndex = index | | |||
70 | } | | |||
71 | | ||||
72 | onExited: { | | |||
73 | connectionView.currentIndex = -1 | | |||
74 | } | | |||
75 | | ||||
76 | onPressed: { | | |||
77 | if (mouse.button & Qt.LeftButton) { | | |||
78 | changeExpanded() | | |||
79 | } | | |||
80 | | ||||
81 | if (mouse.button & Qt.RightButton) { | | |||
82 | contextMenu.visualParent = parent | | |||
83 | contextMenu.prepare(); | | |||
84 | contextMenu.open(mouse.x, mouse.y) | | |||
85 | } | | |||
86 | } | | |||
87 | | ||||
88 | RowLayout { | | |||
89 | id: mainRow | | |||
90 | spacing: units.smallSpacing * 2 | | |||
91 | height: baseHeight | | |||
92 | width: mainColumn.width | | |||
93 | anchors { | | |||
94 | left: parent.left | | |||
95 | right: parent.right | | |||
96 | leftMargin: units.smallSpacing | | |||
97 | // Identical margins around the button. | | |||
98 | rightMargin: Math.round((baseHeight - stateChangeButton.height) / 2) | | |||
99 | } | | |||
100 | | ||||
101 | PlasmaCore.SvgItem { | | |||
102 | id: connectionSvgIcon | | |||
103 | Layout.preferredHeight: Uuid ? units.iconSizes.medium : units.iconSizes.smallMedium | | |||
104 | Layout.preferredWidth: Layout.preferredHeight | | |||
105 | Layout.leftMargin: units.smallSpacing | | |||
106 | elementId: ConnectionIcon | | |||
107 | svg: PlasmaCore.Svg { | | |||
108 | multipleImages: true | | |||
109 | imagePath: "icons/network" | | |||
110 | colorGroup: PlasmaCore.ColorScope.colorGroup | | |||
111 | } | | |||
112 | } | | |||
113 | | ||||
114 | // ColumnLayout with fillWidth in children, creates bind loop for width. | | |||
115 | Column { | | |||
116 | Layout.fillWidth: true | | |||
117 | Layout.preferredHeight: connectionNameLabel.height + (connectionStatusLabel.visible ? connectionStatusLabel.height : 0) | | |||
118 | spacing: 0 | | |||
119 | | ||||
120 | PlasmaComponents.Label { | | |||
121 | id: connectionNameLabel | | |||
122 | width: parent.width | | |||
123 | elide: Text.ElideRight | | |||
124 | height: undefined | | |||
125 | font.weight: ConnectionState == PlasmaNM.Enums.Activated ? Font.DemiBold : Font.Normal | | |||
126 | font.italic: ConnectionState == PlasmaNM.Enums.Activating ? true : false | | |||
127 | text: ItemUniqueName | | |||
128 | textFormat: Text.PlainText | | |||
129 | } | | |||
130 | | ||||
131 | PlasmaComponents.Label { | | |||
132 | id: connectionStatusLabel | | |||
133 | width: parent.width | | |||
134 | elide: Text.ElideRight | | |||
135 | height: undefined | | |||
136 | font.pointSize: theme.smallestFont.pointSize | | |||
137 | opacity: 0.6 | | |||
138 | text: itemText() | | |||
139 | visible: !!Uuid | | |||
140 | } | | |||
141 | } | | |||
142 | | ||||
143 | PlasmaComponents.BusyIndicator { | | |||
144 | id: connectingIndicator | | |||
145 | Layout.preferredHeight: units.iconSizes.medium | | |||
146 | Layout.preferredWidth: Layout.preferredHeight | | |||
147 | running: plasmoid.expanded && !stateChangeButton.visible && ConnectionState == PlasmaNM.Enums.Activating | | |||
148 | visible: running | | |||
149 | opacity: visible | | |||
150 | } | | |||
151 | | ||||
152 | PlasmaComponents.Button { | | |||
153 | id: stateChangeButton | 60 | id: stateChangeButton | ||
154 | opacity: connectionView.currentVisibleButtonIndex == index ? 1 : 0 | 61 | icon.name: model.ConnectionState == PlasmaNM.Enums.Deactivated ? "network-connect" : "network-disconnect" | ||
155 | visible: opacity != 0 | 62 | text: model.ConnectionState == PlasmaNM.Enums.Deactivated ? i18n("Connect") : i18n("Disconnect") | ||
156 | text: (ConnectionState == PlasmaNM.Enums.Deactivated) ? i18n("Connect") : i18n("Disconnect") | 63 | onTriggered: changeState() | ||
157 | | ||||
158 | Behavior on opacity { NumberAnimation { duration: units.shortDuration } } | | |||
159 | | ||||
160 | onClicked: changeState() | | |||
161 | } | | |||
162 | } | | |||
163 | } | | |||
164 | | ||||
165 | Loader { | | |||
166 | id: expandableComponentLoader | | |||
167 | Layout.fillWidth: true | | |||
168 | Layout.alignment: Qt.AlignBottom | | |||
169 | Layout.bottomMargin: units.smallSpacing | | |||
170 | } | | |||
171 | } | 64 | } | ||
172 | 65 | customExpandedViewContent: detailsComponent | |||
173 | PlasmaComponents.Menu { | 66 | contextMenu: PlasmaComponents.Menu { | ||
174 | id: contextMenu | 67 | id: contextMenu | ||
175 | 68 | | |||
176 | property Component showQRComponent: null | 69 | property Component showQRComponent: null | ||
177 | 70 | | |||
178 | function prepare() { | 71 | function prepare() { | ||
179 | showQRMenuItem.visible = false; | 72 | showQRMenuItem.visible = false; | ||
180 | 73 | | |||
181 | if (Uuid && Type === PlasmaNM.Enums.Wireless && passwordIsStatic) { | 74 | if (Uuid && Type === PlasmaNM.Enums.Wireless && passwordIsStatic) { | ||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Line(s) | 170 | Component { | |||
278 | id: passwordDialogComponent | 171 | id: passwordDialogComponent | ||
279 | 172 | | |||
280 | ColumnLayout { | 173 | ColumnLayout { | ||
281 | property alias password: passwordField.text | 174 | property alias password: passwordField.text | ||
282 | property alias passwordInput: passwordField | 175 | property alias passwordInput: passwordField | ||
283 | 176 | | |||
284 | PasswordField { | 177 | PasswordField { | ||
285 | id: passwordField | 178 | id: passwordField | ||
286 | Layout.leftMargin: units.iconSizes.smallMedium + units.smallSpacing * 2 | 179 | Layout.fillWidth: true | ||
287 | Layout.bottomMargin: units.smallSpacing | 180 | Layout.leftMargin: units.iconSizes.smallMedium + units.smallSpacing * 4 | ||
288 | Layout.preferredWidth: units.gridUnit * 15 | 181 | Layout.rightMargin: units.iconSizes.smallMedium + units.smallSpacing * 4 | ||
182 | | ||||
289 | securityType: SecurityType | 183 | securityType: SecurityType | ||
290 | 184 | | |||
291 | onAccepted: { | 185 | onAccepted: { | ||
292 | stateChangeButton.clicked() | 186 | stateChangeButton.trigger() | ||
187 | connectionItem.customExpandedViewContent = detailsComponent | ||||
293 | } | 188 | } | ||
294 | 189 | | |||
295 | onAcceptableInputChanged: { | 190 | onAcceptableInputChanged: { | ||
296 | stateChangeButton.enabled = acceptableInput | 191 | stateChangeButton.enabled = acceptableInput | ||
297 | } | 192 | } | ||
298 | 193 | | |||
299 | Component.onCompleted: { | 194 | Component.onCompleted: { | ||
300 | stateChangeButton.enabled = false | 195 | stateChangeButton.enabled = false | ||
196 | passwordField.forceActiveFocus() | ||||
197 | appletProxyModel.dynamicSortFilter = true | ||||
301 | } | 198 | } | ||
302 | 199 | | |||
303 | Component.onDestruction: { | 200 | Component.onDestruction: { | ||
304 | stateChangeButton.enabled = true | 201 | stateChangeButton.enabled = true | ||
202 | connectionItem.customExpandedViewContent = detailsComponent | ||||
305 | } | 203 | } | ||
306 | } | 204 | } | ||
307 | } | 205 | } | ||
308 | } | 206 | } | ||
309 | 207 | | |||
310 | Timer { | 208 | Timer { | ||
311 | id: timer | 209 | id: timer | ||
312 | repeat: true | 210 | repeat: true | ||
313 | interval: 2000 | 211 | interval: 2000 | ||
314 | running: showSpeed | 212 | running: showSpeed | ||
315 | property real prevRxBytes | 213 | property real prevRxBytes | ||
316 | property real prevTxBytes | 214 | property real prevTxBytes | ||
317 | Component.onCompleted: { | 215 | Component.onCompleted: { | ||
318 | prevRxBytes = RxBytes | 216 | prevRxBytes = RxBytes | ||
319 | prevTxBytes = TxBytes | 217 | prevTxBytes = TxBytes | ||
320 | } | 218 | } | ||
321 | onTriggered: { | 219 | onTriggered: { | ||
322 | rxBytes = (RxBytes - prevRxBytes) * 1000 / interval | 220 | rxBytes = (RxBytes - prevRxBytes) * 1000 / interval | ||
323 | txBytes = (TxBytes - prevTxBytes) * 1000 / interval | 221 | txBytes = (TxBytes - prevTxBytes) * 1000 / interval | ||
324 | prevRxBytes = RxBytes | 222 | prevRxBytes = RxBytes | ||
325 | prevTxBytes = TxBytes | 223 | prevTxBytes = TxBytes | ||
326 | } | 224 | } | ||
327 | } | 225 | } | ||
328 | 226 | | |||
329 | states: [ | | |||
330 | State { | | |||
331 | name: "collapsed" | | |||
332 | when: !(visibleDetails || visiblePasswordDialog) | | |||
333 | StateChangeScript { script: if (expandableComponentLoader.status == Loader.Ready) {expandableComponentLoader.sourceComponent = undefined} } | | |||
334 | }, | | |||
335 | | ||||
336 | State { | | |||
337 | name: "expandedDetails" | | |||
338 | when: visibleDetails | | |||
339 | StateChangeScript { script: createContent() } | | |||
340 | }, | | |||
341 | | ||||
342 | State { | | |||
343 | name: "expandedPasswordDialog" | | |||
344 | when: visiblePasswordDialog | | |||
345 | StateChangeScript { script: createContent() } | | |||
346 | PropertyChanges { target: stateChangeButton; opacity: 1 } | | |||
347 | } | | |||
348 | ] | | |||
349 | | ||||
350 | function createContent() { | | |||
351 | if (visibleDetails) { | | |||
352 | expandableComponentLoader.sourceComponent = detailsComponent | | |||
353 | } else if (visiblePasswordDialog) { | | |||
354 | expandableComponentLoader.sourceComponent = passwordDialogComponent | | |||
355 | expandableComponentLoader.item.passwordInput.forceActiveFocus() | | |||
356 | } | | |||
357 | } | | |||
358 | | ||||
359 | function changeState() { | 227 | function changeState() { | ||
360 | visibleDetails = false | 228 | if (Uuid || !predictableWirelessPassword || passwordDialogComponent.visible) { | ||
361 | if (Uuid || !predictableWirelessPassword || visiblePasswordDialog) { | | |||
362 | if (ConnectionState == PlasmaNM.Enums.Deactivated) { | 229 | if (ConnectionState == PlasmaNM.Enums.Deactivated) { | ||
363 | if (!predictableWirelessPassword && !Uuid) { | 230 | if (!predictableWirelessPassword && !Uuid) { | ||
364 | handler.addAndActivateConnection(DevicePath, SpecificPath) | 231 | handler.addAndActivateConnection(DevicePath, SpecificPath) | ||
365 | } else if (visiblePasswordDialog) { | 232 | } else if (passwordDialogComponent.visible) { | ||
366 | if (expandableComponentLoader.item.password != "") { | 233 | if (connectionItem.customExpandedViewContent.item.password != "") { | ||
367 | handler.addAndActivateConnection(DevicePath, SpecificPath, expandableComponentLoader.item.password) | 234 | handler.addAndActivateConnection(DevicePath, SpecificPath, connectionItem.customExpandedViewContent.item.password) | ||
368 | visiblePasswordDialog = false | 235 | connectionItem.customExpandedViewContent = detailsComponent | ||
236 | connectionItem.collapse() | ||||
369 | } else { | 237 | } else { | ||
370 | connectionItem.clicked() | 238 | connectionItem.expand() | ||
371 | } | 239 | } | ||
372 | } else { | 240 | } else { | ||
373 | handler.activateConnection(ConnectionPath, DevicePath, SpecificPath) | 241 | handler.activateConnection(ConnectionPath, DevicePath, SpecificPath) | ||
374 | } | 242 | } | ||
375 | } else { | 243 | } else { | ||
376 | handler.deactivateConnection(ConnectionPath, DevicePath) | 244 | handler.deactivateConnection(ConnectionPath, DevicePath) | ||
377 | } | 245 | } | ||
378 | } else if (predictableWirelessPassword) { | 246 | } else if (predictableWirelessPassword) { | ||
379 | appletProxyModel.dynamicSortFilter = false | 247 | appletProxyModel.dynamicSortFilter = false | ||
380 | visiblePasswordDialog = true | 248 | connectionItem.customExpandedViewContent = passwordDialogComponent | ||
249 | connectionItem.expand() | ||||
381 | } | 250 | } | ||
382 | } | 251 | } | ||
383 | 252 | | |||
384 | /* This generates the formatted text under the connection name | 253 | /* This generates the formatted text under the connection name | ||
385 | in the popup where the connections can be "Connect"ed and | 254 | in the popup where the connections can be "Connect"ed and | ||
386 | "Disconnect"ed. */ | 255 | "Disconnect"ed. */ | ||
387 | function itemText() { | 256 | function itemText() { | ||
388 | if (ConnectionState == PlasmaNM.Enums.Activating) { | 257 | if (ConnectionState == PlasmaNM.Enums.Activating) { | ||
Show All 21 Lines | 275 | return i18n("Connected, <font color='%1'>⬇</font> %2/s, <font color='%3'>⬆</font> %4/s", | |||
410 | KCoreAddons.Format.formatByteSize(txBytes)) | 279 | KCoreAddons.Format.formatByteSize(txBytes)) | ||
411 | } else { | 280 | } else { | ||
412 | return i18n("Connected") | 281 | return i18n("Connected") | ||
413 | } | 282 | } | ||
414 | } | 283 | } | ||
415 | return "" | 284 | return "" | ||
416 | } | 285 | } | ||
417 | 286 | | |||
418 | function changeExpanded() { | | |||
419 | if (visiblePasswordDialog) { | | |||
420 | appletProxyModel.dynamicSortFilter = true | | |||
421 | visiblePasswordDialog = false | | |||
422 | } else { | | |||
423 | visibleDetails = !visibleDetails | | |||
424 | } | | |||
425 | | ||||
426 | if (visibleDetails || visiblePasswordDialog) { | | |||
427 | ListView.view.currentIndex = index | | |||
428 | } else { | | |||
429 | ListView.view.currentIndex = -1 | | |||
430 | } | | |||
431 | } | | |||
432 | | ||||
433 | onShowSpeedChanged: { | 287 | onShowSpeedChanged: { | ||
434 | connectionModel.setDeviceStatisticsRefreshRateMs(DevicePath, showSpeed ? 2000 : 0) | 288 | connectionModel.setDeviceStatisticsRefreshRateMs(DevicePath, showSpeed ? 2000 : 0) | ||
435 | } | 289 | } | ||
436 | 290 | | |||
437 | onActivatingChanged: { | 291 | onActivatingChanged: { | ||
438 | if (ConnectionState == PlasmaNM.Enums.Activating) { | 292 | if (ConnectionState == PlasmaNM.Enums.Activating) { | ||
439 | ListView.view.positionViewAtBeginning() | 293 | ListView.view.positionViewAtBeginning() | ||
440 | } | 294 | } | ||
Show All 16 Lines |