/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing/
**
** This file is part of the Qt Quick Templates 2 module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL3$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPLv3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or later as published by the Free
** Software Foundation and appearing in the file LICENSE.GPL included in
** the packaging of this file. Please review the following information to
** ensure the GNU General Public License version 2.0 requirements will be
** met: http://www.gnu.org/licenses/gpl-2.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qquickdialogbuttonbox_p.h"
#include "qquickdialogbuttonbox_p_p.h"
#include "qquickabstractbutton_p.h"
#include "qquickbutton_p.h"
#include "qquickdialog_p_p.h"

#include <QtCore/qpointer.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/qpa/qplatformtheme.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcontext.h>
#include <QtQml/qqmlcomponent.h>

#include <algorithm>

QT_BEGIN_NAMESPACE

/*!
    \qmltype DialogButtonBox
    \inherits Container
    \instantiates QQuickDialogButtonBox
    \inqmlmodule QtQuick.Controls
    \ingroup qtquickcontrols2-dialogs
    \brief A button box used in dialogs.
    \since 5.8

    Dialogs and message boxes typically present buttons in an order that
    conforms to the interface guidelines for that platform. Invariably,
    different platforms have their dialog buttons in different orders.
    DialogButtonBox allows a developer to add buttons to it and will
    automatically use the appropriate order for the user's platform.

    Most buttons for a dialog follow certain roles. Such roles include:

    \list
    \li Accepting or rejecting the dialog.
    \li Asking for help.
    \li Performing actions on the dialog itself (such as resetting fields or
       applying changes).
    \endlist

    There can also be alternate ways of dismissing the dialog which may cause
    destructive results.

    Most dialogs have buttons that can almost be considered standard (e.g.
    \uicontrol OK and \uicontrol Cancel buttons). It is sometimes convenient
    to create these buttons in a standard way.

    There are a couple ways of using DialogButtonBox. One way is to specify
    the standard buttons (e.g. \uicontrol OK, \uicontrol Cancel, \uicontrol Save)
    and let the button box setup the buttons.

    \image qtquickcontrols2-dialogbuttonbox.png

    \snippet qtquickcontrols2-dialogbuttonbox.qml 1

    Alternatively, buttons and their roles can be specified by hand:

    \snippet qtquickcontrols2-dialogbuttonbox-attached.qml 1

    You can also mix and match normal buttons and standard buttons.

    When a button is clicked in the button box, the \l clicked() signal is
    emitted for the actual button that is pressed. In addition, the
    following signals are automatically emitted when a button with the
    respective role(s) is pressed:

    \table
    \header
        \li Role
        \li Signal
    \row
        \li \c AcceptRole, \c YesRole
        \li \l accepted()
    \row
        \li \c ApplyRole
        \li \l applied()
    \row
        \li \c DiscardRole
        \li \l discarded()
    \row
        \li \c HelpRole
        \li \l helpRequested()
    \row
        \li \c RejectRole, \c NoRole
        \li \l rejected()
    \row
        \li \c ResetRole
        \li \l reset()
    \endtable

    \sa Dialog
*/

/*!
    \qmlsignal QtQuick.Controls::DialogButtonBox::accepted()

    This signal is emitted when a button defined with the \c AcceptRole or
    \c YesRole is clicked.

    \sa rejected(), clicked(), helpRequested()
*/

/*!
    \qmlsignal QtQuick.Controls::DialogButtonBox::rejected()

    This signal is emitted when a button defined with the \c RejectRole or
    \c NoRole is clicked.

    \sa accepted(), helpRequested(), clicked()
*/

/*!
    \since QtQuick.Controls 2.3 (Qt 5.10)
    \qmlsignal QtQuick.Controls::DialogButtonBox::applied()

    This signal is emitted when a button defined with the \c ApplyRole is
    clicked.

    \sa discarded(), reset()
*/

/*!
    \since QtQuick.Controls 2.3 (Qt 5.10)
    \qmlsignal QtQuick.Controls::DialogButtonBox::reset()

    This signal is emitted when a button defined with the \c ResetRole is
    clicked.

    \sa discarded(), applied()
*/

