47e0ad87创建于 2025年4月1日历史提交
/*
    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 assists in displaying a list of preset options plus a "Custom" option,
 * which invokes a parent-defined flow to let the user input a custom value. One or more
 * custom values can then be added to the `model` by the parent.
 *
 * In addition to the standard ComboBox `model` property, this component wants its user to set
 * two value properties that refer to one of the elements in `model`:
 *
 * - `configuredValue`, if set, will automatically update the ComboBox index to the corresponding
 *   option whenever this value itself or the `model` changes. If no corresponding option exists,
 *   the `configuredValueOptionMissing` signal will be emitted so the component user can easily
 *   add one to the `model`.
 * - `customRequesterValue`, if set, will provide additional signals named `regularValueActivated`
 *    and `customRequest` to conveniently handle simple selection updates vs. custom value selection
 *    respectively.
 *
 * Both of these properties are optional, but together they provide a convenient pattern to
 * implement item selection with custom value options.
 */
QQC2.ComboBox {
    id: root

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

    /**
     * Emitted when `configuredValue` was not found in any element of `model`. To ensure that a
     * corresponding option exists, add an element with `configuredValue` in the `valueRole` role.
     *
     * @see configuredValue
     */
    signal configuredValueOptionMissing()

    /**
     * The value corresponding to the custom requester option which will emit the `customRequest`
     * signal when activated.
     *
     * This value is not usually persisted to configuration.
     * Activation of any other option will emit `regularValueActivated` instead.
     *
     * @see customRequest
     * @see regularValueActivated
     */
    property var customRequesterValue: undefined

    /**
     * Emitted when a regular value option (i.e. any other than the custom requester option)
     * has been activated.
     *
     * You can use this instead of the `activated` signal without manually checking against
     * `customRequesterValue`.
     *
     * @see customRequesterValue
     */
    signal regularValueActivated()

    /**
     * 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.
     *
     * @see customRequesterValue
     * @see customRequestCompleted
     */
    signal customRequest()

    function customRequestCompleted(): void {
        updateIndex();
    }

    /** private */
    property bool __initialized: false
    /** private */
    property bool __allowSignal: true

    Component.onCompleted: {
        __initialized = true;
        updateIndex();
    }
    onModelChanged: { updateIndex(); }
    onCustomRequesterValueChanged: { updateIndex(); }
    onConfiguredValueChanged: { updateIndex(); }

    onActivated: {
        if (customRequesterValue === undefined || currentIndex < 0) {
            return;
        }

        if (currentValue === customRequesterValue) {
            customRequest();
        } else {
            regularValueActivated();
        }
    }

    /**
     * Try to set currentIndex to the option with `configuredValue` in its value role.
     * If no such option exists in the current `model`, the `configuredValueOptionMissing` signal
     * is emitted instead.
     *
     * You shouldn't usually have to call this function manually, but it is available for cases
     * when this component does not pick up on certain changes in the model.
     */
    function updateIndex(): void {
        if (!__initialized) {
            return;
        }
        if (configuredValue === undefined || configuredValue === currentValue || configuredValue === customRequesterValue) {
            return;
        }

        const idx = indexOfValue(configuredValue);
        if (idx !== -1) {
            currentIndex = idx;
        } else if (__allowSignal) {
            // Avoid recursion for `configuredValueOptionMissing` signals.
            __allowSignal = false;
            configuredValueOptionMissing();
            __allowSignal = true;
        }
    }
}