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

#include "qdesigner_propertysheet_p.h"
#include "qdesigner_utils_p.h"
#include "formwindowbase_p.h"
#include "layoutinfo_p.h"
#include "qlayout_widget_p.h"
#include "qdesigner_introspection_p.h"

#include <QtDesigner/private/formbuilderextra_p.h>

#include <QtDesigner/abstractformwindow.h>
#include <QtDesigner/abstractformeditor.h>
#include <QtDesigner/abstractwidgetdatabase.h>

#include <QtWidgets/qlayout.h>
#include <QtWidgets/qdockwidget.h>
#include <QtWidgets/qdialog.h>
#include <QtWidgets/qgroupbox.h>
#include <QtWidgets/qlabel.h>
#include <QtWidgets/qgroupbox.h>
#include <QtWidgets/qstyle.h>
#include <QtWidgets/qabstractbutton.h>
#include <QtWidgets/qapplication.h>
#include <QtWidgets/qtoolbar.h>
#include <QtWidgets/qmainwindow.h>
#include <QtWidgets/qmenubar.h>
#include <QtWidgets/qheaderview.h>

#include <QtGui/qaction.h>

#include <QtCore/qdebug.h>
#include <QtCore/qhash.h>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

static const QDesignerMetaObjectInterface *propertyIntroducedBy(const QDesignerMetaObjectInterface *meta, int index)
{
    if (index >= meta->propertyOffset())
        return meta;

    if (meta->superClass())
        return propertyIntroducedBy(meta->superClass(), index);

    return nullptr;
}

// Layout fake properties (prefixed by 'layout' to distinguish them from other 'margins'
// that might be around. These are forwarded to the layout sheet (after name transformation).
//
// 'layoutObjectName' is new for 4.4. It is the name of the actual layout.
// Up to 4.3, QLayoutWidget's name was displayed in the objectinspector.
// This changes with 4.4; the layout name is displayed. This means that for
// old forms, QLayoutWidget will show up as ''; however, the uic code will
// still use 'verticalLayout' (in case someone accesses it). New Layouts get autogenerated names,
// legacy forms will keep their empty names (unless someone types in a new name).
static constexpr auto layoutObjectNameC = "layoutName"_L1;
static constexpr auto layoutLeftMarginC = "layoutLeftMargin"_L1;
static constexpr auto layoutTopMarginC = "layoutTopMargin"_L1;
static constexpr auto layoutRightMarginC = "layoutRightMargin"_L1;
static constexpr auto layoutBottomMarginC = "layoutBottomMargin"_L1;
static constexpr auto layoutSpacingC = "layoutSpacing"_L1;
static constexpr auto layoutHorizontalSpacingC = "layoutHorizontalSpacing"_L1;
static constexpr auto layoutVerticalSpacingC = "layoutVerticalSpacing"_L1;
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
static constexpr auto layoutSizeConstraintC = "layoutSizeConstraint"_L1;
#else
static constexpr auto layoutHorizontalSizeConstraintC = "horizontalSizeConstraint"_L1;
static constexpr auto layoutVerticalSizeConstraintC = "verticalSizeConstraint"_L1;
#endif
// form layout
static constexpr auto layoutFieldGrowthPolicyC = "layoutFieldGrowthPolicy"_L1;
static constexpr auto layoutRowWrapPolicyC = "layoutRowWrapPolicy"_L1;
static constexpr auto layoutLabelAlignmentC = "layoutLabelAlignment"_L1;
static constexpr auto layoutFormAlignmentC = "layoutFormAlignment"_L1;
// stretches
static constexpr auto layoutboxStretchPropertyC = "layoutStretch"_L1;
static constexpr auto layoutGridRowStretchPropertyC = "layoutRowStretch"_L1;
static constexpr auto layoutGridColumnStretchPropertyC = "layoutColumnStretch"_L1;
static constexpr auto layoutGridRowMinimumHeightC = "layoutRowMinimumHeight"_L1;
static constexpr auto layoutGridColumnMinimumWidthC = "layoutColumnMinimumWidth"_L1;

static bool hasLayoutAttributes(QDesignerFormEditorInterface *core, QObject *object)
{
    if (!object->isWidgetType())
        return false;

    QWidget *w =  qobject_cast<QWidget *>(object);
    if (const QDesignerWidgetDataBaseInterface *db = core->widgetDataBase()) {
        if (db->isContainer(w))
            return true;
    }
    return false;
}

// Cache DesignerMetaEnum by scope/name of a  QMetaEnum
static const qdesigner_internal::DesignerMetaEnum &designerMetaEnumFor(const QDesignerMetaEnumInterface *me)
{
    using ScopeNameKey = std::pair<QString, QString>;
    static QMap<ScopeNameKey, qdesigner_internal::DesignerMetaEnum> cache;

    const QString name = me->name();
    const QString scope = me->scope();

    const ScopeNameKey key = ScopeNameKey(scope, name);
    auto it = cache.find(key);
    if (it == cache.end()) {
        qdesigner_internal::DesignerMetaEnum dme = qdesigner_internal::DesignerMetaEnum(name, scope, me->separator());
        const int keyCount = me->keyCount();
        for (int i=0; i < keyCount; ++i)
            dme.addKey(me->value(i), me->key(i));
        it = cache.insert(key, dme);
    }
    return it.value();
}

// Cache DesignerMetaFlags by scope/name of a  QMetaEnum
static const qdesigner_internal::DesignerMetaFlags &designerMetaFlagsFor(const QDesignerMetaEnumInterface *me)
{
    using ScopeNameKey = std::pair<QString, QString>;
    static QMap<ScopeNameKey, qdesigner_internal::DesignerMetaFlags> cache;

    const QString name = me->name();
    const QString scope = me->scope();

    const ScopeNameKey key = ScopeNameKey(scope, name);
    auto it = cache.find(key);
    if (it == cache.end()) {
        auto dme = qdesigner_internal::DesignerMetaFlags(me->enumName(), scope, me->separator());
        const int keyCount = me->keyCount();
        for (int i=0; i < keyCount; ++i)
            dme.addKey(me->value(i), me->key(i));
        it = cache.insert(key, dme);
    }
    return it.value();
}

// ------------ QDesignerMemberSheetPrivate
class QDesignerPropertySheetPrivate {
public:
    using PropertyType = QDesignerPropertySheet::PropertyType;
    using ObjectType = QDesignerPropertySheet::ObjectType;
    using ObjectFlags = QDesignerPropertySheet::ObjectFlags;

    explicit QDesignerPropertySheetPrivate(QDesignerPropertySheet *sheetPublic, QObject *object, QObject *sheetParent);

    bool invalidIndex(const char *functionName, int index) const;
    inline int count() const { return m_meta->propertyCount() + m_addProperties.size(); }

    PropertyType propertyType(int index) const;
    QString transformLayoutPropertyName(int index) const;
    QLayout* layout(QDesignerPropertySheetExtension **layoutPropertySheet = nullptr) const;
    static ObjectType objectType(const QObject *o);

    bool isReloadableProperty(int index) const;
    bool isResourceProperty(int index) const;
    void addResourceProperty(int index, int type);
    QVariant resourceProperty(int index) const;
    void setResourceProperty(int index, const QVariant &value);
    QVariant emptyResourceProperty(int index) const; // of type PropertySheetPixmapValue / PropertySheetIconValue
    QVariant defaultResourceProperty(int index) const; // of type QPixmap / QIcon (maybe it can be generalized for all types, not resource only)

    bool isStringProperty(int index) const;
    void addStringProperty(int index);
    qdesigner_internal::PropertySheetStringValue stringProperty(int index) const;
    void setStringProperty(int index, const qdesigner_internal::PropertySheetStringValue &value);
    bool isStringListProperty(int index) const;
    void addStringListProperty(int index);
    qdesigner_internal::PropertySheetStringListValue stringListProperty(int index) const;
    void setStringListProperty(int index, const qdesigner_internal::PropertySheetStringListValue &value);

    bool isKeySequenceProperty(int index) const;
    void addKeySequenceProperty(int index);
    qdesigner_internal::PropertySheetKeySequenceValue keySequenceProperty(int index) const;
    void setKeySequenceProperty(int index, const qdesigner_internal::PropertySheetKeySequenceValue &value);

