// Copyright (C) 2018 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "aspects.h"

#include "algorithm.h"
#include "checkablemessagebox.h"
#include "environment.h"
#include "environmentdialog.h"
#include "fancylineedit.h"
#include "guard.h"
#include "guiutils.h"
#include "itemviews.h"
#include "layoutbuilder.h"
#include "macroexpander.h"
#include "passworddialog.h"
#include "pathchooser.h"
#include "pathlisteditor.h"
#include "qtcassert.h"
#include "qtcolorbutton.h"
#include "qtcsettings.h"
#include "qtcwidgets.h"
#include "stylehelper.h"
#include "treemodel.h"
#include "utilsicons.h"
#include "utilstr.h"
#include "variablechooser.h"

#include <QAction>
#include <QButtonGroup>
#include <QCheckBox>
#include <QCompleter>
#include <QDebug>
#include <QFontComboBox>
#include <QGroupBox>
#include <QHeaderView>
#include <QLabel>
#include <QLayout>
#include <QLineEdit>
#include <QListWidget>
#include <QMenu>
#include <QPaintEvent>
#include <QPainter>
#include <QPointer>
#include <QPushButton>
#include <QRadioButton>
#include <QScrollArea>
#include <QSpinBox>
#include <QStandardItemModel>
#include <QTextEdit>
#include <QTreeWidget>
#include <QUndoStack>

using namespace Layouting;

namespace Utils {

static const char ASPECT_PROPERTY[] = "aspect";


BaseAspect::Changes::Changes()
{
    memset(this, 0, sizeof(*this));
}

class Internal::BaseAspectPrivate
{
public:
    explicit BaseAspectPrivate(AspectContainer *container) : m_container(container) {}

    MacroExpander *macroExpander()
    {
        if (!m_expander) {
            m_expander = std::make_unique<MacroExpander>();
            m_expander->setDisplayName("Variables");
            if (m_container) {
                MacroExpanderProvider p(m_container, m_container->macroExpander());
                m_expander->registerSubProvider(p);
            }
        }
        return m_expander.get();
    }

    void setContainer(AspectContainer *container)
    {
        m_container = container;
        if (m_expander) {
            MacroExpanderProvider p(m_container, m_container->macroExpander());
            m_expander->registerSubProvider(p);
        }
    }

    Id m_id;
    QString m_displayName;
    Key m_settingsKey; // Name of data in settings.
    QString m_tooltip;
    QString m_labelText;
    QPixmap m_labelPixmap;
    QIcon m_icon;
    QPointer<QAction> m_action; // Owned by us.
    AspectContainer *m_container = nullptr; // Not owned by us.

    bool m_visible = true;
    bool m_readOnly = false;
    bool m_autoApply = true;
    bool m_saveAlways = false; // if true, also empty keys will be written
    QPointer<BoolAspect> m_enabler;
    bool m_enabled = true;
    int m_spanX = 1;
    int m_spanY = 1;
    BaseAspect::ConfigWidgetCreator m_configWidgetCreator;

    BaseAspect::DataCreator m_dataCreator;
    BaseAspect::DataCloner m_dataCloner;
    QList<BaseAspect::DataExtractor> m_dataExtractors;

    QUndoStack *m_undoStack = nullptr;

private:
    std::unique_ptr<MacroExpander> m_expander;
};

/*!
    \class Utils::BaseAspect
    \inmodule QtCreator

    \brief The \c BaseAspect class provides a common base for classes implementing
    aspects.

    An \e aspect is a hunk of data like a property or collection of related
    properties of some object, together with a description of its behavior
    for common operations like visualizing or persisting.

    Simple aspects are, for example, a boolean property represented by a QCheckBox
    in the user interface, or a string property represented by a PathChooser,
    for selecting directories in the filesystem.

    While aspects implementations usually can visualize and persist
    their data, or use an ID, neither of these is mandatory.

    The derived classes can implement addToLayout() to create a UI.

    Implement \c guiToBuffer() and \c bufferToGui() to synchronize the UI with
    the internal data.
*/

/*!
    \enum Utils::BaseAspect::Announcement

    Whether to emit a signal when a value changes.

    \value DoEmit
           Emit a signal.
    \value BeQuiet
           Don't emit a signal.
*/

/*!
    Constructs a base aspect.

    If \a container is non-null, the aspect is made known to the container.
*/
BaseAspect::BaseAspect(AspectContainer *container)
    : d(new Internal::BaseAspectPrivate(container))
{
    if (container)
        container->registerAspect(this);
    addDataExtractor(this, &BaseAspect::variantValue, &Data::value);
}

/*!
    Destructs a BaseAspect.
*/
BaseAspect::~BaseAspect()
{
    delete d->m_action;
}

Id BaseAspect::id() const
{
    return d->m_id;
}

void BaseAspect::setId(Id id)
{
    d->m_id = id;
}

QVariant BaseAspect::volatileVariantValue() const
{
    return {};
}

QVariant BaseAspect::variantValue() const
{
    return {};
}

/*!
    Sets \a value.

    If \a howToAnnounce is set to \c DoEmit, emits the \c valueChanged signal.

    Prefer the typed \c setValue() of the derived classes.
*/
void BaseAspect::setVariantValue(const QVariant &value, Announcement howToAnnounce)
{
    Q_UNUSED(value)
    Q_UNUSED(howToAnnounce)
    QTC_CHECK(false);
}

void BaseAspect::setDefaultVariantValue(const QVariant &value)
{
    Q_UNUSED(value)
    QTC_CHECK(false);
}

bool BaseAspect::isDefaultValue() const
{
    return defaultVariantValue() == variantValue();
}

QVariant BaseAspect::defaultVariantValue() const
{
    return {};
}

/*!
    \class Utils::TypedAspect
    \inheaderfile utils/aspects.h
    \inmodule QtCreator

    \brief The \c TypedAspect class is a helper class for implementing a simple
    aspect.

    A typed aspect contains a single piece of data that is of the type
    \c ValueType.
*/


/*!
    \fn template <typename ValueType> void Utils::TypedAspect<ValueType>::setDefaultValue(const ValueType &value)

    Sets a default \a value and the current value for this aspect.

    \note The current value will be set silently to the same value.
    It is reasonable to only set default values in the setup phase
    of the aspect.

    Default values will not be stored in settings.
*/

void BaseAspect::setDisplayName(const QString &displayName)
{
    d->m_displayName = displayName;
}

bool BaseAspect::isVisible() const
{
    return d->m_visible;
}

/*!
    Shows or hides the visual representation of this aspect depending
    on the value of \a visible.
    By default, it is visible.
 */
void BaseAspect::setVisible(bool visible)
{
    if (visible == d->m_visible)
        return;

    d->m_visible = visible;
    emit visibleChanged(visible);
}

QLabel *BaseAspect::createLabel()
{
    if (d->m_labelText.isEmpty() && d->m_labelPixmap.isNull())
        return nullptr;

    auto label = new QLabel(d->m_labelText);
    label->setTextInteractionFlags(label->textInteractionFlags() | Qt::TextSelectableByMouse);
    connect(label, &QLabel::linkActivated, this, [this](const QString &link) {
        emit labelLinkActivated(link);
    });
    if (!d->m_labelPixmap.isNull())
        label->setPixmap(d->m_labelPixmap);
    registerSubWidget(label);

    connect(this, &BaseAspect::labelTextChanged, label, [label, this] {
        label->setText(d->m_labelText);
    });
    connect(this, &BaseAspect::labelPixmapChanged, label, [label, this] {
        label->setPixmap(d->m_labelPixmap);
    });

    return label;
}

QLabel *BaseAspect::addLabeledItem(Layout &parent, QWidget *widget)
{
    if (QLabel *l = createLabel()) {
        l->setBuddy(widget);
        parent.addItem(l);
        parent.addItem(Span(std::max(d->m_spanX - 1, 1), widget));
        return l;
    }
    parent.addItem(widget);
    return {};
}

void BaseAspect::addLabeledItems(Layouting::Layout &parent, const QList<QWidget *> &widgets)
{
    if (QLabel *l = createLabel())
        parent.addItem(l);
    for (auto widget : widgets)
        parent.addItem(widget);
}

/*!
    Sets \a labelText as text for the separate label in the visual
    representation of this aspect.
*/
void BaseAspect::setLabelText(const QString &labelText)
{
    d->m_labelText = labelText;
    emit labelTextChanged();
}

/*!
    Sets \a labelPixmap as pixmap for the separate label in the visual
    representation of this aspect.
*/
void BaseAspect::setLabelPixmap(const QPixmap &labelPixmap)
{
    d->m_labelPixmap = labelPixmap;
    emit labelPixmapChanged();
}

void BaseAspect::setIcon(const QIcon &icon)
{
    d->m_icon = icon;
    if (d->m_action)
        d->m_action->setIcon(icon);
}

QIcon BaseAspect::icon() const
{
    return d->m_icon;
}

/*!
    Returns the current text for the separate label in the visual
    representation of this aspect.
*/
QString BaseAspect::labelText() const
{
    return d->m_labelText;
}

QString BaseAspect::toolTip() const
{
    return d->m_tooltip;
}

/*!
    Sets \a tooltip as tool tip for the visual representation of this aspect.
 */
void BaseAspect::setToolTip(const QString &tooltip)
{
    if (tooltip == d->m_tooltip)
        return;

    d->m_tooltip = tooltip;
    emit tooltipChanged(tooltip);
}

void BaseAspect::setUndoStack(QUndoStack *undoStack)
{
    d->m_undoStack = undoStack;
}

QUndoStack *BaseAspect::undoStack() const
{
    return d->m_undoStack;
}

bool BaseAspect::isEnabled() const
{
    if (d->m_enabler)
        return d->m_enabler->isEnabled() && d->m_enabler->volatileValue();
    return d->m_enabled;
}

void BaseAspect::setEnabled(bool enabled)
{
    if (enabled == d->m_enabled)
        return;

    d->m_enabled = enabled;
    emit enabledChanged();
}

/*!
    Makes the enabled state of this aspect depend on the checked state of \a checker.
*/
void BaseAspect::setEnabler(BoolAspect *checker)
{
    QTC_ASSERT(checker, return);

    d->m_enabler = checker;

    auto update = [this] { BaseAspect::setEnabled(isEnabled()); };

    connect(checker, &BoolAspect::volatileValueChanged, this, update);
    connect(checker, &BoolAspect::changed, this, update);
    connect(checker, &BaseAspect::enabledChanged, this, update);

    update();
}

bool BaseAspect::isReadOnly() const
{
    return d->m_readOnly;
}

void BaseAspect::setReadOnly(bool readOnly)
{
    if (readOnly == d->m_readOnly)
        return;

    d->m_readOnly = readOnly;
    emit readOnlyChanged(readOnly);
}

void BaseAspect::setSpan(int x, int y)
{
    d->m_spanX = x;
    d->m_spanY = y;
}

bool BaseAspect::isSaveAlways() const
{
    return d->m_saveAlways;
}

void BaseAspect::setSaveAlways(bool saveAlways)
{
    d->m_saveAlways = saveAlways;
}

bool BaseAspect::isAutoApply() const
{
    return d->m_autoApply;
}

/*!
    Sets auto-apply mode. When auto-apply mode is \a off, user interaction to this
    aspect's widget will not modify the \c value of the aspect until \c apply()
    is called programmatically.

    \sa setSettingsKey()
*/

void BaseAspect::setAutoApply(bool on)
{
    d->m_autoApply = on;
}

/*!
    \internal
*/
void BaseAspect::setConfigWidgetCreator(const ConfigWidgetCreator &configWidgetCreator)
{
    d->m_configWidgetCreator = configWidgetCreator;
}

/*!
    Returns the key to be used when accessing the settings.

    \sa setSettingsKey()
*/
Key BaseAspect::settingsKey() const
{
    return d->m_settingsKey;
}

/*!
    Sets the \a key to be used when accessing the settings.

    \sa settingsKey()
*/
void BaseAspect::setSettingsKey(const Key &key)
{
    d->m_settingsKey = key;
}

/*!
    Sets the \a key and \a group to be used when accessing the settings.

    \sa settingsKey()
*/
void BaseAspect::setSettingsKey(const Key &group, const Key &key)
{
    d->m_settingsKey = group + "/" + key;
}

/*!
    Immediately writes the value of this aspect into its specified
    settings, taking a potential container's settings group specification
    into account.

    \note This is expensive, so it should only be used with good reason.
*/
void BaseAspect::writeToSettingsImmediatly() const
{
    QStringList groups;
    if (d->m_container)
        groups = d->m_container->settingsGroups();
    const SettingsGroupNester nester(groups);
    writeSettings();
}

/*!
    Returns the string that should be used when this action appears in menus
    or other places that are typically used with Book style capitalization.

    If no display name is set, the label text will be used as fallback.
*/

QString BaseAspect::displayName() const
{
    return d->m_displayName.isEmpty() ? d->m_labelText : d->m_displayName;
}

/*!
    \internal
*/
QWidget *BaseAspect::createConfigWidget() const
{
    auto configWidget = d->m_configWidgetCreator ? d->m_configWidgetCreator() : nullptr;
    if (configWidget)
        registerSubWidget(configWidget);

    return configWidget;
}

QAction *BaseAspect::action()
{
    if (!d->m_action) {
        d->m_action = new QAction(labelText());
        d->m_action->setIcon(d->m_icon);
    }
    return d->m_action;
}

AspectContainer *BaseAspect::container() const
{
    return d->m_container;
}

/*!
    Adds the visual representation of this aspect to the layout with the
    specified \a parent using a layout builder.
*/
void BaseAspect::addToLayoutImpl(Layout &)
{
}

void addToLayout(Layouting::Layout *layout, const BaseAspect &aspect)
{
    aspect.addToLayout(*layout);
}

void addToLayout(Layouting::Layout *layout, const BaseAspect *aspect)
{
    aspect->addToLayout(*layout);
}

/*!
    Updates this aspect's value from user-initiated changes in the widget.

    Emits changed() if the value changed.
*/
void BaseAspect::apply()
{
    // We assume m_volatileValue to reflect current gui state as invariant after
    // signalling settled down. It's an aspect (-subclass) implementation problem
    // if this doesn't hold. Fix it up and bark.
    QTC_CHECK(!guiToVolatileValue());

    if (!volatileValueToValue()) // Nothing to do.
        return;

    Changes changes;
    changes.valueFromVolatileValue = true;
    announceChanges(changes);
}

/*!
    Discard user changes in the widget and restore widget contents from
    aspect's value.

    This has only an effect if \c isAutoApply is false.
*/
void BaseAspect::cancel()
{
    Changes changes;
    changes.volatileValueFromValue = valueToVolatileValue();
    volatileValueToGui();
    announceChanges(changes);
}

bool BaseAspect::hasAction() const
{
    return d->m_action != nullptr;
}

void BaseAspect::announceChanges(Changes changes, Announcement howToAnnounce)
{
    if (howToAnnounce == BeQuiet)
        return;

    if (changes.volatileValueFromValue || changes.volatileValueFromOutside || changes.volatileValueFromGui)
        emit volatileValueChanged();

    if (changes.valueFromOutside || changes.valueFromVolatileValue) {
        emit changed();
        if (hasAction())
            emit action()->triggered(variantValue().toBool());
    }
}

bool BaseAspect::isDirty() const
{
    return false;
}

QPointer<const BaseAspect> BaseAspect::aspectForWidget(QWidget *widget)
{
    if (!widget)
        return nullptr;
    const QVariant v = widget->property(ASPECT_PROPERTY);
    if (!v.isValid())
        return nullptr;
    return v.value<QPointer<const BaseAspect>>();
}

void BaseAspect::registerSubWidget(QWidget *widget) const
{
    widget->setEnabled(isEnabled());
    widget->setToolTip(d->m_tooltip);
    QPointer<const BaseAspect> thisPtr(this);
    const auto thisPtrVariant = QVariant::fromValue<QPointer<const BaseAspect>>(thisPtr);
    widget->setProperty(ASPECT_PROPERTY, thisPtrVariant);

    // Visible is on by default. Not setting it explicitly avoid popping
    // it up when the parent is not set yet, the normal case.
    if (!d->m_visible)
        widget->setVisible(d->m_visible);

    connect(this, &BaseAspect::enabledChanged, widget, [this, widget] {
        widget->setEnabled(d->m_enabled);
    });
    connect(this, &BaseAspect::visibleChanged, widget, &QWidget::setVisible);
    connect(this, &BaseAspect::tooltipChanged, widget, &QWidget::setToolTip);

    if (auto lineEdit = qobject_cast<QLineEdit *>(widget))
        connect(this, &BaseAspect::readOnlyChanged, lineEdit, &QLineEdit::setReadOnly);
    else if (auto textEdit = qobject_cast<QTextEdit *>(widget))
        connect(this, &BaseAspect::readOnlyChanged, textEdit, &QTextEdit::setReadOnly);
    else if (auto pathChooser = qobject_cast<PathChooser *>(widget))
        connect(this, &BaseAspect::readOnlyChanged, pathChooser, &PathChooser::setReadOnly);

    connect(this, &BaseAspect::destroyed, widget, &QObject::deleteLater);
}

void BaseAspect::setContainer(AspectContainer *container)
{
    d->setContainer(container);
}

void BaseAspect::saveToMap(Store &data, const QVariant &value,
                           const QVariant &defaultValue, const Key &key) const
{
    if (key.isEmpty() && !d->m_saveAlways)
        return;
    if (value == defaultValue)
        data.remove(key);
    else
        data.insert(key, value);
}

bool BaseAspect::skipSave() const
{
    return settingsKey().isEmpty() && !d->m_saveAlways;
}

/*!
    Retrieves the internal value of this BaseAspect from the Store \a map.
*/
void BaseAspect::fromMap(const Store &map)
{
    if (skipSave())
        return;

    const QVariant val = map.value(settingsKey(), toSettingsValue(defaultVariantValue()));
    setVariantValue(fromSettingsValue(val), BeQuiet);
}

/*!
    Stores the internal value of this BaseAspect into the Store \a map.
*/
void BaseAspect::toMap(Store &map) const
{
    saveToMap(map, toSettingsValue(variantValue()), toSettingsValue(defaultVariantValue()), settingsKey());
}

void BaseAspect::volatileToMap(Store &map) const
{
    saveToMap(map,
              toSettingsValue(volatileVariantValue()),
              toSettingsValue(defaultVariantValue()),
              settingsKey());
}

void BaseAspect::addToLayout(Layouting::Layout &parent) const
{
    const_cast<BaseAspect *>(this)->addToLayoutImpl(parent);
}

void BaseAspect::readSettings()
{
    if (skipSave())
        return;
    const QVariant val = Utils::userSettings().value(settingsKey());
    setVariantValue(val.isValid() ? fromSettingsValue(val) : defaultVariantValue(), BeQuiet);
}

void BaseAspect::writeSettings() const
{
    if (skipSave())
        return;
    Utils::userSettings().setValueWithDefault(settingsKey(),
                                              toSettingsValue(variantValue()),
                                              toSettingsValue(defaultVariantValue()));
}

QVariant BaseAspect::toSettingsValue(const QVariant &valueToSave) const
{
    return valueToSave;
}

QVariant BaseAspect::fromSettingsValue(const QVariant &savedValue) const
{
    return savedValue;
}

void BaseAspect::setMacroExpander(MacroExpander *expander)
{
    d->macroExpander()->clearSubProviders();
    if (expander)
        d->macroExpander()->registerSubProvider({this, [expander] { return expander; }});
}

MacroExpander *BaseAspect::macroExpander() const
{
    return d->macroExpander();
}

void BaseAspect::addOnChanged(QObject *guard, const Callback &callback)
{
    connect(this, &BaseAspect::changed, guard, callback);
}

void BaseAspect::addOnVolatileValueChanged(QObject *guard, const Callback &callback)
{
    connect(this, &BaseAspect::volatileValueChanged, guard, callback);
}

void BaseAspect::addOnCheckedChanged(QObject *guard, const Callback &callback)
{
    connect(this, &BaseAspect::checkedChanged, guard, callback);
}

void BaseAspect::addOnEnabledChanged(QObject *guard, const Callback &callback)
{
    connect(this, &BaseAspect::enabledChanged, guard, callback);
}

void BaseAspect::addOnLabelTextChanged(QObject *guard, const Callback &callback)
{
    connect(this, &BaseAspect::labelTextChanged, guard, callback);
}

void BaseAspect::addOnLabelPixmapChanged(QObject *guard, const Callback &callback)
{
    connect(this, &BaseAspect::labelPixmapChanged, guard, callback);
}

void BaseAspect::addMacroExpansion(QWidget *w)
{
    const auto varChooser = new VariableChooser(w);
    varChooser->addMacroExpanderProvider({this, [this] { return d->macroExpander(); }});
    if (auto pathChooser = qobject_cast<PathChooser *>(w)) {
        pathChooser->setMacroExpander(d->macroExpander());
        varChooser->addSupportedWidget(pathChooser->lineEdit());
    } else {
        varChooser->addSupportedWidget(w);
    }
}

namespace Internal {

class BoolAspectPrivate
{
public:
    BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox;
    BoolAspect::DisplayStyle m_displayStyle = BoolAspect::DisplayStyle::CheckBox;
    UndoableValue<bool> m_undoable;
};

class ToggleAspectPrivate
{
public:
    struct Data
    {
        QIcon icon;
        QString tooltip;
        QString text;
    } on, off;
};

class ColorAspectPrivate
{
public:
    bool m_alphaAllowed = true;
    bool m_withResetButton = true;
    QSize m_minimumSize{64, 0};
};

class FontFamilyAspectPrivate
{
public:
    UndoableValue<QString> m_undoable;
    QFontComboBox::FontFilters m_fontFilters = QFontComboBox::AllFonts;
};

class SelectionAspectPrivate
{
public:
    SelectionAspect::DisplayStyle m_displayStyle = SelectionAspect::DisplayStyle::RadioButtons;
    QList<SelectionAspect::Option> m_options;
    UndoableValue<int> m_undoable;
    bool m_useDataAsSavedValue = false;
};

class MultiSelectionAspectPrivate
{
public:
    explicit MultiSelectionAspectPrivate(MultiSelectionAspect *q) : q(q) {}

