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