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:
2026-04-14 10:51:06 +01:00
parent 51f3c21121
commit cf12defd28
15214 changed files with 20594243 additions and 269 deletions
@@ -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
}
}
}
}
}