    enum PropertyKind { NormalProperty, FakeProperty, DynamicProperty, DefaultDynamicProperty };
    class Info {
    public:
        Info() = default;

        QString group;
        QVariant defaultValue;
        bool changed = false;
        bool visible = true;
        bool attribute = false;
        bool reset = true;
        PropertyType propertyType = QDesignerPropertySheet::PropertyNone;
        PropertyKind kind = NormalProperty;
    };

    Info &ensureInfo(int index);

    QDesignerPropertySheet *q;
    QDesignerFormEditorInterface *m_core;
    const QDesignerMetaObjectInterface *m_meta;
    const ObjectType m_objectType;
    const ObjectFlags m_objectFlags;

    QHash<int, Info> m_info;
    QHash<int, QVariant> m_fakeProperties;
    QHash<int, QVariant> m_addProperties;
    QHash<QString, int> m_addIndex;
    QHash<int, QVariant> m_resourceProperties; // only PropertySheetPixmapValue snd PropertySheetIconValue here
    QHash<int, qdesigner_internal::PropertySheetStringValue> m_stringProperties; // only PropertySheetStringValue
    QHash<int, qdesigner_internal::PropertySheetStringListValue> m_stringListProperties; // only PropertySheetStringListValue
    QHash<int, qdesigner_internal::PropertySheetKeySequenceValue> m_keySequenceProperties; // only PropertySheetKeySequenceValue

    const bool m_canHaveLayoutAttributes;

    // Variables used for caching the layout, access via layout().
    QPointer<QObject> m_object;
    mutable QPointer<QLayout> m_lastLayout;
    mutable QDesignerPropertySheetExtension *m_lastLayoutPropertySheet;
    mutable bool m_LastLayoutByDesigner;

    qdesigner_internal::DesignerPixmapCache *m_pixmapCache;
    qdesigner_internal::DesignerIconCache *m_iconCache;
    QPointer<qdesigner_internal::FormWindowBase> m_fwb;

    // Enable Qt's internal properties starting with prefix "_q_"
    static bool m_internalDynamicPropertiesEnabled;
};

bool QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled = false;

/*
    The property is reloadable if its contents depends on resource.
*/
bool QDesignerPropertySheetPrivate::isReloadableProperty(int index) const
{
    return isResourceProperty(index)
           || propertyType(index) == QDesignerPropertySheet::PropertyStyleSheet
           || propertyType(index) == QDesignerPropertySheet::PropertyText
           || q->property(index).metaType().id() == QMetaType::QUrl;
}

/*
    Resource properties are those which:
        1) are reloadable
        2) their state is associated with a file which can be taken from resources
        3) we don't store them in Qt meta object system (because designer keeps different data structure for them)
*/

bool QDesignerPropertySheetPrivate::isResourceProperty(int index) const
{
    return m_resourceProperties.contains(index);
}

void QDesignerPropertySheetPrivate::addResourceProperty(int index, int type)
{
    if (type == QMetaType::QPixmap)
        m_resourceProperties.insert(index, QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue()));
    else if (type == QMetaType::QIcon)
        m_resourceProperties.insert(index, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()));
}

QVariant QDesignerPropertySheetPrivate::emptyResourceProperty(int index) const
{
    QVariant v = m_resourceProperties.value(index);
    if (v.canConvert<qdesigner_internal::PropertySheetPixmapValue>())
        return QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue());
    if (v.canConvert<qdesigner_internal::PropertySheetIconValue>())
        return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue());
    return v;
}

QVariant QDesignerPropertySheetPrivate::defaultResourceProperty(int index) const
{
    return m_info.value(index).defaultValue;
}

QVariant QDesignerPropertySheetPrivate::resourceProperty(int index) const
{
    return m_resourceProperties.value(index);
}

void QDesignerPropertySheetPrivate::setResourceProperty(int index, const QVariant &value)
{
    Q_ASSERT(isResourceProperty(index));

    QVariant &v = m_resourceProperties[index];
    if ((value.canConvert<qdesigner_internal::PropertySheetPixmapValue>() && v.canConvert<qdesigner_internal::PropertySheetPixmapValue>())
        || (value.canConvert<qdesigner_internal::PropertySheetIconValue>() && v.canConvert<qdesigner_internal::PropertySheetIconValue>()))
        v = value;
}

bool QDesignerPropertySheetPrivate::isStringProperty(int index) const
{
    return m_stringProperties.contains(index);
}

void QDesignerPropertySheetPrivate::addStringProperty(int index)
{
    m_stringProperties.insert(index, qdesigner_internal::PropertySheetStringValue());
}

qdesigner_internal::PropertySheetStringValue QDesignerPropertySheetPrivate::stringProperty(int index) const
{
    return m_stringProperties.value(index);
}

void QDesignerPropertySheetPrivate::setStringProperty(int index, const qdesigner_internal::PropertySheetStringValue &value)
{
    Q_ASSERT(isStringProperty(index));

    m_stringProperties[index] = value;
}

bool QDesignerPropertySheetPrivate::isStringListProperty(int index) const
{
    return m_stringListProperties.contains(index);
}

void QDesignerPropertySheetPrivate::addStringListProperty(int index)
{
    m_stringListProperties.insert(index, qdesigner_internal::PropertySheetStringListValue());
}

qdesigner_internal::PropertySheetStringListValue QDesignerPropertySheetPrivate::stringListProperty(int index) const
{
    return m_stringListProperties.value(index);
}

void QDesignerPropertySheetPrivate::setStringListProperty(int index, const qdesigner_internal::PropertySheetStringListValue &value)
{
    Q_ASSERT(isStringListProperty(index));

    m_stringListProperties[index] = value;
}

bool QDesignerPropertySheetPrivate::isKeySequenceProperty(int index) const
{
    return m_keySequenceProperties.contains(index);
}

void QDesignerPropertySheetPrivate::addKeySequenceProperty(int index)
{
    m_keySequenceProperties.insert(index, qdesigner_internal::PropertySheetKeySequenceValue());
}

qdesigner_internal::PropertySheetKeySequenceValue QDesignerPropertySheetPrivate::keySequenceProperty(int index) const
{
    return m_keySequenceProperties.value(index);
}

void QDesignerPropertySheetPrivate::setKeySequenceProperty(int index, const qdesigner_internal::PropertySheetKeySequenceValue &value)
{
    Q_ASSERT(isKeySequenceProperty(index));

    m_keySequenceProperties[index] = value;
}

QDesignerPropertySheetPrivate::QDesignerPropertySheetPrivate(QDesignerPropertySheet *sheetPublic, QObject *object, QObject *sheetParent) :
    q(sheetPublic),
    m_core(QDesignerPropertySheet::formEditorForObject(sheetParent)),
    m_meta(m_core->introspection()->metaObject(object)),
    m_objectType(QDesignerPropertySheet::objectTypeFromObject(object)),
    m_objectFlags(QDesignerPropertySheet::objectFlagsFromObject(object)),
    m_canHaveLayoutAttributes(hasLayoutAttributes(m_core, object)),
    m_object(object),
    m_lastLayout(nullptr),
    m_lastLayoutPropertySheet(nullptr),
    m_LastLayoutByDesigner(false),
    m_pixmapCache(nullptr),
    m_iconCache(nullptr)
{
}

qdesigner_internal::FormWindowBase *QDesignerPropertySheet::formWindowBase() const
{
    return d->m_fwb;
}

bool QDesignerPropertySheetPrivate::invalidIndex(const char *functionName, int index) const
{
    if (index < 0 || index >= count()) {
        qWarning() <<  "** WARNING " << functionName << " invoked for " << m_object->objectName() << " was  passed an invalid index " << index << '.';
        return true;
    }
    return false;
}

