// 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_propertycommand_p.h"
#include "qdesigner_utils_p.h"
#include "dynamicpropertysheet.h"
#include "qdesigner_propertyeditor_p.h"
#include "spacer_widget_p.h"
#include "qdesigner_propertysheet_p.h"

#include <QtDesigner/abstractformeditor.h>
#include <QtDesigner/abstractintegration.h>
#include <QtDesigner/abstractformwindow.h>
#include <QtDesigner/abstractformwindowcursor.h>
#include <QtDesigner/dynamicpropertysheet.h>
#include <QtDesigner/propertysheet.h>
#include <QtDesigner/abstractpropertyeditor.h>
#include <QtDesigner/abstractobjectinspector.h>
#include <QtDesigner/abstractintegration.h>
#include <QtDesigner/abstractwidgetdatabase.h>
#include <QtDesigner/qextensionmanager.h>

#include <QtWidgets/qwidget.h>
#include <QtWidgets/qapplication.h>
#include <QtWidgets/qdialog.h>
#include <QtWidgets/qpushbutton.h>
#include <QtWidgets/qlayout.h>

#include <QtGui/qaction.h>

#include <QtCore/qdebug.h>
#include <QtCore/qsize.h>
#include <QtCore/qtextstream.h>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

namespace  {
enum { debugPropertyCommands = 0 };

const unsigned QFontFamiliesResolved = (QFont::FamilyResolved | QFont::FamiliesResolved);

// Debug resolve mask of font
QString fontMask(unsigned m)
{
    QString rc;
    if (m & QFontFamiliesResolved)
        rc += "Family"_L1;
    if (m & QFont::SizeResolved)
        rc += "Size "_L1;
    if (m & QFont::WeightResolved)
        rc += "Bold "_L1;
    if (m & QFont::StyleResolved)
        rc += "Style "_L1;
    if (m & QFont::UnderlineResolved)
        rc += "Underline "_L1;
    if (m & QFont::StrikeOutResolved)
        rc += "StrikeOut "_L1;
    if (m & QFont::KerningResolved)
        rc += "Kerning "_L1;
    if (m & QFont::StyleStrategyResolved)
        rc += "StyleStrategy"_L1;
    return rc;
}

// Debug font
QString fontString(const QFont &f)
{
    QString rc; {
        QTextStream str(&rc);
        str << "QFont(\"" <<  f.family() << ',' << f.pointSize();
        if (f.bold())
            str << ',' << "bold";
        if (f.italic())
            str << ',' << "italic";
        if (f.underline())
            str << ',' << "underline";
        if (f.strikeOut())
            str << ',' << "strikeOut";
        if (f.kerning())
            str << ',' << "kerning";
        str << ',' << f.styleStrategy() << " resolve: "
            << fontMask(f.resolveMask()) << ')';
    }
    return rc;
}
QSize checkSize(const QSize &size)
{
    return size.boundedTo(QSize(0xFFFFFF, 0xFFFFFF));
}

QSize diffSize(QDesignerFormWindowInterface *fw)
{
    const QWidget *container = fw->core()->integration()->containerWindow(fw);
    if (!container)
        return QSize();

    const QSize diff = container->size() - fw->size(); // decoration offset of container window
    return diff;
}

void checkSizes(QDesignerFormWindowInterface *fw, const QSize &size, QSize *formSize, QSize *containerSize)
{
    const QWidget *container = fw->core()->integration()->containerWindow(fw);
    if (!container)
        return;

    const  QSize diff = diffSize(fw); // decoration offset of container window

    QSize newFormSize = checkSize(size).expandedTo(fw->mainContainer()->minimumSizeHint()); // don't try to resize to smaller size than minimumSizeHint
    QSize newContainerSize = newFormSize + diff;

    newContainerSize = newContainerSize.expandedTo(container->minimumSizeHint());
    newContainerSize = newContainerSize.expandedTo(container->minimumSize());

    newFormSize = newContainerSize - diff;

    newContainerSize = checkSize(newContainerSize);

    if (formSize)
        *formSize = newFormSize;
    if (containerSize)
        *containerSize = newContainerSize;
}

/* SubProperties: When applying a changed property to a multiselection, it sometimes makes
 * sense to apply only parts (subproperties) of the property.
 * For example, if someone changes the x-value of a geometry in the property editor
 * and applies it to a multi-selection, y should not be applied as this would cause all
 * the widgets to overlap.
 * The following routines can be used to find out the changed subproperties of a property,
 * which are represented as a mask, and to apply them while leaving the others intact. */

enum RectSubPropertyMask {  SubPropertyX=1, SubPropertyY = 2, SubPropertyWidth = 4, SubPropertyHeight = 8 };
enum SizePolicySubPropertyMask { SubPropertyHSizePolicy = 1, SubPropertyHStretch = 2, SubPropertyVSizePolicy = 4, SubPropertyVStretch = 8 };
enum AlignmentSubPropertyMask { SubPropertyHorizontalAlignment = 1, SubPropertyVerticalAlignment = 2 };
enum StringSubPropertyMask { SubPropertyStringValue = 1, SubPropertyStringComment = 2,
                             SubPropertyStringTranslatable = 4, SubPropertyStringDisambiguation = 8,
                             SubPropertyStringId = 16 };
enum StringListSubPropertyMask { SubPropertyStringListValue = 1, SubPropertyStringListComment = 2,
                                 SubPropertyStringListTranslatable = 4, SubPropertyStringListDisambiguation = 8,
                                 SubPropertyStringListId = 16 };
enum KeySequenceSubPropertyMask { SubPropertyKeySequenceValue = 1, SubPropertyKeySequenceComment = 2,
                                  SubPropertyKeySequenceTranslatable = 4, SubPropertyKeySequenceDisambiguation = 8,
                                  SubPropertyKeySequenceId = 16 };

enum CommonSubPropertyMask : quint64 { SubPropertyAll = quint64(-1) };

// Set the mask flag in mask if the properties do not match.
#define COMPARE_SUBPROPERTY(object1, object2, getter, mask, maskFlag) \
    if (object1.getter() != object2.getter()) (mask) |= (maskFlag);

// find changed subproperties of a rectangle
quint64 compareSubProperties(const QRect & r1, const QRect & r2)
{
    quint64 rc = 0;
    COMPARE_SUBPROPERTY(r1, r2, x, rc, SubPropertyX)
    COMPARE_SUBPROPERTY(r1, r2, y, rc, SubPropertyY)
    COMPARE_SUBPROPERTY(r1, r2, width, rc, SubPropertyWidth)
    COMPARE_SUBPROPERTY(r1, r2, height, rc, SubPropertyHeight)
    return rc;
}

// find changed subproperties of a QSize
quint64 compareSubProperties(const QSize & r1, const QSize & r2)
{
    quint64 rc = 0;
    COMPARE_SUBPROPERTY(r1, r2, width,  rc, SubPropertyWidth)
    COMPARE_SUBPROPERTY(r1, r2, height, rc, SubPropertyHeight)
    return rc;
}
// find changed subproperties of a QSizePolicy
quint64 compareSubProperties(const QSizePolicy & sp1, const QSizePolicy & sp2)
{
    quint64 rc = 0;
    COMPARE_SUBPROPERTY(sp1, sp2, horizontalPolicy,  rc, SubPropertyHSizePolicy)
    COMPARE_SUBPROPERTY(sp1, sp2, horizontalStretch, rc, SubPropertyHStretch)
    COMPARE_SUBPROPERTY(sp1, sp2, verticalPolicy,    rc, SubPropertyVSizePolicy)
    COMPARE_SUBPROPERTY(sp1, sp2, verticalStretch,   rc, SubPropertyVStretch)
    return rc;
}
// find changed subproperties of qdesigner_internal::PropertySheetStringValue
quint64 compareSubProperties(const qdesigner_internal::PropertySheetStringValue & str1, const qdesigner_internal::PropertySheetStringValue & str2)
{
    quint64 rc = 0;
    COMPARE_SUBPROPERTY(str1, str2, value,          rc, SubPropertyStringValue)
    COMPARE_SUBPROPERTY(str1, str2, comment,        rc, SubPropertyStringComment)
    COMPARE_SUBPROPERTY(str1, str2, translatable,   rc, SubPropertyStringTranslatable)
    COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyStringDisambiguation)
    COMPARE_SUBPROPERTY(str1, str2, id,             rc, SubPropertyStringId)
    return rc;
}
// find changed subproperties of qdesigner_internal::PropertySheetStringListValue
quint64 compareSubProperties(const qdesigner_internal::PropertySheetStringListValue & str1, const qdesigner_internal::PropertySheetStringListValue & str2)
{
    quint64 rc = 0;
    COMPARE_SUBPROPERTY(str1, str2, value,          rc, SubPropertyStringListValue)
    COMPARE_SUBPROPERTY(str1, str2, comment,        rc, SubPropertyStringListComment)
    COMPARE_SUBPROPERTY(str1, str2, translatable,   rc, SubPropertyStringListTranslatable)
    COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyStringListDisambiguation)
    COMPARE_SUBPROPERTY(str1, str2, id,             rc, SubPropertyStringListId)
    return rc;
}
// find changed subproperties of qdesigner_internal::PropertySheetKeySequenceValue
quint64 compareSubProperties(const qdesigner_internal::PropertySheetKeySequenceValue & str1, const qdesigner_internal::PropertySheetKeySequenceValue & str2)
{
    quint64 rc = 0;
    COMPARE_SUBPROPERTY(str1, str2, value,          rc, SubPropertyKeySequenceValue)
    COMPARE_SUBPROPERTY(str1, str2, comment,        rc, SubPropertyKeySequenceComment)
    COMPARE_SUBPROPERTY(str1, str2, translatable,   rc, SubPropertyKeySequenceTranslatable)
    COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyKeySequenceDisambiguation)
    COMPARE_SUBPROPERTY(str1, str2, id,             rc, SubPropertyKeySequenceId)
    return rc;
}

