UpdatesPage.qml 14.1 KB
Newer Older
1
import QtQuick.Controls 2.3
2
import QtQuick.Layouts 1.1
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
3
import QtQuick 2.4
4
import org.kde.discover 2.0
5
import org.kde.discover.app 1.0
6
import "navigation.js" as Navigation
7
import org.kde.kirigami 2.10 as Kirigami
8

9
DiscoverPage
10
11
{
    id: page
12
    title: i18n("Updates")
13

14
    property string footerLabel: ""
15
    property int footerProgress: 0
Nate Graham's avatar
Nate Graham committed
16
    property bool isBusy: false
17

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
18
    readonly property var resourcesUpdatesModel: ResourcesUpdatesModel {
19
        id: resourcesUpdatesModel
20
21
22
23
        onPassiveMessage: {
            desc.text += message + "<br/>\n"
            sheet.sheetOpen = true
        }
24
25
26
27
28
29
30
31
32
33
34
35
36
        onIsProgressingChanged: {
            if (!isProgressing) {
                resourcesUpdatesModel.prepare()
            }
        }

        Component.onCompleted: {
            if (!isProgressing) {
                resourcesUpdatesModel.prepare()
            }
        }
    }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
37
    readonly property var sheet: Kirigami.OverlaySheet {
38
        id: sheet
39
40
        parent: applicationWindow().overlay

41
        header: Kirigami.Heading { text: i18n("Update Issue") }
42

43
44
45
46
47
48
49
50
51
52
53
        ColumnLayout {
            Label {
                id: desc
                Layout.fillWidth: true
                textFormat: Text.StyledText
                wrapMode: Text.WordWrap
            }

            Button {
                id: okButton
                Layout.alignment: Qt.AlignRight
54
                text: i18n("OK")
55
56
57
58
59
60
61
62
63
64
65
66
67
68
                icon.name: "dialog-ok"
                onClicked: {
                    sheet.sheetOpen = false
                }
            }
        }

        onSheetOpenChanged: if(!sheetOpen) {
            desc.text = ""
        } else {
            okButton.focus = true
        }
    }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
69
    readonly property var updateModel: UpdateModel {
70
71
72
73
        id: updateModel
        backend: resourcesUpdatesModel
    }

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
74
    readonly property var updateAction: Kirigami.Action
75
76
77
78
79
    {
        id: updateAction
        text: page.unselected>0 ? i18n("Update Selected") : i18n("Update All")
        visible: updateModel.toUpdateCount
        iconName: "update-none"
80
        enabled: !resourcesUpdatesModel.isProgressing && !ResourcesModel.isFetching
81
82
83
        onTriggered: resourcesUpdatesModel.updateAll()
    }

84
    footer: ColumnLayout {
85
        width: parent.width
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
        spacing: 0

        ScrollView {
            id: scv
            Layout.fillWidth: true
            Layout.preferredHeight: visible ? Kirigami.Units.gridUnit * 10 : 0
            visible: log.contents.length > 0
            TextArea {
                readOnly: true
                text: log.contents

                cursorPosition: text.length - 1
                font.family: "monospace"

                ReadFile {
                    id: log
                    filter: ".*ALPM-SCRIPTLET\\] .*"
                    path: "/var/log/pacman.log"
                }
            }
        }
        ToolBar {
            id: footerToolbar
            Layout.fillWidth: true
            visible: (updateModel.totalUpdatesCount > 0 && resourcesUpdatesModel.isProgressing) || updateModel.hasUpdates

Nate Graham's avatar
Nate Graham committed
112
            position: ToolBar.Footer
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131

            CheckBox {
                anchors.left: parent.left
                anchors.leftMargin: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
                anchors.right: parent.right
                anchors.verticalCenter: parent.verticalCenter
                text: page.unselected === 0 ? i18n("All updates selected (%1)", updateModel.updateSize) : i18np("%1/%2 update selected (%3)", "%1/%2 updates selected (%3)", updateModel.toUpdateCount, updateModel.totalUpdatesCount, updateModel.updateSize)
                enabled: updateAction.enabled && !resourcesUpdatesModel.isProgressing && !ResourcesModel.isFetching
                tristate: true
                checkState: updateModel.toUpdateCount === 0                             ? Qt.Unchecked
                            : updateModel.toUpdateCount === updateModel.totalUpdatesCount ? Qt.Checked
                                                                                        : Qt.PartiallyChecked

                onClicked: {
                    if (updateModel.toUpdateCount === 0)
                        updateModel.checkAll()
                    else
                        updateModel.uncheckAll()
                }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
132
            }
133
134
135
        }
    }

136
137
138
139
140
141
142
143
144
145
146
    Kirigami.Action
    {
        id: cancelUpdateAction
        iconName: "dialog-cancel"
        text: i18n("Cancel")
        enabled: resourcesUpdatesModel.transaction && resourcesUpdatesModel.transaction.isCancellable
        onTriggered: resourcesUpdatesModel.transaction.cancel()
    }

    readonly property int unselected: (updateModel.totalUpdatesCount - updateModel.toUpdateCount)

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
147
148
149
    supportsRefreshing: true
    onRefreshingChanged: {
        ResourcesModel.updateAction.triggered()
150
        refreshing = false
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
151
    }
152
153
154
155
156
157
158
159
160
161

    readonly property Item report: ColumnLayout {
        parent: page
        anchors.fill: parent
        Item {
            Layout.fillHeight: true
            width: 1
        }
        ProgressBar {
            Layout.alignment: Qt.AlignHCenter
162
            Layout.fillWidth: true
163
164
            Layout.leftMargin: Kirigami.Units.largeSpacing * 2
            Layout.rightMargin: Kirigami.Units.largeSpacing * 2
165
            Layout.maximumWidth: Kirigami.Units.gridUnit * 20
166
167
168
169
170
171
172
            value: page.footerProgress
            from: 0
            to: 100
            visible: page.isBusy
        }
        Kirigami.Icon {
            Layout.alignment: Qt.AlignHCenter
173
            visible: page.footerProgress === 0 && page.footerLabel !== "" && !page.isBusy
174
            source: "update-none"
175
176
            implicitWidth: Kirigami.Units.gridUnit * 4
            implicitHeight: Kirigami.Units.gridUnit * 4
177
178
179
180
181
182
183
184
185
            enabled: false
        }
        Kirigami.Heading {
            id: statusLabel
            Layout.fillWidth: true
            Layout.alignment: Qt.AlignHCenter
            horizontalAlignment: Text.AlignHCenter
            text: page.footerLabel
            level: 3
186
187
        }
        Button {
188
            id: restartButton
189
190
            Layout.alignment: Qt.AlignHCenter
            text: i18n("Restart")
191
            visible: false
192
193
194
195
196
197
198
            onClicked: app.reboot()
        }
        Item {
            Layout.fillHeight: true
            width: 1
        }
    }
Marco Martin's avatar
Marco Martin committed
199
    ListView {
200
201
        id: updatesView
        currentIndex: -1
202

203
204
205
206
207
208
209
        displaced: Transition {
            YAnimator {
                duration: Kirigami.Units.longDuration
                easing.type: Easing.InOutQuad
            }
        }

210
211
212
213
        model: QSortFilterProxyModel {
            sourceModel: updateModel
            sortRole: UpdateModel.SectionResourceProgressRole
        }
214
215
216

        section {
            property: "section"
217
218
219
            delegate: Kirigami.ListSectionHeader {
                width: updatesView.width
                label: section
220
221
222
223
            }
        }

        delegate: Kirigami.AbstractListItem {
224
            id: listItem
225
            backgroundColor: Kirigami.Theme.backgroundColor
226
            highlighted: ListView.isCurrentItem
227
228
229
            onEnabledChanged: if (!enabled) {
                layout.extended = false;
            }
230

231
232
            visible: resourceState < 3 //3=AbstractBackendUpdater.Done

233
234
235
            Keys.onReturnPressed: {
                itemChecked.clicked()
            }
236
237
            Keys.onPressed: if (event.key===Qt.Key_Alt) layout.extended = true
            Keys.onReleased: if (event.key===Qt.Key_Alt)  layout.extended = false
238

239
240
241
            ColumnLayout {
                id: layout
                property bool extended: false
242
                onExtendedChanged: if (extended) {
243
                    updateModel.fetchUpdateDetails(index)
244
                }
245
246
                RowLayout {
                    Layout.fillWidth: true
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
247
248
                    Layout.fillHeight: true

249
                    CheckBox {
250
                        id: itemChecked
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
251
                        Layout.leftMargin: Kirigami.Units.gridUnit
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
252
                        Layout.alignment: Qt.AlignVCenter
Laurent Montel's avatar
Laurent Montel committed
253
254
                        checked: model.checked === Qt.Checked
                        onClicked: model.checked = (model.checked===Qt.Checked ? Qt.Unchecked : Qt.Checked)
255
                        enabled: !resourcesUpdatesModel.isProgressing
256
257
                    }

258
                    Kirigami.Icon {
259
260
                        width: Kirigami.Units.gridUnit * 2
                        Layout.preferredHeight: width
261
                        source: decoration
262
                        smooth: true
263
264
                    }

265
266
267
268
269
270
271
272
273
274
                    ColumnLayout {

                        // App name
                        Kirigami.Heading {
                            Layout.fillWidth: true
                            text: i18n("%1", display)
                            level: 3
                            elide: Text.ElideRight
                        }

275
                        // Version numbers
276
277
                        Label {
                            Layout.fillWidth: true
278
                            elide: truncated ? Text.ElideLeft : Text.ElideRight
279
                            text: resource.upgradeText
280
                            opacity: listItem.hovered? 0.8 : 0.6
281
                        }
282
283
284
                    }

                    LabelBackground {
285
                        Layout.minimumWidth: Kirigami.Units.gridUnit * 6
286
                        text: resourceState == 2 ? i18n("Installing") : size
287
288
289
290
291

                        progress: resourceProgress/100
                    }
                }

292
                Frame {
293
                    Layout.fillWidth: true
294
                    implicitHeight: view.contentHeight
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
295
                    visible: layout.extended && changelog.length>0
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
296
                    Label {
297
                        id: view
298
299
300
301
                        anchors {
                            right: parent.right
                            left: parent.left
                        }
302
                        text: changelog
303
                        textFormat: Text.StyledText
304
                        wrapMode: Text.WordWrap
305
                        onLinkActivated: Qt.openUrlExternally(link)
306

307
                    }
308
309
310
311
312

                    //This saves a binding loop on implictHeight, as the Label
                    //height is updated twice (first time with the wrong value)
                    Behavior on implicitHeight
                    { PropertyAnimation { duration: Kirigami.Units.shortDuration } }
313
314
315
                }

                Button {
316
                    Layout.alignment: Qt.AlignRight
317
                    text: i18n("More Information...")
318
                    visible: layout.extended
319
                    enabled: !resourcesUpdatesModel.isProgressing
320
321
322
323
                    onClicked: Navigation.openApplication(resource)
                }
            }

324
325
            onClicked: {
                layout.extended = !layout.extended
326
            }
327
        }
328
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
329

330
    readonly property alias secSinceUpdate: resourcesUpdatesModel.secsToLastUpdate
331
    state:  ( resourcesUpdatesModel.isProgressing        ? "progressing"
332
            : ResourcesModel.isFetching                  ? "fetching"
333
            : updateModel.hasUpdates                     ? "has-updates"
334
            : resourcesUpdatesModel.needsReboot          ? "reboot"
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
335
336
337
338
339
340
341
342
            : secSinceUpdate < 0                         ? "unknown"
            : secSinceUpdate === 0                       ? "now-uptodate"
            : secSinceUpdate < 1000 * 60 * 60 * 24       ? "uptodate"
            : secSinceUpdate < 1000 * 60 * 60 * 24 * 7   ? "medium"
            :                                              "low"
            )

    states: [
343
344
        State {
            name: "fetching"
345
346
            PropertyChanges { target: page; footerLabel: i18nc("@info", "Fetching updates...") }
            PropertyChanges { target: statusLabel; enabled: true }
347
            PropertyChanges { target: page; footerProgress: ResourcesModel.fetchingUpdatesProgress }
Nate Graham's avatar
Nate Graham committed
348
            PropertyChanges { target: page; isBusy: true }
349
            PropertyChanges { target: updatesView; opacity: 0 }
350
        },
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
351
352
        State {
            name: "progressing"
353
            PropertyChanges { target: page; supportsRefreshing: false }
354
            PropertyChanges { target: page.actions; main: cancelUpdateAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
355
356
357
358
        },
        State {
            name: "has-updates"
            PropertyChanges { target: page; title: i18nc("@info", "Updates") }
359
360
            PropertyChanges { target: page.actions; main: updateAction }
            PropertyChanges { target: page.actions; left: refreshAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
361
        },
362
363
        State {
            name: "reboot"
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
364
            PropertyChanges { target: page; footerLabel: i18nc("@info", "The system requires a restart to apply updates") }
365
            PropertyChanges { target: statusLabel; enabled: true }
366
            PropertyChanges { target: restartButton; visible: true }
367
        },
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
368
369
        State {
            name: "now-uptodate"
370
371
            PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to date") }
            PropertyChanges { target: statusLabel; enabled: false }
372
            PropertyChanges { target: page.actions; main: refreshAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
373
374
375
        },
        State {
            name: "uptodate"
376
377
            PropertyChanges { target: page; footerLabel: i18nc("@info", "Up to date") }
            PropertyChanges { target: statusLabel; enabled: false }
378
            PropertyChanges { target: page.actions; main: refreshAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
379
380
381
        },
        State {
            name: "medium"
382
383
            PropertyChanges { target: page; title: i18nc("@info", "Up to date") }
            PropertyChanges { target: statusLabel; enabled: false }
384
            PropertyChanges { target: page.actions; main: refreshAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
385
386
387
        },
        State {
            name: "low"
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
388
            PropertyChanges { target: page; title: i18nc("@info", "Should check for updates") }
389
            PropertyChanges { target: statusLabel; enabled: true }
390
            PropertyChanges { target: page.actions; main: refreshAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
391
392
393
        },
        State {
            name: "unknown"
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
394
            PropertyChanges { target: page; title: i18nc("@info", "It is unknown when the last check for updates was") }
395
            PropertyChanges { target: statusLabel; enabled: true }
396
            PropertyChanges { target: page.actions; main: refreshAction }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
397
398
        }
    ]
399
}