QLayout* QDesignerPropertySheetPrivate::layout(QDesignerPropertySheetExtension **layoutPropertySheet) const
{
    // Return the layout and its property sheet
    // only if it is managed by designer and not one created on a custom widget.
    // (attempt to cache the value as this requires some hoops).
    if (layoutPropertySheet)
        *layoutPropertySheet = nullptr;

    if (!m_object->isWidgetType() || !m_canHaveLayoutAttributes)
        return nullptr;

    QWidget *widget = qobject_cast<QWidget*>(m_object);
    QLayout *widgetLayout = qdesigner_internal::LayoutInfo::internalLayout(widget);
    if (!widgetLayout) {
        m_lastLayout = nullptr;
        m_lastLayoutPropertySheet = nullptr;
        return nullptr;
    }
    // Smart logic to avoid retrieving the meta DB from the widget every time.
    if (widgetLayout != m_lastLayout) {
        m_lastLayout = widgetLayout;
        m_LastLayoutByDesigner = false;
        m_lastLayoutPropertySheet = nullptr;
        // Is this a layout managed by designer or some layout on a custom widget?
        if (qdesigner_internal::LayoutInfo::managedLayout(m_core ,widgetLayout)) {
            m_LastLayoutByDesigner = true;
            m_lastLayoutPropertySheet = qt_extension<QDesignerPropertySheetExtension*>(m_core->extensionManager(), m_lastLayout);
        }
    }
    if (!m_LastLayoutByDesigner)
        return nullptr;

    if (layoutPropertySheet)
        *layoutPropertySheet = m_lastLayoutPropertySheet;

    return  m_lastLayout;
}

QDesignerPropertySheetPrivate::Info &QDesignerPropertySheetPrivate::ensureInfo(int index)
{
    auto it = m_info.find(index);
    if (it == m_info.end())
        it = m_info.insert(index, Info());
    return it.value();
}

QDesignerPropertySheet::PropertyType QDesignerPropertySheetPrivate::propertyType(int index) const
{
    const auto it = m_info.constFind(index);
    if (it == m_info.constEnd())
        return QDesignerPropertySheet::PropertyNone;
    return it.value().propertyType;
}

QString QDesignerPropertySheetPrivate::transformLayoutPropertyName(int index) const
{
    using TypeNameMap = QMap<QDesignerPropertySheet::PropertyType, QString>;
    static const TypeNameMap typeNameMap = {
        {QDesignerPropertySheet::PropertyLayoutObjectName, u"objectName"_s},
        {QDesignerPropertySheet::PropertyLayoutLeftMargin, u"leftMargin"_s},
        {QDesignerPropertySheet::PropertyLayoutTopMargin, u"topMargin"_s},
        {QDesignerPropertySheet::PropertyLayoutRightMargin, u"rightMargin"_s},
        {QDesignerPropertySheet::PropertyLayoutBottomMargin, u"bottomMargin"_s},
        {QDesignerPropertySheet::PropertyLayoutSpacing, u"spacing"_s},
        {QDesignerPropertySheet::PropertyLayoutHorizontalSpacing, u"horizontalSpacing"_s},
        {QDesignerPropertySheet::PropertyLayoutVerticalSpacing, u"verticalSpacing"_s},
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
        {QDesignerPropertySheet::PropertyLayoutSizeConstraint, u"sizeConstraint"_s},
#else
        {QDesignerPropertySheet::PropertyLayoutHorizontalSizeConstraint, layoutHorizontalSizeConstraintC},
        {QDesignerPropertySheet::PropertyLayoutVerticalSizeConstraint, layoutVerticalSizeConstraintC},
#endif
        {QDesignerPropertySheet::PropertyLayoutFieldGrowthPolicy, u"fieldGrowthPolicy"_s},
        {QDesignerPropertySheet::PropertyLayoutRowWrapPolicy, u"rowWrapPolicy"_s},
        {QDesignerPropertySheet::PropertyLayoutLabelAlignment, u"labelAlignment"_s},
        {QDesignerPropertySheet::PropertyLayoutFormAlignment, u"formAlignment"_s},
        {QDesignerPropertySheet::PropertyLayoutBoxStretch, u"stretch"_s},
        {QDesignerPropertySheet::PropertyLayoutGridRowStretch, u"rowStretch"_s},
        {QDesignerPropertySheet::PropertyLayoutGridColumnStretch, u"columnStretch"_s},
        {QDesignerPropertySheet::PropertyLayoutGridRowMinimumHeight, u"rowMinimumHeight"_s},
        {QDesignerPropertySheet::PropertyLayoutGridColumnMinimumWidth, u"columnMinimumWidth"_s}
    };
    const auto it = typeNameMap.constFind(propertyType(index));
    if (it != typeNameMap.constEnd())
        return it.value();
    return QString();
}

// ----------- QDesignerPropertySheet

QDesignerPropertySheet::ObjectType QDesignerPropertySheet::objectTypeFromObject(const QObject *o)
{
    if (qobject_cast<const QLayout *>(o))
        return ObjectLayout;

    if (!o->isWidgetType())
        return ObjectNone;

    if (qobject_cast<const QLayoutWidget *>(o))
        return ObjectLayoutWidget;

    if (qobject_cast<const QLabel*>(o))
        return ObjectLabel;

    return ObjectNone;
}

QDesignerPropertySheet::ObjectFlags QDesignerPropertySheet::objectFlagsFromObject(const QObject *o)
{
    ObjectFlags result;
    if ((o->isWidgetType() && (qobject_cast<const QAbstractButton *>(o)
                               || qobject_cast<const QGroupBox *>(o)))
        || qobject_cast<const QAction *>(o)) {
        result |= CheckableProperty;
    }
    return result;
}

QDesignerPropertySheet::PropertyType QDesignerPropertySheet::propertyTypeFromName(const QString &name)
{
    static const QHash<QString, PropertyType> propertyTypeHash = {
        {layoutObjectNameC,         PropertyLayoutObjectName},
        {layoutLeftMarginC,         PropertyLayoutLeftMargin},
        {layoutTopMarginC,          PropertyLayoutTopMargin},
        {layoutRightMarginC,        PropertyLayoutRightMargin},
        {layoutBottomMarginC,       PropertyLayoutBottomMargin},
        {layoutSpacingC,            PropertyLayoutSpacing},
        {layoutHorizontalSpacingC,  PropertyLayoutHorizontalSpacing},
        {layoutVerticalSpacingC,    PropertyLayoutVerticalSpacing},
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
        {layoutSizeConstraintC,     PropertyLayoutSizeConstraint},
#else
        {layoutHorizontalSizeConstraintC, PropertyLayoutHorizontalSizeConstraint},
        {layoutVerticalSizeConstraintC,   PropertyLayoutVerticalSizeConstraint},
#endif
        {layoutFieldGrowthPolicyC,  PropertyLayoutFieldGrowthPolicy},
        {layoutRowWrapPolicyC,      PropertyLayoutRowWrapPolicy},
        {layoutLabelAlignmentC,     PropertyLayoutLabelAlignment},
        {layoutFormAlignmentC,      PropertyLayoutFormAlignment},
        {layoutboxStretchPropertyC, PropertyLayoutBoxStretch},
        {layoutGridRowStretchPropertyC,    PropertyLayoutGridRowStretch},
        {layoutGridColumnStretchPropertyC, PropertyLayoutGridColumnStretch},
        {layoutGridRowMinimumHeightC,      PropertyLayoutGridRowMinimumHeight},
        {layoutGridColumnMinimumWidthC,    PropertyLayoutGridColumnMinimumWidth},
        {u"buddy"_s,                   PropertyBuddy},
        {u"geometry"_s,                PropertyGeometry},
        {u"checked"_s,                 PropertyChecked},
        {u"checkable"_s,               PropertyCheckable},
        {u"accessibleName"_s,          PropertyAccessibility},
        {u"accessibleDescription"_s,   PropertyAccessibility},
        {u"visible"_s,                 PropertyVisible},
        {u"windowTitle"_s,             PropertyWindowTitle},
        {u"windowIcon"_s,              PropertyWindowIcon},
        {u"windowFilePath"_s,          PropertyWindowFilePath},
        {u"windowOpacity"_s,           PropertyWindowOpacity},
        {u"windowIconText"_s,          PropertyWindowIconText},
        {u"windowModality"_s,          PropertyWindowModality},
        {u"windowModified"_s,          PropertyWindowModified},
        {u"styleSheet"_s,              PropertyStyleSheet},
        {u"text"_s,                    PropertyText}
    };
    return propertyTypeHash.value(name, PropertyNone);
}