    bool setValueSelectedHelper(const QString &value, bool on);

    MultiSelectionAspect *q;
    QStringList m_allValues;
    MultiSelectionAspect::DisplayStyle m_displayStyle
        = MultiSelectionAspect::DisplayStyle::ListView;

    // These are all owned by the configuration widget.
    QPointer<QListWidget> m_listView;
};

template<class Widget>
void setReadOnly(Widget *w, bool readOnly)
{
    w->setReadOnly(readOnly);
}
template<>
void setReadOnly<QLabel>(QLabel *, bool)
{}

class CheckableAspectImplementation
{
public:
    void fromMap(const Store &map)
    {
        if (m_checked)
            m_checked->fromMap(map);
    }

    void toMap(Store &map)
    {
        if (m_checked)
            m_checked->toMap(map);
    }

    void volatileToMap(Store &map)
    {
        if (m_checked)
            m_checked->volatileToMap(map);
    }

    template<class Widget>
    void updateWidgetFromCheckStatus(BaseAspect *aspect, Widget *w)
    {
        const bool enabled = !m_checked || m_checked->volatileValue();
        if (m_uncheckedSemantics == UncheckedSemantics::Disabled)
            w->setEnabled(enabled && aspect->isEnabled());
        else
            setReadOnly(w, !enabled || aspect->isReadOnly());
    }

    void setUncheckedSemantics(UncheckedSemantics semantics)
    {
        m_uncheckedSemantics = semantics;
    }

    bool isChecked() const
    {
        QTC_ASSERT(m_checked, return false);
        return m_checked->value();
    }

    void setChecked(bool checked)
    {
        QTC_ASSERT(m_checked, return);
        m_checked->setValue(checked);
    }

    bool isCheckable() const { return bool(m_checked); }

    void makeCheckable(CheckBoxPlacement checkBoxPlacement, const QString &checkerLabel,
                       const Key &checkerKey, BaseAspect *aspect)
    {
        QTC_ASSERT(!m_checked, return);
        m_checkBoxPlacement = checkBoxPlacement;
        m_checked.reset(new BoolAspect);
        m_checked->setLabel(checkerLabel, checkBoxPlacement == CheckBoxPlacement::Top
                                              ? BoolAspect::LabelPlacement::InExtraLabel
                                              : BoolAspect::LabelPlacement::AtCheckBox);
        m_checked->setSettingsKey(checkerKey);
        m_checked->addOnChanged(aspect, [aspect] {
            // FIXME: Check.
            aspect->valueToVolatileValue();
            aspect->volatileValueToGui();
            emit aspect->changed();
            aspect->checkedChanged();
        });
        m_checked->addOnVolatileValueChanged(aspect, [aspect] {
            // FIXME: Check.
            aspect->valueToVolatileValue();
            aspect->volatileValueToGui();
        });

        aspect->valueToVolatileValue();
        aspect->volatileValueToGui();
    }

    void addToLayoutFirst(Layout &parent)
    {
        if (m_checked) {
            if (m_checkBoxPlacement == CheckBoxPlacement::Top) {
                m_checked->addToLayoutImpl(parent);
                parent.flush();
            } else if (m_checkBoxPlacement == CheckBoxPlacement::Left) {
                m_checked->addToLayoutImpl(parent);
            }
        }
    }

    void addToLayoutLast(Layout &parent)
    {
        if (m_checked && m_checkBoxPlacement == CheckBoxPlacement::Right)
            m_checked->addToLayoutImpl(parent);
    }

    CheckBoxPlacement m_checkBoxPlacement = CheckBoxPlacement::Right;
    UncheckedSemantics m_uncheckedSemantics = UncheckedSemantics::Disabled;
    std::unique_ptr<BoolAspect> m_checked;
};

class StringAspectPrivate
{
public:
    StringAspect::DisplayStyle m_displayStyle = StringAspect::LabelDisplay;
    std::function<QString(const QString &)> m_displayFilter;

    Qt::TextElideMode m_elideMode = Qt::ElideNone;
    QString m_placeHolderText;
    Key m_historyCompleterKey;
    StringAspect::ValueAcceptor m_valueAcceptor;
    std::optional<FancyLineEdit::ValidationFunction> m_validator;
    std::function<QValidator *(QObject *parent)> m_validatorFactory;

    CheckableAspectImplementation m_checkerImpl;

    bool m_undoRedoEnabled = true;
    bool m_acceptRichText = false;
    bool m_showToolTipOnLabel = false;
    bool m_useResetButton = false;
    bool m_autoApplyOnEditingFinished = false;
    bool m_validatePlaceHolder = false;

    FilePath m_rightSideIconPath;
    int m_minimumHeight = 0;
    QPointer<QCompleter> m_completer;

    UndoableValue<QString> undoable;
};

class IntegerAspectPrivate
{
public:
    std::optional<qint64> m_minimumValue;
    std::optional<qint64> m_maximumValue;
    int m_displayIntegerBase = 10;
    qint64 m_displayScaleFactor = 1;
    QString m_prefix;
    QString m_suffix;
    QString m_specialValueText;
    int m_singleStep = 1;
    QPointer<QSpinBox> m_spinBox; // Owned by configuration widget
};

class DoubleAspectPrivate
{
public:
    std::optional<double> m_minimumValue;
    std::optional<double> m_maximumValue;
    QString m_prefix;
    QString m_suffix;
    QString m_specialValueText;
    double m_singleStep = 1;
    QPointer<QDoubleSpinBox> m_spinBox; // Owned by configuration widget
};

class StringListAspectPrivate
{
public:
    UndoableValue<QStringList> m_undoable;
    bool m_allowAdding{true};
    bool m_allowRemoving{true};
    bool m_allowEditing{true};
    StringListAspect::DisplayStyle m_displayStyle{StringListAspect::DisplayStyle::ListView};
};

class FilePathListAspectPrivate
{
public:
    UndoableValue<QStringList> undoable;
    QString placeHolderText;
};

class TextDisplayPrivate
{
public:
    QString m_message;
    InfoLabel::InfoType m_type;
    bool m_wordWrap = true;
    QPointer<InfoLabel> m_label;
};

} // Internal

/*!
    \enum Utils::StringAspect::DisplayStyle
    \inmodule QtCreator

    The DisplayStyle enum describes the main visual characteristics of a
    string aspect.

      \value LabelDisplay
             Based on QLabel, used for text that cannot be changed by the
             user in this place, for example names of executables that are
             defined in the build system.

      \value LineEditDisplay
             Based on QLineEdit, used for user-editable strings that usually
             fit on a line.

      \value TextEditDisplay
             Based on QTextEdit, used for user-editable strings that often
             do not fit on a line.

      \value PasswordLineEditDisplay
             Based on QLineEdit, used for password strings

    \sa Utils::PathChooser
*/

/*!
    \class Utils::StringAspect
    \inmodule QtCreator

    \brief A string aspect is a string-like property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting.

    String aspects can represent for example a parameter for an external commands,
    paths in a file system, or simply strings.

    The string can be displayed using a QLabel, QLineEdit, QTextEdit or
    Utils::PathChooser.

    The visual representation often contains a label in front of the display
    of the actual value.
*/

/*!
    Constructs the string aspect \a container.
 */

StringAspect::StringAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::StringAspectPrivate)
{
    setSpan(2, 1); // Default: Label + something
}

/*!
    \internal
*/
StringAspect::~StringAspect() = default;

/*!
    \internal
*/
void StringAspect::setValueAcceptor(StringAspect::ValueAcceptor &&acceptor)
{
    d->m_valueAcceptor = std::move(acceptor);
}

/*!
    \reimp
*/
void StringAspect::fromMap(const Store &map)
{
    if (!skipSave())
        setValue(map.value(settingsKey(), defaultValue()).toString(), BeQuiet);
    d->m_checkerImpl.fromMap(map);
}

/*!
    \reimp
*/
void StringAspect::toMap(Store &map) const
{
    saveToMap(map, value(), defaultValue(), settingsKey());
    d->m_checkerImpl.toMap(map);
}

void StringAspect::volatileToMap(Store &map) const
{
    saveToMap(map, volatileValue(), defaultValue(), settingsKey());
    d->m_checkerImpl.volatileToMap(map);
}

/*!
    \internal
*/
void StringAspect::setShowToolTipOnLabel(bool show)
{
    d->m_showToolTipOnLabel = show;
    volatileValueToGui();
}

/*!
    Sets a \a displayFilter for fine-tuning the visual appearance
    of the value of this string aspect.
*/
void StringAspect::setDisplayFilter(const std::function<QString(const QString &)> &displayFilter)
{
    d->m_displayFilter = displayFilter;
}

/*!
    Selects the main display characteristics of the aspect according to
    \a displayStyle.

    \note Not all StringAspect features are available with all display styles.

    \sa Utils::StringAspect::DisplayStyle
*/
void StringAspect::setDisplayStyle(DisplayStyle displayStyle)
{
    d->m_displayStyle = displayStyle;
}

/*!
    Sets \a placeHolderText as place holder for line and text displays.
*/
void StringAspect::setPlaceHolderText(const QString &placeHolderText)
{
    if (d->m_placeHolderText == placeHolderText)
        return;

    d->m_placeHolderText = placeHolderText;
    emit placeholderTextChanged(placeHolderText);
}

/*!
    Sets \a elideMode as label elide mode.
*/
void StringAspect::setElideMode(Qt::TextElideMode elideMode)
{
    if (d->m_elideMode == elideMode)
        return;
    d->m_elideMode = elideMode;
    emit elideModeChanged(elideMode);
}

/*!
    Sets \a historyCompleterKey as key for the history completer settings for
    line edits and path chooser displays.

    \sa Utils::PathChooser::setExpectedKind()
*/
void StringAspect::setHistoryCompleter(const Key &historyCompleterKey)
{
    d->m_historyCompleterKey = historyCompleterKey;
    emit historyCompleterKeyChanged(historyCompleterKey);
}

void StringAspect::setAcceptRichText(bool acceptRichText)
{
    d->m_acceptRichText = acceptRichText;
    emit acceptRichTextChanged(acceptRichText);
}

void StringAspect::setUseResetButton()
{
    d->m_useResetButton = true;
}

void StringAspect::setValidationFunction(const FancyLineEdit::ValidationFunction &validator)
{
    d->m_validator = validator;
    emit validationFunctionChanged(validator);
}

void StringAspect::setValidatorFactory(
    const std::function<QValidator *(QObject *parent)> &validatorFactory)
{
    d->m_validatorFactory = validatorFactory;
}

void StringAspect::setAutoApplyOnEditingFinished(bool applyOnEditingFinished)
{
    d->m_autoApplyOnEditingFinished = applyOnEditingFinished;
}