// Compare font-subproperties taking the [undocumented]
// resolve flag into account
template <class Property>
void compareFontSubProperty(const QFont & f1,
                            const QFont & f2,
                            Property (QFont::*getter) () const,
                            quint64 maskBit,
                            quint64 &mask)
{
    const bool f1Changed = f1.resolveMask() & maskBit;
    const bool f2Changed = f2.resolveMask() & maskBit;
    // Role has been set/reset in editor
    if (f1Changed != f2Changed) {
        mask |= maskBit;
    } else {
        // Was modified in both palettes: Compare values.
        if (f1Changed && f2Changed && (f1.*getter)() != (f2.*getter)())
            mask |= maskBit;
    }
}
// find changed subproperties of a QFont
quint64 compareSubProperties(const QFont & f1, const QFont & f2)
{
    quint64 rc = 0;
    compareFontSubProperty(f1, f2, &QFont::family,        QFontFamiliesResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::pointSize,     QFont::SizeResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::weight,        QFont::WeightResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::italic,        QFont::StyleResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::underline,     QFont::UnderlineResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::strikeOut,     QFont::StrikeOutResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::kerning,       QFont::KerningResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::styleStrategy, QFont::StyleStrategyResolved, rc);
    compareFontSubProperty(f1, f2, &QFont::hintingPreference, QFont::HintingPreferenceResolved, rc);
    if (debugPropertyCommands)
        qDebug() << "compareSubProperties " <<  fontString(f1) << fontString(f2) << "\n\treturns " << fontMask(rc);
    return rc;
}

// find changed subproperties of a QPalette taking the [undocumented] resolve flags into account
quint64 compareSubProperties(const QPalette & p1, const QPalette & p2)
{
    quint64 rc = 0;
    // generate a mask for each role
    const auto p1Changed = p1.resolveMask();
    const auto p2Changed = p2.resolveMask();

    for (int r = 0; r < static_cast<int>(QPalette::NColorRoles); ++r) {
        for (int g = 0; g < static_cast<int>(QPalette::NColorGroups); ++g) {
            const auto role = static_cast<QPalette::ColorRole>(r);
            const auto group = static_cast<QPalette::ColorGroup>(g);
            const auto maskBit = qdesigner_internal::paletteResolveMask(group, role);
            const bool p1RoleChanged = p1Changed & maskBit;
            const bool p2RoleChanged = p2Changed & maskBit;
            if (p1RoleChanged != p2RoleChanged // Role has been set/reset in editor
                // Was modified in both palettes: Compare values.
                || (p1RoleChanged && p2RoleChanged
                    && p1.brush(group, role).color() != p2.brush(group, role).color())) {
                rc |= maskBit;
            }
        }
    }

    return rc;
}

// find changed subproperties of a QAlignment which is a flag combination of vertical and horizontal

quint64 compareSubProperties(Qt::Alignment a1, Qt::Alignment a2)
{
    quint64 rc = 0;
    if ((a1 & Qt::AlignHorizontal_Mask) != (a2 & Qt::AlignHorizontal_Mask))
        rc |= SubPropertyHorizontalAlignment;
    if ((a1 & Qt::AlignVertical_Mask) != (a2 & Qt::AlignVertical_Mask))
        rc |= SubPropertyVerticalAlignment;
    return rc;
}

Qt::Alignment variantToAlignment(const QVariant & q)
{
    return Qt::Alignment(qdesigner_internal::Utils::valueOf(q));
}
// find changed subproperties of a variant
quint64 compareSubProperties(const QVariant & q1, const QVariant & q2, qdesigner_internal::SpecialProperty specialProperty)
{
    // Do not clobber new value in the comparison function in
    // case someone sets a QString on a PropertySheetStringValue.
    const int t1  = q1.metaType().id();
    const int t2  = q2.metaType().id();
    if (t1 != t2)
        return SubPropertyAll;
    switch (t1) {
    case QMetaType::QRect:
        return compareSubProperties(q1.toRect(), q2.toRect());
    case QMetaType::QSize:
        return compareSubProperties(q1.toSize(), q2.toSize());
    case QMetaType::QSizePolicy:
        return compareSubProperties(qvariant_cast<QSizePolicy>(q1), qvariant_cast<QSizePolicy>(q2));
    case QMetaType::QFont:
        return compareSubProperties(qvariant_cast<QFont>(q1), qvariant_cast<QFont>(q2));
    case QMetaType::QPalette:
        return compareSubProperties(qvariant_cast<QPalette>(q1), qvariant_cast<QPalette>(q2));
    default:
        if (q1.userType() == qMetaTypeId<qdesigner_internal::PropertySheetIconValue>())
            return qvariant_cast<qdesigner_internal::PropertySheetIconValue>(q1).compare(qvariant_cast<qdesigner_internal::PropertySheetIconValue>(q2));
        else if (q1.userType() == qMetaTypeId<qdesigner_internal::PropertySheetStringValue>())
            return compareSubProperties(qvariant_cast<qdesigner_internal::PropertySheetStringValue>(q1), qvariant_cast<qdesigner_internal::PropertySheetStringValue>(q2));
        else if (q1.userType() == qMetaTypeId<qdesigner_internal::PropertySheetStringListValue>())
            return compareSubProperties(qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(q1), qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(q2));
        else if (q1.userType() == qMetaTypeId<qdesigner_internal::PropertySheetKeySequenceValue>())
            return compareSubProperties(qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(q1), qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(q2));
        // Enumerations, flags
        switch (specialProperty) {
        case qdesigner_internal::SP_Alignment:
            return compareSubProperties(variantToAlignment(q1), variantToAlignment(q2));
        default:
        break;
        }
        break;
    }
    return SubPropertyAll;
}