/*!
    \since QtQuick.Controls 2.3 (Qt 5.10)
    \qmlsignal QtQuick.Controls::DialogButtonBox::discarded()

    This signal is emitted when a button defined with the \c DiscardRole is
    clicked.

    \sa reset(), applied()
*/

/*!
    \qmlsignal QtQuick.Controls::DialogButtonBox::helpRequested()

    This signal is emitted when a button defined with the \c HelpRole is clicked.

    \sa accepted(), rejected(), clicked()
*/

/*!
    \qmlsignal QtQuick.Controls::DialogButtonBox::clicked(AbstractButton button)

    This signal is emitted when a \a button inside the button box is clicked.

    \sa accepted(), rejected(), helpRequested()
*/

static QPlatformDialogHelper::ButtonLayout platformButtonLayout()
{
    return QGuiApplicationPrivate::platformTheme()->themeHint(QPlatformTheme::DialogButtonBoxLayout).value<QPlatformDialogHelper::ButtonLayout>();
}

void QQuickDialogButtonBoxPrivate::itemImplicitWidthChanged(QQuickItem *item)
{
    QQuickContainerPrivate::itemImplicitWidthChanged(item);
    if (item == contentItem)
        resizeContent();
    else
        updateImplicitContentWidth();
}

void QQuickDialogButtonBoxPrivate::itemImplicitHeightChanged(QQuickItem *item)
{
    QQuickContainerPrivate::itemImplicitHeightChanged(item);
    if (item == contentItem)
        resizeContent();
    else
        updateImplicitContentHeight();
}

// adapted from QStyle::alignedRect()
static QRectF alignedRect(Qt::LayoutDirection direction, Qt::Alignment alignment, const QSizeF &size, const QRectF &rectangle)
{
    alignment = QGuiApplicationPrivate::visualAlignment(direction, alignment);
    qreal x = rectangle.x();
    qreal y = rectangle.y();
    qreal w = size.width();
    qreal h = size.height();
    if ((alignment & Qt::AlignVCenter) == Qt::AlignVCenter || (alignment & Qt::AlignVertical_Mask) == 0)
        y += (rectangle.size().height() - h) / 2;
    else if ((alignment & Qt::AlignBottom) == Qt::AlignBottom)
        y += rectangle.size().height() - h;
    if ((alignment & Qt::AlignRight) == Qt::AlignRight)
        x += rectangle.size().width() - w;
    else if ((alignment & Qt::AlignHCenter) == Qt::AlignHCenter)
        x += (rectangle.size().width() - w) / 2;
    return QRectF(x, y, w, h);
}

void QQuickDialogButtonBoxPrivate::resizeContent()
{
    Q_Q(QQuickDialogButtonBox);
    if (!contentItem)
        return;

    QRectF geometry = q->boundingRect().adjusted(q->leftPadding(), q->topPadding(), -q->rightPadding(), -q->bottomPadding());
    if (alignment != 0)
        geometry = alignedRect(q->isMirrored() ? Qt::RightToLeft : Qt::LeftToRight, alignment, QSizeF(contentWidth, contentHeight), geometry);

    contentItem->setPosition(geometry.topLeft());
    contentItem->setSize(geometry.size());
}

