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 }