// Apply  the sub property if mask flag is set in mask
#define SET_SUBPROPERTY(rc, newValue, getter, setter, mask, maskFlag) \
    if ((mask) & (maskFlag)) rc.setter((newValue).getter());

// apply changed subproperties to a rectangle
QRect applyRectSubProperty(const QRect &oldValue, const QRect &newValue, unsigned mask)
{
    QRect rc = oldValue;
    SET_SUBPROPERTY(rc, newValue, x,      moveLeft,  mask, SubPropertyX)
    SET_SUBPROPERTY(rc, newValue, y,      moveTop,   mask, SubPropertyY)
    SET_SUBPROPERTY(rc, newValue, width,  setWidth,  mask, SubPropertyWidth)
    SET_SUBPROPERTY(rc, newValue, height, setHeight, mask, SubPropertyHeight)
    return rc;
}


// apply changed subproperties to a rectangle QSize
QSize applySizeSubProperty(const QSize &oldValue, const QSize &newValue, unsigned mask)
{
    QSize rc = oldValue;
    SET_SUBPROPERTY(rc, newValue, width,  setWidth,  mask, SubPropertyWidth)
    SET_SUBPROPERTY(rc, newValue, height, setHeight, mask, SubPropertyHeight)
    return rc;
}


// apply changed subproperties to a SizePolicy
QSizePolicy applySizePolicySubProperty(const QSizePolicy &oldValue, const QSizePolicy &newValue, unsigned mask)
{
    QSizePolicy rc = oldValue;
    SET_SUBPROPERTY(rc, newValue, horizontalPolicy,  setHorizontalPolicy,  mask, SubPropertyHSizePolicy)
    SET_SUBPROPERTY(rc, newValue, horizontalStretch, setHorizontalStretch, mask, SubPropertyHStretch)
    SET_SUBPROPERTY(rc, newValue, verticalPolicy,    setVerticalPolicy,    mask, SubPropertyVSizePolicy)
    SET_SUBPROPERTY(rc, newValue, verticalStretch,   setVerticalStretch,   mask, SubPropertyVStretch)
    return rc;
}

// apply changed subproperties to a qdesigner_internal::PropertySheetStringValue
qdesigner_internal::PropertySheetStringValue applyStringSubProperty(const qdesigner_internal::PropertySheetStringValue &oldValue,
            const qdesigner_internal::PropertySheetStringValue &newValue, unsigned mask)
{
    qdesigner_internal::PropertySheetStringValue rc = oldValue;
    SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyStringValue)
    SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyStringComment)
    SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyStringTranslatable)
    SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyStringDisambiguation)
    SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyStringId)
    return rc;
}

// apply changed subproperties to a qdesigner_internal::PropertySheetStringListValue
qdesigner_internal::PropertySheetStringListValue applyStringListSubProperty(const qdesigner_internal::PropertySheetStringListValue &oldValue,
            const qdesigner_internal::PropertySheetStringListValue &newValue, unsigned mask)
{
    qdesigner_internal::PropertySheetStringListValue rc = oldValue;
    SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyStringListValue)
    SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyStringListComment)
    SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyStringListTranslatable)
    SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyStringListDisambiguation)
    SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyStringListId)
    return rc;
}

// apply changed subproperties to a qdesigner_internal::PropertySheetKeySequenceValue
qdesigner_internal::PropertySheetKeySequenceValue applyKeySequenceSubProperty(const qdesigner_internal::PropertySheetKeySequenceValue &oldValue,
            const qdesigner_internal::PropertySheetKeySequenceValue &newValue, unsigned mask)
{
    qdesigner_internal::PropertySheetKeySequenceValue rc = oldValue;
    SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyKeySequenceValue)
    SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyKeySequenceComment)
    SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyKeySequenceTranslatable)
    SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyKeySequenceDisambiguation)
    SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyKeySequenceId)
    return rc;
}

// Apply the font-subproperties keeping the [undocumented]
// resolve flag in sync (note that PropertySetterType might be something like const T&).
template <class PropertyReturnType, class PropertySetterType>
inline void setFontSubProperty(unsigned mask,
                               const QFont &newValue,
                               unsigned maskBit,
                               PropertyReturnType (QFont::*getter) () const,
                               void (QFont::*setter) (PropertySetterType),
                               QFont &value)
{
    if (mask & maskBit) {
        (value.*setter)((newValue.*getter)());
        // Set the resolve bit from NewValue in return value
        uint r = value.resolveMask();
        const bool origFlag = newValue.resolveMask() & maskBit;
        if (origFlag)
            r |= maskBit;
        else
            r &= ~maskBit;
        value.setResolveMask(r);
        if (debugPropertyCommands)
            qDebug() << "setFontSubProperty " <<  fontMask(maskBit) << " resolve=" << origFlag;
    }
}
// apply changed subproperties to a QFont
QFont applyFontSubProperty(const QFont &oldValue, const QFont &newValue, unsigned mask)
{
    QFont  rc = oldValue;
    setFontSubProperty(mask, newValue, QFontFamiliesResolved,        &QFont::family,        &QFont::setFamily, rc);
    setFontSubProperty(mask, newValue, QFont::SizeResolved,          &QFont::pointSize,     &QFont::setPointSize, rc);
    setFontSubProperty(mask, newValue, QFont::WeightResolved,        &QFont::weight,        &QFont::setWeight, rc);
    setFontSubProperty(mask, newValue, QFont::StyleResolved,         &QFont::italic,        &QFont::setItalic, rc);
    setFontSubProperty(mask, newValue, QFont::UnderlineResolved,     &QFont::underline,     &QFont::setUnderline, rc);
    setFontSubProperty(mask, newValue, QFont::StrikeOutResolved,     &QFont::strikeOut,     &QFont::setStrikeOut, rc);
    setFontSubProperty(mask, newValue, QFont::KerningResolved,       &QFont::kerning,       &QFont::setKerning, rc);
    setFontSubProperty(mask, newValue, QFont::StyleStrategyResolved, &QFont::styleStrategy, &QFont::setStyleStrategy, rc);
    setFontSubProperty(mask, newValue, QFont::HintingPreferenceResolved, &QFont::hintingPreference, &QFont::setHintingPreference, rc);
    if (debugPropertyCommands)
        qDebug() << "applyFontSubProperty old " <<  fontMask(oldValue.resolveMask()) << " new " << fontMask(newValue.resolveMask()) << " return: " << fontMask(rc.resolveMask());
    return rc;
}

// apply changed subproperties to a QPalette
QPalette applyPaletteSubProperty(const QPalette &oldValue, const QPalette &newValue,
                                 quint64 mask)
{
    QPalette rc = oldValue;
    // apply a mask for each role/group
    for (int r = 0; r < static_cast<int>(QPalette::NColorRoles); ++r) {
        for (int g = 0; g < static_cast<int>(QPalette::NColorGroups); ++g) {
            const auto role = static_cast<QPalette::ColorRole>(r);
            const auto group = static_cast<QPalette::ColorGroup>(g);
            const auto maskBit = qdesigner_internal::paletteResolveMask(group, role);
            if (mask & maskBit) {
                rc.setColor(group, role, newValue.color(group, role));
                // Set the resolve bit from NewValue in return value
                auto resolveMask = rc.resolveMask();
                const bool origFlag = newValue.resolveMask() & maskBit;
                if (origFlag)
                    resolveMask |= maskBit;
                else
                    resolveMask &= ~maskBit;
                rc.setResolveMask(resolveMask);
            }
        }
    }
    return rc;
}