void QQuickDialogButtonBoxPrivate::updateLayout()
{
    Q_Q(QQuickDialogButtonBox);
    const int count = contentModel->count();
    if (count <= 0)
        return;

    const int halign = alignment & Qt::AlignHorizontal_Mask;
    const int valign = alignment & Qt::AlignVertical_Mask;

    QVector<QQuickAbstractButton *> buttons;
    const qreal cw = (alignment & Qt::AlignHorizontal_Mask) == 0 ? q->availableWidth() : contentWidth;
    const qreal itemWidth = (cw - qMax(0, count - 1) * spacing) / count;

    for (int i = 0; i < count; ++i) {
        QQuickItem *item = q->itemAt(i);
        if (item) {
            QQuickItemPrivate *p = QQuickItemPrivate::get(item);
            if (!p->widthValid) {
                if (!halign)
                    item->setWidth(itemWidth);
                else
                    item->resetWidth();
                if (!valign)
                    item->setHeight(contentHeight);
                else
                    item->resetHeight();
                p->widthValid = false;
            }
        }
        buttons += static_cast<QQuickAbstractButton *>(item);
    }

    struct ButtonLayout {
        ButtonLayout(QPlatformDialogHelper::ButtonLayout layout)
            : m_layout(QPlatformDialogHelper::buttonLayout(Qt::Horizontal, layout))
        {
        }

        bool operator()(QQuickAbstractButton *first, QQuickAbstractButton *second)
        {
            const QPlatformDialogHelper::ButtonRole firstRole = QQuickDialogPrivate::buttonRole(first);
            const QPlatformDialogHelper::ButtonRole secondRole = QQuickDialogPrivate::buttonRole(second);

            if (firstRole != secondRole && firstRole != QPlatformDialogHelper::InvalidRole && secondRole != QPlatformDialogHelper::InvalidRole) {
                const int *l = m_layout;
                while (*l != QPlatformDialogHelper::EOL) {
                    // Unset the Reverse flag.
                    const int role = (*l & ~QPlatformDialogHelper::Reverse);
                    if (role == firstRole)
                        return true;
                    if (role == secondRole)
                        return false;
                    ++l;
                }
            }

            if (firstRole == secondRole)
                return false;

            return firstRole != QPlatformDialogHelper::InvalidRole;
        }
        const int *m_layout;
    };

    std::stable_sort(buttons.begin(), buttons.end(), ButtonLayout(static_cast<QPlatformDialogHelper::ButtonLayout>(buttonLayout)));

    for (int i = 0; i < buttons.count() - 1; ++i)
        q->insertItem(i, buttons.at(i));
}

qreal QQuickDialogButtonBoxPrivate::getContentWidth() const
{
    Q_Q(const QQuickDialogButtonBox);
    const int count = contentModel->count();
    const qreal totalSpacing = qMax(0, count - 1) * spacing;
    qreal totalWidth = totalSpacing;
    qreal maxWidth = 0;
    for (int i = 0; i < count; ++i) {
        QQuickItem *item = q->itemAt(i);
        if (item) {
            totalWidth += item->implicitWidth();
            maxWidth = qMax(maxWidth, item->implicitWidth());
        }
    }
    if ((alignment & Qt::AlignHorizontal_Mask) == 0)
        totalWidth = qMax(totalWidth, count * maxWidth + totalSpacing);
    return totalWidth;
}

qreal QQuickDialogButtonBoxPrivate::getContentHeight() const
{
    Q_Q(const QQuickDialogButtonBox);
    const int count = contentModel->count();
    qreal maxHeight = 0;
    for (int i = 0; i < count; ++i) {
        QQuickItem *item = q->itemAt(i);
        if (item)
            maxHeight = qMax(maxHeight, item->implicitHeight());
    }
    return maxHeight;
}

void QQuickDialogButtonBoxPrivate::handleClick()
{
    Q_Q(QQuickDialogButtonBox);
    QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->sender());
    if (!button)
        return;

    // Can't fetch this *after* emitting clicked, as clicked may destroy the button
    // or change its role. Now changing the role is not possible yet, but arguably
    // both clicked and accepted/rejected/etc. should be emitted "atomically"
    // depending on whatever role the button had at the time of the click.
    const QPlatformDialogHelper::ButtonRole role = QQuickDialogPrivate::buttonRole(button);
    QPointer<QQuickDialogButtonBox> guard(q);

    emit q->clicked(button);

    if (!guard)
        return;

    switch (role) {
    case QPlatformDialogHelper::AcceptRole:
    case QPlatformDialogHelper::YesRole:
        emit q->accepted();
        break;
    case QPlatformDialogHelper::RejectRole:
    case QPlatformDialogHelper::NoRole:
        emit q->rejected();
        break;
    case QPlatformDialogHelper::ApplyRole:
        emit q->applied();
        break;
    case QPlatformDialogHelper::ResetRole:
        emit q->reset();
        break;
    case QPlatformDialogHelper::DestructiveRole:
        emit q->discarded();
        break;
    case QPlatformDialogHelper::HelpRole:
        emit q->helpRequested();
        break;
    default:
        break;
    }
}

