diff options
Diffstat (limited to 'src/libs/utils/aspects.cpp')
-rw-r--r-- | src/libs/utils/aspects.cpp | 1112 |
1 files changed, 951 insertions, 161 deletions
diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp index ef0f7b1866..3207497f4a 100644 --- a/src/libs/utils/aspects.cpp +++ b/src/libs/utils/aspects.cpp @@ -31,18 +31,24 @@ #include "pathchooser.h" #include "qtcassert.h" #include "qtcprocess.h" +#include "qtcsettings.h" #include "utilsicons.h" #include "variablechooser.h" +#include <QAction> #include <QButtonGroup> #include <QCheckBox> #include <QComboBox> +#include <QDebug> #include <QFormLayout> +#include <QGroupBox> #include <QLabel> #include <QLineEdit> #include <QListWidget> #include <QPointer> +#include <QPushButton> #include <QRadioButton> +#include <QSettings> #include <QSpinBox> #include <QTextEdit> #include <QToolButton> @@ -56,17 +62,24 @@ public: Utils::Id m_id; QVariant m_value; QVariant m_defaultValue; + std::function<QVariant(const QVariant &)> m_toSettings; + std::function<QVariant(const QVariant &)> m_fromSettings; QString m_displayName; QString m_settingsKey; // Name of data in settings. QString m_tooltip; QString m_labelText; QPixmap m_labelPixmap; + QIcon m_icon; QPointer<QLabel> m_label; // Owned by configuration widget + QPointer<QAction> m_action; // Owned by us. bool m_visible = true; bool m_enabled = true; bool m_readOnly = true; + bool m_autoApply = true; + int m_spanX = 1; + int m_spanY = 1; BaseAspect::ConfigWidgetCreator m_configWidgetCreator; QList<QPointer<QWidget>> m_subWidgets; }; @@ -102,7 +115,10 @@ BaseAspect::BaseAspect() /*! Destructs a BaseAspect. */ -BaseAspect::~BaseAspect() = default; +BaseAspect::~BaseAspect() +{ + delete d->m_action; +} Id BaseAspect::id() const { @@ -126,8 +142,10 @@ QVariant BaseAspect::value() const */ void BaseAspect::setValue(const QVariant &value) { - if (setValueQuietly(value)) + if (setValueQuietly(value)) { emit changed(); + emitChangedValue(); + } } /*! @@ -149,13 +167,18 @@ QVariant BaseAspect::defaultValue() const } /*! - Sets a default value for this aspect. + Sets a default 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::setDefaultValue(const QVariant &value) { d->m_defaultValue = value; + d->m_value = value; } void BaseAspect::setDisplayName(const QString &displayName) @@ -178,13 +201,19 @@ void BaseAspect::setVisible(bool visible) d->m_visible = visible; for (QWidget *w : qAsConst(d->m_subWidgets)) { QTC_ASSERT(w, continue); - w->setVisible(visible); + // This may happen during layout building. Explicit setting visibility here + // may create a show a toplevel widget for a moment until it is parented + // to some non-shown widget. + if (visible && w->parentWidget()) + w->setVisible(visible); } } void BaseAspect::setupLabel() { QTC_ASSERT(!d->m_label, delete d->m_label); + if (d->m_labelText.isEmpty() && d->m_labelPixmap.isNull()) + return; d->m_label = new QLabel(d->m_labelText); d->m_label->setTextInteractionFlags(Qt::TextSelectableByMouse); if (!d->m_labelPixmap.isNull()) @@ -192,6 +221,20 @@ void BaseAspect::setupLabel() registerSubWidget(d->m_label); } +void BaseAspect::addLabeledItem(LayoutBuilder &builder, QWidget *widget) +{ + setupLabel(); + if (QLabel *l = label()) { + l->setBuddy(widget); + builder.addItem(l); + LayoutBuilder::LayoutItem item(widget); + item.span = std::max(d->m_spanX - 1, 1); + builder.addItem(item); + } else { + builder.addItem(LayoutBuilder::LayoutItem(widget)); + } +} + /*! Sets \a labelText as text for the separate label in the visual representation of this aspect. @@ -214,6 +257,13 @@ void BaseAspect::setLabelPixmap(const QPixmap &labelPixmap) d->m_label->setPixmap(labelPixmap); } +void BaseAspect::setIcon(const QIcon &icon) +{ + d->m_icon = icon; + if (d->m_action) + d->m_action->setIcon(icon); +} + /*! Returns the current text for the separate label in the visual representation of this aspect. @@ -245,6 +295,11 @@ void BaseAspect::setToolTip(const QString &tooltip) } } +bool BaseAspect::isEnabled() const +{ + return d->m_enabled; +} + void BaseAspect::setEnabled(bool enabled) { d->m_enabled = enabled; @@ -254,6 +309,22 @@ void BaseAspect::setEnabled(bool enabled) } } +/*! + Makes the enabled state of this aspect depend on the checked state of \a checker. +*/ +void BaseAspect::setEnabler(BoolAspect *checker) +{ + QTC_ASSERT(checker, return); + setEnabled(checker->value()); + connect(checker, &BoolAspect::volatileValueChanged, this, &BaseAspect::setEnabled); + connect(checker, &BoolAspect::valueChanged, this, &BaseAspect::setEnabled); +} + +bool BaseAspect::isReadOnly() const +{ + return d->m_readOnly; +} + void BaseAspect::setReadOnly(bool readOnly) { d->m_readOnly = readOnly; @@ -266,6 +337,30 @@ void BaseAspect::setReadOnly(bool readOnly) } } +void BaseAspect::setSpan(int x, int y) +{ + d->m_spanX = x; + d->m_spanY = y; +} + +bool BaseAspect::isAutoApply() const +{ + return d->m_autoApply; +} + +/*! + Sets auto-apply mode. When auto-apply mode is on, 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 */ @@ -304,7 +399,17 @@ void BaseAspect::setSettingsKey(const QString &group, const QString &key) d->m_settingsKey = group + "/" + key; } -QString BaseAspect::displayName() const { return d->m_displayName; } +/*! + 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 @@ -314,6 +419,15 @@ QWidget *BaseAspect::createConfigWidget() const return d->m_configWidgetCreator ? d->m_configWidgetCreator() : nullptr; } +QAction *BaseAspect::action() +{ + if (!d->m_action) { + d->m_action = new QAction(labelText()); + d->m_action->setIcon(d->m_icon); + } + return d->m_action; +} + /*! Adds the visual representation of this aspect to a layout using a layout builder. @@ -322,10 +436,72 @@ void BaseAspect::addToLayout(LayoutBuilder &) { } +/*! + Updates this aspect's value from user-initiated changes in the widget. + + This has only an effect if \c isAutoApply is false. +*/ +void BaseAspect::apply() +{ + QTC_CHECK(!d->m_autoApply); + if (isDirty()) + setValue(volatileValue()); +} + +/*! + 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() +{ + QTC_CHECK(!d->m_autoApply); + if (!d->m_subWidgets.isEmpty()) + setVolatileValue(d->m_value); +} + +void BaseAspect::finish() +{ + // No qDeleteAll() possible as long as the connect in registerSubWidget() exist. + while (d->m_subWidgets.size()) + delete d->m_subWidgets.takeLast(); +} + +bool BaseAspect::hasAction() const +{ + return d->m_action != nullptr; +} + +bool BaseAspect::isDirty() const +{ + QTC_CHECK(!isAutoApply()); + // Aspects that were never shown cannot contain unsaved user changes. + if (d->m_subWidgets.isEmpty()) + return false; + return volatileValue() != d->m_value; +} + +QVariant BaseAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + return {}; +} + +void BaseAspect::setVolatileValue(const QVariant &val) +{ + Q_UNUSED(val); +} + void BaseAspect::registerSubWidget(QWidget *widget) { d->m_subWidgets.append(widget); + // FIXME: This interferes with qDeleteAll() in finish() and destructor, + // it would not be needed when all users actually deleted their subwidgets, + // e.g. the SettingsPage::finish() base implementation, but this still + // leaves the cases where no such base functionality is available, e.g. + // in the run/build config aspects. connect(widget, &QObject::destroyed, this, [this, widget] { d->m_subWidgets.removeAll(widget); }); @@ -340,11 +516,10 @@ void BaseAspect::registerSubWidget(QWidget *widget) } void BaseAspect::saveToMap(QVariantMap &data, const QVariant &value, - const QVariant &defaultValue, const QString &keyExtension) const + const QVariant &defaultValue, const QString &key) { - if (settingsKey().isEmpty()) + if (key.isEmpty()) return; - const QString key = settingsKey() + keyExtension; if (value == defaultValue) data.remove(key); else @@ -356,7 +531,8 @@ void BaseAspect::saveToMap(QVariantMap &data, const QVariant &value, */ void BaseAspect::fromMap(const QVariantMap &map) { - setValue(map.value(settingsKey(), defaultValue())); + const QVariant val = map.value(settingsKey(), toSettingsValue(defaultValue())); + setValue(fromSettingsValue(val)); } /*! @@ -364,66 +540,52 @@ void BaseAspect::fromMap(const QVariantMap &map) */ void BaseAspect::toMap(QVariantMap &map) const { - saveToMap(map, d->m_value, d->m_defaultValue); + saveToMap(map, toSettingsValue(d->m_value), toSettingsValue(d->m_defaultValue), settingsKey()); } -/*! - \internal -*/ -void BaseAspect::acquaintSiblings(const BaseAspects &) -{} - -// BaseAspects - -/*! - \class BaseAspects - \inmodule QtCreator - - \brief This class represent a collection of one or more aspects. - - A BaseAspects object assumes ownership on its aspects. -*/ +void BaseAspect::readSettings(const QSettings *settings) +{ + if (settingsKey().isEmpty()) + return; + const QVariant &val = settings->value(settingsKey()); + setValue(val.isValid() ? fromSettingsValue(val) : defaultValue()); +} -/*! - Constructs a BaseAspects object. -*/ -BaseAspects::BaseAspects() = default; +void BaseAspect::writeSettings(QSettings *settings) const +{ + if (settingsKey().isEmpty()) + return; + QtcSettings::setValueWithDefault(settings, + settingsKey(), + toSettingsValue(value()), + toSettingsValue(defaultValue())); +} -/*! - Destructs a BaseAspects object. -*/ -BaseAspects::~BaseAspects() +void BaseAspect::setFromSettingsTransformation(const SavedValueTransformation &transform) { - qDeleteAll(m_aspects); + d->m_fromSettings = transform; } -/*! - Retrieves a BaseAspect with a given \a id, or nullptr if no such aspect is contained. +void BaseAspect::setToSettingsTransformation(const SavedValueTransformation &transform) +{ + d->m_toSettings = transform; +} - \sa BaseAspect. -*/ -BaseAspect *BaseAspects::aspect(Utils::Id id) const +QVariant BaseAspect::toSettingsValue(const QVariant &val) const { - return Utils::findOrDefault(m_aspects, Utils::equal(&BaseAspect::id, id)); + return d->m_toSettings ? d->m_toSettings(val) : val; } -/*! - \internal -*/ -void BaseAspects::fromMap(const QVariantMap &map) const +QVariant BaseAspect::fromSettingsValue(const QVariant &val) const { - for (BaseAspect *aspect : m_aspects) - aspect->fromMap(map); + return d->m_fromSettings ? d->m_fromSettings(val) : val; } /*! \internal */ -void BaseAspects::toMap(QVariantMap &map) const -{ - for (BaseAspect *aspect : m_aspects) - aspect->toMap(map); -} +void BaseAspect::acquaintSiblings(const AspectContainer &) +{} namespace Internal { @@ -432,15 +594,17 @@ class BoolAspectPrivate public: BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox; QPointer<QCheckBox> m_checkBox; // Owned by configuration widget + QPointer<QGroupBox> m_groupBox; // For BoolAspects handling GroupBox check boxes }; class SelectionAspectPrivate { public: + ~SelectionAspectPrivate() { delete m_buttonGroup; } + SelectionAspect::DisplayStyle m_displayStyle = SelectionAspect::DisplayStyle::RadioButtons; - struct Option { QString displayName; QString tooltip; }; - QVector<Option> m_options; + QVector<SelectionAspect::Option> m_options; // These are all owned by the configuration widget. QList<QPointer<QRadioButton>> m_buttons; @@ -490,40 +654,50 @@ public: std::function<void()> m_openTerminal; bool m_undoRedoEnabled = true; + bool m_acceptRichText = false; bool m_showToolTipOnLabel = false; bool m_fileDialogOnly = false; + bool m_useResetButton = false; - template<class Widget> void updateWidgetFromCheckStatus(Widget *w) + template<class Widget> void updateWidgetFromCheckStatus(StringAspect *aspect, Widget *w) { const bool enabled = !m_checker || m_checker->value(); if (m_uncheckedSemantics == StringAspect::UncheckedSemantics::Disabled) - w->setEnabled(enabled); + w->setEnabled(enabled && aspect->isEnabled()); else - w->setReadOnly(!enabled); + w->setReadOnly(!enabled || aspect->isReadOnly()); } }; class IntegerAspectPrivate { public: - QVariant m_minimumValue; - QVariant m_maximumValue; + Utils::optional<qint64> m_minimumValue; + Utils::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 StringListAspectPrivate +class DoubleAspectPrivate { public: + Utils::optional<double> m_minimumValue; + Utils::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 AspectContainerPrivate +class StringListAspectPrivate { public: - QList<BaseAspect *> m_items; }; class TextDisplayPrivate @@ -586,7 +760,10 @@ public: StringAspect::StringAspect() : d(new Internal::StringAspectPrivate) -{} +{ + setDefaultValue(QString()); + setSpan(2, 1); // Default: Label + something +} /*! \internal @@ -631,9 +808,15 @@ void StringAspect::setValue(const QString &val) if (BaseAspect::setValueQuietly(QVariant(processedValue))) { update(); emit changed(); + emit valueChanged(processedValue); } } +void StringAspect::setDefaultValue(const QString &val) +{ + BaseAspect::setDefaultValue(val); +} + /*! \reimp */ @@ -650,7 +833,7 @@ void StringAspect::fromMap(const QVariantMap &map) */ void StringAspect::toMap(QVariantMap &map) const { - saveToMap(map, value(), QString()); + saveToMap(map, value(), defaultValue(), settingsKey()); if (d->m_checker) d->m_checker->toMap(map); } @@ -679,6 +862,11 @@ void StringAspect::setFilePath(const FilePath &value) setValue(value.toUserOutput()); } +PathChooser *StringAspect::pathChooser() const +{ + return d->m_pathChooserDisplay.data(); +} + /*! \internal */ @@ -798,11 +986,28 @@ void StringAspect::setUndoRedoEnabled(bool undoRedoEnabled) d->m_textEditDisplay->setUndoRedoEnabled(undoRedoEnabled); } +void StringAspect::setAcceptRichText(bool acceptRichText) +{ + d->m_acceptRichText = acceptRichText; + if (d->m_textEditDisplay) + d->m_textEditDisplay->setAcceptRichText(acceptRichText); +} + void StringAspect::setMacroExpanderProvider(const MacroExpanderProvider &expanderProvider) { d->m_expanderProvider = expanderProvider; } +void StringAspect::setUseGlobalMacroExpander() +{ + d->m_expanderProvider = &globalMacroExpander; +} + +void StringAspect::setUseResetButton() +{ + d->m_useResetButton = true; +} + void StringAspect::setValidationFunction(const FancyLineEdit::ValidationFunction &validator) { d->m_validator = validator; @@ -837,17 +1042,16 @@ void StringAspect::addToLayout(LayoutBuilder &builder) builder.finishRow(); } - setupLabel(); - builder.addItem(label()); - - const auto useMacroExpander = [this, &builder](QWidget *w) { + const auto useMacroExpander = [this](QWidget *w) { if (!d->m_expanderProvider) return; - const auto chooser = new VariableChooser(builder.layout()->parentWidget()); + const auto chooser = new VariableChooser(w); chooser->addSupportedWidget(w); chooser->addMacroExpanderProvider(d->m_expanderProvider); }; + const QString displayedString = d->m_displayFilter ? d->m_displayFilter(value()) : value(); + switch (d->m_displayStyle) { case PathChooserDisplay: d->m_pathChooserDisplay = createSubWidget<PathChooser>(); @@ -856,12 +1060,16 @@ void StringAspect::addToLayout(LayoutBuilder &builder) d->m_pathChooserDisplay->setHistoryCompleter(d->m_historyCompleterKey); d->m_pathChooserDisplay->setEnvironment(d->m_environment); d->m_pathChooserDisplay->setBaseDirectory(d->m_baseFileName); - useMacroExpander(d->m_pathChooserDisplay->lineEdit()); - connect(d->m_pathChooserDisplay, &PathChooser::pathChanged, - this, &StringAspect::setValue); - builder.addItem(d->m_pathChooserDisplay.data()); d->m_pathChooserDisplay->setFileDialogOnly(d->m_fileDialogOnly); d->m_pathChooserDisplay->setOpenTerminalHandler(d->m_openTerminal); + d->m_pathChooserDisplay->setFilePath(FilePath::fromString(displayedString)); + d->updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data()); + addLabeledItem(builder, d->m_pathChooserDisplay); + useMacroExpander(d->m_pathChooserDisplay->lineEdit()); + if (isAutoApply()) { + connect(d->m_pathChooserDisplay, &PathChooser::pathChanged, this, [this] { + setValue(d->m_pathChooserDisplay->path()); }); + } break; case LineEditDisplay: d->m_lineEditDisplay = createSubWidget<FancyLineEdit>(); @@ -870,28 +1078,48 @@ void StringAspect::addToLayout(LayoutBuilder &builder) d->m_lineEditDisplay->setHistoryCompleter(d->m_historyCompleterKey); if (d->m_validator) d->m_lineEditDisplay->setValidationFunction(d->m_validator); + d->m_lineEditDisplay->setTextKeepingActiveCursor(displayedString); + d->updateWidgetFromCheckStatus(this, d->m_lineEditDisplay.data()); + addLabeledItem(builder, d->m_lineEditDisplay); useMacroExpander(d->m_lineEditDisplay); - connect(d->m_lineEditDisplay, &FancyLineEdit::textEdited, - this, &StringAspect::setValue); - builder.addItem(d->m_lineEditDisplay.data()); + if (isAutoApply()) { + connect(d->m_lineEditDisplay, &FancyLineEdit::textEdited, + this, &StringAspect::setValue); + } + if (d->m_useResetButton) { + auto resetButton = createSubWidget<QPushButton>(tr("Reset")); + resetButton->setEnabled(d->m_lineEditDisplay->text() != defaultValue()); + connect(resetButton, &QPushButton::clicked, this, [this] { + d->m_lineEditDisplay->setText(defaultValue().toString()); + }); + connect(d->m_lineEditDisplay, &QLineEdit::textChanged, this, [this, resetButton] { + resetButton->setEnabled(d->m_lineEditDisplay->text() != defaultValue()); + }); + builder.addItem(resetButton); + } break; case TextEditDisplay: d->m_textEditDisplay = createSubWidget<QTextEdit>(); d->m_textEditDisplay->setPlaceholderText(d->m_placeHolderText); d->m_textEditDisplay->setUndoRedoEnabled(d->m_undoRedoEnabled); - d->m_textEditDisplay->setAcceptRichText(false); + d->m_textEditDisplay->setAcceptRichText(d->m_acceptRichText); d->m_textEditDisplay->setTextInteractionFlags(Qt::TextEditorInteraction); + d->m_textEditDisplay->setText(displayedString); + d->updateWidgetFromCheckStatus(this, d->m_textEditDisplay.data()); + addLabeledItem(builder, d->m_textEditDisplay); useMacroExpander(d->m_textEditDisplay); - connect(d->m_textEditDisplay, &QTextEdit::textChanged, this, [this] { - const QString value = d->m_textEditDisplay->document()->toPlainText(); - setValue(value); - }); - builder.addItem(d->m_textEditDisplay.data()); + if (isAutoApply()) { + connect(d->m_textEditDisplay, &QTextEdit::textChanged, this, [this] { + setValue(d->m_textEditDisplay->document()->toPlainText()); + }); + } break; case LabelDisplay: d->m_labelDisplay = createSubWidget<QLabel>(); d->m_labelDisplay->setTextInteractionFlags(Qt::TextSelectableByMouse); - builder.addItem(d->m_labelDisplay.data()); + d->m_labelDisplay->setText(displayedString); + d->m_labelDisplay->setToolTip(d->m_showToolTipOnLabel ? displayedString : toolTip()); + addLabeledItem(builder, d->m_labelDisplay); break; } @@ -899,8 +1127,50 @@ void StringAspect::addToLayout(LayoutBuilder &builder) if (d->m_checker && d->m_checkBoxPlacement == CheckBoxPlacement::Right) d->m_checker->addToLayout(builder); +} - update(); +QVariant StringAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + switch (d->m_displayStyle) { + case PathChooserDisplay: + QTC_ASSERT(d->m_pathChooserDisplay, return {}); + return d->m_pathChooserDisplay->path(); + case LineEditDisplay: + QTC_ASSERT(d->m_lineEditDisplay, return {}); + return d->m_lineEditDisplay->text(); + case TextEditDisplay: + QTC_ASSERT(d->m_textEditDisplay, return {}); + return d->m_textEditDisplay->document()->toPlainText(); + case LabelDisplay: + break; + } + return {}; +} + +void StringAspect::setVolatileValue(const QVariant &val) +{ + switch (d->m_displayStyle) { + case PathChooserDisplay: + if (d->m_pathChooserDisplay) + d->m_pathChooserDisplay->setPath(val.toString()); + break; + case LineEditDisplay: + if (d->m_lineEditDisplay) + d->m_lineEditDisplay->setText(val.toString()); + break; + case TextEditDisplay: + if (d->m_textEditDisplay) + d->m_textEditDisplay->document()->setPlainText(val.toString()); + break; + case LabelDisplay: + break; + } +} + +void StringAspect::emitChangedValue() +{ + emit valueChanged(value()); } void StringAspect::update() @@ -909,19 +1179,19 @@ void StringAspect::update() if (d->m_pathChooserDisplay) { d->m_pathChooserDisplay->setFilePath(FilePath::fromString(displayedString)); - d->updateWidgetFromCheckStatus(d->m_pathChooserDisplay.data()); + d->updateWidgetFromCheckStatus(this, d->m_pathChooserDisplay.data()); } if (d->m_lineEditDisplay) { d->m_lineEditDisplay->setTextKeepingActiveCursor(displayedString); - d->updateWidgetFromCheckStatus(d->m_lineEditDisplay.data()); + d->updateWidgetFromCheckStatus(this, d->m_lineEditDisplay.data()); } if (d->m_textEditDisplay) { const QString old = d->m_textEditDisplay->document()->toPlainText(); if (displayedString != old) d->m_textEditDisplay->setText(displayedString); - d->updateWidgetFromCheckStatus(d->m_textEditDisplay.data()); + d->updateWidgetFromCheckStatus(this, d->m_textEditDisplay.data()); } if (d->m_labelDisplay) { @@ -975,9 +1245,9 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement, BoolAspect::BoolAspect(const QString &settingsKey) : d(new Internal::BoolAspectPrivate) { - setValue(false); setDefaultValue(false); setSettingsKey(settingsKey); + setSpan(2, 1); } /*! @@ -995,23 +1265,73 @@ void BoolAspect::addToLayout(LayoutBuilder &builder) switch (d->m_labelPlacement) { case LabelPlacement::AtCheckBoxWithoutDummyLabel: d->m_checkBox->setText(labelText()); + builder.addItem(d->m_checkBox.data()); break; - case LabelPlacement::AtCheckBox: + case LabelPlacement::AtCheckBox: { d->m_checkBox->setText(labelText()); - builder.addItem(createSubWidget<QLabel>()); + LayoutBuilder::LayoutType type = builder.layoutType(); + if (type == LayoutBuilder::FormLayout) + builder.addItem(createSubWidget<QLabel>()); + builder.addItem(d->m_checkBox.data()); break; + } case LabelPlacement::InExtraLabel: - setupLabel(); - builder.addItem(label()); + addLabeledItem(builder, d->m_checkBox); break; } d->m_checkBox->setChecked(value()); - builder.addItem(d->m_checkBox.data()); - connect(d->m_checkBox.data(), &QAbstractButton::clicked, this, [this] { - setValue(d->m_checkBox->isChecked()); + if (isAutoApply()) { + connect(d->m_checkBox.data(), &QAbstractButton::clicked, + this, [this](bool val) { setValue(val); }); + } + connect(d->m_checkBox.data(), &QAbstractButton::clicked, + this, &BoolAspect::volatileValueChanged); +} + +QAction *BoolAspect::action() +{ + if (hasAction()) + return BaseAspect::action(); + auto act = BaseAspect::action(); // Creates it. + act->setCheckable(true); + act->setChecked(value()); + connect(act, &QAction::triggered, this, [this](bool newValue) { + // The check would be nice to have in simple conditions, but if we + // have an action that's used both on a settings page and as action + // in a menu like "Use FakeVim", isAutoApply() is false, and yet this + // here can trigger. + //QTC_CHECK(isAutoApply()); + setValue(newValue); }); + return act; } +QVariant BoolAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + if (d->m_checkBox) + return d->m_checkBox->isChecked(); + if (d->m_groupBox) + return d->m_groupBox->isChecked(); + QTC_CHECK(false); + return {}; +} + +void BoolAspect::setVolatileValue(const QVariant &val) +{ + QTC_CHECK(!isAutoApply()); + if (d->m_checkBox) + d->m_checkBox->setChecked(val.toBool()); + else if (d->m_groupBox) + d->m_groupBox->setChecked(val.toBool()); +} + +void BoolAspect::emitChangedValue() +{ + emit valueChanged(value()); +} + + /*! \reimp */ @@ -1026,16 +1346,40 @@ void BoolAspect::setValue(bool value) if (BaseAspect::setValueQuietly(value)) { if (d->m_checkBox) d->m_checkBox->setChecked(value); + //qDebug() << "SetValue: Changing" << labelText() << " to " << value; emit changed(); + //QTC_CHECK(!labelText().isEmpty()); + emit valueChanged(value); + //qDebug() << "SetValue: Changed" << labelText() << " to " << value; + if (hasAction()) { + //qDebug() << "SetValue: Triggering " << labelText() << "with" << value; + emit action()->triggered(value); + } } } +void BoolAspect::setDefaultValue(bool val) +{ + BaseAspect::setDefaultValue(val); +} + void BoolAspect::setLabel(const QString &labelText, LabelPlacement labelPlacement) { BaseAspect::setLabelText(labelText); d->m_labelPlacement = labelPlacement; } +void BoolAspect::setLabelPlacement(BoolAspect::LabelPlacement labelPlacement) +{ + d->m_labelPlacement = labelPlacement; +} + +void BoolAspect::setHandlesGroup(QGroupBox *box) +{ + registerSubWidget(box); + d->m_groupBox = box; +} + /*! \class Utils::SelectionAspect \inmodule QtCreator @@ -1049,7 +1393,9 @@ void BoolAspect::setLabel(const QString &labelText, LabelPlacement labelPlacemen SelectionAspect::SelectionAspect() : d(new Internal::SelectionAspectPrivate) -{} +{ + setSpan(2, 1); +} /*! \reimp @@ -1070,40 +1416,77 @@ void SelectionAspect::addToLayout(LayoutBuilder &builder) d->m_buttonGroup = new QButtonGroup(); d->m_buttonGroup->setExclusive(true); for (int i = 0, n = d->m_options.size(); i < n; ++i) { - const Internal::SelectionAspectPrivate::Option &option = d->m_options.at(i); - auto button = new QRadioButton(option.displayName); + 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); builder.addItems({{}, button}); d->m_buttons.append(button); - d->m_buttonGroup->addButton(button); - connect(button, &QAbstractButton::clicked, this, [this, i] { - setValue(i); - }); + d->m_buttonGroup->addButton(button, i); + if (isAutoApply()) { + connect(button, &QAbstractButton::clicked, this, [this, i] { + setValue(i); + }); + } } break; case DisplayStyle::ComboBox: - setupLabel(); setLabelText(displayName()); d->m_comboBox = createSubWidget<QComboBox>(); for (int i = 0, n = d->m_options.size(); i < n; ++i) d->m_comboBox->addItem(d->m_options.at(i).displayName); - connect(d->m_comboBox.data(), QOverload<int>::of(&QComboBox::activated), - this, &SelectionAspect::setValue); + if (isAutoApply()) { + connect(d->m_comboBox.data(), QOverload<int>::of(&QComboBox::activated), + this, &SelectionAspect::setValue); + } + connect(d->m_comboBox.data(), QOverload<int>::of(&QComboBox::currentIndexChanged), + this, &SelectionAspect::volatileValueChanged); d->m_comboBox->setCurrentIndex(value()); - builder.addItems({label(), d->m_comboBox.data()}); + addLabeledItem(builder, d->m_comboBox); + break; + } +} + +QVariant SelectionAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + switch (d->m_displayStyle) { + case DisplayStyle::RadioButtons: + QTC_ASSERT(d->m_buttonGroup, return {}); + return d->m_buttonGroup->checkedId(); + case DisplayStyle::ComboBox: + QTC_ASSERT(d->m_comboBox, return {}); + return d->m_comboBox->currentIndex(); + } + return {}; +} + +void SelectionAspect::setVolatileValue(const QVariant &val) +{ + QTC_CHECK(!isAutoApply()); + switch (d->m_displayStyle) { + case DisplayStyle::RadioButtons: { + if (d->m_buttonGroup) { + QAbstractButton *button = d->m_buttonGroup->button(val.toInt()); + QTC_ASSERT(button, return); + button->setChecked(true); + } + break; + } + case DisplayStyle::ComboBox: + if (d->m_comboBox) + d->m_comboBox->setCurrentIndex(val.toInt()); break; } } -void SelectionAspect::setVisibleDynamic(bool visible) +void SelectionAspect::finish() { - if (QLabel *l = label()) - l->setVisible(visible); - if (d->m_comboBox) - d->m_comboBox->setVisible(visible); - for (QRadioButton * const button : qAsConst(d->m_buttons)) - button->setVisible(visible); + delete d->m_buttonGroup; + d->m_buttonGroup = nullptr; + BaseAspect::finish(); + d->m_buttons.clear(); } void SelectionAspect::setDisplayStyle(SelectionAspect::DisplayStyle style) @@ -1127,14 +1510,72 @@ void SelectionAspect::setValue(int value) } } +void SelectionAspect::setStringValue(const QString &val) +{ + const int index = indexForDisplay(val); + QTC_ASSERT(index >= 0, return); + setValue(index); +} + +void SelectionAspect::setDefaultValue(int val) +{ + BaseAspect::setDefaultValue(val); +} + +// Note: This needs to be set after all options are added. +void SelectionAspect::setDefaultValue(const QString &val) +{ + BaseAspect::setDefaultValue(indexForDisplay(val)); +} + QString SelectionAspect::stringValue() const { return d->m_options.at(value()).displayName; } +QVariant SelectionAspect::itemValue() const +{ + return d->m_options.at(value()).itemData; +} + void SelectionAspect::addOption(const QString &displayName, const QString &toolTip) { - d->m_options.append({displayName, toolTip}); + d->m_options.append(Option(displayName, toolTip, {})); +} + +void SelectionAspect::addOption(const Option &option) +{ + d->m_options.append(option); +} + +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; +} + +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; } /*! @@ -1151,8 +1592,8 @@ void SelectionAspect::addOption(const QString &displayName, const QString &toolT MultiSelectionAspect::MultiSelectionAspect() : d(new Internal::MultiSelectionAspectPrivate(this)) { - setValue(QStringList()); setDefaultValue(QStringList()); + setSpan(2, 1); } /*! @@ -1171,7 +1612,6 @@ void MultiSelectionAspect::addToLayout(LayoutBuilder &builder) switch (d->m_displayStyle) { case DisplayStyle::ListView: - setupLabel(); d->m_listView = createSubWidget<QListWidget>(); for (const QString &val : qAsConst(d->m_allValues)) { auto item = new QListWidgetItem(val, d->m_listView); @@ -1183,7 +1623,7 @@ void MultiSelectionAspect::addToLayout(LayoutBuilder &builder) if (d->setValueSelectedHelper(item->text(), item->checkState() & Qt::Checked)) emit changed(); }); - builder.addItems({label(), d->m_listView.data()}); + addLabeledItem(builder, d->m_listView); } } @@ -1213,14 +1653,6 @@ void MultiSelectionAspect::setAllValues(const QStringList &val) d->m_allValues = val; } -void MultiSelectionAspect::setVisibleDynamic(bool visible) -{ - if (QLabel *l = label()) - l->setVisible(visible); - if (d->m_listView) - d->m_listView->setVisible(visible); -} - void MultiSelectionAspect::setDisplayStyle(MultiSelectionAspect::DisplayStyle style) { d->m_displayStyle = style; @@ -1268,6 +1700,7 @@ IntegerAspect::IntegerAspect() : d(new Internal::IntegerAspectPrivate) { setDefaultValue(qint64(0)); + setSpan(2, 1); } /*! @@ -1280,23 +1713,37 @@ IntegerAspect::~IntegerAspect() = default; */ void IntegerAspect::addToLayout(LayoutBuilder &builder) { - setupLabel(); - QTC_CHECK(!d->m_spinBox); d->m_spinBox = createSubWidget<QSpinBox>(); - d->m_spinBox->setValue(int(value() / d->m_displayScaleFactor)); d->m_spinBox->setDisplayIntegerBase(d->m_displayIntegerBase); d->m_spinBox->setPrefix(d->m_prefix); d->m_spinBox->setSuffix(d->m_suffix); - if (d->m_maximumValue.isValid() && d->m_maximumValue.isValid()) - d->m_spinBox->setRange(int(d->m_minimumValue.toLongLong() / d->m_displayScaleFactor), - int(d->m_maximumValue.toLongLong() / d->m_displayScaleFactor)); - - builder.addItems({label(), d->m_spinBox.data()}); - connect(d->m_spinBox.data(), QOverload<int>::of(&QSpinBox::valueChanged), - this, [this](int value) { - setValue(value * d->m_displayScaleFactor); - }); + d->m_spinBox->setSingleStep(d->m_singleStep); + d->m_spinBox->setSpecialValueText(d->m_specialValueText); + if (d->m_maximumValue && d->m_maximumValue) + d->m_spinBox->setRange(int(d->m_minimumValue.value() / d->m_displayScaleFactor), + int(d->m_maximumValue.value() / d->m_displayScaleFactor)); + d->m_spinBox->setValue(int(value() / d->m_displayScaleFactor)); // Must happen after setRange() + addLabeledItem(builder, d->m_spinBox); + + if (isAutoApply()) { + connect(d->m_spinBox.data(), QOverload<int>::of(&QSpinBox::valueChanged), + this, [this] { setValue(d->m_spinBox->value()); }); + } +} + +QVariant IntegerAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + QTC_ASSERT(d->m_spinBox, return {}); + return d->m_spinBox->value() * d->m_displayScaleFactor; +} + +void IntegerAspect::setVolatileValue(const QVariant &val) +{ + QTC_CHECK(!isAutoApply()); + if (d->m_spinBox) + d->m_spinBox->setValue(int(val.toLongLong() / d->m_displayScaleFactor)); } qint64 IntegerAspect::value() const @@ -1306,11 +1753,7 @@ qint64 IntegerAspect::value() const void IntegerAspect::setValue(qint64 value) { - if (setValueQuietly(value)) { - if (d->m_spinBox) - d->m_spinBox->setValue(int(value / d->m_displayScaleFactor)); - emit changed(); - } + BaseAspect::setValue(value); } void IntegerAspect::setRange(qint64 min, qint64 max) @@ -1349,6 +1792,121 @@ void IntegerAspect::setDefaultValue(qint64 defaultValue) BaseAspect::setDefaultValue(defaultValue); } +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() + : d(new Internal::DoubleAspectPrivate) +{ + setDefaultValue(double(0)); + setSpan(2, 1); +} + +/*! + \reimp +*/ +DoubleAspect::~DoubleAspect() = default; + +/*! + \reimp +*/ +void DoubleAspect::addToLayout(LayoutBuilder &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_maximumValue && d->m_maximumValue) + d->m_spinBox->setRange(d->m_minimumValue.value(), d->m_maximumValue.value()); + d->m_spinBox->setValue(value()); // Must happen after setRange()! + addLabeledItem(builder, d->m_spinBox); + + if (isAutoApply()) { + connect(d->m_spinBox.data(), QOverload<double>::of(&QDoubleSpinBox::valueChanged), + this, [this] { setValue(d->m_spinBox->value()); }); + } +} + +QVariant DoubleAspect::volatileValue() const +{ + QTC_CHECK(!isAutoApply()); + QTC_ASSERT(d->m_spinBox, return {}); + return d->m_spinBox->value(); +} + +void DoubleAspect::setVolatileValue(const QVariant &val) +{ + QTC_CHECK(!isAutoApply()); + if (d->m_spinBox) + d->m_spinBox->setValue(val.toDouble()); +} + +double DoubleAspect::value() const +{ + return BaseAspect::value().toDouble(); +} + +void DoubleAspect::setValue(double value) +{ + BaseAspect::setValue(value); +} + +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::setDefaultValue(double defaultValue) +{ + BaseAspect::setDefaultValue(defaultValue); +} + +void DoubleAspect::setSpecialValueText(const QString &specialText) +{ + d->m_specialValueText = specialText; +} + +void DoubleAspect::setSingleStep(double step) +{ + d->m_singleStep = step; +} + + /*! \class Utils::BaseTristateAspect \inmodule QtCreator @@ -1363,7 +1921,6 @@ TriStateAspect::TriStateAspect(const QString onString, const QString &offString, const QString &defaultString) { setDisplayStyle(DisplayStyle::ComboBox); - setValue(TriState::Default); setDefaultValue(TriState::Default); addOption(onString); addOption(offString); @@ -1435,6 +1992,88 @@ void StringListAspect::setValue(const QStringList &value) BaseAspect::setValue(value); } +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); +} + +/*! + \class Utils::IntegerListAspect + \inmodule QtCreator + + \brief A string list aspect represents a property of some object + that is a list of strings. +*/ + +IntegersAspect::IntegersAspect() +{ + setDefaultValue({}); +} + +/*! + \reimp +*/ +IntegersAspect::~IntegersAspect() = default; + +/*! + \reimp +*/ +void IntegersAspect::addToLayout(LayoutBuilder &builder) +{ + Q_UNUSED(builder) + // TODO - when needed. +} + +void IntegersAspect::emitChangedValue() +{ + emit valueChanged(value()); +} + +QList<int> IntegersAspect::value() const +{ + return Utils::transform(BaseAspect::value().toList(), + [](QVariant v) { return v.toInt(); }); +} + +void IntegersAspect::setValue(const QList<int> &value) +{ + BaseAspect::setValue(Utils::transform(value, &QVariant::fromValue<int>)); +} + +void IntegersAspect::setDefaultValue(const QList<int> &value) +{ + BaseAspect::setDefaultValue(Utils::transform(value, &QVariant::fromValue<int>)); +} + + /*! \class Utils::TextDisplay @@ -1471,9 +2110,12 @@ void TextDisplay::addToLayout(LayoutBuilder &builder) d->m_label->setTextInteractionFlags(Qt::TextSelectableByMouse); d->m_label->setElideMode(Qt::ElideNone); d->m_label->setWordWrap(true); + // 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); } builder.addItem(d->m_label.data()); - d->m_label->setVisible(isVisible()); } /*! @@ -1487,59 +2129,207 @@ void TextDisplay::setIconType(InfoLabel::InfoType t) d->m_label->setType(t); } +void TextDisplay::setText(const QString &message) +{ + d->m_message = 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. */ -AspectContainer::AspectContainer() - : d(new Internal::AspectContainerPrivate) +namespace Internal { + +class AspectContainerPrivate +{ +public: + QList<BaseAspect *> m_items; // Not owned + bool m_autoApply = true; + bool m_ownsSubAspects = false; + QStringList m_settingsGroup; +}; + +} // Internal + +AspectContainer::AspectContainer(QObject *parent) + : QObject(parent), d(new Internal::AspectContainerPrivate) {} /*! \reimp */ -AspectContainer::~AspectContainer() = default; +AspectContainer::~AspectContainer() +{ + if (d->m_ownsSubAspects) + qDeleteAll(d->m_items); +} /*! \internal */ -void AspectContainer::addAspectHelper(BaseAspect *aspect) +void AspectContainer::registerAspect(BaseAspect *aspect) { + aspect->setAutoApply(d->m_autoApply); d->m_items.append(aspect); - connect(aspect, &BaseAspect::changed, this, &BaseAspect::changed); } -/*! - Adds all visible sub-aspects to \a builder. -*/ -void AspectContainer::addToLayout(LayoutBuilder &builder) +void AspectContainer::registerAspects(const AspectContainer &aspects) { - for (BaseAspect *aspect : qAsConst(d->m_items)) { - if (aspect->isVisible()) - aspect->addToLayout(builder); - } + for (BaseAspect *aspect : qAsConst(aspects.d->m_items)) + registerAspect(aspect); } /*! - \reimp + 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 Utils::findOrDefault(d->m_items, Utils::equal(&BaseAspect::id, id)); +} + +AspectContainer::const_iterator AspectContainer::begin() const +{ + return d->m_items.begin(); +} + +AspectContainer::const_iterator AspectContainer::end() const +{ + return d->m_items.end(); +} + +const QList<BaseAspect *> &AspectContainer::aspects() const +{ + return d->m_items; +} + void AspectContainer::fromMap(const QVariantMap &map) { for (BaseAspect *aspect : qAsConst(d->m_items)) aspect->fromMap(map); + + emit fromMapFinished(); + } -/*! - \reimp -*/ void AspectContainer::toMap(QVariantMap &map) const { for (BaseAspect *aspect : qAsConst(d->m_items)) aspect->toMap(map); } +void AspectContainer::readSettings(QSettings *settings) +{ + for (const QString &group : d->m_settingsGroup) + settings->beginGroup(group); + + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->readSettings(settings); + + for (int i = 0; i != d->m_settingsGroup.size(); ++i) + settings->endGroup(); +} + +void AspectContainer::writeSettings(QSettings *settings) const +{ + for (const QString &group : d->m_settingsGroup) + settings->beginGroup(group); + + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->writeSettings(settings); + + for (int i = 0; i != d->m_settingsGroup.size(); ++i) + settings->endGroup(); +} + +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}; +} + +void AspectContainer::apply() +{ + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->apply(); + + emit applied(); +} + +void AspectContainer::cancel() +{ + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->cancel(); +} + +void AspectContainer::finish() +{ + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->finish(); +} + +void AspectContainer::reset() +{ + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->setValueQuietly(aspect->defaultValue()); +} + +void AspectContainer::setAutoApply(bool on) +{ + d->m_autoApply = on; + for (BaseAspect *aspect : qAsConst(d->m_items)) + aspect->setAutoApply(on); +} + +void AspectContainer::setOwnsSubAspects(bool on) +{ + d->m_ownsSubAspects = on; +} + +bool AspectContainer::isDirty() const +{ + for (BaseAspect *aspect : qAsConst(d->m_items)) { + if (aspect->isDirty()) + return true; + } + return false; +} + +bool AspectContainer::equals(const AspectContainer &other) const +{ + // FIXME: Expensive, but should not really be needed in a fully aspectified world. + QVariantMap thisMap, thatMap; + toMap(thisMap); + other.toMap(thatMap); + return thisMap == thatMap; +} + +void AspectContainer::copyFrom(const AspectContainer &other) +{ + QVariantMap map; + other.toMap(map); + fromMap(map); +} + +void AspectContainer::forEachAspect(const std::function<void(BaseAspect *)> &run) const +{ + for (BaseAspect *aspect : qAsConst(d->m_items)) { + if (auto container = dynamic_cast<AspectContainer *>(aspect)) + container->forEachAspect(run); + else + run(aspect); + } +} + } // namespace Utils |