// apply changed subproperties to  a QAlignment which is a flag combination of vertical and horizontal
Qt::Alignment applyAlignmentSubProperty(Qt::Alignment oldValue, Qt::Alignment newValue, unsigned mask)
{
    // easy: both changed.
    if (mask == (SubPropertyHorizontalAlignment|SubPropertyVerticalAlignment))
        return newValue;
    // Change subprop
    const Qt::Alignment changeMask   = (mask & SubPropertyHorizontalAlignment) ? Qt::AlignHorizontal_Mask : Qt::AlignVertical_Mask;
    const Qt::Alignment takeOverMask = (mask & SubPropertyHorizontalAlignment) ? Qt::AlignVertical_Mask   : Qt::AlignHorizontal_Mask;
    return (oldValue & takeOverMask) | (newValue & changeMask);
}

}

namespace qdesigner_internal {

// apply changed subproperties to a variant
PropertyHelper::Value applySubProperty(const QVariant &oldValue, const QVariant &newValue,
                                       qdesigner_internal::SpecialProperty specialProperty,
                                       quint64 mask, bool changed)
{
    if (mask == SubPropertyAll)
        return PropertyHelper::Value(newValue, changed);

    switch (oldValue.metaType().id()) {
    case QMetaType::QRect:
        return PropertyHelper::Value(applyRectSubProperty(oldValue.toRect(), newValue.toRect(), mask), changed);
    case QMetaType::QSize:
        return PropertyHelper::Value(applySizeSubProperty(oldValue.toSize(), newValue.toSize(), mask), changed);
    case QMetaType::QSizePolicy:
        return PropertyHelper::Value(QVariant::fromValue(applySizePolicySubProperty(qvariant_cast<QSizePolicy>(oldValue), qvariant_cast<QSizePolicy>(newValue), mask)), changed);
    case QMetaType::QFont: {
        // Changed flag in case of font and palette depends on resolve mask only, not on the passed "changed" value.

        // The first case: the user changed bold subproperty and then pressed reset button for this subproperty (not for
        // the whole font property). We instantiate SetPropertyCommand passing changed=true. But in this case no
        // subproperty is changed and the whole property should be marked an unchanged.

        // The second case: there are 2 pushbuttons, for 1st the user set bold and italic subproperties,
        // for the 2nd he set bold only. He does multiselection so that the current widget is the 2nd one.
        // He press reset next to bold subproperty. In result the 2nd widget should have the whole
        // font property marked as unchanged and the 1st widget should have the font property
        // marked as changed and only italic subproperty should be marked as changed (the bold should be reset).

        // The third case: there are 2 pushbuttons, for 1st the user set bold and italic subproperties,
        // for the 2nd he set bold only. He does multiselection so that the current widget is the 2nd one.
        // He press reset button for the whole font property. In result whole font properties for both
        // widgets should be marked as unchanged.
        QFont font = applyFontSubProperty(qvariant_cast<QFont>(oldValue), qvariant_cast<QFont>(newValue), mask);
        return PropertyHelper::Value(QVariant::fromValue(font), font.resolveMask());
        }
    case QMetaType::QPalette: {
        QPalette palette = applyPaletteSubProperty(qvariant_cast<QPalette>(oldValue), qvariant_cast<QPalette>(newValue), mask);
        return PropertyHelper::Value(QVariant::fromValue(palette), palette.resolveMask());
        }
    default:
        if (oldValue.userType() == qMetaTypeId<qdesigner_internal::PropertySheetIconValue>()) {
            PropertySheetIconValue icon = qvariant_cast<qdesigner_internal::PropertySheetIconValue>(oldValue);
            icon.assign(qvariant_cast<qdesigner_internal::PropertySheetIconValue>(newValue), mask);
            return PropertyHelper::Value(QVariant::fromValue(icon), icon.mask());
        } else if (oldValue.userType() == qMetaTypeId<qdesigner_internal::PropertySheetStringValue>()) {
            qdesigner_internal::PropertySheetStringValue str = applyStringSubProperty(
                        qvariant_cast<qdesigner_internal::PropertySheetStringValue>(oldValue),
                        qvariant_cast<qdesigner_internal::PropertySheetStringValue>(newValue), mask);
            return PropertyHelper::Value(QVariant::fromValue(str), changed);
        } else if (oldValue.userType() == qMetaTypeId<qdesigner_internal::PropertySheetStringListValue>()) {
            qdesigner_internal::PropertySheetStringListValue str = applyStringListSubProperty(
                        qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(oldValue),
                        qvariant_cast<qdesigner_internal::PropertySheetStringListValue>(newValue), mask);
            return PropertyHelper::Value(QVariant::fromValue(str), changed);
        } else if (oldValue.userType() == qMetaTypeId<qdesigner_internal::PropertySheetKeySequenceValue>()) {
            qdesigner_internal::PropertySheetKeySequenceValue key = applyKeySequenceSubProperty(
                        qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(oldValue),
                        qvariant_cast<qdesigner_internal::PropertySheetKeySequenceValue>(newValue), mask);
            return PropertyHelper::Value(QVariant::fromValue(key), changed);
        }
        // Enumerations, flags
        switch (specialProperty) {
        case qdesigner_internal::SP_Alignment: {
            qdesigner_internal::PropertySheetFlagValue f = qvariant_cast<qdesigner_internal::PropertySheetFlagValue>(oldValue);
            f.value = applyAlignmentSubProperty(variantToAlignment(oldValue), variantToAlignment(newValue), mask);
            QVariant v;
            v.setValue(f);
            return PropertyHelper::Value(v, changed);
                                               }
        default:
        break;
        }
        break;
    }
    return PropertyHelper::Value(newValue, changed);

}
// figure out special property
enum SpecialProperty getSpecialProperty(const QString& propertyName)
{
    if (propertyName == "objectName"_L1)
        return SP_ObjectName;
    if (propertyName == "layoutName"_L1)
        return SP_LayoutName;
    if (propertyName == "spacerName"_L1)
        return SP_SpacerName;
    if (propertyName == "icon"_L1)
        return SP_Icon;
    if (propertyName == "currentTabName"_L1)
        return SP_CurrentTabName;
    if (propertyName == "currentItemName"_L1)
        return SP_CurrentItemName;
    if (propertyName == "currentPageName"_L1)
        return SP_CurrentPageName;
    if (propertyName == "geometry"_L1)
        return SP_Geometry;
    if (propertyName == "windowTitle"_L1)
        return SP_WindowTitle;
    if (propertyName == "minimumSize"_L1)
        return SP_MinimumSize;
    if (propertyName == "maximumSize"_L1)
        return SP_MaximumSize;
    if (propertyName == "alignment"_L1)
        return SP_Alignment;
    if (propertyName == "autoDefault"_L1)
        return SP_AutoDefault;
    if (propertyName == "shortcut"_L1)
        return SP_Shortcut;
    if (propertyName == "orientation"_L1)
        return SP_Orientation;
    return SP_None;
}


PropertyHelper::PropertyHelper(QObject* object,
                               SpecialProperty specialProperty,
                               QDesignerPropertySheetExtension *sheet,
                               int index) :
    m_specialProperty(specialProperty),
    m_object(object),
    m_objectType(OT_Object),
    m_propertySheet(sheet),  m_index(index),
    m_oldValue(m_propertySheet->property(m_index), m_propertySheet->isChanged(m_index))
{
    if (object->isWidgetType()) {
        m_parentWidget = (qobject_cast<QWidget*>(object))->parentWidget();
        m_objectType = OT_Widget;
    } else {
        if (const QAction *action = qobject_cast<const QAction *>(m_object)) {
            const QObjectList associatedObjects = action->associatedObjects();
            auto it = std::find_if(associatedObjects.cbegin(), associatedObjects.cend(),
                                   [](QObject *obj) {
                                       return qobject_cast<QWidget *>(obj) != nullptr;
                                   });
            m_objectType = (it == associatedObjects.cend()) ? OT_FreeAction : OT_AssociatedAction;
        }
    }

    if(debugPropertyCommands)
        qDebug() << "PropertyHelper on " << m_object->objectName() << " index= " << m_index << " type = " << m_objectType;
}

QDesignerIntegration *PropertyHelper::integration(QDesignerFormWindowInterface *fw) const
{
    return qobject_cast<QDesignerIntegration *>(fw->core()->integration());
}

// Set widget value, apply corrections and checks in case of main window.
void PropertyHelper::checkApplyWidgetValue(QDesignerFormWindowInterface *fw, QWidget* w,
                                      SpecialProperty specialProperty, QVariant &value)
{

    bool isMainContainer = false;
    if (QDesignerFormWindowCursorInterface *cursor = fw->cursor()) {
        if (cursor->isWidgetSelected(w)) {
            if (cursor->isWidgetSelected(fw->mainContainer())) {
                isMainContainer = true;
            }
        }
    }
    if (!isMainContainer)
        return;

    QWidget *container = fw->core()->integration()->containerWindow(fw);
    if (!container)
        return;


    switch (specialProperty) {
    case SP_MinimumSize: {
        const QSize size = checkSize(value.toSize());
        value.setValue(size);
    }

        break;
    case SP_MaximumSize: {
        QSize fs, cs;
        checkSizes(fw, value.toSize(), &fs, &cs);
        container->setMaximumSize(cs);
        fw->mainContainer()->setMaximumSize(fs);
        value.setValue(fs);

    }
        break;
    case SP_Geometry: {
        QRect r = value.toRect();
        QSize fs, cs;
        checkSizes(fw, r.size(), &fs, &cs);
        container->resize(cs);
        r.setSize(fs);
        value.setValue(r);
    }
        break;
    default:
        break;
    }
}

unsigned PropertyHelper::updateMask() const
{
    unsigned rc = 0;
    switch (m_specialProperty) {
    case SP_ObjectName:
    case SP_LayoutName:
    case SP_SpacerName:
    case SP_CurrentTabName:
    case SP_CurrentItemName:
    case SP_CurrentPageName:
        if (m_objectType != OT_FreeAction)
            rc |= UpdateObjectInspector;
        break;
    case SP_Icon:
        if (m_objectType == OT_AssociatedAction)
            rc |= UpdateObjectInspector;
        break;
    case SP_Orientation: // for updating splitter icon
        rc |= UpdateObjectInspector;
        break;
    default:
        break;

    }
    return rc;
}


bool PropertyHelper::canMerge(const PropertyHelper &other) const
{
    return m_object == other.m_object &&  m_index == other.m_index;
}

void PropertyHelper::triggerActionChanged(QAction *a)
{
    a->setData(QVariant(true)); // this triggers signal "changed" in QAction
    a->setData(QVariant(false));
}

// Update the object to reflect the changes
void PropertyHelper::updateObject(QDesignerFormWindowInterface *fw, const QVariant &oldValue, const QVariant &newValue)
{
    if(debugPropertyCommands){
         qDebug() << "PropertyHelper::updateObject(" << m_object->objectName() << ") " << oldValue << " -> " << newValue;
    }
    switch (m_objectType) {
    case OT_Widget: {
        switch (m_specialProperty) {
        case SP_ObjectName: {
            const QString oldName = qvariant_cast<PropertySheetStringValue>(oldValue).value();
            const QString newName = qvariant_cast<PropertySheetStringValue>(newValue).value();
            QDesignerFormWindowCommand::updateBuddies(fw, oldName, newName);
        }
            break;
        default:
            break;
        }
    } break;
    case OT_AssociatedAction:
    case OT_FreeAction:
        // SP_Shortcut is a fake property, so, QAction::changed does not trigger.
        if (m_specialProperty == SP_ObjectName || m_specialProperty == SP_Shortcut)
            triggerActionChanged(qobject_cast<QAction *>(m_object));
        break;
    default:
        break;
    }

    switch (m_specialProperty) {
    case SP_ObjectName:
    case SP_LayoutName:
    case SP_SpacerName:
        if (QDesignerIntegration *integr = integration(fw)) {
            const QString oldName = qvariant_cast<PropertySheetStringValue>(oldValue).value();
            const QString newName = qvariant_cast<PropertySheetStringValue>(newValue).value();
            integr->emitObjectNameChanged(fw, m_object, newName, oldName);
        }
        break;
    default:
        break;
    }
}

void PropertyHelper::ensureUniqueObjectName(QDesignerFormWindowInterface *fw, QObject *object) const
{
    switch (m_specialProperty) {
    case SP_SpacerName:
        if (object->isWidgetType()) {
            if (Spacer *sp = qobject_cast<Spacer *>(object)) {
                fw->ensureUniqueObjectName(sp);
                return;
            }
        }
        fw->ensureUniqueObjectName(object);
        break;
    case SP_LayoutName: // Layout name is invoked on the parent widget.
        if (object->isWidgetType()) {
            const QWidget * w = qobject_cast<const QWidget *>(object);
            if (QLayout *wlayout = w->layout()) {
                fw->ensureUniqueObjectName(wlayout);
                return;
            }
        }
        fw->ensureUniqueObjectName(object);
        break;
    case SP_ObjectName:
        fw->ensureUniqueObjectName(object);
        break;
    default:
        break;
    }
}

PropertyHelper::Value PropertyHelper::setValue(QDesignerFormWindowInterface *fw, const QVariant &value,
                                               bool changed, quint64 subPropertyMask)
{
    // Set new whole value
    if (subPropertyMask == SubPropertyAll)
        return  applyValue(fw, m_oldValue.first, Value(value, changed));

    // apply subproperties
    const PropertyHelper::Value maskedNewValue = applySubProperty(m_oldValue.first, value, m_specialProperty, subPropertyMask, changed);
    return applyValue(fw, m_oldValue.first, maskedNewValue);
}

// Apply the value and update. Returns corrected value
PropertyHelper::Value PropertyHelper::applyValue(QDesignerFormWindowInterface *fw, const QVariant &oldValue, Value newValue)
{
     if(debugPropertyCommands){
         qDebug() << "PropertyHelper::applyValue(" << m_object << ") " << oldValue << " -> " << newValue.first << " changed=" << newValue.second;
     }

    if (m_objectType ==  OT_Widget) {
        checkApplyWidgetValue(fw, qobject_cast<QWidget *>(m_object), m_specialProperty, newValue.first);
    }

    m_propertySheet->setProperty(m_index, newValue.first);
    m_propertySheet->setChanged(m_index, newValue.second);

    switch (m_specialProperty) {
    case SP_LayoutName:
    case SP_ObjectName:
    case SP_SpacerName:
        ensureUniqueObjectName(fw, m_object);
        newValue.first = m_propertySheet->property(m_index);
        break;
    default:
        break;
    }

    updateObject(fw, oldValue, newValue.first);
    return newValue;
}

PropertyHelper::Value PropertyHelper::restoreOldValue(QDesignerFormWindowInterface *fw)
{
    return applyValue(fw, m_propertySheet->property(m_index), m_oldValue);
}

// find the default value in widget DB in case PropertySheet::reset fails
QVariant PropertyHelper::findDefaultValue(QDesignerFormWindowInterface *fw) const
{
    if (m_specialProperty == SP_AutoDefault && qobject_cast<const QPushButton*>(m_object)) {
        // AutoDefault defaults to true on dialogs
        const bool isDialog = qobject_cast<const QDialog *>(fw->mainContainer());
        return QVariant(isDialog);
    }

    const int item_idx = fw->core()->widgetDataBase()->indexOfObject(m_object);
    if (item_idx == -1)
        return  m_oldValue.first; // We simply don't know the value in this case

    const QDesignerWidgetDataBaseItemInterface *item = fw->core()->widgetDataBase()->item(item_idx);
    const auto default_prop_values = item->defaultPropertyValues();
    if (m_index < default_prop_values.size())
        return default_prop_values.at(m_index);

    if (m_oldValue.first.metaType().id() == QMetaType::QColor)
        return QColor();

    return m_oldValue.first; // Again, we just don't know
}

PropertyHelper::Value PropertyHelper::restoreDefaultValue(QDesignerFormWindowInterface *fw)
{

    Value defaultValue{{}, false};
    const QVariant currentValue = m_propertySheet->property(m_index);
    // try to reset sheet, else try to find default
    if (m_propertySheet->reset(m_index)) {
        defaultValue.first = m_propertySheet->property(m_index);
    } else {
        defaultValue.first = findDefaultValue(fw);
        m_propertySheet->setProperty(m_index, defaultValue.first);
    }

    m_propertySheet->setChanged(m_index, defaultValue.second);

    if (m_objectType == OT_Widget) {
        checkApplyWidgetValue(fw, qobject_cast<QWidget *>(m_object), m_specialProperty, defaultValue.first);
    }

    switch (m_specialProperty) {
    case SP_LayoutName:
    case SP_ObjectName:
    case SP_SpacerName:
        ensureUniqueObjectName(fw, m_object);
        defaultValue.first = m_propertySheet->property(m_index);
        break;
    default:
        break;
    }

    updateObject(fw, currentValue, defaultValue.first);
    return defaultValue;
}

// ---- PropertyListCommand::PropertyDescription(


PropertyListCommand::PropertyDescription::PropertyDescription(const QString &propertyName,
                                                              QDesignerPropertySheetExtension *propertySheet,
                                                              int index) :
    m_propertyName(propertyName),
    m_propertyGroup(propertySheet->propertyGroup(index)),
    m_propertyType(propertySheet->property(index).metaType().id()),
    m_specialProperty(getSpecialProperty(propertyName))
{
}

void PropertyListCommand::PropertyDescription::debug() const
{
    qDebug() << m_propertyName << m_propertyGroup << m_propertyType << m_specialProperty;
}

bool PropertyListCommand::PropertyDescription::equals(const PropertyDescription &p) const
{
    return m_propertyType == p.m_propertyType && m_specialProperty == p.m_specialProperty &&
           m_propertyName == p.m_propertyName && m_propertyGroup   == p.m_propertyGroup;
}


// ---- PropertyListCommand
PropertyListCommand::PropertyListCommand(QDesignerFormWindowInterface *formWindow,
                                         QUndoCommand *parent) :
    QDesignerFormWindowCommand(QString(), formWindow, parent)
{
}

const QString PropertyListCommand::propertyName() const
{
    return m_propertyDescription.m_propertyName;
}

SpecialProperty PropertyListCommand::specialProperty() const
{
    return m_propertyDescription.m_specialProperty;
}

// add an object
bool PropertyListCommand::add(QObject *object, const QString &propertyName)
{
    QDesignerPropertySheetExtension* sheet = propertySheet(object);
    Q_ASSERT(sheet);

    const int index = sheet->indexOf(propertyName);
    if (index == -1)
        return false;

    if (!sheet->isEnabled(index))
        return false;

    const PropertyDescription description(propertyName, sheet, index);

    if (m_propertyHelperList.empty()) {
        // first entry
        m_propertyDescription = description;
    } else {
        // checks: mismatch or only one object in case of name
        const bool match = m_propertyDescription.equals(description);
        if (!match || m_propertyDescription.m_specialProperty == SP_ObjectName)
            return false;
    }

    auto ph = createPropertyHelper(object, m_propertyDescription.m_specialProperty, sheet, index);
    m_propertyHelperList.push_back(std::move(ph));
    return true;
}

std::unique_ptr<PropertyHelper>
PropertyListCommand::createPropertyHelper(QObject *object, SpecialProperty sp,
                                          QDesignerPropertySheetExtension *sheet, int sheetIndex) const
{
    return std::make_unique<PropertyHelper>(object, sp, sheet, sheetIndex);
}

// Init from a list and make sure referenceObject is added first to obtain the right property group
bool PropertyListCommand::initList(const QObjectList &list, const QString &apropertyName, QObject *referenceObject)
{
    m_propertyHelperList.clear();

    // Ensure the referenceObject (property editor) is first, so the right property group is chosen.
    if (referenceObject) {
        if (!add(referenceObject, apropertyName))
            return false;
    }
    for (QObject *o : list) {
        if (o != referenceObject)
            add(o, apropertyName);
    }

    return !m_propertyHelperList.empty();
}


QObject* PropertyListCommand::object(int index) const
{
    Q_ASSERT(size_t(index) < m_propertyHelperList.size());
    return m_propertyHelperList[index]->object();
}

QVariant PropertyListCommand::oldValue(int index) const
{
    Q_ASSERT(size_t(index) < m_propertyHelperList.size());
    return m_propertyHelperList[index]->oldValue();
}

void PropertyListCommand::setOldValue(const QVariant &oldValue, int index)
{
    Q_ASSERT(size_t(index) < m_propertyHelperList.size());
    m_propertyHelperList[index]->setOldValue(oldValue);
}
// ----- SetValueFunction: Set a new value when applied to a PropertyHelper.
class SetValueFunction {
public:
    SetValueFunction(QDesignerFormWindowInterface *formWindow, const PropertyHelper::Value &newValue,
                     quint64 subPropertyMask);