QQuickAbstractButton *QQuickDialogButtonBoxPrivate::createStandardButton(QPlatformDialogHelper::StandardButton standardButton)
{
    Q_Q(QQuickDialogButtonBox);
    if (!delegate)
        return nullptr;

    QQmlContext *creationContext = delegate->creationContext();
    if (!creationContext)
        creationContext = qmlContext(q);
    QQmlContext *context = new QQmlContext(creationContext, q);
    context->setContextObject(q);

    QObject *object = delegate->beginCreate(context);
    QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton*>(object);
    if (button) {
        QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, true));
        QQuickDialogButtonBoxAttachedPrivate::get(attached)->standardButton = standardButton;
        attached->setButtonRole(QPlatformDialogHelper::buttonRole(standardButton));
        button->setText(QPlatformTheme::removeMnemonics(QGuiApplicationPrivate::platformTheme()->standardButtonText(standardButton)));
        delegate->completeCreate();
        return button;
    }

    delete object;
    return nullptr;
}

void QQuickDialogButtonBoxPrivate::removeStandardButtons()
{
    Q_Q(QQuickDialogButtonBox);
    int i = q->count() - 1;
    while (i >= 0) {
        QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(q->itemAt(i));
        if (button) {
            QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(button, false));
            if (attached) {
                QQuickDialogButtonBoxAttachedPrivate *p = QQuickDialogButtonBoxAttachedPrivate::get(attached);
                if (p->standardButton != QPlatformDialogHelper::NoButton) {
                    q->removeItem(i);
                    button->deleteLater();
                }
            }
        }
        --i;
    }
}

QQuickDialogButtonBox::QQuickDialogButtonBox(QQuickItem *parent)
    : QQuickContainer(*(new QQuickDialogButtonBoxPrivate), parent)
{
    Q_D(QQuickDialogButtonBox);
    d->changeTypes |= QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight;
    d->buttonLayout = platformButtonLayout();
}

QQuickDialogButtonBox::~QQuickDialogButtonBox()
{
}

/*!
    \qmlproperty enumeration QtQuick.Controls::DialogButtonBox::position

    This property holds the position of the button box.

    \note If the button box is assigned as a header or footer of ApplicationWindow
    or Page, the appropriate position is set automatically.

    Possible values:
    \value DialogButtonBox.Header The button box is at the top, as a window or page header.
    \value DialogButtonBox.Footer The button box is at the bottom, as a window or page header.

    The default value is \c Footer.

    \sa Dialog::header, Dialog::footer
*/
QQuickDialogButtonBox::Position QQuickDialogButtonBox::position() const
{
    Q_D(const QQuickDialogButtonBox);
    return d->position;
}

void QQuickDialogButtonBox::setPosition(Position position)
{
    Q_D(QQuickDialogButtonBox);
    if (d->position == position)
        return;

    d->position = position;
    emit positionChanged();
}

/*!
    \qmlproperty flags QtQuick.Controls::DialogButtonBox::alignment

    This property holds the alignment of the buttons.

    Possible values:
    \value undefined The buttons are resized to fill the available space.
    \value Qt.AlignLeft The buttons are aligned to the left.
    \value Qt.AlignHCenter The buttons are horizontally centered.
    \value Qt.AlignRight The buttons are aligned to the right.
    \value Qt.AlignTop The buttons are aligned to the top.
    \value Qt.AlignVCenter The buttons are vertically centered.
    \value Qt.AlignBottom The buttons are aligned to the bottom.
*/
Qt::Alignment QQuickDialogButtonBox::alignment() const
{
    Q_D(const QQuickDialogButtonBox);
    return d->alignment;
}