QDesignerPropertySheet::QDesignerPropertySheet(QObject *object, QObject *parent) :
    QObject(parent),
    d(new QDesignerPropertySheetPrivate(this, object, parent))
{
    using Info = QDesignerPropertySheetPrivate::Info;
    const QDesignerMetaObjectInterface *baseMeta = d->m_meta;

    while (baseMeta &&baseMeta->className().startsWith("QDesigner"_L1)) {
        baseMeta = baseMeta->superClass();
    }
    Q_ASSERT(baseMeta != nullptr);

    QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(d->m_object);
    d->m_fwb = qobject_cast<qdesigner_internal::FormWindowBase *>(formWindow);
    if (d->m_fwb) {
        d->m_pixmapCache = d->m_fwb->pixmapCache();
        d->m_iconCache = d->m_fwb->iconCache();
        d->m_fwb->addReloadablePropertySheet(this, object);
    }

    for (int index=0; index<count(); ++index) {
        const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
        const QString name = p->name();
        if (p->type() == QMetaType::QKeySequence) {
            createFakeProperty(name);
        } else {
            setVisible(index, false); // use the default for `real' properties
        }

        QString pgroup = baseMeta->className();

        if (const QDesignerMetaObjectInterface *pmeta = propertyIntroducedBy(baseMeta, index)) {
            pgroup = pmeta->className();
        }

        Info &info = d->ensureInfo(index);
        info.group = pgroup;
        info.propertyType = propertyTypeFromName(name);

        const int type = p->type();
        switch (type) {
        case QMetaType::QCursor:
        case QMetaType::QIcon:
        case QMetaType::QPixmap:
            info.defaultValue = p->read(d->m_object);
            if (type == QMetaType::QIcon || type == QMetaType::QPixmap)
                d->addResourceProperty(index, type);
            break;
        case QMetaType::QString:
            d->addStringProperty(index);
            break;
        case QMetaType::QStringList:
            d->addStringListProperty(index);
            break;
        case QMetaType::QKeySequence:
            d->addKeySequenceProperty(index);
            break;
        default:
            break;
        }
    }

    if (object->isWidgetType()) {
        createFakeProperty(u"focusPolicy"_s);
        createFakeProperty(u"cursor"_s);
        createFakeProperty(u"toolTip"_s);
        createFakeProperty(u"whatsThis"_s);
        createFakeProperty(u"acceptDrops"_s);
        createFakeProperty(u"dragEnabled"_s);
        // windowModality/Opacity is visible only for the main container, in which case the form windows enables it on loading
        setVisible(createFakeProperty(u"windowModality"_s), false);
        setVisible(createFakeProperty(u"windowOpacity"_s, double(1.0)), false);
        if (qobject_cast<const QToolBar *>(d->m_object)) { // prevent toolbars from being dragged off
            createFakeProperty(u"floatable"_s, QVariant(true));
        } else {
            if (qobject_cast<const QMenuBar *>(d->m_object)) {
                // Keep the menu bar editable in the form even if a native menu bar is used.
                const bool nativeMenuBarDefault =
                    !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar);
                createFakeProperty(u"nativeMenuBar"_s, QVariant(nativeMenuBarDefault));
            }
        }
        if (d->m_canHaveLayoutAttributes) {
            const QString layoutGroup = u"Layout"_s;
            static constexpr QLatin1StringView fakeLayoutProperties[] = {
                layoutObjectNameC, layoutLeftMarginC, layoutTopMarginC, layoutRightMarginC, layoutBottomMarginC, layoutSpacingC, layoutHorizontalSpacingC, layoutVerticalSpacingC,
                layoutFieldGrowthPolicyC, layoutRowWrapPolicyC, layoutLabelAlignmentC, layoutFormAlignmentC,
                layoutboxStretchPropertyC, layoutGridRowStretchPropertyC, layoutGridColumnStretchPropertyC,
                layoutGridRowMinimumHeightC, layoutGridColumnMinimumWidthC,
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
                layoutSizeConstraintC
#else
                layoutHorizontalSizeConstraintC, layoutVerticalSizeConstraintC
#endif
            };
            static constexpr int fakeLayoutPropertyCount = sizeof(fakeLayoutProperties)/sizeof(fakeLayoutProperties[0]);
            const int size = count();
            for (int i = 0; i < fakeLayoutPropertyCount; i++) {
                createFakeProperty(fakeLayoutProperties[i], 0);
                setAttribute(size  + i, true);
                setPropertyGroup(size  + i, layoutGroup);
            }
        }

        if (d->m_objectType == ObjectLabel)
            createFakeProperty(u"buddy"_s, QVariant(QByteArray()));
        /* We need to create a fake property since the property does not work
         * for non-toplevel windows or on other systems than Mac and only if
         * it is above a certain Mac OS version. */
        if (qobject_cast<const QMainWindow *>(d->m_object))
            createFakeProperty(u"unifiedTitleAndToolBarOnMac"_s, false);
    }

    if (qobject_cast<const QDialog*>(object)) {
        createFakeProperty(u"modal"_s);
    }
    if (qobject_cast<const QDockWidget*>(object)) {
        createFakeProperty(u"floating"_s);
    }

    const QByteArrayList names = object->dynamicPropertyNames();
    for (const auto &nameB : names) {
        const QString name = QString::fromLatin1(nameB);
        const int idx = addDynamicProperty(name, object->property(nameB.constData()));
        if (idx != -1)
            d->ensureInfo(idx).kind = QDesignerPropertySheetPrivate::DefaultDynamicProperty;
    }
}

QDesignerPropertySheet::~QDesignerPropertySheet()
{
    delete d;
}

QObject *QDesignerPropertySheet::object() const
{
    return d->m_object;
}

bool QDesignerPropertySheet::dynamicPropertiesAllowed() const
{
    return true;
}

bool QDesignerPropertySheet::canAddDynamicProperty(const QString &propName) const
{
    // used internally
    if (propName == "database"_L1 || propName == "buttonGroupId"_L1)
        return false;
    const int index = d->m_meta->indexOfProperty(propName);
    if (index != -1)
        return false; // property already exists and is not a dynamic one
    if (d->m_addIndex.contains(propName)) {
        const int idx = d->m_addIndex.value(propName);
        return !isVisible(idx); // dynamic property already exists
    }
    return QDesignerPropertySheet::internalDynamicPropertiesEnabled()
        || !propName.startsWith("_q_"_L1);
}

int QDesignerPropertySheet::addDynamicProperty(const QString &propName, const QVariant &value)
{
    using Info = QDesignerPropertySheetPrivate::Info;
    if (!value.isValid())
        return -1; // property has invalid type
    if (!canAddDynamicProperty(propName))
        return -1;

    QVariant v = value;
    switch (value.metaType().id()) {
    case QMetaType::QIcon:
        v = QVariant::fromValue(qdesigner_internal::PropertySheetIconValue());
        break;
    case QMetaType::QPixmap:
        v = QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue());
        break;
    case QMetaType::QString:
        v = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue(value.toString()));
        break;
    case QMetaType::QStringList:
        v = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue(value.toStringList()));
        break;
    case QMetaType::QKeySequence: {
        const QKeySequence keySequence = qvariant_cast<QKeySequence>(value);
        v = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue(keySequence));
    }
        break;
    }

    if (d->m_addIndex.contains(propName)) {
        const int idx = d->m_addIndex.value(propName);
        // have to be invisible, this was checked in canAddDynamicProperty() method
        setVisible(idx, true);
        d->m_addProperties.insert(idx, v);
        setChanged(idx, false);
        const int index = d->m_meta->indexOfProperty(propName);
        Info &info = d->ensureInfo(index);
        info.defaultValue = value;
        info.kind = QDesignerPropertySheetPrivate::DynamicProperty;
        switch (value.metaType().id()) {
        case QMetaType::QIcon:
        case QMetaType::QPixmap:
            d->addResourceProperty(idx, value.metaType().id());
            break;
        case QMetaType::QString:
            d->addStringProperty(idx);
            break;
        case QMetaType::QKeySequence:
            d->addKeySequenceProperty(idx);
            break;
        }
        return idx;
    }

    const int index = count();
    d->m_addIndex.insert(propName, index);
    d->m_addProperties.insert(index, v);
    Info &info = d->ensureInfo(index);
    info.visible = true;
    info.changed = false;
    info.defaultValue = value;
    info.kind = QDesignerPropertySheetPrivate::DynamicProperty;
    setPropertyGroup(index, tr("Dynamic Properties"));
    switch (value.metaType().id()) {
    case QMetaType::QIcon:
    case QMetaType::QPixmap:
        d->addResourceProperty(index, value.metaType().id());
        break;
    case QMetaType::QString:
        d->addStringProperty(index);
        break;
    case QMetaType::QStringList:
        d->addStringListProperty(index);
        break;
    case QMetaType::QKeySequence:
        d->addKeySequenceProperty(index);
        break;
    default:
        break;
    }
    return index;
}