    PropertyHelper::Value operator()(PropertyHelper&);
private:
    QDesignerFormWindowInterface *m_formWindow;
    const PropertyHelper::Value m_newValue;
    const quint64 m_subPropertyMask;
};


SetValueFunction::SetValueFunction(QDesignerFormWindowInterface *formWindow,
                                   const PropertyHelper::Value &newValue,
                                   quint64 subPropertyMask) :
    m_formWindow(formWindow),
    m_newValue(newValue),
    m_subPropertyMask(subPropertyMask)
{
}

PropertyHelper::Value SetValueFunction::operator()(PropertyHelper &ph) {
        return ph.setValue(m_formWindow, m_newValue.first, m_newValue.second, m_subPropertyMask);
}

// ----- UndoSetValueFunction: Restore old value when applied to a PropertyHelper.
class UndoSetValueFunction {
public:
    UndoSetValueFunction(QDesignerFormWindowInterface *formWindow) : m_formWindow(formWindow) {}
    PropertyHelper::Value operator()(PropertyHelper& ph) { return ph.restoreOldValue(m_formWindow); }
private:
    QDesignerFormWindowInterface *m_formWindow;
};

// ----- RestoreDefaultFunction: Restore default value when applied to a PropertyHelper.
class RestoreDefaultFunction {
public:
    RestoreDefaultFunction(QDesignerFormWindowInterface *formWindow) : m_formWindow(formWindow) {}
    PropertyHelper::Value operator()(PropertyHelper& ph) { return ph.restoreDefaultValue(m_formWindow); }
private:
    QDesignerFormWindowInterface *m_formWindow;
};

// ----- changePropertyList: Iterates over a sequence of PropertyHelpers and
// applies a function to them.
// The function returns the  corrected value which is then set in  the property editor.
// Returns a combination of update flags.
template <class PropertyListIterator, class Function>
        unsigned changePropertyList(QDesignerFormEditorInterface *core,
                                    const QString &propertyName,
                                    PropertyListIterator begin,
                                    PropertyListIterator end,
                                    Function function)
{
    unsigned updateMask = 0;
    QDesignerPropertyEditorInterface *propertyEditor = core->propertyEditor();
    bool updatedPropertyEditor = false;

    for (auto it = begin; it != end; ++it) {
        PropertyHelper *ph = it->get();
        if (QObject* object = ph->object()) { // Might have been deleted in the meantime
            const PropertyHelper::Value newValue = function( *ph );
            updateMask |= ph->updateMask();
            // Update property editor if it is the current object
            if (!updatedPropertyEditor && propertyEditor && object == propertyEditor->object()) {
                propertyEditor->setPropertyValue(propertyName, newValue.first,  newValue.second);
                updatedPropertyEditor = true;
            }
        }
    }
    if (!updatedPropertyEditor) updateMask |=  PropertyHelper::UpdatePropertyEditor;
    return updateMask;
}


// set a new value, return update mask
unsigned PropertyListCommand::setValue(const QVariant &value, bool changed, quint64 subPropertyMask)
{
    if(debugPropertyCommands)
        qDebug() << "PropertyListCommand::setValue(" << value
                 << changed << subPropertyMask << ')';
    return changePropertyList(formWindow()->core(),
                              m_propertyDescription.m_propertyName,
                              m_propertyHelperList.begin(), m_propertyHelperList.end(),
                              SetValueFunction(formWindow(), PropertyHelper::Value(value, changed), subPropertyMask));
}

// restore old value,  return update mask
unsigned PropertyListCommand::restoreOldValue()
{
    if(debugPropertyCommands)
        qDebug() << "PropertyListCommand::restoreOldValue()";

    return changePropertyList(formWindow()->core(),
                              m_propertyDescription.m_propertyName, m_propertyHelperList.begin(), m_propertyHelperList.end(),
                              UndoSetValueFunction(formWindow()));
}
// set default value,  return update mask
unsigned PropertyListCommand::restoreDefaultValue()
{
    if(debugPropertyCommands)
        qDebug() << "PropertyListCommand::restoreDefaultValue()";

    return changePropertyList(formWindow()->core(),
                              m_propertyDescription.m_propertyName, m_propertyHelperList.begin(), m_propertyHelperList.end(),
                              RestoreDefaultFunction(formWindow()));
}

// update
void PropertyListCommand::update(unsigned updateMask)
{
    if(debugPropertyCommands)
        qDebug() << "PropertyListCommand::update(" << updateMask << ')';

    if (updateMask & PropertyHelper::UpdateObjectInspector) {
        if (QDesignerObjectInspectorInterface *oi = formWindow()->core()->objectInspector())
            oi->setFormWindow(formWindow());
    }

    if (updateMask & PropertyHelper::UpdatePropertyEditor) {
        // this is needed when f.ex. undo, changes parent's palette, but
        // the child is the active widget,
        // TODO: current object?
        if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) {
            propertyEditor->setObject(propertyEditor->object());
        }
    }
}