void StringAspect::addToLayoutImpl(Layout &parent)
{
    d->m_checkerImpl.addToLayoutFirst(parent);

    const QString displayedString = d->m_displayFilter ? d->m_displayFilter(volatileValue())
                                                       : volatileValue();

    switch (d->m_displayStyle) {
    case PasswordLineEditDisplay:
    case LineEditDisplay: {
        auto lineEditDisplay = createSubWidget<FancyLineEdit>();
        addMacroExpansion(lineEditDisplay);
        lineEditDisplay->setPlaceholderText(d->m_placeHolderText);
        lineEditDisplay->setMinimumHeight(d->m_minimumHeight);

        if (d->m_completer)
            lineEditDisplay->setSpecialCompleter(d->m_completer);

        if (!d->m_rightSideIconPath.isEmpty()) {
            QIcon icon(d->m_rightSideIconPath.toFSPathString());
            QTC_CHECK(!icon.isNull());
            lineEditDisplay->setButtonIcon(FancyLineEdit::Right, icon);
            lineEditDisplay->setButtonVisible(FancyLineEdit::Right, true);
            connect(lineEditDisplay, &FancyLineEdit::rightButtonClicked,
                    this, &StringAspect::rightSideIconClicked);
        }

        if (!d->m_historyCompleterKey.isEmpty())
            lineEditDisplay->setHistoryCompleter(d->m_historyCompleterKey);

        connect(this,
                &StringAspect::historyCompleterKeyChanged,
                lineEditDisplay,
                [lineEditDisplay](const Key &historyCompleterKey) {
                    lineEditDisplay->setHistoryCompleter(historyCompleterKey);
                });
        connect(this,
                &StringAspect::placeholderTextChanged,
                lineEditDisplay,
                &FancyLineEdit::setPlaceholderText);

        if (d->m_validator)
            lineEditDisplay->setValidationFunction(*d->m_validator);
        else if (d->m_validatorFactory)
            lineEditDisplay->setValidator(d->m_validatorFactory(lineEditDisplay));

        lineEditDisplay->setTextKeepingActiveCursor(displayedString);
        lineEditDisplay->setReadOnly(isReadOnly());
        lineEditDisplay->setValidatePlaceHolder(d->m_validatePlaceHolder);

        QLabel *label = addLabeledItem(parent, lineEditDisplay);

        d->m_checkerImpl.updateWidgetFromCheckStatus(this, lineEditDisplay);
        if (label)
            d->m_checkerImpl.updateWidgetFromCheckStatus(this, label);

        if (d->m_checkerImpl.m_checked.get()) {
            connect(
                d->m_checkerImpl.m_checked.get(),
                &BoolAspect::volatileValueChanged,
                lineEditDisplay,
                [this, lineEditDisplay] {
                    d->m_checkerImpl.updateWidgetFromCheckStatus(this, lineEditDisplay);
                });
            if (label) {
                connect(
                    d->m_checkerImpl.m_checked.get(),
                    &BoolAspect::volatileValueChanged,
                    label,
                    [this, label] { d->m_checkerImpl.updateWidgetFromCheckStatus(this, label); });
            }
        }

        if (d->m_useResetButton) {
            auto resetButton = createSubWidget<QPushButton>(Tr::tr("Reset"));
            resetButton->setEnabled(lineEditDisplay->text() != defaultValue());
            connect(resetButton, &QPushButton::clicked, lineEditDisplay, [this, lineEditDisplay] {
                lineEditDisplay->setText(defaultValue());
            });
            connect(lineEditDisplay,
                    &QLineEdit::textChanged,
                    resetButton,
                    [this, lineEditDisplay, resetButton] {
                        resetButton->setEnabled(lineEditDisplay->text() != defaultValue());
                    });
            parent.addItem(resetButton);
        }
        connect(lineEditDisplay, &FancyLineEdit::validChanged, this, &StringAspect::validChanged);
        volatileValueToGui();
        if (isAutoApply() && d->m_autoApplyOnEditingFinished) {
            connect(lineEditDisplay, &FancyLineEdit::editingFinished, this, [this, lineEditDisplay] {
                if (lineEditDisplay->text() != d->undoable.get()) {
                    d->undoable.set(undoStack(), lineEditDisplay->text());
                    handleGuiChanged();
                }
            });
        } else {
            connect(lineEditDisplay, &QLineEdit::textChanged, this, [this, lineEditDisplay] {
                d->undoable.set(undoStack(), lineEditDisplay->text());
                handleGuiChanged();
            });
        }
        if (d->m_displayStyle == PasswordLineEditDisplay) {
            auto showPasswordButton = createSubWidget<ShowPasswordButton>();
            lineEditDisplay->setEchoMode(QLineEdit::PasswordEchoOnEdit);
            parent.addItem(showPasswordButton);
            connect(showPasswordButton,
                    &ShowPasswordButton::toggled,
                    lineEditDisplay,
                    [showPasswordButton, lineEditDisplay] {
                        lineEditDisplay->setEchoMode(showPasswordButton->isChecked()
                                                         ? QLineEdit::Normal
                                                         : QLineEdit::PasswordEchoOnEdit);
                    });
        }

        connect(&d->undoable.m_signal,
                &UndoSignaller::changed,
                lineEditDisplay,
                [this, lineEditDisplay] {
                    if (lineEditDisplay->text() != d->undoable.get())
                        lineEditDisplay->setTextKeepingActiveCursor(d->undoable.get());

                    lineEditDisplay->validate();
                });

        break;
    }
    case TextEditDisplay: {
        auto textEditDisplay = createSubWidget<QTextEdit>();
        addMacroExpansion(textEditDisplay);
        textEditDisplay->setPlaceholderText(d->m_placeHolderText);
        textEditDisplay->setUndoRedoEnabled(false);
        textEditDisplay->setAcceptRichText(d->m_acceptRichText);
        textEditDisplay->setTextInteractionFlags(Qt::TextEditorInteraction);
        textEditDisplay->setText(displayedString);
        textEditDisplay->setReadOnly(isReadOnly());
        QLabel *label = addLabeledItem(parent, textEditDisplay);
        d->m_checkerImpl.updateWidgetFromCheckStatus(this, textEditDisplay);
        if (label)
            d->m_checkerImpl.updateWidgetFromCheckStatus(this, label);

        if (d->m_checkerImpl.m_checked) {
            connect(d->m_checkerImpl.m_checked.get(),
                    &BoolAspect::volatileValueChanged,
                    textEditDisplay,
                    [this, textEditDisplay] {
                        d->m_checkerImpl.updateWidgetFromCheckStatus(this, textEditDisplay);
                    });
            if (label) {
                connect(
                    d->m_checkerImpl.m_checked.get(),
                    &BoolAspect::volatileValueChanged,
                    label,
                    [this, label] { d->m_checkerImpl.updateWidgetFromCheckStatus(this, label); });
            }
        }

        volatileValueToGui();
        connect(this,
                &StringAspect::acceptRichTextChanged,
                textEditDisplay,
                &QTextEdit::setAcceptRichText);
        connect(this,
                &StringAspect::placeholderTextChanged,
                textEditDisplay,
                &QTextEdit::setPlaceholderText);

        connect(textEditDisplay, &QTextEdit::textChanged, this, [this, textEditDisplay] {
            if (textEditDisplay->toPlainText() != d->undoable.get()) {
                d->undoable.set(undoStack(), textEditDisplay->toPlainText());
                handleGuiChanged();
            }
        });

        connect(&d->undoable.m_signal,
                &UndoSignaller::changed,
                textEditDisplay,
                [this, textEditDisplay] {
                    if (textEditDisplay->toPlainText() != d->undoable.get())
                        textEditDisplay->setText(d->undoable.get());
                });
        break;
    }
    case LabelDisplay: {
        auto labelDisplay = createSubWidget<ElidingLabel>();
        labelDisplay->setElideMode(d->m_elideMode);
        labelDisplay->setTextInteractionFlags(Qt::TextSelectableByMouse);
        labelDisplay->setText(displayedString);
        labelDisplay->setToolTip(d->m_showToolTipOnLabel ? displayedString : toolTip());
        connect(this, &StringAspect::elideModeChanged, labelDisplay, &ElidingLabel::setElideMode);
        addLabeledItem(parent, labelDisplay);

        connect(&d->undoable.m_signal, &UndoSignaller::changed, labelDisplay, [this, labelDisplay] {
            labelDisplay->setText(d->undoable.get());
            labelDisplay->setToolTip(d->m_showToolTipOnLabel ? d->undoable.get() : toolTip());
        });

        break;
    }
    }

    d->m_checkerImpl.addToLayoutLast(parent);
}

QString StringAspect::expandedValue() const
{
    return operator()();
}

QString StringAspect::operator()() const
{
    if (!m_value.isEmpty()) {
        if (auto expander = macroExpander())
            return expander->expand(m_value);
    }
    return m_value;
}

bool StringAspect::guiToVolatileValue()
{
    return updateStorage(m_volatileValue, d->undoable.get());
}

bool StringAspect::volatileValueToValue()
{
    if (d->m_valueAcceptor) {
        if (const std::optional<QString> tmp = d->m_valueAcceptor(m_value, m_volatileValue))
           return updateStorage(m_value, *tmp);
        return false;
    }
    return updateStorage(m_value, m_volatileValue);
}

bool StringAspect::valueToVolatileValue()
{
    const QString val = d->m_displayFilter ? d->m_displayFilter(m_value) : m_value;
    return updateStorage(m_volatileValue, val);
}

void StringAspect::volatileValueToGui()
{
    d->undoable.setWithoutUndo(m_volatileValue);
}

/*!
    Adds a check box with a \a checkerLabel according to \a checkBoxPlacement
    to the line edit.

    The state of the check box is made persistent when using a non-emtpy
    \a checkerKey.
*/
void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement,
                                 const QString &checkerLabel, const Key &checkerKey)
{
    d->m_checkerImpl.makeCheckable(checkBoxPlacement, checkerLabel, checkerKey, this);
}

bool StringAspect::isChecked() const
{
    return d->m_checkerImpl.isChecked();
}

void StringAspect::setChecked(bool checked)
{
    return d->m_checkerImpl.setChecked(checked);
}

void StringAspect::addOnRightSideIconClicked(QObject *guard,
                                             const std::function<void ()> &callback)
{
    connect(this, &StringAspect::rightSideIconClicked, guard, callback);
}

void StringAspect::setMinimumHeight(int height)
{
    d->m_minimumHeight = height;
}

void StringAspect::setCompleter(QCompleter *completer)
{
    d->m_completer = completer;
}

void StringAspect::setRightSideIconPath(const FilePath &path)
{
    d->m_rightSideIconPath = path;
}


/*!
    \class Utils::FilePathAspect
    \inmodule QtCreator

    \brief A file path aspect is shallow wrapper around a Utils::StringAspect that
    represents a file in the file system.

    It is displayed by default using Utils::PathChooser.

    The visual representation often contains a label in front of the display
    of the actual value.

    \sa Utils::StringAspect
*/

class Internal::FilePathAspectPrivate
{
public:
    std::function<QString(const QString &)> m_displayFilter;

    QString m_placeHolderText;
    QString m_prompDialogFilter;
    QString m_prompDialogTitle;
    QStringList m_commandVersionArguments;
    Key m_historyCompleterKey;
    PathChooser::Kind m_expectedKind = PathChooser::File;
    Environment m_environment;
    QPointer<PathChooser> m_pathChooserDisplay;
    Lazy<FilePath> m_baseDirectory;
    FilePath m_initialBrowsePathBackup;
    StringAspect::ValueAcceptor m_valueAcceptor;
    std::optional<FancyLineEdit::ValidationFunction> m_validator;
    std::optional<FilePath> m_effectiveBinary;
    std::function<void()> m_openTerminal;

    CheckableAspectImplementation m_checkerImpl;

    bool m_showToolTipOnLabel = false;
    bool m_fileDialogOnly = false;
    bool m_autoApplyOnEditingFinished = false;
    bool m_allowPathFromDevice = true;
    bool m_validatePlaceHolder = false;
    FilePaths m_valueAlternatives;

    Guard m_editFinishedGuard;
};

FilePathAspect::FilePathAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::FilePathAspectPrivate)
{
    setSpan(2, 1); // Default: Label + something

    addDataExtractor(this, &FilePathAspect::value, &Data::value);
    addDataExtractor(this, &FilePathAspect::operator(), &Data::filePath);

    connect(this, &BaseAspect::changed, this, [this] { d->m_effectiveBinary.reset(); });
}

FilePathAspect::~FilePathAspect() = default;

/*!
    Returns the value of this aspect as \c Utils::FilePath.

    \note This simply uses \c FilePath::fromUserInput() for the
    conversion. It does not use any check that the value is actually
    a valid file path.
*/

FilePath FilePathAspect::operator()() const
{
    return expandedValue();
}

FilePath FilePathAspect::expandedValue() const
{
    const QString value = TypedAspect::value();
    if (!value.isEmpty()) {
        if (auto expander = macroExpander())
            return FilePath::fromUserInput(expander->expand(value));
    }
    return FilePath::fromUserInput(value);
}

FilePath FilePathAspect::expandedVolatileValue() const
{
    const QString value = TypedAspect::volatileValue();
    if (!value.isEmpty()) {
        if (auto expander = macroExpander())
            return FilePath::fromUserInput(expander->expand(value));
    }
    return FilePath::fromUserInput(value);
}

/*!
    Returns the full path of the set command. Only makes a difference if
    expected kind is \c Command or \c ExistingCommand and the current
    file path is an executable provided without its path.
    Performs a lookup in PATH if necessary.
 */
FilePath FilePathAspect::effectiveBinary() const
{
    if (d->m_effectiveBinary)
        return *d->m_effectiveBinary;

    d->m_effectiveBinary.emplace(
        PathChooser::expandPath(expandedValue(), nullptr, {}, {}, d->m_expectedKind));
    return *d->m_effectiveBinary;
}

QString FilePathAspect::value() const
{
    return TypedAspect::value();
}

/*!
    Sets the value of this file path aspect to \a filePath.

    If \a howToAnnounce is set to \c DoEmit, emits the \c valueChanged signal.

    \note This does not use any check that the value is actually
    a file path.
*/

void FilePathAspect::setValue(const FilePath &filePath, Announcement howToAnnounce)
{
    TypedAspect::setValue(filePath.toUserOutput(), howToAnnounce);
}

void FilePathAspect::setValue(const QString &filePath, Announcement howToAnnounce)
{
    TypedAspect::setValue(filePath, howToAnnounce);
}

void FilePathAspect::setDefaultValue(const QString &filePath)
{
    TypedAspect::setDefaultValue(filePath);
}

void FilePathAspect::setDefaultPathValue(const FilePath &filePath)
{
    TypedAspect::setDefaultValue(filePath.toUserOutput());
}

/*!
    Adds a check box with a \a checkerLabel according to \a checkBoxPlacement
    to the line edit.

    The state of the check box is made persistent when using a non-emtpy
    \a checkerKey.
*/
void FilePathAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement,
                                   const QString &checkerLabel,
                                   const Key &checkerKey)
{
    d->m_checkerImpl.makeCheckable(checkBoxPlacement, checkerLabel, checkerKey, this);
}

bool FilePathAspect::isChecked() const
{
    return d->m_checkerImpl.isChecked();
}

void FilePathAspect::setChecked(bool checked)
{
    return d->m_checkerImpl.setChecked(checked);
}

void FilePathAspect::setValueAcceptor(ValueAcceptor &&acceptor)
{
    d->m_valueAcceptor = std::move(acceptor);
}

bool FilePathAspect::isCheckable() const
{
    return d->m_checkerImpl.isCheckable();
}

bool FilePathAspect::guiToVolatileValue()
{
    if (d->m_pathChooserDisplay)
        return updateStorage(m_volatileValue, d->m_pathChooserDisplay->lineEdit()->text());
    return false;
}

bool FilePathAspect::volatileValueToValue()
{
    if (d->m_valueAcceptor) {
        if (const std::optional<QString> tmp = d->m_valueAcceptor(m_value, m_volatileValue))
           return updateStorage(m_value, *tmp);
        return false;
    }
    return updateStorage(m_value, m_volatileValue);
}

bool FilePathAspect::valueToVolatileValue()
{
    const QString val = d->m_displayFilter ? d->m_displayFilter(m_value) : m_value;
    return updateStorage(m_volatileValue, val);
}

void FilePathAspect::volatileValueToGui()
{
    if (d->m_pathChooserDisplay) {
        d->m_pathChooserDisplay->lineEdit()->setText(m_volatileValue);
        d->m_checkerImpl.updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data());
    }

    validateInput();
}

PathChooser *FilePathAspect::pathChooser() const
{
    return d->m_pathChooserDisplay.data();
}

void FilePathAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    d->m_checkerImpl.addToLayoutFirst(parent);

    const QString displayedString = d->m_displayFilter ? d->m_displayFilter(value()) : value();

    d->m_pathChooserDisplay = createSubWidget<PathChooser>();
    addMacroExpansion(d->m_pathChooserDisplay);
    d->m_pathChooserDisplay->setExpectedKind(d->m_expectedKind);
    if (!d->m_historyCompleterKey.isEmpty())
        d->m_pathChooserDisplay->setHistoryCompleter(d->m_historyCompleterKey);

    if (d->m_validator)
        d->m_pathChooserDisplay->setValidationFunction(*d->m_validator);
    d->m_pathChooserDisplay->setEnvironment(d->m_environment);
    d->m_pathChooserDisplay->setBaseDirectory(d->m_baseDirectory);
    d->m_pathChooserDisplay->setInitialBrowsePathBackup(d->m_initialBrowsePathBackup);
    d->m_pathChooserDisplay->setOpenTerminalHandler(d->m_openTerminal);
    d->m_pathChooserDisplay->setPromptDialogFilter(d->m_prompDialogFilter);
    d->m_pathChooserDisplay->setPromptDialogTitle(d->m_prompDialogTitle);
    d->m_pathChooserDisplay->setCommandVersionArguments(d->m_commandVersionArguments);
    d->m_pathChooserDisplay->setAllowPathFromDevice(d->m_allowPathFromDevice);
    d->m_pathChooserDisplay->setReadOnly(isReadOnly());
    d->m_pathChooserDisplay->lineEdit()->setValidatePlaceHolder(d->m_validatePlaceHolder);
    d->m_pathChooserDisplay->setValueAlternatives(d->m_valueAlternatives);
    if (defaultValue() == value())
        d->m_pathChooserDisplay->setDefaultValue(FilePath::fromUserInput(defaultValue()));
    else
        d->m_pathChooserDisplay->setFilePath(FilePath::fromUserInput(displayedString));
    // do not override default value with placeholder, but use placeholder if default is empty
    if (d->m_pathChooserDisplay->lineEdit()->placeholderText().isEmpty())
        d->m_pathChooserDisplay->lineEdit()->setPlaceholderText(d->m_placeHolderText);
    d->m_checkerImpl.updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data());
    addLabeledItem(parent, d->m_pathChooserDisplay);
    connect(d->m_pathChooserDisplay, &PathChooser::validChanged, this, &FilePathAspect::validChanged);
    volatileValueToGui();
    if (isAutoApply() && d->m_autoApplyOnEditingFinished) {
        connect(d->m_pathChooserDisplay, &PathChooser::editingFinished, this, [this] {
            if (d->m_editFinishedGuard.isLocked())
                return;
            GuardLocker lk(d->m_editFinishedGuard);
            handleGuiChanged();
        });
        connect(d->m_pathChooserDisplay, &PathChooser::browsingFinished,
                this, &FilePathAspect::handleGuiChanged);
    } else {
        connect(d->m_pathChooserDisplay, &PathChooser::textChanged,
                this, &FilePathAspect::handleGuiChanged);
    }

    d->m_checkerImpl.addToLayoutLast(parent);
}

/*!
    \reimp
*/
void FilePathAspect::fromMap(const Store &map)
{
    if (!skipSave())
        setValue(map.value(settingsKey(), defaultValue()).toString(), BeQuiet);
    d->m_checkerImpl.fromMap(map);
}

/*!
    \reimp
*/
void FilePathAspect::toMap(Store &map) const
{
    saveToMap(map, value(), defaultValue(), settingsKey());
    d->m_checkerImpl.toMap(map);
}

void FilePathAspect::volatileToMap(Store &map) const
{
    saveToMap(map, volatileValue(), defaultValue(), settingsKey());
    d->m_checkerImpl.volatileToMap(map);
}

void FilePathAspect::setFocusToInputField()
{
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setFocus();
}

void FilePathAspect::setPromptDialogFilter(const QString &filter)
{
    d->m_prompDialogFilter = filter;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setPromptDialogFilter(filter);
}

void FilePathAspect::setPromptDialogTitle(const QString &title)
{
    d->m_prompDialogTitle = title;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setPromptDialogTitle(title);
}

void FilePathAspect::setCommandVersionArguments(const QStringList &arguments)
{
    d->m_commandVersionArguments = arguments;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setCommandVersionArguments(arguments);
}

void FilePathAspect::setAllowPathFromDevice(bool allowPathFromDevice)
{
    d->m_allowPathFromDevice = allowPathFromDevice;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setAllowPathFromDevice(allowPathFromDevice);
}

void FilePathAspect::setValidatePlaceHolder(bool validatePlaceHolder)
{
    d->m_validatePlaceHolder = validatePlaceHolder;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->lineEdit()->setValidatePlaceHolder(validatePlaceHolder);
}

void FilePathAspect::setShowToolTipOnLabel(bool show)
{
    d->m_showToolTipOnLabel = show;
    volatileValueToGui();
}

void FilePathAspect::setAutoApplyOnEditingFinished(bool applyOnEditingFinished)
{
    d->m_autoApplyOnEditingFinished = applyOnEditingFinished;
}

void FilePathAspect::setValueAlternatives(const FilePaths &candidates)
{
    d->m_valueAlternatives = candidates;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setValueAlternatives(candidates);
}

/*!
  Sets \a expectedKind as expected kind for path chooser displays.

  \sa Utils::PathChooser::setExpectedKind()
*/
void FilePathAspect::setExpectedKind(const PathChooser::Kind expectedKind)
{
    if (d->m_expectedKind != expectedKind) {
        d->m_expectedKind = expectedKind;
        d->m_effectiveBinary.reset();
        if (d->m_pathChooserDisplay)
            d->m_pathChooserDisplay->setExpectedKind(expectedKind);
    }
}

void FilePathAspect::setEnvironment(const Environment &env)
{
    d->m_environment = env;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setEnvironment(env);
}

void FilePathAspect::setBaseDirectory(const Lazy<FilePath> &baseDirectory)
{
    d->m_baseDirectory = baseDirectory;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setBaseDirectory(baseDirectory);
}

void FilePathAspect::setInitialBrowsePathBackup(const FilePath &initialBrowsePathBackup)
{
    d->m_initialBrowsePathBackup = initialBrowsePathBackup;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setInitialBrowsePathBackup(initialBrowsePathBackup);
}

void FilePathAspect::setPlaceHolderText(const QString &placeHolderText)
{
    d->m_placeHolderText = placeHolderText;
}

void FilePathAspect::setValidationFunction(const FancyLineEdit::ValidationFunction &validator)
{
    d->m_validator = validator;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setValidationFunction(*d->m_validator);
}

void FilePathAspect::setDisplayFilter(const std::function<QString (const QString &)> &displayFilter)
{
    d->m_displayFilter = displayFilter;
}

void FilePathAspect::setHistoryCompleter(const Key &historyCompleterKey)
{
    d->m_historyCompleterKey = historyCompleterKey;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setHistoryCompleter(historyCompleterKey);
}

void FilePathAspect::validateInput()
{
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->triggerChanged();
}

void FilePathAspect::setOpenTerminalHandler(const std::function<void ()> &openTerminal)
{
    d->m_openTerminal = openTerminal;
    if (d->m_pathChooserDisplay)
        d->m_pathChooserDisplay->setOpenTerminalHandler(openTerminal);
}

/*!
    \class Utils::ColorAspect
    \inmodule QtCreator

    \brief A color aspect is a color property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting.

    The color aspect is displayed using a QtColorButton.
*/

ColorAspect::ColorAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::ColorAspectPrivate)
{
    setDefaultValue(QColor::fromRgb(0, 0, 0));
    setSpan(1, 1);
}

ColorAspect::~ColorAspect() = default;

void ColorAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    auto button = createSubWidget<QtColorButton>();
    button->setColor(volatileValue());
    button->setAlphaAllowed(d->m_alphaAllowed);
    button->setMinimumSize(d->m_minimumSize);

    connect(button, &QtColorButton::colorChanged, this, [this](const QColor &c) {
        setVolatileValue(c);
    });

    addOnVolatileValueChanged(button, [this, button] {
        if (button->color() != volatileValue())
            button->setColor(volatileValue());
    });

    if (d->m_withResetButton) {
        auto resetButton = createSubWidget<QPushButton>(Tr::tr("Reset"));
        resetButton->setToolTip(Tr::tr("Reset to default.", "Color"));
        connect(resetButton, &QAbstractButton::clicked, this, [this] {
            setVolatileValue(defaultValue());
        });
        addLabeledItems(parent, {button, resetButton});
    } else {
        QMenu *menu = new QMenu(button);
        QAction *resetAction = menu->addAction(Tr::tr("Reset to default"), this, [this] {
            setVolatileValue(defaultValue());
        });
        resetAction->setIcon(button->generatePixmap());
        resetAction->setIconVisibleInMenu(true);
        button->setMenu(menu);
        button->setToolTip(
            QStringList{toolTip(), Tr::tr("Press and Hold to reset to default.")}.join('\n'));
        addLabeledItem(parent, button);
    }
}

void ColorAspect::setAlphaAllowed(bool allowed)
{
    d->m_alphaAllowed = allowed;
}

void ColorAspect::setWithResetButton(bool withResetButton)
{
    d->m_withResetButton = withResetButton;
}

void ColorAspect::setMinimumSize(const QSize &size)
{
    d->m_minimumSize = size;
}

/*!
    \class Utils::FontAspect
    \inmodule QtCreator

    \brief A font aspect is a font property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting.

    The font aspect is displayed using a QFontComboBox.
*/

FontFamilyAspect::FontFamilyAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::FontFamilyAspectPrivate)
{
    setSpan(2, 1); // Default: Label + Combobox
}

FontFamilyAspect::~FontFamilyAspect() = default;

void FontFamilyAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    if (QLabel *l = createLabel())
        parent.addItem(l);

    auto fontComboBox = createSubWidget<QFontComboBox>();
    fontComboBox->setFontFilters(d->m_fontFilters);
    // Note: The extra QFontInfo hoop below is needed to get an actually
    // resolved for on the system,  otherwise asking "Monospace" can result
    // in "Dejavu Sans Mono" being selected.
    fontComboBox->setCurrentFont(QFontInfo(QFont(value())).family());
    parent.addItem(fontComboBox);

    connect(fontComboBox, &QFontComboBox::currentTextChanged, this, [this](const QString &text) {
        d->m_undoable.set(undoStack(), text);
        updateStorage(m_volatileValue, text);
        emit volatileValueChanged();
    });
}

void FontFamilyAspect::setFontFilters(QFontComboBox::FontFilters fontFilters)
{
    d->m_fontFilters = fontFilters;
}

void FontFamilyAspect::volatileValueToGui()
{
    d->m_undoable.setWithoutUndo(m_volatileValue);
}

bool FontFamilyAspect::guiToVolatileValue()
{
    return updateStorage(m_volatileValue, d->m_undoable.get());
}

bool FontFamilyAspect::valueToVolatileValue()
{
    return updateStorage(m_volatileValue, m_value);
}

bool FontFamilyAspect::volatileValueToValue()
{
    const QString fontFamily = QFontInfo(QFont(m_volatileValue)).family();
    return updateStorage(m_value, fontFamily);
}

bool FontFamilyAspect::isDirty() const
{
    if (TypedAspect::isDirty())
        return true;
    const QString resolved = QFontInfo(QFont(m_value)).family();
    const QString resolvedVolatile = QFontInfo(QFont(m_volatileValue)).family();
    return resolved != resolvedVolatile;
}

void FontFamilyAspect::setDefaultValue(const QString &font)
{
    const QString fontFamily = QFontInfo(QFont(font)).family();
    TypedAspect::setDefaultValue(fontFamily);
}

// !internal

static void updateToggleAction(ToggleAspect &aspect,
                               const std::unique_ptr<Internal::ToggleAspectPrivate> &d)
{
    if (!aspect.hasAction())
        return;

    QAction *action = aspect.action();

    Internal::ToggleAspectPrivate::Data data = aspect.value() ? d->on : d->off;
    if (data.icon.isNull())
        data.icon = aspect() ? aspect.icon() : d->on.icon;
    if (data.text.isEmpty())
        data.text = aspect() ? aspect.toolTip() : d->on.text;
    if (data.tooltip.isEmpty())
        data.tooltip = aspect() ? aspect.toolTip() : d->on.tooltip;

    action->setIcon(data.icon);
    action->setText(data.text);
    action->setToolTip(data.tooltip);
}

/*!
    \class Utils::ToggleAspect
    \inmodule QtCreator

    \brief A toggle aspect is a boolean property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting. It also contains independent tooltips, icons and text for the action()
    according to the on / off state of the aspect.

    The aspect is displayed using a QCheckBox.

    The visual representation often contains a label in front or after
    the display of the actual checkmark.
*/

ToggleAspect::ToggleAspect(AspectContainer *container)
    : BoolAspect(container)
    , d(std::make_unique<Internal::ToggleAspectPrivate>())
{}

ToggleAspect::~ToggleAspect() {}

void ToggleAspect::setOffIcon(const QIcon &icon)
{
    d->off.icon = icon;
    updateToggleAction(*this, d);
}

void ToggleAspect::setOffTooltip(const QString &tooltip)
{
    d->off.tooltip = tooltip;
    updateToggleAction(*this, d);
}

void ToggleAspect::setOnTooltip(const QString &tooltip)
{
    d->on.tooltip = tooltip;
    updateToggleAction(*this, d);
}

void ToggleAspect::setOnIcon(const QIcon &icon)
{
    d->on.icon = icon;
    updateToggleAction(*this, d);
}

QString ToggleAspect::onTooltip() const
{
    return d->on.tooltip;
}

QIcon ToggleAspect::onIcon() const
{
    return d->on.icon;
}

QString ToggleAspect::offTooltip() const
{
    return d->off.tooltip;
}

QIcon ToggleAspect::offIcon() const
{
    return d->off.icon;
}

void ToggleAspect::setOnText(const QString &text)
{
    d->on.text = text;
}

QString ToggleAspect::onText() const
{
    return d->on.text;
}

void ToggleAspect::setOffText(const QString &text)
{
    d->off.text = text;
}
QString ToggleAspect::offText() const
{
    return d->off.text;
}

void ToggleAspect::announceChanges(Changes changes, Announcement howToAnnounce)
{
    if (changes.valueFromVolatileValue || changes.valueFromOutside)
        updateToggleAction(*this, d);
    BoolAspect::announceChanges(changes, howToAnnounce);
}

QAction *ToggleAspect::action()
{
    if (hasAction())
        return BoolAspect::action();

    QAction *a = BoolAspect::action();
    updateToggleAction(*this, d);

    return a;
}

/*!
    \class Utils::BoolAspect
    \inmodule QtCreator

    \brief A boolean aspect is a boolean property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting.

    The boolean aspect is displayed using a QCheckBox.

    The visual representation often contains a label in front or after
    the display of the actual checkmark.
*/


BoolAspect::BoolAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::BoolAspectPrivate)
{
    setDefaultValue(false);
    setSpan(2, 1);
}

/*!
    \internal
*/
BoolAspect::~BoolAspect() = default;

void BoolAspect::addToLayoutHelper(Layouting::Layout &parent, QAbstractButton *button)
{
    switch (d->m_labelPlacement) {
    case LabelPlacement::Compact:
        button->setText(labelText());
        parent.addItem(button);
        break;
    case LabelPlacement::AtCheckBox:
        button->setText(labelText());
        parent.addItem(empty);
        parent.addItem(button);
        break;
    case LabelPlacement::InExtraLabel:
        addLabeledItem(parent, button);
        break;
    case LabelPlacement::ShowTip: {
        parent.addItem(empty);
        button->setText(labelText());
        auto ttLabel = new QLabel(toolTip());
        ttLabel->setFont(StyleHelper::uiFont(StyleHelper::UiElementLabelSmall));
        auto lt = new QVBoxLayout;
        lt->setContentsMargins({});
        lt->setSpacing(StyleHelper::SpacingTokens::GapVXs);
        lt->addWidget(button);
        lt->addWidget(ttLabel);
        parent.addItem(lt);
        break;
    }
    }

    connect(button, &QAbstractButton::clicked, this, [button, this] {
        d->m_undoable.set(undoStack(), button->isChecked());
    });

    connect(&d->m_undoable.m_signal, &UndoSignaller::changed, button, [button, this] {
        button->setChecked(d->m_undoable.get());
        handleGuiChanged();
    });
}

std::function<void(Layouting::Layout *)> BoolAspect::adoptButton(QAbstractButton *button)
{
    return [this, button](Layouting::Layout *layout) {
        addToLayoutHelper(*layout, button);
        volatileValueToGui();
    };
}

