Advance Wayland and KDE package bring-up
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
|
||||
add_library(KirigamiDialogs)
|
||||
ecm_add_qml_module(KirigamiDialogs URI "org.kde.kirigami.dialogs"
|
||||
VERSION 2.0
|
||||
GENERATE_PLUGIN_SOURCE
|
||||
INSTALLED_PLUGIN_TARGET KF6KirigamiDialogsplugin
|
||||
DEPENDENCIES QtQuick org.kde.kirigami.platform
|
||||
)
|
||||
|
||||
|
||||
|
||||
ecm_target_qml_sources(KirigamiDialogs SOURCES
|
||||
Dialog.qml
|
||||
MenuDialog.qml
|
||||
PromptDialog.qml
|
||||
SearchDialog.qml
|
||||
)
|
||||
|
||||
set_target_properties(KirigamiDialogs PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION 6
|
||||
EXPORT_NAME "KirigamiDialogs"
|
||||
)
|
||||
|
||||
target_include_directories(KirigamiDialogs PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
||||
|
||||
target_link_libraries(KirigamiDialogs PRIVATE Qt6::Quick KirigamiPlatform)
|
||||
|
||||
ecm_finalize_qml_module(KirigamiDialogs EXPORT KirigamiTargets)
|
||||
|
||||
install(TARGETS KirigamiDialogs EXPORT KirigamiTargets ${KF_INSTALL_DEFAULT_ARGUMENTS})
|
||||
@@ -0,0 +1,534 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
|
||||
SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
|
||||
SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||
*/
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQml
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Templates as T
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* @brief Popup dialog that is used for short tasks and user interaction.
|
||||
*
|
||||
* Dialog consists of three components: the header, the content,
|
||||
* and the footer.
|
||||
*
|
||||
* By default, the header is a heading with text specified by the
|
||||
* `title` property.
|
||||
*
|
||||
* By default, the footer consists of a row of buttons specified by
|
||||
* the `standardButtons` and `customFooterActions` properties.
|
||||
*
|
||||
* The `implicitHeight` and `implicitWidth` of the dialog contentItem is
|
||||
* the primary hint used for the dialog size. The dialog will be the
|
||||
* minimum size required for the header, footer and content unless
|
||||
* it is larger than `maximumHeight` and `maximumWidth`. Use
|
||||
* `preferredHeight` and `preferredWidth` in order to manually specify
|
||||
* a size for the dialog.
|
||||
*
|
||||
* If the content height exceeds the maximum height of the dialog, the
|
||||
* dialog's contents will become scrollable.
|
||||
*
|
||||
* If the contentItem is a <b>ListView</b>, the dialog will take care of the
|
||||
* necessary scrollbars and scrolling behaviour. Do <b>not</b> attempt
|
||||
* to nest ListViews (it must be the top level item), as the scrolling
|
||||
* behaviour will not be handled. Use ListView's `header` and `footer` instead.
|
||||
*
|
||||
* Example for a selection dialog:
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import QtQuick.Layouts
|
||||
* import QtQuick.Controls as QQC2
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.Dialog {
|
||||
* title: i18n("Dialog")
|
||||
* padding: 0
|
||||
* preferredWidth: Kirigami.Units.gridUnit * 16
|
||||
*
|
||||
* standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
*
|
||||
* onAccepted: console.log("OK button pressed")
|
||||
* onRejected: console.log("Rejected")
|
||||
*
|
||||
* ColumnLayout {
|
||||
* spacing: 0
|
||||
* Repeater {
|
||||
* model: 5
|
||||
* delegate: QQC2.CheckDelegate {
|
||||
* topPadding: Kirigami.Units.smallSpacing * 2
|
||||
* bottomPadding: Kirigami.Units.smallSpacing * 2
|
||||
* Layout.fillWidth: true
|
||||
* text: modelData
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Example with scrolling (ListView scrolling behaviour is handled by the Dialog):
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import QtQuick.Layouts
|
||||
* import QtQuick.Controls as QQC2
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.Dialog {
|
||||
* id: scrollableDialog
|
||||
* title: i18n("Select Number")
|
||||
*
|
||||
* ListView {
|
||||
* id: listView
|
||||
* // hints for the dialog dimensions
|
||||
* implicitWidth: Kirigami.Units.gridUnit * 16
|
||||
* implicitHeight: Kirigami.Units.gridUnit * 16
|
||||
*
|
||||
* model: 100
|
||||
* delegate: QQC2.RadioDelegate {
|
||||
* topPadding: Kirigami.Units.smallSpacing * 2
|
||||
* bottomPadding: Kirigami.Units.smallSpacing * 2
|
||||
* implicitWidth: listView.width
|
||||
* text: modelData
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* There are also sub-components of the Dialog that target specific usecases,
|
||||
* and can reduce boilerplate code if used:
|
||||
*
|
||||
* @see PromptDialog
|
||||
* @see MenuDialog
|
||||
*
|
||||
* @inherit QtQuick.QtObject
|
||||
*/
|
||||
T.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief This property holds the dialog's contents; includes Items and QtObjects.
|
||||
* @property list<QtObject> dialogData
|
||||
*/
|
||||
default property alias dialogData: contentControl.contentData
|
||||
|
||||
/**
|
||||
* @brief This property holds the content items of the dialog.
|
||||
*
|
||||
* The initial height and width of the dialog is calculated from the
|
||||
* `implicitWidth` and `implicitHeight` of the content.
|
||||
*
|
||||
* @property list<Item> dialogChildren
|
||||
*/
|
||||
property alias dialogChildren: contentControl.contentChildren
|
||||
|
||||
/**
|
||||
* @brief This property sets the absolute maximum height the dialog can have.
|
||||
*
|
||||
* The height restriction is solely applied on the content, so if the
|
||||
* maximum height given is not larger than the height of the header and
|
||||
* footer, it will be ignored.
|
||||
*
|
||||
* This is the window height, subtracted by largeSpacing on both the top
|
||||
* and bottom.
|
||||
*/
|
||||
readonly property real absoluteMaximumHeight: parent ? (parent.height - Kirigami.Units.largeSpacing * 2) : Infinity
|
||||
|
||||
/**
|
||||
* @brief This property holds the absolute maximum width the dialog can have.
|
||||
*
|
||||
* By default, it is the window width, subtracted by largeSpacing on both
|
||||
* the top and bottom.
|
||||
*/
|
||||
readonly property real absoluteMaximumWidth: parent ? (parent.width - Kirigami.Units.largeSpacing * 2) : Infinity
|
||||
|
||||
readonly property real __borderWidth: 1
|
||||
|
||||
/**
|
||||
* @brief This property holds the maximum height the dialog can have
|
||||
* (including the header and footer).
|
||||
*
|
||||
* The height restriction is solely enforced on the content, so if the
|
||||
* maximum height given is not larger than the height of the header and
|
||||
* footer, it will be ignored.
|
||||
*
|
||||
* By default, this is `absoluteMaximumHeight`.
|
||||
*/
|
||||
property real maximumHeight: absoluteMaximumHeight
|
||||
|
||||
/**
|
||||
* @brief This property holds the maximum width the dialog can have.
|
||||
*
|
||||
* By default, this is `absoluteMaximumWidth`.
|
||||
*/
|
||||
property real maximumWidth: absoluteMaximumWidth
|
||||
|
||||
/**
|
||||
* @brief This property holds the preferred height of the dialog.
|
||||
*
|
||||
* The content will receive a hint for how tall it should be to have
|
||||
* the dialog to be this height.
|
||||
*
|
||||
* If the content, header or footer require more space, then the height
|
||||
* of the dialog will expand to the necessary amount of space.
|
||||
*/
|
||||
property real preferredHeight: -1
|
||||
|
||||
/**
|
||||
* @brief This property holds the preferred width of the dialog.
|
||||
*
|
||||
* The content will receive a hint for how wide it should be to have
|
||||
* the dialog be this wide.
|
||||
*
|
||||
* If the content, header or footer require more space, then the width
|
||||
* of the dialog will expand to the necessary amount of space.
|
||||
*/
|
||||
property real preferredWidth: -1
|
||||
|
||||
|
||||
/**
|
||||
* @brief This property holds the component to the left of the footer buttons.
|
||||
*/
|
||||
property Component footerLeadingComponent
|
||||
|
||||
/**
|
||||
* @brief his property holds the component to the right of the footer buttons.
|
||||
*/
|
||||
property Component footerTrailingComponent
|
||||
|
||||
/**
|
||||
* @brief This property sets whether to show the close button in the header.
|
||||
*/
|
||||
property bool showCloseButton: true
|
||||
|
||||
/**
|
||||
* @brief This property sets whether the footer button style should be flat.
|
||||
*/
|
||||
property bool flatFooterButtons: false
|
||||
|
||||
/**
|
||||
* @brief This property holds the custom actions displayed in the footer.
|
||||
*
|
||||
* Example usage:
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.PromptDialog {
|
||||
* id: dialog
|
||||
* title: i18n("Confirm Playback")
|
||||
* subtitle: i18n("Are you sure you want to play this song? It's really loud!")
|
||||
*
|
||||
* standardButtons: Kirigami.Dialog.Cancel
|
||||
* customFooterActions: [
|
||||
* Kirigami.Action {
|
||||
* text: i18n("Play")
|
||||
* icon.name: "media-playback-start"
|
||||
* onTriggered: {
|
||||
* //...
|
||||
* dialog.close();
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see org::kde::kirigami::Action
|
||||
*/
|
||||
property list<T.Action> customFooterActions
|
||||
|
||||
// DialogButtonBox should NOT contain invisible buttons, because in Qt 6
|
||||
// ListView preserves space even for invisible items.
|
||||
readonly property list<T.Action> __visibleCustomFooterActions: customFooterActions
|
||||
.filter(action => !(action instanceof Kirigami.Action) || action?.visible)
|
||||
|
||||
function standardButton(button): T.AbstractButton {
|
||||
// in case a footer is redefined
|
||||
if (footer instanceof T.DialogButtonBox) {
|
||||
return footer.standardButton(button);
|
||||
} else if (footer === footerToolBar) {
|
||||
return dialogButtonBox.standardButton(button);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function customFooterButton(action: T.Action): T.AbstractButton {
|
||||
if (!action) {
|
||||
// Even if there's a null object in the list of actions, we should
|
||||
// not return a button for it.
|
||||
return null;
|
||||
}
|
||||
const index = __visibleCustomFooterActions.indexOf(action);
|
||||
if (index < 0) {
|
||||
return null;
|
||||
}
|
||||
return customFooterButtons.itemAt(index) as T.AbstractButton;
|
||||
}
|
||||
|
||||
z: Kirigami.OverlayZStacking.z
|
||||
|
||||
// calculate dimensions and in case footer is wider than content, use that
|
||||
implicitWidth: Math.max(contentItem.implicitWidth, footerToolBar.implicitWidth, heading.implicitWidth) + leftPadding + rightPadding // maximum width enforced from our content (one source of truth) to avoid binding loops
|
||||
implicitHeight: contentItem.implicitHeight + topPadding + bottomPadding
|
||||
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
|
||||
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0);
|
||||
|
||||
// misc. dialog settings
|
||||
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnReleaseOutside
|
||||
modal: true
|
||||
clip: false
|
||||
padding: 0
|
||||
horizontalPadding: __borderWidth + padding
|
||||
|
||||
// determine parent so that popup knows which window to popup in
|
||||
// we want to open the dialog in the center of the window, if possible
|
||||
Component.onCompleted: {
|
||||
if (typeof applicationWindow !== "undefined") {
|
||||
parent = applicationWindow().overlay;
|
||||
}
|
||||
}
|
||||
|
||||
// center dialog
|
||||
x: parent ? Math.round((parent.width - width) / 2) : 0
|
||||
y: parent ? Math.round((parent.height - height) / 2) + Kirigami.Units.gridUnit * 2 * (1 - opacity) : 0 // move animation
|
||||
|
||||
// dialog enter and exit transitions
|
||||
enter: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 0; to: 1; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
exit: Transition {
|
||||
NumberAnimation { property: "opacity"; from: 1; to: 0; easing.type: Easing.InOutQuad; duration: Kirigami.Units.longDuration }
|
||||
}
|
||||
|
||||
// black background, fades in and out
|
||||
QQC2.Overlay.modal: Rectangle {
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
|
||||
// the opacity of the item is changed internally by QQuickPopup on open/close
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.longDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dialog view background
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
id: rect
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
Kirigami.Theme.inherit: false
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
radius: Kirigami.Units.cornerRadius
|
||||
shadow {
|
||||
size: radius * 2
|
||||
color: Qt.rgba(0, 0, 0, 0.3)
|
||||
yOffset: 1
|
||||
}
|
||||
|
||||
border {
|
||||
width: root.__borderWidth
|
||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast);
|
||||
}
|
||||
}
|
||||
|
||||
// dialog content
|
||||
contentItem: QQC2.ScrollView {
|
||||
id: contentControl
|
||||
|
||||
// ensure view colour scheme, and background color
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
|
||||
// height of everything else in the dialog other than the content
|
||||
property real otherHeights: (root.header?.height ?? 0) + (root.footer?.height ?? 0) + root.topPadding + root.bottomPadding;
|
||||
|
||||
property real calculatedMaximumWidth: Math.min(root.absoluteMaximumWidth, root.maximumWidth) - root.leftPadding - root.rightPadding
|
||||
property real calculatedMaximumHeight: Math.min(root.absoluteMaximumHeight, root.maximumHeight) - root.topPadding - root.bottomPadding
|
||||
property real calculatedImplicitWidth: (contentChildren.length === 1 && contentChildren[0].implicitWidth > 0 ? contentChildren[0].implicitWidth : contentItem.implicitWidth) + leftPadding + rightPadding
|
||||
property real calculatedImplicitHeight: (contentChildren.length === 1 && contentChildren[0].implicitHeight > 0? contentChildren[0].implicitHeight: contentItem.implicitHeight) + topPadding + bottomPadding
|
||||
|
||||
onContentItemChanged: {
|
||||
const contentFlickable = contentItem as Flickable;
|
||||
if (contentFlickable) {
|
||||
/*
|
||||
Why this is necessary? A Flickable mainItem syncs its size with the contents only on startup,
|
||||
and if the contents can change their size dinamically afterwards (wrapping text does that),
|
||||
the contentsize will be wrong see BUG 477257.
|
||||
|
||||
We also don't do this declaratively but only we are sure a contentItem is declared/created as just
|
||||
accessing the property would create an internal Flickable, making it impossible to assign custom
|
||||
flickables/listviews to the Dialog.
|
||||
*/
|
||||
contentFlickable.contentHeight = Qt.binding(() => calculatedImplicitHeight);
|
||||
|
||||
contentFlickable.clip = true;
|
||||
}
|
||||
}
|
||||
|
||||
// how do we deal with the scrollbar width?
|
||||
// - case 1: the dialog itself has the preferredWidth set
|
||||
// -> we hint a width to the content so it shrinks to give space to the scrollbar
|
||||
// - case 2: preferredWidth not set, so we are using the content's implicit width
|
||||
// -> we expand the dialog's width to accommodate the scrollbar width (to respect the content's desired width)
|
||||
|
||||
// don't enforce preferred width and height if not set (-1), and expand to a larger implicit size
|
||||
property real preferredWidth: Math.max(root.preferredWidth, calculatedImplicitWidth)
|
||||
property real preferredHeight: Math.max(root.preferredHeight - otherHeights, calculatedImplicitHeight)
|
||||
|
||||
property real maximumWidth: calculatedMaximumWidth
|
||||
property real maximumHeight: calculatedMaximumHeight - otherHeights // we enforce maximum height solely from the content
|
||||
|
||||
implicitWidth: Math.min(preferredWidth, maximumWidth)
|
||||
implicitHeight: Math.min(preferredHeight, maximumHeight)
|
||||
|
||||
// give an implied width and height to the contentItem so that features like word wrapping/eliding work
|
||||
// cannot placed directly in contentControl as a child, so we must use a property
|
||||
property var widthHint: Binding {
|
||||
target: contentControl.contentChildren[0] || null
|
||||
property: "width"
|
||||
value: contentControl.width + contentControl.leftPadding + contentControl.rightPadding
|
||||
restoreMode: Binding.RestoreBinding
|
||||
}
|
||||
}
|
||||
|
||||
header: T.Control {
|
||||
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
|
||||
implicitContentWidth + leftPadding + rightPadding)
|
||||
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
|
||||
implicitContentHeight + topPadding + bottomPadding)
|
||||
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
bottomPadding: verticalPadding + headerSeparator.implicitHeight // add space for bottom separator
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
id: heading
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: root.title.length === 0 ? " " : root.title // always have text to ensure header height
|
||||
elide: Text.ElideRight
|
||||
|
||||
// use tooltip for long text that is elided
|
||||
QQC2.ToolTip.visible: truncated && titleHoverHandler.hovered
|
||||
QQC2.ToolTip.text: root.title
|
||||
HoverHandler { id: titleHoverHandler }
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
id: closeIcon
|
||||
|
||||
// We want to position the close button in the top-right
|
||||
// corner if the header is very tall, but we want to
|
||||
// vertically center it in a short header
|
||||
readonly property bool tallHeader: parent.height > (Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.largeSpacing * 2)
|
||||
Layout.alignment: tallHeader ? Qt.AlignRight | Qt.AlignTop : Qt.AlignRight | Qt.AlignVCenter
|
||||
Layout.topMargin: tallHeader ? Kirigami.Units.largeSpacing : 0
|
||||
|
||||
visible: root.showCloseButton
|
||||
icon.name: closeIcon.hovered ? "window-close" : "window-close-symbolic"
|
||||
text: qsTr("Close", "@action:button close dialog")
|
||||
onClicked: root.reject()
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
}
|
||||
}
|
||||
|
||||
// header background
|
||||
background: Item {
|
||||
Kirigami.Separator {
|
||||
id: headerSeparator
|
||||
width: parent.width
|
||||
anchors.bottom: parent.bottom
|
||||
visible: contentControl.contentHeight > contentControl.implicitHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use top level control rather than toolbar, since toolbar causes button rendering glitches
|
||||
footer: T.Control {
|
||||
id: footerToolBar
|
||||
|
||||
// if there is nothing in the footer, still maintain a height so that we can create a rounded bottom buffer for the dialog
|
||||
property bool bufferMode: !root.footerLeadingComponent && !dialogButtonBox.visible
|
||||
implicitHeight: bufferMode ? Math.round(Kirigami.Units.smallSpacing / 2) : contentItem.implicitHeight + topPadding + bottomPadding
|
||||
implicitWidth: dialogButtonBox.implicitWidth + leftPadding + rightPadding
|
||||
|
||||
padding: !bufferMode ? Kirigami.Units.largeSpacing : 0
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: footerToolBar.spacing
|
||||
// Don't let user interact with footer during transitions
|
||||
enabled: root.opened
|
||||
|
||||
Loader {
|
||||
id: leadingLoader
|
||||
sourceComponent: root.footerLeadingComponent
|
||||
}
|
||||
|
||||
// footer buttons
|
||||
QQC2.DialogButtonBox {
|
||||
// we don't explicitly set padding, to let the style choose the padding
|
||||
id: dialogButtonBox
|
||||
standardButtons: root.standardButtons
|
||||
visible: count > 0
|
||||
padding: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: dialogButtonBox.alignment
|
||||
|
||||
position: QQC2.DialogButtonBox.Footer
|
||||
|
||||
// ensure themes don't add a background, since it can lead to visual inconsistencies
|
||||
// with the rest of the dialog
|
||||
background: null
|
||||
|
||||
// we need to hook all of the buttonbox events to the dialog events
|
||||
onAccepted: root.accept()
|
||||
onRejected: root.reject()
|
||||
onApplied: root.applied()
|
||||
onDiscarded: root.discarded()
|
||||
onHelpRequested: root.helpRequested()
|
||||
onReset: root.reset()
|
||||
|
||||
// add custom footer buttons
|
||||
Repeater {
|
||||
id: customFooterButtons
|
||||
model: root.__visibleCustomFooterActions
|
||||
// we have to use Button instead of ToolButton, because ToolButton has no visual distinction when disabled
|
||||
delegate: QQC2.Button {
|
||||
required property T.Action modelData
|
||||
|
||||
flat: root.flatFooterButtons
|
||||
action: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: trailingLoader
|
||||
sourceComponent: root.footerTrailingComponent
|
||||
}
|
||||
}
|
||||
|
||||
background: Item {
|
||||
Kirigami.Separator {
|
||||
id: footerSeparator
|
||||
visible: contentControl.contentHeight > contentControl.implicitHeight && footerToolBar.padding !== 0
|
||||
width: parent.width
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Templates as T
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* A dialog that prompts users with a context menu, with
|
||||
* list items that perform actions.
|
||||
*
|
||||
* Example usage:
|
||||
* @code{.qml}
|
||||
* Kirigami.MenuDialog {
|
||||
* title: i18n("Track Options")
|
||||
*
|
||||
* actions: [
|
||||
* Kirigami.Action {
|
||||
* icon.name: "media-playback-start"
|
||||
* text: i18nc("Start playback of the selected track", "Play")
|
||||
* tooltip: i18n("Start playback of the selected track")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* enabled: false
|
||||
* icon.name: "document-open-folder"
|
||||
* text: i18nc("Show the file for this song in the file manager", "Show in folder")
|
||||
* tooltip: i18n("Show the file for this song in the file manager")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* icon.name: "documentinfo"
|
||||
* text: i18nc("Show track metadata", "View details")
|
||||
* tooltip: i18n("Show track metadata")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* icon.name: "list-add"
|
||||
* text: i18nc("Add the track to the queue, right after the current track", "Play next")
|
||||
* tooltip: i18n("Add the track to the queue, right after the current track")
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* icon.name: "list-add"
|
||||
* text: i18nc("Enqueue current track", "Add to queue")
|
||||
* tooltip: i18n("Enqueue current track")
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @see Dialog
|
||||
* @see PromptDialog
|
||||
* @inherit org::kde::kirigami::Dialog
|
||||
*/
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief This property holds the actions displayed in the context menu.
|
||||
*/
|
||||
property list<T.Action> actions
|
||||
|
||||
/**
|
||||
* @brief This property holds the content header, which appears above the actions.
|
||||
* but below the header bar.
|
||||
*/
|
||||
property alias contentHeader: columnHeader.contentItem
|
||||
|
||||
/**
|
||||
* @brief This property holds the content header.
|
||||
*
|
||||
* This makes it possible to access its internal properties to, for example, change its padding:
|
||||
* ``contentHeaderControl.topPadding``
|
||||
*
|
||||
* @property QtQuick.Controls.Control contentHeaderControl
|
||||
*/
|
||||
property alias contentHeaderControl: columnHeader
|
||||
|
||||
preferredWidth: Kirigami.Units.gridUnit * 20
|
||||
padding: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: column
|
||||
|
||||
spacing: 0
|
||||
|
||||
QQC2.Control {
|
||||
id: columnHeader
|
||||
|
||||
topPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
bottomPadding: 0
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: root.actions
|
||||
|
||||
delegate: QQC2.ItemDelegate {
|
||||
required property T.Action modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
action: modelData
|
||||
visible: !(modelData instanceof Kirigami.Action) || modelData.visible
|
||||
|
||||
icon.width: Kirigami.Units.gridUnit
|
||||
icon.height: Kirigami.Units.gridUnit
|
||||
|
||||
horizontalPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||
leftPadding: undefined
|
||||
rightPadding: undefined
|
||||
|
||||
QQC2.ToolTip.text: modelData instanceof Kirigami.Action ? modelData.tooltip : ""
|
||||
QQC2.ToolTip.visible: QQC2.ToolTip.text.length > 0 && (Kirigami.Settings.tabletMode ? pressed : hovered)
|
||||
QQC2.ToolTip.delay: Kirigami.Settings.tabletMode ? Qt.styleHints.mousePressAndHoldInterval : Kirigami.Units.toolTipDelay
|
||||
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
standardButtons: QQC2.DialogButtonBox.NoButton
|
||||
showCloseButton: true
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* A simple dialog to quickly prompt a user with information,
|
||||
* and possibly perform an action.
|
||||
*
|
||||
* Provides content padding (instead of padding outside of the scroll
|
||||
* area). Also has a default preferredWidth, as well as the `subtitle` property.
|
||||
*
|
||||
* <b>Note:</b> If a `mainItem` is specified, it will replace
|
||||
* the subtitle label, and so the respective property will have no effect.
|
||||
*
|
||||
* @see Dialog
|
||||
* @see MenuDialog
|
||||
*
|
||||
* Example usage:
|
||||
*
|
||||
* @code{.qml}
|
||||
* Kirigami.PromptDialog {
|
||||
* title: "Reset settings?"
|
||||
* subtitle: "The stored settings for the application will be deleted, with the defaults restored."
|
||||
* standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||
*
|
||||
* onAccepted: console.log("Accepted")
|
||||
* onRejected: console.log("Rejected")
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* Text field prompt dialog:
|
||||
*
|
||||
* @code{.qml}
|
||||
* Kirigami.PromptDialog {
|
||||
* id: textPromptDialog
|
||||
* title: qsTr("New Folder")
|
||||
*
|
||||
* standardButtons: Kirigami.Dialog.NoButton
|
||||
* customFooterActions: [
|
||||
* Kirigami.Action {
|
||||
* text: qsTr("Create Folder")
|
||||
* icon.name: "dialog-ok"
|
||||
* onTriggered: {
|
||||
* showPassiveNotification("Created");
|
||||
* textPromptDialog.close();
|
||||
* }
|
||||
* },
|
||||
* Kirigami.Action {
|
||||
* text: qsTr("Cancel")
|
||||
* icon.name: "dialog-cancel"
|
||||
* onTriggered: {
|
||||
* textPromptDialog.close();
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* QQC2.TextField {
|
||||
* placeholderText: qsTr("Folder name…")
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @inherit Dialog
|
||||
*/
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
default property alias mainItem: mainLayout.data
|
||||
|
||||
enum DialogType {
|
||||
Success,
|
||||
Warning,
|
||||
Error,
|
||||
Information,
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
* This property holds the dialogType. It can be either:
|
||||
*
|
||||
* - `PromptDialog.Success`: For a sucess message
|
||||
* - `PromptDialog.Warning`: For a warning message
|
||||
* - `PromptDialog.Error`: For an actual error
|
||||
* - `PromptDialog.Information`: For an informational message
|
||||
* - `PromptDialog.None`: No specific dialog type.
|
||||
*
|
||||
* By default, the dialogType is `Kirigami.PromptDialog.None`
|
||||
*/
|
||||
property int dialogType: Kirigami.PromptDialog.None
|
||||
|
||||
/**
|
||||
* The text to use in the dialog's contents.
|
||||
*/
|
||||
property string subtitle
|
||||
|
||||
/**
|
||||
* The padding around the content, within the scroll area.
|
||||
*
|
||||
* Default is `Kirigami.Units.largeSpacing`.
|
||||
*/
|
||||
property real contentPadding: Kirigami.Units.largeSpacing
|
||||
|
||||
/**
|
||||
* The top padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentTopPadding: contentPadding
|
||||
|
||||
/**
|
||||
* The bottom padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentBottomPadding: footer.padding === 0 ? contentPadding : 0 // add bottom padding if there is no footer
|
||||
|
||||
/**
|
||||
* The left padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentLeftPadding: contentPadding
|
||||
|
||||
/**
|
||||
* The right padding of the content, within the scroll area.
|
||||
*/
|
||||
property real contentRightPadding: contentPadding
|
||||
|
||||
/**
|
||||
* This property holds the icon name used by the PromptDialog.
|
||||
*/
|
||||
property string iconName: switch (dialogType) {
|
||||
case Kirigami.PromptDialog.Success:
|
||||
return "data-success";
|
||||
case Kirigami.PromptDialog.Warning:
|
||||
return "data-warning";
|
||||
case Kirigami.PromptDialog.Error:
|
||||
return "data-error";
|
||||
case Kirigami.PromptDialog.Information:
|
||||
return "data-information";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
|
||||
padding: 0
|
||||
|
||||
header: null
|
||||
|
||||
Kirigami.Padding {
|
||||
id: wrapper
|
||||
|
||||
topPadding: root.contentTopPadding
|
||||
leftPadding: root.contentLeftPadding
|
||||
rightPadding: root.contentRightPadding
|
||||
bottomPadding: root.contentBottomPadding
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Kirigami.Icon {
|
||||
source: root.iconName
|
||||
visible: root.iconName.length > 0
|
||||
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
Layout.alignment: Qt.AlignTop
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: mainLayout
|
||||
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
text: root.title
|
||||
visible: root.title.length > 0
|
||||
elide: QQC2.Label.ElideRight
|
||||
wrapMode: Text.WordWrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
text: root.subtitle
|
||||
wrapMode: TextEdit.Wrap
|
||||
visible: text.length > 0
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
/**
|
||||
* A dialog to let's you do a global search accross your applications
|
||||
* documents, chat rooms and more.
|
||||
*
|
||||
* Example usage for a chat app where we want to quickly search for a room.
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick
|
||||
* import org.kde.kitemmodels as KItemModels
|
||||
* import org.kde.kirigami as Kirigami
|
||||
*
|
||||
* Kirigami.SearchDialog {
|
||||
* id: root
|
||||
*
|
||||
* onTextChanged: {
|
||||
* sortModel.filterText = text;
|
||||
* }
|
||||
* onAccepted: listView.currentItem.clicked()
|
||||
*
|
||||
* emptyText: i18nc("Placeholder message", "No room found.")
|
||||
*
|
||||
* model: KItemModels.KSortFilterProxyModel {
|
||||
* id: sortModel
|
||||
*
|
||||
* sourceModel: RoomModel { }
|
||||
* }
|
||||
*
|
||||
* delegate: RoomDelegate {
|
||||
* onClicked: root.close()
|
||||
* }
|
||||
*
|
||||
* Shortcut {
|
||||
* sequence: "Ctrl+K"
|
||||
* onActivated: root.open()
|
||||
* }
|
||||
* }
|
||||
* @endcode{}
|
||||
*
|
||||
* @image html searchdialog.html
|
||||
*
|
||||
* @note This component is unsuitable on mobile. Instead on mobile prefer to
|
||||
* use a seperate page for the search.
|
||||
*
|
||||
* @since Kirigami 6.3
|
||||
*/
|
||||
QQC2.Dialog {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* This property holds an alias to the model of the internal ListView.
|
||||
*/
|
||||
property alias model: listView.model
|
||||
|
||||
/**
|
||||
* This property holds an alias to the delegate component of the internal ListView.
|
||||
*/
|
||||
property alias delegate: listView.delegate
|
||||
|
||||
/**
|
||||
* This property holds an alias to the currentItem component of the internal ListView.
|
||||
*/
|
||||
property alias currentItem: listView.currentItem
|
||||
|
||||
/**
|
||||
* This property holds an alias to the section component of the internal ListView.
|
||||
*/
|
||||
property alias section: listView.section
|
||||
|
||||
/**
|
||||
* This property holds an alias to the content of the search field.
|
||||
*/
|
||||
property alias text: searchField.text
|
||||
|
||||
/**
|
||||
* This property holds an alias to the left actions of the seach field.
|
||||
*/
|
||||
property alias searchFieldLeftActions: searchField.leftActions
|
||||
|
||||
/**
|
||||
* This property holds an alias to the right actions of the seach field.
|
||||
*/
|
||||
property alias searchFieldRightActions: searchField.rightActions
|
||||
|
||||
/**
|
||||
* The placeholder text shown in the (empty) search field.
|
||||
*/
|
||||
property alias searchFieldPlaceholderText: searchField.placeholderText
|
||||
|
||||
/**
|
||||
* This property holds the number of search results displayed in the internal ListView.
|
||||
*/
|
||||
property alias count: listView.count
|
||||
|
||||
/**
|
||||
* This property holds an alias to the placeholder message text displayed
|
||||
* when the internal list view is empty.
|
||||
*/
|
||||
property alias emptyText: placeholder.text
|
||||
|
||||
/**
|
||||
* This property holds an alias to the placeholder message icon displayed
|
||||
* when the internal list view is empty.
|
||||
*/
|
||||
property alias emptyIcon: placeholder.icon
|
||||
|
||||
/**
|
||||
* @brief Helpful action when the list is empty
|
||||
*
|
||||
* This property holds an alias to the helpful action of the placeholder message
|
||||
* when the internal list view is empty.
|
||||
*
|
||||
* @since 6.10
|
||||
*/
|
||||
property alias emptyHelpfulAction: placeholder.helpfulAction
|
||||
|
||||
width: Math.min(Kirigami.Units.gridUnit * 35, parent.width)
|
||||
height: Math.min(Kirigami.Units.gridUnit * 20, parent.height)
|
||||
|
||||
padding: 0
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
modal: true
|
||||
|
||||
onOpened: {
|
||||
searchField.forceActiveFocus();
|
||||
searchField.text = "";
|
||||
listView.currentIndex = 0;
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: searchField
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
background: null
|
||||
|
||||
Layout.margins: Kirigami.Units.smallSpacing
|
||||
|
||||
Keys.onDownPressed: {
|
||||
const listViewHadFocus = listView.activeFocus;
|
||||
listView.forceActiveFocus();
|
||||
if (listView.currentIndex < listView.count - 1) {
|
||||
// don't move to the next entry when we just changed focus from the search field to the list view
|
||||
if (listViewHadFocus) {
|
||||
listView.currentIndex++;
|
||||
}
|
||||
} else {
|
||||
listView.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
Keys.onUpPressed: {
|
||||
listView.forceActiveFocus();
|
||||
if (listView.currentIndex === 0) {
|
||||
listView.currentIndex = listView.count - 1;
|
||||
} else {
|
||||
listView.currentIndex--;
|
||||
}
|
||||
}
|
||||
Keys.onPressed: (event) => {
|
||||
switch (event.key) {
|
||||
case Qt.Key_PageDown:
|
||||
listView.forceActiveFocus();
|
||||
listView.currentIndex = Math.min(listView.count - 1, listView.currentIndex + Math.floor(listView.height / listView.currentItem.height));
|
||||
event.accepted = true;
|
||||
break;
|
||||
case Qt.Key_PageUp:
|
||||
listView.forceActiveFocus();
|
||||
listView.currentIndex = Math.max(0, listView.currentIndex - Math.floor(listView.height / listView.currentItem.height));
|
||||
event.accepted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
focusSequence: ""
|
||||
autoAccept: false
|
||||
|
||||
onAccepted: root.accepted()
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
QQC2.ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Keys.forwardTo: searchField
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
|
||||
currentIndex: 0
|
||||
clip: true
|
||||
highlightMoveDuration: 200
|
||||
Keys.forwardTo: searchField
|
||||
keyNavigationEnabled: true
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
id: placeholder
|
||||
anchors.centerIn: parent
|
||||
width: parent.width - Kirigami.Units.gridUnit * 4
|
||||
icon.name: 'system-search-symbolic'
|
||||
visible: listView.count === 0 && text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user