void PropertyListCommand::undo()
{
    update(restoreOldValue());
    QDesignerPropertyEditor *designerPropertyEditor = qobject_cast<QDesignerPropertyEditor *>(core()->propertyEditor());
    if (designerPropertyEditor)
        designerPropertyEditor->updatePropertySheet();
}

// check if lists are aequivalent for command merging (same widgets and props)
bool PropertyListCommand::canMergeLists(const PropertyHelperList& other) const
{
    if (m_propertyHelperList.size() != other.size())
        return false;
    for (size_t i = 0; i < m_propertyHelperList.size(); ++i) {
        if (!m_propertyHelperList[i]->canMerge(*other[i]))
            return false;
    }
    return true;
}

// ---- SetPropertyCommand ----
SetPropertyCommand::SetPropertyCommand(QDesignerFormWindowInterface *formWindow,
                                       QUndoCommand *parent)
    :  PropertyListCommand(formWindow, parent),
       m_subPropertyMask(SubPropertyAll)
{
}

bool SetPropertyCommand::init(QObject *object, const QString &apropertyName, const QVariant &newValue)
{
    Q_ASSERT(object);

    m_newValue = newValue;

    propertyHelperList().clear();
    if (!add(object, apropertyName))
        return false;

    setDescription();
    return true;
}