/*!
    \reimp
*/
void BoolAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    if (d->m_displayStyle == DisplayStyle::CheckBox)
        addToLayoutHelper(parent, createSubWidget<QCheckBox>());
    else
        addToLayoutHelper(parent, createSubWidget<QRadioButton>());
    volatileValueToGui();
}

std::function<void (QObject *)> BoolAspect::groupChecker()
{
    return [this](QObject *target) {
        auto groupBox = qobject_cast<QGroupBox *>(target);
        QTC_ASSERT(groupBox, return);
        registerSubWidget(groupBox);
        groupBox->setCheckable(true);
        groupBox->setChecked(value());

        connect(groupBox, &QGroupBox::clicked, this, [groupBox, this] {
            d->m_undoable.set(undoStack(), groupBox->isChecked());
        });

        connect(&d->m_undoable.m_signal, &UndoSignaller::changed, groupBox, [groupBox, this] {
            groupBox->setChecked(d->m_undoable.get());
            handleGuiChanged();
        });
        volatileValueToGui();
    };
}

QAction *BoolAspect::action()
{
    if (hasAction())
        return TypedAspect::action();
    auto act = TypedAspect::action(); // Creates it.
    act->setCheckable(true);
    act->setChecked(m_value);
    act->setToolTip(toolTip());
    connect(act, &QAction::triggered, this, [this](bool newValue) {
        setValue(newValue);
    });
    connect(this, &BoolAspect::changed, act, [act, this] { act->setChecked(m_value); });

    return act;
}

bool BoolAspect::guiToVolatileValue()
{
    return updateStorage(m_volatileValue, d->m_undoable.get());
}

void BoolAspect::volatileValueToGui()
{
    d->m_undoable.setWithoutUndo(m_volatileValue);
}

void BoolAspect::setLabel(const QString &labelText, LabelPlacement labelPlacement)
{
    TypedAspect::setLabelText(labelText);
    d->m_labelPlacement = labelPlacement;
}

void BoolAspect::setLabelPlacement(BoolAspect::LabelPlacement labelPlacement)
{
    d->m_labelPlacement = labelPlacement;
}

void BoolAspect::setDisplayStyle(DisplayStyle displayStyle)
{
    d->m_displayStyle = displayStyle;
}

CheckableDecider BoolAspect::askAgainCheckableDecider()
{
    return CheckableDecider(
        [this] { return value(); },
        [this] { setValue(false); }
    );
}

CheckableDecider BoolAspect::doNotAskAgainCheckableDecider()
{
    return CheckableDecider(
        [this] { return !value(); },
        [this] { setValue(true); }
    );
}

/*!
 \internal
*/
QVariant InvertedSavedBoolAspect::fromSettingsValue(const QVariant &savedValue) const
{
    return !savedValue.toBool();
}

/*!
 \internal
*/
QVariant InvertedSavedBoolAspect::toSettingsValue(const QVariant &valueToSave) const
{
    return !valueToSave.toBool();
}

/*!
    \class Utils::SelectionAspect
    \inmodule QtCreator

    \brief A selection aspect represents a specific choice out of
    several.

    The selection aspect is displayed using a QComboBox or
    QRadioButtons in a QButtonGroup.
*/

SelectionAspect::SelectionAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::SelectionAspectPrivate)
{
    setSpan(2, 1);
    d->m_undoable.setSilently(value());
}

/*!
    \internal
*/
SelectionAspect::~SelectionAspect() = default;

/*!
    \reimp
*/
void SelectionAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    switch (d->m_displayStyle) {
    case DisplayStyle::RadioButtons: {
        auto buttonGroup = new QButtonGroup(parent.product());
        buttonGroup->setObjectName(objectName());
        buttonGroup->setExclusive(true);
        for (int i = 0, n = d->m_options.size(); i < n; ++i) {
            const Option &option = d->m_options.at(i);
            auto button = createSubWidget<QRadioButton>(option.displayName);
            button->setChecked(i == value());
            button->setEnabled(option.enabled);
            button->setToolTip(option.tooltip);
            parent.addItem(button);
            buttonGroup->addButton(button, i);
        }
        volatileValueToGui();
        connect(&d->m_undoable.m_signal, &UndoSignaller::changed, buttonGroup, [buttonGroup, this] {
            QAbstractButton *button = buttonGroup->button(d->m_undoable.get());
            QTC_ASSERT(button, return);
            button->setChecked(true);
        });

        connect(buttonGroup, &QButtonGroup::idToggled, this, [this, buttonGroup] {
            d->m_undoable.set(undoStack(), buttonGroup->id(buttonGroup->checkedButton()));
            handleGuiChanged();
        });
        break;
    }
    case DisplayStyle::ComboBox:
        setLabelText(displayName());
        auto comboBox = createSubWidget<QComboBox>();
        comboBox->setObjectName(objectName());
        for (int i = 0, n = d->m_options.size(); i < n; ++i)
            comboBox->addItem(d->m_options.at(i).displayName);
        comboBox->setCurrentIndex(volatileValue());
        addLabeledItem(parent, comboBox);
        connect(&d->m_undoable.m_signal, &UndoSignaller::changed, comboBox, [comboBox, this] {
            comboBox->setCurrentIndex(d->m_undoable.get());
        });
        connect(comboBox, &QComboBox::currentIndexChanged, this, [this, comboBox] {
            d->m_undoable.set(undoStack(), comboBox->currentIndex());
            handleGuiChanged();
        });

        break;
    }
}

bool SelectionAspect::guiToVolatileValue()
{
    return updateStorage(m_volatileValue, d->m_undoable.get());
}

void SelectionAspect::volatileValueToGui()
{
    return d->m_undoable.setWithoutUndo(m_volatileValue);
}

void SelectionAspect::setDisplayStyle(SelectionAspect::DisplayStyle style)
{
    d->m_displayStyle = style;
}

QVariant SelectionAspect::toSettingsValue(const QVariant &valueToSave) const
{
    if (!d->m_useDataAsSavedValue)
        return valueToSave;

    return itemValueForIndex(valueToSave.toInt());
}

QVariant SelectionAspect::fromSettingsValue(const QVariant &savedValue) const
{
    if (!d->m_useDataAsSavedValue)
        return savedValue;

    const int index = indexForItemValue(savedValue);
    return index >= 0 ? index : defaultVariantValue();
}

void SelectionAspect::setUseDataAsSavedValue()
{
    d->m_useDataAsSavedValue = true;
}

void SelectionAspect::setStringValue(const QString &val)
{
    const int index = indexForDisplay(val);
    QTC_ASSERT(index >= 0, return);
    setValue(index);
}

void SelectionAspect::setDefaultValue(int val)
{
    TypedAspect::setDefaultValue(val);
}

// Note: This needs to be set after all options are added.
void SelectionAspect::setDefaultValue(const QString &val)
{
    TypedAspect::setDefaultValue(indexForDisplay(val));
}

QString SelectionAspect::stringValue() const
{
    const int idx = value();
    return idx >= 0 && idx < d->m_options.size() ? d->m_options.at(idx).displayName : QString();
}

QVariant SelectionAspect::itemValue() const
{
    const int idx = value();
    return idx >= 0 && idx < d->m_options.size() ? d->m_options.at(idx).itemData : QVariant();
}

void SelectionAspect::addOption(const QString &displayName, const QString &toolTip)
{
    d->m_options.append(Option(displayName, toolTip, {}));
}

void SelectionAspect::addOption(const Option &option)
{
    d->m_options.append(option);
}

int SelectionAspect::optionCount() const
{
    return d->m_options.size();
}

int SelectionAspect::indexForDisplay(const QString &displayName) const
{
    for (int i = 0, n = d->m_options.size(); i < n; ++i) {
        if (d->m_options.at(i).displayName == displayName)
            return i;
    }
    return -1;
}

QString SelectionAspect::displayForIndex(int index) const
{
    QTC_ASSERT(index >= 0 && index < d->m_options.size(), return {});
    return d->m_options.at(index).displayName;
}

std::optional<SelectionAspect::Option> SelectionAspect::optionForIndex(int index) const
{
    QTC_ASSERT(index >= 0 && index < d->m_options.size(), return {});
    return d->m_options.at(index);
}

void SelectionAspect::setOptionForIndex(int index, const Option &option)
{
    QTC_ASSERT(index >= 0 && index < d->m_options.size(), return);
    d->m_options[index] = option;
}

int SelectionAspect::indexForItemValue(const QVariant &value) const
{
    for (int i = 0, n = d->m_options.size(); i < n; ++i) {
        if (d->m_options.at(i).itemData == value)
            return i;
    }
    return -1;
}

QVariant SelectionAspect::itemValueForIndex(int index) const
{
    QTC_ASSERT(index >= 0 && index < d->m_options.size(), return {});
    return d->m_options.at(index).itemData;
}

/*!
    \class Utils::MultiSelectionAspect
    \inmodule QtCreator

    \brief A multi-selection aspect represents one or more choices out of
    several.

    The multi-selection aspect is displayed using a QListWidget with
    checkable items.
*/

MultiSelectionAspect::MultiSelectionAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::MultiSelectionAspectPrivate(this))
{
    setDefaultValue(QStringList());
    setSpan(2, 1);
}

/*!
    \internal
*/
MultiSelectionAspect::~MultiSelectionAspect() = default;

/*!
    \reimp
*/
void MultiSelectionAspect::addToLayoutImpl(Layout &builder)
{
    QTC_CHECK(d->m_listView == nullptr);
    if (d->m_allValues.isEmpty())
        return;

    switch (d->m_displayStyle) {
    case DisplayStyle::ListView:
        d->m_listView = createSubWidget<QListWidget>();
        for (const QString &val : std::as_const(d->m_allValues))
            (void) new QListWidgetItem(val, d->m_listView);
        addLabeledItem(builder, d->m_listView);

        volatileValueToGui();
        connect(d->m_listView, &QListWidget::itemChanged,
                this, &MultiSelectionAspect::handleGuiChanged);
    }
}

bool Internal::MultiSelectionAspectPrivate::setValueSelectedHelper(const QString &val, bool on)
{
    QStringList list = q->value();
    if (on && !list.contains(val)) {
        list.append(val);
        q->setValue(list);
        return true;
    }
    if (!on && list.contains(val)) {
        list.removeOne(val);
        q->setValue(list);
        return true;
    }
    return false;
}

QStringList MultiSelectionAspect::allValues() const
{
    return d->m_allValues;
}

void MultiSelectionAspect::setAllValues(const QStringList &val)
{
    d->m_allValues = val;
}

void MultiSelectionAspect::setDisplayStyle(MultiSelectionAspect::DisplayStyle style)
{
    d->m_displayStyle = style;
}

void MultiSelectionAspect::volatileValueToGui()
{
    if (d->m_listView) {
        const int n = d->m_listView->count();
        QTC_CHECK(n == d->m_allValues.size());
        for (int i = 0; i != n; ++i) {
            auto item = d->m_listView->item(i);
            item->setCheckState(m_volatileValue.contains(item->text()) ? Qt::Checked : Qt::Unchecked);
        }
    }
}

bool MultiSelectionAspect::guiToVolatileValue()
{
    if (d->m_listView) {
        QStringList val;
        const int n = d->m_listView->count();
        QTC_CHECK(n == d->m_allValues.size());
        for (int i = 0; i != n; ++i) {
            auto item = d->m_listView->item(i);
            if (item->checkState() == Qt::Checked)
                val.append(item->text());
        }
        return updateStorage(m_volatileValue, val);
    }
    return false;
}


/*!
    \class Utils::IntegerAspect
    \inmodule QtCreator

    \brief An integer aspect is a integral property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting.

    The integer aspect is displayed using a \c QSpinBox.

    The visual representation often contains a label in front
    the display of the spin box.
*/

// IntegerAspect

IntegerAspect::IntegerAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::IntegerAspectPrivate)
{
    setSpan(2, 1);
}

/*!
    \internal
*/
IntegerAspect::~IntegerAspect() = default;

/*!
    \reimp
*/
void IntegerAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    QTC_CHECK(!d->m_spinBox);
    d->m_spinBox = createSubWidget<QSpinBox>();
    d->m_spinBox->setDisplayIntegerBase(d->m_displayIntegerBase);
    d->m_spinBox->setPrefix(d->m_prefix);
    d->m_spinBox->setSuffix(d->m_suffix);
    d->m_spinBox->setSingleStep(d->m_singleStep);
    d->m_spinBox->setSpecialValueText(d->m_specialValueText);
    if (d->m_minimumValue && d->m_maximumValue)
        d->m_spinBox->setRange(int(d->m_minimumValue.value() / d->m_displayScaleFactor),
                               int(d->m_maximumValue.value() / d->m_displayScaleFactor));
    volatileValueToGui();
    addLabeledItem(parent, d->m_spinBox);
    connect(d->m_spinBox.data(), &QSpinBox::valueChanged,
            this, &IntegerAspect::handleGuiChanged);
}

bool IntegerAspect::guiToVolatileValue()
{
    if (d->m_spinBox)
        return updateStorage(m_volatileValue, d->m_spinBox->value() * d->m_displayScaleFactor);
    return false;
}

void IntegerAspect::volatileValueToGui()
{
    if (d->m_spinBox)
        d->m_spinBox->setValue(m_volatileValue / d->m_displayScaleFactor);
}

QVariant IntegerAspect::fromSettingsValue(const QVariant &savedValue) const
{
    qint64 v = savedValue.value<qint64>();
    if (d->m_minimumValue && v < *d->m_minimumValue)
        v = *d->m_minimumValue;
    if (d->m_maximumValue && v > *d->m_maximumValue)
        v = *d->m_maximumValue;
    return v;
}

void IntegerAspect::setRange(qint64 min, qint64 max)
{
    d->m_minimumValue = min;
    d->m_maximumValue = max;
}

void IntegerAspect::setLabel(const QString &label)
{
    setLabelText(label);
}

void IntegerAspect::setPrefix(const QString &prefix)
{
    d->m_prefix = prefix;
}

void IntegerAspect::setSuffix(const QString &suffix)
{
    d->m_suffix = suffix;
}

void IntegerAspect::setDisplayIntegerBase(int base)
{
    d->m_displayIntegerBase = base;
}

void IntegerAspect::setDisplayScaleFactor(qint64 factor)
{
    d->m_displayScaleFactor = factor;
}

void IntegerAspect::setSpecialValueText(const QString &specialText)
{
    d->m_specialValueText = specialText;
}

void IntegerAspect::setSingleStep(qint64 step)
{
    d->m_singleStep = step;
}


/*!
    \class Utils::DoubleAspect
    \inmodule QtCreator

    \brief An double aspect is a numerical property of some object, together with
    a description of its behavior for common operations like visualizing or
    persisting.

    The double aspect is displayed using a \c QDoubleSpinBox.

    The visual representation often contains a label in front
    the display of the spin box.
*/

DoubleAspect::DoubleAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::DoubleAspectPrivate)
{
    setDefaultValue(double(0));
    setSpan(2, 1);
}

/*!
    \internal
*/
DoubleAspect::~DoubleAspect() = default;

/*!
    \reimp
*/
void DoubleAspect::addToLayoutImpl(Layout &builder)
{
    QTC_CHECK(!d->m_spinBox);
    d->m_spinBox = createSubWidget<QDoubleSpinBox>();
    d->m_spinBox->setPrefix(d->m_prefix);
    d->m_spinBox->setSuffix(d->m_suffix);
    d->m_spinBox->setSingleStep(d->m_singleStep);
    d->m_spinBox->setSpecialValueText(d->m_specialValueText);
    if (d->m_minimumValue && d->m_maximumValue)
        d->m_spinBox->setRange(d->m_minimumValue.value(), d->m_maximumValue.value());
    volatileValueToGui(); // Must happen after setRange()!
    addLabeledItem(builder, d->m_spinBox);
    connect(d->m_spinBox.data(), &QDoubleSpinBox::valueChanged,
            this, &DoubleAspect::handleGuiChanged);
}

bool DoubleAspect::guiToVolatileValue()
{
    if (d->m_spinBox)
        return updateStorage(m_volatileValue, d->m_spinBox->value());
    return false;
}

void DoubleAspect::volatileValueToGui()
{
    if (d->m_spinBox)
        d->m_spinBox->setValue(m_volatileValue);
}

void DoubleAspect::setRange(double min, double max)
{
    d->m_minimumValue = min;
    d->m_maximumValue = max;
}

void DoubleAspect::setPrefix(const QString &prefix)
{
    d->m_prefix = prefix;
}

void DoubleAspect::setSuffix(const QString &suffix)
{
    d->m_suffix = suffix;
}