bool QDesignerPropertySheet::removeDynamicProperty(int index)
{
    if (!d->m_addIndex.contains(propertyName(index)))
        return false;

    setVisible(index, false);
    return true;
}

bool QDesignerPropertySheet::isDynamic(int index) const
{
    if (!d->m_addProperties.contains(index))
        return false;

    switch (propertyType(index)) {
    case PropertyBuddy:
        if (d->m_objectType == ObjectLabel)
            return false;
        break;
    case PropertyLayoutLeftMargin:
    case PropertyLayoutTopMargin:
    case PropertyLayoutRightMargin:
    case PropertyLayoutBottomMargin:
    case PropertyLayoutSpacing:
    case PropertyLayoutHorizontalSpacing:
    case PropertyLayoutVerticalSpacing:
    case PropertyLayoutObjectName:
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
    case PropertyLayoutSizeConstraint:
#else
    case PropertyLayoutHorizontalSizeConstraint:
    case PropertyLayoutVerticalSizeConstraint:
#endif
    case PropertyLayoutFieldGrowthPolicy:
    case PropertyLayoutRowWrapPolicy:
    case PropertyLayoutLabelAlignment:
    case PropertyLayoutFormAlignment:
    case PropertyLayoutBoxStretch:
    case PropertyLayoutGridRowStretch:
    case PropertyLayoutGridColumnStretch:
    case PropertyLayoutGridRowMinimumHeight:
    case PropertyLayoutGridColumnMinimumWidth:
        if (d->m_object->isWidgetType() && d->m_canHaveLayoutAttributes)
            return false;
        break;
    default:
        break;
    }
    return true;
}

bool QDesignerPropertySheet::isDynamicProperty(int index) const
{
    // Do not complain here, as an invalid index might be encountered
    // if someone implements a property sheet only, omitting the dynamic sheet.
    if (index < 0 || index >= count())
        return false;
    return d->m_info.value(index).kind == QDesignerPropertySheetPrivate::DynamicProperty;
}

bool QDesignerPropertySheet::isDefaultDynamicProperty(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    return d->m_info.value(index).kind == QDesignerPropertySheetPrivate::DefaultDynamicProperty;
}

bool QDesignerPropertySheet::isResourceProperty(int index) const
{
    return d->isResourceProperty(index);
}

QVariant QDesignerPropertySheet::defaultResourceProperty(int index) const
{
    return d->defaultResourceProperty(index);
}

qdesigner_internal::DesignerPixmapCache *QDesignerPropertySheet::pixmapCache() const
{
    return d->m_pixmapCache;
}

void QDesignerPropertySheet::setPixmapCache(qdesigner_internal::DesignerPixmapCache *cache)
{
    d->m_pixmapCache = cache;
}

qdesigner_internal::DesignerIconCache *QDesignerPropertySheet::iconCache() const
{
    return d->m_iconCache;
}

void QDesignerPropertySheet::setIconCache(qdesigner_internal::DesignerIconCache *cache)
{
    d->m_iconCache = cache;
}

int QDesignerPropertySheet::createFakeProperty(const QString &propertyName, const QVariant &value)
{
    using Info = QDesignerPropertySheetPrivate::Info;
    // fake properties
    const int index = d->m_meta->indexOfProperty(propertyName);
    if (index != -1) {
        if (!(d->m_meta->property(index)->attributes() & QDesignerMetaPropertyInterface::DesignableAttribute))
            return -1;
        Info &info = d->ensureInfo(index);
        info.visible = false;
        info.kind = QDesignerPropertySheetPrivate::FakeProperty;
        QVariant v = value.isValid() ? value : metaProperty(index);
        switch (v.metaType().id()) {
        case QMetaType::QString:
            v = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue());
            break;
        case QMetaType::QStringList:
            v = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue());
            break;
        case QMetaType::QKeySequence:
            v = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue());
            break;
        }
        d->m_fakeProperties.insert(index, v);
        return index;
    }
    if (!value.isValid())
        return -1;

    const int newIndex = count();
    d->m_addIndex.insert(propertyName, newIndex);
    d->m_addProperties.insert(newIndex, value);
    Info &info = d->ensureInfo(newIndex);
    info.propertyType = propertyTypeFromName(propertyName);
    info.kind = QDesignerPropertySheetPrivate::FakeProperty;
    return newIndex;
}

bool QDesignerPropertySheet::isAdditionalProperty(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    return d->m_addProperties.contains(index);
}

bool QDesignerPropertySheet::isFakeProperty(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    // additional properties must be fake
    return (d->m_fakeProperties.contains(index) || isAdditionalProperty(index));
}

int QDesignerPropertySheet::count() const
{
    return d->count();
}

int QDesignerPropertySheet::indexOf(const QString &name) const
{
    int index = d->m_meta->indexOfProperty(name);

    if (index == -1)
        index = d->m_addIndex.value(name, -1);

    return index;
}

QDesignerPropertySheet::PropertyType QDesignerPropertySheet::propertyType(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return PropertyNone;
    return d->propertyType(index);
}

QDesignerPropertySheet::ObjectType QDesignerPropertySheet::objectType() const
{
    return d->m_objectType;
}

QString QDesignerPropertySheet::propertyName(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return QString();
    if (isAdditionalProperty(index))
        return d->m_addIndex.key(index);

    return d->m_meta->property(index)->name();
}

QString QDesignerPropertySheet::propertyGroup(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return QString();
    const QString g = d->m_info.value(index).group;

    if (!g.isEmpty())
        return g;

    if (propertyType(index) == PropertyAccessibility)
        return u"Accessibility"_s;

    if (isAdditionalProperty(index))
        return d->m_meta->className();

    return g;
}

void QDesignerPropertySheet::setPropertyGroup(int index, const QString &group)
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return;
    d->ensureInfo(index).group = group;
}

QVariant QDesignerPropertySheet::property(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return QVariant();
    if (isAdditionalProperty(index)) {
        if (isFakeLayoutProperty(index)) {
            QDesignerPropertySheetExtension *layoutPropertySheet;
            if (d->layout(&layoutPropertySheet) && layoutPropertySheet) {
                const QString newPropName = d->transformLayoutPropertyName(index);
                if (!newPropName.isEmpty()) {
                    const int newIndex = layoutPropertySheet->indexOf(newPropName);
                    if (newIndex != -1)
                        return layoutPropertySheet->property(newIndex);
                    return QVariant();
                }
            }
        }
        return d->m_addProperties.value(index);
    }

    if (isFakeProperty(index)) {
        return d->m_fakeProperties.value(index);
    }

    if (d->isResourceProperty(index))
        return d->resourceProperty(index);

    if (d->isStringProperty(index)) {
        QString strValue = metaProperty(index).toString();
        qdesigner_internal::PropertySheetStringValue value = d->stringProperty(index);
        if (strValue != value.value()) {
            value.setValue(strValue);
            d->setStringProperty(index, value); // cache it
        }
        return QVariant::fromValue(value);
    }

    if (d->isStringListProperty(index)) {
        const QStringList listValue = metaProperty(index).toStringList();
        qdesigner_internal::PropertySheetStringListValue value = d->stringListProperty(index);
        if (listValue != value.value()) {
            value.setValue(listValue);
            d->setStringListProperty(index, value); // cache it
        }
        return QVariant::fromValue(value);
    }

    if (d->isKeySequenceProperty(index)) {
        QKeySequence keyValue = qvariant_cast<QKeySequence>(metaProperty(index));
        qdesigner_internal::PropertySheetKeySequenceValue value = d->keySequenceProperty(index);
        if (keyValue != value.value()) {
            value.setValue(keyValue);
            d->setKeySequenceProperty(index, value); // cache it
        }
        return QVariant::fromValue(value);
    }

    QVariant result =  metaProperty(index);
    // QTBUG-49591: "visible" is only exposed for QHeaderView as a fake
    // property ("headerVisible") for the item view. If the item view is not
    // visible (on a page based container), check the WA_WState_Hidden instead,
    // since otherwise false is returned when saving.
    if (result.typeId() == QMetaType::Bool && !result.toBool()
        && d->m_object->isWidgetType()
        && propertyType(index) == PropertyVisible) {
        if (auto *hv = qobject_cast<QHeaderView *>(d->m_object)) {
            if (auto *parent = hv->parentWidget())  {
                if (!parent->isVisible())
                    result = QVariant(!hv->testAttribute(Qt::WA_WState_Hidden));
            }
        }
    }
    return result;
}