void QQuickDialogButtonBox::setAlignment(Qt::Alignment alignment)
{
    Q_D(QQuickDialogButtonBox);
    if (d->alignment == alignment)
        return;

    d->alignment = alignment;
    if (isComponentComplete()) {
        d->resizeContent();
        polish();
    }
    emit alignmentChanged();
}

void QQuickDialogButtonBox::resetAlignment()
{
    setAlignment(0);
}

/*!
    \qmlproperty enumeration QtQuick.Controls::DialogButtonBox::standardButtons

    This property holds a combination of standard buttons that are used by the button box.

    \snippet qtquickcontrols2-dialogbuttonbox.qml 1

    The buttons will be positioned in the appropriate order for the user's platform.

    Possible flags:
    \value DialogButtonBox.Ok An "OK" button defined with the \c AcceptRole.
    \value DialogButtonBox.Open An "Open" button defined with the \c AcceptRole.
    \value DialogButtonBox.Save A "Save" button defined with the \c AcceptRole.
    \value DialogButtonBox.Cancel A "Cancel" button defined with the \c RejectRole.
    \value DialogButtonBox.Close A "Close" button defined with the \c RejectRole.
    \value DialogButtonBox.Discard A "Discard" or "Don't Save" button, depending on the platform, defined with the \c DestructiveRole.
    \value DialogButtonBox.Apply An "Apply" button defined with the \c ApplyRole.
    \value DialogButtonBox.Reset A "Reset" button defined with the \c ResetRole.
    \value DialogButtonBox.RestoreDefaults A "Restore Defaults" button defined with the \c ResetRole.
    \value DialogButtonBox.Help A "Help" button defined with the \c HelpRole.
    \value DialogButtonBox.SaveAll A "Save All" button defined with the \c AcceptRole.
    \value DialogButtonBox.Yes A "Yes" button defined with the \c YesRole.
    \value DialogButtonBox.YesToAll A "Yes to All" button defined with the \c YesRole.
    \value DialogButtonBox.No A "No" button defined with the \c NoRole.
    \value DialogButtonBox.NoToAll A "No to All" button defined with the \c NoRole.
    \value DialogButtonBox.Abort An "Abort" button defined with the \c RejectRole.
    \value DialogButtonBox.Retry A "Retry" button defined with the \c AcceptRole.
    \value DialogButtonBox.Ignore An "Ignore" button defined with the \c AcceptRole.
    \value DialogButtonBox.NoButton An invalid button.

    \sa standardButton()
*/
QPlatformDialogHelper::StandardButtons QQuickDialogButtonBox::standardButtons() const
{
    Q_D(const QQuickDialogButtonBox);
    return d->standardButtons;
}

void QQuickDialogButtonBox::setStandardButtons(QPlatformDialogHelper::StandardButtons buttons)
{
    Q_D(QQuickDialogButtonBox);
    if (d->standardButtons == buttons)
        return;

    d->removeStandardButtons();

    for (int i = QPlatformDialogHelper::FirstButton; i <= QPlatformDialogHelper::LastButton; i<<=1) {
        QPlatformDialogHelper::StandardButton standardButton = static_cast<QPlatformDialogHelper::StandardButton>(i);
        if (standardButton & buttons) {
            QQuickAbstractButton *button = d->createStandardButton(standardButton);
            if (button)
                addItem(button);
        }
    }

    if (isComponentComplete())
        polish();

    d->standardButtons = buttons;
    emit standardButtonsChanged();
}

/*!
    \qmlmethod AbstractButton QtQuick.Controls::DialogButtonBox::standardButton(StandardButton button)

    Returns the specified standard \a button, or \c null if it does not exist.

    \sa standardButtons
*/
QQuickAbstractButton *QQuickDialogButtonBox::standardButton(QPlatformDialogHelper::StandardButton button) const
{
    Q_D(const QQuickDialogButtonBox);
    if (Q_UNLIKELY(!(d->standardButtons & button)))
        return nullptr;
    for (int i = 0, n = count(); i < n; ++i) {
        QQuickAbstractButton *btn = qobject_cast<QQuickAbstractButton *>(d->itemAt(i));
        if (Q_LIKELY(btn)) {
            QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(btn, false));
            if (attached && QQuickDialogButtonBoxAttachedPrivate::get(attached)->standardButton == button)
                return btn;
        }
    }
    return nullptr;
}