void DoubleAspect::setSpecialValueText(const QString &specialText)
{
    d->m_specialValueText = specialText;
}

void DoubleAspect::setSingleStep(double step)
{
    d->m_singleStep = step;
}


/*!
    \class Utils::TriStateAspect
    \inmodule QtCreator

    \brief A tristate aspect is a property of some object that can have
    three values: enabled, disabled, and unspecified.

    Its visual representation is a QComboBox with three items.
*/

TriStateAspect::TriStateAspect(AspectContainer *container,
                               const QString &enabledDisplay,
                               const QString &disabledDisplay,
                               const QString &defaultDisplay)
    : SelectionAspect(container)
{
    setDisplayStyle(DisplayStyle::ComboBox);
    setDefaultValue(TriState::Default);
    SelectionAspect::addOption({});
    SelectionAspect::addOption({});
    SelectionAspect::addOption({});
    setOptionText(TriState::EnabledValue, enabledDisplay);
    setOptionText(TriState::DisabledValue, disabledDisplay);
    setOptionText(TriState::DefaultValue, defaultDisplay);
}

static QString defaultTristateDisplay(TriState::Value tristate)
{
    switch (tristate) {
        case TriState::EnabledValue: return Tr::tr("Enable");
        case TriState::DisabledValue: return Tr::tr("Disable");
        case TriState::DefaultValue: return Tr::tr("Default");
    }
    QTC_CHECK(false);
    return {};
}

void TriStateAspect::setOptionText(const TriState::Value tristate, const QString &display)
{
    d->m_options[tristate].displayName = display.isEmpty()
        ? defaultTristateDisplay(tristate) : display;
}

TriState TriStateAspect::value() const
{
    return TriState::fromInt(SelectionAspect::value());
}

void TriStateAspect::setValue(TriState value)
{
    SelectionAspect::setValue(value.toInt());
}

TriState TriStateAspect::defaultValue() const
{
    return TriState::fromInt(SelectionAspect::defaultValue());
}

void TriStateAspect::setDefaultValue(TriState value)
{
    SelectionAspect::setDefaultValue(value.toInt());
}

const TriState TriState::Enabled{TriState::EnabledValue};
const TriState TriState::Disabled{TriState::DisabledValue};
const TriState TriState::Default{TriState::DefaultValue};

TriState TriState::fromVariant(const QVariant &variant)
{
    return fromInt(variant.toInt());
}

TriState TriState::fromInt(int v)
{
    QTC_ASSERT(v == EnabledValue || v == DisabledValue || v == DefaultValue, v = DefaultValue);
    return TriState(Value(v));
}


/*!
    \class Utils::StringListAspect
    \inmodule QtCreator

    \brief A string list aspect represents a property of some object
    that is a list of strings.
*/

StringListAspect::StringListAspect(AspectContainer *container)
    : TypedAspect(container), d(new Internal::StringListAspectPrivate)
{
    setDefaultValue(QStringList());
}

/*!
    \internal
*/
StringListAspect::~StringListAspect() = default;

bool StringListAspect::guiToVolatileValue()
{
    const QStringList newValue = d->m_undoable.get();
    if (newValue != m_volatileValue) {
        m_volatileValue = newValue;
        return true;
    }
    return false;
}

void StringListAspect::volatileValueToGui()
{
    d->m_undoable.setWithoutUndo(m_volatileValue);
}

void StringListAspect::setDisplayStyle(DisplayStyle displayStyle)
{
    d->m_displayStyle = displayStyle;
}

void StringListAspect::addToLayoutImpl(Layout &parent)
{
    if (d->m_displayStyle == DisplayStyle::CommaSeparatedLineEdit) {
        auto lineEdit = createSubWidget<FancyLineEdit>();

        auto listToText = [](const QStringList &list) { return list.join(","); };
        auto textToList = [](const QString &text) {
            QStringList parts = text.split(',', Qt::SkipEmptyParts);
            for (QString &p : parts)
                p = p.trimmed();
            parts.removeAll({});
            return parts;
        };

        lineEdit->setText(listToText(d->m_undoable.get()));
        lineEdit->setReadOnly(isReadOnly());

        connect(lineEdit, &QLineEdit::textEdited, this, [this, lineEdit, textToList] {
            d->m_undoable.set(undoStack(), textToList(lineEdit->text()));
        });

        connect(
            &d->m_undoable.m_signal,
            &UndoSignaller::changed,
            lineEdit,
            [this, lineEdit, listToText, textToList] {
                if (textToList(lineEdit->text()) != d->m_undoable.get())
                    lineEdit->setText(listToText(d->m_undoable.get()));
                handleGuiChanged();
            });

        addLabeledItem(parent, lineEdit);
        return;
    }

    auto editor = createSubWidget<QTreeWidget>();
    editor->setHeaderHidden(true);
    editor->setRootIsDecorated(false);
    editor->setEditTriggers(
        d->m_allowEditing ? QAbstractItemView::AllEditTriggers : QAbstractItemView::NoEditTriggers);

    QPushButton *add = d->m_allowAdding ? createSubWidget<QPushButton>(Tr::tr("Add")) : nullptr;
    QPushButton *remove = d->m_allowRemoving ? createSubWidget<QPushButton>(Tr::tr("Remove")) : nullptr;

    auto itemsToStringList = [editor] {
        QStringList items;
        const QTreeWidgetItem *rootItem = editor->invisibleRootItem();
        for (int i = 0, count = rootItem->childCount(); i < count; ++i) {
            auto expr = rootItem->child(i)->data(0, Qt::DisplayRole).toString();
            items.append(expr);
        }
        return items;
    };

    auto populate = [editor, this] {
        editor->clear();
        for (const QString &entry : d->m_undoable.get()) {
            auto item = new QTreeWidgetItem(editor, {entry});
            item->setData(0, Qt::ToolTipRole, entry);
            item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable);
        }
    };

    if (add) {
        connect(add, &QPushButton::clicked, this, [this, populate, editor] {
            d->m_undoable.setSilently(d->m_undoable.get() << "");
            populate();
            const QTreeWidgetItem *root = editor->invisibleRootItem();
            QTreeWidgetItem *lastChild = root->child(root->childCount() - 1);
            const QModelIndex index = editor->indexFromItem(lastChild, 0);
            editor->edit(index);
        });
    }

    if (remove) {
        connect(remove, &QPushButton::clicked, this, [this, editor, itemsToStringList] {
            const QList<QTreeWidgetItem *> selected = editor->selectedItems();
            QTC_ASSERT(selected.size() == 1, return);
            editor->invisibleRootItem()->removeChild(selected.first());
            delete selected.first();
            d->m_undoable.set(undoStack(), itemsToStringList());
        });
    }

    connect(
        &d->m_undoable.m_signal, &UndoSignaller::changed, editor, [this, populate, itemsToStringList] {
            if (itemsToStringList() != d->m_undoable.get())
                populate();

            handleGuiChanged();
        });

    connect(
        editor->model(),
        &QAbstractItemModel::dataChanged,
        this,
        [this,
         itemsToStringList](const QModelIndex &tl, const QModelIndex &br, const QList<int> &roles) {
            if (!roles.contains(Qt::DisplayRole))
                return;
            if (tl != br)
                return;
            d->m_undoable.set(undoStack(), itemsToStringList());
        });

    populate();

    // clang-format off
    QWidget *mainWdgt = Widget {
        Row {
            noMargin,
            editor,
            If (d->m_allowAdding || d->m_allowRemoving) >> Then {
                Column {
                    If (d->m_allowAdding) >> Then {add},
                    If (d->m_allowRemoving) >> Then {remove},
                    st,
                }
            },
        }
    }.emerge();
    // clang-format on

    registerSubWidget(mainWdgt);

    parent.addItem(createLabel());
    parent.addItem(mainWdgt);
}

void StringListAspect::appendValue(const QString &s, bool allowDuplicates)
{
    QStringList val = value();
    if (allowDuplicates || !val.contains(s))
        val.append(s);
    setValue(val);
}

void StringListAspect::removeValue(const QString &s)
{
    QStringList val = value();
    val.removeAll(s);
    setValue(val);
}

void StringListAspect::appendValues(const QStringList &values, bool allowDuplicates)
{
    QStringList val = value();
    for (const QString &s : values) {
        if (allowDuplicates || !val.contains(s))
            val.append(s);
    }
    setValue(val);
}

void StringListAspect::removeValues(const QStringList &values)
{
    QStringList val = value();
    for (const QString &s : values)
        val.removeAll(s);
    setValue(val);
}

void StringListAspect::setUiAllowAdding(bool allowAdding)
{
    d->m_allowAdding = allowAdding;
}
void StringListAspect::setUiAllowRemoving(bool allowRemoving)
{
    d->m_allowRemoving = allowRemoving;
}
void StringListAspect::setUiAllowEditing(bool allowEditing)
{
    d->m_allowEditing = allowEditing;
}

bool StringListAspect::uiAllowAdding() const
{
    return d->m_allowAdding;
}
bool StringListAspect::uiAllowRemoving() const
{
    return d->m_allowRemoving;
}
bool StringListAspect::uiAllowEditing() const
{
    return d->m_allowEditing;
}

/*!
    \class Utils::FilePathListAspect
    \inmodule QtCreator

    \brief A filepath list aspect represents a property of some object
    that is a list of filepathList.
*/

FilePathListAspect::FilePathListAspect(AspectContainer *container)
    : TypedAspect(container)
    , d(new Internal::FilePathListAspectPrivate)
{
    setDefaultValue(QStringList());
}

FilePathListAspect::~FilePathListAspect() = default;

FilePaths FilePathListAspect::operator()() const
{
    return Utils::transform(m_value, [expander = macroExpander()](const QString &f) {
        if (expander)
            return FilePath::fromUserInput(expander->expand(f));
        return FilePath::fromUserInput(f);
    });
}

bool FilePathListAspect::guiToVolatileValue()
{
    const QStringList newValue = d->undoable.get();
    if (newValue != m_volatileValue) {
        m_volatileValue = newValue;
        return true;
    }
    return false;
}

void FilePathListAspect::volatileValueToGui()
{
    d->undoable.setWithoutUndo(m_volatileValue);
}

void FilePathListAspect::addToLayoutImpl(Layout &parent)
{
    PathListEditor *editor = createSubWidget<PathListEditor>();
    editor->setPathList(value());
    connect(editor, &PathListEditor::changed, this, [this, editor] {
        d->undoable.set(undoStack(), editor->pathList());
    });
    connect(&d->undoable.m_signal, &UndoSignaller::changed, editor, [this, editor] {
        if (editor->pathList() != d->undoable.get())
            editor->setPathList(d->undoable.get());

        handleGuiChanged();
    });
    connect(editor, &PathListEditor::changed, this, &FilePathListAspect::volatileValueChanged);

    editor->setToolTip(toolTip());
    editor->setMaximumHeight(100);
    editor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    editor->setPlaceholderText(d->placeHolderText);

    registerSubWidget(editor);

    connect(this, &FilePathListAspect::placeHolderTextChanged,
            editor, &PathListEditor::setPlaceholderText);

    parent.addItem(editor);
}

void FilePathListAspect::setPlaceHolderText(const QString &placeHolderText)
{
    if (placeHolderText == d->placeHolderText)
        return;

    d->placeHolderText = placeHolderText;
    emit placeHolderTextChanged(placeHolderText);
}

void FilePathListAspect::appendValue(const FilePath &path, bool allowDuplicates)
{
    const QString asString = path.toUserOutput();
    QStringList val = value();
    if (allowDuplicates || !val.contains(asString))
        val.append(asString);
    setValue(val);
}

void FilePathListAspect::removeValue(const FilePath &s)
{
    QStringList val = value();
    val.removeAll(s.toUserOutput());
    setValue(val);
}

void FilePathListAspect::appendValues(const FilePaths &paths, bool allowDuplicates)
{
    QStringList val = value();

    for (const FilePath &path : paths) {
        const QString asString = path.toUserOutput();
        if (allowDuplicates || !val.contains(asString))
            val.append(asString);
    }
    setValue(val);
}

void FilePathListAspect::removeValues(const FilePaths &paths)
{
    QStringList val = value();
    for (const FilePath &path : paths)
        val.removeAll(path.toUserOutput());
    setValue(val);
}

/*!
    \class Utils::IntegersAspect
    \internal
    \inmodule QtCreator

    \brief An integers aspect represents a property of some object
    that is a list of integers.
*/

IntegersAspect::IntegersAspect(AspectContainer *container)
    : TypedAspect(container)
{}

/*!
    \internal
*/
IntegersAspect::~IntegersAspect() = default;

/*!
    \reimp
*/
void IntegersAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    Q_UNUSED(parent)
    // TODO - when needed.
}


/*!
    \class Utils::TextDisplay
    \inmodule QtCreator

    \brief A text display is a phony aspect with the sole purpose of providing
    some text display using an Utils::InfoLabel in places where otherwise
    more expensive Utils::StringAspect items would be used.

    A text display does not have a real value.
*/

/*!
    Constructs a text display with the parent \a container. The display shows
    \a message and an icon representing the type \a type.
 */
TextDisplay::TextDisplay(AspectContainer *container, const QString &message, InfoLabel::InfoType type)
    : BaseAspect(container), d(new Internal::TextDisplayPrivate)
{
    d->m_message = message;
    d->m_type = type;
}

/*!
    \internal
*/
TextDisplay::~TextDisplay() = default;

/*!
    \reimp
*/
void TextDisplay::addToLayoutImpl(Layout &parent)
{
    if (!d->m_label) {
        d->m_label = createSubWidget<InfoLabel>(d->m_message, d->m_type);
        d->m_label->setTextInteractionFlags(Qt::TextSelectableByMouse);
        d->m_label->setElideMode(Qt::ElideNone);
        d->m_label->setWordWrap(d->m_wordWrap);
        // Do not use m_label->setVisible(isVisible()) unconditionally, it does not
        // have a QWidget parent yet when used in a LayoutBuilder.
        if (!isVisible())
            d->m_label->setVisible(false);

        connect(this, &TextDisplay::changed, d->m_label, [this] {
            d->m_label->setText(d->m_message);
        });
    }
    parent.addItem(d->m_label.data());
}

/*!
    Sets \a t as the information label type for the visual representation
    of this aspect.
 */
void TextDisplay::setIconType(InfoLabel::InfoType t)
{
    d->m_type = t;
    if (d->m_label)
        d->m_label->setType(t);
}

void TextDisplay::setText(const QString &message)
{
    d->m_message = message;
    emit changed();
}

void TextDisplay::setWordWrap(bool on)
{
    d->m_wordWrap = on;
    if (d->m_label)
        d->m_label->setWordWrap(on);
}

QString TextDisplay::text() const
{
    return d->m_message;
}

/*!
    \class Utils::AspectContainer
    \inmodule QtCreator

    \brief The AspectContainer class wraps one or more aspects while providing
    the interface of a single aspect.

    Sub-aspects ownership can be declared using \a setOwnsSubAspects.
*/

class Internal::AspectContainerPrivate
{
public:
    QList<BaseAspect *> m_items; // Both owned and non-owned.
    QList<BaseAspect *> m_ownedItems; // Owned only.
    QStringList m_settingsGroup;
    std::function<Layouting::Layout()> m_layouter;
};

AspectContainer::AspectContainer(AspectContainer *parentContainer)
    : BaseAspect(parentContainer)
    , d(new Internal::AspectContainerPrivate)
{}

/*!
    \internal
*/
AspectContainer::~AspectContainer()
{
    qDeleteAll(d->m_ownedItems);
}

void AspectContainer::addToLayoutImpl(Layouting::Layout &parent)
{
    parent.addItem(layouter()());
}

/*!
    \internal
*/
void AspectContainer::registerAspect(BaseAspect *aspect, bool takeOwnership)
{
    aspect->setContainer(this);
    aspect->setAutoApply(isAutoApply());
    aspect->setEnabled(aspect->isEnabled() && isEnabled());
    d->m_items.append(aspect);
    if (takeOwnership)
        d->m_ownedItems.append(aspect);

    connect(aspect, &BaseAspect::changed, this, &BaseAspect::changed);
    connect(aspect, &BaseAspect::changed, this, [this, aspect] { emit subAspectChanged(aspect); });
    connect(aspect, &BaseAspect::volatileValueChanged, this, &BaseAspect::volatileValueChanged);
}

void AspectContainer::registerAspects(const AspectContainer &aspects)
{
    for (BaseAspect *aspect : std::as_const(aspects.d->m_items))
        registerAspect(aspect);
}

