From bfb0a9ebe3b1dc6b0f06ec2d66469ba878fbe98b Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Sat, 21 May 2016 10:17:39 +0200 Subject: Add DialogButtonBox [ChangeLog][Controls] Added DialogButtonBox to provide convenience for handling dialog buttons. DialogButtonBox is able to create a set of standard buttons with a single line of QML code, and provides convenient accepted() and rejected() signals. Task-number: QTBUG-51090 Change-Id: I9b3c6ba1b2836dadf9a2ac9086be1eba214e7c4d Reviewed-by: Mitch Curtis --- examples/quickcontrols2/gallery/gallery.qml | 45 +- src/imports/controls/DialogButtonBox.qml | 69 ++ src/imports/controls/controls.pri | 1 + .../qtquickcontrols2-dialogbuttonbox-attached.png | Bin 0 -> 1993 bytes .../images/qtquickcontrols2-dialogbuttonbox.png | Bin 0 -> 1987 bytes .../qtquickcontrols2-dialogbuttonbox-attached.qml | 42 ++ .../snippets/qtquickcontrols2-dialogbuttonbox.qml | 38 ++ src/imports/controls/material/DialogButtonBox.qml | 75 +++ src/imports/controls/material/material.pri | 1 + src/imports/controls/qtquickcontrols2plugin.cpp | 1 + src/imports/controls/universal/DialogButtonBox.qml | 71 +++ src/imports/controls/universal/universal.pri | 1 + src/imports/templates/qtquicktemplates2plugin.cpp | 3 + src/quicktemplates2/qquickapplicationwindow.cpp | 15 +- src/quicktemplates2/qquickdialogbuttonbox.cpp | 703 +++++++++++++++++++++ src/quicktemplates2/qquickdialogbuttonbox_p.h | 160 +++++ src/quicktemplates2/qquickdialogbuttonbox_p_p.h | 108 ++++ src/quicktemplates2/qquickpage.cpp | 15 +- src/quicktemplates2/quicktemplates2.pri | 3 + tests/auto/controls/data/tst_dialogbuttonbox.qml | 210 ++++++ 20 files changed, 1519 insertions(+), 42 deletions(-) create mode 100644 src/imports/controls/DialogButtonBox.qml create mode 100644 src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox-attached.png create mode 100644 src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox.png create mode 100644 src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox-attached.qml create mode 100644 src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox.qml create mode 100644 src/imports/controls/material/DialogButtonBox.qml create mode 100644 src/imports/controls/universal/DialogButtonBox.qml create mode 100644 src/quicktemplates2/qquickdialogbuttonbox.cpp create mode 100644 src/quicktemplates2/qquickdialogbuttonbox_p.h create mode 100644 src/quicktemplates2/qquickdialogbuttonbox_p_p.h create mode 100644 tests/auto/controls/data/tst_dialogbuttonbox.qml diff --git a/examples/quickcontrols2/gallery/gallery.qml b/examples/quickcontrols2/gallery/gallery.qml index 113874a9..38e60632 100644 --- a/examples/quickcontrols2/gallery/gallery.qml +++ b/examples/quickcontrols2/gallery/gallery.qml @@ -40,7 +40,7 @@ import QtQuick 2.6 import QtQuick.Layouts 1.3 -import QtQuick.Controls 2.0 +import QtQuick.Controls 2.1 import QtQuick.Controls.Material 2.0 import QtQuick.Controls.Universal 2.0 import Qt.labs.settings 1.0 @@ -100,7 +100,7 @@ ApplicationWindow { MenuItem { text: "Settings" - onTriggered: settingsPopup.open() + onTriggered: settingsDialog.open() } MenuItem { text: "About" @@ -208,7 +208,7 @@ ApplicationWindow { } Popup { - id: settingsPopup + id: settingsDialog x: (window.width - width) / 2 y: window.height / 6 width: Math.min(window.width, window.height) / 3 * 2 @@ -255,38 +255,15 @@ ApplicationWindow { Layout.fillHeight: true } - RowLayout { - spacing: 10 - - Button { - id: okButton - text: "Ok" - onClicked: { - settings.style = styleBox.displayText - settingsPopup.close() - } - - Material.foreground: Material.primary - Material.background: "transparent" - Material.elevation: 0 - - Layout.preferredWidth: 0 - Layout.fillWidth: true + DialogButtonBox { + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + onAccepted: { + settings.style = styleBox.displayText + settingsDialog.close() } - - Button { - id: cancelButton - text: "Cancel" - onClicked: { - styleBox.currentIndex = styleBox.styleIndex - settingsPopup.close() - } - - Material.background: "transparent" - Material.elevation: 0 - - Layout.preferredWidth: 0 - Layout.fillWidth: true + onRejected: { + styleBox.currentIndex = styleBox.styleIndex + settingsDialog.close() } } } diff --git a/src/imports/controls/DialogButtonBox.qml b/src/imports/controls/DialogButtonBox.qml new file mode 100644 index 00000000..599c5d73 --- /dev/null +++ b/src/imports/controls/DialogButtonBox.qml @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Templates 2.1 as T + +T.DialogButtonBox { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + + spacing: 1 + alignment: count === 1 ? Qt.AlignRight : undefined + + delegate: Button { + width: control.count === 1 ? control.availableWidth / 2 : undefined + } + + contentItem: ListView { + implicitWidth: contentWidth + implicitHeight: 40 + + model: control.contentModel + spacing: control.spacing + orientation: ListView.Horizontal + boundsBehavior: Flickable.StopAtBounds + snapMode: ListView.SnapToItem + } + + background: Rectangle { + implicitHeight: 40 + } +} diff --git a/src/imports/controls/controls.pri b/src/imports/controls/controls.pri index 523356a1..2b33c720 100644 --- a/src/imports/controls/controls.pri +++ b/src/imports/controls/controls.pri @@ -17,6 +17,7 @@ QML_CONTROLS = \ CheckIndicator.qml \ ComboBox.qml \ Dial.qml \ + DialogButtonBox.qml \ Drawer.qml \ Frame.qml \ GroupBox.qml \ diff --git a/src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox-attached.png b/src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox-attached.png new file mode 100644 index 00000000..53ecf83d Binary files /dev/null and b/src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox-attached.png differ diff --git a/src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox.png b/src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox.png new file mode 100644 index 00000000..87cece98 Binary files /dev/null and b/src/imports/controls/doc/images/qtquickcontrols2-dialogbuttonbox.png differ diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox-attached.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox-attached.qml new file mode 100644 index 00000000..a93cda30 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox-attached.qml @@ -0,0 +1,42 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 2.1 + +//! [1] +DialogButtonBox { + Button { + text: qsTr("Save") + DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole + } + Button { + text: qsTr("Close") + DialogButtonBox.buttonRole: DialogButtonBox.DestructiveRole + } +} +//! [1] diff --git a/src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox.qml b/src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox.qml new file mode 100644 index 00000000..41e6cf23 --- /dev/null +++ b/src/imports/controls/doc/snippets/qtquickcontrols2-dialogbuttonbox.qml @@ -0,0 +1,38 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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 Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Controls 2.1 + +//! [1] +DialogButtonBox { + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: console.log("Ok clicked") + onRejected: console.log("Cancel clicked") +} +//! [1] diff --git a/src/imports/controls/material/DialogButtonBox.qml b/src/imports/controls/material/DialogButtonBox.qml new file mode 100644 index 00000000..027acca8 --- /dev/null +++ b/src/imports/controls/material/DialogButtonBox.qml @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 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$ +** +****************************************************************************/ + +import QtQuick 2.6 +import QtQuick.Templates 2.1 as T +import QtQuick.Controls.Material 2.0 + +T.DialogButtonBox { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + + flat: true + spacing: 8 + padding: 8 + topPadding: padding - 4 + bottomPadding: padding - 4 + alignment: Qt.AlignRight + + Material.foreground: Material.accent + + delegate: Button { flat: true } + + contentItem: ListView { + implicitWidth: contentWidth + implicitHeight: 48 + + model: control.contentModel + spacing: control.spacing + orientation: ListView.Horizontal + boundsBehavior: Flickable.StopAtBounds + snapMode: ListView.SnapToItem + } + + background: Rectangle { + implicitHeight: 52 + color: control.Material.backgroundColor + } +} diff --git a/src/imports/controls/material/material.pri b/src/imports/controls/material/material.pri index d6247895..89d98131 100644 --- a/src/imports/controls/material/material.pri +++ b/src/imports/controls/material/material.pri @@ -20,6 +20,7 @@ QML_FILES += \ $$PWD/CheckIndicator.qml \ $$PWD/ComboBox.qml \ $$PWD/Dial.qml \ + $$PWD/DialogButtonBox.qml \ $$PWD/Drawer.qml \ $$PWD/ElevationEffect.qml \ $$PWD/Frame.qml \ diff --git a/src/imports/controls/qtquickcontrols2plugin.cpp b/src/imports/controls/qtquickcontrols2plugin.cpp index b94afd57..fae77543 100644 --- a/src/imports/controls/qtquickcontrols2plugin.cpp +++ b/src/imports/controls/qtquickcontrols2plugin.cpp @@ -137,6 +137,7 @@ void QtQuickControls2Plugin::registerTypes(const char *uri) // QtQuick.Controls 2.1 (Qt 5.8) qmlRegisterType(uri, 2, 1, "ButtonGroup"); + qmlRegisterType(selector.select(QStringLiteral("DialogButtonBox.qml")), uri, 2, 1, "DialogButtonBox"); qmlRegisterType(selector.select(QStringLiteral("Slider.qml")), uri, 2, 1, "Slider"); qmlRegisterType(selector.select(QStringLiteral("StackView.qml")), uri, 2, 1, "StackView"); qmlRegisterType(selector.select(QStringLiteral("SwipeView.qml")), uri, 2, 1, "SwipeView"); diff --git a/src/imports/controls/universal/DialogButtonBox.qml b/src/imports/controls/universal/DialogButtonBox.qml new file mode 100644 index 00000000..2cd4d498 --- /dev/null +++ b/src/imports/controls/universal/DialogButtonBox.qml @@ -0,0 +1,71 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Quick Controls 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$ +** +****************************************************************************/ + +import QtQuick 2.7 +import QtQuick.Templates 2.1 as T +import QtQuick.Controls.Universal 2.0 + +T.DialogButtonBox { + id: control + + implicitWidth: Math.max(background ? background.implicitWidth : 0, + contentItem.implicitWidth + leftPadding + rightPadding) + implicitHeight: Math.max(background ? background.implicitHeight : 0, + contentItem.implicitHeight + topPadding + bottomPadding) + + spacing: 4 + alignment: count === 1 ? Qt.AlignRight : undefined + + delegate: Button { + width: control.count === 1 ? control.availableWidth / 2 : undefined + } + + contentItem: ListView { + implicitWidth: contentWidth + implicitHeight: 32 + + model: control.contentModel + spacing: control.spacing + orientation: ListView.Horizontal + boundsBehavior: Flickable.StopAtBounds + snapMode: ListView.SnapToItem + } + + background: Rectangle { + implicitHeight: 32 + color: control.Universal.chromeMediumLowColor + } +} diff --git a/src/imports/controls/universal/universal.pri b/src/imports/controls/universal/universal.pri index 61b6912a..eb04106a 100644 --- a/src/imports/controls/universal/universal.pri +++ b/src/imports/controls/universal/universal.pri @@ -7,6 +7,7 @@ QML_FILES += \ $$PWD/CheckIndicator.qml \ $$PWD/ComboBox.qml \ $$PWD/Dial.qml \ + $$PWD/DialogButtonBox.qml \ $$PWD/Drawer.qml \ $$PWD/Frame.qml \ $$PWD/GroupBox.qml \ diff --git a/src/imports/templates/qtquicktemplates2plugin.cpp b/src/imports/templates/qtquicktemplates2plugin.cpp index 2d916666..eee27e50 100644 --- a/src/imports/templates/qtquicktemplates2plugin.cpp +++ b/src/imports/templates/qtquicktemplates2plugin.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -174,6 +175,8 @@ void QtQuickTemplates2Plugin::registerTypes(const char *uri) // QtQuick.Controls 2.1 (Qt 5.8) qmlRegisterType(uri, 2, 1, "ButtonGroup"); + qmlRegisterType(uri, 2, 1, "DialogButtonBox"); + qmlRegisterType(); qmlRegisterType(uri, 2, 1, "Slider"); qmlRegisterType(uri, 2, 1, "StackView"); qmlRegisterType(uri, 2, 1, "SwipeView"); diff --git a/src/quicktemplates2/qquickapplicationwindow.cpp b/src/quicktemplates2/qquickapplicationwindow.cpp index 878fe7c7..ae18123d 100644 --- a/src/quicktemplates2/qquickapplicationwindow.cpp +++ b/src/quicktemplates2/qquickapplicationwindow.cpp @@ -42,6 +42,7 @@ #include "qquicktextfield_p.h" #include "qquicktoolbar_p.h" #include "qquicktabbar_p.h" +#include "qquickdialogbuttonbox_p.h" #include #include @@ -314,8 +315,9 @@ void QQuickApplicationWindow::setBackground(QQuickItem *background) This property holds the window header item. The header item is positioned to the top, and resized to the width of the window. The default value is \c null. - \note Assigning a ToolBar or TabBar as a window header sets the respective - \l ToolBar::position or \l TabBar::position property automatically to \c Header. + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a window header + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Header. \sa footer, Page::header */ @@ -348,6 +350,8 @@ void QQuickApplicationWindow::setHeader(QQuickItem *header) toolBar->setPosition(QQuickToolBar::Header); else if (QQuickTabBar *tabBar = qobject_cast(header)) tabBar->setPosition(QQuickTabBar::Header); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast(header)) + buttonBox->setPosition(QQuickDialogButtonBox::Header); } if (isComponentComplete()) d->relayout(); @@ -360,8 +364,9 @@ void QQuickApplicationWindow::setHeader(QQuickItem *header) This property holds the window footer item. The footer item is positioned to the bottom, and resized to the width of the window. The default value is \c null. - \note Assigning a ToolBar or TabBar as a window footer sets the respective - \l ToolBar::position or \l TabBar::position property automatically to \c Footer. + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a window footer + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Footer. \sa header, Page::footer */ @@ -394,6 +399,8 @@ void QQuickApplicationWindow::setFooter(QQuickItem *footer) toolBar->setPosition(QQuickToolBar::Footer); else if (QQuickTabBar *tabBar = qobject_cast(footer)) tabBar->setPosition(QQuickTabBar::Footer); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast(footer)) + buttonBox->setPosition(QQuickDialogButtonBox::Footer); } if (isComponentComplete()) d->relayout(); diff --git a/src/quicktemplates2/qquickdialogbuttonbox.cpp b/src/quicktemplates2/qquickdialogbuttonbox.cpp new file mode 100644 index 00000000..bc05bd7a --- /dev/null +++ b/src/quicktemplates2/qquickdialogbuttonbox.cpp @@ -0,0 +1,703 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 +#include +#include +#include +#include +#include + +#include + +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. For convenience, if the + button has an \c AcceptRole, \c RejectRole, or \c HelpRole, the \l accepted(), + \l rejected(), or \l helpRequested() signals are emitted respectively. +*/ + +/*! + \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() +*/ + +/*! + \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::ButtonRole buttonRole(QQuickAbstractButton *button) +{ + const QQuickDialogButtonBoxAttached *attached = qobject_cast(qmlAttachedPropertiesObject(button, false)); + return attached ? attached->buttonRole() : QPlatformDialogHelper::InvalidRole; +} + +QQuickDialogButtonBoxPrivate::QQuickDialogButtonBoxPrivate() : + flat(false), + alignment(0), + position(QQuickDialogButtonBox::Footer), + standardButtons(QPlatformDialogHelper::NoButton), + delegate(nullptr) +{ +} + +void QQuickDialogButtonBoxPrivate::itemImplicitWidthChanged(QQuickItem *item) +{ + Q_UNUSED(item); + resizeContent(); +} + +void QQuickDialogButtonBoxPrivate::itemImplicitHeightChanged(QQuickItem *item) +{ + Q_UNUSED(item); + resizeContent(); +} + +// 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) + y += rectangle.size().height() / 2 - 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() / 2 - w / 2; + return QRectF(x, y, w, h); +} + +void QQuickDialogButtonBoxPrivate::resizeContent() +{ + Q_Q(QQuickDialogButtonBox); + if (!contentItem) + return; + + const int halign = alignment & Qt::AlignHorizontal_Mask; + const int valign = alignment & Qt::AlignVertical_Mask; + + const qreal cw = !halign ? q->availableWidth() : contentItem->implicitWidth(); + const qreal ch = !valign ? q->availableHeight() : contentItem->implicitWidth(); + + QRectF geometry = q->boundingRect().adjusted(q->leftPadding(), q->topPadding(), -q->rightPadding(), -q->bottomPadding()); + if (halign || valign) + geometry = alignedRect(q->isMirrored() ? Qt::RightToLeft : Qt::LeftToRight, alignment, QSizeF(cw, ch), 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 buttons; + const qreal maxItemWidth = (contentItem->width() - qMax(0, count - 1) * spacing) / count; + const qreal maxItemHeight = contentItem->height(); + + 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(maxItemWidth); + else + item->resetWidth(); + if (!valign) + item->setHeight(maxItemHeight); + else + item->resetHeight(); + p->widthValid = false; + } + } + buttons += static_cast(item); + } + + struct ButtonLayout { + bool operator()(QQuickAbstractButton *first, QQuickAbstractButton *second) + { + const QPlatformDialogHelper::ButtonRole firstRole = buttonRole(first); + const QPlatformDialogHelper::ButtonRole secondRole = buttonRole(second); + + if (firstRole != secondRole && firstRole != QPlatformDialogHelper::InvalidRole && secondRole != QPlatformDialogHelper::InvalidRole) { + const int *l = m_layout; + while (*l != QPlatformDialogHelper::EOL) { + const int role = (*l & ~QPlatformDialogHelper::Reverse); + if (role == firstRole) + return true; + if (role == secondRole) + return false; + ++l; + } + } + + if (firstRole == secondRole) + return first < second; + + return firstRole != QPlatformDialogHelper::InvalidRole; + } + const int *m_layout = QPlatformDialogHelper::buttonLayout(); + }; + + std::sort(buttons.begin(), buttons.end(), ButtonLayout()); + + for (int i = 0; i < buttons.count() - 1; ++i) + q->insertItem(i, buttons.at(i)); +} + +void QQuickDialogButtonBoxPrivate::handleClick() +{ + Q_Q(QQuickDialogButtonBox); + QQuickAbstractButton *button = qobject_cast(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 = buttonRole(button); + QPointer 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::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); + context->setContextObject(q); + + QObject *object = delegate->beginCreate(context); + QQuickAbstractButton *button = qobject_cast(object); + if (button) { + QQuickDialogButtonBoxAttached *attached = qobject_cast(qmlAttachedPropertiesObject(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(q->itemAt(i)); + if (button) { + QQuickDialogButtonBoxAttached *attached = qobject_cast(qmlAttachedPropertiesObject(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) +{ +} + +QQuickDialogButtonBox::~QQuickDialogButtonBox() +{ +} + +/*! + \qmlproperty bool QtQuick.Controls::DialogButtonBox::flat + + This property holds whether the buttons in the button box are flat. + + The default value is \c false. + + \sa Button::flat +*/ +bool QQuickDialogButtonBox::isFlat() const +{ + Q_D(const QQuickDialogButtonBox); + return d->flat; +} + +void QQuickDialogButtonBox::setFlat(bool flat) +{ + Q_D(QQuickDialogButtonBox); + if (flat == d->flat) + return; + + for (int i = 0; i < count(); ++i) { + QQuickButton *button = qobject_cast(itemAt(i)); + if (button) + button->setFlat(flat); + } + + d->flat = flat; + emit flatChanged(); +} + +/*! + \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 + + 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(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_UNUSED(button); + 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); +} + +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(item); +} + +void QQuickDialogButtonBox::itemAdded(int index, QQuickItem *item) +{ + Q_D(QQuickDialogButtonBox); + Q_UNUSED(index); + if (QQuickAbstractButton *button = qobject_cast(item)) + QObjectPrivate::connect(button, &QQuickAbstractButton::clicked, d, &QQuickDialogButtonBoxPrivate::handleClick); + if (QQuickButton *button = qobject_cast(item)) + button->setFlat(d->flat); + if (QQuickDialogButtonBoxAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, false))) + QQuickDialogButtonBoxAttachedPrivate::get(attached)->setButtonBox(this); + if (isComponentComplete()) + polish(); +} + +void QQuickDialogButtonBox::itemRemoved(int index, QQuickItem *item) +{ + Q_D(QQuickDialogButtonBox); + Q_UNUSED(index); + if (QQuickAbstractButton *button = qobject_cast(item)) + QObjectPrivate::disconnect(button, &QQuickAbstractButton::clicked, d, &QQuickDialogButtonBoxPrivate::handleClick); + if (QQuickDialogButtonBoxAttached *attached = qobject_cast(qmlAttachedPropertiesObject(item, false))) + QQuickDialogButtonBoxAttachedPrivate::get(attached)->setButtonBox(nullptr); + if (isComponentComplete()) + polish(); +} + +#ifndef QT_NO_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(parent); + while (parentItem && !d->buttonBox) { + d->buttonBox = qobject_cast(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 diff --git a/src/quicktemplates2/qquickdialogbuttonbox_p.h b/src/quicktemplates2/qquickdialogbuttonbox_p.h new file mode 100644 index 00000000..633cf577 --- /dev/null +++ b/src/quicktemplates2/qquickdialogbuttonbox_p.h @@ -0,0 +1,160 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOGBUTTONBOX_P_H +#define QQUICKDIALOGBUTTONBOX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQmlComponent; +class QQuickAbstractButton; +class QQuickDialogButtonBoxPrivate; +class QQuickDialogButtonBoxAttached; +class QQuickDialogButtonBoxAttachedPrivate; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBox : public QQuickContainer +{ + Q_OBJECT + Q_PROPERTY(bool flat READ isFlat WRITE setFlat NOTIFY flatChanged FINAL) + Q_PROPERTY(Position position READ position WRITE setPosition NOTIFY positionChanged FINAL) + Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment RESET resetAlignment NOTIFY alignmentChanged FINAL) + Q_PROPERTY(QPlatformDialogHelper::StandardButtons standardButtons READ standardButtons WRITE setStandardButtons NOTIFY standardButtonsChanged FINAL) + Q_PROPERTY(QQmlComponent *delegate READ delegate WRITE setDelegate NOTIFY delegateChanged FINAL) + Q_FLAGS(QPlatformDialogHelper::StandardButtons) + +public: + explicit QQuickDialogButtonBox(QQuickItem *parent = nullptr); + ~QQuickDialogButtonBox(); + + bool isFlat() const; + void setFlat(bool flat); + + enum Position { + Header, + Footer + }; + Q_ENUM(Position) + + Position position() const; + void setPosition(Position position); + + Qt::Alignment alignment() const; + void setAlignment(Qt::Alignment alignment); + void resetAlignment(); + + QPlatformDialogHelper::StandardButtons standardButtons() const; + void setStandardButtons(QPlatformDialogHelper::StandardButtons buttons); + Q_INVOKABLE QQuickAbstractButton *standardButton(QPlatformDialogHelper::StandardButton button) const; + + QQmlComponent *delegate() const; + void setDelegate(QQmlComponent *delegate); + + static QQuickDialogButtonBoxAttached *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void accepted(); + void rejected(); + void helpRequested(); + void clicked(QQuickAbstractButton *button); + + void flatChanged(); + void positionChanged(); + void alignmentChanged(); + void standardButtonsChanged(); + void delegateChanged(); + +protected: + void updatePolish() override; + void componentComplete() override; + void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) override; + void contentItemChange(QQuickItem *newItem, QQuickItem *oldItem) override; + bool isContent(QQuickItem *item) const override; + void itemAdded(int index, QQuickItem *item) override; + void itemRemoved(int index, QQuickItem *item) override; + +#ifndef QT_NO_ACCESSIBILITY + QAccessible::Role accessibleRole() const override; +#endif + +private: + Q_DISABLE_COPY(QQuickDialogButtonBox) + Q_DECLARE_PRIVATE(QQuickDialogButtonBox) +}; + +class Q_QUICKTEMPLATES2_PRIVATE_EXPORT QQuickDialogButtonBoxAttached : public QObject +{ + Q_OBJECT + Q_PROPERTY(QQuickDialogButtonBox *buttonBox READ buttonBox NOTIFY buttonBoxChanged FINAL) + Q_PROPERTY(QPlatformDialogHelper::ButtonRole buttonRole READ buttonRole WRITE setButtonRole NOTIFY buttonRoleChanged FINAL) + Q_ENUMS(QPlatformDialogHelper::ButtonRole) + +public: + explicit QQuickDialogButtonBoxAttached(QObject *parent = nullptr); + + QQuickDialogButtonBox *buttonBox() const; + + QPlatformDialogHelper::ButtonRole buttonRole() const; + void setButtonRole(QPlatformDialogHelper::ButtonRole role); + +Q_SIGNALS: + void buttonBoxChanged(); + void buttonRoleChanged(); + +private: + Q_DISABLE_COPY(QQuickDialogButtonBoxAttached) + Q_DECLARE_PRIVATE(QQuickDialogButtonBoxAttached) +}; + +QT_END_NAMESPACE + +QML_DECLARE_TYPE(QQuickDialogButtonBox) +QML_DECLARE_TYPEINFO(QQuickDialogButtonBox, QML_HAS_ATTACHED_PROPERTIES) + +#endif // QQUICKDIALOGBUTTONBOX_P_H diff --git a/src/quicktemplates2/qquickdialogbuttonbox_p_p.h b/src/quicktemplates2/qquickdialogbuttonbox_p_p.h new file mode 100644 index 00000000..b40fb75a --- /dev/null +++ b/src/quicktemplates2/qquickdialogbuttonbox_p_p.h @@ -0,0 +1,108 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QQUICKDIALOGBUTTONBOX_P_P_H +#define QQUICKDIALOGBUTTONBOX_P_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include + +QT_BEGIN_NAMESPACE + +class QQuickDialogButtonBoxPrivate : public QQuickContainerPrivate +{ + Q_DECLARE_PUBLIC(QQuickDialogButtonBox) + +public: + QQuickDialogButtonBoxPrivate(); + + static QQuickDialogButtonBoxPrivate *get(QQuickDialogButtonBox *box) + { + return box->d_func(); + } + + void itemImplicitWidthChanged(QQuickItem *item) override; + void itemImplicitHeightChanged(QQuickItem *item) override; + + void resizeContent() override; + void updateLayout(); + void handleClick(); + + QQuickAbstractButton *createStandardButton(QPlatformDialogHelper::StandardButton button); + void removeStandardButtons(); + + bool flat; + Qt::Alignment alignment; + QQuickDialogButtonBox::Position position; + QPlatformDialogHelper::StandardButtons standardButtons; + QQmlComponent *delegate; +}; + +class QQuickDialogButtonBoxAttachedPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QQuickDialogButtonBoxAttached) + +public: + QQuickDialogButtonBoxAttachedPrivate() : buttonBox(nullptr), + buttonRole(QPlatformDialogHelper::InvalidRole), + standardButton(QPlatformDialogHelper::NoButton) { } + + static QQuickDialogButtonBoxAttachedPrivate *get(QQuickDialogButtonBoxAttached *q) + { + return q->d_func(); + } + + void setButtonBox(QQuickDialogButtonBox *box); + + QQuickDialogButtonBox *buttonBox; + QPlatformDialogHelper::ButtonRole buttonRole; + QPlatformDialogHelper::StandardButton standardButton; +}; + +QT_END_NAMESPACE + +#endif // QQUICKDIALOGBUTTONBOX_P_P_H diff --git a/src/quicktemplates2/qquickpage.cpp b/src/quicktemplates2/qquickpage.cpp index dd9bcc21..56a9f86e 100644 --- a/src/quicktemplates2/qquickpage.cpp +++ b/src/quicktemplates2/qquickpage.cpp @@ -38,6 +38,7 @@ #include "qquickcontrol_p_p.h" #include "qquicktoolbar_p.h" #include "qquicktabbar_p.h" +#include "qquickdialogbuttonbox_p.h" #include @@ -189,8 +190,9 @@ void QQuickPage::setTitle(const QString &title) This property holds the page header item. The header item is positioned to the top, and resized to the width of the page. The default value is \c null. - \note Assigning a ToolBar or TabBar as a page header sets the respective - \l ToolBar::position or \l TabBar::position property automatically to \c Header. + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a page header + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Header. \sa footer, ApplicationWindow::header */ @@ -223,6 +225,8 @@ void QQuickPage::setHeader(QQuickItem *header) toolBar->setPosition(QQuickToolBar::Header); else if (QQuickTabBar *tabBar = qobject_cast(header)) tabBar->setPosition(QQuickTabBar::Header); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast(header)) + buttonBox->setPosition(QQuickDialogButtonBox::Header); } if (isComponentComplete()) d->relayout(); @@ -235,8 +239,9 @@ void QQuickPage::setHeader(QQuickItem *header) This property holds the page footer item. The footer item is positioned to the bottom, and resized to the width of the page. The default value is \c null. - \note Assigning a ToolBar or TabBar as a page footer sets the respective - \l ToolBar::position or \l TabBar::position property automatically to \c Footer. + \note Assigning a ToolBar, TabBar, or DialogButtonBox as a page footer + automatically sets the respective \l ToolBar::position, \l TabBar::position, + or \l DialogButtonBox::position property to \c Footer. \sa header, ApplicationWindow::footer */ @@ -269,6 +274,8 @@ void QQuickPage::setFooter(QQuickItem *footer) toolBar->setPosition(QQuickToolBar::Footer); else if (QQuickTabBar *tabBar = qobject_cast(footer)) tabBar->setPosition(QQuickTabBar::Footer); + else if (QQuickDialogButtonBox *buttonBox = qobject_cast(footer)) + buttonBox->setPosition(QQuickDialogButtonBox::Header); } if (isComponentComplete()) d->relayout(); diff --git a/src/quicktemplates2/quicktemplates2.pri b/src/quicktemplates2/quicktemplates2.pri index 173112d3..df036ed9 100644 --- a/src/quicktemplates2/quicktemplates2.pri +++ b/src/quicktemplates2/quicktemplates2.pri @@ -15,6 +15,8 @@ HEADERS += \ $$PWD/qquickcontrol_p.h \ $$PWD/qquickcontrol_p_p.h \ $$PWD/qquickdial_p.h \ + $$PWD/qquickdialogbuttonbox_p.h \ + $$PWD/qquickdialogbuttonbox_p_p.h \ $$PWD/qquickdrawer_p.h \ $$PWD/qquickframe_p.h \ $$PWD/qquickframe_p_p.h \ @@ -72,6 +74,7 @@ SOURCES += \ $$PWD/qquickcontainer.cpp \ $$PWD/qquickcontrol.cpp \ $$PWD/qquickdial.cpp \ + $$PWD/qquickdialogbuttonbox.cpp \ $$PWD/qquickdrawer.cpp \ $$PWD/qquickframe.cpp \ $$PWD/qquickgroupbox.cpp \ diff --git a/tests/auto/controls/data/tst_dialogbuttonbox.qml b/tests/auto/controls/data/tst_dialogbuttonbox.qml new file mode 100644 index 00000000..296a4e0e --- /dev/null +++ b/tests/auto/controls/data/tst_dialogbuttonbox.qml @@ -0,0 +1,210 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of The Qt Company Ltd nor the names of its +** contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.2 +import QtTest 1.0 +import QtQuick.Controls 2.1 + +TestCase { + id: testCase + width: 200 + height: 200 + visible: true + when: windowShown + name: "DialogButtonBox" + + Component { + id: buttonBox + DialogButtonBox { } + } + + Component { + id: button + Button { } + } + + Component { + id: signalSpy + SignalSpy { } + } + + function test_defaults() { + var control = buttonBox.createObject(testCase) + verify(control) + compare(control.count, 0) + verify(control.delegate) + compare(control.standardButtons, 0) + control.destroy() + } + + function test_standardButtons() { + var control = buttonBox.createObject(testCase) + + compare(control.count, 0) + + control.standardButtons = DialogButtonBox.Ok + compare(control.count, 1) + var okButton = control.itemAt(0) + verify(okButton) + compare(okButton.text.toUpperCase(), "OK") + + control.standardButtons = DialogButtonBox.Cancel + compare(control.count, 1) + var cancelButton = control.itemAt(0) + verify(cancelButton) + compare(cancelButton.text.toUpperCase(), "CANCEL") + + control.standardButtons = DialogButtonBox.Ok | DialogButtonBox.Cancel + compare(control.count, 2) + if (control.itemAt(0).text.toUpperCase() === "OK") { + okButton = control.itemAt(0) + cancelButton = control.itemAt(1) + } else { + okButton = control.itemAt(1) + cancelButton = control.itemAt(0) + } + verify(okButton) + verify(cancelButton) + compare(okButton.text.toUpperCase(), "OK") + compare(cancelButton.text.toUpperCase(), "CANCEL") + + control.standardButtons = 0 + compare(control.count, 0) + + control.destroy() + } + + function test_attached() { + var control = buttonBox.createObject(testCase) + + control.standardButtons = DialogButtonBox.Ok + var okButton = control.itemAt(0) + compare(okButton.DialogButtonBox.buttonBox, control) + compare(okButton.DialogButtonBox.buttonRole, DialogButtonBox.AcceptRole) + + var saveButton = button.createObject(control, {text: "Save"}) + compare(saveButton.DialogButtonBox.buttonBox, control) + compare(saveButton.DialogButtonBox.buttonRole, DialogButtonBox.InvalidRole) + saveButton.DialogButtonBox.buttonRole = DialogButtonBox.AcceptRole + compare(saveButton.DialogButtonBox.buttonRole, DialogButtonBox.AcceptRole) + + var closeButton = button.createObject(null, {text: "Save"}) + compare(closeButton.DialogButtonBox.buttonBox, null) + compare(closeButton.DialogButtonBox.buttonRole, DialogButtonBox.InvalidRole) + closeButton.DialogButtonBox.buttonRole = DialogButtonBox.DestructiveRole + compare(closeButton.DialogButtonBox.buttonRole, DialogButtonBox.DestructiveRole) + control.addItem(closeButton) + compare(closeButton.DialogButtonBox.buttonBox, control) + + control.contentModel.clear() + compare(okButton.DialogButtonBox.buttonBox, null) + compare(saveButton.DialogButtonBox.buttonBox, null) + compare(closeButton.DialogButtonBox.buttonBox, null) + + control.destroy() + } + + function test_flat() { + var control = buttonBox.createObject(testCase) + + control.flat = true + compare(control.flat, true) + + control.standardButtons = DialogButtonBox.Ok | DialogButtonBox.Cancel | DialogButtonBox.Apply + button.createObject(control, {text: "Custom"}) + compare(control.count, 4) + + for (var i = 0; i < control.count; ++i) + compare(control.itemAt(i).flat, true) + + control.flat = false + compare(control.flat, false) + + for (var j = 0; j < control.count; ++j) + compare(control.itemAt(j).flat, false) + + control.destroy() + } + + function test_signals_data() { + return [ + { tag: "Ok", standardButton: DialogButtonBox.Ok, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Open", standardButton: DialogButtonBox.Open, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Save", standardButton: DialogButtonBox.Save, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Cancel", standardButton: DialogButtonBox.Cancel, buttonRole: DialogButtonBox.RejectRole, signalName: "rejected" }, + { tag: "Close", standardButton: DialogButtonBox.Close, buttonRole: DialogButtonBox.RejectRole, signalName: "rejected" }, + { tag: "Discard", standardButton: DialogButtonBox.Discard, buttonRole: DialogButtonBox.DestructiveRole }, + { tag: "Apply", standardButton: DialogButtonBox.Apply, buttonRole: DialogButtonBox.ApplyRole }, + { tag: "Reset", standardButton: DialogButtonBox.Reset, buttonRole: DialogButtonBox.ResetRole }, + { tag: "RestoreDefaults", standardButton: DialogButtonBox.RestoreDefaults, buttonRole: DialogButtonBox.ResetRole }, + { tag: "Help", standardButton: DialogButtonBox.Help, buttonRole: DialogButtonBox.HelpRole, signalName: "helpRequested" }, + { tag: "SaveAll", standardButton: DialogButtonBox.SaveAll, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Yes", standardButton: DialogButtonBox.Yes, buttonRole: DialogButtonBox.YesRole, signalName: "accepted" }, + { tag: "YesToAll", standardButton: DialogButtonBox.YesToAll, buttonRole: DialogButtonBox.YesRole, signalName: "accepted" }, + { tag: "No", standardButton: DialogButtonBox.No, buttonRole: DialogButtonBox.NoRole, signalName: "rejected" }, + { tag: "NoToAll", standardButton: DialogButtonBox.NoToAll, buttonRole: DialogButtonBox.NoRole, signalName: "rejected" }, + { tag: "Abort", standardButton: DialogButtonBox.Abort, buttonRole: DialogButtonBox.RejectRole, signalName: "rejected" }, + { tag: "Retry", standardButton: DialogButtonBox.Retry, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" }, + { tag: "Ignore", standardButton: DialogButtonBox.Ignore, buttonRole: DialogButtonBox.AcceptRole, signalName: "accepted" } + ] + } + + function test_signals(data) { + var control = buttonBox.createObject(testCase) + + control.standardButtons = data.standardButton + compare(control.count, 1) + var button = control.itemAt(0) + verify(button) + compare(button.DialogButtonBox.buttonRole, data.buttonRole) + + var clickedSpy = signalSpy.createObject(control, {target: control, signalName: "clicked"}) + verify(clickedSpy.valid) + var roleSpy = signalSpy.createObject(control, {target: control, signalName: data.signalName}) + compare(roleSpy.valid, !!data.signalName) + + button.clicked() + compare(clickedSpy.count, 1) + compare(clickedSpy.signalArguments[0][0], button) + compare(roleSpy.count, !!data.signalName ? 1 : 0) + + control.destroy() + } +} -- cgit v1.2.3