/*!
    \qmlproperty Component QtQuick.Controls::DialogButtonBox::delegate

    This property holds a delegate for creating standard buttons.

    \sa standardButtons
*/
QQmlComponent *QQuickDialogButtonBox::delegate() const
{
    Q_D(const QQuickDialogButtonBox);
    return d->delegate;
}

void QQuickDialogButtonBox::setDelegate(QQmlComponent* delegate)
{
    Q_D(QQuickDialogButtonBox);
    if (d->delegate == delegate)
        return;

    delete d->delegate;
    d->delegate = delegate;
    emit delegateChanged();
}

QQuickDialogButtonBoxAttached *QQuickDialogButtonBox::qmlAttachedProperties(QObject *object)
{
    return new QQuickDialogButtonBoxAttached(object);
}

/*!
    \since QtQuick.Controls 2.5 (Qt 5.12)
    \qmlproperty enumeration QtQuick.Controls::DialogButtonBox::buttonLayout

    This property holds the button layout policy to be used when arranging the buttons contained in the button box.
    The default value is platform-specific.

    Available values:
    \value DialogButtonBox.WinLayout Use a policy appropriate for applications on Windows.
    \value DialogButtonBox.MacLayout Use a policy appropriate for applications on macOS.
    \value DialogButtonBox.KdeLayout Use a policy appropriate for applications on KDE.
    \value DialogButtonBox.GnomeLayout Use a policy appropriate for applications on GNOME.
    \value DialogButtonBox.AndroidLayout Use a policy appropriate for applications on Android.
*/
QPlatformDialogHelper::ButtonLayout QQuickDialogButtonBox::buttonLayout() const
{
    Q_D(const QQuickDialogButtonBox);
    return d->buttonLayout;
}

void QQuickDialogButtonBox::setButtonLayout(QPlatformDialogHelper::ButtonLayout layout)
{
    Q_D(QQuickDialogButtonBox);
    if (d->buttonLayout == layout)
        return;

    d->buttonLayout = layout;
    if (isComponentComplete())
        d->updateLayout();
    emit buttonLayoutChanged();
}

void QQuickDialogButtonBox::resetButtonLayout()
{
    setButtonLayout(platformButtonLayout());
}

void QQuickDialogButtonBox::updatePolish()
{
    Q_D(QQuickDialogButtonBox);
    QQuickContainer::updatePolish();
    d->updateLayout();
}

void QQuickDialogButtonBox::componentComplete()
{
    Q_D(QQuickDialogButtonBox);
    QQuickContainer::componentComplete();
    d->updateLayout();
}

void QQuickDialogButtonBox::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
{
    Q_D(QQuickDialogButtonBox);
    QQuickContainer::geometryChanged(newGeometry, oldGeometry);
    d->updateLayout();
}

void QQuickDialogButtonBox::contentItemChange(QQuickItem *newItem, QQuickItem *oldItem)
{
    Q_D(QQuickDialogButtonBox);
    QQuickContainer::contentItemChange(newItem, oldItem);
    if (oldItem)
        QQuickItemPrivate::get(oldItem)->removeItemChangeListener(d, QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight);
    if (newItem)
        QQuickItemPrivate::get(newItem)->addItemChangeListener(d, QQuickItemPrivate::ImplicitWidth | QQuickItemPrivate::ImplicitHeight);
}

bool QQuickDialogButtonBox::isContent(QQuickItem *item) const
{
    return qobject_cast<QQuickAbstractButton *>(item);
}

void QQuickDialogButtonBox::itemAdded(int index, QQuickItem *item)
{
    Q_D(QQuickDialogButtonBox);
    Q_UNUSED(index);
    if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item))
        QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, d, &QQuickDialogButtonBoxPrivate::handleClick);
    if (QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(item, false)))
        QQuickDialogButtonBoxAttachedPrivate::get(attached)->setButtonBox(this);
    d->updateImplicitContentSize();
    if (isComponentComplete())
        polish();
}

