Warning, /frameworks/kirigami/src/controls/Dialog.qml is written in an unsupported language. File is not indexed.

0001 /*
0002     SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
0003     SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
0004     SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 import QtQuick 2.15
0009 import QtQml 2.15
0010 import QtQuick.Layouts 1.15
0011 import QtQuick.Templates 2.15 as T
0012 import QtQuick.Controls 2.15 as QQC2
0013 import org.kde.kirigami 2.12 as Kirigami
0014 import QtGraphicalEffects 1.12 as GE
0015 
0016 /**
0017  * @brief Popup dialog that is used for short tasks and user interaction.
0018  *
0019  * Dialog consists of three components:
0020  * the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#header-prop">header</a>,
0021  * the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#contentItem-prop">contentItem</a>,
0022  * and the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#footer-prop">footer</a>.
0023  *
0024  * By default, the header is a heading with text specified by the
0025  * <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#title-prop">title</a> property.
0026  *
0027  * By default, the footer consists of a row of buttons specified by
0028  * the <a href="https://doc.qt.io/qt-5/qml-qtquick-controls2-dialog.html#standardButtons-prop">standardButtons</a>
0029  * and ::customFooterActions properties.
0030  *
0031  * The `implicitHeight` and `implicitWidth` of the dialog contentItem is
0032  * the primary hint used for the dialog size. The dialog will be the
0033  * minimum size required for the header, footer and content unless
0034  * it is larger than `maximumHeight` and `maximumWidth`. Use
0035  * `preferredHeight` and `preferredWidth` in order to manually specify
0036  * a size for the dialog.
0037  *
0038  * If the content height exceeds the maximum height of the dialog, the
0039  * dialog's contents will become scrollable.
0040  *
0041  * If the contentItem is a QtQuick.ListView,
0042  * the dialog will take care of the necessary scrollbars and scrolling behaviour.
0043  * Do @b not attempt
0044  * to nest ListViews (it must be the top level item), as the scrolling
0045  * behaviour will not be handled. Use ListView's `header` and `footer` instead.
0046  *
0047  * Example for a selection dialog:
0048  * @code{.qml}
0049  * import QtQuick 2.15
0050  * import QtQuick.Layouts 1.15
0051  * import QtQuick.Controls 2.15 as Controls
0052  * import org.kde.kirigami 2.19 as Kirigami
0053  *
0054  * Kirigami.Dialog {
0055  *     title: i18n("Dialog")
0056  *     padding: 0
0057  *     preferredWidth: Kirigami.Units.gridUnit * 16
0058  *
0059  *     standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
0060  *
0061  *     onAccepted: console.log("OK button pressed")
0062  *     onRejected: console.log("Rejected")
0063  *
0064  *     ColumnLayout {
0065  *         spacing: 0
0066  *         Repeater {
0067  *             model: 5
0068  *             delegate: Controls.CheckDelegate {
0069  *                 topPadding: Kirigami.Units.smallSpacing * 2
0070  *                 bottomPadding: Kirigami.Units.smallSpacing * 2
0071  *                 Layout.fillWidth: true
0072  *                 text: modelData
0073  *             }
0074  *         }
0075  *     }
0076  * }
0077  * @endcode
0078  *
0079  * Example with scrolling (ListView scrolling behaviour is handled by the Dialog):
0080  * @code{.qml}
0081  * Kirigami.Dialog {
0082  *     id: scrollableDialog
0083  *     title: i18n("Select Number")
0084  *
0085  *     ListView {
0086  *         id: listView
0087  *         // hints for the dialog dimensions
0088  *         implicitWidth: Kirigami.Units.gridUnit * 16
0089  *         implicitHeight: Kirigami.Units.gridUnit * 16
0090  *
0091  *         model: 100
0092  *         delegate: Controls.RadioDelegate {
0093  *             topPadding: Kirigami.Units.smallSpacing * 2
0094  *             bottomPadding: Kirigami.Units.smallSpacing * 2
0095  *             implicitWidth: listView.width
0096  *             text: modelData
0097  *         }
0098  *     }
0099  * }
0100  * @endcode
0101  *
0102  * There are also sub-components of the Dialog that target specific usecases,
0103  * and can reduce boilerplate code if used:
0104  *
0105  * @see kirigami::PromptDialog
0106  * @see kirigami::MenuDialog
0107  * @see <a href="https://develop.kde.org/hig/components/navigation/dialog">Human Interface Guidelines on Dialogs</a>
0108  * @see <a href="https://develop.kde.org/hig/components/assistance/message">Human Interface Guidelines on Modal Message Dialogs</a>
0109  * @inherit QtQuick.QtObject
0110  */
0111 T.Dialog {
0112     id: root
0113 
0114     // TODO KF6: remove this property
0115     /**
0116      * @deprecated This property will be removed in the next major frameworks release (KF6)
0117      */
0118     property Item mainItem: contentControl.contentChildren.length > 0 ? contentControl.contentChildren[0] : null
0119 
0120     /**
0121      * @brief This property holds the dialog's contents; includes Items and QtObjects.
0122      * @property list<QtObject> dialogData
0123      */
0124     default property alias dialogData: contentControl.contentData
0125 
0126     /**
0127      * @brief This property holds the content items of the dialog.
0128      *
0129      * The initial height and width of the dialog is calculated from the
0130      * `implicitWidth` and `implicitHeight` of the content.
0131      *
0132      * @property list<Item> dialogChildren
0133      */
0134     property alias dialogChildren: contentControl.contentChildren
0135 
0136     /**
0137      * @brief This property sets the absolute maximum height the dialog can have.
0138      *
0139      * The height restriction is solely applied on the content, so if the
0140      * maximum height given is not larger than the height of the header and
0141      * footer, it will be ignored.
0142      *
0143      * This is the window height, subtracted by Kirigami.Units.largeSpacing on both the top
0144      * and bottom.
0145      * 
0146      * @see maximumHeight
0147      */
0148     readonly property real absoluteMaximumHeight: parent.height - Kirigami.Units.largeSpacing * 2
0149 
0150     /**
0151      * @brief This property holds the absolute maximum width the dialog can have.
0152      *
0153      * By default, it is the window width, subtracted by Kirigami.Units.largeSpacing on both
0154      * the top and bottom.
0155      * 
0156      * @see maximumWidth
0157      */
0158     readonly property real absoluteMaximumWidth: parent.width - Kirigami.Units.largeSpacing * 2
0159 
0160     /**
0161      * @brief This property holds the maximum height the dialog can have
0162      * (including the header and footer).
0163      *
0164      * The height restriction is solely enforced on the content, so if the
0165      * maximum height given is not larger than the height of the header and
0166      * footer, it will be ignored.
0167      *
0168      * By default, this is ::absoluteMaximumHeight.
0169      */
0170     property real maximumHeight: absoluteMaximumHeight
0171 
0172     /**
0173      * @brief This property holds the maximum width the dialog can have.
0174      *
0175      * By default, this is ::absoluteMaximumWidth.
0176      */
0177     property real maximumWidth: absoluteMaximumWidth
0178 
0179     /**
0180      * @brief This property holds the preferred height of the dialog.
0181      *
0182      * The content will receive a hint for how tall it should be to have
0183      * the dialog to be this height.
0184      *
0185      * If the content, header or footer require more space, then the height
0186      * of the dialog will expand to the necessary amount of space.
0187      */
0188     property real preferredHeight: -1
0189 
0190     /**
0191      * @brief This property holds the preferred width of the dialog.
0192      *
0193      * The content will receive a hint for how wide it should be to have
0194      * the dialog be this wide.
0195      *
0196      * If the content, header or footer require more space, then the width
0197      * of the dialog will expand to the necessary amount of space.
0198      */
0199     property real preferredWidth: -1
0200 
0201 
0202     /**
0203      * @brief This property holds the component to the left of the footer buttons.
0204      */
0205     property Component footerLeadingComponent
0206 
0207     /**
0208      * @brief his property holds the component to the right of the footer buttons.
0209      */
0210     property Component footerTrailingComponent
0211 
0212     /**
0213      * @brief This property sets whether to show the close button in the header.
0214      */
0215     property bool showCloseButton: true
0216 
0217     /**
0218      * @brief This property sets whether the footer button style should be flat.
0219      */
0220     property bool flatFooterButtons: false
0221 
0222     /**
0223      * @brief This property holds the custom actions displayed in the footer.
0224      *
0225      * Example usage:
0226      * @code{.qml}
0227      * import QtQuick 2.15
0228      * import QtQuick.Controls 2.15 as Controls
0229      * import org.kde.kirigami 2.18 as Kirigami
0230      *
0231      * Kirigami.PromptDialog {
0232      *     id: dialog
0233      *     title: i18n("Confirm Playback")
0234      *     subtitle: i18n("Are you sure you want to play this song? It's really loud!")
0235      *
0236      *     standardButtons: Kirigami.Dialog.Cancel
0237      *     customFooterActions: [
0238      *         Kirigami.Action {
0239      *             text: i18n("Play")
0240      *             iconName: "media-playback-start"
0241      *             onTriggered: {
0242      *                 //...
0243      *                 dialog.close();
0244      *             }
0245      *         }
0246      *     ]
0247      * }
0248      * @endcode
0249      * @see kirigami::Action
0250      */
0251     property list<Kirigami.Action> customFooterActions
0252 
0253     // default standard button
0254     standardButtons: QQC2.Dialog.Close
0255 
0256     function standardButton(button): T.AbstractButton {
0257         // in case a footer is redefined
0258         if (footer instanceof T.DialogButtonBox) {
0259             return footer.standardButton(button);
0260         } else if (footer === footerToolBar) {
0261             return dialogButtonBox.standardButton(button);
0262         } else {
0263             return null;
0264         }
0265     }
0266 
0267     // calculate dimensions
0268     implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding // maximum width enforced from our content (one source of truth) to avoid binding loops
0269     implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
0270                     + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
0271                     + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0);
0272 
0273     // misc. dialog settings
0274     closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnReleaseOutside
0275     modal: true
0276     clip: false
0277     padding: 0
0278 
0279     // determine parent so that popup knows which window to popup in
0280     // we want to open the dialog in the center of the window, if possible
0281     Component.onCompleted: {
0282         if (typeof applicationWindow !== "undefined") {
0283             parent = applicationWindow().overlay;
0284         }
0285     }
0286 
0287     // center dialog
0288     x: Math.round((parent.width - width) / 2)
0289     y: Math.round((parent.height - height) / 2) + Kirigami.Units.gridUnit * 2 * (1 - opacity) // move animation
0290 
0291     // dialog enter and exit transitions
0292     enter: Transition {
0293         NumberAnimation { property: "opacity"; from: 0; to: 1; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
0294     }
0295     exit: Transition {
0296         NumberAnimation { property: "opacity"; from: 1; to: 0; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
0297     }
0298 
0299     // black background, fades in and out
0300     QQC2.Overlay.modal: Rectangle {
0301         color: Qt.rgba(0, 0, 0, 0.3)
0302 
0303         // the opacity of the item is changed internally by QQuickPopup on open/close
0304         Behavior on opacity {
0305             OpacityAnimator {
0306                 duration: Kirigami.Units.longDuration
0307                 easing.type: Easing.InOutQuad
0308             }
0309         }
0310     }
0311 
0312     // dialog view background
0313     background: Item {
0314         GE.RectangularGlow {
0315             anchors.fill: rect
0316             anchors.topMargin: 1
0317             cornerRadius: rect.radius * 2
0318             glowRadius: 2
0319             spread: 0.2
0320             color: Qt.rgba(0, 0, 0, 0.3)
0321         }
0322 
0323         Rectangle {
0324             id: rect
0325             anchors.fill: parent
0326             Kirigami.Theme.colorSet: Kirigami.Theme.View
0327             Kirigami.Theme.inherit: false
0328             color: Kirigami.Theme.backgroundColor
0329             radius: Kirigami.Units.smallSpacing
0330         }
0331     }
0332 
0333     // dialog content
0334     contentItem: ColumnLayout {
0335         QQC2.ScrollView {
0336             id: contentControl
0337 
0338             // ensure view colour scheme, and background color
0339             Kirigami.Theme.inherit: false
0340             Kirigami.Theme.colorSet: Kirigami.Theme.View
0341 
0342             QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
0343 
0344             // height of everything else in the dialog other than the content
0345             property real otherHeights: root.header.height + root.footer.height + root.topPadding + root.bottomPadding;
0346 
0347             property real calculatedMaximumWidth: Math.min(root.absoluteMaximumWidth, root.maximumWidth) - root.leftPadding - root.rightPadding
0348             property real calculatedMaximumHeight: Math.min(root.absoluteMaximumHeight, root.maximumHeight) - root.topPadding - root.bottomPadding
0349             property real calculatedImplicitWidth: (contentChildren.length === 1 && contentChildren[0].implicitWidth > 0
0350                 ? contentChildren[0].implicitWidth
0351                 : (contentItem.implicitWidth > 0 ? contentItem.implicitWidth : contentItem.width)) + leftPadding + rightPadding
0352             property real calculatedImplicitHeight: (contentChildren.length === 1 && contentChildren[0].implicitHeight > 0
0353                 ? contentChildren[0].implicitHeight
0354                 : (contentItem.implicitHeight > 0 ? contentItem.implicitHeight : contentItem.height)) + topPadding + bottomPadding
0355 
0356             // how do we deal with the scrollbar width?
0357             // - case 1: the dialog itself has the preferredWidth set
0358             //   -> we hint a width to the content so it shrinks to give space to the scrollbar
0359             // - case 2: preferredWidth not set, so we are using the content's implicit width
0360             //   -> we expand the dialog's width to accommodate the scrollbar width (to respect the content's desired width)
0361 
0362             // don't enforce preferred width and height if not set
0363             Layout.preferredWidth: (root.preferredWidth >= 0 ? root.preferredWidth : calculatedImplicitWidth)
0364             Layout.preferredHeight: root.preferredHeight >= 0 ? root.preferredHeight - otherHeights : calculatedImplicitHeight
0365 
0366             Layout.fillWidth: true
0367             Layout.maximumWidth: calculatedMaximumWidth
0368             Layout.maximumHeight: calculatedMaximumHeight - otherHeights // we enforce maximum height solely from the content
0369 
0370             // give an implied width and height to the contentItem so that features like word wrapping/eliding work
0371             // cannot placed directly in contentControl as a child, so we must use a property
0372             property var widthHint: Binding {
0373                 target: contentControl.contentChildren[0] || null
0374                 property: "width"
0375 
0376                 // we want to avoid horizontal scrolling, so we apply maximumWidth as a hint if necessary
0377                 property real preferredWidthHint: contentControl.contentItem.width
0378                 property real maximumWidthHint: contentControl.calculatedMaximumWidth - contentControl.leftPadding - contentControl.rightPadding
0379 
0380                 value: Math.min(maximumWidthHint, preferredWidthHint)
0381 
0382                 restoreMode: Binding.RestoreBinding
0383             }
0384         }
0385     }
0386 
0387     header: T.Control {
0388         implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
0389                                 implicitContentWidth + leftPadding + rightPadding)
0390         implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
0391                                 implicitContentHeight + topPadding + bottomPadding)
0392 
0393         padding: Kirigami.Units.largeSpacing
0394         bottomPadding: verticalPadding + headerSeparator.implicitHeight // add space for bottom separator
0395 
0396         contentItem: RowLayout {
0397             Kirigami.Heading {
0398                 id: heading
0399                 Layout.fillWidth: true
0400                 Layout.alignment: Qt.AlignVCenter
0401                 level: 2
0402                 text: root.title === "" ? " " : root.title // always have text to ensure header height
0403                 elide: Text.ElideRight
0404 
0405                 // use tooltip for long text that is elided
0406                 QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
0407                 QQC2.ToolTip.text: root.title
0408                 HoverHandler { id: titleHoverHandler }
0409             }
0410             Kirigami.Icon {
0411                 id: closeIcon
0412                 visible: root.showCloseButton
0413 
0414                 // We want to position the close button in the top-right
0415                 // corner if the header is very tall, but we want to
0416                 // vertically center it in a short header
0417                 readonly property bool tallHeader: parent.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing + Kirigami.Units.largeSpacing)
0418                 Layout.alignment: tallHeader ? Qt.AlignRight | Qt.AlignTop : Qt.AlignRight | Qt.AlignVCenter
0419                 Layout.topMargin: tallHeader ? Kirigami.Units.largeSpacing : 0
0420                 implicitHeight: Kirigami.Units.iconSizes.smallMedium
0421                 implicitWidth: implicitHeight
0422 
0423                 source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
0424                 active: closeMouseArea.containsMouse
0425                 MouseArea {
0426                     id: closeMouseArea
0427                     hoverEnabled: Qt.styleHints.useHoverEffects
0428                     anchors.fill: parent
0429                     onClicked: mouse => root.reject()
0430                 }
0431             }
0432         }
0433 
0434         // header background
0435         background: Kirigami.ShadowedRectangle {
0436             corners.topLeftRadius: Kirigami.Units.smallSpacing
0437             corners.topRightRadius: Kirigami.Units.smallSpacing
0438             Kirigami.Theme.colorSet: Kirigami.Theme.Header
0439             Kirigami.Theme.inherit: false
0440             color: Kirigami.Theme.backgroundColor
0441             Kirigami.Separator {
0442                 id: headerSeparator
0443                 width: parent.width
0444                 anchors.bottom: parent.bottom
0445             }
0446         }
0447     }
0448 
0449     // use top level control rather than toolbar, since toolbar causes button rendering glitches
0450     footer: T.Control {
0451         id: footerToolBar
0452 
0453         // if there is nothing in the footer, still maintain a height so that we can create a rounded bottom buffer for the dialog
0454         property bool bufferMode: contentItem.implicitHeight === 0
0455         implicitHeight: bufferMode ? Kirigami.Units.smallSpacing : contentItem.implicitHeight
0456 
0457         leftPadding: 0; rightPadding: 0; bottomPadding: 0
0458         topPadding: bufferMode ? 0 : footerSeparator.implicitHeight // add space for the separator above the footer
0459 
0460         contentItem: RowLayout {
0461             spacing: parent.spacing
0462             // Don't let user interact with footer during transitions
0463             enabled: root.opened
0464 
0465             Loader {
0466                 id: leadingLoader
0467                 sourceComponent: root.footerLeadingComponent
0468             }
0469 
0470             // footer buttons
0471             QQC2.DialogButtonBox {
0472                 // we don't explicitly set padding, to let the style choose the padding
0473                 id: dialogButtonBox
0474                 standardButtons: root.standardButtons
0475                 visible: count > 0
0476 
0477                 Layout.fillWidth: true
0478                 Layout.alignment: dialogButtonBox.alignment
0479 
0480                 position: QQC2.DialogButtonBox.Footer
0481 
0482                 // ensure themes don't add a background, since it can lead to visual inconsistencies
0483                 // with the rest of the dialog
0484                 background: null
0485 
0486                 // we need to hook all of the buttonbox events to the dialog events
0487                 onAccepted: root.accept()
0488                 onRejected: root.reject()
0489                 onApplied: root.applied()
0490                 onDiscarded: root.discarded()
0491                 onHelpRequested: root.helpRequested()
0492                 onReset: root.reset()
0493 
0494                 // add custom footer buttons
0495                 Repeater {
0496                     model: root.customFooterActions
0497                     // we have to use Button instead of ToolButton, because ToolButton has no visual distinction when disabled
0498                     delegate: QQC2.Button {
0499                         flat: flatFooterButtons
0500                         action: modelData
0501                         visible: modelData.visible
0502                     }
0503                 }
0504             }
0505 
0506             Loader {
0507                 id: trailingLoader
0508                 sourceComponent: root.footerTrailingComponent
0509             }
0510         }
0511 
0512         background: Kirigami.ShadowedRectangle {
0513             // curved footer bottom corners
0514             corners.bottomLeftRadius: Kirigami.Units.smallSpacing
0515             corners.bottomRightRadius: Kirigami.Units.smallSpacing
0516 
0517             // we act as a content buffer if nothing is in the footer
0518             Kirigami.Theme.colorSet: footerToolBar.bufferMode ? Kirigami.Theme.View : Kirigami.Theme.Window
0519             Kirigami.Theme.inherit: false
0520             color: Kirigami.Theme.backgroundColor
0521 
0522             // separator above footer
0523             Kirigami.Separator {
0524                 id: footerSeparator
0525                 visible: !footerToolBar.bufferMode
0526                 width: parent.width
0527                 anchors.top: parent.top
0528             }
0529         }
0530     }
0531 }