Kirigami2

Dialog.qml
1 /*
2  SPDX-FileCopyrightText: 2021 Devin Lin <[email protected]>
3  SPDX-FileCopyrightText: 2021 Noah Davis <[email protected]>
4  SPDX-FileCopyrightText: 2022 ivan tkachenko <[email protected]>
5  SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7 
8 import QtQuick 2.15
9 import QtQml 2.15
10 import QtQuick.Layouts 1.15
11 import QtQuick.Templates 2.15 as T
12 import QtQuick.Controls 2.15 as QQC2
13 import org.kde.kirigami 2.12 as Kirigami
14 import QtGraphicalEffects 1.12 as GE
15 
16 /**
17  * @brief Popup dialog that is used for short tasks and user interaction.
18  *
19  * Dialog consists of three components: the header, the content,
20  * and the footer.
21  *
22  * By default, the header is a heading with text specified by the
23  * `title` property.
24  *
25  * By default, the footer consists of a row of buttons specified by
26  * the `standardButtons` and `customFooterActions` properties.
27  *
28  * The `implicitHeight` and `implicitWidth` of the dialog contentItem is
29  * the primary hint used for the dialog size. The dialog will be the
30  * minimum size required for the header, footer and content unless
31  * it is larger than `maximumHeight` and `maximumWidth`. Use
32  * `preferredHeight` and `preferredWidth` in order to manually specify
33  * a size for the dialog.
34  *
35  * If the content height exceeds the maximum height of the dialog, the
36  * dialog's contents will become scrollable.
37  *
38  * If the contentItem is a <b>ListView</b>, the dialog will take care of the
39  * necessary scrollbars and scrolling behaviour. Do <b>not</b> attempt
40  * to nest ListViews (it must be the top level item), as the scrolling
41  * behaviour will not be handled. Use ListView's `header` and `footer` instead.
42  *
43  * Example for a selection dialog:
44  *
45  * @code{.qml}
46  * import QtQuick 2.15
47  * import QtQuick.Layouts 1.15
48  * import QtQuick.Controls 2.15 as Controls
49  * import org.kde.kirigami 2.19 as Kirigami
50  *
51  * Kirigami.Dialog {
52  * title: i18n("Dialog")
53  * padding: 0
54  * preferredWidth: Kirigami.Units.gridUnit * 16
55  *
56  * standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
57  *
58  * onAccepted: console.log("OK button pressed")
59  * onRejected: console.log("Rejected")
60  *
61  * ColumnLayout {
62  * spacing: 0
63  * Repeater {
64  * model: 5
65  * delegate: Controls.CheckDelegate {
66  * topPadding: Kirigami.Units.smallSpacing * 2
67  * bottomPadding: Kirigami.Units.smallSpacing * 2
68  * Layout.fillWidth: true
69  * text: modelData
70  * }
71  * }
72  * }
73  * }
74  * @endcode
75  *
76  * Example with scrolling (ListView scrolling behaviour is handled by the Dialog):
77  *
78  * @code{.qml}
79  * Kirigami.Dialog {
80  * id: scrollableDialog
81  * title: i18n("Select Number")
82  *
83  * ListView {
84  * id: listView
85  * // hints for the dialog dimensions
86  * implicitWidth: Kirigami.Units.gridUnit * 16
87  * implicitHeight: Kirigami.Units.gridUnit * 16
88  *
89  * model: 100
90  * delegate: Controls.RadioDelegate {
91  * topPadding: Kirigami.Units.smallSpacing * 2
92  * bottomPadding: Kirigami.Units.smallSpacing * 2
93  * implicitWidth: listView.width
94  * text: modelData
95  * }
96  * }
97  * }
98  * @endcode
99  *
100  * There are also sub-components of the Dialog that target specific usecases,
101  * and can reduce boilerplate code if used:
102  *
103  * @see PromptDialog
104  * @see MenuDialog
105  *
106  * @inherit QtQuick.QtObject
107  */
108 T.Dialog {
109  id: root
110 
111  // TODO KF6: remove this property
112  /**
113  * @deprecated This property will be removed in the next major frameworks release (KF6)
114  */
115  property Item mainItem: contentControl.contentChildren.length > 0 ? contentControl.contentChildren[0] : null
116 
117  /**
118  * @brief This property holds the dialog's contents; includes Items and QtObjects.
119  * @property list<QtObject> dialogData
120  */
121  default property alias dialogData: contentControl.contentData
122 
123  /**
124  * @brief This property holds the content items of the dialog.
125  *
126  * The initial height and width of the dialog is calculated from the
127  * `implicitWidth` and `implicitHeight` of the content.
128  *
129  * @property list<Item> dialogChildren
130  */
131  property alias dialogChildren: contentControl.contentChildren
132 
133  /**
134  * @brief This property sets the absolute maximum height the dialog can have.
135  *
136  * The height restriction is solely applied on the content, so if the
137  * maximum height given is not larger than the height of the header and
138  * footer, it will be ignored.
139  *
140  * This is the window height, subtracted by largeSpacing on both the top
141  * and bottom.
142  */
143  readonly property real absoluteMaximumHeight: parent.height - Kirigami.Units.largeSpacing * 2
144 
145  /**
146  * @brief This property holds the absolute maximum width the dialog can have.
147  *
148  * By default, it is the window width, subtracted by largeSpacing on both
149  * the top and bottom.
150  */
151  readonly property real absoluteMaximumWidth: parent.width - Kirigami.Units.largeSpacing * 2
152 
153  /**
154  * @brief This property holds the maximum height the dialog can have
155  * (including the header and footer).
156  *
157  * The height restriction is solely enforced on the content, so if the
158  * maximum height given is not larger than the height of the header and
159  * footer, it will be ignored.
160  *
161  * By default, this is `absoluteMaximumHeight`.
162  */
163  property real maximumHeight: absoluteMaximumHeight
164 
165  /**
166  * @brief This property holds the maximum width the dialog can have.
167  *
168  * By default, this is `absoluteMaximumWidth`.
169  */
170  property real maximumWidth: absoluteMaximumWidth
171 
172  /**
173  * @brief This property holds the preferred height of the dialog.
174  *
175  * The content will receive a hint for how tall it should be to have
176  * the dialog to be this height.
177  *
178  * If the content, header or footer require more space, then the height
179  * of the dialog will expand to the necessary amount of space.
180  */
181  property real preferredHeight: -1
182 
183  /**
184  * @brief This property holds the preferred width of the dialog.
185  *
186  * The content will receive a hint for how wide it should be to have
187  * the dialog be this wide.
188  *
189  * If the content, header or footer require more space, then the width
190  * of the dialog will expand to the necessary amount of space.
191  */
192  property real preferredWidth: -1
193 
194 
195  /**
196  * @brief This property holds the component to the left of the footer buttons.
197  */
198  property Component footerLeadingComponent
199 
200  /**
201  * @brief his property holds the component to the right of the footer buttons.
202  */
203  property Component footerTrailingComponent
204 
205  /**
206  * @brief This property sets whether to show the close button in the header.
207  */
208  property bool showCloseButton: true
209 
210  /**
211  * @brief This property sets whether the footer button style should be flat.
212  */
213  property bool flatFooterButtons: false
214 
215  /**
216  * @brief This property holds the custom actions displayed in the footer.
217  *
218  * Example usage:
219  * @code{.qml}
220  * import QtQuick 2.15
221  * import QtQuick.Controls 2.15 as Controls
222  * import org.kde.kirigami 2.18 as Kirigami
223  *
224  * Kirigami.PromptDialog {
225  * id: dialog
226  * title: i18n("Confirm Playback")
227  * subtitle: i18n("Are you sure you want to play this song? It's really loud!")
228  *
229  * standardButtons: Kirigami.Dialog.Cancel
230  * customFooterActions: [
231  * Kirigami.Action {
232  * text: i18n("Play")
233  * iconName: "media-playback-start"
234  * onTriggered: {
235  * //...
236  * dialog.close();
237  * }
238  * }
239  * ]
240  * }
241  * @endcode
242  *
243  * @see org::kde::kirigami::Action
244  */
245  property list<Kirigami.Action> customFooterActions
246 
247  // default standard button
248  standardButtons: QQC2.Dialog.Close
249 
250  function standardButton(button): T.AbstractButton {
251  // in case a footer is redefined
252  if (footer instanceof T.DialogButtonBox) {
253  return footer.standardButton(button);
254  } else if (footer === footerToolBar) {
255  return dialogButtonBox.standardButton(button);
256  } else {
257  return null;
258  }
259  }
260 
261  // calculate dimensions
262  implicitWidth: contentItem.implicitWidth + leftPadding + rightPadding // maximum width enforced from our content (one source of truth) to avoid binding loops
263  implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
264  + (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
265  + (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0);
266 
267  // misc. dialog settings
268  closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnReleaseOutside
269  modal: true
270  clip: false
271  padding: 0
272 
273  // determine parent so that popup knows which window to popup in
274  // we want to open the dialog in the center of the window, if possible
275  Component.onCompleted: {
276  if (typeof applicationWindow !== "undefined") {
277  parent = applicationWindow().overlay;
278  }
279  }
280 
281  // center dialog
282  x: Math.round((parent.width - width) / 2)
283  y: Math.round((parent.height - height) / 2) + Kirigami.Units.gridUnit * 2 * (1 - opacity) // move animation
284 
285  // dialog enter and exit transitions
286  enter: Transition {
287  NumberAnimation { property: "opacity"; from: 0; to: 1; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
288  }
289  exit: Transition {
290  NumberAnimation { property: "opacity"; from: 1; to: 0; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
291  }
292 
293  // black background, fades in and out
294  QQC2.Overlay.modal: Rectangle {
295  color: Qt.rgba(0, 0, 0, 0.3)
296 
297  // the opacity of the item is changed internally by QQuickPopup on open/close
298  Behavior on opacity {
299  OpacityAnimator {
300  duration: Kirigami.Units.longDuration
301  easing.type: Easing.InOutQuad
302  }
303  }
304  }
305 
306  // dialog view background
307  background: Item {
308  GE.RectangularGlow {
309  anchors.fill: rect
310  anchors.topMargin: 1
311  cornerRadius: rect.radius * 2
312  glowRadius: 2
313  spread: 0.2
314  color: Qt.rgba(0, 0, 0, 0.3)
315  }
316 
317  Rectangle {
318  id: rect
319  anchors.fill: parent
320  Kirigami.Theme.colorSet: Kirigami.Theme.View
321  Kirigami.Theme.inherit: false
322  color: Kirigami.Theme.backgroundColor
323  radius: Kirigami.Units.smallSpacing
324  }
325  }
326 
327  // dialog content
328  contentItem: ColumnLayout {
329  QQC2.ScrollView {
330  id: contentControl
331 
332  // ensure view colour scheme, and background color
333  Kirigami.Theme.inherit: false
334  Kirigami.Theme.colorSet: Kirigami.Theme.View
335 
336  QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
337 
338  // height of everything else in the dialog other than the content
339  property real otherHeights: root.header.height + root.footer.height + root.topPadding + root.bottomPadding;
340 
341  property real calculatedMaximumWidth: Math.min(root.absoluteMaximumWidth, root.maximumWidth) - root.leftPadding - root.rightPadding
342  property real calculatedMaximumHeight: Math.min(root.absoluteMaximumHeight, root.maximumHeight) - root.topPadding - root.bottomPadding
343  property real calculatedImplicitWidth: (contentChildren.length === 1 && contentChildren[0].implicitWidth > 0
344  ? contentChildren[0].implicitWidth
345  : (contentItem.implicitWidth > 0 ? contentItem.implicitWidth : contentItem.width)) + leftPadding + rightPadding
346  property real calculatedImplicitHeight: (contentChildren.length === 1 && contentChildren[0].implicitHeight > 0
347  ? contentChildren[0].implicitHeight
348  : (contentItem.implicitHeight > 0 ? contentItem.implicitHeight : contentItem.height)) + topPadding + bottomPadding
349 
350  // how do we deal with the scrollbar width?
351  // - case 1: the dialog itself has the preferredWidth set
352  // -> we hint a width to the content so it shrinks to give space to the scrollbar
353  // - case 2: preferredWidth not set, so we are using the content's implicit width
354  // -> we expand the dialog's width to accommodate the scrollbar width (to respect the content's desired width)
355 
356  // don't enforce preferred width and height if not set
357  Layout.preferredWidth: (root.preferredWidth >= 0 ? root.preferredWidth : calculatedImplicitWidth)
358  Layout.preferredHeight: root.preferredHeight >= 0 ? root.preferredHeight - otherHeights : calculatedImplicitHeight
359 
360  Layout.fillWidth: true
361  Layout.maximumWidth: calculatedMaximumWidth
362  Layout.maximumHeight: calculatedMaximumHeight - otherHeights // we enforce maximum height solely from the content
363 
364  // give an implied width and height to the contentItem so that features like word wrapping/eliding work
365  // cannot placed directly in contentControl as a child, so we must use a property
366  property var widthHint: Binding {
367  target: contentControl.contentChildren[0] || null
368  property: "width"
369 
370  // we want to avoid horizontal scrolling, so we apply maximumWidth as a hint if necessary
371  property real preferredWidthHint: contentControl.contentItem.width
372  property real maximumWidthHint: contentControl.calculatedMaximumWidth - contentControl.leftPadding - contentControl.rightPadding
373 
374  value: Math.min(maximumWidthHint, preferredWidthHint)
375 
376  restoreMode: Binding.RestoreBinding
377  }
378  }
379  }
380 
381  header: T.Control {
382  implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
383  implicitContentWidth + leftPadding + rightPadding)
384  implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
385  implicitContentHeight + topPadding + bottomPadding)
386 
387  padding: Kirigami.Units.largeSpacing
388  bottomPadding: verticalPadding + headerSeparator.implicitHeight // add space for bottom separator
389 
390  contentItem: RowLayout {
391  Kirigami.Heading {
392  id: heading
393  Layout.fillWidth: true
394  Layout.alignment: Qt.AlignVCenter
395  level: 2
396  text: root.title === "" ? " " : root.title // always have text to ensure header height
397  elide: Text.ElideRight
398 
399  // use tooltip for long text that is elided
400  QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
401  QQC2.ToolTip.text: root.title
402  HoverHandler { id: titleHoverHandler }
403  }
404  Kirigami.Icon {
405  id: closeIcon
406  visible: root.showCloseButton
407 
408  // We want to position the close button in the top-right
409  // corner if the header is very tall, but we want to
410  // vertically center it in a short header
411  readonly property bool tallHeader: parent.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing + Kirigami.Units.largeSpacing)
412  Layout.alignment: tallHeader ? Qt.AlignRight | Qt.AlignTop : Qt.AlignRight | Qt.AlignVCenter
413  Layout.topMargin: tallHeader ? Kirigami.Units.largeSpacing : 0
414  implicitHeight: Kirigami.Units.iconSizes.smallMedium
415  implicitWidth: implicitHeight
416 
417  source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
418  active: closeMouseArea.containsMouse
419  MouseArea {
420  id: closeMouseArea
421  hoverEnabled: Qt.styleHints.useHoverEffects
422  anchors.fill: parent
423  onClicked: mouse => root.reject()
424  }
425  }
426  }
427 
428  // header background
429  background: Kirigami.ShadowedRectangle {
430  corners.topLeftRadius: Kirigami.Units.smallSpacing
431  corners.topRightRadius: Kirigami.Units.smallSpacing
432  Kirigami.Theme.colorSet: Kirigami.Theme.Header
433  Kirigami.Theme.inherit: false
434  color: Kirigami.Theme.backgroundColor
435  Kirigami.Separator {
436  id: headerSeparator
437  width: parent.width
438  anchors.bottom: parent.bottom
439  }
440  }
441  }
442 
443  // use top level control rather than toolbar, since toolbar causes button rendering glitches
444  footer: T.Control {
445  id: footerToolBar
446 
447  // if there is nothing in the footer, still maintain a height so that we can create a rounded bottom buffer for the dialog
448  property bool bufferMode: contentItem.implicitHeight === 0
449  implicitHeight: bufferMode ? Kirigami.Units.smallSpacing : contentItem.implicitHeight
450 
451  leftPadding: 0; rightPadding: 0; bottomPadding: 0
452  topPadding: bufferMode ? 0 : footerSeparator.implicitHeight // add space for the separator above the footer
453 
454  contentItem: RowLayout {
455  spacing: parent.spacing
456 
457  Loader {
458  id: leadingLoader
459  sourceComponent: root.footerLeadingComponent
460  }
461 
462  // footer buttons
463  QQC2.DialogButtonBox {
464  // we don't explicitly set padding, to let the style choose the padding
465  id: dialogButtonBox
466  standardButtons: root.standardButtons
467  visible: count > 0
468 
469  Layout.fillWidth: true
470  Layout.alignment: dialogButtonBox.alignment
471 
472  position: QQC2.DialogButtonBox.Footer
473 
474  // ensure themes don't add a background, since it can lead to visual inconsistencies
475  // with the rest of the dialog
476  background: null
477 
478  // we need to hook all of the buttonbox events to the dialog events
479  onAccepted: root.accept()
480  onRejected: root.reject()
481  onApplied: root.applied()
482  onDiscarded: root.discarded()
483  onHelpRequested: root.helpRequested()
484  onReset: root.reset()
485 
486  // add custom footer buttons
487  Repeater {
488  model: root.customFooterActions
489  // we have to use Button instead of ToolButton, because ToolButton has no visual distinction when disabled
490  delegate: QQC2.Button {
491  flat: flatFooterButtons
492  action: modelData
493  visible: modelData.visible
494  }
495  }
496  }
497 
498  Loader {
499  id: trailingLoader
500  sourceComponent: root.footerTrailingComponent
501  }
502  }
503 
504  background: Kirigami.ShadowedRectangle {
505  // curved footer bottom corners
506  corners.bottomLeftRadius: Kirigami.Units.smallSpacing
507  corners.bottomRightRadius: Kirigami.Units.smallSpacing
508 
509  // we act as a content buffer if nothing is in the footer
510  Kirigami.Theme.colorSet: footerToolBar.bufferMode ? Kirigami.Theme.View : Kirigami.Theme.Window
511  Kirigami.Theme.inherit: false
512  color: Kirigami.Theme.backgroundColor
513 
514  // separator above footer
515  Kirigami.Separator {
516  id: footerSeparator
517  visible: !footerToolBar.bufferMode
518  width: parent.width
519  anchors.top: parent.top
520  }
521  }
522  }
523 }
QStringView level(QStringView ifopt)
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 6 2023 04:13:17 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.