File: DurationPromptDialog.qml

package info (click to toggle)
powerdevil 4%3A6.5.4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 10,680 kB
  • sloc: cpp: 13,284; xml: 1,911; python: 1,204; sh: 19; makefile: 10
file content (229 lines) | stat: -rw-r--r-- 9,434 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
/*
    SPDX-FileCopyrightText: 2024 Kristen McWilliam <kmcwilliampublic@gmail.com>
    SPDX-FileCopyrightText: 2024 Jakob Petsovits <jpetso@petsovits.com>

    SPDX-License-Identifier: LGPL-2.1-or-later

    Originating from kcm_screenlocker. Upstream any changes there until it goes into Kirigami (Addons).
*/

import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami

/**
 * A dialog that prompts the user to enter a duration.
 *
 * The dialog contains a spin box for the user to enter a duration, and its corresponding time unit.
 *
 * The dialog emits the `accepted` signal when the user clicks the confirm button, and the `rejected` signal when the user clicks the cancel button.
 *
 * After the dialog has been accepted, the `value` property will contain the duration value entered
 * by the user. The `unit` property will contain the time unit of `value`.
 */
Kirigami.Dialog {
    id: root

    /**
     * @brief The possible time units of the value of the input field.
     */
    enum Unit {
        Milliseconds,
        Seconds,
        Minutes,
        Hours,
        Days,
        Weeks,
        Months,
        Years
    }

    /**
     * @brief An array of time units that can be associated with the duration value.
     *
     * All elements must be `DurationPromptDialog.Unit` enum values, with no duplicates.
     *
     * At least one element is required. If more than one accepted unit is specified, the dialog
     * allows the user to select the unit of the duration.
     */
    required property var acceptsUnits

    /**
     * @brief A text label in front of the input field.
     */
    property string label

    /**
     * @brief The value of the input field.
     *
     * The value is in seconds if `unit` is `DurationPromptDialog.ValueType.Seconds`,
     * in minutes if `valueType` is `DurationPromptDialog.ValueType.Minutes`, and so on.
     *
     * Assign before opening the dialog to set the initial value.
     *
     * The value will have been updated when the user accepts the dialog.
     */
    property alias value: durationValueSpinBox.value

    property alias from: durationValueSpinBox.from
    property alias to: durationValueSpinBox.to

    /**
     * @brief The unit of the value of the input field, of type `DurationPromptDialog.Unit`.
     *
     * This must be one of the elements from the specified `acceptsUnits` property.
     * The first element of that array is used as default unit.
     */
    property int unit: acceptsUnits[0]

    padding: Kirigami.Units.largeSpacing
    standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
    showCloseButton: false

    // FIXME: KF 6.3 and up doesn't need content.implicitWidth as part of preferredWidth, it will
    //        automatically expand to implicit content size. Remove this once we can rely on KF 6.3.
    preferredWidth: Math.max(content.implicitWidth,
                             Kirigami.Units.gridUnit * 10, implicitHeaderWidth, implicitFooterWidth)

    onOpened: {
        // `focus: true` is not enough, because the OK button wants focus and gets priority.
        durationValueSpinBox.forceActiveFocus()
        // Set SpinBox width once, don't resize it if `to` changes based on unit changes.
        durationValueSpinBox.Layout.preferredWidth = durationValueSpinBox.implicitWidth;
    }

    RowLayout {
        id: content
        anchors.centerIn: parent

        spacing: Kirigami.Units.largeSpacing

        QQC2.Label {
            id: labelItem
            visible: (root.label?.length ?? 0) > 0

            Kirigami.MnemonicData.enabled: visible && durationValueSpinBox.enabled
            Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.FormLabel
            Kirigami.MnemonicData.label: root.label ?? ""
            text: Kirigami.MnemonicData.richTextLabel
        }

        RowLayout {
            spacing: Kirigami.Units.smallSpacing

            QQC2.SpinBox {
                id: durationValueSpinBox
                from: 0
                to: 9999

                Shortcut {
                    sequence: labelItem.Kirigami.MnemonicData.sequence
                    onActivated: { durationValueSpinBox.forceActiveFocus(); }
                }
                Keys.onReturnPressed: event => root.accept()
            }

            function unitSuffixForValue(val, unit) {
                switch (unit) {
                case DurationPromptDialog.Unit.Milliseconds:
                    return i18ncp("The unit of the time input field", "millisecond", "milliseconds", val);
                case DurationPromptDialog.Unit.Seconds:
                    return i18ncp("The unit of the time input field", "second", "seconds", val);
                case DurationPromptDialog.Unit.Minutes:
                    return i18ncp("The unit of the time input field", "minute", "minutes", val);
                case DurationPromptDialog.Unit.Hours:
                    return i18ncp("The unit of the time input field", "hour", "hours", val);
                case DurationPromptDialog.Unit.Days:
                    return i18ncp("The unit of the time input field", "day", "days", val);
                case DurationPromptDialog.Unit.Weeks:
                    return i18ncp("The unit of the time input field", "week", "weeks", val);
                case DurationPromptDialog.Unit.Months:
                    return i18ncp("The unit of the time input field", "month", "months", val);
                case DurationPromptDialog.Unit.Years:
                    return i18ncp("The unit of the time input field", "year", "years", val);
                }
                console.warn("invalid unit in unitSuffixForValue()");
            }

            QQC2.Label {
                visible: acceptsUnits.length == 1
                text: parent.unitSuffixForValue(value, acceptsUnits[0])

                Layout.preferredWidth: Math.max(implicitWidth, pluralUnitLabelMetrics.advanceWidth + leftPadding + rightPadding)
                TextMetrics {
                    id: pluralUnitLabelMetrics
                    text: unitSelectionRadios.labelForUnit(acceptsUnits[0])
                }
            }

            ColumnLayout {
                id: unitSelectionRadios
                spacing: Kirigami.Units.smallSpacing
                visible: acceptsUnits.length > 1

                function labelForUnit(unit) {
                    switch (unit) {
                    case DurationPromptDialog.Unit.Milliseconds:
                        return i18nc("@text:radiobutton Unit of the time input field", "milliseconds");
                    case DurationPromptDialog.Unit.Seconds:
                        return i18nc("@text:radiobutton Unit of the time input field", "seconds");
                    case DurationPromptDialog.Unit.Minutes:
                        return i18nc("@text:radiobutton Unit of the time input field", "minutes");
                    case DurationPromptDialog.Unit.Hours:
                        return i18nc("@text:radiobutton Unit of the time input field", "hours");
                    case DurationPromptDialog.Unit.Days:
                        return i18nc("@text:radiobutton Unit of the time input field", "days");
                    case DurationPromptDialog.Unit.Weeks:
                        return i18nc("@text:radiobutton Unit of the time input field", "weeks");
                    case DurationPromptDialog.Unit.Months:
                        return i18nc("@text:radiobutton Unit of the time input field", "months");
                    case DurationPromptDialog.Unit.Years:
                        return i18nc("@text:radiobutton Unit of the time input field", "years");
                    }
                    console.warn("invalid unit in radioButtonLabelForValue()");
                }

                Repeater {
                    id: repeater
                    model: acceptsUnits

                    QQC2.RadioButton {
                        required property int index
                        required property int modelData

                        text: parent.labelForUnit(modelData)
                        checked: unit === modelData
                        onClicked: {
                            unit = modelData;
                        }

                        Keys.onReturnPressed: event => root.accept()
                        Keys.onUpPressed: event => {
                            const prev = repeater.itemAt(index - 1);
                            if (prev) {
                                prev.focus = true;
                            }
                        }
                        Keys.onDownPressed: event => {
                            const next = repeater.itemAt(index + 1);
                            if (next) {
                                next.focus = true;
                            }
                        }
                        Keys.onLeftPressed: event => {
                            if (!mirrored) {
                                durationValueSpinBox.forceActiveFocus();
                            }
                        }
                        Keys.onRightPressed: event => {
                            if (mirrored) {
                                durationValueSpinBox.forceActiveFocus();
                            }
                        }
                    }
                }
            }
        }
    }
}