void QQuickDialogButtonBox::itemRemoved(int index, QQuickItem *item)
{
    Q_D(QQuickDialogButtonBox);
    Q_UNUSED(index);
    if (QQuickAbstractButton *button = qobject_cast<QQuickAbstractButton *>(item))
        QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, d, &QQuickDialogButtonBoxPrivate::handleClick);
    if (QQuickDialogButtonBoxAttached *attached = qobject_cast<QQuickDialogButtonBoxAttached *>(qmlAttachedPropertiesObject<QQuickDialogButtonBox>(item, false)))
        QQuickDialogButtonBoxAttachedPrivate::get(attached)->setButtonBox(nullptr);
    d->updateImplicitContentSize();
    if (isComponentComplete())
        polish();
}

#if QT_CONFIG(accessibility)
QAccessible::Role QQuickDialogButtonBox::accessibleRole() const
{
    return QAccessible::PageTabList;
}
#endif

void QQuickDialogButtonBoxAttachedPrivate::setButtonBox(QQuickDialogButtonBox *box)
{
    Q_Q(QQuickDialogButtonBoxAttached);
    if (buttonBox == box)
        return;

    buttonBox = box;
    emit q->buttonBoxChanged();
}

QQuickDialogButtonBoxAttached::QQuickDialogButtonBoxAttached(QObject *parent)
    : QObject(*(new QQuickDialogButtonBoxAttachedPrivate), parent)
{
    Q_D(QQuickDialogButtonBoxAttached);
    QQuickItem *parentItem = qobject_cast<QQuickItem *>(parent);
    while (parentItem && !d->buttonBox) {
        d->buttonBox = qobject_cast<QQuickDialogButtonBox *>(parentItem);
        parentItem = parentItem->parentItem();
    }
}

/*!
    \qmlattachedproperty DialogButtonBox QtQuick.Controls::DialogButtonBox::buttonBox
    \readonly

    This attached property holds the button box that manages this button, or
    \c null if the button is not in a button box.
*/
QQuickDialogButtonBox *QQuickDialogButtonBoxAttached::buttonBox() const
{
    Q_D(const QQuickDialogButtonBoxAttached);
    return d->buttonBox;
}

/*!
    \qmlattachedproperty enumeration QtQuick.Controls::DialogButtonBox::buttonRole

    This attached property holds the role of each button in a button box.

    \snippet qtquickcontrols2-dialogbuttonbox-attached.qml 1

    Available values:
    \value DialogButtonBox.InvalidRole The button is invalid.
    \value DialogButtonBox.AcceptRole Clicking the button causes the dialog to be accepted (e.g. \uicontrol OK).
    \value DialogButtonBox.RejectRole Clicking the button causes the dialog to be rejected (e.g. \uicontrol Cancel).
    \value DialogButtonBox.DestructiveRole Clicking the button causes a destructive change (e.g. for discarding changes) and closes the dialog.
    \value DialogButtonBox.ActionRole Clicking the button causes changes to the elements within the dialog.
    \value DialogButtonBox.HelpRole The button can be clicked to request help.
    \value DialogButtonBox.YesRole The button is a "Yes"-like button.
    \value DialogButtonBox.NoRole The button is a "No"-like button.
    \value DialogButtonBox.ResetRole The button resets the dialog's fields to default values.
    \value DialogButtonBox.ApplyRole The button applies current changes.
*/
QPlatformDialogHelper::ButtonRole QQuickDialogButtonBoxAttached::buttonRole() const
{
    Q_D(const QQuickDialogButtonBoxAttached);
    return d->buttonRole;
}

void QQuickDialogButtonBoxAttached::setButtonRole(QPlatformDialogHelper::ButtonRole role)
{
    Q_D(QQuickDialogButtonBoxAttached);
    if (d->buttonRole == role)
        return;

    d->buttonRole = role;
    emit buttonRoleChanged();
}

QT_END_NAMESPACE
