Kirigami2

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

KDE's Doxygen guidelines are available online.