/**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "aspects.h" #include "algorithm.h" #include "fancylineedit.h" #include "layoutbuilder.h" #include "pathchooser.h" #include "qtcassert.h" #include "qtcprocess.h" #include "qtcsettings.h" #include "utilsicons.h" #include "variablechooser.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Utils { namespace Internal { class BaseAspectPrivate { public: Utils::Id m_id; QVariant m_value; QVariant m_defaultValue; QString m_displayName; QString m_settingsKey; // Name of data in settings. QString m_tooltip; QString m_labelText; QPixmap m_labelPixmap; QIcon m_icon; QPointer m_label; // Owned by configuration widget QPointer m_action; // Owned by us. bool m_visible = true; bool m_enabled = true; bool m_readOnly = true; bool m_autoApply = true; BaseAspect::ConfigWidgetCreator m_configWidgetCreator; QList> m_subWidgets; }; } // Internal /*! \class Utils::BaseAspect \inmodule QtCreator \brief The \c BaseAspect class provides a common base for classes implementing aspects. An 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, selecting directories in the filesystem. While aspects implementations usually have the ability to visualize and to persist their data, or use an ID, neither of these is mandatory. */ /*! Constructs a BaseAspect. */ BaseAspect::BaseAspect() : d(new Internal::BaseAspectPrivate) {} /*! 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::value() const { return d->m_value; } /*! Sets value. Emits changed() if the value changed. */ void BaseAspect::setValue(const QVariant &value) { if (setValueQuietly(value)) { emit changed(); emitChangedValue(); } } /*! Sets value without emitting changed() Returns whether the value changed. */ bool BaseAspect::setValueQuietly(const QVariant &value) { if (d->m_value == value) return false; d->m_value = value; return true; } QVariant BaseAspect::defaultValue() const { return d->m_defaultValue; } /*! 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) { 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) { d->m_visible = visible; for (QWidget *w : qAsConst(d->m_subWidgets)) { QTC_ASSERT(w, continue); // 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()) d->m_label->setPixmap(d->m_labelPixmap); registerSubWidget(d->m_label); } void BaseAspect::addLabeledItem(LayoutBuilder &builder, QWidget *widget) { setupLabel(); if (QLabel *l = label()) { l->setBuddy(widget); builder.addItems({l, widget}); } else { builder.addItems({LayoutBuilder::LayoutItem(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; if (d->m_label) d->m_label->setText(labelText); } /*! 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; if (d->m_label) 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. */ QString BaseAspect::labelText() const { return d->m_labelText; } QLabel *BaseAspect::label() const { return d->m_label.data(); } 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) { d->m_tooltip = tooltip; for (QWidget *w : qAsConst(d->m_subWidgets)) { QTC_ASSERT(w, continue); w->setToolTip(tooltip); } } void BaseAspect::setEnabled(bool enabled) { d->m_enabled = enabled; for (QWidget *w : qAsConst(d->m_subWidgets)) { QTC_ASSERT(w, continue); w->setEnabled(enabled); } } void BaseAspect::setReadOnly(bool readOnly) { d->m_readOnly = readOnly; for (QWidget *w : qAsConst(d->m_subWidgets)) { QTC_ASSERT(w, continue); if (auto lineEdit = qobject_cast(w)) lineEdit->setReadOnly(readOnly); else if (auto textEdit = qobject_cast(w)) textEdit->setReadOnly(readOnly); } } 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 */ void BaseAspect::setConfigWidgetCreator(const ConfigWidgetCreator &configWidgetCreator) { d->m_configWidgetCreator = configWidgetCreator; } /*! Returns the key to be used when accessing the settings. \sa setSettingsKey() */ QString BaseAspect::settingsKey() const { return d->m_settingsKey; } /*! Sets the key to be used when accessing the settings. \sa settingsKey() */ void BaseAspect::setSettingsKey(const QString &key) { d->m_settingsKey = key; } /*! Sets the key and group to be used when accessing the settings. \sa settingsKey() */ void BaseAspect::setSettingsKey(const QString &group, const QString &key) { d->m_settingsKey = group + "/" + key; } /*! 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 { 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. */ 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() { // FIXME: Delete widgets? } 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); connect(widget, &QObject::destroyed, this, [this, widget] { d->m_subWidgets.removeAll(widget); }); widget->setEnabled(d->m_enabled); widget->setToolTip(d->m_tooltip); // 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); } void BaseAspect::saveToMap(QVariantMap &data, const QVariant &value, const QVariant &defaultValue, const QString &key) { if (key.isEmpty()) return; if (value == defaultValue) data.remove(key); else data.insert(key, value); } /*! Retrieves the internal value of this BaseAspect from a \c QVariantMap. */ void BaseAspect::fromMap(const QVariantMap &map) { setValue(map.value(settingsKey(), defaultValue())); } /*! Stores the internal value of this BaseAspect into a \c QVariantMap. */ void BaseAspect::toMap(QVariantMap &map) const { saveToMap(map, d->m_value, d->m_defaultValue, settingsKey()); } void BaseAspect::readSettings(const QSettings *settings) { if (settingsKey().isEmpty()) return; setValue(settings->value(settingsKey(), defaultValue())); } void BaseAspect::writeSettings(QSettings *settings) const { if (settingsKey().isEmpty()) return; QtcSettings::setValueWithDefault(settings, settingsKey(), value(), defaultValue()); } /*! \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. */ /*! Constructs a BaseAspects object. */ BaseAspects::BaseAspects() = default; /*! Destructs a BaseAspects object. */ BaseAspects::~BaseAspects() { qDeleteAll(m_aspects); } /*! Retrieves a BaseAspect with a given \a id, or nullptr if no such aspect is contained. \sa BaseAspect. */ BaseAspect *BaseAspects::aspect(Utils::Id id) const { return Utils::findOrDefault(m_aspects, Utils::equal(&BaseAspect::id, id)); } /*! \internal */ void BaseAspects::fromMap(const QVariantMap &map) const { for (BaseAspect *aspect : m_aspects) aspect->fromMap(map); } /*! \internal */ void BaseAspects::toMap(QVariantMap &map) const { for (BaseAspect *aspect : m_aspects) aspect->toMap(map); } namespace Internal { class BoolAspectPrivate { public: BoolAspect::LabelPlacement m_labelPlacement = BoolAspect::LabelPlacement::AtCheckBox; QPointer m_checkBox; // Owned by configuration widget }; class SelectionAspectPrivate { public: SelectionAspect::DisplayStyle m_displayStyle = SelectionAspect::DisplayStyle::RadioButtons; struct Option { QString displayName; QString tooltip; }; QVector