bool SetPropertyCommand::init(const QObjectList &list, const QString &apropertyName, const QVariant &newValue,
                              QObject *referenceObject, bool enableSubPropertyHandling)
{
    if (!initList(list, apropertyName, referenceObject))
        return false;

    m_newValue = newValue;

    if(debugPropertyCommands)
        qDebug() << "SetPropertyCommand::init()" << propertyHelperList().size() << '/' << list.size() << " reference " << referenceObject;

    setDescription();

    if (enableSubPropertyHandling)
        m_subPropertyMask = subPropertyMask(newValue, referenceObject);
    return true;
}

quint64 SetPropertyCommand::subPropertyMask(const QVariant &newValue, QObject *referenceObject)
{
    // figure out the mask of changed sub properties when comparing newValue to the current value of the reference object.
    if (!referenceObject)
        return SubPropertyAll;

    QDesignerPropertySheetExtension* sheet = propertySheet(referenceObject);
    Q_ASSERT(sheet);

    const int index = sheet->indexOf(propertyName());
    if (index == -1 || !sheet->isVisible(index))
        return SubPropertyAll;

    return compareSubProperties(sheet->property(index), newValue, specialProperty());
}

void SetPropertyCommand::setDescription()
{
    if (propertyHelperList().size() == 1) {
        setText(QApplication::translate("Command", "Changed '%1' of '%2'")
                                        .arg(propertyName(), propertyHelperList().front()->object()->objectName()));
    } else {
        int count = static_cast<int>(propertyHelperList().size());
        setText(QCoreApplication::translate("Command", "Changed '%1' of %n objects", "", count).arg(propertyName()));
    }
}

void SetPropertyCommand::redo()
{
    update(setValue(m_newValue, true, m_subPropertyMask));
    QDesignerPropertyEditor *designerPropertyEditor = qobject_cast<QDesignerPropertyEditor *>(core()->propertyEditor());
    if (designerPropertyEditor)
        designerPropertyEditor->updatePropertySheet();
}


int SetPropertyCommand::id() const
{
    return 1976;
}

QVariant SetPropertyCommand::mergeValue(const QVariant &newValue)
{
    return newValue;
}

bool SetPropertyCommand::mergeWith(const QUndoCommand *other)
{
    if (id() != other->id() || !formWindow()->isDirty())
        return false;

    // Merging: When  for example when the user types ahead in an inplace-editor,
    // it makes sense to merge all the generated commands containing the one-character changes.
    // In the case of subproperties, if the user changes the font size from 10 to 30 via 20
    // and then changes to bold, it makes sense to merge the font size commands only.
    // This is why the m_subPropertyMask is checked.

    const SetPropertyCommand *cmd = static_cast<const SetPropertyCommand*>(other);
    if (!propertyDescription().equals(cmd->propertyDescription()) ||
        m_subPropertyMask  != cmd->m_subPropertyMask ||
        !canMergeLists(cmd->propertyHelperList()))
        return false;

    const QVariant newValue = mergeValue(cmd->newValue());
    if (!newValue.isValid())
        return false;
    m_newValue = newValue;
    m_subPropertyMask |= cmd->m_subPropertyMask;
    if(debugPropertyCommands)
        qDebug() << "SetPropertyCommand::mergeWith() succeeded " << propertyName();

    return true;
}

