Warning, /plasma/plasma-firewall/kcm/ui/ViewBase.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2018 Alexis Lopes Zubeta <contact@azubieta.net>
0003 // SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
0004 
0005 import QtQml 2.12
0006 import QtQuick
0007 import QtQuick.Layouts 1.3
0008 import QtQuick.Controls as QQC2
0009 import org.kde.kirigami 2.14 as Kirigami
0010 
0011 import org.kde.kcmutils as KCMUtils
0012 import org.kde.kitemmodels 1.0
0013 import org.kcm.firewall 1.0
0014 
0015 KCMUtils.ScrollViewKCM {
0016     id: root
0017 
0018     property QtObject model
0019     property var columns: []
0020     property alias emptyListText: emptyListLabel.text
0021 
0022     property QtObject currentJob: null
0023 
0024     property var blacklistRuleFactory
0025     property var blacklistColumns: []
0026     property string blacklistRuleSuccessMessage
0027 
0028     property int sortColumn: 0
0029     property int sortOrder: Qt.AscendingOrder
0030 
0031     property alias filterPlaceholderText: searchField.placeholderText
0032     property var filterColumns: []
0033 
0034     property var errorMessage: modelErrorMessage
0035 
0036     KSortFilterProxyModel {
0037         id: proxyModel
0038         sourceModel: root.model
0039         sortColumn: root.sortColumn
0040         sortOrder:  root.sortOrder
0041 
0042         function filterCb(source_row, source_parent) {
0043             const query = searchField.text.toLocaleLowerCase();
0044             const columns = filterColumns;
0045 
0046             const modelType = getModelType();
0047 
0048             for (var i = 0, length = columns.length; i < length; ++i) {
0049                 const idx = sourceModel.index(source_row,  columns[i], source_parent);
0050                 const data = String(sourceModel.data(idx) || "").toLocaleLowerCase();
0051 
0052                 if (data.includes(query)) {
0053                     return true;
0054                 }
0055             }
0056 
0057             return false;
0058         }
0059 
0060         filterRowCallback: searchField.length > 0 && filterColumns.length > 0 ? filterCb : null
0061 
0062         filterColumnCallback: (source_column, source_parent) => {
0063             return columns.map(column => column.column).includes(source_column)
0064         }
0065     }
0066 
0067     function getModelType() {
0068         // can this be done generically? :(
0069         if (root.model instanceof LogListModel) {
0070             return LogListModel;
0071         } else if (root.model instanceof ConnectionsModel) {
0072             return ConnectionsModel;
0073         }
0074         return null;
0075     }
0076 
0077     function blacklistRow(row) {
0078         // FIXME why does TableView does that? :(
0079         // Unfortunately it also casts to 0, so the resulting model index is deemed valid
0080         if (row === undefined) {
0081             return;
0082         }
0083 
0084         console.log("Accessing blacklist row", row);
0085         const idx = proxyModel.index(row, 0);
0086         const columns = blacklistColumns;
0087         const args = columns.map((column) => {
0088             return proxyModel.data(proxyModel.index(row, column));
0089         });
0090 
0091         if (args[0] === undefined) {
0092             console.log("Error, a model refresh happened when you tried to blacklist a connection.");
0093             return;
0094         }
0095 
0096         const rule = blacklistRuleFactory(...args);
0097         const job = kcm.client.addRule(rule);
0098 
0099         currentJob = job;
0100         ruleCreationErrorMessage.visible = false;
0101         console.log(...args);
0102 
0103         job.result.connect(function() {
0104             currentJob = null;
0105 
0106             if (job.error) {
0107                 if (job.error !== 4) { // FIXME magic number
0108                     let indexError = job.errorString.indexOf("ERROR:");
0109                     let errorStrings = job.errorString.substring(indexError);
0110 
0111                     console.log(errorStrings);
0112                     ruleCreationErrorMessage.text = i18n("Error creating rule: %1", errorStrings);
0113                     ruleCreationErrorMessage.visible = true;
0114                 }
0115                 return;
0116             }
0117 
0118             if (blacklistRuleSuccessMessage) {
0119                 kcm.showPassiveNotification(blacklistRuleSuccessMessage);
0120             }
0121         });
0122     }
0123 
0124     header: ColumnLayout {
0125         Kirigami.InlineMessage {
0126             id: modelErrorMessage
0127             Layout.fillWidth: true
0128             type: Kirigami.MessageType.Error
0129             showCloseButton: true
0130 
0131             Connections {
0132                 target: root.model
0133                 function onShowErrorMessage(message) {
0134                     modelErrorMessage.text = message;
0135                     modelErrorMessage.visible = true;
0136                 }
0137             }
0138         }
0139 
0140         Kirigami.InlineMessage {
0141             id: ruleCreationErrorMessage
0142             type: Kirigami.MessageType.Error
0143             Layout.fillWidth: true
0144             showCloseButton: true
0145         }
0146 
0147         Kirigami.SearchField {
0148             id: searchField
0149             Layout.fillWidth: true
0150             onTextChanged: proxyModel.invalidateFilter();
0151             enabled: root.model.count > 0
0152             visible: root.filterColumns.length > 0
0153         }
0154     }
0155 
0156     QQC2.HorizontalHeaderView {
0157         id: horizontalHeader
0158         visible: tableView.rows > 0
0159         model: KColumnHeadersModel {
0160             sourceModel: proxyModel
0161             sortColumn: root.sortColumn
0162         }
0163         selectionModel: ItemSelectionModel{}
0164         syncView: tableView
0165         Layout.fillWidth: true
0166 
0167         TapHandler {
0168             acceptedButtons: Qt.LeftButton
0169 
0170             onTapped: (eventPoint, button) => {
0171                 let cell = horizontalHeader.cellAtPosition(eventPoint.position)
0172                 if (cell.x == root.sortColumn) {
0173                     root.sortOrder = header.sortOrder == Qt.AscendingOrder ? Qt.DescendingOrder : Qt.AscendingOrder
0174                 } else {
0175                     root.sortOrder = Qt.AscendingOrder
0176                 }
0177                 root.sortColumn = cell.x
0178             }
0179         }
0180     }
0181 
0182     view: TableView {
0183         id: tableView
0184         anchors.fill: parent
0185         topMargin: horizontalHeader.height
0186         alternatingRows: true
0187 
0188         selectionModel: ItemSelectionModel {}
0189         selectionBehavior: TreeView.SelectRows
0190         function selectRelative(delta) {
0191             var nextRow = selectionModel.currentIndex.row + delta
0192             if (nextRow < 0) {
0193                 nextRow = 0
0194             }
0195             if (nextRow >= rows) {
0196                 nextRow = rows - 1
0197             }
0198             var index = model.index(nextRow, selectionModel.currentIndex.column)
0199             selectionModel.setCurrentIndex(index, ItemSelectionModel.ClearAndSelect | ItemSelectionModel.Rows)
0200         }
0201         Keys.onUpPressed: selectRelative(-1)
0202         Keys.onDownPressed: selectRelative(1)
0203 
0204         model: proxyModel
0205         columnWidthProvider: (column) => {
0206             let explicitWidth = explicitColumnWidth(column)
0207             if (explicitWidth > 0) {
0208                 return explicitWidth
0209             }
0210             let sourceColumn = proxyModel.mapToSource(proxyModel.index(0, column)).column
0211             let width = root.columns.find(c => c.column == sourceColumn).width
0212             return width
0213         }
0214         clip: true
0215         delegate: QQC2.ItemDelegate {
0216             required property var model
0217             required property bool selected
0218             required property bool current
0219             text: model.display
0220             highlighted: selected || current
0221             onClicked: {
0222                 tableView.selectionModel.setCurrentIndex(tableView.model.index(model.row, model.column), ItemSelectionModel.Rows | ItemSelectionModel.ClearAndSelect)
0223             }
0224         }
0225     }
0226 
0227     Kirigami.PlaceholderMessage {
0228         id: emptyListLabel
0229         parent: tableView.parent
0230         anchors.centerIn: parent
0231         width: parent.width - (Kirigami.Units.largeSpacing * 4)
0232         visible: root.model.count === 0 && !root.model.busy && !modelErrorMessage.visible
0233     }
0234 
0235     QQC2.BusyIndicator {
0236         parent: tableView.parent
0237         anchors.centerIn: parent
0238         // Show busy spinner only on initial population and not while an error is shown
0239         running: root.model.count === 0 && root.model.busy && !modelErrorMessage.visible
0240     }
0241 
0242     footer: RowLayout {
0243         Item {
0244             Layout.fillWidth: true
0245         }
0246 
0247         InlineBusyIndicator {
0248             horizontalAlignment: Qt.AlignRight
0249             running: root.currentJob
0250         }
0251 
0252         QQC2.Button {
0253             text: i18n("Blacklist Connection")
0254             icon.name: "network-disconnect"
0255             // HACK TableView lets us select a fake zero index when view is empty...
0256             enabled: tableView.selectionModel.currentIndex  && model.count > 0 && !root.currentJob
0257             onClicked: blacklistRow(tableView.selectionModel.currentIndex.row)
0258         }
0259     }
0260 }