/*!
    Retrieves a BaseAspect with a given \a id, or nullptr if no such aspect is contained.

    \sa BaseAspect
*/
BaseAspect *AspectContainer::aspect(Id id) const
{
    return findOrDefault(d->m_items, equal(&BaseAspect::id, id));
}

AspectContainer::const_iterator AspectContainer::begin() const
{
    return d->m_items.cbegin();
}

AspectContainer::const_iterator AspectContainer::end() const
{
    return d->m_items.cend();
}

void AspectContainer::setLayouter(const std::function<Layouting::Layout ()> &layouter)
{
    d->m_layouter = layouter;
}

std::function<Layout ()> AspectContainer::layouter() const
{
    return d->m_layouter;
}

const QList<BaseAspect *> &AspectContainer::aspects() const
{
    return d->m_items;
}

void AspectContainer::fromMap(const Store &map)
{
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->fromMap(map);

    emit fromMapFinished();
}

void AspectContainer::toMap(Store &map) const
{
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->toMap(map);
}

void AspectContainer::volatileToMap(Store &map) const
{
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->volatileToMap(map);
}

void AspectContainer::readSettings()
{
    const SettingsGroupNester nester(d->m_settingsGroup);
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->readSettings();
}

void AspectContainer::writeSettings() const
{
    const SettingsGroupNester nester(d->m_settingsGroup);
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->writeSettings();
}

void AspectContainer::volatileValueToGui()
{
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->volatileValueToGui();
}

bool AspectContainer::guiToVolatileValue()
{
    bool result = true;
    for (BaseAspect *aspect : std::as_const(d->m_items)) {
        if (!aspect->guiToVolatileValue())
            result = false;
    }
    return result;
}

void AspectContainer::setSettingsGroup(const QString &groupKey)
{
    d->m_settingsGroup = QStringList{groupKey};
}

void AspectContainer::setSettingsGroups(const QString &groupKey, const QString &subGroupKey)
{
    d->m_settingsGroup = QStringList{groupKey, subGroupKey};
}

QStringList AspectContainer::settingsGroups() const
{
    return d->m_settingsGroup;
}

void AspectContainer::apply()
{
    const bool willChange = isDirty();

    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->apply();

    emit applied();

    if (willChange)
        emit changed();
}

void AspectContainer::cancel()
{
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->cancel();
}

void AspectContainer::reset()
{
    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->setVariantValue(aspect->defaultVariantValue());
}

void AspectContainer::setAutoApply(bool on)
{
    BaseAspect::setAutoApply(on);

    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->setAutoApply(on);
}

bool AspectContainer::isDirty() const
{
    for (BaseAspect *aspect : std::as_const(d->m_items)) {
        if (aspect->isDirty())
            return true;
    }
    return false;
}

void AspectContainer::setUndoStack(QUndoStack *undoStack)
{
    BaseAspect::setUndoStack(undoStack);

    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->setUndoStack(undoStack);
}

void AspectContainer::setEnabled(bool enabled)
{
    BaseAspect::setEnabled(enabled);

    for (BaseAspect *aspect : std::as_const(d->m_items))
        aspect->setEnabled(enabled);
}

bool AspectContainer::equals(const AspectContainer &other) const
{
    // FIXME: Expensive, but should not really be needed in a fully aspectified world.
    Store thisMap, thatMap;
    toMap(thisMap);
    other.toMap(thatMap);
    return thisMap == thatMap;
}

void AspectContainer::copyFrom(const AspectContainer &other)
{
    Store map;
    other.toMap(map);
    fromMap(map);
}

void AspectContainer::forEachAspect(const std::function<void(BaseAspect *)> &run) const
{
    for (BaseAspect *aspect : std::as_const(d->m_items)) {
        if (auto container = dynamic_cast<AspectContainer *>(aspect))
            container->forEachAspect(run);
        else
            run(aspect);
    }
}

BaseAspect::Data::Ptr BaseAspect::extractData() const
{
    QTC_ASSERT(d->m_dataCreator, return {});
    Data *data = d->m_dataCreator();
    data->m_classId = metaObject();
    data->m_id = id();
    data->m_cloner = d->m_dataCloner;
    for (const DataExtractor &extractor : std::as_const(d->m_dataExtractors))
        extractor(data);
    return Data::Ptr(data);
}

/*
    Mirrors the internal volatile value to the GUI element if they are already
    created.

    No-op otherwise.
*/
void BaseAspect::volatileValueToGui()
{
}

/*
    Mirrors the data stored in GUI element if they are already created to
    the internal volatile value.

    No-op otherwise.

    \return true when the buffered volatile value changed.
*/
bool BaseAspect::guiToVolatileValue()
{
    return false;
}

/*
    Mirrors buffered volatile value to the internal value.
    This function is used for \c apply().

    \return true when the internal value changed.
*/

bool BaseAspect::volatileValueToValue()
{
    return false;
}

bool BaseAspect::valueToVolatileValue()
{
    return false;
}

void BaseAspect::handleGuiChanged()
{
    if (guiToVolatileValue())
        emit volatileValueChanged();
    if (isAutoApply())
        apply();
}

void BaseAspect::addDataExtractorHelper(const DataExtractor &extractor) const
{
    d->m_dataExtractors.append(extractor);
}

void BaseAspect::setDataCreatorHelper(const DataCreator &creator) const
{
    d->m_dataCreator = creator;
}

void BaseAspect::setDataClonerHelper(const DataCloner &cloner) const
{
    d->m_dataCloner = cloner;
}

const BaseAspect::Data *AspectContainerData::aspect(Id instanceId) const
{
    for (const BaseAspect::Data::Ptr &data : m_data) {
        if (data.get()->id() == instanceId)
            return data.get();
    }
    return nullptr;
}

const BaseAspect::Data *AspectContainerData::aspect(BaseAspect::Data::ClassId classId) const
{
    for (const BaseAspect::Data::Ptr &data : m_data) {
        if (data.get()->classId() == classId)
            return data.get();
    }
    return nullptr;
}

void AspectContainerData::append(const BaseAspect::Data::Ptr &data)
{
    m_data.append(data);
}

// BaseAspect::Data

BaseAspect::Data::~Data() = default;

void BaseAspect::Data::Ptr::operator=(const Ptr &other)
{
    if (this == &other)
        return;
    delete m_data;
    m_data = other.m_data->clone();
}

// SettingsGroupNester

SettingsGroupNester::SettingsGroupNester(const QStringList &groups)
    : m_groupCount(groups.size())
{
    for (const QString &group : groups)
        Utils::userSettings().beginGroup(keyFromString(group));
}

SettingsGroupNester::~SettingsGroupNester()
{
    for (int i = 0; i != m_groupCount; ++i)
        Utils::userSettings().endGroup();
}

class AddItemCommand : public QUndoCommand
{
public:
    AddItemCommand(AspectList *aspect, const std::shared_ptr<BaseAspect> &item)
        : m_aspect(aspect)
        , m_item(item)
    {}

    void undo() override { m_aspect->actualRemoveItem(m_item); }
    void redo() override { m_aspect->actualAddItem(m_item); }

private:
    AspectList *m_aspect;
    std::shared_ptr<BaseAspect> m_item;
};

class RemoveItemCommand : public QUndoCommand
{
public:
    RemoveItemCommand(AspectList *aspect, const std::shared_ptr<BaseAspect> &item)
        : m_aspect(aspect)
        , m_item(item)
    {}

    void undo() override { m_aspect->actualAddItem(m_item); }
    void redo() override { m_aspect->actualRemoveItem(m_item); }

private:
    AspectList *m_aspect;
    std::shared_ptr<BaseAspect> m_item;
};

class AspectListModelItem : public TypedTreeItem<AspectListModelItem>
{
    std::shared_ptr<BaseAspect> m_aspect;
    QObject guard;
    std::function<QVariant(BaseAspect *, int role)> m_dataFunction;

public:
    AspectListModelItem() = default;
    AspectListModelItem(
        const std::shared_ptr<BaseAspect> &aspect,
        const std::function<QVariant(BaseAspect *, int role)> &dataFunction)
        : m_aspect(aspect)
        , m_dataFunction(dataFunction)

    {
        auto upd = [this] { update(); };
        QObject::connect(aspect.get(), &BaseAspect::volatileValueChanged, &guard, upd);
        QObject::connect(aspect.get(), &BaseAspect::changed, &guard, upd);
    }

    QVariant data(int column, int role) const final
    {
        if (column != 0)
            return {};
        if (role == Qt::FontRole) {
            QFont f;
            f.setBold(m_aspect->isDirty() || added);
            f.setStrikeOut(deleted);
            return f;
        }

        QVariant data = m_dataFunction(m_aspect.get(), role);
        if (data.canConvert<QFuture<QVariant>>()) {
            QFuture<QVariant> future = data.value<QFuture<QVariant>>();
            if (!future.isFinished()) {
                future.then(model(), [m = model(), idx = index(), role](const QFuture<QVariant> &f) {
                    if (f.isFinished())
                        m->dataChanged(idx, idx, {role});
                });
                return {};
            }
            return future.result();
        }
        return data;
    }

    bool hasAspect(const std::shared_ptr<BaseAspect> &aspect) const { return aspect == m_aspect; }

    std::shared_ptr<BaseAspect> aspect() const { return m_aspect; }

    void setStatus(bool isAdded, bool isDeleted)
    {
        if (added == isAdded && deleted == isDeleted)
            return;

        aspect()->setEnabled(!isDeleted);

        added = isAdded;
        deleted = isDeleted;
        update();
    }

    bool added = false;
    bool deleted = false;
};

class AspectListModel : public TreeModel<AspectListModelItem, AspectListModelItem>
{
public:
    AspectListModel(const std::function<QVariant(BaseAspect *, int role)> &dataFunction)
        : TreeModel<AspectListModelItem, AspectListModelItem>()
        , m_dataFunction(dataFunction)
    {
    }

    void sync(AspectList &aspect)
    {
        QList<std::shared_ptr<BaseAspect>> volatileItems = aspect.volatileItems();
        QList<std::shared_ptr<BaseAspect>> items = aspect.items();
        QSet<std::shared_ptr<BaseAspect>> itemSet
            = QSet<std::shared_ptr<BaseAspect>>(items.begin(), items.end());
        QSet<std::shared_ptr<BaseAspect>> volatileItemSet
            = QSet<std::shared_ptr<BaseAspect>>(volatileItems.begin(), volatileItems.end());

        auto newItems = volatileItemSet - itemSet;
        auto removedItems = itemSet - volatileItemSet;

        auto itAspect = volatileItems.begin();
        auto modelIdx = 0;

        while (itAspect != volatileItems.end() && modelIdx < rootItem()->childCount()) {
            AspectListModelItem *modelItem = rootItem()->childAt(modelIdx);
            auto inModelAspect = modelItem->aspect();

            const bool inVolatile = volatileItemSet.contains(inModelAspect);
            const bool isGone = !inVolatile && !itemSet.contains(inModelAspect);

            if (*itAspect == inModelAspect) {
                // The item is both in the model and in the volatile part of the aspect, just update its state.
                modelItem->setStatus(
                    newItems.contains(inModelAspect), removedItems.contains(inModelAspect));
                ++itAspect;
                ++modelIdx;
            } else {
                if (!inVolatile) {
                    // The item in the model is not in the volatile part of the aspect
                    if (isGone) {
                        // The item is also not in the applied part of the aspect, remove it from model.
                        delete takeItem(modelItem);
                        modelItem = nullptr;
                    } else {
                        // The item is in the applied part of the aspect, but not in the volatile part, mark it as deleted.
                        modelItem->setStatus(false, true);
                        ++modelIdx;
                    }
                } else {
                    // The item in the aspect is not in the model, add it.
                    modelItem = new AspectListModelItem(*itAspect, m_dataFunction);
                    modelItem->setStatus(newItems.contains(*itAspect), !inVolatile);
                    rootItem()->insertChild(modelIdx, modelItem);
                    ++modelIdx;
                    ++itAspect;
                }
            }
        }

        // Remove remaining items in model.
        if (modelIdx < rootItem()->childCount()) {
            for (; modelIdx < rootItem()->childCount();) {
                auto item = rootItem()->childAt(modelIdx);
                const bool isGone = !itemSet.contains(item->aspect());
                if (isGone)
                    delete takeItem(item);
                else {
                    item->setStatus(false, true);
                    modelIdx++;
                }
            }
        }

        // Add remaining items in aspect to model.
        if (itAspect != volatileItems.end()) {
            for (; itAspect != volatileItems.end(); ++itAspect) {
                auto item = new AspectListModelItem(*itAspect, m_dataFunction);
                item->setStatus(newItems.contains(*itAspect), false);
                rootItem()->appendChild(item);
            }
        }
    }

private:
    std::function<QVariant(BaseAspect *, int)> m_dataFunction;
};

class Internal::AspectListPrivate
{
public:
    QList<std::shared_ptr<BaseAspect>> items;
    QList<std::shared_ptr<BaseAspect>> volatileItems;

    void connectVolatile(const std::shared_ptr<BaseAspect> &aspect, AspectList *list)
    {
        QObject::connect(
            aspect.get(),
            &BaseAspect::volatileValueChanged,
            list,
            &AspectList::volatileValueChanged);
    }
    void disconnectVolatile(const std::shared_ptr<BaseAspect> &aspect, AspectList *list)
    {
        QObject::disconnect(
            aspect.get(),
            &BaseAspect::volatileValueChanged,
            list,
            &AspectList::volatileValueChanged);
    }

    AspectList::CreateItem createItem;

    AspectList::DisplayStyle displayStyle = AspectList::DisplayStyle::InlineList;

    AspectListModel model;

    AspectListPrivate(std::function<QVariant(BaseAspect *, int)> dataFunction)
        : model(dataFunction)
    {}

    void addToLayoutImplInlineList(Layouting::Layout &parent, AspectList *aspect)
    {
        using namespace Layouting;
        using namespace Utils::QtcWidgets;

        auto fill = [this, aspect] {
            const auto createRow = [aspect](const std::shared_ptr<BaseAspect> &item) {
                // clang-format off
                return Row {
                    *item,
                    IconButton {
                        ::icon(Utils::Icons::EDIT_CLEAR),
                        sizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Fixed}),
                        onClicked(aspect, [aspect, item] {
                            aspect->removeItem(item);
                        })
                    },
                    spacing(5),
                    noMargin,
                };
                // clang-format on
            };

            // clang-format off
            return Column {
                Utils::transform(aspect->volatileItems(), createRow),
                Row {
                    noMargin,
                    st,
                    IconButton {
                        ::icon(Utils::Icons::PLUS),
                        onClicked(aspect, [this, aspect](){
                            aspect->addItem(createItem());
                        })
                    }
                }
            };
            // clang-format on
        };

        // clang-format off
        parent.addItem(
            Group {
                replaceLayoutOn(aspect, &AspectList::volatileItemListChanged, fill)
            }
        );
        // clang-format on
    }

    void addToLayoutImplListView(Layouting::Layout &parent, AspectList *aspect)
    {
        using namespace Layouting;
        using namespace Utils::QtcWidgets;

        QPushButton *removeButton = nullptr;
        QWidget *configWidget = nullptr;
        auto listView = aspect->createSubWidget<TreeView>();
        listView->header()->hide();
        listView->setModel(&model);
        listView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);

        auto add = [aspect, listView, this] {
            auto newItem = aspect->createAndAddItem();
            // Find new index
            auto item = model.findItemAtLevel<1>(
                [newItem](AspectListModelItem *item) { return item->hasAspect(newItem); });
            QModelIndex newIdx = model.indexForItem(item);
            listView->setCurrentIndex(newIdx);
        };

        auto removeCurrent = [listView, this, aspect] {
            QModelIndex currentIndex = listView->currentIndex();
            QTC_ASSERT(currentIndex.isValid(), return);
            const auto item = model.itemForIndex(currentIndex);
            QTC_ASSERT(item, return);
            aspect->removeItem(item->aspect());
        };
        QLayout *layout = nullptr;

        // clang-format off
        parent.addItem(
            Column {
                bindTo(&layout),
                Row {
                    listView,
                    Column {
                        PushButton {
                            text(Tr::tr("Add")),
                            onClicked(aspect, add),
                        },
                        PushButton {
                            bindTo(&removeButton),
                            text(Tr::tr("Remove")),
                            onClicked(aspect, removeCurrent),
                        },
                        st,
                    }
                }
            }
        );
        // clang-format on

        const auto onCurrentChanged =
            [listView, layout, configWidget, this](const QModelIndex &current) mutable {
                QWidget *newConfigWidget = nullptr;
                if (current.isValid()) {
                    newConfigWidget = new QWidget();
                    const AspectListModelItem *item = model.itemForIndex(current);
                    QTC_ASSERT(item, return);

                    if (auto container = dynamic_cast<AspectContainer *>(item->aspect().get()))
                        container->layouter()().attachTo(newConfigWidget);
                    else
                        Column{item->aspect().get()}.attachTo(newConfigWidget);
                }

                if (newConfigWidget) {
                    if (!configWidget) {
                        layout->addWidget(newConfigWidget);
                    } else {
                        delete layout->replaceWidget(configWidget, newConfigWidget);
                        delete configWidget;
                    }
                } else {
                    delete configWidget;
                }
                configWidget = newConfigWidget;
                listView->scrollTo(current, QListView::ScrollHint::EnsureVisible);
            };

        QObject::connect(
            listView->selectionModel(),
            &QItemSelectionModel::currentChanged,
            aspect,
            onCurrentChanged);
    }

    void addToLayoutImpl(Layouting::Layout &parent, AspectList *aspect)
    {
        if (displayStyle == AspectList::DisplayStyle::InlineList)
            addToLayoutImplInlineList(parent, aspect);
        else if (displayStyle == AspectList::DisplayStyle::ListViewWithDetails)
            addToLayoutImplListView(parent, aspect);
    }
};