QVariant QDesignerPropertySheet::metaProperty(int index) const
{
    Q_ASSERT(!isFakeProperty(index));

    const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
    QVariant v = p->read(d->m_object);
    switch (p->kind()) {
    case QDesignerMetaPropertyInterface::FlagKind: {
        qdesigner_internal::PropertySheetFlagValue psflags = qdesigner_internal::PropertySheetFlagValue(v.toInt(), designerMetaFlagsFor(p->enumerator()));
        v.setValue(psflags);
    }
        break;
    case QDesignerMetaPropertyInterface::EnumKind: {
        qdesigner_internal::PropertySheetEnumValue pse = qdesigner_internal::PropertySheetEnumValue(v.toInt(), designerMetaEnumFor(p->enumerator()));
        v.setValue(pse);
    }
        break;
    case QDesignerMetaPropertyInterface::OtherKind:
        break;
    }
    return v;
}

QVariant QDesignerPropertySheet::resolvePropertyValue(int index, const QVariant &value) const
{
    if (value.canConvert<qdesigner_internal::PropertySheetEnumValue>())
        return qvariant_cast<qdesigner_internal::PropertySheetEnumValue>(value).value;

    if (value.canConvert<qdesigner_internal::PropertySheetFlagValue>())
        return qvariant_cast<qdesigner_internal::PropertySheetFlagValue>(value).value;

    if (value.canConvert<qdesigner_internal::PropertySheetStringValue>())
        return qvariant_cast<qdesigner_internal::PropertySheetStringValue>(value).value();

    if (value.canConvert<qdesigner_internal::PropertySheetStringListValue>())
        return qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(value).value();

    if (value.canConvert<qdesigner_internal::PropertySheetKeySequenceValue>())
        return QVariant::fromValue(qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(value).value());

    if (value.canConvert<qdesigner_internal::PropertySheetPixmapValue>()) {
        const QString path = qvariant_cast<qdesigner_internal::PropertySheetPixmapValue>(value).path();
        if (path.isEmpty())
            return defaultResourceProperty(index);
        if (d->m_pixmapCache) {
            return d->m_pixmapCache->pixmap(qvariant_cast<qdesigner_internal::PropertySheetPixmapValue>(value));
        }
    }

    if (value.canConvert<qdesigner_internal::PropertySheetIconValue>()) {
        const unsigned mask = qvariant_cast<qdesigner_internal::PropertySheetIconValue>(value).mask();
        if (mask == 0)
            return defaultResourceProperty(index);
        if (d->m_iconCache)
            return d->m_iconCache->icon(qvariant_cast<qdesigner_internal::PropertySheetIconValue>(value));
    }

    return value;
}

void QDesignerPropertySheet::setFakeProperty(int index, const QVariant &value)
{
    Q_ASSERT(isFakeProperty(index));

    QVariant &v = d->m_fakeProperties[index];

    // set resource properties also (if we are going to have fake resource properties)
    if (value.canConvert<qdesigner_internal::PropertySheetFlagValue>() || value.canConvert<qdesigner_internal::PropertySheetEnumValue>()) {
        v = value;
    } else if (v.canConvert<qdesigner_internal::PropertySheetFlagValue>()) {
        qdesigner_internal::PropertySheetFlagValue f = qvariant_cast<qdesigner_internal::PropertySheetFlagValue>(v);
        f.value = value.toInt();
        v.setValue(f);
        Q_ASSERT(value.metaType().id() == QMetaType::Int);
    } else if (v.canConvert<qdesigner_internal::PropertySheetEnumValue>()) {
        qdesigner_internal::PropertySheetEnumValue e = qvariant_cast<qdesigner_internal::PropertySheetEnumValue>(v);
        e.value = value.toInt();
        v.setValue(e);
        Q_ASSERT(value.metaType().id() == QMetaType::Int);
    } else {
        v = value;
    }
}

void QDesignerPropertySheet::clearFakeProperties()
{
    d->m_fakeProperties.clear();
}

// Buddy needs to be byte array, else uic won't work
static QVariant toByteArray(const QVariant &value) {
    if (value.metaType().id() == QMetaType::QByteArray)
        return value;
    const QByteArray ba = value.toString().toUtf8();
    return QVariant(ba);
}

void QDesignerPropertySheet::setProperty(int index, const QVariant &value)
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return;
    if (isAdditionalProperty(index)) {
        if (d->m_objectType == ObjectLabel && propertyType(index) == PropertyBuddy) {
            QFormBuilderExtra::applyBuddy(value.toString(), QFormBuilderExtra::BuddyApplyVisibleOnly, qobject_cast<QLabel *>(d->m_object));
            d->m_addProperties[index] = toByteArray(value);
            return;
        }

        if (isFakeLayoutProperty(index)) {
            QDesignerPropertySheetExtension *layoutPropertySheet;
            if (d->layout(&layoutPropertySheet) && layoutPropertySheet) {
                const QString newPropName = d->transformLayoutPropertyName(index);
                if (!newPropName.isEmpty()) {
                    const int newIndex = layoutPropertySheet->indexOf(newPropName);
                    if (newIndex != -1)
                        layoutPropertySheet->setProperty(newIndex, value);
                }
            }
        }

        if (isDynamicProperty(index) || isDefaultDynamicProperty(index)) {
            if (d->isResourceProperty(index))
                d->setResourceProperty(index, value);
            if (d->isStringProperty(index))
                d->setStringProperty(index, qvariant_cast<qdesigner_internal::PropertySheetStringValue>(value));
            if (d->isStringListProperty(index))
                d->setStringListProperty(index, qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(value));
            if (d->isKeySequenceProperty(index))
                d->setKeySequenceProperty(index, qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(value));
            d->m_object->setProperty(propertyName(index).toUtf8(), resolvePropertyValue(index, value));
            if (d->m_object->isWidgetType()) {
                QWidget *w = qobject_cast<QWidget *>(d->m_object);
                w->setStyleSheet(w->styleSheet());
            }
        }
        d->m_addProperties[index] = value;
    } else if (isFakeProperty(index)) {
        setFakeProperty(index, value);
    } else {
        if (d->isResourceProperty(index))
            d->setResourceProperty(index, value);
        if (d->isStringProperty(index))
            d->setStringProperty(index, qvariant_cast<qdesigner_internal::PropertySheetStringValue>(value));
        if (d->isStringListProperty(index))
            d->setStringListProperty(index, qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(value));
        if (d->isKeySequenceProperty(index))
            d->setKeySequenceProperty(index, qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(value));
        const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
        p->write(d->m_object, resolvePropertyValue(index, value));
        if (qobject_cast<QGroupBox *>(d->m_object) && propertyType(index) == PropertyCheckable) {
            const int idx = indexOf(u"focusPolicy"_s);
            if (!isChanged(idx)) {
                qdesigner_internal::PropertySheetEnumValue e = qvariant_cast<qdesigner_internal::PropertySheetEnumValue>(property(idx));
                if (value.toBool()) {
                    const QDesignerMetaPropertyInterface *p = d->m_meta->property(idx);
                    p->write(d->m_object, Qt::NoFocus);
                    e.value = Qt::StrongFocus;
                    QVariant v;
                    v.setValue(e);
                    setFakeProperty(idx, v);
                } else {
                    e.value = Qt::NoFocus;
                    QVariant v;
                    v.setValue(e);
                    setFakeProperty(idx, v);
                }
            }
        }
    }
}

