File: ComboBoxWithCustomValue.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 (164 lines) | stat: -rw-r--r-- 5,523 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
/*
    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.Controls as QQC2
import org.kde.kirigami as Kirigami

/**
 * A specialized ComboBox that displays a list of preset options plus a "Custom" option,
 * which invokes a parent-defined flow to let the user input a custom value. Once provided,
 * the custom value appears as an extra option at the bottom of the list.
 */
QQC2.ComboBox {
    id: root

    /**
     * Preset array of JS objects (or strings) with model data, to be used as base set of options for `model`.
     *
     * Include the custom requester option here, and set `customRequesterValue` accordingly.
     * Do not set `model` directly, assign this property instead.
     */
    required property var presetOptions

    /**
     * The latest valid value that has been selected.
     *
     * Changing this value will select the corresponding index. If no corresponding index exists,
     * the `configuredValueOptionMissing` signal will be emitted.
     */
    property var configuredValue: undefined

    /**
     * Emitted when `configuredValue` was not found in any element of `presetOptions`
     * or `customOptions`. To ensure a corresponding option exists, add an element to
     * `customOptions` with `configuredValue` in the `valueRole` role.
     *
     * @see customOptions
     */
    signal configuredValueOptionMissing

    /**
     * Emitted when a regular value option (i.e. any other than the custom requester option)
     * has been activated. Useful for accepting the new value without further checks.
     */
    signal regularValueActivated

    /**
     * This value used to find the option that will emit the `customRequest` signal when activated.
     *
     * This value is not usually persisted to configuration and only needed for consistency with
     * elements from `presetOptions`. Activation of any other option will emit `valueOptionActivated`.
     */
    property var customRequesterValue: undefined

    /**
     * Emitted when the custom value requester option was activated.
     *
     * Handle this by letting the user select the custom value (e.g. via modal dialog).
     * If the user selects a custom option, assign it to `configuredValue`. Always call
     * `customRequestCompleted()` at the end to ensure the correct index is set.
     */
    signal customRequest

    function customRequestCompleted() { updateIndex(); }

    /**
     * A model of elements to represent any additional custom values.
     *
     * The model follows the same format as the elements in `presetOptions`. All elements
     * will be appended to the preset options to constitute the combined `model`.
     *
     * This option is typically initialized with a single element containing `configuredValue`
     * in the `valueRole`, but the option list can remain unchanged even after `configuredValue`
     * has been updated.
     *
     * @see configuredValueOptionMissing
     */
    property var customOptions

    /** Exposed for completeness but not usually necessary. Will be set to -1 if no corresponding option has been found in presetOptions. */
    readonly property int customRequesterIndex: priv.customRequesterIndex

    QtObject {
        id: priv
        property bool initialized: false
        property int customRequesterIndex: -1
    }

    Component.onCompleted: {
        priv.initialized = true;
        updateOptions();
        updateCustomRequesterIndex();
        updateIndex();
    }
    onPresetOptionsChanged: {
        if (!priv.initialized) {
            return;
        }
        updateOptions();
        updateCustomRequesterIndex();
        updateIndex();
    }
    onCustomRequesterValueChanged: {
        if (!priv.initialized) {
            return;
        }
        updateCustomRequesterIndex();
        updateIndex();
    }
    onCustomOptionsChanged: {
        if (!priv.initialized) {
            return;
        }
        updateOptions();
        // Avoid recursion for `configuredValueOptionMissing` signals,
        // and also allow changing customOptions before setting configuredValue.
        const allowSignal = false;
        updateIndex(allowSignal);
    }
    onConfiguredValueChanged: {
        if (!priv.initialized) {
            return;
        }
        updateIndex();
    }

    onActivated: {
        if (currentIndex < 0) {
            return;
        }

        if (currentIndex === customRequesterIndex) {
            customRequest();
        } else {
            regularValueActivated();
        }
    }

    function updateOptions() {
        // Create a new array instance, to force a model update.
        model = [...(presetOptions ?? []), ...(customOptions ?? [])];
    }

    function updateCustomRequesterIndex() {
        priv.customRequesterIndex = indexOfValue(customRequesterValue);
    }

    function updateIndex(allowSignal = true) {
        if (configuredValue !== undefined && configuredValue !== currentValue) {
            const idx = indexOfValue(configuredValue);
            if (idx !== -1 && idx !== priv.customRequesterIndex && idx !== currentIndex) {
                currentIndex = idx;
            } else if (allowSignal) {
                configuredValueOptionMissing();
            }
        }
    }
}