1/*
2 * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7import QtQuick 2.12
8import QtQuick.Controls 2.12
9
10import Qt.labs.qmlmodels 1.0
11
12import org.kde.kirigami 2.12 as Kirigami
13import org.kde.kitemmodels 1.0 as KItemModels
14import org.kde.quickcharts 1.0 as Charts
15
16import org.kde.ksysguard.formatter 1.0 as Formatter
17import org.kde.ksysguard.process 1.0 as Process
18import org.kde.ksysguard.table 1.0 as Table
19
20Table.BaseTableView {
21    id: view
22
23    Kirigami.PlaceholderMessage {
24        visible: !appModel.available
25        anchors.fill: parent
26        anchors.margins: Kirigami.Units.largeSpacing
27        icon.name: "action-unavailable-symbolic"
28        text: i18nc("Warning message shown on runtime error", "Applications view is unsupported on your system");
29    }
30
31    property var enabledColumns: []
32    property alias columnDisplay: displayModel.columnDisplay
33    property alias sourceModel: appModel
34
35    property alias filterString: sortColumnFilter.filterString
36
37    property var selectedApplications: {
38        var result = []
39        var rows = {}
40
41        for (var i of selection.selectedIndexes) {
42            if (rows[i.row] != undefined) {
43                continue
44            }
45            rows[i.row] = true
46
47            var index = sortColumnFilter.mapToSource(i)
48            var item = applicationInformation.createObject()
49            item.index = index
50            result.push(item)
51        }
52
53        return result
54    }
55
56    property Component applicationInformation: ApplicationInformation {
57        model: appModel
58        sensorColumns: {
59            "name": appModel.enabledAttributes.indexOf("appName"),
60            "cpu": appModel.enabledAttributes.indexOf("usage"),
61            "memory": appModel.enabledAttributes.indexOf("vmPSS"),
62            "netInbound": appModel.enabledAttributes.indexOf("netInbound"),
63            "netOutbound": appModel.enabledAttributes.indexOf("netOutbound"),
64            "diskRead": appModel.enabledAttributes.indexOf("ioCharactersActuallyReadRate"),
65            "diskWrite": appModel.enabledAttributes.indexOf("ioCharactersActuallyWrittenRate"),
66            "iconName": appModel.enabledAttributes.indexOf("iconName")
67        }
68        role: Process.ProcessDataModel.Value
69        pidsRole: Process.ProcessDataModel.PIDs
70    }
71
72    idRole: "Attribute"
73
74    columnWidths: [200, 100, 100, 100, 100]
75
76    onSort: {
77        sortColumnFilter.sortColumn = column
78        sortColumnFilter.sortOrder = order
79    }
80
81    headerModel: sortColumnFilter
82
83    model: KItemModels.KSortFilterProxyModel {
84        id: sortColumnFilter
85
86        sourceModel: cacheModel
87        filterKeyColumn: appModel.nameColumn
88        filterCaseSensitivity: Qt.CaseInsensitive
89        filterColumnCallback: function(column, parent) {
90            // Note: This assumes displayModel column == appModel column
91            // This may not always hold, but we get incorrect results if we try to
92            // map to source indices when the model is empty.
93            var sensorId = appModel.enabledAttributes[column]
94            if (appModel.hiddenAttributes.indexOf(sensorId) != -1) {
95                return false
96            }
97            return true
98        }
99        filterRowCallback: function(row, parent) {
100            if (filterString.length == 0) {
101                return true
102            }
103            const name = sourceModel.data(sourceModel.index(row, filterKeyColumn, parent), filterRole).toLowerCase()
104            const parts = filterString.toLowerCase().split(",").map(s => s.trim()).filter(s => s.length > 0)
105            return parts.some(part => name.includes(part))
106        }
107
108        sortRole: "Value"
109    }
110
111    Table.ComponentCacheProxyModel {
112        id: cacheModel
113        sourceModel: displayModel
114
115        component: Charts.HistoryProxySource {
116            id: history
117            source: Charts.ModelSource {
118                model: history.Table.ComponentCacheProxyModel.model
119                column: history.Table.ComponentCacheProxyModel.column
120                roleName: "Value"
121            }
122            item: Table.ComponentCacheProxyModel.row
123            maximumHistory: 10
124            interval: 2000
125            fillMode: Charts.HistoryProxySource.FillFromEnd
126        }
127    }
128
129    Table.ColumnDisplayModel {
130        id: displayModel
131        sourceModel: appModel
132        idRole: "Attribute"
133    }
134
135    Process.ApplicationDataModel {
136        id: appModel
137
138        property int nameColumn: enabledAttributes.indexOf("appName")
139        property int iconColumn: enabledAttributes.indexOf("iconName")
140
141        property var requiredAttributes: [
142            "iconName",
143            "appName",
144            "usage",
145            "vmPSS",
146            "netInbound",
147            "netOutbound",
148            "ioCharactersActuallyReadRate",
149            "ioCharactersActuallyWrittenRate",
150        ]
151        property var hiddenAttributes: []
152
153        enabled: view.visible
154
155        enabledAttributes: {
156            var result = []
157            for (let i of view.enabledColumns) {
158                if (appModel.availableAttributes.includes(i)) {
159                    result.push(i)
160                }
161            }
162
163            var hidden = []
164            for (let i of requiredAttributes) {
165                if (result.indexOf(i) == -1) {
166                    result.push(i)
167                    hidden.push(i)
168                }
169            }
170
171            hiddenAttributes = hidden
172            return result;
173        }
174
175        Component.onCompleted: {
176            if (!available) {
177                console.error("Implementation matching https://systemd.io/DESKTOP_ENVIRONMENTS/ was not found. ApplicationsView will not be available")
178            }
179        }
180    }
181
182    delegate: DelegateChooser {
183        role: "displayStyle"
184        DelegateChoice {
185            column: view.LayoutMirroring.enabled ? view.model.columnCount() - 1 : 0
186            Table.FirstCellDelegate {
187                iconName: {
188                    var index = sortColumnFilter.mapToSource(sortColumnFilter.index(model.row, 0));
189                    index = appModel.index(index.row, appModel.iconColumn)
190                    return appModel.data(index)
191                    return ""
192                }
193            }
194        }
195        DelegateChoice {
196            roleValue: "line"
197            Table.LineChartCellDelegate {
198                valueSources: model.cachedComponent != undefined ? model.cachedComponent : []
199                maximum: sortColumnFilter.data(sortColumnFilter.index(model.row, model.column), Process.ProcessDataModel.Maximum)
200            }
201        }
202        DelegateChoice {
203            roleValue: "lineScaled"
204            Table.LineChartCellDelegate {
205                valueSources: model.cachedComponent != undefined ? model.cachedComponent : []
206                maximum: sortColumnFilter.data(sortColumnFilter.index(model.row, model.column), Process.ProcessDataModel.Maximum)
207                text: Formatter.Formatter.formatValue(parseInt(model.Value) / model.Maximum * 100, model.Unit)
208            }
209
210        }
211        DelegateChoice {
212            roleValue: "textScaled"
213            Table.TextCellDelegate {
214                text: Formatter.Formatter.formatValue(parseInt(model.Value) / model.Maximum * 100, model.Unit)
215            }
216        }
217        DelegateChoice { Table.TextCellDelegate { } }
218    }
219}
220