bool QDesignerPropertySheet::hasReset(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    if (isAdditionalProperty(index))
        return d->m_info.value(index).reset;
    return true;
}

bool QDesignerPropertySheet::reset(int index)
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    if (d->isStringProperty(index)) {
        qdesigner_internal::PropertySheetStringValue value;
        // Main container: Reset to stored class name as not to change the file names generated by uic.
        if (propertyName(index) == "objectName"_L1) {
            const QVariant classNameDefaultV = d->m_object->property("_q_classname");
            if (classNameDefaultV.isValid())
                value.setValue(classNameDefaultV.toString());
        } else if (!isAdditionalProperty(index)) {
            const QDesignerMetaPropertyInterface *property = d->m_meta->property(index);
            if ((property->accessFlags() & QDesignerMetaPropertyInterface::ResetAccess) && property->reset(d->m_object))
                value.setValue(property->read(d->m_object).toString());
            else
                return false;
        }
        setProperty(index, QVariant::fromValue(value));
        return true;
    }
    if (d->isStringListProperty(index))
        setProperty(index, QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue()));
    if (d->isKeySequenceProperty(index))
        setProperty(index, QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue()));
    if (d->isResourceProperty(index)) {
        setProperty(index, d->emptyResourceProperty(index));
        return true;
    }
    if (isDynamic(index)) {
        const QString propName = propertyName(index);
        const QVariant oldValue = d->m_addProperties.value(index);
        const QVariant defaultValue = d->m_info.value(index).defaultValue;
        QVariant newValue = defaultValue;
        if (d->isStringProperty(index)) {
            newValue = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue(newValue.toString()));
        } else if (d->isStringListProperty(index)) {
            newValue = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue(newValue.toStringList()));
        } else if (d->isKeySequenceProperty(index)) {
            const QKeySequence keySequence = qvariant_cast<QKeySequence>(newValue);
            newValue = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue(keySequence));
        }
        if (oldValue == newValue)
            return true;
        d->m_object->setProperty(propName.toUtf8(), defaultValue);
        d->m_addProperties[index] = newValue;
        return true;
    } else if (!d->m_info.value(index).defaultValue.isNull()) {
        setProperty(index, d->m_info.value(index).defaultValue);
        return true;
    }
    if (isAdditionalProperty(index)) {
        const PropertyType pType = propertyType(index);
        if (d->m_objectType == ObjectLabel && pType == PropertyBuddy) {
            setProperty(index, QVariant(QByteArray()));
            return true;
        }
        if (isFakeLayoutProperty(index)) {
           // special properties
           switch (pType) {
           case PropertyLayoutObjectName:
              setProperty(index, QString());
              return true;
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
           case PropertyLayoutSizeConstraint:
#else
           case PropertyLayoutHorizontalSizeConstraint:
           case PropertyLayoutVerticalSizeConstraint:
#endif
              setProperty(index, QVariant(QLayout::SetDefaultConstraint));
              return true;
           case PropertyLayoutBoxStretch:
           case PropertyLayoutGridRowStretch:
           case PropertyLayoutGridColumnStretch:
           case PropertyLayoutGridRowMinimumHeight:
           case PropertyLayoutGridColumnMinimumWidth:
           case PropertyLayoutFieldGrowthPolicy:
           case PropertyLayoutRowWrapPolicy:
           case PropertyLayoutLabelAlignment:
           case PropertyLayoutFormAlignment: {
               QDesignerPropertySheetExtension *layoutPropertySheet;
               if (d->layout(&layoutPropertySheet) && layoutPropertySheet)
                   return layoutPropertySheet->reset(layoutPropertySheet->indexOf(d->transformLayoutPropertyName(index)));
           }
               break;
           default:
              break;
           }
           // special margins
            int value = -1;
            switch (d->m_objectType) {
            case ObjectLayoutWidget:
                if (pType == PropertyLayoutLeftMargin ||
                        pType == PropertyLayoutTopMargin ||
                        pType == PropertyLayoutRightMargin ||
                        pType == PropertyLayoutBottomMargin)
                    value = 0;
                break;
            default:
                break;
            }
            setProperty(index, value);
            return true;
        }
        return false;
    }
    if (isFakeProperty(index)) {
        const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
        const bool result = p->reset(d->m_object);
        d->m_fakeProperties[index] = p->read(d->m_object);
        return result;
    }
    if (propertyType(index) == PropertyGeometry && d->m_object->isWidgetType()) {
        if (QWidget *w = qobject_cast<QWidget*>(d->m_object)) {
            QWidget *widget = w;
            if (qdesigner_internal::Utils::isCentralWidget(d->m_fwb, widget) && d->m_fwb->parentWidget())
                widget = d->m_fwb->parentWidget();

            if (widget != w && widget->parentWidget()) {
                QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
                widget->parentWidget()->adjustSize();
            }
            QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
            widget->adjustSize();
            return true;
        }
    }
    // ### TODO: reset for fake properties.

    const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
    return p->reset(d->m_object);
}

bool QDesignerPropertySheet::isChanged(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    if (isAdditionalProperty(index)) {
        if (isFakeLayoutProperty(index)) {
            QDesignerPropertySheetExtension *layoutPropertySheet;
            if (d->layout(&layoutPropertySheet) && layoutPropertySheet) {
                const QString newPropName = d->transformLayoutPropertyName(index);
                if (!newPropName.isEmpty()) {
                    const int newIndex = layoutPropertySheet->indexOf(newPropName);
                    if (newIndex != -1)
                        return layoutPropertySheet->isChanged(newIndex);
                    return false;
                }
            }
        }
    }
    return d->m_info.value(index).changed;
}

void QDesignerPropertySheet::setChanged(int index, bool changed)
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return;
    if (isAdditionalProperty(index)) {
        if (isFakeLayoutProperty(index)) {
            QDesignerPropertySheetExtension *layoutPropertySheet;
            if (d->layout(&layoutPropertySheet) && layoutPropertySheet) {
                const QString newPropName = d->transformLayoutPropertyName(index);
                if (!newPropName.isEmpty()) {
                    const int newIndex = layoutPropertySheet->indexOf(newPropName);
                    if (newIndex != -1)
                        layoutPropertySheet->setChanged(newIndex, changed);
                }
            }
        }
    }
    if (d->isReloadableProperty(index)) {
        if (d->m_fwb) {
            if (changed)
                d->m_fwb->addReloadableProperty(this, index);
            else
                d->m_fwb->removeReloadableProperty(this, index);
        }
    }
    d->ensureInfo(index).changed = changed;
}

bool QDesignerPropertySheet::isFakeLayoutProperty(int index) const
{
    if (!isAdditionalProperty(index))
        return false;

    switch (propertyType(index)) {
    case PropertyLayoutObjectName:
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
    case PropertyLayoutSizeConstraint:
#else
    case PropertyLayoutHorizontalSizeConstraint:
    case PropertyLayoutVerticalSizeConstraint:
#endif
        return true;
    case PropertyLayoutLeftMargin:
    case PropertyLayoutTopMargin:
    case PropertyLayoutRightMargin:
    case PropertyLayoutBottomMargin:
    case PropertyLayoutSpacing:
    case PropertyLayoutHorizontalSpacing:
    case PropertyLayoutVerticalSpacing:
    case PropertyLayoutFieldGrowthPolicy:
    case PropertyLayoutRowWrapPolicy:
    case PropertyLayoutLabelAlignment:
    case PropertyLayoutFormAlignment:
    case PropertyLayoutBoxStretch:
    case PropertyLayoutGridRowStretch:
    case PropertyLayoutGridColumnStretch:
    case PropertyLayoutGridRowMinimumHeight:
    case PropertyLayoutGridColumnMinimumWidth:
        return d->m_canHaveLayoutAttributes;
    default:
        break;
    }
    return false;
}