// ---- ResetPropertyCommand ----
ResetPropertyCommand::ResetPropertyCommand(QDesignerFormWindowInterface *formWindow)
    : PropertyListCommand(formWindow)
{
}

bool ResetPropertyCommand::init(QObject *object, const QString &apropertyName)
{
    Q_ASSERT(object);

    propertyHelperList().clear();
    if (!add(object, apropertyName))
        return false;

    setDescription();
    return true;
}

bool ResetPropertyCommand::init(const QObjectList &list, const QString &apropertyName, QObject *referenceObject)
{
    QObjectList modifiedList = list; // filter out modified properties
    for (auto it = modifiedList.begin(); it != modifiedList.end() ; ) {
        QDesignerPropertySheetExtension* sheet = propertySheet(*it);
        Q_ASSERT(sheet);
        const int index = sheet->indexOf(apropertyName);
        if (index == -1 || !sheet->isChanged(index))
            it = modifiedList.erase(it);
        else
            ++it;
    }
    if (!modifiedList.contains(referenceObject))
        referenceObject = nullptr;
    if (modifiedList.isEmpty() || !initList(modifiedList, apropertyName, referenceObject))
        return false;

    if(debugPropertyCommands)
        qDebug() << "ResetPropertyCommand::init()" << propertyHelperList().size() << '/' << list.size();

    setDescription();
    return true;
}

void ResetPropertyCommand::setDescription()
{
    if (propertyHelperList().size() == 1) {
        setText(QCoreApplication::translate("Command", "Reset '%1' of '%2'")
                                            .arg(propertyName(), propertyHelperList().front()->object()->objectName()));
    } else {
        int count = static_cast<int>(propertyHelperList().size());
        setText(QCoreApplication::translate("Command", "Reset '%1' of %n objects", "", count).arg(propertyName()));
    }
}

void ResetPropertyCommand::redo()
{
    update(restoreDefaultValue());
    QDesignerPropertyEditor *designerPropertyEditor = qobject_cast<QDesignerPropertyEditor *>(core()->propertyEditor());
    if (designerPropertyEditor)
        designerPropertyEditor->updatePropertySheet();
}

AddDynamicPropertyCommand::AddDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow)
    : QDesignerFormWindowCommand(QString(), formWindow)
{

}

bool AddDynamicPropertyCommand::init(const QObjectList &selection, QObject *current,
            const QString &propertyName, const QVariant &value)
{
    Q_ASSERT(current);
    m_propertyName = propertyName;

    QDesignerFormEditorInterface *core = formWindow()->core();
    QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), current);
    Q_ASSERT(dynamicSheet);

    m_selection.clear();

    if (!value.isValid())
        return false;

    if (!dynamicSheet->canAddDynamicProperty(m_propertyName))
        return false;

    m_selection.append(current);

    m_value = value;

    for (QObject *obj : selection) {
        if (m_selection.contains(obj))
            continue;
        dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), obj);
        Q_ASSERT(dynamicSheet);
        if (dynamicSheet->canAddDynamicProperty(m_propertyName))
            m_selection.append(obj);
    }

    setDescription();
    return true;
}

void AddDynamicPropertyCommand::redo()
{
    QDesignerFormEditorInterface *core = formWindow()->core();
    for (QObject *obj : std::as_const(m_selection)) {
        QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), obj);
        dynamicSheet->addDynamicProperty(m_propertyName, m_value);
        if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) {
            if (propertyEditor->object() == obj)
                propertyEditor->setObject(obj);
        }
    }
}

void AddDynamicPropertyCommand::undo()
{
    QDesignerFormEditorInterface *core = formWindow()->core();
    for (QObject *obj : std::as_const(m_selection)) {
        QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), obj);
        QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), obj);
        dynamicSheet->removeDynamicProperty(sheet->indexOf(m_propertyName));
        if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) {
            if (propertyEditor->object() == obj)
                propertyEditor->setObject(obj);
        }
    }
}

void AddDynamicPropertyCommand::setDescription()
{
    if (m_selection.size() == 1) {
        setText(QApplication::translate("Command", "Add dynamic property '%1' to '%2'")
                                       .arg(m_propertyName, m_selection.first()->objectName()));
    } else {
        int count = m_selection.size();
        setText(QCoreApplication::translate("Command", "Add dynamic property '%1' to %n objects", "", count)
                                            .arg(m_propertyName));
    }
}


RemoveDynamicPropertyCommand::RemoveDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow)
    : QDesignerFormWindowCommand(QString(), formWindow)
{

}

bool RemoveDynamicPropertyCommand::init(const QObjectList &selection, QObject *current,
            const QString &propertyName)
{
    Q_ASSERT(current);
    m_propertyName = propertyName;

    QDesignerFormEditorInterface *core = formWindow()->core();
    QDesignerPropertySheetExtension *propertySheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), current);
    Q_ASSERT(propertySheet);
    QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), current);
    Q_ASSERT(dynamicSheet);

    m_objectToValueAndChanged.clear();

    const int index = propertySheet->indexOf(m_propertyName);
    if (!dynamicSheet->isDynamicProperty(index))
        return false;

    m_objectToValueAndChanged[current] = {propertySheet->property(index),
                                          propertySheet->isChanged(index)};

    for (QObject *obj : selection) {
        if (m_objectToValueAndChanged.contains(obj))
            continue;

        propertySheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), obj);
        dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), obj);
        const int idx = propertySheet->indexOf(m_propertyName);
        if (dynamicSheet->isDynamicProperty(idx))
            m_objectToValueAndChanged[obj] = {propertySheet->property(idx),
                                              propertySheet->isChanged(idx)};
    }

    setDescription();
    return true;
}

void RemoveDynamicPropertyCommand::redo()
{
    QDesignerFormEditorInterface *core = formWindow()->core();
    for (auto it = m_objectToValueAndChanged.cbegin(), end = m_objectToValueAndChanged.cend(); it != end; ++it) {
        QObject *obj = it.key();
        QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), obj);
        QDesignerPropertySheetExtension *sheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), obj);
        dynamicSheet->removeDynamicProperty(sheet->indexOf(m_propertyName));
        if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) {
            if (propertyEditor->object() == obj)
                propertyEditor->setObject(obj);
        }
    }
}

void RemoveDynamicPropertyCommand::undo()
{
    QDesignerFormEditorInterface *core = formWindow()->core();
    for (auto it = m_objectToValueAndChanged.cbegin(), end = m_objectToValueAndChanged.cend(); it != end; ++it) {
        QObject *obj = it.key();
        QDesignerPropertySheetExtension *propertySheet = qt_extension<QDesignerPropertySheetExtension*>(core->extensionManager(), obj);
        QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension<QDesignerDynamicPropertySheetExtension*>(core->extensionManager(), obj);
        const int index = dynamicSheet->addDynamicProperty(m_propertyName, it.value().first);
        propertySheet->setChanged(index, it.value().second);
        if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) {
            if (propertyEditor->object() == obj)
                propertyEditor->setObject(obj);
        }
    }
}

void RemoveDynamicPropertyCommand::setDescription()
{
    if (m_objectToValueAndChanged.size() == 1) {
        setText(QApplication::translate("Command",
                                        "Remove dynamic property '%1' from '%2'")
                                        .arg(m_propertyName, m_objectToValueAndChanged.constBegin().key()->objectName()));
    } else {
        int count = m_objectToValueAndChanged.size();
        setText(QApplication::translate("Command",
                                        "Remove dynamic property '%1' from %n objects", "", count)
                                        .arg(m_propertyName));
    }
}


} // namespace qdesigner_internal

QT_END_NAMESPACE