AspectList::AspectList(Utils::AspectContainer *container)
    : Utils::BaseAspect(container)
    , d(std::make_unique<Internal::AspectListPrivate>(
          [this](BaseAspect *aspect, int role) -> QVariant {
              QTC_ASSERT(listViewDataCallback, return QString("No listViewDataCallback set"));
              return listViewDataCallback(aspect, role);
          }))
{}

AspectList::~AspectList() = default;

void AspectList::fromMap(const Utils::Store &map)
{
    QTC_ASSERT(!settingsKey().isEmpty(), return);

    setVariantValue(map[settingsKey()], BeQuiet);
    d->model.sync(*this);
}

QVariantList AspectList::toList(bool v) const
{
    QVariantList list;
    const auto &items = v ? d->volatileItems : d->items;

    for (const std::shared_ptr<BaseAspect> &item : items) {
        Utils::Store childStore;
        if (v)
            item->volatileToMap(childStore);
        else
            item->toMap(childStore);

        list.append(Utils::variantFromStore(childStore));
    }

    return list;
}

void AspectList::toMap(Utils::Store &map) const
{
    QTC_ASSERT(!settingsKey().isEmpty(), return);
    const Utils::Key key = settingsKey();
    map[key] = toList(false);
}

void AspectList::volatileToMap(Utils::Store &map) const
{
    QTC_ASSERT(!settingsKey().isEmpty(), return);
    const Utils::Key key = settingsKey();
    map[key] = toList(true);
}

std::shared_ptr<BaseAspect> AspectList::actualAddItem(const std::shared_ptr<BaseAspect> &item)
{
    item->setAutoApply(isAutoApply());
    item->setUndoStack(undoStack());

    d->volatileItems.append(item);
    d->connectVolatile(item, this);
    if (itemAddedCallback)
        itemAddedCallback(item);
    d->model.sync(*this);
    emit volatileValueChanged();
    emit volatileItemListChanged();
    if (isAutoApply())
        d->items = d->volatileItems;
    return item;
}

QList<std::shared_ptr<BaseAspect>> AspectList::items() const
{
    return d->items;
}
QList<std::shared_ptr<BaseAspect>> AspectList::volatileItems() const
{
    return d->volatileItems;
}

std::shared_ptr<BaseAspect> AspectList::createAndAddItem()
{
    return addItem(d->createItem());
}

std::shared_ptr<BaseAspect> AspectList::addItem(const std::shared_ptr<BaseAspect> &item)
{
    if (undoStack())
        undoStack()->push(new AddItemCommand(this, item));
    else
        return actualAddItem(item);

    return item;
}

void AspectList::actualRemoveItem(const std::shared_ptr<BaseAspect> &item)
{
    d->disconnectVolatile(item, this);
    d->volatileItems.removeOne(item);
    if (itemRemovedCallback)
        itemRemovedCallback(item);
    d->model.sync(*this);
    emit volatileValueChanged();
    emit volatileItemListChanged();
    if (isAutoApply())
        d->items = d->volatileItems;
}

void AspectList::removeItem(const std::shared_ptr<BaseAspect> &item)
{
    if (undoStack())
        undoStack()->push(new RemoveItemCommand(this, item));
    else
        actualRemoveItem(item);
}

void AspectList::clear()
{
    if (undoStack()) {
        undoStack()->beginMacro("Clear");

        for (const std::shared_ptr<BaseAspect> &item : volatileItems())
            undoStack()->push(new RemoveItemCommand(this, item));

        undoStack()->endMacro();
    } else {
        for (const std::shared_ptr<BaseAspect> &item : volatileItems())
            actualRemoveItem(item);
    }
}

void AspectList::apply()
{
    d->items = d->volatileItems;
    forEachItem([](BaseAspect *aspect) { aspect->apply(); });
    d->model.sync(*this);
    emit changed();
}

void AspectList::cancel()
{
    for (const auto &item : d->volatileItems)
        d->disconnectVolatile(item, this);

    d->volatileItems = d->items;

    for (const auto &item : d->volatileItems)
        d->connectVolatile(item, this);

    forEachItem([](BaseAspect *aspect) { aspect->cancel(); });
    d->model.sync(*this);
    emit volatileValueChanged();
    emit volatileItemListChanged();
}

void AspectList::setAutoApply(bool on)
{
    BaseAspect::setAutoApply(on);
    forEachItem([on](BaseAspect *aspect) { aspect->setAutoApply(on); });
}

void AspectList::setCreateItemFunction(CreateItem createItem)
{
    d->createItem = createItem;
}

qsizetype AspectList::size() const
{
    return d->volatileItems.size();
}

bool AspectList::isDirty() const
{
    if (d->items != d->volatileItems)
        return true;

    for (const std::shared_ptr<BaseAspect> &item : std::as_const(d->volatileItems)) {
        if (item->isDirty())
            return true;
    }
    return false;
}

void AspectList::setVariantValue(const QVariant &value, Announcement howToAnnounce)
{
    const QVariantList list = value.toList();
    for (const std::shared_ptr<BaseAspect> &item : d->volatileItems)
        d->disconnectVolatile(item, this);

    d->volatileItems.clear();
    for (const QVariant &entry : list) {
        auto item = d->createItem();
        item->setAutoApply(isAutoApply());
        item->setUndoStack(undoStack());
        item->fromMap(Utils::storeFromVariant(entry));
        d->volatileItems.append(item);
        d->connectVolatile(item, this);
    }

    d->items = d->volatileItems;
    if (howToAnnounce == DoEmit)
        emit changed();

    d->model.sync(*this);
}

class ColoredRow : public QWidget
{
public:
    ColoredRow(int idx, QWidget *parent = nullptr)
        : QWidget(parent)
        , m_index(idx)
    {}
    void paintEvent(QPaintEvent *event) override
    {
        QPainter p(this);
        QPalette pal = palette();
        if (m_index % 2 == 0)
            p.fillRect(event->rect(), pal.base());
        else
            p.fillRect(event->rect(), pal.alternateBase());
    }

private:
    int m_index;
};

void AspectList::setDisplayStyle(DisplayStyle displayStyle)
{
    d->displayStyle = displayStyle;
}

void AspectList::addToLayoutImpl(Layouting::Layout &parent)
{
    d->addToLayoutImpl(parent, this);
}

StringSelectionAspect::StringSelectionAspect(AspectContainer *container)
    : TypedAspect<QString>(container)
{}

QStandardItem *StringSelectionAspect::itemById(const QString &id)
{
    for (int i = 0; i < m_model->rowCount(); ++i) {
        auto cur = m_model->item(i);
        if (cur->data() == id)
            return cur;
    }

    return nullptr;
}

void StringSelectionAspect::volatileValueToGui()
{
    if (!m_model) {
        m_undoable.setSilently(m_volatileValue);
        return;
    }

    auto selected = itemById(m_volatileValue);
    if (selected) {
        m_undoable.setSilently(selected->data().toString());
        m_selectionModel->setCurrentIndex(selected->index(),
                                          QItemSelectionModel::SelectionFlag::ClearAndSelect);
        return;
    }

    if (m_model->rowCount() > 0) {
        m_undoable.setSilently(m_model->item(0)->data().toString());
        m_selectionModel->setCurrentIndex(m_model->item(0)->index(),
                                          QItemSelectionModel::SelectionFlag::ClearAndSelect);
    } else {
        m_undoable.setSilently(m_volatileValue);
        m_selectionModel->setCurrentIndex(QModelIndex(), QItemSelectionModel::SelectionFlag::Clear);
    }

    handleGuiChanged();
}

bool StringSelectionAspect::guiToVolatileValue()
{
    if (!m_model)
        return false;

    auto oldBuffer = m_volatileValue;

    m_volatileValue = m_undoable.get();

    return oldBuffer != m_volatileValue;
}

void StringSelectionAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    QTC_ASSERT(m_fillCallback, return);

    QComboBox *comboBox = createSubWidget<QComboBox>();

    connect(
        this,
        &StringSelectionAspect::modelChange,
        comboBox,
        [this, comboBox, lastValue = QVariant()](bool changing) mutable {
            if (changing) {
                comboBox->blockSignals(true);
                lastValue = m_volatileValue;
            } else {
                comboBox->blockSignals(false);
                if (lastValue != m_volatileValue) {
                    emit comboBox->currentIndexChanged(comboBox->currentIndex());
                    emit comboBox->currentTextChanged(comboBox->currentText());
                }
            }
        });

    if (!m_model) {
        m_model = new QStandardItemModel(this);
        m_selectionModel = new QItemSelectionModel(m_model);

        auto cb = [this](const QList<QStandardItem *> &items) {
            emit modelChange(true);

            QString oldValue = m_volatileValue;
            m_model->clear();
            for (QStandardItem *item : items)
                m_model->appendRow(item);

            volatileValueToGui();
            emit modelChange(false);
        };

        connect(this, &StringSelectionAspect::refillRequested, this, [this, cb] {
            m_fillCallback(cb);
        });

        m_fillCallback(cb);
    }

    comboBox->setInsertPolicy(QComboBox::InsertPolicy::NoInsert);
    comboBox->setEditable(m_comboBoxEditable);
    if (m_comboBoxEditable) {
        comboBox->completer()->setCompletionMode(QCompleter::PopupCompletion);
        comboBox->completer()->setFilterMode(Qt::MatchContains);
    }
    comboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
    comboBox->setCurrentText(value());
    comboBox->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);

    comboBox->setModel(m_model);
    setWheelScrollingWithoutFocusBlocked(comboBox);

    fixupComboBox(comboBox);

    connect(m_selectionModel,
            &QItemSelectionModel::currentChanged,
            comboBox,
            [comboBox](QModelIndex currentIdx) {
                if (currentIdx.isValid() && comboBox->currentIndex() != currentIdx.row())
                    comboBox->setCurrentIndex(currentIdx.row());
            });

    connect(comboBox, &QComboBox::activated, this, [this](int idx) {
        QModelIndex modelIdx = m_model->index(idx, 0);
        if (!modelIdx.isValid())
            return;

        const QString newValue = m_model->index(idx, 0).data(Qt::UserRole + 1).toString();
        m_undoable.set(undoStack(), newValue);
        volatileValueToGui();
    });

    connect(&m_undoable.m_signal, &UndoSignaller::changed, comboBox, [this, comboBox] {
        auto item = itemById(m_undoable.get());
        if (item)
            m_selectionModel->setCurrentIndex(item->index(), QItemSelectionModel::ClearAndSelect);
        else
            comboBox->setCurrentText(m_undoable.get());

        handleGuiChanged();
    });

    if (m_selectionModel->currentIndex().isValid())
        comboBox->setCurrentIndex(m_selectionModel->currentIndex().row());

    addLabeledItem(parent, comboBox);
}


//
// FontAspect
//

FontAspect::FontAspect(AspectContainer *container)
    : AspectContainer(container)
{}

QFont FontAspect::operator()() const
{
    return value();
}

QFont FontAspect::value() const
{
    QFont font;
    font.setFamily(fontFamily.value());
    font.setPointSize(fontPointSize.value());
    return font;
}

QFont FontAspect::volatileValue() const
{
    QFont font;
    font.setFamily(fontFamily.volatileValue());
    font.setPointSize(fontPointSize.volatileValue());
    return font;
}

void FontAspect::setValue(const QFont &font)
{
    fontFamily.setValue(font.family());
    fontPointSize.setValue(font.pointSize());
}

void FontAspect::setVolatileValue(const QFont &font)
{
    fontFamily.setVolatileValue(font.family());
    fontPointSize.setVolatileValue(font.pointSize());
}

void FontAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    parent.addItem(fontFamily);

    QComboBox *sizeComboBox = createSubWidget<QComboBox>();
    parent.addItem(fontPointSize.labelText());
    parent.addItem(sizeComboBox);

    auto updateFontSizeSelector = [this, sizeComboBox] {

        const QString family = fontFamily.volatileValue();
        const QString fontStyle = QFontDatabase::styleString(volatileValue());

        QList<int> pointSizes = QFontDatabase::pointSizes(family, fontStyle);
        if (pointSizes.empty())
            pointSizes = QFontDatabase::standardSizes();

        QSignalBlocker blocker(sizeComboBox);
        sizeComboBox->clear();
        sizeComboBox->setCurrentIndex(-1);
        sizeComboBox->setEnabled(!pointSizes.empty());

        //  try to maintain selection or select closest.
        if (pointSizes.empty())
            return;

        QString n;
        for (int pointSize : std::as_const(pointSizes))
            sizeComboBox->addItem(n.setNum(pointSize), QVariant(pointSize));

        int desiredPointSize = fontPointSize.volatileValue();

        //  try to maintain selection or select closest.
        int closestIndex = -1;
        int closestAbsError = 0xFFFF;

        const int pointSizeCount = sizeComboBox->count();
        for (int i = 0; i < pointSizeCount; i++) {
            const int itemPointSize = sizeComboBox->itemData(i).toInt();
            const int absError = qAbs(desiredPointSize - itemPointSize);
            if (absError < closestAbsError) {
                closestIndex  = i;
                closestAbsError = absError;
                if (closestAbsError == 0)
                    break;
            } else {    // past optimum
                if (absError > closestAbsError)
                    break;
            }
        }

        if (closestIndex != -1)
            sizeComboBox->setCurrentIndex(closestIndex);
    };

    updateFontSizeSelector();

    connect(sizeComboBox, &QComboBox::currentIndexChanged, this, [this, sizeComboBox] {
        int fontSize = 14;
        int currentIndex = sizeComboBox->currentIndex();
        if (currentIndex != -1)
            fontSize = sizeComboBox->itemData(currentIndex).toInt();
        fontPointSize.setVolatileValue(fontSize);
    });

    fontFamily.addOnVolatileValueChanged(sizeComboBox, updateFontSizeSelector);
}

void IdAspect::fromMap(const Store &map)
{
    if (skipSave())
        return;
    setValue(Id::fromSetting(map.value(settingsKey(), defaultValue().toSetting())), BeQuiet);
}

void IdAspect::toMap(Store &map) const
{
    saveToMap(map, value().toSetting(), defaultValue().toSetting(), settingsKey());
}

ByteArrayAspect::ByteArrayAspect(AspectContainer *container)
    : TypedAspect<QByteArray>(container)
{}

ByteArrayAspect::~ByteArrayAspect() = default;

void EnvironmentChangesAspect::addToLayoutImpl(Layouting::Layout &parent)
{
    auto label = createLabel();
    if (label)
        parent.addItem(label);

    auto changesLabel = new ElidingLabel();
    QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
    changesLabel->setSizePolicy(sizePolicy);
    changesLabel->setElideMode(Qt::ElideRight);
    auto updateChangesLabel = [this, changesLabel]() {
        const EnvironmentItems items = volatileValue().itemsFromUser();
        changesLabel->setText(EnvironmentItem::toShortSummary(items, false));
    };
    updateChangesLabel();
    connect(this, &EnvironmentChangesAspect::volatileValueChanged, this, updateChangesLabel);
    parent.addItem(changesLabel);

    QPushButton *changeButton = new QPushButton(Tr::tr("Change..."));
    changeButton->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);
    parent.addItem(changeButton);
    connect(changeButton, &QPushButton::clicked, this, [changeButton, this]() {
        std::optional<EnvironmentChanges> changes
            = runEnvironmentItemsDialog(changeButton, volatileValue());
        if (changes)
            setVolatileValue(*changes);
    });
}
} // namespace Utils