// Visible vs. Enabled: In Qt 5, it was possible to define a boolean function
// for the DESIGNABLE attribute of Q_PROPERTY. Qt Widgets Designer would use that to
// determine isEnabled() for the property and return isVisible() = false
// for properties that specified 'false' for DESIGNABLE.
// This was used for example for the "checked" property of QAbstractButton,
// QGroupBox and QAction, where "checkable" would determine isEnabled().
// This is now implemented by querying the property directly.

bool QDesignerPropertySheet::isVisible(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;

    const PropertyType type = propertyType(index);
    if (isAdditionalProperty(index)) {
        if (isFakeLayoutProperty(index) && d->m_object->isWidgetType()) {
            const QLayout *currentLayout = d->layout();
            if (!currentLayout)
                return false;
            const int visibleMask = qdesigner_internal::LayoutProperties::visibleProperties(currentLayout);
            switch (type) {
            case  PropertyLayoutSpacing:
                return visibleMask & qdesigner_internal::LayoutProperties::SpacingProperty;
            case PropertyLayoutHorizontalSpacing:
            case PropertyLayoutVerticalSpacing:
                return visibleMask & qdesigner_internal::LayoutProperties::HorizSpacingProperty;
            case PropertyLayoutFieldGrowthPolicy:
                return visibleMask & qdesigner_internal::LayoutProperties::FieldGrowthPolicyProperty;
            case PropertyLayoutRowWrapPolicy:
                return visibleMask & qdesigner_internal::LayoutProperties::RowWrapPolicyProperty;
            case PropertyLayoutLabelAlignment:
                return visibleMask & qdesigner_internal::LayoutProperties::LabelAlignmentProperty;
            case PropertyLayoutFormAlignment:
                return visibleMask & qdesigner_internal::LayoutProperties::FormAlignmentProperty;
            case PropertyLayoutBoxStretch:
                return visibleMask & qdesigner_internal::LayoutProperties::BoxStretchProperty;
            case PropertyLayoutGridRowStretch:
                return visibleMask & qdesigner_internal::LayoutProperties::GridRowStretchProperty;
            case PropertyLayoutGridColumnStretch:
                return visibleMask & qdesigner_internal::LayoutProperties::GridColumnStretchProperty;
            case PropertyLayoutGridRowMinimumHeight:
                return visibleMask & qdesigner_internal::LayoutProperties::GridRowMinimumHeightProperty;
            case PropertyLayoutGridColumnMinimumWidth:
                return visibleMask & qdesigner_internal::LayoutProperties::GridColumnMinimumWidthProperty;
            default:
                break;
            }
            return true;
        }
        return d->m_info.value(index).visible;
    }

    if (isFakeProperty(index)) {
        switch (type) {
        case PropertyWindowModality: // Hidden for child widgets
        case PropertyWindowOpacity:
            return d->m_info.value(index).visible;
        default:
            break;
        }
        return true;
    }

    const bool visible = d->m_info.value(index).visible;
    switch (type) {
    case PropertyWindowTitle:
    case PropertyWindowIcon:
    case PropertyWindowFilePath:
    case PropertyWindowOpacity:
    case PropertyWindowIconText:
    case PropertyWindowModified:
        return visible;
    default:
        if (visible)
            return true;
        break;
    }

    const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
    if  (!(p->accessFlags() & QDesignerMetaPropertyInterface::WriteAccess))
         return false;

    return p->attributes().testFlag(QDesignerMetaPropertyInterface::DesignableAttribute);
}

void QDesignerPropertySheet::setVisible(int index, bool visible)
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return;
    d->ensureInfo(index).visible = visible;
}

bool QDesignerPropertySheet::isEnabled(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    if (isAdditionalProperty(index))
        return true;

    if (isFakeProperty(index))
        return true;

    // Grey out geometry of laid-out widgets (including splitter)
    if (propertyType(index) == PropertyGeometry && d->m_object->isWidgetType()) {
        bool isManaged;
        const qdesigner_internal::LayoutInfo::Type lt = qdesigner_internal::LayoutInfo::laidoutWidgetType(d->m_core, qobject_cast<QWidget *>(d->m_object), &isManaged);
        return !isManaged || lt == qdesigner_internal::LayoutInfo::NoLayout;
    }

    if (d->m_info.value(index).visible)
        return true;

    // Enable setting of properties for statically non-designable properties
    // as this might be done via TaskMenu/Cursor::setProperty. Note that those
    // properties are not visible.
    const QDesignerMetaPropertyInterface *p = d->m_meta->property(index);
    if (!p->accessFlags().testFlag(QDesignerMetaPropertyInterface::WriteAccess))
        return false;

    if (!p->attributes().testFlag(QDesignerMetaPropertyInterface::DesignableAttribute))
        return false;

    const PropertyType type = propertyType(index);
    if (type == PropertyChecked && d->m_objectFlags.testFlag(CheckableProperty))
        return d->m_object->property("checkable").toBool();
    return true;
}

bool QDesignerPropertySheet::isAttribute(int index) const
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return false;
    if (isAdditionalProperty(index))
        return d->m_info.value(index).attribute;

    if (isFakeProperty(index))
        return false;

    return d->m_info.value(index).attribute;
}

void QDesignerPropertySheet::setAttribute(int index, bool attribute)
{
    if (d->invalidIndex(Q_FUNC_INFO, index))
        return;
    d->ensureInfo(index).attribute = attribute;
}

QDesignerFormEditorInterface *QDesignerPropertySheet::core() const
{
    return d->m_core;
}

bool QDesignerPropertySheet::internalDynamicPropertiesEnabled()
{
    return QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled;
}

void QDesignerPropertySheet::setInternalDynamicPropertiesEnabled(bool v)
{
    QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled = v;
}

// Find the form editor in the hierarchy.
// We know that the parent of the sheet is the extension manager
// whose parent is the core.
QDesignerFormEditorInterface *QDesignerPropertySheet::formEditorForObject(QObject *o)
{
    do {
        if (auto *core = qobject_cast<QDesignerFormEditorInterface*>(o))
            return core;
        o = o->parent();
    } while (o);
    Q_ASSERT(o);
    return nullptr;
}

// ---------- QDesignerAbstractPropertySheetFactory

struct QDesignerAbstractPropertySheetFactory::PropertySheetFactoryPrivate {
    PropertySheetFactoryPrivate();
    const QString m_propertySheetId;
    const QString m_dynamicPropertySheetId;

    QHash<QObject *, QObject *> m_extensions;
};

QDesignerAbstractPropertySheetFactory::PropertySheetFactoryPrivate::PropertySheetFactoryPrivate() :
    m_propertySheetId(Q_TYPEID(QDesignerPropertySheetExtension)),
    m_dynamicPropertySheetId(Q_TYPEID(QDesignerDynamicPropertySheetExtension))
{
}

// ---------- QDesignerAbstractPropertySheetFactory


QDesignerAbstractPropertySheetFactory::QDesignerAbstractPropertySheetFactory(QExtensionManager *parent) :
    QExtensionFactory(parent),
    m_impl(new PropertySheetFactoryPrivate)
{
}

QDesignerAbstractPropertySheetFactory::~QDesignerAbstractPropertySheetFactory()
{
    delete m_impl;
}

QObject *QDesignerAbstractPropertySheetFactory::extension(QObject *object, const QString &iid) const
{
    if (!object)
        return nullptr;

    if (iid != m_impl->m_propertySheetId && iid != m_impl->m_dynamicPropertySheetId)
        return nullptr;

    QObject *ext = m_impl->m_extensions.value(object, 0);
    if (!ext && (ext = createPropertySheet(object, const_cast<QDesignerAbstractPropertySheetFactory*>(this)))) {
        connect(ext, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed);
        connect(object, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed);
        m_impl->m_extensions.insert(object, ext);
    }

    return ext;
}

void QDesignerAbstractPropertySheetFactory::objectDestroyed(QObject *object)
{
    for (auto it = m_impl->m_extensions.begin(), end = m_impl->m_extensions.end(); it != end; /*erasing*/) {
        if (it.key() == object || it.value() == object) {
            if (it.key() == object) {
                QObject *ext = it.value();
                disconnect(ext, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed);
                delete ext;
            }
            it = m_impl->m_extensions.erase(it);
        } else {
            ++it;
        }
    }
}

QT_END_NAMESPACE
