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,64 @@
cmake_minimum_required(VERSION 3.16)
# Build only C++ core, no QML modules
add_library(Kirigami STATIC)
add_library(KF6::Kirigami ALIAS Kirigami)
# Core C++ sources that don't require QML/QtQuick
target_sources(Kirigami PRIVATE
enums.h
imagecolors.cpp
imagecolors.h
mnemonicattached.cpp
mnemonicattached.h
overlayzstackingattached.cpp
overlayzstackingattached.h
pagepool.cpp
pagepool.h
scenepositionattached.cpp
scenepositionattached.h
spellcheckattached.cpp
spellcheckattached.h
wheelhandler.cpp
wheelhandler.h
)
target_include_directories(Kirigami PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
target_link_libraries(Kirigami PUBLIC
Qt6::Core
Qt6::Gui
PRIVATE
Qt6::Concurrent
)
ecm_qt_declare_logging_category(Kirigami
HEADER loggingcategory.h
IDENTIFIER KirigamiLog
CATEGORY_NAME kf.kirigami
DESCRIPTION "Kirigami"
DEFAULT_SEVERITY Warning
EXPORT KIRIGAMI
)
set_target_properties(Kirigami PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 6
EXPORT_NAME "Kirigami"
)
install(TARGETS Kirigami EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
install(EXPORT KirigamiTargets
DESTINATION ${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6Kirigami
FILE KF6KirigamiTargets.cmake
NAMESPACE KF6
)
ecm_qt_install_logging_categories(
EXPORT KIRIGAMI
FILE kirigami.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
@@ -0,0 +1,9 @@
<rules>
<dependencies>
<lib name="Kirigami">
<depends>
<bundled file="plugins/kf6/kirigami" />
</depends>
</lib>
</dependencies>
</rules>
@@ -0,0 +1,4 @@
#! /usr/bin/env bash
$EXTRACT_TR_STRINGS `find . -name \*.qml -o -name \*.cpp` -o $podir/libkirigami6_qt.pot
rm -f rc.cpp
@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "actionhelper.h"
ActionHelper::ActionHelper(QObject *parent)
: QObject(parent)
{
}
QString ActionHelper::iconName(const QIcon &icon) const
{
return icon.name();
}
QList<QKeySequence> ActionHelper::alternateShortcuts(QAction *action) const
{
if (!action || action->shortcuts().length() <= 1) {
return {};
} else {
return action->shortcuts().mid(1);
}
}
@@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: 2024 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <QAction>
#include <QtQml/qqmlregistration.h>
/// \internal This is private API, do not use.
class ActionHelper : public QObject
{
Q_OBJECT
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF
public:
explicit ActionHelper(QObject *parent = nullptr);
Q_INVOKABLE QList<QKeySequence> alternateShortcuts(QAction *action) const;
Q_INVOKABLE QString iconName(const QIcon &icon) const;
};
@@ -0,0 +1,408 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
//TODO: Kf6: move somewhere else which can depend from KAboutData?
/**
* @brief An about item that displays the about data
*
* Allows to show the copyright notice of the application
* together with the contributors and some information of which platform it's
* running on.
*
* @since 5.87
* @since org.kde.kirigami 2.19
*/
Item {
id: aboutItem
/**
* @brief This property holds an object with the same shape as KAboutData.
*
* Example usage:
* @code{json}
* aboutData: {
"displayName" : "KirigamiApp",
"productName" : "kirigami/app",
"componentName" : "kirigamiapp",
"shortDescription" : "A Kirigami example",
"homepage" : "",
"bugAddress" : "submit@bugs.kde.org",
"version" : "5.14.80",
"otherText" : "",
"authors" : [
{
"name" : "...",
"task" : "",
"emailAddress" : "somebody@kde.org",
"webAddress" : "",
"ocsUsername" : ""
}
],
"credits" : [],
"translators" : [],
"licenses" : [
{
"name" : "GPL v2",
"text" : "long, boring, license text",
"spdx" : "GPL-2.0"
}
],
"copyrightStatement" : "© 2010-2018 Plasma Development Team",
"desktopFileName" : "org.kde.kirigamiapp"
}
@endcode
*
* @see KAboutData
*/
property var aboutData
/**
* @brief This property holds a link to a "Get Involved" page.
*
* default: `"https://community.kde.org/Get_Involved" when application id starts with "org.kde.", otherwise it is empty.`
*/
property url getInvolvedUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://community.kde.org/Get_Involved" : ""
/**
* @brief This property holds a link to a "Donate" page.
*
* default: `"https://kde.org/community/donations" when application id starts with "org.kde.", otherwise it is empty.`
*/
property url donateUrl: aboutData.desktopFileName.startsWith("org.kde.") ? "https://kde.org/community/donations" : ""
/** @internal */
property bool _usePageStack: false
/**
* @see org::kde::kirigami::FormLayout::wideMode
* @property bool wideMode
*/
property alias wideMode: form.wideMode
/** @internal */
default property alias _content: form.data
// if aboutData is a native KAboutData object, avatarUrl should be a proper url instance,
// otherwise if it was defined as a string in pure JavaScript it should work too.
readonly property bool __hasAvatars: aboutItem.aboutData.authors.some(__hasAvatar)
function __hasAvatar(person): bool {
return typeof person.avatarUrl !== "undefined"
&& person.avatarUrl.toString().length > 0;
}
/**
* @brief This property controls whether to load avatars by URL.
*
* If set to false, a fallback "user" icon will be displayed.
*
* default: ``false``
*/
property bool loadAvatars: false
implicitHeight: form.implicitHeight
implicitWidth: form.implicitWidth
Component {
id: personDelegate
RowLayout {
id: delegate
// type: KAboutPerson | { name?, task?, emailAddress?, webAddress?, avatarUrl? }
required property var modelData
property bool hasAvatar: aboutItem.__hasAvatar(modelData)
Layout.fillWidth: true
spacing: Kirigami.Units.smallSpacing * 2
Kirigami.Icon {
id: avatarIcon
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: implicitWidth
fallback: "user"
source: {
if (delegate.hasAvatar && aboutItem.loadAvatars) {
// Appending to the params of the url does not work, thus the search is set
const url = new URL(modelData.avatarUrl);
const params = new URLSearchParams(url.search);
params.append("s", width);
url.search = params.toString();
return url;
} else {
return "user"
}
}
visible: status !== Kirigami.Icon.Loading
}
// So it's clear that something is happening while avatar images are loaded
QQC2.BusyIndicator {
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: implicitWidth
visible: avatarIcon.status === Kirigami.Icon.Loading
running: visible
}
QQC2.Label {
Layout.fillWidth: true
readonly property bool withTask: typeof(modelData.task) !== "undefined" && modelData.task.length > 0
text: withTask ? qsTr("%1 (%2)").arg(modelData.name).arg(modelData.task) : modelData.name
wrapMode: Text.WordWrap
}
QQC2.ToolButton {
visible: typeof(modelData.emailAddress) !== "undefined" && modelData.emailAddress.length > 0
icon.name: "mail-sent"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: qsTr("Send an email to %1").arg(modelData.emailAddress)
onClicked: Qt.openUrlExternally("mailto:%1".arg(modelData.emailAddress))
}
QQC2.ToolButton {
visible: typeof(modelData.webAddress) !== "undefined" && modelData.webAddress.length > 0
icon.name: "globe"
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: (typeof(modelData.webAddress) === "undefined" && modelData.webAddress.length > 0) ? "" : modelData.webAddress
onClicked: Qt.openUrlExternally(modelData.webAddress)
}
}
}
Kirigami.FormLayout {
id: form
anchors.fill: parent
GridLayout {
columns: 2
Layout.fillWidth: true
Kirigami.Icon {
Layout.rowSpan: 3
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.preferredWidth: height
Layout.maximumWidth: aboutItem.width / 3;
Layout.rightMargin: Kirigami.Units.largeSpacing
source: Kirigami.Settings.applicationWindowIcon || aboutItem.aboutData.programLogo || aboutItem.aboutData.programIconName || aboutItem.aboutData.componentName
}
Kirigami.Heading {
Layout.fillWidth: true
text: aboutItem.aboutData.displayName + " " + aboutItem.aboutData.version
wrapMode: Text.WordWrap
}
Kirigami.Heading {
Layout.fillWidth: true
level: 2
wrapMode: Text.WordWrap
text: aboutItem.aboutData.shortDescription
}
RowLayout {
spacing: Kirigami.Units.largeSpacing * 2
UrlButton {
text: qsTr("Get Involved")
url: aboutItem.getInvolvedUrl
visible: url.toString().length > 0
}
UrlButton {
text: qsTr("Donate")
url: aboutItem.donateUrl
visible: url.toString().length > 0
}
UrlButton {
readonly property string theUrl: {
if (aboutItem.aboutData.bugAddress !== "submit@bugs.kde.org") {
return aboutItem.aboutData.bugAddress
}
const elements = aboutItem.aboutData.productName.split('/');
let url = `https://bugs.kde.org/enter_bug.cgi?format=guided&product=${elements[0]}&version=${aboutItem.aboutData.version}`;
if (elements.length === 2) {
url += "&component=" + elements[1];
}
return url;
}
text: qsTr("Report a Bug")
url: theUrl
visible: theUrl.toString().length > 0
}
}
}
Separator {
Layout.fillWidth: true
}
Kirigami.Heading {
Kirigami.FormData.isSection: true
text: qsTr("Copyright")
}
QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
text: aboutItem.aboutData.otherText
visible: text.length > 0
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
text: aboutItem.aboutData.copyrightStatement
visible: text.length > 0
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
UrlButton {
Layout.leftMargin: Kirigami.Units.gridUnit
url: aboutItem.aboutData.homepage
visible: url.length > 0
wrapMode: Text.WordWrap
Layout.fillWidth: true
}
OverlaySheet {
id: licenseSheet
property alias text: bodyLabel.text
contentItem: SelectableLabel {
id: bodyLabel
text: licenseSheet.text
wrapMode: Text.Wrap
}
}
Component {
id: licenseLinkButton
RowLayout {
Layout.leftMargin: Kirigami.Units.smallSpacing
QQC2.Label { text: qsTr("License:") }
LinkButton {
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: modelData.name
onClicked: mouse => {
licenseSheet.text = modelData.text
licenseSheet.title = modelData.name
licenseSheet.open()
}
}
}
}
Component {
id: licenseTextItem
QQC2.Label {
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.fillWidth: true
wrapMode: Text.WordWrap
text: qsTr("License: %1").arg(modelData.name)
}
}
Repeater {
model: aboutItem.aboutData.licenses
delegate: _usePageStack ? licenseLinkButton : licenseTextItem
}
Kirigami.Heading {
Kirigami.FormData.isSection: visible
text: qsTr("Libraries in use")
Layout.fillWidth: true
wrapMode: Text.WordWrap
visible: Kirigami.Settings.information
}
Repeater {
model: Kirigami.Settings.information
delegate: QQC2.Label {
Layout.leftMargin: Kirigami.Units.gridUnit
Layout.fillWidth: true
wrapMode: Text.WordWrap
id: libraries
text: modelData
}
}
Repeater {
model: aboutItem.aboutData.components
delegate: QQC2.Label {
Layout.fillWidth: true
wrapMode: Text.WordWrap
Layout.leftMargin: Kirigami.Units.gridUnit
text: modelData.name + (modelData.version.length === 0 ? "" : " %1".arg(modelData.version))
}
}
Kirigami.Heading {
Layout.fillWidth: true
Kirigami.FormData.isSection: visible
text: qsTr("Authors")
wrapMode: Text.WordWrap
visible: aboutItem.aboutData.authors.length > 0
}
QQC2.CheckBox {
id: remoteAvatars
visible: aboutItem.__hasAvatars
checked: aboutItem.loadAvatars
onToggled: aboutItem.loadAvatars = checked
text: qsTr("Show author photos")
}
Repeater {
id: authorsRepeater
model: aboutItem.aboutData.authors
delegate: personDelegate
}
Kirigami.Heading {
Kirigami.FormData.isSection: visible
text: qsTr("Credits")
visible: repCredits.count > 0
}
Repeater {
id: repCredits
model: aboutItem.aboutData.credits
delegate: personDelegate
}
Kirigami.Heading {
Kirigami.FormData.isSection: visible
text: qsTr("Translators")
visible: repTranslators.count > 0
}
Repeater {
id: repTranslators
model: aboutItem.aboutData.translators
delegate: personDelegate
}
}
}
@@ -0,0 +1,108 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
//TODO KF6: move somewhere else? kirigami addons?
/**
* @brief An "About" page that is ready to integrate in a Kirigami app.
*
* Allows to have a page that will show the copyright notice of the application
* together with the contributors and some information of which platform it's
* running on.
*
* @since 5.52
* @since org.kde.kirigami 2.6
* @inherit org::kde::kirigami::ScrollablePage
*/
Kirigami.ScrollablePage {
id: page
//BEGIN properties
/**
* @brief This property holds an object with the same shape as KAboutData.
*
* For example:
* @code{json}
* aboutData: {
"displayName" : "KirigamiApp",
"productName" : "kirigami/app",
"componentName" : "kirigamiapp",
"shortDescription" : "A Kirigami example",
"homepage" : "",
"bugAddress" : "submit@bugs.kde.org",
"version" : "5.14.80",
"otherText" : "",
"authors" : [
{
"name" : "...",
"task" : "",
"emailAddress" : "somebody@kde.org",
"webAddress" : "",
"ocsUsername" : ""
}
],
"credits" : [],
"translators" : [],
"licenses" : [
{
"name" : "GPL v2",
"text" : "long, boring, license text",
"spdx" : "GPL-2.0"
}
],
"copyrightStatement" : "© 2010-2018 Plasma Development Team",
"desktopFileName" : "org.kde.kirigamiapp"
}
@endcode
*
* @see KAboutData
* @see org::kde::kirigami::AboutItem::aboutData
* @property KAboutData aboutData
*/
property alias aboutData: aboutItem.aboutData
/**
* @brief This property holds a link to a "Get Involved" page.
*
* default: `"https://community.kde.org/Get_Involved" when your application id starts with "org.kde.", otherwise is empty`
*
* @property url getInvolvedUrl
*/
property alias getInvolvedUrl: aboutItem.getInvolvedUrl
/**
* @brief This property holds a link to a "Donate" page.
* @since 5.101
*
* default: `"https://kde.org/community/donations" when application id starts with "org.kde.", otherwise it is empty.`
*/
property alias donateUrl: aboutItem.donateUrl
/**
* @brief This property controls whether to load avatars by URL.
*
* If set to false, a fallback "user" icon will be displayed.
*
* default: ``false``
*/
property alias loadAvatars: aboutItem.loadAvatars
/** @internal */
default property alias _content: aboutItem._content
//END properties
title: qsTr("About %1").arg(page.aboutData.displayName)
Kirigami.AboutItem {
id: aboutItem
wideMode: page.width >= aboutItem.implicitWidth
_usePageStack: applicationWindow().pageStack ? true : false
}
}
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import "private" as P
import "templates" as T
//TODO KF6: remove
/**
* @brief An item that can be used as a title for the application.
*
* Scrolling the main page will make it taller or shorter (through the point of going away)
* It's a behavior similar to the typical mobile web browser addressbar
* the minimum, preferred and maximum heights of the item can be controlled with
* * ``minimumHeight``: default is 0, i.e. hidden
* * ``preferredHeight``: default is Kirigami.Units.gridUnit * 1.6
* * ``maximumHeight``: default is Kirigami.Units.gridUnit * 3
*
* To achieve a titlebar that stays completely fixed just set the 3 sizes as the same
*
* @inherit org::kde::kirigami::templates::AbstractApplicationHeader
*/
T.AbstractApplicationHeader {
id: root
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
background: Rectangle {
color: Kirigami.Theme.backgroundColor
P.EdgeShadow {
id: shadow
visible: root.separatorVisible
anchors {
right: parent.right
left: parent.left
top: parent.bottom
}
edge: Qt.TopEdge
opacity: (!root.page || !root.page.header || root.page.header.toString().indexOf("ToolBar") === -1)
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Behavior on opacity {
OpacityAnimator {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
@@ -0,0 +1,355 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "templates/private" as TP
import "templates" as KT
/**
* @brief An item that provides the features of AbstractApplicationWindow without the window itself.
*
* This allows embedding into a larger application.
* Unless you need extra flexibility it is recommended to use ApplicationItem instead.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.AbstractApplicationItem {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack: Kirigami.PageRow {
* ...
* }
* [...]
* }
* @endcode
*
* @inherit QtQuick.Item
*/
Item {
id: root
//BEGIN properties
/**
* @brief This property holds the stack used to allocate the pages and to manage the
* transitions between them.
*
* Put a container here, such as QtQuick.Controls.StackView.
*/
property Item pageStack
/**
* @brief This property holds the font for this item.
*
* default: ``Kirigami.Theme.defaultFont``
*/
property font font: Kirigami.Theme.defaultFont
/**
* @brief This property holds the locale for this item.
*/
property Locale locale
/**
* @brief This property holds an item that can be used as a menuBar for the application.
*/
property T.MenuBar menuBar
/**
* @brief This property holds an item that can be used as a title for the application.
*
* Scrolling the main page will make it taller or shorter (through the point of going away).
*
* It's a behavior similar to the typical mobile web browser addressbar.
*
* The minimum, preferred and maximum heights of the item can be controlled with
*
* * ``Layout.minimumHeight``: default is 0, i.e. hidden
* * ``Layout.preferredHeight``: default is Kirigami.Units.gridUnit * 1.6
* * ``Layout.maximumHeight``: default is Kirigami.Units.gridUnit * 3
*
* To achieve a titlebar that stays completely fixed, just set the 3 sizes as the same.
*
* @property kirigami::templates::AbstractApplicationHeader header
*/
property KT.AbstractApplicationHeader header
/**
* @brief This property holds an item that can be used as a footer for the application.
*/
property Item footer
/**
* @brief This property sets whether the standard chrome of the app is visible.
*
* These are the action button, the drawer handles and the application header.
*
* default: ``true``
*/
property bool controlsVisible: true
/**
* @brief This property holds the drawer for global actions.
*
* Thos drawer can be opened by sliding from the left screen edge
* or by dragging the ActionButton to the right.
*
* @note It is recommended to use the GlobalDrawer here.
* @property org::kde::kirigami::OverlayDrawer globalDrawer
*/
property OverlayDrawer globalDrawer
/**
* @brief This property tells us whether the application is in "widescreen" mode.
*
* This is enabled on desktops or horizontal tablets.
*
* @note Different styles can have their own logic for deciding this.
*/
property bool wideScreen: width >= Kirigami.Units.gridUnit * 60
/**
* @brief This property holds the drawer for context-dependent actions.
*
* The drawer that will be opened by sliding from the right screen edge
* or by dragging the ActionButton to the left.
*
* @note It is recommended to use the ContextDrawer type here.
*
* The contents of the context drawer should depend from what page is
* loaded in the main pageStack
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* contextDrawer: Kirigami.ContextDrawer {
* enabled: true
* actions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
* }
* @endcode
*
* @property org::kde::kirigami::ContextDrawer contextDrawer
*/
property OverlayDrawer contextDrawer
/**
* @brief This property holds the list of all children of this item.
* @internal
* @property list<Object> __data
*/
default property alias __data: contentItemRoot.data
/**
* @brief This property holds the Item of the main part of the Application UI.
*/
readonly property Item contentItem: Item {
id: contentItemRoot
parent: root
anchors {
fill: parent
topMargin: controlsVisible ? (root.header ? root.header.height : 0) + (root.menuBar ? root.menuBar.height : 0) : 0
bottomMargin: controlsVisible && root.footer ? root.footer.height : 0
leftMargin: root.globalDrawer && root.globalDrawer.modal === false ? root.globalDrawer.contentItem.width * root.globalDrawer.position : 0
rightMargin: root.contextDrawer && root.contextDrawer.modal === false ? root.contextDrawer.contentItem.width * root.contextDrawer.position : 0
}
}
/**
* @brief This property holds the color for the background.
*
* default: ``Kirigami.Theme.backgroundColor``
*/
property color color: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the background of the Application UI.
*/
property Item background
property alias overlay: overlayRoot
//END properties
//BEGIN functions
/**
* @brief This function shows a little passive notification at the bottom of the app window
* lasting for few seconds, with an optional action button.
*
* @param message The text message to be shown to the user.
* @param timeout How long to show the message:
* possible values: "short", "long" or the number of milliseconds
* @param actionText Text in the action button, if any.
* @param callBack A JavaScript function that will be executed when the
* user clicks the button.
*/
function showPassiveNotification(message, timeout, actionText, callBack) {
notificationsObject.showNotification(message, timeout, actionText, callBack);
}
/**
* @brief This function hides the passive notification at specified index, if any is shown.
* @param index Index of the notification to hide. Default is 0 (oldest notification).
*/
function hidePassiveNotification(index = 0) {
notificationsObject.hideNotification(index);
}
/**
* @brief This property gets application windows object anywhere in the application.
* @returns a pointer to this item.
*/
function applicationWindow() {
return root;
}
//END functions
//BEGIN signals handlers
onMenuBarChanged: {
if (menuBar) {
menuBar.parent = root.contentItem
if (menuBar instanceof T.ToolBar) {
menuBar.position = T.ToolBar.Footer
} else if (menuBar instanceof T.DialogButtonBox) {
menuBar.position = T.DialogButtonBox.Footer
}
menuBar.width = Qt.binding(() => root.contentItem.width)
// FIXME: (root.header.height ?? 0) when we can depend from 5.15
menuBar.y = Qt.binding(() => -menuBar.height - (root.header.height ? root.header.height : 0))
}
}
onHeaderChanged: {
if (header) {
header.parent = root.contentItem
if (header instanceof T.ToolBar) {
header.position = T.ToolBar.Header
} else if (header instanceof T.DialogButtonBox) {
header.position = T.DialogButtonBox.Header
}
header.width = Qt.binding(() => root.contentItem.width)
header.y = Qt.binding(() => -header.height)
}
}
onFooterChanged: {
if (footer) {
footer.parent = root.contentItem
if (footer instanceof T.ToolBar) {
footer.position = T.ToolBar.Footer
} else if (footer instanceof T.DialogButtonBox) {
footer.position = T.DialogButtonBox.Footer
}
footer.width = Qt.binding(() => root.contentItem.width)
footer.y = Qt.binding(() => root.contentItem.height)
}
}
onBackgroundChanged: {
if (background) {
background.parent = root.contentItem
background.anchors.fill = background.parent
}
}
onPageStackChanged: pageStack.parent = root.contentItem;
//END signals handlers
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
TP.PassiveNotificationsManager {
id: notificationsObject
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
z: 1
}
Item {
anchors.fill: parent
parent: root.parent || root
z: 999999
Item {
id: overlayRoot
z: -1
anchors.fill: parent
}
}
// Don't use root.overlay property here. For one, we know that in a window
// it will always be the same as T.Overlay.overlay; secondly Drawers
// don't care about being contained/parented to anything else anyway.
onGlobalDrawerChanged: {
if (globalDrawer) {
globalDrawer.parent = Qt.binding(() => visible ? T.Overlay.overlay : null);
}
}
onContextDrawerChanged: {
if (contextDrawer) {
contextDrawer.parent = Qt.binding(() => visible ? T.Overlay.overlay : null);
}
}
Window.onWindowChanged: {
if (globalDrawer) {
globalDrawer.visible = globalDrawer.drawerOpen;
}
if (contextDrawer) {
contextDrawer.visible = contextDrawer.drawerOpen;
}
}
implicitWidth: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 30 : Kirigami.Units.gridUnit * 55
implicitHeight: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 40
visible: true
}
@@ -0,0 +1,289 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "templates/private" as TP
/**
* A window that provides some basic features needed for all apps
* Use this class only if you need a custom content for your application,
* different from the Page Row behavior recommended by the HIG and provided
* by ApplicationWindow.
* It is recommended to use ApplicationWindow instead
* @see ApplicationWindow
*
* It's usually used as a root QML component for the application.
* It provides support for a central page stack, side drawers, and
* basic support for the Android back button.
*
* Setting a width and height property on the ApplicationWindow
* will set its initial size, but it won't set it as an automatically binding.
* to resize programmatically the ApplicationWindow they need to
* be assigned again in an imperative fashion
*
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack: PageStack {
* ...
* }
* [...]
* }
* @endcode
*
* @inherit QtQuick.Controls.ApplicationWindow
*/
QQC2.ApplicationWindow {
id: root
//BEGIN properties
/**
* @brief This property holds the stack used to allocate the pages and to manage the
* transitions between them.
*
* Put a container here, such as QtQuick.Controls.StackView.
*/
property Item pageStack
/**
* @brief This property sets whether the standard chrome of the app is visible.
*
* These are the action button, the drawer handles, and the application header.
*
* default: ``true``
*/
property bool controlsVisible: true
/**
* @brief This property holds the drawer for global actions.
*
* This drawer can be opened by sliding from the left screen edge
* or by dragging the ActionButton to the right.
*
* @note It is recommended to use the GlobalDrawer here.
* @property org::kde::kirigami::OverlayDrawer globalDrawer
*/
property OverlayDrawer globalDrawer
/**
* @brief This property tells whether the application is in "widescreen" mode.
*
* This is enabled on desktops or horizontal tablets.
*
* @note Different styles can have their own logic for deciding this.
*/
property bool wideScreen: width >= Kirigami.Units.gridUnit * 60
/**
* @brief This property holds the drawer for context-dependent actions.
*
* The drawer that will be opened by sliding from the right screen edge
* or by dragging the ActionButton to the left.
*
* @note It is recommended to use the ContextDrawer type here.
*
* The contents of the context drawer should depend from what page is
* loaded in the main pageStack
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* contextDrawer: Kirigami.ContextDrawer {
* enabled: true
* actions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
* }
* @endcode
*
* @property org::kde::kirigami::ContextDrawer contextDrawer
*/
property OverlayDrawer contextDrawer
/**
* Effectively the same as T.Overlay.overlay
*/
readonly property Item overlay: T.Overlay.overlay
/**
* This property holds a standard action that will quit the application when triggered.
* Its properties have the following values:
*
* @code
* Action {
* text: "Quit"
* icon.name: "application-exit-symbolic"
* shortcut: StandardKey.Quit
* // ...
* }
* @endcode
* @since 5.76
*/
readonly property Kirigami.Action quitAction: Kirigami.Action {
text: qsTr("Quit")
icon.name: "application-exit";
shortcut: StandardKey.Quit
onTriggered: source => root.close();
}
//END properties
//BEGIN functions
/**
* @brief This function shows a little passive notification at the bottom of the app window
* lasting for few seconds, with an optional action button.
*
* @param message The text message to be shown to the user.
* @param timeout How long to show the message:
* possible values: "short", "long" or the number of milliseconds
* @param actionText Text in the action button, if any.
* @param callBack A JavaScript function that will be executed when the
* user clicks the button.
*/
function showPassiveNotification(message, timeout, actionText, callBack) {
notificationsObject.showNotification(message, timeout, actionText, callBack);
}
/**
* @brief This function hides the passive notification at specified index, if any is shown.
* @param index Index of the notification to hide. Default is 0 (oldest notification).
*/
function hidePassiveNotification(index = 0) {
notificationsObject.hideNotification(index);
}
/**
* @brief This function returns application window's object anywhere in the application.
* @returns a pointer to this application window
* can be used anywhere in the application.
*/
function applicationWindow() {
return root;
}
//END functions
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
color: Kirigami.Theme.backgroundColor
TP.PassiveNotificationsManager {
id: notificationsObject
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
z: 1
}
contentItem.z: 1
contentItem.anchors.left: contentItem.parent.left
contentItem.anchors.right: contentItem.parent.right
contentItem.anchors.topMargin: root.wideScreen && header && controlsVisible ? header.height : 0
contentItem.anchors.leftMargin: root.globalDrawer && root.globalDrawer.modal === false && (!root.pageStack || root.pageStack.leftSidebar !== root.globalDrawer) ? root.globalDrawer.width * root.globalDrawer.position : 0
contentItem.anchors.rightMargin: root.contextDrawer && root.contextDrawer.modal === false ? root.contextDrawer.width * root.contextDrawer.position : 0
Binding {
target: root.header
property: "x"
value: -contentItem.x
}
Binding {
target: root.footer
property: "x"
value: -contentItem.x
}
// Don't use root.overlay property here. For one, we know that in a window
// it will always be the same as T.Overlay.overlay; secondly Drawers
// don't care about being contained/parented to anything else anyway.
onGlobalDrawerChanged: {
if (globalDrawer) {
globalDrawer.parent = Qt.binding(() => T.Overlay.overlay);
}
}
onContextDrawerChanged: {
if (contextDrawer) {
contextDrawer.parent = Qt.binding(() => T.Overlay.overlay);
}
}
onPageStackChanged: {
if (pageStack) {
// contentItem is declared as CONSTANT, so binding isn't needed.
pageStack.parent = contentItem;
}
}
width: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 30 : Kirigami.Units.gridUnit * 55
height: Kirigami.Settings.isMobile ? Kirigami.Units.gridUnit * 45 : Kirigami.Units.gridUnit * 40
visible: true
Component.onCompleted: {
// Explicitly break the binding as we need this to be set only at startup.
// if the bindings are active, after this the window is resized by the
// compositor and then the bindings are reevaluated, then the window
// size would reset ignoring what the compositor asked.
// see BUG 433849
root.width = root.width;
root.height = root.height;
}
// This is needed because discover in mobile mode does not
// close with the global drawer open.
Shortcut {
sequence: root.quitAction.shortcut
enabled: root.quitAction.enabled
context: Qt.ApplicationShortcut
onActivated: root.close();
}
}
@@ -0,0 +1,31 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import "templates" as T
import "private" as P
/**
* @brief AbstractCard is the base for cards.
*
* A Card is a visual object that serves as an entry point for more detailed information.
* An abstractCard is empty, providing just the look and the base properties and signals
* for an ItemDelegate. It can be filled with any custom layout of items,
* its content is organized in 3 properties: header, contentItem and footer.
* Use this only when you need particular custom contents, for a standard layout
* for cards, use the Card component.
*
* @see org::kde::kirigami::Card
* @inherit org::kde::kirigami::templates::AbstractCard
* @since 2.4
*/
T.AbstractCard {
id: root
background: P.DefaultCardBackground {
clickFeedback: root.showClickFeedback
hoverFeedback: root.hoverEnabled
}
}
@@ -0,0 +1,160 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQml
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import org.kde.kirigami.private as P
/**
* @brief An item that represents an abstract Action
* @inherit QtQuick.QQC2.Action
*/
QQC2.Action {
//BEGIN properties
/**
* @brief This property holds whether the graphic representation of the action
* is supposed to be visible.
*
* It's up to the action representation to honor this property.
*
* default: ``true``
*/
property bool visible: !fromQAction || fromQAction.visible
/**
* @brief This property holds the tooltip text that is shown when the cursor is hovering over the control.
*
* Leaving this undefined or setting it to an empty string means that no tooltip will be shown when
* the cursor is hovering over the control that triggers the tooltip.
* @warning Tooltips may not be supported on all platforms.
*/
property string tooltip
/**
* @brief This property sets whether this action is a separator action.
*
* default: ``false``
*/
property bool separator: false
/**
* @brief This property holds whether auto-exclusivity is enabled.
*
* If auto-exclusivity is enabled, checkable actions that belong to the
* same parent item behave as if they were part of the same ButtonGroup.
* Only one action can be checked at any time; checking another action
* automatically unchecks the previously checked one.
*
* default: ``false``
*/
property bool autoExclusive: false
/**
* @brief This property sets whether this action becomes a title displaying
* its child actions as sub-items in GlobalDrawers and ContextDrawers.
*
* default: ``false``
*
* @since 2.6
*/
property bool expandible: false
/**
* @brief This property holds the parent action.
*/
property T.Action parent
/**
* @brief This property sets this action's display type.
*
* These are provided to implementations to indicate a preference for certain display
* styles.
*
* default: ``Kirigami.DisplayHint.NoPreference``
*
* @note This property contains only preferences, implementations may choose to disregard them.
* @see org::kde::kirigami::DisplayHint
* @since 2.12
*/
property int displayHint: Kirigami.DisplayHint.NoPreference
/**
* @brief This property holds the component that should be used for displaying this action.
* @note This can be used to display custom components in the toolbar.
* @since 5.65
* @since 2.12
*/
property Component displayComponent
/**
* @brief This property holds a list of child actions.
*
* This is useful for tree-like menus, such as the GlobalDrawer.
*
* Example usage:
* @code
* import QtQuick.Controls as QQC2
* import org.kde.kirigami as Kirigami
*
* Kirigami.Action {
* text: "Tools"
*
* QQC2.Action {
* text: "Action1"
* }
* Kirigami.Action {
* text: "Action2"
* }
* }
* @endcode
* @property list<T.Action> children
*/
default property list<T.Action> children
/**
* This property holds a QAction
*
* When provided Kirigami.Action will be initialized from the given QAction.
*
* @since Kirigami 6.4.0
*/
property QtObject fromQAction
//END properties
onChildrenChanged: {
children
.filter(action => action instanceof Kirigami.Action)
.forEach(action => {
action.parent = this;
});
}
/**
* @brief This property holds the action's visible child actions.
* @property list<T.Action> visibleChildren
*/
readonly property list<T.Action> visibleChildren: children
.filter(action => !(action instanceof Kirigami.Action) || action.visible)
shortcut: fromQAction?.shortcut
text: fromQAction?.text ?? ''
icon.name: fromQAction ? P.ActionHelper.iconName(fromQAction.icon) : ''
onTriggered: if (fromQAction) {
fromQAction.trigger();
}
checkable: fromQAction?.checkable ?? false
checked: fromQAction?.checked ?? false
enabled: !fromQAction || fromQAction.enabled
readonly property Shortcut alternateShortcut : Shortcut {
sequences: P.ActionHelper.alternateShortcuts(fromQAction)
onActivated: root.trigger()
}
}
@@ -0,0 +1,191 @@
/*
* SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* This is advanced textfield. It is recommended to use this class when there
* is a need to create a create a textfield with action buttons (e.g a clear
* action).
*
* Example usage for a search field:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ActionTextField {
* id: searchField
*
* placeholderText: i18n("Search…")
*
* focusSequence: StandardKey.Find
*
* rightActions: Kirigami.Action {
* icon.name: "edit-clear"
* visible: searchField.text.length > 0
* onTriggered: {
* searchField.clear();
* searchField.accepted();
* }
* }
*
* onAccepted: console.log("Search text is " + searchField.text);
* }
* @endcode
*
* @since 5.56
* @inherit QtQuick.Controls.TextField
*/
QQC2.TextField {
id: root
/**
* @brief This property holds a shortcut sequence that will focus the text field.
* @since 5.56
*/
property alias focusSequence: focusShortcut.sequence
/**
* @brief This property holds a list of actions that will be displayed on the left side of the text field.
*
* By default this list is empty.
*
* @since 5.56
*/
property list<T.Action> leftActions
/**
* @brief This property holds a list of actions that will be displayed on the right side of the text field.
*
* By default this list is empty.
*
* @since 5.56
*/
property list<T.Action> rightActions
property alias _leftActionsRow: leftActionsRow
property alias _rightActionsRow: rightActionsRow
hoverEnabled: true
// Manually setting this fixes alignment in RTL layouts
horizontalAlignment: TextInput.AlignLeft
leftPadding: Kirigami.Units.smallSpacing + (root.effectiveHorizontalAlignment === TextInput.AlignRight ? rightActionsRow : leftActionsRow).width
rightPadding: Kirigami.Units.smallSpacing + (root.effectiveHorizontalAlignment === TextInput.AlignRight ? leftActionsRow : rightActionsRow).width
Behavior on leftPadding {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on rightPadding {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Shortcut {
id: focusShortcut
enabled: root.visible && root.enabled
onActivated: {
root.forceActiveFocus(Qt.ShortcutFocusReason)
root.selectAll()
}
}
QQC2.ToolTip {
visible: focusShortcut.nativeText.length > 0 && root.text.length === 0 && root.hovered
text: focusShortcut.nativeText
}
component InlineActionIcon: Kirigami.Icon {
id: iconDelegate
required property T.Action modelData
implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
anchors.verticalCenter: parent.verticalCenter
source: modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source
visible: !(modelData instanceof Kirigami.Action) || modelData.visible
active: actionArea.containsPress || actionArea.activeFocus
enabled: modelData.enabled
MouseArea {
id: actionArea
anchors.fill: parent
activeFocusOnTab: true
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
Accessible.name: iconDelegate.modelData.text
Accessible.role: Accessible.Button
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
clicked(null);
event.accepted = true;
break;
}
}
onClicked: mouse => iconDelegate.modelData.trigger()
}
QQC2.ToolTip {
visible: (actionArea.containsMouse || actionArea.activeFocus) && (iconDelegate.modelData.text.length > 0)
text: iconDelegate.modelData.text
}
}
Row {
id: leftActionsRow
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
layoutDirection: Qt.LeftToRight
LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
anchors.left: parent.left
anchors.leftMargin: Kirigami.Units.smallSpacing
anchors.top: parent.top
anchors.topMargin: parent.topPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: parent.bottomPadding
Repeater {
model: root.leftActions
InlineActionIcon { }
}
}
Row {
id: rightActionsRow
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
layoutDirection: Qt.RightToLeft
LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
anchors.right: parent.right
anchors.rightMargin: Kirigami.Units.smallSpacing
anchors.top: parent.top
anchors.topMargin: parent.topPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: parent.bottomPadding
Repeater {
model: root.rightActions
InlineActionIcon { }
}
}
}
@@ -0,0 +1,243 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as P
/**
* @brief A toolbar built out of a list of actions.
*
* The default representation for visible actions is a QtQuick.Controls.ToolButton, but
* it can be changed by setting the `Action.displayComponent` for an action.
* The default behavior of ActionToolBar is to display as many actions as possible,
* placing those that will not fit into an overflow menu. This can be changed by
* setting the `displayHint` property on an Action. For example, when setting the
* `DisplayHint.KeepVisible` display hint, ActionToolBar will try to keep that action
* in view as long as possible, using an icon-only button if a button with text
* does not fit.
*
* @inherit QtQuick.Controls.Control
* @since 2.5
*/
QQC2.Control {
id: root
//BEGIN properties
/**
* @brief This property holds a list of visible actions.
*
* The ActionToolBar will try to display as many actions as possible.
* Those that won't fit will go into an overflow menu.
*
* @property list<Action> actions
*/
readonly property alias actions: layout.actions
/**
* @brief This property holds whether the buttons will have a flat appearance.
*
* default: ``true``
*/
property bool flat: true
/**
* @brief This property determines how the icon and text are displayed within the button.
*
* Permitted values are:
* * ``Button.IconOnly``
* * ``Button.TextOnly``
* * ``Button.TextBesideIcon``
* * ``Button.TextUnderIcon``
*
* default: ``Controls.Button.TextBesideIcon``
*
* @see QtQuick.Controls.AbstractButton
* @property int display
*/
property int display: QQC2.Button.TextBesideIcon
/**
* @brief This property holds the alignment of the buttons.
*
* When there is more space available than required by the visible delegates,
* we need to determine how to place the delegates.
*
* When there is more space available than required by the visible action delegates,
* we need to determine where to position them.
*
* default: ``Qt.AlignLeft``
*
* @see Qt::AlignmentFlag
* @property int alignment
*/
property alias alignment: layout.alignment
/**
* @brief This property holds the position of the toolbar.
*
* If this ActionToolBar is the contentItem of a QQC2 Toolbar, the position is bound to the ToolBar's position
*
* Permitted values are:
* * ``ToolBar.Header``: The toolbar is at the top, as a window or page header.
* * ``ToolBar.Footer``: The toolbar is at the bottom, as a window or page footer.
*
* @property int position
*/
property int position: parent instanceof T.ToolBar ? parent.position : QQC2.ToolBar.Header
/**
* @brief This property holds the maximum width of the content of this ToolBar.
*
* If the toolbar's width is larger than this value, empty space will
* be added on the sides, according to the Alignment property.
*
* The value of this property is derived from the ToolBar's actions and their properties.
*
* @property int maximumContentWidth
*/
readonly property alias maximumContentWidth: layout.implicitWidth
/**
* @brief This property holds the name of the icon to use for the overflow menu button.
*
* default: ``"overflow-menu"``
*
* @since 5.65
* @since 2.12
*/
property string overflowIconName: "overflow-menu"
/**
* @brief This property holds the combined width of all visible delegates.
* @property int visibleWidth
*/
readonly property alias visibleWidth: layout.visibleWidth
/**
* @brief This property sets the handling method for items that do not match the toolbar's height.
*
* When toolbar items do not match the height of the toolbar, there are
* several ways we can deal with this. This property sets the preferred way.
*
* Permitted values are:
* * ``HeightMode.AlwaysCenter``
* * ``HeightMode.AlwaysFill``
* * ``AlwaysFill.ConstrainIfLarger``
*
* default: ``HeightMode::ConstrainIfLarger``
*
* @see ToolBarLayout::heightMode
* @see ToolBarLayout::HeightMode
* @property ToolBarLayout::HeightMode heightMode
*/
property alias heightMode: layout.heightMode
//END properties
implicitHeight: layout.implicitHeight
implicitWidth: layout.implicitWidth
Layout.minimumWidth: layout.minimumWidth
Layout.preferredWidth: 0
Layout.fillWidth: true
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
Accessible.role: Accessible.ToolBar
contentItem: Kirigami.ToolBarLayout {
id: layout
spacing: Kirigami.Units.smallSpacing
layoutDirection: root.mirrored ? Qt.RightToLeft : Qt.LeftToRight
fullDelegate: P.PrivateActionToolButton {
flat: root.flat
display: root.display
action: Kirigami.ToolBarLayout.action
}
iconDelegate: P.PrivateActionToolButton {
flat: root.flat
display: QQC2.Button.IconOnly
action: Kirigami.ToolBarLayout.action
showMenuArrow: false
menuActions: {
if (action.displayComponent) {
return [action]
}
if (action instanceof Kirigami.Action) {
return action.children;
}
return []
}
}
separatorDelegate: QQC2.ToolSeparator {}
moreButton: P.PrivateActionToolButton {
flat: root.flat
action: Kirigami.Action {
tooltip: qsTr("More Actions")
icon.name: root.overflowIconName
displayHint: Kirigami.DisplayHint.IconOnly | Kirigami.DisplayHint.HideChildIndicator
}
Accessible.name: action.tooltip
menuActions: root.actions
menuComponent: P.ActionsMenu {
submenuComponent: P.ActionsMenu {
Binding {
target: parentItem
property: "visible"
value: layout.hiddenActions.includes(parentAction)
&& (!(parentAction instanceof Kirigami.Action) || parentAction.visible)
restoreMode: Binding.RestoreBinding
}
Binding {
target: parentItem
property: "autoExclusive"
value: action instanceof Kirigami.Action && action.autoExclusive
restoreMode: Binding.RestoreBinding
}
}
itemDelegate: P.ActionMenuItem {
visible: layout.hiddenActions.includes(action)
&& (!(action instanceof Kirigami.Action) || action.visible)
autoExclusive: action instanceof Kirigami.Action && action.autoExclusive
}
loaderDelegate: Loader {
property T.Action action
height: visible ? implicitHeight : 0
visible: layout.hiddenActions.includes(action)
&& (!(action instanceof Kirigami.Action) || action.visible)
}
separatorDelegate: QQC2.MenuSeparator {
property T.Action action
visible: layout.hiddenActions.includes(action)
&& (!(action instanceof Kirigami.Action) || action.visible)
}
}
}
}
}
@@ -0,0 +1,141 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief An item that provides the features of ApplicationWindow without the window itself.
*
* This allows embedding into a larger application.
* It's based around the PageRow component that allows adding/removing of pages.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationItem {
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack.initialPage: Kirigami.Page {
* mainAction: Kirigami.Action {
* icon.name: "edit"
* onTriggered: {
* // do stuff
* }
* }
* contextualActions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* // ...
* }
* }
* @endcode
*/
Kirigami.AbstractApplicationItem {
id: root
/**
* @brief This property holds the PageRow used to allocate the pages and
* manage the transitions between them.
*
* It's using a PageRow, while having the same API as PageStack,
* it positions the pages as adjacent columns, with as many columns
* as can fit in the screen. An handheld device would usually have a single
* fullscreen column, a tablet device would have many tiled columns.
*
* @property org::kde::kirigami::PageRow pageStack
*/
readonly property alias pageStack: __pageStack
// Redefines here as here we can know a pointer to PageRow
wideScreen: width >= applicationWindow().pageStack.defaultColumnWidth * 2
Component.onCompleted: {
pageStack.currentItem?.forceActiveFocus();
}
Kirigami.PageRow {
id: __pageStack
anchors {
fill: parent
}
function goBack() {
// NOTE: drawers are handling the back button by themselves
const backEvent = {accepted: false}
if (root.pageStack.currentIndex >= 1) {
root.pageStack.currentItem.backRequested(backEvent);
if (!backEvent.accepted) {
root.pageStack.flickBack();
backEvent.accepted = true;
}
}
if (Kirigami.Settings.isMobile && !backEvent.accepted && Qt.platform.os !== "ios") {
Qt.quit();
}
}
function goForward() {
root.pageStack.currentIndex = Math.min(root.pageStack.depth - 1, root.pageStack.currentIndex + 1);
}
Keys.onBackPressed: event => {
goBack();
event.accepted = true;
}
Shortcut {
sequences: [StandardKey.Forward]
onActivated: __pageStack.goForward();
}
Shortcut {
sequences: [StandardKey.Back]
onActivated: __pageStack.goBack();
}
background: Rectangle {
color: root.color
}
focus: true
}
}
@@ -0,0 +1,120 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief A window that provides some basic features needed for all apps
*
* It's usually used as a root QML component for the application.
* It's based around the PageRow component, the application will be
* about pages adding and removal.
* For most of the usages, this class should be used instead
* of AbstractApplicationWindow
* @see AbstractApplicationWindow
*
* Setting a width and height property on the ApplicationWindow
* will set its initial size, but it won't set it as an automatically binding.
* to resize programmatically the ApplicationWindow they need to
* be assigned again in an imperative fashion
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
*
* contextDrawer: Kirigami.ContextDrawer {
* id: contextDrawer
* }
*
* pageStack.initialPage: Kirigami.Page {
* mainAction: Kirigami.Action {
* icon.name: "edit"
* onTriggered: {
* // do stuff
* }
* }
* contextualActions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* [...]
* }
* [...]
* }
* @endcode
*
*/
Kirigami.AbstractApplicationWindow {
id: root
/**
* @brief This property holds the stack used to allocate the pages and to
* manage the transitions between them.
*
* It's using a PageRow, while having the same API as PageStack,
* it positions the pages as adjacent columns, with as many columns
* as can fit in the screen. An handheld device would usually have a single
* fullscreen column, a tablet device would have many tiled columns.
*
* @property org::kde::kirigami::PageRow pageStack
*/
readonly property alias pageStack: __pageStack
// Redefined here as here we can know a pointer to PageRow.
// We negate the canBeEnabled check because we don't want to factor in the automatic drawer provided by Kirigami for page actions for our calculations
wideScreen: width >= (root.pageStack.defaultColumnWidth) + ((contextDrawer && !(contextDrawer instanceof Kirigami.ContextDrawer)) ? contextDrawer.width : 0) + (globalDrawer ? globalDrawer.width : 0)
Component.onCompleted: {
pageStack.currentItem?.forceActiveFocus()
}
Kirigami.PageRow {
id: __pageStack
globalToolBar.style: Kirigami.ApplicationHeaderStyle.Auto
anchors {
fill: parent
}
focus: true
}
}
@@ -0,0 +1,117 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as P
/**
* @brief This is the standard layout of a Card.
*
* It is recommended to use this class when the concept of Cards is needed
* in the application.
*
* This Card has default items as header and footer. The header is an
* image that can contain an optional title and icon, accessible via the
* banner grouped property.
*
* The footer will show a series of toolbuttons (and eventual overflow menu)
* representing the actions list accessible with the list property actions.
* It is possible even tough is discouraged to override the footer:
* in this case the actions property shouldn't be used.
*
* @inherit org::kde::kirigami::AbstractCard
* @since 2.4
*/
Kirigami.AbstractCard {
id: root
/**
* @brief This property holds the clickable actions that will be available in the footer
* of the card.
*
* The actions will be represented by a list of ToolButtons with an optional overflow
* menu, when not all of them will fit in the available Card width.
*
* @property list<T.Action> actions
*/
property list<T.Action> actions
/**
* @brief This grouped property controls the banner image present in the header.
*
* This grouped property has the following sub-properties:
* * ``source: url``: The source for the image. It understands any URL valid for an Image component.
* * ``titleIcon: string``: The optional icon to put in the banner, either a freedesktop-compatible
* icon name (recommended) or any URL supported by QtQuick.Image.
* * ``title: string``: The title for the banner, shown as contrasting text over the image.
* * ``titleAlignment: Qt::Alignment``: The alignment of the title inside the image.
* default: ``Qt.AlignTop | Qt.AlignLeft``
* * ``titleLevel: int``: The Kirigami.Heading level for the title, which controls the font size.
* default: ``1``, which is the largest size.
* * ``titleWrapMode: QtQuick.Text::wrapMode``: Whether the header text should be able to wrap.
* default: ``Text.NoWrap``
*
* It also has the full set of properties that QtQuick.Image has, such as sourceSize and fillMode.
*
* @see org::kde::kirigami::private::BannerImage
* @property Image banner
*/
readonly property alias banner: bannerImage
Accessible.name: banner.title
header: Kirigami.Padding {
topPadding: -root.topPadding + root.background.border.width
leftPadding: -root.leftPadding + root.background.border.width
rightPadding: -root.rightPadding + root.background.border.width
bottomPadding: root.contentItem ? 0 : -root.bottomPadding + root.background.border.width
contentItem: P.BannerImage {
id: bannerImage
implicitWidth: Layout.preferredWidth
implicitHeight: (source.toString().length > 0 && sourceSize.width > 0 && sourceSize.height > 0 ? width / (sourceSize.width / sourceSize.height) : Layout.minimumHeight) + parent.topPadding + parent.bottomPadding
readonly property real widthWithBorder: width + root.background.border.width * 2
readonly property real heightWithBorder: height + root.background.border.width * 2
readonly property real radiusFromBackground: root.background.radius - root.background.border.width
corners.topLeftRadius: radiusFromBackground
corners.topRightRadius: radiusFromBackground
corners.bottomLeftRadius: radiusFromBackground
corners.bottomRightRadius: heightWithBorder < root.height ? 0 : radiusFromBackground
checkable: root.checkable
checked: root.checkable && root.checked
onToggled: checked => {
root.checked = checked;
root.toggled(checked);
}
}
}
onHeaderChanged: {
if (!header) {
return;
}
header.anchors.topMargin = Qt.binding(() => -root.topPadding);
header.anchors.leftMargin = Qt.binding(() => -root.leftPadding);
header.anchors.rightMargin = Qt.binding(() => -root.rightPadding);
header.anchors.bottomMargin = Qt.binding(() => 0);
}
footer: Kirigami.ActionToolBar {
id: actionsToolBar
actions: root.actions
position: QQC2.ToolBar.Footer
}
}
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief A GridLayout optimized for showing one or two columns of cards,
* depending on the available space.
*
* It Should be used when the cards are not instantiated by a model or by a
* model which has always very few items.
*
* They are presented as a grid of two columns which will remain
* centered if the application is really wide, or become a single
* column if there is not enough space for two columns,
* such as a mobile phone screen.
*
* A CardsLayout should always be contained within a ColumnLayout.
*
* @since 2.4
* @inherit QtQuick.Layouts.GridLayout
*/
GridLayout {
/**
* @brief This property holds the maximum number of columns.
*
* This layout will never lay out the items in more columns than maximumColumns
*
* default: ``2``
*
* @since 2.5
*/
property int maximumColumns: 2
/**
* @brief This property holds the maximum width the columns may have.
*
* The cards will never become wider than this size; when the GridLayout is wider than
* maximumColumnWidth, it will switch from one to two columns.
*
* If the default needs to be overridden for some reason,
* it is advised to express this unit as a multiple
* of Kirigami.Units.gridUnit.
*
* default: ``20 * Kirigami.Units.gridUnit``
*/
property int maximumColumnWidth: Kirigami.Units.gridUnit * 20
/**
* @brief This property holds the minimum width the columns may have.
*
* The layout will try to dispose of items
* in a number of columns that will respect this size constraint.
*
* default: ``12 * Kirigami.Units.gridUnit``
*
* @since 2.5
*/
property int minimumColumnWidth: Kirigami.Units.gridUnit * 12
columns: Math.max(1, Math.min(maximumColumns > 0 ? maximumColumns : Infinity,
Math.floor(width/minimumColumnWidth),
Math.ceil(width/maximumColumnWidth)));
rowSpacing: Kirigami.Units.largeSpacing
columnSpacing: Kirigami.Units.largeSpacing
// NOTE: this default width which defaults to 2 columns is just to remove a binding loop on columns
width: maximumColumnWidth*2 + Kirigami.Units.largeSpacing
// same computation of columns, but on the parent size
Layout.preferredWidth: maximumColumnWidth * Math.max(1, Math.min(maximumColumns > 0 ? maximumColumns : Infinity,
Math.floor(parent.width/minimumColumnWidth),
Math.ceil(parent.width/maximumColumnWidth))) + Kirigami.Units.largeSpacing * (columns - 1)
Layout.maximumWidth: Layout.preferredWidth
Layout.alignment: Qt.AlignHCenter
Component.onCompleted: childrenChanged()
onChildrenChanged: {
for (const child of children) {
child.Layout.fillHeight = true;
}
}
}
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* CardsListView is a ListView which can have AbstractCard as its delegate: it will
* automatically assign the proper spacings and margins around the cards adhering
* to the design guidelines.
*
* CardsListView should be used only with cards which can look good at any
* horizontal size, so it is recommended to directly use AbstractCard with an
* appropriate layout inside, because they are stretching for the whole list width.
*
* Therefore, it is discouraged to use it with the Card type.
*
* The choice between using this view with AbstractCard or a normal ListView
* is purely a choice based on aesthetics alone.
*
* It is recommended to use default values.
*
* @inherit QtQuick.ListView
* @since 2.4
*/
ListView {
id: root
spacing: Kirigami.Units.largeSpacing * 2
topMargin: headerPositioning !== ListView.InlineHeader ? spacing : 0
rightMargin: Kirigami.Units.largeSpacing * 2
leftMargin: Kirigami.Units.largeSpacing * 2
reuseItems: true
headerPositioning: ListView.OverlayHeader
Keys.onPressed: event => {
if (event.key === Qt.Key_Home) {
positionViewAtBeginning();
currentIndex = 0;
event.accepted = true;
}
else if (event.key === Qt.Key_End) {
positionViewAtEnd();
currentIndex = count - 1;
event.accepted = true;
}
}
}
@@ -0,0 +1,96 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import "templates" as KT
import "private" as P
/**
* @brief A compact element that represents an attribute, action, or filter.
*
* Should be used in a group of multiple elements. e.g when displaying tags in a image viewer.
*
* Example usage:
* * @code
* import org.kde.kirigami as Kirigami
*
* Flow {
* Repeater {
* model: chipsModel
*
* Kirigami.Chip {
* text: model.text
* icon.name: "tag-symbolic"
* closable: model.closable
* onClicked: {
* [...]
* }
* onRemoved: {
* [...]
* }
* }
* }
* }
* @endcode
*
* @since 2.19
*/
KT.Chip {
id: chip
implicitWidth: layout.implicitWidth
implicitHeight: toolButton.implicitHeight
checkable: !closable
hoverEnabled: true
/**
* @brief This property holds the label item; used for accessing the usual Text properties.
* @property QtQuick.Controls.Label labelItem
*/
property alias labelItem: label
contentItem: RowLayout {
id: layout
spacing: 0
Kirigami.Icon {
id: icon
visible: icon.valid
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.leftMargin: Kirigami.Units.smallSpacing
color: chip.icon.color
isMask: chip.iconMask
source: chip.icon.name || chip.icon.source
}
QQC2.Label {
id: label
Layout.fillWidth: true
Layout.minimumWidth: Kirigami.Units.gridUnit * 1.5
Layout.leftMargin: icon.visible ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
Layout.rightMargin: chip.closable ? Kirigami.Units.smallSpacing : Kirigami.Units.largeSpacing
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
text: chip.text
color: Kirigami.Theme.textColor
elide: Text.ElideRight
}
QQC2.ToolButton {
id: toolButton
visible: chip.closable
text: qsTr("Remove Tag")
icon.name: "edit-delete-remove"
icon.width: Kirigami.Units.iconSizes.sizeForLabels
icon.height: Kirigami.Units.iconSizes.sizeForLabels
display: QQC2.AbstractButton.IconOnly
onClicked: chip.removed()
}
}
background: P.DefaultChipBackground {}
}
@@ -0,0 +1,182 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as KP
/**
* A specialized type of drawer that will show a list of actions
* relevant to the application's current page.
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* contextDrawer: Kirigami.ContextDrawer {
* enabled: true
* actions: [
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: "Action text"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
* }
* @endcode
*
* @inherit OverlayDrawer
*/
Kirigami.OverlayDrawer {
id: root
handleClosedIcon.source: null
handleOpenIcon.source: null
/**
* @brief A title for the action list that will be shown to the user when opens the drawer
*
* default: ``qsTr("Actions")``
*/
property string title: qsTr("Actions")
/**
* List of contextual actions to be displayed in a ListView.
*
* @see QtQuick.Action
* @see org::kde::kirigami::Action
* @property list<T.Action> actions
*/
property list<T.Action> actions
/**
* @brief Arbitrary content to show above the list view.
*
* default: `an Item containing a Kirigami.Heading that displays a title whose text is
* controlled by the title property.`
*
* @property Component header
* @since 2.7
*/
property alias header: menu.header
/**
* @brief Arbitrary content to show below the list view.
* @property Component footer
* @since 2.7
*/
property alias footer: menu.footer
// Not stored in a property, so we don't have to waste memory on an extra list.
function visibleActions() {
return actions.filter(
action => !(action instanceof Kirigami.Action) || action.visible
);
}
// Disable for empty menus or when we have a global toolbar
enabled: {
const pageStack = typeof applicationWindow !== "undefined" ? applicationWindow().pageStack : null;
const itemExistsButStyleIsNotToolBar = item => item && item.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar;
return menu.count > 0
&& (!pageStack
|| !pageStack.globalToolBar
|| (pageStack.layers.depth > 1
&& itemExistsButStyleIsNotToolBar(pageStack.layers.currentItem))
|| itemExistsButStyleIsNotToolBar(pageStack.trailingVisibleItem));
}
edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.LeftEdge : Qt.RightEdge
drawerOpen: false
// list items go to edges, have their own padding
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
property bool handleVisible: {
if (typeof applicationWindow === "function") {
const w = applicationWindow();
if (w) {
return w.controlsVisible;
}
}
// For a ContextDrawer its handle is hidden by default
return false;
}
contentItem: QQC2.ScrollView {
// this just to create the attached property
Kirigami.Theme.inherit: true
implicitWidth: Kirigami.Units.gridUnit * 20
ListView {
id: menu
interactive: contentHeight > height
model: root.visibleActions()
topMargin: root.handle.y > 0 ? menu.height - menu.contentHeight : 0
header: QQC2.ToolBar {
height: pageStack.globalToolBar.preferredHeight
width: parent.width
Kirigami.Heading {
id: heading
elide: Text.ElideRight
text: root.title
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
leftMargin: Kirigami.Units.largeSpacing
rightMargin: Kirigami.Units.largeSpacing
}
}
}
delegate: Column {
id: delegate
required property T.Action modelData
width: parent.width
KP.ContextDrawerActionItem {
tAction: delegate.modelData
width: parent.width
}
Repeater {
model: delegate.modelData instanceof Kirigami.Action && delegate.modelData.expandible
? delegate.modelData.children : null
delegate: KP.ContextDrawerActionItem {
width: parent.width
leftPadding: Kirigami.Units.gridUnit
opacity: !root.collapsed
}
}
}
}
}
}
@@ -0,0 +1,92 @@
/*
SPDX-FileCopyrightText: 2020 Felix Ernst <fe.a.ernst@gmail.com>
SPDX-FileCopyrightText: 2024 Nate Graham <nate@kde.org>
SPDX-FileCopyrightText: 2024 ivan tkachenko <me@ratijas.tk>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief An inline help button that shows a tooltip when clicked.
*
* Use this component when you want to explain details or usage of a feature of
* the UI, but the explanation is too long to fit in an inline label, and too
* important to put in a hover tooltip and risk the user missing it.
*
* @image html ContextualHelpButton.png "Example of ContextualHelpButton usage"
*
* Example usage:
* @code{.qml}
* import QtQuick.Controls as QQC2
* import QtQuick.Layouts
* import org.kde.kirigami as Kirigami
*
* RowLayout {
* spacing: Kirigami.Units.smallSpacing
*
* QQC2.CheckBox {
* text: i18n("Allow screen tearing in fullscreen windows")
* }
*
* Kirigami.ContextualHelpButton {
* toolTipText: i18n("With most displays, screen tearing reduces latency at the cost of some visual fidelity at high framerates. Note that not all graphics drivers support this setting.")
* }
* }
*
* @endcode
*/
QQC2.ToolButton {
id: root
property alias toolTipText: toolTip.text
property bool toolTipVisible: false
text: qsTr("Show Contextual Help")
icon.name: "help-contextual-symbolic"
display: QQC2.ToolButton.IconOnly
Accessible.description: toolTipText
onReleased: {
toolTip.delay = toolTipVisible ? Kirigami.Units.toolTipDelay : 0;
toolTipVisible = !toolTipVisible;
}
onActiveFocusChanged: {
toolTip.delay = Kirigami.Units.toolTipDelay;
toolTipVisible = false;
}
Layout.maximumHeight: parent?.height ?? -1
QQC2.ToolTip {
id: toolTip
clip: true
visible: root.hovered || root.toolTipVisible || toolTipHandler.hovered
onVisibleChanged: {
if (!visible && root.toolTipVisible) {
root.toolTipVisible = false;
delay = Kirigami.Units.toolTipDelay;
}
}
timeout: -1 // Don't disappear while the user might still be reading it!
HoverHandler {
// Also keep the tooltip open while hovering it
// Fixes the flickering when the popup covers the button
id: toolTipHandler
enabled: !root.toolTipVisible // Only if activated by hovering
}
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.WhatsThisCursor
acceptedButtons: Qt.NoButton
}
}
@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
//TODO KF6: how much is this used? can be removed?
/**
* @brief FlexColumn is a column that grows in width to a fixed cap.
* @inherit QtQuick.Layouts.ColumnLayout
*/
ColumnLayout {
id: __outer
default property alias columnChildren: __inner.children
/**
* @brief This property holds the column's offset from the cross axis.
*
* Note that padding is applied on both sides
* when the column is aligned to a centered cross axis.
*
* default: ``Kirigami.Units.largeSpacing``
*/
property real padding: Kirigami.Units.largeSpacing
/**
* @brief This property holds maximum column width.
*
* default: ``Kirigami.Units.gridUnit * 50``
*/
property real maximumWidth: Kirigami.Units.gridUnit * 50
/**
* @brief This property sets column's alignment when it hits its maximum width.
*
* default: ``Qt.AlignHCenter | Qt.AlignTop``
*
* @property Qt::Alignment alignment
*/
property int alignment: Qt.AlignHCenter | Qt.AlignTop
/**
* @brief This property holds the inner column's width.
*/
property real innerWidth: __inner.width
Layout.fillWidth: true
Layout.fillHeight: true
enum CrossAxis {
Left,
Center,
Right
}
ColumnLayout {
id: __inner
spacing: __outer.spacing
Layout.maximumWidth: __outer.maximumWidth
Layout.leftMargin: __outer.alignment & Qt.AlignLeft || __outer.alignment & Qt.AlignHCenter ? __outer.padding : 0
Layout.rightMargin: __outer.alignment & Qt.AlignRight || __outer.alignment & Qt.AlignHCenter ? __outer.padding : 0
Layout.alignment: __outer.alignment
}
}
@@ -0,0 +1,627 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* 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
import "private" as KP
/**
* A specialized form of the Drawer intended for showing an application's
* always-available global actions. Think of it like a mobile version of
* a desktop application's menubar.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
* }
* @endcode
*/
Kirigami.OverlayDrawer {
id: root
edge: Qt.application.layoutDirection === Qt.RightToLeft ? Qt.RightEdge : Qt.LeftEdge
handleClosedIcon.source: null
handleOpenIcon.source: null
handleVisible: {
// When drawer is inline with content and opened, there is no point is showing handle.
if (!modal && drawerOpen) {
return false;
}
// GlobalDrawer can be hidden by controlsVisible...
if (typeof applicationWindow === "function") {
const w = applicationWindow();
if (w && !w.controlsVisible) {
return false;
}
}
// ...but it still performs additional checks.
return !isMenu || Kirigami.Settings.isMobile;
}
enabled: !isMenu || Kirigami.Settings.isMobile
//BEGIN properties
/**
* @brief This property holds the title displayed at the top of the drawer.
* @see org::kde::kirigami::private::BannerImage::title
* @property string title
*/
property string title
/**
* @brief This property holds an icon to be displayed alongside the title.
* @see org::kde::kirigami::private::BannerImage::titleIcon
* @see org::kde::kirigami::Icon::source
* @property var titleIcon
*/
property var titleIcon
/**
* @brief This property holds the actions displayed in the drawer.
*
* The list of actions can be nested having a tree structure.
* A tree depth bigger than 2 is discouraged.
*
* Example usage:
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [
* Kirigami.Action {
* text: "View"
* icon.name: "view-list-icons"
* Kirigami.Action {
* text: "action 1"
* }
* Kirigami.Action {
* text: "action 2"
* }
* Kirigami.Action {
* text: "action 3"
* }
* },
* Kirigami.Action {
* text: "Sync"
* icon.name: "folder-sync"
* }
* ]
* }
* }
* @endcode
* @property list<T.Action> actions
*/
property list<T.Action> actions
/**
* @brief This property holds an item that will always be displayed at the top of the drawer.
*
* If the drawer contents can be scrolled, this item will stay still and won't scroll.
*
* @note This property is mainly intended for toolbars.
* @since 2.12
*/
property alias header: mainLayout.header
/**
* @brief This property holds an item that will always be displayed at the bottom of the drawer.
*
* If the drawer contents can be scrolled, this item will stay still and won't scroll.
*
* @note This property is mainly intended for toolbars.
* @since 6.0
*/
property alias footer: mainLayout.footer
/**
* @brief This property holds items that are displayed above the actions.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [...]
* topContent: [Button {
* text: "Button"
* onClicked: //do stuff
* }]
* }
* [...]
* }
* @endcode
* @property list<QtObject> topContent
*/
property alias topContent: topContent.data
/**
* @brief This property holds items that are displayed under the actions.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* [...]
* globalDrawer: Kirigami.GlobalDrawer {
* actions: [...]
* Button {
* text: "Button"
* onClicked: //do stuff
* }
* }
* [...]
* }
* @endcode
* @note This is a `default` property.
* @property list<QtObject> content
*/
default property alias content: mainContent.data
/**
* @brief This property sets whether content items at the top should be shown.
* when the drawer is collapsed as a sidebar.
*
* If you want to keep some items visible and some invisible, set this to
* false and control the visibility/opacity of individual items,
* binded to the collapsed property
*
* default: ``false``
*
* @since 2.5
*/
property bool showTopContentWhenCollapsed: false
/**
* @brief This property sets whether content items at the bottom should be shown.
* when the drawer is collapsed as a sidebar.
*
* If you want to keep some items visible and some invisible, set this to
* false and control the visibility/opacity of individual items,
* binded to the collapsed property
*
* default: ``false``
*
* @see content
* @since 2.5
*/
property bool showContentWhenCollapsed: false
// TODO
property bool showHeaderWhenCollapsed: false
/**
* @brief This property sets whether activating a leaf action resets the
* menu to show leaf's parent actions.
*
* A leaf action is an action without any child actions.
*
* default: ``true``
*/
property bool resetMenuOnTriggered: true
/**
* @brief This property points to the action acting as a submenu
*/
readonly property T.Action currentSubMenu: stackView.currentItem?.current ?? null
/**
* @brief This property sets whether the drawer becomes a menu on the desktop.
*
* default: ``false``
*
* @since 2.11
*/
property bool isMenu: false
/**
* @brief This property sets the visibility of the collapse button
* when the drawer collapsible.
*
* default: ``true``
*
* @since 2.12
*/
property bool collapseButtonVisible: true
//END properties
/**
* @brief This function reverts the menu back to its initial state
*/
function resetMenu() {
stackView.pop(stackView.get(0, T.StackView.DontLoad));
if (root.modal) {
root.drawerOpen = false;
}
}
// rightPadding: !Kirigami.Settings.isMobile && mainFlickable.contentHeight > mainFlickable.height ? Kirigami.Units.gridUnit : Kirigami.Units.smallSpacing
Kirigami.Theme.colorSet: modal ? Kirigami.Theme.Window : Kirigami.Theme.View
onIsMenuChanged: drawerOpen = false
Component {
id: menuComponent
Column {
property alias model: actionsRepeater.model
property T.Action current
property int level: 0
spacing: 0
Layout.maximumHeight: Layout.minimumHeight
QQC2.ItemDelegate {
id: backItem
visible: level > 0
width: parent.width
icon.name: mirrored ? "go-previous-symbolic-rtl" : "go-previous-symbolic"
text: Kirigami.MnemonicData.richTextLabel
activeFocusOnTab: true
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: qsTr("Back")
onClicked: stackView.pop()
Keys.onEnterPressed: stackView.pop()
Keys.onReturnPressed: stackView.pop()
Keys.onDownPressed: nextItemInFocusChain().focus = true
Keys.onUpPressed: nextItemInFocusChain(false).focus = true
}
Shortcut {
sequence: backItem.Kirigami.MnemonicData.sequence
onActivated: backItem.clicked()
}
Repeater {
id: actionsRepeater
readonly property bool withSections: {
for (const action of root.actions) {
if (action.hasOwnProperty("expandible") && action.expandible) {
return true;
}
}
return false;
}
model: root.actions
delegate: ActionDelegate {
required property T.Action modelData
tAction: modelData
withSections: actionsRepeater.withSections
}
}
}
}
component ActionDelegate : Column {
id: delegate
required property int index
required property T.Action tAction
required property bool withSections
// `as` case operator is still buggy
readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
readonly property bool isExpanded: {
return !root.collapsed
&& kAction
&& kAction.expandible
&& kAction.children.length > 0;
}
visible: kAction?.visible ?? true
width: parent.width
KP.GlobalDrawerActionItem {
Kirigami.Theme.colorSet: !root.modal && !root.collapsed && delegate.withSections
? Kirigami.Theme.Window : parent.Kirigami.Theme.colorSet
visible: !delegate.isExpanded
width: parent.width
tAction: delegate.tAction
onCheckedChanged: {
// move every checked item into view
if (checked && topContent.height + backItem.height + (delegate.index + 1) * height - mainFlickable.contentY > mainFlickable.height) {
mainFlickable.contentY += height
}
}
}
Item {
id: headerItem
visible: delegate.isExpanded
height: sectionHeader.implicitHeight
width: parent.width
Kirigami.ListSectionHeader {
id: sectionHeader
anchors.fill: parent
Kirigami.Theme.colorSet: root.modal ? Kirigami.Theme.View : Kirigami.Theme.Window
contentItem: RowLayout {
spacing: sectionHeader.spacing
Kirigami.Icon {
property int size: Kirigami.Units.iconSizes.smallMedium
Layout.minimumHeight: size
Layout.maximumHeight: size
Layout.minimumWidth: size
Layout.maximumWidth: size
source: delegate.tAction.icon.name || delegate.tAction.icon.source
}
Kirigami.Heading {
level: 4
text: delegate.tAction.text
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}
Repeater {
model: delegate.isExpanded ? (delegate.kAction?.children ?? null) : null
NestedActionDelegate {
required property T.Action modelData
tAction: modelData
withSections: delegate.withSections
}
}
}
component NestedActionDelegate : KP.GlobalDrawerActionItem {
required property bool withSections
width: parent.width
opacity: !root.collapsed
leftPadding: withSections && !root.collapsed && !root.modal ? padding * 2 : padding * 4
}
contentItem: Kirigami.HeaderFooterLayout {
id: mainLayout
anchors {
fill: parent
topMargin: root.collapsed && !showHeaderWhenCollapsed ? -contentItem.y : 0
}
Behavior on anchors.topMargin {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
header: RowLayout {
visible: root.title.length > 0 || Boolean(root.titleIcon)
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: root.titleIcon
}
Kirigami.Heading {
text: root.title
elide: Text.ElideRight
visible: !root.collapsed
Layout.fillWidth: true
}
}
contentItem: QQC2.ScrollView {
id: scrollView
//ensure the attached property exists
Kirigami.Theme.inherit: true
// HACK: workaround for https://bugreports.qt.io/browse/QTBUG-83890
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
implicitWidth: Math.min(Kirigami.Units.gridUnit * 20, root.parent.width * 0.8)
Flickable {
id: mainFlickable
contentWidth: width
contentHeight: mainColumn.Layout.minimumHeight
clip: (mainLayout.header?.visible ?? false) || (mainLayout.footer?.visible ?? false)
ColumnLayout {
id: mainColumn
width: mainFlickable.width
spacing: 0
height: Math.max(scrollView.height, Layout.minimumHeight)
ColumnLayout {
id: topContent
spacing: 0
Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: root.leftPadding
Layout.rightMargin: root.rightPadding
Layout.bottomMargin: Kirigami.Units.smallSpacing
Layout.topMargin: root.topPadding
Layout.fillWidth: true
Layout.fillHeight: true
Layout.preferredHeight: implicitHeight * opacity
// NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
// as items are added only after this column creation
Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
visible: children.length > 0 && childrenRect.height > 0 && opacity > 0
opacity: !root.collapsed || showTopContentWhenCollapsed
Behavior on opacity {
// not an animator as is binded
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
T.StackView {
id: stackView
property KP.ActionsMenu openSubMenu
clip: true
Layout.fillWidth: true
Layout.minimumHeight: currentItem ? currentItem.implicitHeight : 0
Layout.maximumHeight: Layout.minimumHeight
initialItem: menuComponent
// NOTE: it's important those are NumberAnimation and not XAnimators
// as while the animation is running the drawer may close, and
// the animator would stop when not drawing see BUG 381576
popEnter: Transition {
NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * -stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
popExit: Transition {
NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
pushEnter: Transition {
NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
pushExit: Transition {
NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
replaceEnter: Transition {
NumberAnimation { property: "x"; from: (stackView.mirrored ? -1 : 1) * stackView.width; to: 0; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
replaceExit: Transition {
NumberAnimation { property: "x"; from: 0; to: (stackView.mirrored ? -1 : 1) * -stackView.width; duration: Kirigami.Units.veryLongDuration; easing.type: Easing.OutCubic }
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: root.actions.length > 0
Layout.minimumHeight: Kirigami.Units.smallSpacing
}
ColumnLayout {
id: mainContent
Layout.alignment: Qt.AlignHCenter
Layout.leftMargin: root.leftPadding
Layout.rightMargin: root.rightPadding
Layout.fillWidth: true
Layout.fillHeight: true
// NOTE: why this? just Layout.fillWidth: true doesn't seem sufficient
// as items are added only after this column creation
Layout.minimumWidth: parent.width - root.leftPadding - root.rightPadding
visible: children.length > 0 && (opacity > 0 || mainContentAnimator.running)
opacity: !root.collapsed || showContentWhenCollapsed
Behavior on opacity {
OpacityAnimator {
id: mainContentAnimator
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Item {
Layout.minimumWidth: Kirigami.Units.smallSpacing
Layout.minimumHeight: root.bottomPadding
}
QQC2.ToolButton {
Layout.fillWidth: true
icon.name: {
if (root.collapsible && root.collapseButtonVisible) {
// Check for edge regardless of RTL/locale/mirrored status,
// because edge can be set externally.
const mirrored = root.edge === Qt.RightEdge;
if (root.collapsed) {
return mirrored ? "sidebar-expand-right" : "sidebar-expand-left";
} else {
return mirrored ? "sidebar-collapse-right" : "sidebar-collapse-left";
}
}
return "";
}
visible: root.collapsible && root.collapseButtonVisible
text: root.collapsed ? "" : qsTr("Close Sidebar")
onClicked: root.collapsed = !root.collapsed
QQC2.ToolTip.visible: root.collapsed && (Kirigami.Settings.tabletMode ? pressed : hovered)
QQC2.ToolTip.text: qsTr("Open Sidebar")
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
}
}
}
@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2012 Sebastian Kügler <sebas@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* @brief A heading label used for subsections of texts.
*
* The characteristics of the text will be automatically set according to the
* Kirigami.Theme. Use this components for section titles or headings in your UI,
* for example page or section titles.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
* [...]
* Column {
* Kirigami.Heading {
* text: "Apples in the sunlight"
* level: 2
* }
* [...]
* }
* @endcode
*
* The most important property is "text", which applies to the text property of
* Label. See the Label component from QtQuick.Controls 2 and primitive QML Text
* element API for additional properties, methods and signals.
*
* @inherit QtQuick.Controls.Label
*/
QQC2.Label {
id: heading
/**
* @brief This property holds the level of the heading, which determines its size.
*
* This property holds the level, which determines how large the header is.
*
* Acceptable values range from 1 (big) to 5 (small).
*
* default: ``1``
*/
property int level: 1
/**
* @brief This enumeration defines heading types.
*
* This enum helps with heading visibility (making it less or more important).
*/
enum Type {
Normal,
Primary,
Secondary
}
/**
* @brief This property holds the heading type.
*
* The type of the heading. This can be:
* * ``Kirigami.Heading.Type.Normal``: Create a normal heading (default)
* * ``Kirigami.Heading.Type.Primary``: Makes the heading more prominent. Useful
* when making the heading bigger is not enough.
* * ``Kirigami.Heading.Type.Secondary``: Makes the heading less prominent.
* Useful when an heading is for a less important section in an application.
*
* @property Heading::Type type
* @since 5.82
*/
property int type: Heading.Type.Normal
font.pointSize: {
let factor = 1;
switch (heading.level) {
case 1:
factor = 1.35;
break;
case 2:
factor = 1.20;
break;
case 3:
factor = 1.15;
break;
case 4:
factor = 1.10;
break;
default:
break;
}
return Kirigami.Theme.defaultFont.pointSize * factor;
}
font.weight: type === Heading.Type.Primary ? Font.DemiBold : Font.Normal
opacity: type === Heading.Type.Secondary ? 0.7 : 1
Accessible.role: Accessible.Heading
}
@@ -0,0 +1,110 @@
/*
* SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2018 Kai Uwe Broulik <kde@privat.broulik.de>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigami.templates as KT
/**
* An inline message item with support for informational, positive,
* warning and error types, and with support for associated actions.
*
* InlineMessage can be used to give information to the user or
* interact with the user, without requiring the use of a dialog.
*
* The InlineMessage item is hidden by default. It also manages its
* height (and implicitHeight) during an animated reveal when shown.
* You should avoid setting height on an InlineMessage unless it is
* already visible.
*
* Optionally an icon can be set, defaulting to an icon appropriate
* to the message type otherwise.
*
* Optionally a close button can be shown.
*
* Actions are added from left to right. If more actions are set than
* can fit, an overflow menu is provided.
*
* Example usage:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.InlineMessage {
* type: Kirigami.MessageType.Error
*
* text: i18n("My error message")
*
* actions: [
* Kirigami.Action {
* icon.name: "list-add"
* text: i18n("Add")
* onTriggered: source => {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: i18n("Edit")
* onTriggered: source => {
* // do stuff
* }
* }
* ]
* }
* @endcode
* @inherit org::kde::kirigami::templates::InlineMessage
* @since 5.45
*/
KT.InlineMessage {
id: root
// a rectangle padded with anchors.margins is used to simulate a border
leftPadding: bgFillRect.anchors.leftMargin + Kirigami.Units.smallSpacing
topPadding: bgFillRect.anchors.topMargin + Kirigami.Units.smallSpacing
rightPadding: bgFillRect.anchors.rightMargin + Kirigami.Units.smallSpacing
bottomPadding: bgFillRect.anchors.bottomMargin + Kirigami.Units.smallSpacing
background: Rectangle {
id: bgBorderRect
color: switch (root.type) {
case Kirigami.MessageType.Positive: return Kirigami.Theme.positiveTextColor;
case Kirigami.MessageType.Warning: return Kirigami.Theme.neutralTextColor;
case Kirigami.MessageType.Error: return Kirigami.Theme.negativeTextColor;
default: return Kirigami.Theme.activeTextColor;
}
radius: root.position === KT.InlineMessage.Position.Inline ? Kirigami.Units.cornerRadius : 0
Rectangle {
id: bgFillRect
anchors.fill: parent
anchors {
leftMargin: root.position === KT.InlineMessage.Position.Inline ? 1 : 0
topMargin: root.position === KT.InlineMessage.Position.Header ? 0 : 1
rightMargin: root.position === KT.InlineMessage.Position.Inline ? 1 : 0
bottomMargin: root.position === KT.InlineMessage.Position.Footer ? 0 : 1
}
color: Kirigami.Theme.backgroundColor
radius: bgBorderRect.radius * 0.60
}
Rectangle {
anchors.fill: bgFillRect
color: bgBorderRect.color
opacity: 0.20
radius: bgFillRect.radius
}
}
}
@@ -0,0 +1,155 @@
/*
* SPDX-FileCopyrightText: 2023 Nate Graham <nate@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief A fancy inline view header showing a title and optional actions.
*
* Designed to be set as the header: property of a ListView or GridView, this
* component provides a fancy inline header suitable for explaining the contents
* of its view to the user in an attractive and standardized way. Actions globally
* relevant to the view can be defined using the actions: property. They will
* appear on the right side of the header as buttons, and collapse into an
* overflow menu when there isn't room to show them all.
*
* The width: property must be manually set to the parent view's width.
*
* Example usage:
* @code{.qml}
* import org.kde.kirigami as Kirigami
*
* ListView {
* id: listView
*
* headerPositioning: ListView.OverlayHeader
* header: InlineViewHeader {
* width: listView.width
* text: "My amazing view"
* actions: [
* Kirigami.Action {
* icon.name: "list-add-symbolic"
* text: "Add item"
* onTriggered: {
* // do stuff
* }
* }
* ]
* }
*
* model: [...]
* delegate: [...]
* }
* @endcode
* @inherit QtQuick.QQC2.ToolBar
*/
T.ToolBar {
id: root
//BEGIN properties
/**
* @brief This property holds the title text.
*/
property string text
/**
* This property holds the list of actions to show on the header. Actions
* are added from left to right. If more actions are set than can fit, an
* overflow menu is provided.
*/
property list<T.Action> actions
//END properties
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
Math.ceil(label.implicitWidth)
+ rowLayout.spacing
+ Math.ceil(Math.max(buttonsLoader.implicitWidth, buttonsLoader.Layout.minimumWidth))
+ leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
topPadding: Kirigami.Units.smallSpacing + (root.position === T.ToolBar.Footer ? separator.implicitHeight : 0)
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing + (root.position === T.ToolBar.Header ? separator.implicitHeight : 0)
z: 999 // don't let content overlap it
// HACK Due to the lack of a GridView.headerPositioning property,
// we need to "stick" ourselves to the top manually by translating Y accordingly.
// see see https://bugreports.qt.io/browse/QTBUG-117035.
// Conveniently, GridView is only attached to the root of the delegate (or headerItem),
// so this will only be done if the InlineViewHeader itself is the header item.
// And of course it won't be there for ListView either, where we have headerPositioning.
transform: Translate {
y: root.GridView.view ? root.GridView.view.contentY + root.height : 0
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
// We want a color that's basically halfway between the view background
// color and the window background color. But due to the use of color
// scopes, only one will be available at a time. So to get basically the
// same thing, we blend the view background color with a smidgen of the
// text color.
color: Qt.tint(Kirigami.Theme.backgroundColor, Qt.alpha(Kirigami.Theme.textColor, 0.03))
Kirigami.Separator {
id: separator
anchors {
top: root.position === T.ToolBar.Footer ? parent.top : undefined
left: parent.left
right: parent.right
bottom: root.position === T.ToolBar.Header ? parent.bottom : undefined
}
}
}
contentItem: RowLayout {
id: rowLayout
spacing: 0
Kirigami.Heading {
id: label
Layout.fillWidth: !buttonsLoader.active
Layout.maximumWidth: {
if (!buttonsLoader.active) {
return -1;
}
return rowLayout.width
- rowLayout.spacing
- buttonsLoader.Layout.minimumWidth;
}
Layout.alignment: Qt.AlignVCenter
level: 2
text: root.text
elide: Text.ElideRight
wrapMode: Text.NoWrap
maximumLineCount: 1
}
Loader {
id: buttonsLoader
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Layout.minimumWidth: item?.Layout.minimumWidth ?? 0
active: root.actions.length > 0
sourceComponent: Kirigami.ActionToolBar {
actions: root.actions
alignment: Qt.AlignRight
}
}
}
}
@@ -0,0 +1,87 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief A button that looks like a link.
*
* It uses the link color settings and triggers an action when clicked.
*
* Maps to the Command Link in the HIG:
* https://develop.kde.org/hig/components/navigation/commandlink/
*
* @since 5.52
* @since org.kde.kirigami 2.6
* @inherit QtQuick.Controls.Label
*/
QQC2.Label {
id: control
property T.Action action
/**
* @brief This property holds the mouse buttons that the mouse area reacts to.
* @see QtQuick.MouseArea::acceptedButtons
* @property Qt::MouseButtons acceptedButtons
*/
property alias acceptedButtons: area.acceptedButtons
/**
* @brief This property holds the mouse area element covering the button.
* @property MouseArea area
*/
property alias mouseArea: area
activeFocusOnTab: true
Accessible.role: Accessible.Button
Accessible.name: text
Accessible.onPressAction: clicked({ button: Qt.LeftButton })
text: action?.text ?? ""
enabled: action?.enabled ?? true
onClicked: action?.trigger()
font.bold: activeFocus
font.underline: enabled
color: enabled ? Kirigami.Theme.linkColor : Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
signal pressed(var mouse)
signal clicked(var mouse)
Keys.onPressed: event => {
switch (event.key) {
case Qt.Key_Space:
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Select:
control.clicked({ button: Qt.LeftButton });
event.accepted = true;
break;
case Qt.Key_Menu:
control.pressed({ button: Qt.RightButton });
event.accepted = true;
break;
}
}
MouseArea {
id: area
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: mouse => control.clicked(mouse)
onPressed: mouse => control.pressed(mouse)
}
}
@@ -0,0 +1,230 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* Implements a drag handle supposed to be in items in ListViews to reorder items
* The ListView must visualize a model which supports item reordering,
* such as ListModel.move() or QAbstractItemModel instances with moveRows() correctly implemented.
* In order for ListItemDragHandle to work correctly, the listItem that is being dragged
* should not directly be the delegate of the ListView, but a child of it.
*
* It is recommended to use DelagateRecycler as base delegate like the following code:
* @code
* import QtQuick
* import QtQuick.Layouts
* import QtQuick.Controls as QQC2
* import org.kde.kirigami as Kirigami
* ...
* Component {
* id: delegateComponent
* QQC2.ItemDelegate {
* id: listItem
* contentItem: RowLayout {
* Kirigami.ListItemDragHandle {
* listItem: listItem
* listView: mainList
* onMoveRequested: (oldIndex, newIndex) => {
* listModel.move(oldIndex, newIndex, 1);
* }
* }
* QQC2.Label {
* text: model.label
* }
* }
* }
* }
* ListView {
* id: mainList
*
* model: ListModel {
* id: listModel
* ListElement {
* label: "Item 1"
* }
* ListElement {
* label: "Item 2"
* }
* ListElement {
* label: "Item 3"
* }
* }
* //this is optional to make list items animated when reordered
* moveDisplaced: Transition {
* YAnimator {
* duration: Kirigami.Units.longDuration
* easing.type: Easing.InOutQuad
* }
* }
* delegate: Loader {
* width: mainList.width
* sourceComponent: delegateComponent
* }
* }
* ...
* @endcode
*
* @since 2.5
* @inherit QtQuick.Item
*/
Item {
id: root
/**
* @brief This property holds the delegate that will be dragged around.
*
* This item *must* be a child of the actual ListView's delegate.
*/
property Item listItem
/**
* @brief This property holds the ListView that the delegate belong to.
*/
property ListView listView
/**
* @brief This signal is emitted when the drag handle wants to move the item in the model.
*
* The following example does the move in the case a ListModel is used:
* @code
* onMoveRequested: (oldIndex, newIndex) => {
* listModel.move(oldIndex, newIndex, 1);
* }
* @endcode
* @param oldIndex the index the item is currently at
* @param newIndex the index we want to move the item to
*/
signal moveRequested(int oldIndex, int newIndex)
/**
* @brief This signal is emitted when the drag operation is complete and the item has been
* dropped in the new final position.
*/
signal dropped()
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
MouseArea {
id: mouseArea
anchors.fill: parent
drag {
target: listItem
axis: Drag.YAxis
minimumY: 0
}
cursorShape: pressed ? Qt.ClosedHandCursor : Qt.OpenHandCursor
preventStealing: true
Kirigami.Icon {
id: internal
anchors.fill: parent
source: "handle-sort"
opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6
property int startY
property int mouseDownY
property Item originalParent
property int listItemLastY
property bool draggingUp
function arrangeItem() {
const newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(mouseArea, 0, internal.mouseDownY).y);
if (newIndex > -1 && ((internal.draggingUp && newIndex < index) || (!internal.draggingUp && newIndex > index))) {
root.moveRequested(index, newIndex);
}
}
}
onPressed: mouse => {
internal.originalParent = listItem.parent;
listItem.parent = listView;
listItem.y = internal.originalParent.mapToItem(listItem.parent, listItem.x, listItem.y).y;
internal.originalParent.z = 99;
internal.startY = listItem.y;
internal.listItemLastY = listItem.y;
internal.mouseDownY = mouse.y;
// while dragging listItem's height could change
// we want a const maximumY during the dragging time
mouseArea.drag.maximumY = listView.height - listItem.height;
}
onPositionChanged: mouse => {
if (!pressed || listItem.y === internal.listItemLastY) {
return;
}
internal.draggingUp = listItem.y < internal.listItemLastY
internal.listItemLastY = listItem.y;
internal.arrangeItem();
// autoscroll when the dragging item reaches the listView's top/bottom boundary
scrollTimer.running = (listView.contentHeight > listView.height)
&& ((listItem.y === 0 && !listView.atYBeginning)
|| (listItem.y === mouseArea.drag.maximumY && !listView.atYEnd));
}
onReleased: mouse => dropped()
onCanceled: dropped()
function dropped() {
listItem.y = internal.originalParent.mapFromItem(listItem, 0, 0).y;
listItem.parent = internal.originalParent;
dropAnimation.running = true;
scrollTimer.running = false;
root.dropped();
}
SequentialAnimation {
id: dropAnimation
YAnimator {
target: listItem
from: listItem.y
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
PropertyAction {
target: listItem.parent
property: "z"
value: 0
}
}
Timer {
id: scrollTimer
interval: 50
repeat: true
onTriggered: {
if (internal.draggingUp) {
listView.contentY -= Kirigami.Units.gridUnit;
if (listView.atYBeginning) {
listView.positionViewAtBeginning();
stop();
}
} else {
listView.contentY += Kirigami.Units.gridUnit;
if (listView.atYEnd) {
listView.positionViewAtEnd();
stop();
}
}
internal.arrangeItem();
}
}
}
}
@@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2019 Björn Feber <bfeber@protonmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief A section delegate for the primitive ListView component.
*
* It's intended to make all listviews look coherent.
*
* Any additional content items will be positioned in a row at the trailing side
* of this component.
*
* Example usage:
* @code
* import QtQuick
* import QtQuick.Controls as QQC2
* import org.kde.kirigami as Kirigami
*
* ListView {
* section.delegate: Kirigami.ListSectionHeader {
* text: section
*
* QQC2.Button {
* text: "Button 1"
* }
* QQC2.Button {
* text: "Button 2"
* }
* }
* }
* @endcode
*/
QQC2.ItemDelegate {
id: listSection
/**
* @brief This property sets the text of the ListView's section header.
* @property string label
* @deprecated since 6.2 Use base type's AbstractButton::text property directly
*/
@Deprecated { reason: "Use base type's AbstractButton::text property directly" }
property alias label: listSection.text
default property alias _contents: rowLayout.data
hoverEnabled: false
activeFocusOnTab: false
// we do not need a background
background: Item {}
topPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
Accessible.role: Accessible.Heading
contentItem: RowLayout {
id: rowLayout
spacing: Kirigami.Units.largeSpacing
Kirigami.Heading {
Layout.maximumWidth: rowLayout.width
Layout.alignment: Qt.AlignVCenter
opacity: 0.7
level: 5
type: Kirigami.Heading.Primary
text: listSection.text
elide: Text.ElideRight
// we override the Primary type's font weight (DemiBold) for Bold for contrast with small text
font.weight: Font.Bold
Accessible.ignored: true
}
Kirigami.Separator {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
Accessible.ignored: true
}
}
}
@@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief A placeholder for loading pages.
*
* Example usage:
* @code{.qml}
* Kirigami.Page {
* Kirigami.LoadingPlaceholder {
* anchors.centerIn: parent
* }
* }
* @endcode
* @code{.qml}
* Kirigami.Page {
* Kirigami.LoadingPlaceholder {
* anchors.centerIn: parent
* determinate: true
* progressBar.value: loadingValue
* }
* }
* @endcode
* @inherit org::kde::kirigami::PlaceholderMessage
*/
Kirigami.PlaceholderMessage {
id: loadingPlaceholder
/**
* @brief This property holds whether the loading message shows a
* determinate progress bar or not.
*
* This should be true if you want to display the actual
* percentage when it's loading.
*
* default: ``false``
*/
property bool determinate: false
/**
* @brief This property holds a progress bar.
*
* This should be used to access the progress bar to change its value.
*
* @property QtQuick.Controls.ProgressBar _progressBar
*/
property alias progressBar: _progressBar
text: qsTr("Loading…")
QQC2.ProgressBar {
id: _progressBar
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 20
indeterminate: !determinate
from: 0
to: 100
}
}
@@ -0,0 +1,280 @@
/*
* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQml
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief Page navigation tab-bar, used as an alternative to sidebars for 3-5 elements.
*
* Can be combined with secondary toolbars above (if in the footer) to provide page actions.
*
* Example usage:
* @code{.qml}
* import QtQuick
* import org.kde.kirigami as Kirigami
*
* Kirigami.ApplicationWindow {
* title: "Clock"
*
* pageStack.initialPage: worldPage
*
* Kirigami.Page {
* id: worldPage
* title: "World"
* visible: false
* }
* Kirigami.Page {
* id: timersPage
* title: "Timers"
* visible: false
* }
* Kirigami.Page {
* id: stopwatchPage
* title: "Stopwatch"
* visible: false
* }
* Kirigami.Page {
* id: alarmsPage
* title: "Alarms"
* visible: false
* }
*
* footer: Kirigami.NavigationTabBar {
* actions: [
* Kirigami.Action {
* icon.name: "globe"
* text: "World"
* checked: worldPage.visible
* onTriggered: {
* if (!worldPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(worldPage);
* }
* }
* },
* Kirigami.Action {
* icon.name: "player-time"
* text: "Timers"
* checked: timersPage.visible
* onTriggered: {
* if (!timersPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(timersPage);
* }
* }
* },
* Kirigami.Action {
* icon.name: "chronometer"
* text: "Stopwatch"
* checked: stopwatchPage.visible
* onTriggered: {
* if (!stopwatchPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(stopwatchPage);
* }
* }
* },
* Kirigami.Action {
* icon.name: "notifications"
* text: "Alarms"
* checked: alarmsPage.visible
* onTriggered: {
* if (!alarmsPage.visible) {
* while (pageStack.depth > 0) {
* pageStack.pop();
* }
* pageStack.push(alarmsPage);
* }
* }
* }
* ]
* }
* }
* @endcode
*
* @see NavigationTabButton
* @since 5.87
* @since org.kde.kirigami 2.19
* @inherit QtQuick.Templates.Toolbar
*/
QQC2.ToolBar {
id: root
//BEGIN properties
/**
* @brief This property holds the list of actions to be displayed in the toolbar.
*/
property list<T.Action> actions
/**
* @brief This property holds a subset of visible actions of the list of actions.
*
* An action is considered visible if it is either a Kirigami.Action with
* ``visible`` property set to true, or it is a plain QQC2.Action.
*/
readonly property list<T.Action> visibleActions: actions
// Note: instanceof check implies `!== null`
.filter(action => action instanceof Kirigami.Action
? action.visible
: action !== null
)
/**
* @brief The property holds the maximum width of the toolbar actions, before margins are added.
*/
property real maximumContentWidth: {
const minDelegateWidth = Kirigami.Units.gridUnit * 5;
// Always have at least the width of 5 items, so that small amounts of actions look natural.
return minDelegateWidth * Math.max(visibleActions.length, 5);
}
/**
* @brief This property holds the index of currently checked tab.
*
* If the index set is out of bounds, or the triggered signal did not change any checked property of an action, the index
* will remain the same.
*/
property int currentIndex: tabGroup.checkedButton && tabGroup.buttons.length > 0 ? tabGroup.checkedButton.tabIndex : -1
/**
* @brief This property holds the number of tab buttons.
*/
readonly property int count: tabGroup.buttons.length
/**
* @brief This property holds the ButtonGroup used to manage the tabs.
*/
readonly property T.ButtonGroup tabGroup: tabGroup
/**
* @brief This property holds the calculated width that buttons on the tab bar use.
*
* @since 5.102
*/
property real buttonWidth: {
// Counting buttons because Repeaters can be counted among visibleChildren
let visibleButtonCount = 0;
const minWidth = contentItem.height * 0.75;
for (const visibleChild of contentItem.visibleChildren) {
if (contentItem.width / visibleButtonCount >= minWidth && // make buttons go off the screen if there is physically no room for them
visibleChild instanceof T.AbstractButton) { // Checking for AbstractButtons because any AbstractButton can act as a tab
++visibleButtonCount;
}
}
return Math.round(contentItem.width / visibleButtonCount);
}
//END properties
onCurrentIndexChanged: {
if (currentIndex === -1) {
if (tabGroup.checkState !== Qt.Unchecked) {
tabGroup.checkState = Qt.Unchecked;
}
return;
}
if (!tabGroup.checkedButton || tabGroup.checkedButton.tabIndex !== currentIndex) {
const buttonForCurrentIndex = tabGroup.buttons[currentIndex]
if (buttonForCurrentIndex.action) {
// trigger also toggles and causes clicked() to be emitted
buttonForCurrentIndex.action.trigger();
} else {
// toggle() does not trigger the action,
// so don't use it if you want to use an action.
// It also doesn't cause clicked() to be emitted.
buttonForCurrentIndex.toggle();
}
}
}
// ensure that by default, we do not have unintended padding and spacing from the style
spacing: 0
padding: 0
topPadding: undefined
leftPadding: undefined
rightPadding: undefined
bottomPadding: undefined
verticalPadding: undefined
// Using Math.round() on horizontalPadding can cause the contentItem to jitter left and right when resizing the window.
horizontalPadding: Math.floor(Math.max(0, width - root.maximumContentWidth) / 2)
contentWidth: Math.ceil(Math.min(root.availableWidth, root.maximumContentWidth))
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, contentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, contentHeight + topPadding + bottomPadding)
position: {
if (QQC2.ApplicationWindow.window?.footer === root) {
return QQC2.ToolBar.Footer
} else if (parent?.footer === root) {
return QQC2.ToolBar.Footer
} else if (parent?.parent?.footer === parent) {
return QQC2.ToolBar.Footer
} else {
return QQC2.ToolBar.Header
}
}
contentItem: RowLayout {
id: rowLayout
spacing: root.spacing
}
// Used to manage which tab is checked and change the currentIndex
T.ButtonGroup {
id: tabGroup
exclusive: true
buttons: root.contentItem.children.filter((child) => child !== instantiator)
onCheckedButtonChanged: {
if (!checkedButton) {
return
}
if (root.currentIndex !== checkedButton.tabIndex) {
root.currentIndex = checkedButton.tabIndex;
}
}
}
// Using a Repeater here because Instantiator was causing issues:
// NavigationTabButtons that were supposed to be destroyed were still
// registered as buttons in tabGroup.
// NOTE: This will make Repeater show up as child through visibleChildren
Repeater {
id: instantiator
model: root.visibleActions
delegate: NavigationTabButton {
id: delegate
required property T.Action modelData
parent: root.contentItem
action: modelData
// Workaround setting the action when checkable is not explicitly set making tabs uncheckable
onActionChanged: action.checkable = true
Layout.minimumWidth: root.buttonWidth
Layout.maximumWidth: root.buttonWidth
Layout.fillHeight: true
Kirigami.Theme.textColor: root.Kirigami.Theme.textColor
Kirigami.Theme.backgroundColor: root.Kirigami.Theme.backgroundColor
Kirigami.Theme.highlightColor: root.Kirigami.Theme.highlightColor
}
}
}
@@ -0,0 +1,219 @@
/* SPDX-FileCopyrightText: 2021 Devin Lin <espidev@gmail.com>
* SPDX-FileCopyrightText: 2021 Noah Davis <noahadvs@gmail.com>
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief Navigation buttons to be used for the NavigationTabBar component.
*
* It supplies its own padding, and also supports using the QQC2 AbstractButton ``display`` property to be used in column lists.
*
* Alternative way to the "actions" property on NavigationTabBar, as it can be used
* with Repeater to generate buttons from models.
*
* Example usage:
* @code{.qml}
* Kirigami.NavigationTabBar {
* id: navTabBar
* Kirigami.NavigationTabButton {
* visible: true
* icon.name: "document-save"
* text: `test ${tabIndex + 1}`
* QQC2.ButtonGroup.group: navTabBar.tabGroup
* }
* Kirigami.NavigationTabButton {
* visible: false
* icon.name: "document-send"
* text: `test ${tabIndex + 1}`
* QQC2.ButtonGroup.group: navTabBar.tabGroup
* }
* actions: [
* Kirigami.Action {
* visible: true
* icon.name: "edit-copy"
* icon.height: 32
* icon.width: 32
* text: `test 3`
* checked: true
* },
* Kirigami.Action {
* visible: true
* icon.name: "edit-cut"
* text: `test 4`
* checkable: true
* },
* Kirigami.Action {
* visible: false
* icon.name: "edit-paste"
* text: `test 5`
* },
* Kirigami.Action {
* visible: true
* icon.source: "../logo.png"
* text: `test 6`
* checkable: true
* }
* ]
* }
* @endcode
*
* @since 5.87
* @since org.kde.kirigami 2.19
* @inherit QtQuick.Templates.TabButton
*/
T.TabButton {
id: control
/**
* @brief This property tells the index of this tab within the tab bar.
*/
readonly property int tabIndex: {
let tabIdx = 0
for (const child of parent.children) {
if (child === this) {
return tabIdx
}
// Checking for AbstractButtons because any AbstractButton can act as a tab
if (child instanceof T.AbstractButton) {
++tabIdx
}
}
return -1
}
// FIXME: all those internal properties should go, and the button should style itself in a more standard way
// probably similar to view items
readonly property color __foregroundColor: Kirigami.Theme.textColor
readonly property color __highlightForegroundColor: Kirigami.Theme.textColor
readonly property color __pressedColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.3)
readonly property color __hoverSelectColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.2)
readonly property color __checkedBorderColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.7)
readonly property color __pressedBorderColor: Qt.alpha(Kirigami.Theme.highlightColor, 0.9)
readonly property real __verticalMargins: (display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.largeSpacing : 0
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
implicitContentWidth + leftPadding + rightPadding)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding)
display: T.AbstractButton.TextUnderIcon
Kirigami.Theme.colorSet: Kirigami.Theme.Window
Kirigami.Theme.inherit: false
hoverEnabled: true
padding: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
icon.height: display === T.AbstractButton.TextBesideIcon ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
icon.width: display === T.AbstractButton.TextBesideIcon ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.smallMedium
icon.color: checked ? __highlightForegroundColor : __foregroundColor
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: text
Accessible.onPressAction: control.action.trigger()
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Button
Kirigami.Theme.inherit: false
implicitHeight: (control.display === T.AbstractButton.TextBesideIcon) ? 0 : (Kirigami.Units.gridUnit * 3 + Kirigami.Units.smallSpacing * 2)
color: "transparent"
Rectangle {
width: parent.width - Kirigami.Units.largeSpacing
height: parent.height - Kirigami.Units.largeSpacing
anchors.centerIn: parent
radius: Kirigami.Units.cornerRadius
color: control.down ? control.__pressedColor : (control.checked || control.hovered ? control.__hoverSelectColor : "transparent")
border.color: control.visualFocus ? control.__checkedBorderColor : (control.down ? control.__pressedBorderColor : color)
border.width: 1
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
Behavior on border.color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
}
}
contentItem: GridLayout {
columnSpacing: 0
rowSpacing: (label.visible && label.lineCount > 1) ? 0 : control.spacing
// if this is a row or a column
columns: control.display !== T.AbstractButton.TextBesideIcon ? 1 : 2
Kirigami.Icon {
id: icon
source: control.icon.name || control.icon.source
visible: (control.icon.name.length > 0 || control.icon.source.toString().length > 0) && control.display !== T.AbstractButton.TextOnly
color: control.icon.color
Layout.topMargin: control.__verticalMargins
Layout.bottomMargin: control.__verticalMargins
Layout.leftMargin: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.gridUnit : 0
Layout.rightMargin: (control.display === T.AbstractButton.TextBesideIcon) ? Kirigami.Units.gridUnit : 0
Layout.alignment: {
if (control.display === T.AbstractButton.TextBesideIcon) {
// row layout
return Qt.AlignVCenter | Qt.AlignRight;
} else {
// column layout
return Qt.AlignHCenter | ((!label.visible || label.lineCount > 1) ? Qt.AlignVCenter : Qt.AlignBottom);
}
}
implicitHeight: source ? control.icon.height : 0
implicitWidth: source ? control.icon.width : 0
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
}
QQC2.Label {
id: label
text: control.Kirigami.MnemonicData.richTextLabel
horizontalAlignment: (control.display === T.AbstractButton.TextBesideIcon) ? Text.AlignLeft : Text.AlignHCenter
visible: control.display !== T.AbstractButton.IconOnly
wrapMode: Text.Wrap
elide: Text.ElideMiddle
color: control.checked ? control.__highlightForegroundColor : control.__foregroundColor
font.bold: control.checked
font.pointSize: !icon.visible && control.display === T.AbstractButton.TextBelowIcon
? Kirigami.Theme.defaultFont.pointSize * 1.20 // 1.20 is equivalent to level 2 heading
: Kirigami.Theme.defaultFont.pointSize
Behavior on color { ColorAnimation { duration: Kirigami.Units.shortDuration } }
Layout.topMargin: control.__verticalMargins
Layout.bottomMargin: control.__verticalMargins
Layout.alignment: {
if (control.display === T.AbstractButton.TextBesideIcon) {
// row layout
return Qt.AlignVCenter | Qt.AlignLeft;
} else {
// column layout
return icon.visible ? Qt.AlignHCenter | Qt.AlignTop : Qt.AlignCenter;
}
}
Layout.fillWidth: true
Accessible.ignored: true
}
}
}
@@ -0,0 +1,236 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private" as KP
import "templates" as KT
/**
* Overlay Drawers are used to expose additional UI elements needed for
* small secondary tasks for which the main UI elements are not needed.
* For example in Okular Mobile, an Overlay Drawer is used to display
* thumbnails of all pages within a document along with a search field.
* This is used for the distinct task of navigating to another page.
*
* @inherit org::kde::kirigami::templates::OverlayDrawer
*/
KT.OverlayDrawer {
id: root
//BEGIN Properties
focus: false
modal: true
drawerOpen: !modal
closePolicy: modal ? T.Popup.CloseOnEscape | T.Popup.CloseOnReleaseOutside : T.Popup.NoAutoClose
handleVisible: interactive && (modal || !drawerOpen) && (typeof(applicationWindow)===typeof(Function) && applicationWindow() ? applicationWindow().controlsVisible : true)
// FIXME: set to false when it does not lead to blocking closePolicy.
// See Kirigami bug: 454119
interactive: true
onPositionChanged: {
if (!modal && !root.peeking && !root.animating) {
position = 1;
}
}
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Item {
parent: root.handle
anchors.fill: parent
Kirigami.ShadowedRectangle {
id: handleGraphics
anchors.centerIn: parent
Kirigami.Theme.colorSet: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.colorSet : Kirigami.Theme.Button
Kirigami.Theme.backgroundColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.backgroundColor : undefined
Kirigami.Theme.textColor: parent.parent.handleAnchor && parent.parent.handleAnchor.visible ? parent.parent.handleAnchor.Kirigami.Theme.textColor : undefined
Kirigami.Theme.inherit: false
color: root.handle.pressed ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
visible: !parent.parent.handleAnchor || !parent.parent.handleAnchor.visible || root.handle.pressed || (root.modal && root.position > 0)
shadow.color: Qt.rgba(0, 0, 0, root.handle.pressed ? 0.6 : 0.4)
shadow.yOffset: 1
shadow.size: Kirigami.Units.gridUnit / 2
width: Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
height: width
radius: Kirigami.Units.cornerRadius
Behavior on color {
ColorAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
Loader {
anchors.centerIn: handleGraphics
width: height
height: Kirigami.Units.iconSizes.smallMedium
Kirigami.Theme.colorSet: handleGraphics.Kirigami.Theme.colorSet
Kirigami.Theme.backgroundColor: handleGraphics.Kirigami.Theme.backgroundColor
Kirigami.Theme.textColor: handleGraphics.Kirigami.Theme.textColor
asynchronous: true
source: {
let edge = root.edge;
if (Qt.application.layoutDirection === Qt.RightToLeft) {
if (edge === Qt.LeftEdge) {
edge = Qt.RightEdge;
} else {
edge = Qt.LeftEdge;
}
}
if ((root.handleClosedIcon.source || root.handleClosedIcon.name)
&& (root.handleOpenIcon.source || root.handleOpenIcon.name)) {
return Qt.resolvedUrl("templates/private/GenericDrawerIcon.qml");
} else if (edge === Qt.LeftEdge) {
return Qt.resolvedUrl("templates/private/MenuIcon.qml");
} else if (edge === Qt.RightEdge && root instanceof Kirigami.ContextDrawer) {
return Qt.resolvedUrl("templates/private/ContextIcon.qml");
} else {
return "";
}
}
onItemChanged: {
if (item) {
item.drawer = root;
item.color = Qt.binding(() => root.handle.pressed
? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor);
}
}
}
}
Kirigami.Separator {
id: separator
LayoutMirroring.enabled: false
anchors {
top: root.edge === Qt.TopEdge ? parent.bottom : (root.edge === Qt.BottomEdge ? undefined : parent.top)
left: root.edge === Qt.LeftEdge ? parent.right : (root.edge === Qt.RightEdge ? undefined : parent.left)
right: root.edge === Qt.RightEdge ? parent.left : (root.edge === Qt.LeftEdge ? undefined : parent.right)
bottom: root.edge === Qt.BottomEdge ? parent.top : (root.edge === Qt.TopEdge ? undefined : parent.bottom)
topMargin: segmentedSeparator.height
}
visible: !root.modal
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
}
Item {
id: segmentedSeparator
// an alternative to segmented style is full height
readonly property bool shouldUseSegmentedStyle: {
if (root.edge !== Qt.LeftEdge && root.edge !== Qt.RightEdge) {
return false;
}
if (root.collapsed) {
return false;
}
// compatible header
const header = root.header ?? null;
if (header instanceof T.ToolBar || header instanceof KT.AbstractApplicationHeader) {
return true;
}
// or compatible content
if (root.contentItem instanceof ColumnLayout && root.contentItem.children[0] instanceof T.ToolBar) {
return true;
}
return false;
}
anchors {
top: parent.top
left: separator.left
right: separator.right
}
height: {
if (root.edge !== Qt.LeftEdge && root.edge !== Qt.RightEdge) {
return 0;
}
if (typeof applicationWindow === "undefined") {
return 0;
}
const window = applicationWindow();
const globalToolBar = window.pageStack?.globalToolBar;
if (!globalToolBar) {
return 0;
}
return globalToolBar.preferredHeight;
}
visible: separator.visible
Kirigami.Separator {
LayoutMirroring.enabled: false
anchors {
fill: parent
topMargin: segmentedSeparator.shouldUseSegmentedStyle ? Kirigami.Units.largeSpacing : 0
bottomMargin: segmentedSeparator.shouldUseSegmentedStyle ? Kirigami.Units.largeSpacing : 0
}
Behavior on anchors.topMargin {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Behavior on anchors.bottomMargin {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Header
}
}
KP.EdgeShadow {
z: -2
visible: root.modal
edge: root.edge
anchors {
right: root.edge === Qt.RightEdge ? parent.left : (root.edge === Qt.LeftEdge ? undefined : parent.right)
left: root.edge === Qt.LeftEdge ? parent.right : (root.edge === Qt.RightEdge ? undefined : parent.left)
top: root.edge === Qt.TopEdge ? parent.bottom : (root.edge === Qt.BottomEdge ? undefined : parent.top)
bottom: root.edge === Qt.BottomEdge ? parent.top : (root.edge === Qt.TopEdge ? undefined : parent.bottom)
}
opacity: root.position === 0 ? 0 : 1
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
}
@@ -0,0 +1,28 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import org.kde.kirigami as Kirigami
import "private" as P
import "templates" as T
/**
* @brief An overlay sheet that covers the current Page content.
*
* Its contents can be scrolled up or down, scrolling all the way up or
* all the way down, dismisses it.
* Use this for big, modal dialogs or information display, that can't be
* logically done as a new separate Page, even if potentially
* are taller than the screen space.
* @inherit org::kde::kirigami::templates::OverlaySheet
*/
T.OverlaySheet {
id: root
background: P.DefaultCardBackground {
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
Kirigami.Theme.inherit: false
}
}
@@ -0,0 +1,299 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as P
/**
* Page is a container for all the app pages: everything pushed to the
* ApplicationWindow's pageStack should be a Page.
*
* @see ScrollablePage
* For content that should be scrollable, such as ListViews, use ScrollablePage instead.
* @inherit QtQuick.Controls.Page
*/
QQC2.Page {
id: root
//BEGIN properties
padding: Kirigami.Units.gridUnit
/**
* @brief If the central element of the page is a Flickable
* (ListView and Gridview as well) you can set it there.
*
* Normally, you wouldn't need to do that, but just use the
* ScrollablePage element instead.
*
* Use this if your flickable has some non standard properties, such as not covering the whole Page.
*
* @see ScrollablePage
*/
property Flickable flickable
/**
* @brief This property holds the actions for the page.
*
* These actions will be displayed in the toolbar on the desktop and inside
* the ContextDrawer on mobile.
*
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.Page {
* actions: [
* Kirigami.Action {...},
* Kirigami.Action {...}
* }
* }
* @endcode
*/
property list<T.Action> actions
/**
* @brief This property tells us if it is the currently active page.
*
* Specifies if it's the currently selected page in the window's pages row, or if layers
* are used whether this is the topmost item on the layers stack. If the page is
* not attached to either a column view or a stack view, expect this to be true.
*
* @since 2.1
*/
//TODO KF6: remove this or at least all the assumptions about the internal tree structure of items
// Kirigami.ColumnView.view.parent.parent is the StackView in which the ColumnView is, the condition means "is the ColumnView the current layer of the pagerow"
readonly property bool isCurrentPage: Kirigami.ColumnView.view
? (Kirigami.ColumnView.index === Kirigami.ColumnView.view.currentIndex && Kirigami.ColumnView.view.parent.parent.currentItem === Kirigami.ColumnView.view.parent)
: (parent && parent instanceof QQC2.StackView
? parent.currentItem === root
: true)
/**
* An item which stays on top of every other item in the page,
* if you want to make sure some elements are completely in a
* layer on top of the whole content, parent items to this one.
* It's a "local" version of ApplicationWindow's overlay
*
* @property Item overlay
* @since 2.5
*/
readonly property alias overlay: overlayItem
/**
* @brief This holds the icon that represents this page.
* @property var icon
*/
property P.ActionIconGroup icon: P.ActionIconGroup {}
/**
* @brief Progress of a task this page is doing.
*
* Set to undefined to indicate that there are no ongoing tasks.
*
* default: ``undefined``
*
* @property real progress
*/
property var progress: undefined
/**
* @brief The delegate which will be used to draw the page title.
*
* It can be customized to put any kind of Item in there.
*
* @since 2.7
*/
property Component titleDelegate: Component {
id: defaultTitleDelegate
P.DefaultPageTitleDelegate {
text: root.title
}
}
/**
* The item used as global toolbar for the page
* present only if we are in a PageRow as a page or as a layer,
* and the style is either Titles or ToolBar.
*
* @since 2.5
*/
readonly property Item globalToolBarItem: globalToolBar.item
/**
* The style for the automatically generated global toolbar: by default the Page toolbar is the one set globally in the PageRow in its globalToolBar.style property.
* A single page can override the application toolbar style for itself.
* It is discouraged to use this, except very specific exceptions, like a chat
* application which can't have controls on the bottom except the text field.
* If the Page is not in a PageRow, by default the toolbar will be invisible,
* so has to be explicitly set to Kirigami.ApplicationHeaderStyle.ToolBar if
* desired to be used in that case.
*/
property int globalToolBarStyle: {
if (globalToolBar.row && !globalToolBar.stack) {
return globalToolBar.row.globalToolBar.actualStyle;
} else if (globalToolBar.stack) {
return Kirigami.ApplicationHeaderStyle.ToolBar;
} else {
return Kirigami.ApplicationHeaderStyle.None;
}
}
//END properties
//BEGIN signal and signal handlers
/**
* @brief Emitted when the application requests a Back action.
*
* For instance a global "back" shortcut or the Android
* Back button has been pressed.
* The page can manage the back event by itself,
* and if it set event.accepted = true, it will stop the main
* application to manage the back event.
*/
signal backRequested(var event);
background: Rectangle {
color: Kirigami.Theme.backgroundColor
}
// FIXME: on material the shadow would bleed over
clip: root.header !== null;
Component.onCompleted: {
headerChanged();
parentChanged(root.parent);
globalToolBar.syncSource();
bottomToolBar.pageComplete = true
}
onParentChanged: {
if (!parent) {
return;
}
globalToolBar.stack = null;
globalToolBar.row = null;
if (root.Kirigami.ColumnView.view) {
globalToolBar.row = root.Kirigami.ColumnView.view.__pageRow;
}
if (root.T.StackView.view) {
globalToolBar.stack = root.T.StackView.view;
globalToolBar.row = root.T.StackView.view.parent instanceof Kirigami.PageRow ? root.T.StackView.view.parent : null;
}
if (globalToolBar.row) {
root.globalToolBarStyleChanged.connect(globalToolBar.syncSource);
globalToolBar.syncSource();
}
}
//END signals and signal handlers
// in data in order for them to not be considered for contentItem, contentChildren, contentData
data: [
Item {
id: overlayItem
parent: root
z: 9997
anchors {
fill: parent
topMargin: globalToolBar.height
}
}
]
// global top toolbar if we are in a PageRow (in the row or as a layer)
Kirigami.ColumnView.globalHeader: Loader {
id: globalToolBar
z: 9999
height: item ? item.implicitHeight : 0
width: root.width
property Kirigami.PageRow row
property T.StackView stack
// don't load async so that on slower devices we don't have the page content height changing while loading in
// otherwise, it looks unpolished and jumpy
asynchronous: false
visible: active
active: (root.titleDelegate !== defaultTitleDelegate || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar || root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.Titles)
onActiveChanged: {
if (active) {
syncSource();
}
}
function syncSource() {
if (root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.ToolBar &&
root.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.Titles &&
root.titleDelegate !== defaultTitleDelegate) {
sourceComponent = root.titleDelegate;
} else if (active) {
const url = root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar
? "private/globaltoolbar/ToolBarPageHeader.qml"
: "private/globaltoolbar/TitlesPageHeader.qml";
// TODO: find container reliably, remove assumption
setSource(Qt.resolvedUrl(url), {
pageRow: Qt.binding(() => row),
page: root,
current: Qt.binding(() => {
if (!row && !stack) {
return true;
} else if (stack) {
return stack;
} else {
return row.currentIndex === root.Kirigami.ColumnView.level;
}
}),
});
}
}
}
// bottom action buttons
Kirigami.ColumnView.globalFooter: Loader {
id: bottomToolBar
property T.Page page: root
property bool pageComplete: false
visible: active
active: {
// Important! Do not do anything until the page has been
// completed, so we are sure what the globalToolBarStyle is,
// otherwise we risk creating the content and then discarding it.
if (!pageComplete) {
return false;
}
if ((globalToolBar.row && globalToolBar.row.globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar)
|| root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.ToolBar
|| root.globalToolBarStyle === Kirigami.ApplicationHeaderStyle.None) {
return false;
}
if (root.actions.length === 0) {
return false;
}
// Legacy
if (typeof applicationWindow === "undefined") {
return true;
}
const drawer = applicationWindow() ? applicationWindow()['contextDrawer'] : undefined;
if (Boolean(drawer) && drawer.enabled && drawer.handleVisible) {
return false;
}
return true;
}
source: Qt.resolvedUrl("./private/globaltoolbar/ToolBarPageFooter.qml")
}
Layout.fillWidth: true
}
@@ -0,0 +1,238 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* An action used to load Pages coming from a common PagePool
* in a PageRow or QtQuickControls2 StackView.
*
* @see PagePool
*/
Kirigami.Action {
id: root
//BEGIN properties
/**
* @brief This property holds the url or filename of the page that this action will load.
*/
property string page
/**
* @brief This property holds the PagePool object used by this PagePoolAction.
*
* PagePool will make sure only one instance of the page identified by the page url will be created and reused.
* PagePool's lastLoaderUrl property will be used to control the mutual exclusivity of the checked
* state of the PagePoolAction instances sharing the same PagePool.
*/
property Kirigami.PagePool pagePool
/**
* The pageStack property accepts either a Kirigami.PageRow or a QtQuickControls2 StackView.
* The component that will instantiate the pages, which has to work with a stack logic.
* Kirigami.PageRow is recommended, but will work with QtQuicControls2 StackView as well.
*
* default: `bound to ApplicationWindow's global pageStack, which is a PageRow by default`
*/
property Item pageStack: typeof applicationWindow !== 'undefined' ? applicationWindow().pageStack : null
/**
* @brief This property sets the page in the pageStack after which
* new pages will be pushed.
*
* All pages present after the given basePage will be removed from the pageStack
*/
property T.Page basePage
/**
* This property holds a function that generate the property values for the created page
* when it is pushed onto the Kirigami.PagePool.
*
* Example usage:
* @code{.qml}
* Kirigami.PagePoolAction {
* text: i18n("Security")
* icon.name: "security-low"
* page: Qt.resolvedUrl("Security.qml")
* initialProperties: {
* return {
* room: root.room
* }
* }
* }
* @endcode
* @property QVariantMap initialProperties
*/
property var initialProperties
/**
* @brief This property sets whether PagePoolAction will use the layers property
* implemented by the pageStack.
*
* This is intended for use with PageRow layers to allow PagePoolActions to
* push context-specific pages onto the layers stack.
*
* default: ``false``
*
* @since 5.70
* @since org.kde.kirigami 2.12
*/
property bool useLayers: false
//END properties
/**
* @returns the page item held in the PagePool or null if it has not been loaded yet.
*/
function pageItem(): Item {
return pagePool.pageForUrl(page)
}
/**
* @returns true if the page has been loaded and placed on pageStack.layers
* and useLayers is true, otherwise returns null.
*/
function layerContainsPage(): bool {
if (!useLayers || !pageStack.hasOwnProperty("layers")) {
return false;
}
const pageItem = this.pageItem();
const item = pageStack.layers.find(item => item === pageItem);
return item !== null;
}
/**
* @returns true if the page has been loaded and placed on the pageStack,
* otherwise returns null.
*/
function stackContainsPage(): bool {
if (useLayers) {
return false;
}
return pageStack.columnView.containsItem(pagePool.pageForUrl(page));
}
checkable: true
onTriggered: {
if (page.length === 0 || !pagePool || !pageStack) {
return;
}
// User intends to "go back" to this layer.
const pageItem = this.pageItem();
if (layerContainsPage() && pageItem !== pageStack.layers.currentItem) {
pageStack.layers.replace(pageItem, pageItem); // force pop above
return;
}
// User intends to "go back" to this page.
if (stackContainsPage()) {
if (pageStack.hasOwnProperty("layers")) {
pageStack.layers.clear();
}
}
const stack = useLayers ? pageStack.layers : pageStack
if (pageItem != null && stack.currentItem == pageItem) {
return;
}
if (initialProperties && typeof(initialProperties) !== "object") {
console.warn("initialProperties must be of type object");
return;
}
if (!stack.hasOwnProperty("pop") || typeof stack.pop !== "function" || !stack.hasOwnProperty("push") || typeof stack.push !== "function") {
return;
}
if (pagePool.isLocalUrl(page)) {
if (basePage) {
stack.pop(basePage);
} else if (!useLayers) {
stack.clear();
}
stack.push(initialProperties
? pagePool.loadPageWithProperties(page, initialProperties)
: pagePool.loadPage(page));
} else {
const callback = item => {
if (basePage) {
stack.pop(basePage);
} else if (!useLayers) {
stack.clear();
}
stack.push(item);
};
if (initialProperties) {
pagePool.loadPage(page, initialProperties, callback);
} else {
pagePool.loadPage(page, callback);
}
}
}
// Exposing this as a property is required as Action does not have a default property
property QtObject _private: QtObject {
id: _private
function setChecked(checked) {
root.checked = checked;
}
function clearLayers() {
root.pageStack.layers.clear();
}
property list<Connections> connections: [
Connections {
target: root.pageStack
function onCurrentItemChanged() {
if (root.useLayers) {
if (root.layerContainsPage()) {
_private.clearLayers();
}
if (root.checkable) {
_private.setChecked(false);
}
} else {
if (root.checkable) {
_private.setChecked(root.stackContainsPage());
}
}
}
},
Connections {
enabled: root.pageStack.hasOwnProperty("layers")
target: root.pageStack.layers
function onCurrentItemChanged() {
if (root.useLayers && root.checkable) {
_private.setChecked(root.layerContainsPage());
} else {
if (root.pageStack.layers.depth === 1 && root.stackContainsPage()) {
_private.setChecked(true);
}
}
}
}
]
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief This is a standard password text field.
*
* Example usage:
* @code{.qml}
* import org.kde.kirigami as Kirigami
*
* Kirigami.PasswordField {
* id: passwordField
* onAccepted: {
* // check if passwordField.text is valid
* }
* }
* @endcode
*
* @inherit org::kde::kirgami::ActionTextField
* @since 5.57
* @author Carl Schwan <carl@carlschwan.eu>
*/
Kirigami.ActionTextField {
id: root
/**
* @brief This property tells whether the password will be displayed in cleartext rather than obfuscated.
*
* default: ``false``
*
* @since 5.57
*/
property bool showPassword: false
echoMode: root.showPassword ? TextInput.Normal : TextInput.Password
placeholderText: qsTr("Password")
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhSensitiveData
rightActions: Kirigami.Action {
text: root.showPassword ? qsTr("Hide Password") : qsTr("Show Password")
icon.name: root.showPassword ? "password-show-off" : "password-show-on"
onTriggered: root.showPassword = !root.showPassword
}
Keys.onPressed: event => {
if (event.matches(StandardKey.Undo)) {
// Disable undo action for security reasons
// See QTBUG-103934
event.accepted = true
}
}
}
@@ -0,0 +1,298 @@
/*
* SPDX-FileCopyrightText: 2020 Nate Graham <nate@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as P
/**
* @brief A placeholder message indicating that a view is empty.
*
* The message comprises a label with text, an optional explanation below the main text,
* an optional icon above all the text, and an optional button below all the text which
* can be used to easily show the user what to do next to add content to the view.
*
* The explanatory text is selectable and can contain clickable links. In this latter
* case, client code must implement an ``onLinkactivated:`` signal handler or the links
* will not work.
*
* The top-level component is a ColumnLayout, so additional components items can
* simply be added as child items and they will be positioned sanely.
*
* Example usage:
* @code{.qml}
** used as a "this view is empty" message
* import org.kde.kirigami as Kirigami
*
* ListView {
* id: listView
* model: [...]
* delegate: [...]
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: listView.count === 0
*
* text: "There are no items in this list"
* }
* }
* @endcode
* @code{.qml}
** Used as a "here's how to proceed" message
* import org.kde.kirigami as Kirigami
*
* ListView {
* id: listView
* model: [...]
* delegate: [...]
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: listView.count === 0
*
* text: "Add an item to proceed"
*
* helpfulAction: Kirigami.Action {
* icon.name: "list-add"
* text: "Add item..."
* onTriggered: {
* [...]
* }
* }
* }
* [...]
* }
* @endcode
* @code{.qml}
** Used as a "there was a problem here" message
* import org.kde.kirigami as Kirigami
*
* Kirigami.Page {
* id: root
* readonly property bool networkConnected: [...]
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: root.networkConnected
*
* icon.name: "network-disconnect"
* text: "Unable to load content"
* explanation: "Please try again later."
* " Visit <a href="https://foo.com/com>this link</a> for more details."
* onLinkActivated: link => Qt.openUrlExternally(link)
* }
* }
* @endcode
* @code{.qml}
* import org.kde.kirigami as Kirigami
*
** Used as a "Here's what you do next" button
* Kirigami.Page {
* id: root
*
* Kirigami.PlaceholderMessage {
* anchors.centerIn: parent
* width: parent.width - (Kirigami.Units.largeSpacing * 4)
*
* visible: root.loading
*
* helpfulAction: Kirigami.Action {
* icon.name: "list-add"
* text: "Add item..."
* onTriggered: {
* [...]
* }
* }
* }
* }
* @endcode
* @inherit QtQuick.Layouts.ColumnLayout
* @since 2.12
*/
ColumnLayout {
id: root
enum Type {
Actionable,
Informational
}
//BEGIN properties
/**
* @brief This property holds the PlaceholderMessage type.
*
* The type of the message. This can be:
* * ``Kirigami.PlaceholderMessage.Type.Actionable``: Makes it more attention-getting. Useful when the user is expected to interact with the message.
* * ``Kirigami.PlaceholderMessage.Type.Informational``: Makes it less prominent. Useful when the message in only informational.
*
* default: `if a helpfulAction is provided this will be of type Actionable otherwise of type Informational.`
*
* @since 5.94
*/
property int type: actionButton.action?.enabled
? PlaceholderMessage.Type.Actionable
: PlaceholderMessage.Type.Informational
/**
* @brief This property holds the text to show in the placeholder label.
*
* Optional; if not defined, the message will have no large text label
* text. If both text: and explanation: are omitted, the message will have
* no text and only an icon, action button, and/or other custom content.
*
* @since 5.70
*/
property string text
/**
* @brief This property holds the smaller explanatory text to show below the larger title-style text
*
* Useful for providing a user-friendly explanation on how to proceed.
*
* Optional; if not defined, the message will have no supplementary
* explanatory text.
*
* @since 5.80
*/
property string explanation
/**
* @brief This property provides an icon to display above the top text label.
* @note It accepts ``icon.name`` and ``icon.source`` to set the icon source.
* It is suggested to use ``icon.name``.
*
* Optional; if undefined, the message will have no icon.
* Falls back to `undefined` if the specified icon is not valid or cannot
* be loaded.
*
* @see org::kde::kirigami::private::ActionIconGroup
* @since 5.70
*/
readonly property P.ActionIconGroup icon: P.ActionIconGroup {
width: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
height: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
color: Kirigami.Theme.textColor
}
/**
* @brief This property holds an action that helps the user proceed.
*
* Typically used to guide the user to the next step for adding
* content or items to an empty view.
*
* Optional; if undefined, no button will appear below the text label.
*
* @property QtQuick.Controls.Action helpfulAction
* @since 5.70
*/
property alias helpfulAction: actionButton.action
/**
* This property holds the link embedded in the explanatory message text that
* the user is hovering over.
*/
readonly property alias hoveredLink: label.hoveredLink
/**
* This signal is emitted when a link is hovered in the explanatory message
* text.
* @param The hovered link.
*/
signal linkHovered(string link)
/**
* This signal is emitted when a link is clicked or tapped in the explanatory
* message text.
* @param The clicked or tapped link.
*/
signal linkActivated(string link)
//END properties
spacing: Kirigami.Units.largeSpacing
Component.onCompleted: _announce();
onVisibleChanged: {
_announce();
}
function _announce()
{
if (visible && Accessible.announce) {
// Accessible.announce was added in Qt 6.8.0
if (root.text.length > 0)
Accessible.announce(root.text);
if (root.explanation.length > 0)
Accessible.announce(root.explanation);
}
}
Kirigami.Icon {
visible: source !== undefined
opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.5
Layout.alignment: Qt.AlignHCenter
Layout.preferredWidth: root.icon.width
Layout.preferredHeight: root.icon.height
color: root.icon.color
source: {
if (root.icon.source.length > 0) {
return root.icon.source
} else if (root.icon.name.length > 0) {
return root.icon.name
}
return undefined
}
}
Kirigami.Heading {
text: root.text
visible: text.length > 0
type: Kirigami.Heading.Primary
opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.65
Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
wrapMode: Text.WordWrap
}
Kirigami.SelectableLabel {
id: label
text: root.explanation
visible: root.explanation.length > 0
opacity: root.type === PlaceholderMessage.Type.Actionable ? 1 : 0.65
horizontalAlignment: Qt.AlignHCenter
wrapMode: Text.WordWrap
Layout.fillWidth: true
onLinkHovered: link => root.linkHovered(link)
onLinkActivated: link => root.linkActivated(link)
}
QQC2.Button {
id: actionButton
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: Kirigami.Units.gridUnit
visible: action?.enabled ?? false
}
}
@@ -0,0 +1,309 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.kirigami.templates as KT
import "private"
// TODO KF6: undo many workarounds to make existing code work?
/**
* @brief ScrollablePage is a Page that holds scrollable content, such as a ListView.
*
* Scrolling and scrolling indicators will be automatically managed.
*
* Example usage:
* @code
* ScrollablePage {
* id: root
* // The page will automatically be scrollable
* Rectangle {
* width: root.width
* height: 99999
* }
* }
* @endcode
*
* @warning Do not put a ScrollView inside of a ScrollablePage; children of a ScrollablePage are already inside a ScrollView.
*
* Another behavior added by this class is a "scroll down to refresh" behavior
* It also can give the contents of the flickable to have more top margins in order
* to make possible to scroll down the list to reach it with the thumb while using the
* phone with a single hand.
*
* Implementations should handle the refresh themselves as follows
*
* Example usage:
* @code
* Kirigami.ScrollablePage {
* id: view
* supportsRefreshing: true
* onRefreshingChanged: {
* if (refreshing) {
* myModel.refresh();
* }
* }
* ListView {
* // NOTE: MyModel doesn't come from the components,
* // it's purely an example on how it can be used together
* // some application logic that can update the list model
* // and signals when it's done.
* model: MyModel {
* onRefreshDone: view.refreshing = false;
* }
* delegate: ItemDelegate {}
* }
* }
* [...]
* @endcode
*/
Kirigami.Page {
id: root
//BEGIN properties
/**
* @brief This property tells whether the list is asking for a refresh.
*
* This property will automatically be set to true when the user pulls the list down enough,
* which in return, shows a loading spinner. When this is set to true, it signals
* the application logic to start its refresh procedure.
*
* default: ``false``
*
* @note The application itself will have to set back this property to false when done.
*/
property bool refreshing: false
/**
* @brief This property sets whether scrollable page supports "pull down to refresh" behaviour.
*
* default: ``false``
*/
property bool supportsRefreshing: false
/**
* @brief This property holds the main Flickable item of this page.
* @deprecated here for compatibility; will be removed in KF6.
*/
property Flickable flickable: Flickable {} // FIXME KF6: this empty flickable exists for compatibility reasons. some apps assume flickable exists right from the beginning but ScrollView internally assumes it does not
onFlickableChanged: scrollView.contentItem = flickable;
/**
* @brief This property sets the vertical scrollbar policy.
* @property Qt::ScrollBarPolicy verticalScrollBarPolicy
*/
property int verticalScrollBarPolicy
/**
* @brief Set if the vertical scrollbar should be interactable.
* @property bool verticalScrollBarInteractive
*/
property bool verticalScrollBarInteractive: true
/**
* @brief This property sets the horizontal scrollbar policy.
* @property Qt::ScrollBarPolicy horizontalScrollBarPolicy
*/
property int horizontalScrollBarPolicy: QQC2.ScrollBar.AlwaysOff
/**
* @brief Set if the horizontal scrollbar should be interactable.
* @property bool horizontalScrollBarInteractive
*/
property bool horizontalScrollBarInteractive: true
default property alias scrollablePageData: itemsParent.data
property alias scrollablePageChildren: itemsParent.children
/*
* @deprecated here for compatibility; will be removed in KF6.
*/
property QtObject mainItem
onMainItemChanged: {
print("Warning: the mainItem property is deprecated");
scrollablePageData.push(mainItem);
}
/**
* @brief This property sets whether it is possible to navigate the items in a view that support it.
*
* If true, and if flickable is an item view (e.g. ListView, GridView), it will be possible
* to navigate the view current items with keyboard up/down arrow buttons.
* Also, any key event will be forwarded to the current list item.
*
* default: ``true``
*/
property bool keyboardNavigationEnabled: true
//END properties
implicitWidth: flickable?.contentItem?.implicitWidth
?? Math.max(implicitBackgroundWidth + leftInset + rightInset,
contentWidth + leftPadding + rightPadding,
implicitHeaderWidth,
implicitFooterWidth)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
contentHeight + topPadding + bottomPadding
+ (implicitHeaderHeight > 0 ? implicitHeaderHeight + spacing : 0)
+ (implicitFooterHeight > 0 ? implicitFooterHeight + spacing : 0))
contentHeight: flickable?.contentHeight ?? 0
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: flickable?.hasOwnProperty("model") ? Kirigami.Theme.View : Kirigami.Theme.Window
Keys.forwardTo: {
if (root.keyboardNavigationEnabled && root.flickable) {
if (("currentItem" in root.flickable) && root.flickable.currentItem) {
return [ root.flickable.currentItem, root.flickable ];
} else {
return [ root.flickable ];
}
} else {
return [];
}
}
contentItem: QQC2.ScrollView {
id: scrollView
anchors {
top: root.header?.visible
? root.header.bottom
: parent.top
bottom: root.footer?.visible ? root.footer.top : parent.bottom
left: parent.left
right: parent.right
}
clip: true
QQC2.ScrollBar.horizontal.policy: root.horizontalScrollBarPolicy
QQC2.ScrollBar.horizontal.interactive: root.horizontalScrollBarInteractive
QQC2.ScrollBar.vertical.policy: root.verticalScrollBarPolicy
QQC2.ScrollBar.vertical.interactive: root.verticalScrollBarInteractive
}
data: [
// Has to be a MouseArea that accepts events otherwise touch events on Wayland will get lost
MouseArea {
id: scrollingArea
width: root.horizontalScrollBarPolicy === QQC2.ScrollBar.AlwaysOff ? root.flickable.width : Math.max(root.flickable.width, implicitWidth)
height: Math.max(root.flickable.height, implicitHeight)
implicitWidth: {
let implicit = 0;
for (const child of itemsParent.visibleChildren) {
if (child.implicitWidth <= 0) {
implicit = Math.max(implicit, child.width);
} else {
implicit = Math.max(implicit, child.implicitWidth);
}
}
return implicit + itemsParent.anchors.leftMargin + itemsParent.anchors.rightMargin;
}
implicitHeight: {
let implicit = 0;
for (const child of itemsParent.visibleChildren) {
if (child.implicitHeight <= 0) {
implicit = Math.max(implicit, child.height);
} else {
implicit = Math.max(implicit, child.implicitHeight);
}
}
return implicit + itemsParent.anchors.topMargin + itemsParent.anchors.bottomMargin;
}
Item {
id: itemsParent
property Flickable flickable
anchors {
fill: parent
topMargin: root.topPadding
leftMargin: root.leftPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
onChildrenChanged: {
const child = children[children.length - 1];
if (child instanceof QQC2.ScrollView) {
print("Warning: it's not supported to have ScrollViews inside a ScrollablePage")
}
}
}
Binding {
target: root.flickable
property: "bottomMargin"
value: root.bottomPadding
restoreMode: Binding.RestoreBinding
}
},
Loader {
id: busyIndicatorLoader
active: root.supportsRefreshing
sourceComponent: PullDownIndicator {
parent: root
active: root.refreshing
onTriggered: root.refreshing = true
}
}
]
Component.onCompleted: {
let flickableFound = false;
for (const child of itemsParent.data) {
if (child instanceof Flickable) {
// If there were more flickable children, take the last one, as behavior compatibility
// with old internal ScrollView
child.activeFocusOnTab = true;
root.flickable = child;
flickableFound = true;
if (child instanceof ListView) {
child.keyNavigationEnabled = true;
child.keyNavigationWraps = false;
}
} else if (child instanceof Item) {
child.anchors.left = itemsParent.left;
child.anchors.right = itemsParent.right;
} else if (child instanceof KT.OverlaySheet) {
// Reparent sheets, needs to be done before Component.onCompleted
if (child.parent === itemsParent || child.parent === null) {
child.parent = root;
}
}
}
if (flickableFound) {
scrollView.contentItem = root.flickable;
root.flickable.parent = scrollView;
// The flickable needs focus only if the page didn't already explicitly set focus to some other control (eg a text field in the header)
Qt.callLater(() => {
if (root.activeFocus) {
root.flickable.forceActiveFocus();
}
});
// Some existing code incorrectly uses anchors
root.flickable.anchors.fill = undefined;
root.flickable.anchors.top = undefined;
root.flickable.anchors.left = undefined;
root.flickable.anchors.right = undefined;
root.flickable.anchors.bottom = undefined;
scrollingArea.visible = false;
} else {
scrollView.contentItem = root.flickable;
scrollingArea.parent = root.flickable.contentItem;
scrollingArea.visible = true;
root.flickable.contentHeight = Qt.binding(() => scrollingArea.implicitHeight - root.flickable.topMargin - root.flickable.bottomMargin);
root.flickable.contentWidth = Qt.binding(() => scrollingArea.implicitWidth);
scrollView.forceActiveFocus(Qt.TabFocusReason); // QTBUG-44043 : Focus on currentItem instead of pageStack itself
}
root.flickable.flickableDirection = Flickable.VerticalFlick;
// HACK: Qt's default flick deceleration is too high, and we can't change it from plasma-integration, see QTBUG-121500
root.flickable.flickDeceleration = 1500;
root.flickable.maximumFlickVelocity = 5000;
}
}
@@ -0,0 +1,132 @@
/*
* SPDX-FileCopyrightText: 2019 Carl-Lucien Schwan <carl@carlschwan.eu>
* SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief This is a standard TextField following the KDE HIG, which, by default,
* uses Ctrl+F as the focus keyboard shortcut and "Search…" as a placeholder text.
*
* Example usage for the search field component:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.SearchField {
* id: searchField
* onAccepted: console.log("Search text is " + searchField.text)
* }
* @endcode
*
* @inherit org::kde::kirigami::ActionTextField
*/
Kirigami.ActionTextField {
id: root
/**
* @brief This property sets whether the accepted signal is fired automatically
* when the text is changed.
*
* Setting this to false will require that the user presses return or enter
* (the same way a QtQuick.Controls.TextInput works).
*
* default: ``true``
*
* @since 5.81
* @since org.kde.kirigami 2.16
*/
property bool autoAccept: true
/**
* @brief This property sets whether to delay automatic acceptance of the search input.
*
* Set this to true if your search is expensive (such as for online
* operations or in exceptionally slow data sets) and want to delay it
* for 2.5 seconds.
*
* @note If you must have immediate feedback (filter-style), use the
* text property directly instead of accepted()
*
* default: ``false``
*
* @since 5.81
* @since org.kde.kirigami 2.16
*/
property bool delaySearch: false
// padding to accommodate search icon nicely
leftPadding: if (effectiveHorizontalAlignment === TextInput.AlignRight) {
return _rightActionsRow.width + Kirigami.Units.smallSpacing
} else {
return searchIcon.width + Kirigami.Units.smallSpacing * 3
}
rightPadding: if (effectiveHorizontalAlignment === TextInput.AlignRight) {
return searchIcon.width + Kirigami.Units.smallSpacing * 3
} else {
return _rightActionsRow.width + Kirigami.Units.smallSpacing
}
Kirigami.Icon {
id: searchIcon
LayoutMirroring.enabled: root.effectiveHorizontalAlignment === TextInput.AlignRight
anchors.left: root.left
anchors.leftMargin: Kirigami.Units.smallSpacing * 2
anchors.verticalCenter: root.verticalCenter
anchors.verticalCenterOffset: Math.round((root.topPadding - root.bottomPadding) / 2)
implicitHeight: Kirigami.Units.iconSizes.sizeForLabels
implicitWidth: Kirigami.Units.iconSizes.sizeForLabels
color: root.placeholderTextColor
source: "search"
}
placeholderText: qsTr("Search…")
Accessible.name: qsTr("Search")
Accessible.searchEdit: true
focusSequence: StandardKey.Find
inputMethodHints: Qt.ImhNoPredictiveText
EnterKey.type: Qt.EnterKeySearch
rightActions: [
Kirigami.Action {
//ltr confusingly refers to the direction of the arrow in the icon, not the text direction which it should be used in
icon.name: root.effectiveHorizontalAlignment === TextInput.AlignRight ? "edit-clear-locationbar-ltr" : "edit-clear-locationbar-rtl"
visible: root.text.length > 0
text: qsTr("Clear search")
onTriggered: {
root.clear();
// Since we are always sending the accepted signal here (whether or not the user has requested
// that the accepted signal be delayed), stop the delay timer that gets started by the text changing
// above, so that we don't end up sending two of those in rapid succession.
fireSearchDelay.stop();
root.accepted();
}
}
]
Timer {
id: fireSearchDelay
interval: root.delaySearch ? Kirigami.Units.humanMoment : Kirigami.Units.shortDuration
running: false
repeat: false
onTriggered: {
if (root.acceptableInput) {
root.accepted();
}
}
}
onAccepted: {
fireSearchDelay.running = false
}
onTextChanged: {
if (root.autoAccept) {
fireSearchDelay.restart();
} else {
fireSearchDelay.stop();
}
}
}
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2022 Fushan Wen <qydwhotmail@gmail.com>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
* SPDX-FileCopyrightText: 2024 Akseli Lahtinen <akselmo@akselmo.dev>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
/**
* @brief This is a label which supports text selection.
*
* The label uses TextEdit component, which is wrapped inside a Control component.
*
*
* Example usage:
* @code{.qml}
* Kirigami.SelectableLabel {
* text: "Label"
* }
* @endcode
*
* @see https://bugreports.qt.io/browse/QTBUG-14077
* @since org.kde.kirigami 2.20
* @since 5.95
* @inherit QtQuick.Control
*/
QQC2.Control {
id: root
//TODO KF7: Cleanup from unnecessary properties we dont need to expose for a label
activeFocusOnTab: false
padding: undefined
topPadding: undefined
leftPadding: undefined
rightPadding: undefined
bottomPadding: undefined
Accessible.name: textEdit.text
Accessible.role: Accessible.StaticText
Accessible.selectableText: true
Accessible.editable: false
property bool contextMenuEnabled: true
property alias readOnly: textEdit.readOnly
property alias selectByMouse: textEdit.selectByMouse
property alias color: textEdit.color
property alias selectedTextColor: textEdit.selectedTextColor
property alias selectionColor: textEdit.selectionColor
property alias text: textEdit.text
property alias baseUrl: textEdit.baseUrl
property var cursorShape
property alias horizontalAlignment: textEdit.horizontalAlignment
property alias verticalAlignment: textEdit.verticalAlignment
property alias textFormat: textEdit.textFormat
property alias wrapMode: textEdit.wrapMode
property alias activeFocusOnPress: textEdit.activeFocusOnPress
property alias cursorDelegate: textEdit.cursorDelegate
property alias cursorPosition: textEdit.cursorPosition
property alias cursorVisible: textEdit.cursorVisible
property alias inputMethodHints: textEdit.inputMethodHints
property alias mouseSelectionMode: textEdit.mouseSelectionMode
property alias overwriteMode: textEdit.overwriteMode
property alias persistentSelection: textEdit.persistentSelection
property alias renderType: textEdit.renderType
property alias selectByKeyboard: textEdit.selectByKeyboard
property alias tabStopDistance: textEdit.tabStopDistance
property alias textMargin: textEdit.textMargin
readonly property alias canPaste: textEdit.canPaste
readonly property alias canRedo: textEdit.canRedo
readonly property alias canUndo: textEdit.canUndo
readonly property alias inputMethodComposing: textEdit.inputMethodComposing
readonly property alias length: textEdit.length
readonly property alias lineCount: textEdit.lineCount
readonly property alias selectionEnd: textEdit.selectionEnd
readonly property alias selectionStart: textEdit.selectionStart
readonly property alias contentHeight: textEdit.contentHeight
readonly property alias contentWidth: textEdit.contentWidth
readonly property alias hoveredLink: textEdit.hoveredLink
readonly property alias preeditText: textEdit.preeditText
readonly property alias selectedText: textEdit.selectedText
readonly property alias cursorRectangle: textEdit.cursorRectangle
readonly property alias cursorSelection: textEdit.cursorSelection
readonly property alias effectiveHorizontalAlignment: textEdit.effectiveHorizontalAlignment
readonly property alias textDocument: textEdit.textDocument
signal editingFinished()
signal clicked()
signal linkActivated(string link)
signal linkHovered(string link)
//BEGIN TextArea dummy entries
property var flickable: undefined
property var placeholderText: undefined
property var placeholderTextColor: undefined
signal pressAndHold(MouseEvent event)
signal pressed(MouseEvent event)
signal released(MouseEvent event)
//END TextArea dummy entries
contentItem: TextEdit {
id: textEdit
/**
* @brief This property holds the cursor shape that will appear whenever
* the mouse is hovering over the label.
*
* default: @c Qt.IBeamCursor
*
* @property Qt::CursorShape cursorShape
*/
property alias cursorShape: hoverHandler.cursorShape
activeFocusOnTab: root.activeFocusOnTab
color: Kirigami.Theme.textColor
readOnly: true
selectByMouse: true
padding: 0
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
textFormat: TextEdit.AutoText
verticalAlignment: TextEdit.AlignTop
wrapMode: TextEdit.WordWrap
font: root.font
persistentSelection: contextMenu.visible
onLinkActivated: root.linkActivated(textEdit.hoveredLink)
onLinkHovered: root.linkHovered(textEdit.hoveredLink)
onEditingFinished: root.editingFinished()
HoverHandler {
id: hoverHandler
// By default HoverHandler accepts the left button while it shouldn't accept anything,
// causing https://bugreports.qt.io/browse/QTBUG-106489.
// Qt.NoButton unfortunately is not a valid value for acceptedButtons.
// Disabling masks the problem, but
// there is no proper workaround other than an upstream fix
// See qqc2-desktop-style Label.qml
enabled: false
cursorShape: root.cursorShape ? root.cursorShape : (textEdit.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor)
}
TapHandler {
// For custom click actions we want selection to be turned off
enabled: !textEdit.selectByMouse
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.LeftButton
onTapped: root.clicked()
}
TapHandler {
enabled: textEdit.selectByMouse && root.contextMenuEnabled
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad | PointerDevice.Stylus
acceptedButtons: Qt.RightButton
onPressedChanged: if (pressed) {
contextMenu.popup();
}
}
QQC2.Menu {
id: contextMenu
QQC2.MenuItem {
action: T.Action {
icon.name: "edit-copy-symbolic"
text: qsTr("Copy")
shortcut: StandardKey.Copy
}
enabled: textEdit.selectedText.length > 0
onTriggered: {
textEdit.copy();
textEdit.deselect();
}
}
QQC2.MenuSeparator {}
QQC2.MenuItem {
action: T.Action {
icon.name: "edit-select-all-symbolic"
text: qsTr("Select All")
shortcut: StandardKey.SelectAll
}
onTriggered: {
textEdit.selectAll();
}
}
}
}
}
@@ -0,0 +1,541 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <notmart@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import "private"
/**
* An item delegate intended to support extra actions obtainable
* by uncovering them by dragging away the item with the handle.
*
* This acts as a container for normal list items.
*
* Example usage:
* @code
* ListView {
* model: myModel
* delegate: SwipeListItem {
* QQC2.Label {
* text: model.text
* }
* actions: [
* Action {
* icon.name: "document-decrypt"
* onTriggered: print("Action 1 clicked")
* },
* Action {
* icon.name: model.action2Icon
* onTriggered: //do something
* }
* ]
* }
*
* }
* @endcode
*
* @inherit QtQuick.Templates.SwipeDelegate
*/
QQC2.SwipeDelegate {
id: listItem
//BEGIN properties
/**
* @brief This property sets whether the item should emit signals related to mouse interaction.
*
* default: ``true``
*
* @deprecated Use hoverEnabled instead.
* @property bool supportsMouseEvents
*/
property alias supportsMouseEvents: listItem.hoverEnabled
/**
* @brief This property tells whether the cursor is currently hovering over the item.
*
* On mobile touch devices, this will be true only when pressed.
*
* @see QtQuick.Templates.ItemDelegate::hovered
* @deprecated This will be removed in KF6; use the ``hovered`` property instead.
* @property bool containsMouse
*/
readonly property alias containsMouse: listItem.hovered
/**
* @brief This property sets whether instances of this list item will alternate
* between two colors, helping readability.
*
* It is suggested to use this only when implementing a view with multiple columns.
*
* default: ``false``
*
* @since 2.7
*/
property bool alternatingBackground: false
/**
* @brief This property sets whether this item is a section delegate.
*
* Setting this to true will make the list item look like a "title" for items under it.
*
* default: ``false``
*
* @see ListSectionHeader
*/
property bool sectionDelegate: false
/**
* @brief This property sets whether the separator is visible.
*
* The separator is a line between this and the item under it.
*
* default: ``false``
*/
property bool separatorVisible: false
/**
* @brief This property holds the background color of the list item.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.backgroundColor``
*/
property color backgroundColor: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the background color to be used when
* background alternating is enabled.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.alternateBackgroundColor``
*
* @since 2.7
*/
property color alternateBackgroundColor: Kirigami.Theme.alternateBackgroundColor
/**
* @brief This property holds the color of the background
* when the item is pressed or selected.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.highlightColor``
*/
property color activeBackgroundColor: Kirigami.Theme.highlightColor
/**
* @brief This property holds the color of the text in the item.
*
* It is advised to use the default value.
* default: ``Theme.textColor``
*
* If custom text elements are inserted in a SwipeListItem,
* their color will have to be manually set with this property.
*/
property color textColor: Kirigami.Theme.textColor
/**
* @brief This property holds the color of the text when the item is pressed or selected.
*
* It is advised to use the default value.
* default: ``Kirigami.Theme.highlightedTextColor``
*
* If custom text elements are inserted in a SwipeListItem,
* their color property will have to be manually bound with this property
*/
property color activeTextColor: Kirigami.Theme.highlightedTextColor
/**
* @brief This property tells whether actions are visible and interactive.
*
* True if it's possible to see and interact with the item's actions.
*
* Actions become hidden while editing of an item, for example.
*
* @since 2.5
*/
readonly property bool actionsVisible: actionsLayout.hasVisibleActions
/**
* @brief This property sets whether actions behind this SwipeListItem will always be visible.
*
* default: `true in desktop and tablet mode`
*
* @since 2.15
*/
property bool alwaysVisibleActions: !Kirigami.Settings.isMobile
/**
* @brief This property holds actions of the list item.
*
* At most 4 actions can be revealed when sliding away the list item;
* others will be shown in the overflow menu.
*/
property list<T.Action> actions
/**
* @brief This property holds the width of the overlay.
*
* The value can represent the width of the handle component or the action layout.
*
* @since 2.19
* @property real overlayWidth
*/
readonly property alias overlayWidth: overlayLoader.width
//END properties
LayoutMirroring.childrenInherit: true
hoverEnabled: true
implicitWidth: contentItem ? implicitContentWidth : Kirigami.Units.gridUnit * 12
width: parent ? parent.width : implicitWidth
implicitHeight: Math.max(Kirigami.Units.gridUnit * 2, implicitContentHeight) + topPadding + bottomPadding
padding: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode ? Kirigami.Units.largeSpacing : Kirigami.Units.smallSpacing
leftPadding: padding * 2 + (mirrored ? overlayLoader.paddingOffset : 0)
rightPadding: padding * 2 + (mirrored ? 0 : overlayLoader.paddingOffset)
topPadding: padding
bottomPadding: padding
QtObject {
id: internal
property Flickable view: listItem.ListView.view || (listItem.parent ? (listItem.parent.ListView.view || (listItem.parent instanceof Flickable ? listItem.parent : null)) : null)
function viewHasPropertySwipeFilter(): bool {
return view && view.parent && view.parent.parent && "_swipeFilter" in view.parent.parent;
}
readonly property QtObject swipeFilterItem: (viewHasPropertySwipeFilter() && view.parent.parent._swipeFilter) ? view.parent.parent._swipeFilter : null
readonly property bool edgeEnabled: swipeFilterItem ? swipeFilterItem.currentItem === listItem || swipeFilterItem.currentItem === listItem.parent : false
// install the SwipeItemEventFilter
onViewChanged: {
if (listItem.alwaysVisibleActions || !Kirigami.Settings.tabletMode) {
return;
}
if (viewHasPropertySwipeFilter() && Kirigami.Settings.tabletMode && !internal.view.parent.parent._swipeFilter) {
const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
internal.view.parent.parent._swipeFilter = component.createObject(internal.view.parent.parent);
component.destroy();
}
}
}
Connections {
target: Kirigami.Settings
function onTabletModeChanged() {
if (!internal.viewHasPropertySwipeFilter()) {
return;
}
if (Kirigami.Settings.tabletMode) {
if (!internal.swipeFilterItem) {
const component = Qt.createComponent(Qt.resolvedUrl("../private/SwipeItemEventFilter.qml"));
listItem.ListView.view.parent.parent._swipeFilter = component.createObject(listItem.ListView.view.parent.parent);
component.destroy();
}
} else {
if (listItem.ListView.view.parent.parent._swipeFilter) {
listItem.ListView.view.parent.parent._swipeFilter.destroy();
slideAnim.to = 0;
slideAnim.restart();
}
}
}
}
//BEGIN items
Loader {
id: overlayLoader
readonly property int paddingOffset: (visible ? width : 0) + Kirigami.Units.smallSpacing
readonly property var theAlias: anchors
function validate(want, defaultValue) {
const expectedLeftPadding = () => listItem.padding * 2 + (listItem.mirrored ? overlayLoader.paddingOffset : 0)
const expectedRightPadding = () => listItem.padding * 2 + (listItem.mirrored ? 0 : overlayLoader.paddingOffset)
const warningText =
`Don't override the leftPadding or rightPadding on a SwipeListItem!\n` +
`This makes it impossible for me to adjust my layout as I need to for various usecases.\n` +
`I'll try to fix the mistake for you, but you should remove your overrides from your app's code entirely.\n` +
`If I can't fix the paddings, I'll fall back to a default layout, but it'll be slightly incorrect and lacks\n` +
`adaptations needed for touch screens and right-to-left languages, among other things.`
if (listItem.leftPadding != expectedLeftPadding() || listItem.rightPadding != expectedRightPadding()) {
listItem.leftPadding = Qt.binding(expectedLeftPadding)
listItem.rightPadding = Qt.binding(expectedRightPadding)
console.warn(warningText)
return defaultValue
}
return want
}
anchors {
right: validate(listItem.mirrored ? undefined : (contentItem ? contentItem.right : undefined), contentItem ? contentItem.right : undefined)
rightMargin: validate(-paddingOffset, 0)
left: validate(!listItem.mirrored ? undefined : (contentItem ? contentItem.left : undefined), undefined)
leftMargin: validate(-paddingOffset, 0)
top: parent.top
bottom: parent.bottom
}
LayoutMirroring.enabled: false
parent: listItem
z: contentItem ? contentItem.z + 1 : 0
width: item ? item.implicitWidth : actionsLayout.implicitWidth
active: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode
visible: listItem.actionsVisible && opacity > 0
asynchronous: true
sourceComponent: handleComponent
opacity: listItem.alwaysVisibleActions || Kirigami.Settings.tabletMode || listItem.hovered ? 1 : 0
Behavior on opacity {
OpacityAnimator {
id: opacityAnim
duration: Kirigami.Units.veryShortDuration
easing.type: Easing.InOutQuad
}
}
}
Component {
id: handleComponent
MouseArea {
id: dragButton
anchors {
right: parent.right
}
implicitWidth: Kirigami.Units.iconSizes.smallMedium
preventStealing: true
readonly property real openPosition: (listItem.width - width - listItem.leftPadding * 2)/listItem.width
property real startX: 0
property real lastPosition: 0
property bool openIntention
onPressed: mouse => {
startX = mapToItem(listItem, 0, 0).x;
}
onClicked: mouse => {
if (Math.abs(mapToItem(listItem, 0, 0).x - startX) > Qt.styleHints.startDragDistance) {
return;
}
if (listItem.mirrored) {
if (listItem.swipe.position < 0.5) {
slideAnim.to = openPosition
} else {
slideAnim.to = 0
}
} else {
if (listItem.swipe.position > -0.5) {
slideAnim.to = -openPosition
} else {
slideAnim.to = 0
}
}
slideAnim.restart();
}
onPositionChanged: mouse => {
const pos = mapToItem(listItem, mouse.x, mouse.y);
if (listItem.mirrored) {
listItem.swipe.position = Math.max(0, Math.min(openPosition, (pos.x / listItem.width)));
openIntention = listItem.swipe.position > lastPosition;
} else {
listItem.swipe.position = Math.min(0, Math.max(-openPosition, (pos.x / (listItem.width -listItem.rightPadding) - 1)));
openIntention = listItem.swipe.position < lastPosition;
}
lastPosition = listItem.swipe.position;
}
onReleased: mouse => {
if (listItem.mirrored) {
if (openIntention) {
slideAnim.to = openPosition
} else {
slideAnim.to = 0
}
} else {
if (openIntention) {
slideAnim.to = -openPosition
} else {
slideAnim.to = 0
}
}
slideAnim.restart();
}
Kirigami.Icon {
id: handleIcon
anchors.fill: parent
selected: listItem.checked || (listItem.down && !listItem.checked && !listItem.sectionDelegate)
source: (listItem.mirrored ? (listItem.background.x < listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left") : (listItem.background.x < -listItem.background.width/2 ? "overflow-menu-right" : "overflow-menu-left"))
}
Connections {
id: swipeFilterConnection
target: internal.edgeEnabled ? internal.swipeFilterItem : null
function onPeekChanged() {
if (!listItem.actionsVisible) {
return;
}
if (listItem.mirrored) {
listItem.swipe.position = Math.max(0, Math.min(dragButton.openPosition, internal.swipeFilterItem.peek));
dragButton.openIntention = listItem.swipe.position > dragButton.lastPosition;
} else {
listItem.swipe.position = Math.min(0, Math.max(-dragButton.openPosition, -internal.swipeFilterItem.peek));
dragButton.openIntention = listItem.swipe.position < dragButton.lastPosition;
}
dragButton.lastPosition = listItem.swipe.position;
}
function onPressed(mouse) {
if (internal.edgeEnabled) {
dragButton.pressed(mouse);
}
}
function onClicked(mouse) {
if (Math.abs(listItem.background.x) < Kirigami.Units.gridUnit && internal.edgeEnabled) {
dragButton.clicked(mouse);
}
}
function onReleased(mouse) {
if (internal.edgeEnabled) {
dragButton.released(mouse);
}
}
function onCurrentItemChanged() {
if (!internal.edgeEnabled) {
slideAnim.to = 0;
slideAnim.restart();
}
}
}
}
}
// TODO: expose in API?
Component {
id: actionsBackgroundDelegate
Item {
anchors.fill: parent
z: 1
readonly property Item contentItem: swipeBackground
Rectangle {
id: swipeBackground
anchors {
top: parent.top
bottom: parent.bottom
}
clip: true
color: parent.pressed ? Qt.darker(Kirigami.Theme.backgroundColor, 1.1) : Qt.darker(Kirigami.Theme.backgroundColor, 1.05)
x: listItem.mirrored ? listItem.background.x - width : (listItem.background.x + listItem.background.width)
width: listItem.mirrored ? parent.width - (parent.width - x) : parent.width - x
TapHandler {
onTapped: listItem.swipe.close()
}
EdgeShadow {
edge: Qt.TopEdge
visible: background.x != 0
anchors {
right: parent.right
left: parent.left
top: parent.top
}
}
EdgeShadow {
edge: listItem.mirrored ? Qt.RightEdge : Qt.LeftEdge
visible: background.x != 0
anchors {
top: parent.top
bottom: parent.bottom
}
}
}
visible: listItem.swipe.position != 0
}
}
RowLayout {
id: actionsLayout
LayoutMirroring.enabled: listItem.mirrored
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
rightMargin: Kirigami.Units.smallSpacing
}
visible: parent !== listItem
parent: !listItem.alwaysVisibleActions && Kirigami.Settings.tabletMode
? listItem.swipe.leftItem?.contentItem || listItem.swipe.rightItem?.contentItem || listItem
: overlayLoader
property bool hasVisibleActions: false
function updateVisibleActions(definitelyVisible: bool) {
hasVisibleActions = definitelyVisible || listItem.actions.some(isActionVisible);
}
function isActionVisible(action: T.Action): bool {
return (action instanceof Kirigami.Action) ? action.visible : true;
}
Repeater {
model: listItem.actions
delegate: QQC2.ToolButton {
required property T.Action modelData
action: modelData
display: T.AbstractButton.IconOnly
visible: actionsLayout.isActionVisible(action)
onVisibleChanged: actionsLayout.updateVisibleActions(visible);
Component.onCompleted: actionsLayout.updateVisibleActions(visible);
Component.onDestruction: actionsLayout.updateVisibleActions(visible);
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: (Kirigami.Settings.tabletMode ? pressed : hovered) && QQC2.ToolTip.text.length > 0
QQC2.ToolTip.text: (action as Kirigami.Action)?.tooltip ?? action?.text ?? ""
onClicked: {
slideAnim.to = 0;
slideAnim.restart();
}
Accessible.name: text
Accessible.description: (action as Kirigami.Action)?.tooltip ?? ""
}
}
}
swipe {
enabled: false
right: listItem.alwaysVisibleActions || listItem.mirrored || !Kirigami.Settings.tabletMode ? null : actionsBackgroundDelegate
left: listItem.alwaysVisibleActions || listItem.mirrored && Kirigami.Settings.tabletMode ? actionsBackgroundDelegate : null
}
NumberAnimation {
id: slideAnim
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
target: listItem.swipe
property: "position"
from: listItem.swipe.position
}
//END items
}
@@ -0,0 +1,63 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigami.private as KirigamiPrivate
import QtQuick.Controls as QQC2
/**
* @brief A link button that contains a URL.
*
* It will open the url by default, allow to copy it if triggered with the
* secondary mouse button.
*
* @since 5.63
* @since org.kde.kirigami 2.6
* @inherit QtQuick.LinkButton
*/
Kirigami.LinkButton {
id: button
property string url
text: url
enabled: url.length > 0
visible: text.length > 0
acceptedButtons: Qt.LeftButton | Qt.RightButton
Accessible.name: text
Accessible.description: text !== url
? qsTr("Open link %1", "@info:whatsthis").arg(url)
: qsTr("Open link", "@info:whatsthis")
onPressed: mouse => {
if (mouse.button === Qt.RightButton) {
menu.popup();
}
}
onClicked: mouse => {
if (mouse.button !== Qt.RightButton) {
Qt.openUrlExternally(url);
}
}
QQC2.ToolTip {
// If button's text has been overridden, show a tooltip to expose the raw URL
visible: button.text !== button.url && button.mouseArea.containsMouse
text: button.url
}
QQC2.Menu {
id: menu
QQC2.MenuItem {
text: qsTr("Copy Link to Clipboard")
icon.name: "edit-copy"
onClicked: KirigamiPrivate.CopyHelperPrivate.copyTextToClipboard(button.url)
}
}
}
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQml
import QtQuick
QtObject {
property string name
property string source
property int width
property int height
property color color: Qt.rgba(0, 0, 0, 0)
}
@@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.MenuItem {
visible: !(action instanceof Kirigami.Action) || action.visible
autoExclusive: action instanceof Kirigami.Action && action.autoExclusive
height: visible ? implicitHeight : 0
QQC2.ToolTip.text: (action instanceof Kirigami.Action) ? action.tooltip : ""
QQC2.ToolTip.visible: hovered && QQC2.ToolTip.text.length > 0
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
Accessible.onPressAction: action.trigger()
}
@@ -0,0 +1,76 @@
/*
* SPDX-FileCopyrightText: 2018 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.Menu {
id: root
property alias actions: actionsInstantiator.model
property Component submenuComponent
property Component itemDelegate: ActionMenuItem {}
property Component separatorDelegate: QQC2.MenuSeparator {
property T.Action action
}
property Component loaderDelegate: Loader {
property T.Action action
}
property T.Action parentAction
property T.MenuItem parentItem
Instantiator {
id: actionsInstantiator
active: root.visible
delegate: QtObject {
readonly property T.Action action: modelData
property QtObject item: null
property bool isSubMenu: false
Component.onCompleted: {
const isKirigamiAction = action instanceof Kirigami.Action;
if (!isKirigamiAction || action.children.length === 0) {
if (isKirigamiAction && action.separator) {
item = root.separatorDelegate.createObject(null, { action });
} else if (action.displayComponent) {
item = root.loaderDelegate.createObject(null, {
action,
sourceComponent: action.displayComponent,
});
} else {
item = root.itemDelegate.createObject(null, { action });
}
root.addItem(item)
} else if (root.submenuComponent) {
item = root.submenuComponent.createObject(null, {
parentAction: action,
title: action.text,
actions: action.children,
});
root.insertMenu(root.count, item);
item.parentItem = root.contentData[root.contentData.length - 1];
item.parentItem.icon = action.icon;
isSubMenu = true;
}
}
Component.onDestruction: {
if (isSubMenu) {
root.removeMenu(item);
} else {
root.removeItem(item);
}
item.destroy();
}
}
}
}
@@ -0,0 +1,224 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* This Component is used as the header of GlobalDrawer and as the header
* of Card, It can be accessed there as a grouped property but can never
* be instantiated directly.
* \private
*/
Kirigami.ShadowedImage {
id: root
//BEGIN properties
/**
* @brief This property holds an icon to be displayed alongside the title.
*
* It can be a QIcon, a FreeDesktop-compatible icon name, or any URL understood by QtQuick.Image.
*
* @property var titleIcon
*/
property alias titleIcon: headingIcon.source
/**
* @brief This property holds the title's text which is to be displayed on top.
* of the image.
* @see QtQuick.Text::text
* @property string title
*/
property alias title: heading.text
/**
* @brief This property holds the title's position.
*
* default: ``Qt.AlignTop | Qt.AlignLeft``
*
* @property Qt::Alignment titleAlignment
*/
property int titleAlignment: Qt.AlignTop | Qt.AlignLeft
/**
* @brief This property holds the title's level.
*
* Available text size values range from 1 (largest) to 5 (smallest).
*
* default: ``1``
*
* @see org::kde::kirigami::Heading::level
* @property int titleLevel
*/
property alias titleLevel: heading.level
/**
* @brief This property holds the title's wrap mode.
*
* default: ``Text.NoWrap``
*
* @see QtQuick.Text::wrapMode
* @property int titleWrapMode
*/
property alias titleWrapMode: heading.wrapMode
/**
* @brief This property holds whether the title is part of an item considered
* checkable.
*
* If true, a checkbox will appear in the top-right corner of the title area.
*
* default: false
*
* @property bool checkable
*/
property bool checkable: false
/**
* @brief This property holds whether the title's checkbox is currently checked.
*
* If using this outside of a GlobalDrawer or a Card, you must manually bind
* this to the checked condition of the parent item, or whatever else in your
* UI signals checkability. You must also handle the `toggled` signal when
* the user manually clicks the checkbox.
*
* default: false
*
* @property bool checked
*/
property bool checked: false
property int leftPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int topPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int rightPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int bottomPadding: headingIcon.valid ? Kirigami.Units.smallSpacing * 2 : Kirigami.Units.largeSpacing
property int implicitWidth: Layout.preferredWidth
readonly property bool empty: title.length === 0 && // string
source.toString().length === 0 && // QUrl
!titleIcon // QVariant hanled by Kirigami.Icon
//END properties
signal toggled(bool checked)
Layout.fillWidth: true
Layout.preferredWidth: titleLayout.implicitWidth || sourceSize.width
Layout.preferredHeight: titleLayout.completed && source.toString().length > 0 ? width/(sourceSize.width / sourceSize.height) : Layout.minimumHeight
Layout.minimumHeight: titleLayout.implicitHeight > 0 ? titleLayout.implicitHeight + Kirigami.Units.smallSpacing * 2 : 0
onTitleAlignmentChanged: {
// VERTICAL ALIGNMENT
titleLayout.anchors.top = undefined
titleLayout.anchors.verticalCenter = undefined
titleLayout.anchors.bottom = undefined
shadowedRectangle.anchors.top = undefined
shadowedRectangle.anchors.verticalCenter = undefined
shadowedRectangle.anchors.bottom = undefined
if (root.titleAlignment & Qt.AlignTop) {
titleLayout.anchors.top = root.top
shadowedRectangle.anchors.top = root.top
}
else if (root.titleAlignment & Qt.AlignVCenter) {
titleLayout.anchors.verticalCenter = root.verticalCenter
shadowedRectangle.anchors.verticalCenter = root.verticalCenter
}
else if (root.titleAlignment & Qt.AlignBottom) {
titleLayout.anchors.bottom = root.bottom
shadowedRectangle.anchors.bottom = root.bottom
}
// HORIZONTAL ALIGNMENT
titleLayout.anchors.left = undefined
titleLayout.anchors.horizontalCenter = undefined
titleLayout.anchors.right = undefined
if (root.titleAlignment & Qt.AlignRight) {
titleLayout.anchors.right = root.right
}
else if (root.titleAlignment & Qt.AlignHCenter) {
titleLayout.anchors.horizontalCenter = root.horizontalCenter
}
else if (root.titleAlignment & Qt.AlignLeft) {
titleLayout.anchors.left = root.left
}
}
fillMode: Image.PreserveAspectCrop
asynchronous: true
color: "transparent"
Component.onCompleted: {
titleLayout.completed = true;
}
Kirigami.ShadowedRectangle {
id: shadowedRectangle
anchors {
left: parent.left
right: parent.right
}
height: Math.min(parent.height, titleLayout.height * 1.5)
opacity: 0.5
color: "black"
corners.topLeftRadius: root.titleAlignment & Qt.AlignTop ? root.corners.topLeftRadius : 0
corners.topRightRadius: root.titleAlignment & Qt.AlignTop ? root.corners.topRightRadius : 0
corners.bottomLeftRadius: root.titleAlignment & Qt.AlignBottom ? root.corners.bottomLeftRadius : 0
corners.bottomRightRadius: root.titleAlignment & Qt.AlignBottom ? root.corners.bottomRightRadius : 0
visible: root.source.toString().length !== 0 && root.title.length !== 0 && ((root.titleAlignment & Qt.AlignTop) || (root.titleAlignment & Qt.AlignVCenter) || (root.titleAlignment & Qt.AlignBottom))
}
RowLayout {
id: titleLayout
property bool completed: false
anchors {
leftMargin: root.leftPadding
topMargin: root.topPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
width: Math.min(implicitWidth, parent.width -root.leftPadding -root.rightPadding - (checkboxLoader.active ? Kirigami.Units.largeSpacing : 0))
height: Math.min(implicitHeight, parent.height -root.topPadding -root.bottomPadding)
Kirigami.Icon {
id: headingIcon
Layout.minimumWidth: Kirigami.Units.iconSizes.large
Layout.minimumHeight: width
visible: valid
isMask: false
}
Kirigami.Heading {
id: heading
Layout.fillWidth: true
Layout.fillHeight: true
verticalAlignment: Text.AlignVCenter
visible: text.length > 0
level: 1
color: root.source.toString().length > 0 ? "white" : Kirigami.Theme.textColor
wrapMode: Text.NoWrap
elide: Text.ElideRight
}
}
Loader {
id: checkboxLoader
anchors {
top: parent.top
right: parent.right
topMargin: root.topPadding
}
active: root.checkable
sourceComponent: QQC2.CheckBox {
checked: root.checked
onToggled: root.toggled(checked);
}
}
}
@@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
QQC2.ItemDelegate {
id: listItem
required property T.Action tAction
readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
readonly property bool isSeparator: kAction?.separator ?? false
readonly property bool isExpandable: kAction?.expandible ?? false
checked: tAction.checked || (actionsMenu && actionsMenu.visible)
highlighted: checked
icon.name: tAction.icon.name
text: tAction.text ? tAction.text : tAction.tooltip
hoverEnabled: (!isExpandable || root.collapsed) && !Kirigami.Settings.tabletMode && !isSeparator
font.pointSize: Kirigami.Theme.defaultFont.pointSize * (isExpandable ? 1.30 : 1)
enabled: !isExpandable && tAction.enabled
visible: kAction?.visible ?? true
opacity: enabled || isExpandable ? 1.0 : 0.6
Accessible.onPressAction: listItem.clicked()
Kirigami.Separator {
id: separatorAction
visible: listItem.isSeparator
Layout.fillWidth: true
}
ActionsMenu {
id: actionsMenu
y: Kirigami.Settings.isMobile ? -height : listItem.height
actions: kAction?.children ?? []
submenuComponent: ActionsMenu {}
}
Loader {
Layout.fillWidth: true
Layout.fillHeight: true
sourceComponent: kAction?.displayComponent ?? null
onStatusChanged: {
for (const child of parent.children) {
if (child === this) {
child.visible = status === Loader.Ready;
break;
} else {
child.visible = status !== Loader.Ready;
}
}
}
Component.onCompleted: statusChanged()
}
onPressed: {
if (kAction && kAction.children.length > 0) {
actionsMenu.open();
}
}
onClicked: {
if (!kAction || kAction.children.length === 0) {
root.drawerOpen = false;
}
tAction?.trigger();
}
}
@@ -0,0 +1,106 @@
/*
* SPDX-FileCopyrightText: 2019 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
/**
* @brief This is the default background for Cards.
*
* It provides background feedback on hover and click events, border customizability, and the ability to change the radius of each individual corner.
*
* @inherit org::kde::kirigami::ShadowedRectangle
*/
Kirigami.ShadowedRectangle {
id: root
//BEGIN properties
/**
* @brief This property sets whether there should be a background change on a click event.
*
* default: ``false``
*/
property bool clickFeedback: false
/**
* @brief This property sets whether there should be a background change on a click event.
*
* default: ``false``
*/
property bool hoverFeedback: false
/**
* @brief This property holds the card's normal background color.
*
* default: ``Kirigami.Theme.backgroundColor``
*/
property color defaultColor: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the color displayed when a click event is triggered.
* @see DefaultCardBackground::clickFeedback
*/
property color pressedColor: Kirigami.ColorUtils.tintWithAlpha(
defaultColor,
Kirigami.Theme.highlightColor, 0.3)
/**
* @brief This property holds the color displayed when a hover event is triggered.
* @see DefaultCardBackground::hoverFeedback
*/
property color hoverColor: Kirigami.ColorUtils.tintWithAlpha(
defaultColor,
Kirigami.Theme.highlightColor, 0.1)
/**
* @brief This property holds the border width which is displayed at the edge of DefaultCardBackground.
*
* default: ``1``
*/
property int borderWidth: 1
/**
* @brief This property holds the border color which is displayed at the edge of DefaultCardBackground.
*/
property color borderColor: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
//END properties
color: {
if (root.parent.checked || (root.clickFeedback && (root.parent.down || root.parent.highlighted)))
return root.pressedColor
else if (root.hoverFeedback && root.parent.hovered)
return root.hoverColor
return root.defaultColor
}
radius: Kirigami.Units.cornerRadius
border {
width: root.borderWidth
color: root.borderColor
}
shadow {
size: Kirigami.Units.gridUnit
color: Qt.rgba(0, 0, 0, 0.05)
yOffset: 2
}
// basic drop shadow
Rectangle {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Math.round(Kirigami.Units.smallSpacing / 4)
radius: root.radius
height: root.height
color: Qt.darker(Qt.rgba(Kirigami.Theme.backgroundColor.r, Kirigami.Theme.backgroundColor.g, Kirigami.Theme.backgroundColor.b, 0.6), 1.1)
visible: !root.clickFeedback || !root.parent.down
z: -1
}
}
@@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import org.kde.kirigami as Kirigami
Rectangle {
/**
* @brief This property holds the chip's default background color.
*/
property color defaultColor: Kirigami.Theme.backgroundColor
/**
* @brief This property holds the color of the Chip's background when it is being pressed.
* @see QtQuick.AbstractButton::down
*/
property color pressedColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.3)
/**
* @brief This property holds the color of the Chip's background when it is checked.
* @see QtQuick.AbstractButton::checked
*/
property color checkedColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.2)
/**
* @brief This property holds the chip's default border color.
*/
property color defaultBorderColor: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
/**
* @brief This property holds the color of the Chip's border when it is checked.
* @see QtQuick.AbstractButton::checked
*/
property color checkedBorderColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.9)
/**
* @brief This property holds the color of the Chip's border when it is being pressed.
* @see QtQuick.AbstractButton::down
*/
property color pressedBorderColor: Qt.rgba(Kirigami.Theme.highlightColor.r, Kirigami.Theme.highlightColor.g, Kirigami.Theme.highlightColor.b, 0.7)
/**
* @brief This property holds the color of the Chip's border when it is hovered.
* @see QtQuick.Control::hovered
*/
property color hoveredBorderColor: Kirigami.Theme.hoverColor
Kirigami.Theme.colorSet: Kirigami.Theme.Header
Kirigami.Theme.inherit: false
color: {
if (parent.down) {
return pressedColor
} else if (parent.checked) {
return checkedColor
} else {
return defaultColor
}
}
border.color: {
if (parent.down) {
return pressedBorderColor
} else if (parent.checked) {
return checkedBorderColor
} else if (parent.hovered) {
return hoveredBorderColor
} else {
return defaultBorderColor
}
}
border.width: 1
radius: Kirigami.Units.cornerRadius
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* This component is used as a default representation for a page title within
* page's header/toolbar. It is just a Heading item with shrinking + eliding
* behavior.
*
* \private
*/
Item {
property alias text: heading.text
Layout.fillWidth: true
Layout.minimumWidth: 0
Layout.maximumWidth: implicitWidth
implicitWidth: Math.ceil(heading.implicitWidth)
implicitHeight: Math.ceil(heading.implicitHeight)
Kirigami.Heading {
id: heading
anchors.fill: parent
maximumLineCount: 1
elide: Text.ElideRight
textFormat: Text.PlainText
}
}
@@ -0,0 +1,62 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
id: shadow
/**
* @brief This property holds the edge of the shadow that will determine the direction of the gradient.
* The acceptable values are:
* * ``Qt.TopEdge``: the top edge of the content item.
* * ``Qt.LeftEdge``: the left edge of the content item
* * ``Qt.RightEdge``: the right edge of the content item.
* * ``Qt.BottomEdge``: the bottom edge of the content item.
*
* @see Qt::Edges
*/
property int edge: Qt.LeftEdge
property int radius: Kirigami.Units.cornerRadius
implicitWidth: radius
implicitHeight: radius
Rectangle {
x: shadow.width / 2 - width / 2
y: shadow.height / 2 - height / 2
width: (shadow.edge === Qt.LeftEdge || shadow.edge === Qt.RightEdge) ? shadow.height : shadow.width
height: (shadow.edge === Qt.LeftEdge || shadow.edge === Qt.RightEdge) ? shadow.width : shadow.height
rotation: {
switch (shadow.edge) {
case Qt.TopEdge: return 0;
case Qt.LeftEdge: return 270;
case Qt.RightEdge: return 90;
case Qt.BottomEdge: return 180;
}
}
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(0, 0, 0, 0.25)
}
GradientStop {
position: 0.20
color: Qt.rgba(0, 0, 0, 0.1)
}
GradientStop {
position: 0.35
color: Qt.rgba(0, 0, 0, 0.02)
}
GradientStop {
position: 1.0
color: "transparent"
}
}
}
}
@@ -0,0 +1,206 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Controls.impl as QQC2Impl
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.ItemDelegate {
id: listItem
required property T.Action tAction
// `as` case operator is still buggy
readonly property Kirigami.Action kAction: tAction instanceof Kirigami.Action ? tAction : null
readonly property bool actionVisible: kAction?.visible ?? true
readonly property bool isSeparator: kAction?.separator ?? false
readonly property bool isExpandable: kAction?.expandible ?? false
readonly property bool hasChildren: kAction ? kAction.children.length > 0 : false
readonly property bool hasVisibleMenu: actionsMenu?.visible ?? false
readonly property bool hasToolTip: kAction ? kAction.tooltip !== "" : false
checked: checkedBinding()
highlighted: checked
activeFocusOnTab: true
width: parent.width
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
id: iconItem
color: listItem.tAction.icon.color
source: listItem.tAction.icon.name || listItem.tAction.icon.source
readonly property int size: Kirigami.Units.iconSizes.smallMedium
Layout.minimumHeight: size
Layout.maximumHeight: size
Layout.minimumWidth: size
Layout.maximumWidth: size
selected: (listItem.highlighted || listItem.checked || listItem.down)
visible: source !== undefined && !listItem.isSeparator
}
QQC2Impl.MnemonicLabel {
id: labelItem
visible: !listItem.isSeparator
text: width > height * 2 ? listItem.Kirigami.MnemonicData.mnemonicLabel : ""
// Work around Qt bug where left aligned text is not right aligned
// in RTL mode unless horizontalAlignment is explicitly set.
// https://bugreports.qt.io/browse/QTBUG-95873
horizontalAlignment: Text.AlignLeft
Layout.fillWidth: true
mnemonicVisible: listItem.Kirigami.MnemonicData.active
color: (listItem.highlighted || listItem.checked || listItem.down) ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
elide: Text.ElideRight
font: listItem.font
opacity: {
if (root.collapsed) {
return 0;
} else if (!listItem.enabled) {
return 0.6;
} else {
return 1.0;
}
}
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration/2
easing.type: Easing.InOutQuad
}
}
}
Kirigami.Separator {
id: separatorAction
visible: listItem.isSeparator
Layout.fillWidth: true
}
Kirigami.Icon {
isMask: true
Layout.alignment: Qt.AlignVCenter
Layout.leftMargin: !root.collapsed ? 0 : -width
Layout.preferredHeight: !root.collapsed ? Kirigami.Units.iconSizes.small : Kirigami.Units.iconSizes.small/2
opacity: 0.7
selected: listItem.checked || listItem.down
Layout.preferredWidth: Layout.preferredHeight
source: listItem.mirrored ? "go-next-symbolic-rtl" : "go-next-symbolic"
visible: (!listItem.isExpandable || root.collapsed) && !listItem.isSeparator && listItem.hasChildren
}
}
Accessible.name: listItem.tAction?.text ?? ""
Kirigami.MnemonicData.enabled: enabled && visible
Kirigami.MnemonicData.controlType: Kirigami.MnemonicData.MenuItem
Kirigami.MnemonicData.label: tAction?.text ?? ""
Shortcut {
sequence: listItem.Kirigami.MnemonicData.sequence
onActivated: listItem.clicked()
}
property ActionsMenu actionsMenu: ActionsMenu {
x: Qt.application.layoutDirection === Qt.RightToLeft ? -width : listItem.width
actions: listItem.kAction?.children ?? []
submenuComponent: ActionsMenu {}
onVisibleChanged: {
if (visible) {
stackView.openSubMenu = listItem.actionsMenu;
} else if (stackView.openSubMenu === listItem.actionsMenu) {
stackView.openSubMenu = null;
}
}
}
// TODO: animate the hide by collapse
visible: actionVisible && opacity > 0
opacity: !root.collapsed || iconItem.source.toString().length > 0
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration / 2
easing.type: Easing.InOutQuad
}
}
enabled: tAction?.enabled ?? false
hoverEnabled: (!isExpandable || root.collapsed) && !Kirigami.Settings.tabletMode && !isSeparator
font.pointSize: isExpandable ? Kirigami.Theme.defaultFont.pointSize * 1.30 : Kirigami.Theme.defaultFont.pointSize
height: implicitHeight * opacity
QQC2.ToolTip {
visible: !listItem.isSeparator
&& (listItem.hasToolTip || root.collapsed)
&& !listItem.hasVisibleMenu
&& listItem.hovered
&& text.length > 0
text: (listItem.kAction?.tooltip || listItem.tAction?.text) ?? ""
delay: Kirigami.Units.toolTipDelay
y: (listItem.height - height) / 2
x: Qt.application.layoutDirection === Qt.RightToLeft ? -width : listItem.width
}
onHoveredChanged: {
if (!hovered) {
return;
}
if (stackView.openSubMenu) {
stackView.openSubMenu.visible = false;
if (actionsMenu.count > 0) {
actionsMenu.popup(this, width, 0);
}
}
}
onClicked: trigger()
Accessible.onPressAction: trigger()
Keys.onEnterPressed: event => trigger()
Keys.onReturnPressed: event => trigger()
function trigger() {
tAction?.trigger();
if (hasChildren) {
if (root.collapsed) {
if (actionsMenu.count > 0 && !actionsMenu.visible) {
stackView.openSubMenu = actionsMenu;
actionsMenu.popup(this, width, 0);
}
} else {
stackView.push(menuComponent, {
model: kAction?.children ?? [],
level: level + 1,
current: tAction,
});
}
} else if (root.resetMenuOnTriggered) {
root.resetMenu();
}
checked = Qt.binding(() => checkedBinding());
}
function checkedBinding(): bool {
return (tAction?.checked || actionsMenu?.visible) ?? false;
}
Keys.onDownPressed: event => nextItemInFocusChain().focus = true
Keys.onUpPressed: event => nextItemInFocusChain(false).focus = true
}
@@ -0,0 +1,73 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
Kirigami.Dialog {
id: dialog
clip: true
modal: true
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
header: Kirigami.AbstractApplicationHeader {
pageRow: null
page: null
minimumHeight: Kirigami.Units.gridUnit * 1.6
maximumHeight: Kirigami.Units.gridUnit * 1.6
preferredHeight: Kirigami.Units.gridUnit * 1.6
Keys.onEscapePressed: event => {
if (dialog.opened) {
dialog.close();
} else {
event.accepted = false;
}
}
contentItem: RowLayout {
width: parent.width
Kirigami.Heading {
Layout.leftMargin: Kirigami.Units.largeSpacing
text: dialog.title
elide: Text.ElideRight
}
Item {
Layout.fillWidth: true
}
Kirigami.Icon {
id: closeIcon
Layout.alignment: Qt.AlignVCenter
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
source: closeMouseArea.containsMouse ? "window-close" : "window-close-symbolic"
active: closeMouseArea.containsMouse
MouseArea {
id: closeMouseArea
hoverEnabled: true
anchors.fill: parent
onClicked: mouse => dialog.close();
}
}
}
}
contentItem: QQC2.Control {
topPadding: 0
leftPadding: 0
rightPadding: 0
bottomPadding: 0
}
}
@@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
QQC2.ToolButton {
id: control
signal menuAboutToShow()
hoverEnabled: true
display: QQC2.ToolButton.TextBesideIcon
property bool showMenuArrow: !Kirigami.DisplayHint.displayHintSet(action, Kirigami.DisplayHint.HideChildIndicator)
property list<T.Action> menuActions: {
if (action instanceof Kirigami.Action) {
return action.children;
}
return []
}
property Component menuComponent: ActionsMenu {
submenuComponent: ActionsMenu { }
}
property T.Menu menu: null
// We create the menu instance only when there are any actual menu items.
// This also happens in the background, avoiding slowdowns due to menu item
// creation on the main thread.
onMenuActionsChanged: {
if (menuComponent && menuActions.length > 0) {
if (!menu) {
const setupIncubatedMenu = incubatedMenu => {
menu = incubatedMenu
// Important: We handle the press on parent in the parent, so ignore it here.
menu.closePolicy = QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
menu.closed.connect(() => control.checked = false)
menu.actions = control.menuActions
}
const incubator = menuComponent.incubateObject(control, { actions: menuActions })
if (incubator.status !== Component.Ready) {
incubator.onStatusChanged = status => {
if (status === Component.Ready) {
setupIncubatedMenu(incubator.object)
}
}
} else {
setupIncubatedMenu(incubator.object);
}
} else {
menu.actions = menuActions
}
}
}
visible: action instanceof Kirigami.Action ? action.visible : true
autoExclusive: action instanceof Kirigami.Action ? action.autoExclusive : false
// Workaround for QTBUG-85941
Binding {
target: control
property: "checkable"
value: (control.action?.checkable ?? false) || (control.menuActions.length > 0)
restoreMode: Binding.RestoreBinding
}
// Important: This cannot be a direct onVisibleChanged handler in the button
// because it breaks action assignment if we do that. However, this slightly
// more indirect Connections object does not have that effect.
Connections {
target: control
function onVisibleChanged() {
if (!control.visible && control.menu && control.menu.visible) {
control.menu.dismiss()
}
}
}
onToggled: {
if (menuActions.length > 0 && menu) {
if (checked) {
control.menuAboutToShow();
menu.popup(control, 0, control.height)
} else {
menu.dismiss()
}
}
}
QQC2.ToolTip {
visible: control.hovered && text.length > 0 && !(control.menu && control.menu.visible) && !control.pressed
text: {
const a = control.action;
if (a) {
if (a.tooltip) {
return a.tooltip;
} else if (control.display === QQC2.Button.IconOnly) {
return a.text;
}
}
return "";
}
}
// This will set showMenuArrow when using qqc2-desktop-style.
Accessible.role: (control.showMenuArrow && control.menuActions.length > 0) ? Accessible.ButtonMenu : Accessible.Button
Accessible.ignored: !visible
Accessible.onPressAction: {
if (control.checkable) {
control.toggle();
} else {
control.action.trigger();
}
}
}
@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2023 Connor Carney <hello@connorcarney.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import QtQuick.Shapes as QQShapes
/**
* @brief A pull-down to refresh indicator that can be added to any Flickable or ScrollablePage.
*/
Item {
id: root
//BEGIN properties
/**
* @brief The flickable that this indicator is attached to.
*
* If this is not set, the indicator will search for a Flickable in its parent chain.
*/
property Flickable flickable: {
let candidate = parent
while (candidate) {
if (candidate instanceof Flickable) {
return candidate
} else if (candidate instanceof Kirigami.ScrollablePage) {
return candidate.flickable
}
candidate = candidate.parent
}
return null;
}
/**
* @brief Whether to show the busy indicator at the top of the flickable
*
* This should be set to true whenever a refresh is in progress. It should typically
* be set to true whe triggered() is emitted, and set to false when the refresh is
* complete. This is not done automatically because the refresh may be triggered
* from outside the indicator.
*/
property bool active: false
/**
* @brief How far the flickable has been pulled down, between 0 (not at all) and 1 (where a refresh is triggered).
*/
readonly property real progress: !refreshing ? Math.min(-Math.min(flickable?.verticalOvershoot ?? 0, 0) / indicatorContainer.height, 1) : 0
/**
* @brief Time to wait after the flickable has been pulled down before triggering a refresh
*
* This gives the user a chance to back out of the refresh if they release the flickable
* before the refreshDelay has elapsed.
*/
property int refreshDelay: 500
/**
* @brief emitted when the flickable is pulled down far enough to trigger a refresh
*/
signal triggered()
//END properties
Item {
id: indicatorContainer
parent: root.flickable
anchors {
bottom: parent?.contentItem?.top
bottomMargin: root.flickable.topMargin
}
width: flickable?.width
height: Kirigami.Units.gridUnit * 4
QQC2.BusyIndicator {
id: busyIndicator
z: 1
anchors.centerIn: parent
running: root.active
visible: root.active
// Android busywidget QQC seems to be broken at custom sizes
}
QQShapes.Shape {
id: spinnerProgress
anchors {
fill: busyIndicator
margins: Kirigami.Units.smallSpacing
}
visible: !root.active && root.progress > 0
QQShapes.ShapePath {
strokeWidth: Kirigami.Units.smallSpacing
strokeColor: Kirigami.Theme.highlightColor
fillColor: "transparent"
PathAngleArc {
centerX: spinnerProgress.width / 2
centerY: spinnerProgress.height / 2
radiusX: spinnerProgress.width / 2 - Kirigami.Units.smallSpacing / 2
radiusY: spinnerProgress.height / 2 - Kirigami.Units.smallSpacing / 2
startAngle: 0
sweepAngle: 360 * root.progress
}
}
}
}
onProgressChanged: {
if (!root.active && root.progress >= 1) {
refreshTriggerTimer.running = true;
} else {
refreshTriggerTimer.running = false;
}
}
states: [
State {
name: "active"
when: root.active
PropertyChanges {
target: indicatorContainer
anchors.bottomMargin: root.flickable.topMargin - indicatorContainer.height
}
PropertyChanges {
target: root.flickable
explicit: true
// this is not a loop because of explicit:true above
// It adds the height of the indicator to the topMargin of the flickable
// when we enter the active state; the change is automatically reversed
// when returning to the base state.
topMargin: indicatorContainer.height + root.flickable.topMargin
}
}
]
transitions: [
Transition {
from: ""
to: "active"
enabled: root.flickable.verticalOvershoot >= 0
reversible: true
NumberAnimation {
target: root.flickable
properties: "topMargin"
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
}
]
Timer {
id: refreshTriggerTimer
interval: root.refreshDelay
onTriggered: {
if (!root.active && root.progress >= 1) {
root.triggered()
}
}
}
}
@@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
MouseArea {
id: swipeFilter
anchors {
right: parent.right
top: parent.top
bottom: parent.bottom
}
z: 99999
property Item currentItem
property real peek
preventStealing: true
width: Kirigami.Units.gridUnit
onPressed: mouse => {
const mapped = mapToItem(parent.flickableItem.contentItem, mouse.x, mouse.y);
currentItem = parent.flickableItem.itemAt(mapped.x, mapped.y);
}
onPositionChanged: mouse => {
const mapped = mapToItem(parent.flickableItem.contentItem, mouse.x, mouse.y);
currentItem = parent.flickableItem.itemAt(mapped.x, mapped.y);
peek = 1 - mapped.x / parent.flickableItem.contentItem.width;
}
}
@@ -0,0 +1,55 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Kirigami.AbstractApplicationHeader {
id: root
// anchors.fill: parent
property Item container
property bool current
minimumHeight: pageRow ? pageRow.globalToolBar.minimumHeight : Kirigami.Units.iconSizes.medium + Kirigami.Units.smallSpacing * 2
maximumHeight: pageRow ? pageRow.globalToolBar.maximumHeight : minimumHeight
preferredHeight: pageRow ? pageRow.globalToolBar.preferredHeight : minimumHeight
separatorVisible: pageRow ? pageRow.globalToolBar.separatorVisible : true
Kirigami.Theme.colorSet: pageRow ? pageRow.globalToolBar.colorSet : Kirigami.Theme.Header
leftPadding: pageRow
? Math.min(
width / 2,
Math.max(
(page.title.length > 0 ? pageRow.globalToolBar.titleLeftPadding : 0),
Qt.application.layoutDirection === Qt.LeftToRight
? Math.min(pageRow.globalToolBar.leftReservedSpace,
pageRow.Kirigami.ScenePosition.x
- page.Kirigami.ScenePosition.x
+ pageRow.globalToolBar.leftReservedSpace)
+ Kirigami.Units.smallSpacing
: Math.min(pageRow.globalToolBar.leftReservedSpace,
-pageRow.width
+ pageRow.Kirigami.ScenePosition.x
+ page.Kirigami.ScenePosition.x
+ page.width
+ pageRow.globalToolBar.leftReservedSpace)
+ Kirigami.Units.smallSpacing))
: Kirigami.Units.smallSpacing
rightPadding: pageRow
? Math.max(0,
Qt.application.layoutDirection === Qt.LeftToRight
? (-pageRow.width
- pageRow.Kirigami.ScenePosition.x
+ page.width
+ page.Kirigami.ScenePosition.x
+ pageRow.globalToolBar.rightReservedSpace)
: (pageRow.Kirigami.ScenePosition.x
- page.Kirigami.ScenePosition.x
+ pageRow.globalToolBar.rightReservedSpace))
: 0
}
@@ -0,0 +1,174 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
* SPDX-FileCopyrightText: 2023 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
ListView {
id: root
readonly property Kirigami.PageRow pageRow: {
// This is fetched from breadcrumbLoader in PageRowGlobalToolBarUI.qml
const pr = parent?.pageRow ?? null;
return pr as Kirigami.PageRow;
}
currentIndex: {
if (!pageRow) {
return -1;
}
// This ListView is eventually consistent with PageRow, so it has to
// force-refresh currentIndex when its count finally catches up,
// otherwise currentIndex might get reset and stuck at -1.
void count;
// TODO: This "eventual consistency" causes Behavior on contentX to
// scroll from the start each time a page is added. Besides, simple
// number is not the most efficient model, because ListView
// recreates all delegates when number changes.
if (pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView.
// Since it is ignored, depth has to be adjusted by 1.
// In case of layers, current index is always the last one,
// which is one less than their count, thus minus another 1.
return pageRow.layers.depth - 2;
} else {
return pageRow.currentIndex;
}
}
// This function exists outside of delegate, so that when popping layers
// the JavaScript execution context won't be destroyed along with delegate.
function selectIndex(index: int) {
if (!pageRow) {
return;
}
if (pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView.
// Since it is ignored, index has to be adjusted by 1.
// We want to pop anything after selected index,
// turning selected layer into current one, thus plus another 1.
while (pageRow.layers.depth > index + 2) {
pageRow.layers.pop();
}
} else {
pageRow.currentIndex = index;
}
}
contentHeight: height
clip: true
orientation: ListView.Horizontal
boundsBehavior: Flickable.StopAtBounds
interactive: Kirigami.Settings.hasTransientTouchInput
contentX: {
if (!currentItem) {
return 0;
}
// preferred position: current item is centered within viewport
const preferredPosition = currentItem.x + (currentItem.width - width) / 2;
// Note: Order of min/max is important. Make sure to test on all sorts
// and sizes before committing changes to this formula.
if (LayoutMirroring.enabled) {
// In a mirrored ListView contentX starts from left edge and increases to the left.
const maxLeftPosition = -contentWidth;
const minRightPosition = -width;
return Math.round(Math.min(minRightPosition, Math.max(preferredPosition, maxLeftPosition)));
} else {
const minLeftPosition = 0;
const maxRightPosition = contentWidth - width;
return Math.round(Math.max(minLeftPosition, Math.min(preferredPosition, maxRightPosition)));
}
}
Behavior on contentX {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
model: {
if (!root.pageRow) {
return null;
}
if (root.pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView; ignore it.
return root.pageRow.layers.depth - 1;
} else {
return root.pageRow.depth;
}
}
delegate: MouseArea {
id: delegate
required property int index
// We can't use Kirigami.Page here instead of Item since we now accept
// pushing PageRow to a new layer.
readonly property Item page: {
if (!root.pageRow) {
return null;
}
if (root.pageRow.layers.depth > 1) {
// First layer (index 0) is the main columnView.
// Since it is ignored, index has to be adjusted by 1.
return pageRow.layers.get(index + 1);
} else {
return pageRow.get(index);
}
}
width: Math.ceil(layout.implicitWidth)
height: ListView.view?.height ?? 0
hoverEnabled: !Kirigami.Settings.tabletMode
onClicked: mouse => {
root.selectIndex(index);
}
// background
Rectangle {
color: Kirigami.Theme.highlightColor
anchors.fill: parent
radius: Kirigami.Units.cornerRadius
opacity: root.count > 1 && parent.containsMouse ? 0.1 : 0
}
// content
RowLayout {
id: layout
anchors.fill: parent
spacing: 0
Kirigami.Icon {
visible: delegate.index > 0
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.preferredWidth: Kirigami.Units.iconSizes.small
isMask: true
color: Kirigami.Theme.textColor
source: LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic"
}
Kirigami.Heading {
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
color: Kirigami.Theme.textColor
verticalAlignment: Text.AlignVCenter
wrapMode: Text.NoWrap
text: delegate.page?.title ?? ""
opacity: delegate.ListView.isCurrentItem ? 1 : 0.4
}
}
}
}
@@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
QtObject {
id: globalToolBar
property int style: Kirigami.ApplicationHeaderStyle.None
readonly property int actualStyle: {
if (style === Kirigami.ApplicationHeaderStyle.Auto) {
if (!Kirigami.Settings.isMobile) {
return Kirigami.ApplicationHeaderStyle.ToolBar
} else if (root.wideMode) {
return Kirigami.ApplicationHeaderStyle.Titles
} else {
return Kirigami.ApplicationHeaderStyle.Breadcrumb
}
}
return style;
}
/** @property kirigami::ApplicationHeaderStyle::NavigationButtons */
property int showNavigationButtons: (!Kirigami.Settings.isMobile || Qt.platform.os === "ios")
? (Kirigami.ApplicationHeaderStyle.ShowBackButton | Kirigami.ApplicationHeaderStyle.ShowForwardButton)
: Kirigami.ApplicationHeaderStyle.NoNavigationButtons
property bool separatorVisible: true
//Unfortunately we can't access pageRow.globalToolbar.Kirigami.Theme directly in a declarative way
property int colorSet: Kirigami.Theme.Header
// whether or not the header should be
// "pushed" back when scrolling using the
// touch screen
property bool hideWhenTouchScrolling: false
/**
* If true, when any kind of toolbar is shown, the drawer handles will be shown inside the toolbar, if they're present
*/
property bool canContainHandles: true
property int toolbarActionAlignment: Qt.AlignRight
property int toolbarActionHeightMode: Kirigami.ToolBarLayout.ConstrainIfLarger
property int minimumHeight: 0
// FIXME: Figure out the exact standard size of a Toolbar
property int preferredHeight: (actualStyle === Kirigami.ApplicationHeaderStyle.ToolBar
? Kirigami.Units.iconSizes.medium
: Kirigami.Units.gridUnit * 1.8) + Kirigami.Units.smallSpacing * 2
property int maximumHeight: preferredHeight
// Sets the minimum leading padding for the title in a page header
property int titleLeftPadding: Kirigami.Units.gridUnit
}
@@ -0,0 +1,157 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import "../../templates" as KT
import "../../templates/private" as TP
import "../" as P
Kirigami.AbstractApplicationHeader {
id: header
readonly property int leftReservedSpace: (buttonsLayout.visible && buttonsLayout.visibleChildren.length > 0 ? buttonsLayout.width + Kirigami.Units.smallSpacing : 0) // Take into account the layout margins the nav buttons have
+ (leftHandleAnchor.visible ? leftHandleAnchor.width : 0)
+ (menuButton.visible ? menuButton.width : 0)
readonly property int rightReservedSpace: rightHandleAnchor.visible ? rightHandleAnchor.width + Kirigami.Units.smallSpacing : 0
readonly property alias leftHandleAnchor: leftHandleAnchor
readonly property alias rightHandleAnchor: rightHandleAnchor
readonly property bool breadcrumbVisible: layerIsMainRow && breadcrumbLoader.active
readonly property bool layerIsMainRow: (root.layers.currentItem.hasOwnProperty("columnView")) ? root.layers.currentItem.columnView === root.columnView : false
readonly property Item currentItem: layerIsMainRow ? root.columnView : root.layers.currentItem
function __shouldHandleAnchorBeVisible(handleAnchor: Item, drawerProperty: string, itemProperty: string): bool {
if (typeof applicationWindow === "undefined") {
return false;
}
const w = applicationWindow();
if (!w) {
return false;
}
const drawer = w[drawerProperty] as KT.OverlayDrawer;
if (!drawer || !drawer.enabled || !drawer.handleVisible || drawer.handle.handleAnchor !== handleAnchor) {
return false;
}
const item = breadcrumbLoader.pageRow?.[itemProperty] as Item;
const style = item?.globalToolBarStyle ?? Kirigami.ApplicationHeaderStyle.None;
return globalToolBar.canContainHandles || style === Kirigami.ApplicationHeaderStyle.ToolBar;
}
height: visible ? implicitHeight : 0
minimumHeight: globalToolBar.minimumHeight
preferredHeight: globalToolBar.preferredHeight
maximumHeight: globalToolBar.maximumHeight
separatorVisible: globalToolBar.separatorVisible
Kirigami.Theme.colorSet: globalToolBar.colorSet
RowLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.preferredWidth: applicationWindow().pageStack.globalToolBar.leftReservedSpace
visible: applicationWindow().pageStack !== root
}
Item {
id: leftHandleAnchor
visible: header.__shouldHandleAnchorBeVisible(leftHandleAnchor, "globalDrawer", "leadingVisibleItem")
Layout.preferredHeight: Math.max(backButton.implicitHeight, parent.height)
Layout.preferredWidth: height
}
P.PrivateActionToolButton {
id: menuButton
visible: !Kirigami.Settings.isMobile && applicationWindow().globalDrawer && "isMenu" in applicationWindow().globalDrawer && applicationWindow().globalDrawer.isMenu
icon.name: "open-menu-symbolic"
showMenuArrow: false
Layout.preferredHeight: Math.min(backButton.implicitHeight, parent.height)
Layout.preferredWidth: height
Layout.leftMargin: Kirigami.Units.smallSpacing
action: Kirigami.Action {
children: applicationWindow().globalDrawer && applicationWindow().globalDrawer.actions ? applicationWindow().globalDrawer.actions : []
tooltip: checked ? qsTr("Close menu") : qsTr("Open menu")
}
Accessible.name: action.tooltip
Connections {
// Only target the GlobalDrawer when it *is* a GlobalDrawer, since
// it can be something else, and that something else probably
// doesn't have an isMenuChanged() signal.
target: applicationWindow().globalDrawer as Kirigami.GlobalDrawer
function onIsMenuChanged() {
if (!applicationWindow().globalDrawer.isMenu && menuButton.menu) {
menuButton.menu.dismiss()
}
}
}
}
RowLayout {
id: buttonsLayout
Layout.fillHeight: true
Layout.preferredHeight: Math.max(backButton.visible ? backButton.implicitHeight : 0, forwardButton.visible ? forwardButton.implicitHeight : 0)
Layout.leftMargin: leftHandleAnchor.visible ? Kirigami.Units.smallSpacing : 0
visible: (globalToolBar.showNavigationButtons !== Kirigami.ApplicationHeaderStyle.NoNavigationButtons || applicationWindow().pageStack.layers.depth > 1 && !(applicationWindow().pageStack.layers.currentItem instanceof Kirigami.PageRow || header.layerIsMainRow))
&& globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.None
Layout.maximumWidth: visibleChildren.length > 0 ? Layout.preferredWidth : 0
TP.BackButton {
id: backButton
Layout.leftMargin: leftHandleAnchor.visible ? 0 : Kirigami.Units.smallSpacing
Layout.minimumWidth: implicitHeight
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: buttonsLayout.height
}
TP.ForwardButton {
id: forwardButton
Layout.minimumWidth: implicitHeight
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: buttonsLayout.height
}
}
QQC2.ToolSeparator {
visible: (menuButton.visible || (buttonsLayout.visible && buttonsLayout.visibleChildren.length > 0)) && breadcrumbVisible && pageRow.depth > 1
}
Loader {
id: breadcrumbLoader
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: -1
Layout.preferredHeight: -1
property Kirigami.PageRow pageRow: root
asynchronous: true
active: globalToolBar.actualStyle === Kirigami.ApplicationHeaderStyle.Breadcrumb
&& header.currentItem
&& header.currentItem.globalToolBarStyle !== Kirigami.ApplicationHeaderStyle.None
source: Qt.resolvedUrl("BreadcrumbControl.qml")
}
Item {
id: rightHandleAnchor
visible: header.__shouldHandleAnchorBeVisible(rightHandleAnchor, "contextDrawer", "trailingVisibleItem")
Layout.preferredHeight: Math.max(backButton.implicitHeight, parent.height)
Layout.preferredWidth: height
}
}
background.opacity: breadcrumbLoader.active ? 1 : 0
}
@@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
AbstractPageHeader {
id: root
Loader {
id: titleLoader
anchors {
verticalCenter: parent.verticalCenter
left: parent.left
right: parent.right
}
height: Math.min(root.height, item
? (item.Layout.preferredHeight > 0 ? item.Layout.preferredHeight : item.implicitHeight)
: 0)
// Don't load async to prevent jumpy behaviour on slower devices as it loads in.
// If the title delegate really needs to load async, it should be its responsibility to do it itself.
asynchronous: false
sourceComponent: page ? page.titleDelegate : null
}
}
@@ -0,0 +1,42 @@
/*
* SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ToolBar {
id: root
position: QQC2.ToolBar.Footer
NumberAnimation {
id: appearAnim
target: root
property: "height"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
Connections {
target: applicationWindow()
function onControlsVisibleChanged() {
if (applicationWindow().controlsVisible) {
appearAnim.from = 0;
appearAnim.to = root.implicitHeight;
} else {
appearAnim.from = root.implicitHeight;
appearAnim.to = 0;
}
appearAnim.restart();
}
}
contentItem: Kirigami.ActionToolBar {
display: QQC2.Button.TextUnderIcon
alignment: Qt.AlignCenter
actions: root.parent.page.actions
}
}
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
AbstractPageHeader {
id: root
implicitWidth: layout.implicitWidth + Kirigami.Units.smallSpacing * 2
implicitHeight: Math.max(titleLoader.implicitHeight, toolBar.implicitHeight) + Kirigami.Units.smallSpacing * 2
MouseArea {
anchors.fill: parent
onPressed: mouse => {
page.forceActiveFocus()
mouse.accepted = false
}
}
RowLayout {
id: layout
anchors.fill: parent
anchors.rightMargin: Kirigami.Units.smallSpacing
spacing: Kirigami.Units.smallSpacing
Loader {
id: titleLoader
Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter
Layout.fillWidth: item?.Layout.fillWidth ?? false
Layout.minimumWidth: item?.Layout.minimumWidth ?? -1
Layout.preferredWidth: item?.Layout.preferredWidth ?? -1
Layout.maximumWidth: item?.Layout.maximumWidth ?? -1
// Don't load async to prevent jumpy behaviour on slower devices as it loads in.
// If the title delegate really needs to load async, it should be its responsibility to do it itself.
asynchronous: false
sourceComponent: page?.titleDelegate ?? null
}
Kirigami.ActionToolBar {
id: toolBar
Layout.alignment: Qt.AlignVCenter
Layout.fillWidth: true
Layout.fillHeight: true
visible: actions.length > 0
alignment: pageRow?.globalToolBar.toolbarActionAlignment ?? Qt.AlignRight
heightMode: pageRow?.globalToolBar.toolbarActionHeightMode ?? Kirigami.ToolBarLayout.ConstrainIfLarger
actions: page?.actions ?? []
}
}
}
@@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
/**
* @brief An item that can be used as a title for the application.
*
* Scrolling the main page will make it taller or shorter (through the point of going away)
* It's a behavior similar to the typical mobile web browser addressbar
* the minimum, preferred and maximum heights of the item can be controlled with
* * minimumHeight: default is 0, i.e. hidden
* * preferredHeight: default is Units.gridUnit * 1.6
* * maximumHeight: default is Units.gridUnit * 3
*
* To achieve a titlebar that stays completely fixed just set the 3 sizes as the same
*
* @inherit QtQuick.Item
*/
Item {
id: root
z: 90
property int minimumHeight: 0
// Use an inline arrow function, referring to an external normal function makes QV4 crash, see https://bugreports.qt.io/browse/QTBUG-119395
property int preferredHeight: mainItem.children.reduce((accumulator, item) => {
return Math.max(accumulator, item.implicitHeight);
}, 0) + topPadding + bottomPadding
property int maximumHeight: Kirigami.Units.gridUnit * 3
property int position: QQC2.ToolBar.Header
property Kirigami.PageRow pageRow: __appWindow?.pageStack ?? null
property Kirigami.Page page: pageRow?.currentItem as Kirigami.Page ?? null
default property alias contentItem: mainItem.data
readonly property int paintedHeight: headerItem.y + headerItem.height - 1
property int leftPadding: 0
property int topPadding: 0
property int rightPadding: 0
property int bottomPadding: 0
property bool separatorVisible: true
/**
* This property specifies whether the header should be pushed back when
* scrolling using the touch screen.
*/
property bool hideWhenTouchScrolling: root.pageRow?.globalToolBar.hideWhenTouchScrolling ?? false
LayoutMirroring.enabled: Qt.application.layoutDirection === Qt.RightToLeft
LayoutMirroring.childrenInherit: true
Kirigami.Theme.inherit: true
// FIXME: remove
property QtObject __appWindow: typeof applicationWindow !== "undefined" ? applicationWindow() : null
implicitHeight: preferredHeight
height: Layout.preferredHeight
/**
* @brief This property holds the background item.
* @note the background will be automatically sized to fill the whole control
*/
property Item background
onBackgroundChanged: {
background.z = -1;
background.parent = headerItem;
background.anchors.fill = headerItem;
}
Component.onCompleted: AppHeaderSizeGroup.items.push(this)
onMinimumHeightChanged: implicitHeight = preferredHeight;
onPreferredHeightChanged: implicitHeight = preferredHeight;
opacity: height > 0 ? 1 : 0
NumberAnimation {
id: heightAnim
target: root
property: "implicitHeight"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
Connections {
target: root.__appWindow
function onControlsVisibleChanged() {
heightAnim.from = root.implicitHeight;
heightAnim.to = root.__appWindow.controlsVisible ? root.preferredHeight : 0;
heightAnim.restart();
}
}
Connections {
target: root.page?.Kirigami.ColumnView ?? null
function onScrollIntention(event) {
headerItem.scrollIntentHandler(event);
}
}
Item {
id: headerItem
anchors {
left: parent.left
right: parent.right
bottom: !Kirigami.Settings.isMobile || root.position === QQC2.ToolBar.Header ? parent.bottom : undefined
top: !Kirigami.Settings.isMobile || root.position === QQC2.ToolBar.Footer ? parent.top : undefined
}
height: Math.max(root.height, root.minimumHeight > 0 ? root.minimumHeight : root.preferredHeight)
function scrollIntentHandler(event) {
if (!root.hideWhenTouchScrolling) {
return
}
if (root.pageRow
&& root.pageRow.globalToolBar.actualStyle !== Kirigami.ApplicationHeaderStyle.Breadcrumb) {
return;
}
if (!root.page.flickable || (root.page.flickable.atYBeginning && root.page.flickable.atYEnd)) {
return;
}
root.implicitHeight = Math.max(0, Math.min(root.preferredHeight, root.implicitHeight + event.delta.y))
event.accepted = root.implicitHeight > 0 && root.implicitHeight < root.preferredHeight;
slideResetTimer.restart();
if ((root.page.flickable instanceof ListView) && root.page.flickable.verticalLayoutDirection === ListView.BottomToTop) {
root.page.flickable.contentY -= event.delta.y;
}
}
Connections {
target: root.page?.globalToolBarItem ?? null
enabled: target
function onImplicitHeightChanged() {
root.implicitHeight = root.page.globalToolBarItem.implicitHeight;
}
}
Timer {
id: slideResetTimer
interval: 500
onTriggered: {
if ((root.pageRow?.wideMode ?? (root.__appWindow?.wideScreen ?? false)) || !Kirigami.Settings.isMobile) {
return;
}
if (root.height > root.minimumHeight + (root.preferredHeight - root.minimumHeight) / 2) {
heightAnim.to = root.preferredHeight;
} else {
heightAnim.to = root.minimumHeight;
}
heightAnim.from = root.implicitHeight
heightAnim.restart();
}
}
Connections {
target: pageRow
function onCurrentItemChanged() {
if (!root.page) {
return;
}
heightAnim.from = root.implicitHeight;
heightAnim.to = root.preferredHeight;
heightAnim.restart();
}
}
Item {
id: mainItem
clip: childrenRect.width > width
onChildrenChanged: {
for (const child of children) {
child.anchors.verticalCenter = verticalCenter;
}
}
anchors {
fill: parent
topMargin: root.topPadding
leftMargin: root.leftPadding
rightMargin: root.rightPadding
bottomMargin: root.bottomPadding
}
}
}
}
@@ -0,0 +1,128 @@
/*
* SPDX-FileCopyrightText: 2018 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* A AbstractCard is the base for cards. A Card is a visual object that serves
* as an entry point for more detailed information. An abstractCard is empty,
* providing just the look and the base properties and signals for an ItemDelegate.
* It can be filled with any custom layout of items, its content is organized
* in 3 properties: header, contentItem and footer.
* Use this only when you need particular custom contents, for a standard layout
* for cards, use the Card component.
*
* @see Card
* @inherit QtQuick.Controls.ItemDelegate
* @since 2.4
*/
T.ItemDelegate {
id: root
//BEGIN properties
/**
* @brief This property holds an item that serves as a header.
*
* This item will be positioned on top if headerOrientation is ``Qt.Vertical``
* or on the left if it is ``Qt.Horizontal``.
*/
property alias header: headerFooterLayout.header
/**
* @brief This property sets the card's orientation.
*
* * ``Qt.Vertical``: the header will be positioned on top
* * ``Qt.Horizontal``: the header will be positioned on the left (or right if an RTL layout is used)
*
* default: ``Qt.Vertical``
*
* @property Qt::Orientation headerOrientation
*/
property int headerOrientation: Qt.Vertical
/**
* @brief This property holds an item that serves as a footer.
*
* This item will be positioned at the bottom if headerOrientation is ``Qt.Vertical``
* or on the right if it is ``Qt.Horizontal``.
*/
property alias footer: headerFooterLayout.footer
/**
* @brief This property sets whether clicking or tapping on the card area shows a visual click feedback.
*
* Use this if you want to do an action in the onClicked signal handler of the card.
*
* default: ``false``
*/
property bool showClickFeedback: false
//END properties
Layout.fillWidth: true
implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset,
outerPaddingLayout.implicitWidth)
implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset,
outerPaddingLayout.implicitHeight)
hoverEnabled: !Kirigami.Settings.tabletMode && showClickFeedback
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
width: ListView.view ? ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin : undefined
padding: Kirigami.Units.largeSpacing
// Card component repurposes control's contentItem property, so it has to
// reimplement content layout and its padding manually.
Kirigami.Padding {
id: outerPaddingLayout
anchors.fill: parent
topPadding: root.topPadding
leftPadding: root.leftPadding
rightPadding: root.rightPadding
bottomPadding: root.bottomPadding
contentItem: Kirigami.HeaderFooterLayout {
id: headerFooterLayout
contentItem: Kirigami.Padding {
id: innerPaddingLayout
contentItem: root.contentItem
// Hide it altogether, so that vertical padding won't be
// included in control's total implicit height.
visible: contentItem !== null
topPadding: headerFooterLayout.header ? Kirigami.Units.largeSpacing : 0
bottomPadding: headerFooterLayout.footer ? Kirigami.Units.largeSpacing : 0
}
}
}
// HACK: A Control like this ItemDelegate tries to manage its
// contentItem's positioning, so we need to override that. This is
// equivalent to declaring x/y/width/height bindings in QQC2 style
// implementations.
Connections {
target: root.contentItem
function onXChanged() {
root.contentItem.x = 0;
}
function onYChanged() {
root.contentItem.y = Qt.binding(() => innerPaddingLayout.topPadding);
}
}
}
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2022 Felipe Kinoshita <kinofhek@gmail.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
import QtQuick
import QtQuick.Templates as T
/**
* @brief Chip is a visual object based on AbstractButton
* that provides a friendly way to display predetermined elements
* with the visual styling of "tags" or "tokens."
*
* @see Chip
* @since 2.19
* @inherit QtQuick.Controls.AbstractButton
*/
T.AbstractButton {
id: chip
/**
* @brief This property holds whether or not to display a close button.
*
* default: ``true``
*/
property bool closable: true
/**
* @brief This property holds whether the icon should be masked or not. This controls the Kirigami.Icon.isMask property.
*
* default: ``false``
*/
property bool iconMask: false
/**
* @brief This signal is emitted when the close button has been clicked.
*/
signal removed()
}
@@ -0,0 +1,411 @@
/*
* SPDX-FileCopyrightText: 2018 Eike Hein <hein@kde.org>
* SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
import org.kde.kirigami.templates.private as TP
/**
* An inline message item with support for informational, positive,
* warning and error types, and with support for associated actions.
*
* InlineMessage can be used to give information to the user or
* interact with the user, without requiring the use of a dialog.
*
* The InlineMessage item is hidden by default. It also manages its
* height (and implicitHeight) during an animated reveal when shown.
* You should avoid setting height on an InlineMessage unless it is
* already visible.
*
* Optionally an icon can be set, defaulting to an icon appropriate
* to the message type otherwise.
*
* Optionally a close button can be shown.
*
* Actions are added from left to right. If more actions are set than
* can fit, an overflow menu is provided.
*
* Example:
* @code
* import org.kde.kirigami as Kirigami
*
* Kirigami.InlineMessage {
* type: Kirigami.MessageType.Error
*
* text: i18n("My error message")
*
* actions: [
* Kirigami.Action {
* icon.name: "list-add"
* text: i18n("Add")
* onTriggered: source => {
* // do stuff
* }
* },
* Kirigami.Action {
* icon.name: "edit"
* text: i18n("Edit")
* onTriggered: source => {
* // do stuff
* }
* }
* ]
* }
* @endcode
*
* @since 5.45
* @inherit QtQuick.Templates.Control
*/
T.Control {
id: root
visible: false
/**
* Defines a position for the message: whether it's to be used as an inline component inside the page,
* a page header, or a page footer.
*/
enum Position {
Inline,
Header,
Footer
}
/**
* Adjust the look of the message based upon the position.
* If a message is positioned in the header area or in the footer area
* of a page, it might be desirable to not have borders but just a line
* separating it from the content area. In this case, use the Header or
* Footer position.
* Default is InlineMessage.Position.Inline
*/
property int position: InlineMessage.Position.Inline
/**
* This signal is emitted when a link is hovered in the message text.
* @param The hovered link.
*/
signal linkHovered(string link)
/**
* This signal is emitted when a link is clicked or tapped in the message text.
* @param The clicked or tapped link.
*/
signal linkActivated(string link)
/**
* This property holds the link embedded in the message text that the user is hovering over.
*/
readonly property alias hoveredLink: label.hoveredLink
/**
* This property holds the message type. One of Information, Positive, Warning or Error.
*
* The default is Kirigami.MessageType.Information.
*/
property int type: Kirigami.MessageType.Information
/**
* This grouped property holds the description of an optional icon.
*
* * source: The source of the icon, a freedesktop-compatible icon name is recommended.
* * color: An optional tint color for the icon.
*
* If no custom icon is set, an icon appropriate to the message type
* is shown.
*/
property TP.IconPropertiesGroup icon: TP.IconPropertiesGroup {}
/**
* This property holds the message text.
*/
property string text
/**
* This property holds whether the close button is displayed.
*
* The default is false.
*/
property bool showCloseButton: false
/**
* This property holds the list of actions to show. Actions are added from left to
* right. If more actions are set than can fit, an overflow menu is
* provided.
*/
property list<T.Action> actions
/**
* This property holds whether the current message item is animating.
*/
readonly property bool animating: _animating
property bool _animating: false
implicitHeight: visible ? (contentLayout.implicitHeight + topPadding + bottomPadding) : 0
padding: Kirigami.Units.smallSpacing
Accessible.role: Accessible.AlertMessage
Accessible.ignored: !visible
Behavior on implicitHeight {
enabled: !root.visible
SequentialAnimation {
PropertyAction { targets: root; property: "_animating"; value: true }
NumberAnimation { duration: Kirigami.Units.longDuration }
}
}
onVisibleChanged: {
if (!visible) {
contentLayout.opacity = 0;
}
}
opacity: visible ? 1 : 0
Behavior on opacity {
enabled: !root.visible
NumberAnimation { duration: Kirigami.Units.shortDuration }
}
onOpacityChanged: {
if (opacity === 0) {
contentLayout.opacity = 0;
} else if (opacity === 1) {
contentLayout.opacity = 1;
}
}
onImplicitHeightChanged: {
height = implicitHeight;
}
contentItem: Item {
id: contentLayout
// Used to defer opacity animation until we know if InlineMessage was
// initialized visible.
property bool complete: false
Behavior on opacity {
enabled: root.visible && contentLayout.complete
SequentialAnimation {
NumberAnimation { duration: Kirigami.Units.shortDuration * 2 }
PropertyAction { targets: root; property: "_animating"; value: false }
}
}
implicitHeight: {
if (atBottom) {
return label.implicitHeight + actionsLayout.implicitHeight + actionsLayout.anchors.topMargin
} else {
return Math.max(icon.implicitHeight, label.implicitHeight, closeButton.implicitHeight, actionsLayout.implicitHeight)
}
}
Accessible.ignored: true
readonly property real remainingWidth: width - (
icon.width
+ label.anchors.leftMargin + label.implicitWidth + label.anchors.rightMargin
+ (root.showCloseButton ? closeButton.width : 0)
)
readonly property bool multiline: remainingWidth <= 0 || atBottom
readonly property bool atBottom: (root.actions.length > 0) && (label.lineCount > 1 || actionsLayout.implicitWidth > remainingWidth)
Kirigami.Icon {
id: icon
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
anchors {
left: parent.left
leftMargin: Kirigami.Units.smallSpacing
topMargin: Kirigami.Units.smallSpacing
}
states: [
State {
name: "multi-line"
when: contentLayout.atBottom || label.height > icon.height * 1.7
AnchorChanges {
target: icon
anchors.top: icon.parent.top
anchors.verticalCenter: undefined
}
},
// States are evaluated in the order they are declared.
// This is a fallback state.
State {
name: "single-line"
when: true
AnchorChanges {
target: icon
anchors.top: undefined
anchors.verticalCenter: parent.verticalCenter
}
}
]
source: {
if (root.icon.name) {
return root.icon.name;
} else if (root.icon.source) {
return root.icon.source;
}
switch (root.type) {
case Kirigami.MessageType.Positive:
return "emblem-success";
case Kirigami.MessageType.Warning:
return "emblem-warning";
case Kirigami.MessageType.Error:
return "emblem-error";
default:
return "emblem-information";
}
}
color: root.icon.color
Accessible.ignored: !root.visible
Accessible.name: {
switch (root.type) {
case Kirigami.MessageType.Positive:
return qsTr("Success");
case Kirigami.MessageType.Warning:
return qsTr("Warning");
case Kirigami.MessageType.Error:
return qsTr("Error");
default:
return qsTr("Note");
}
}
}
Kirigami.SelectableLabel {
id: label
anchors {
left: icon.right
leftMargin: Kirigami.Units.largeSpacing
right: root.showCloseButton ? closeButton.left : parent.right
rightMargin: root.showCloseButton ? Kirigami.Units.smallSpacing : 0
top: parent.top
}
color: Kirigami.Theme.textColor
wrapMode: Text.WordWrap
text: root.text
verticalAlignment: Text.AlignVCenter
// QTBUG-117667 TextEdit (super-type of SelectableLabel) needs
// very specific state-management trick so it doesn't get stuck.
// State names serve purely as a description.
states: [
State {
name: "multi-line"
when: contentLayout.multiline
AnchorChanges {
target: label
anchors.bottom: undefined
}
PropertyChanges {
target: label
height: label.implicitHeight
}
},
// States are evaluated in the order they are declared.
// This is a fallback state.
State {
name: "single-line"
when: true
AnchorChanges {
target: label
anchors.bottom: label.parent.bottom
}
}
]
onLinkHovered: link => root.linkHovered(link)
onLinkActivated: link => root.linkActivated(link)
Accessible.ignored: !root.visible
}
Kirigami.ActionToolBar {
id: actionsLayout
flat: false
actions: root.actions
visible: root.actions.length > 0
Accessible.ignored: !visible || !root.visible
alignment: Qt.AlignRight
anchors {
left: parent.left
top: contentLayout.atBottom ? label.bottom : parent.top
topMargin: contentLayout.atBottom ? Kirigami.Units.largeSpacing : 0
right: (!contentLayout.atBottom && root.showCloseButton) ? closeButton.left : parent.right
rightMargin: !contentLayout.atBottom && root.showCloseButton ? Kirigami.Units.smallSpacing : 0
}
}
QQC2.ToolButton {
id: closeButton
visible: root.showCloseButton
anchors.right: parent.right
// Incompatible anchors need to be evaluated in a given order,
// which simple declarative bindings cannot assure
states: [
State {
name: "onTop"
when: contentLayout.atBottom
AnchorChanges {
target: closeButton
anchors.top: parent.top
anchors.verticalCenter: undefined
}
} ,
State {
name: "centered"
AnchorChanges {
target: closeButton
anchors.top: undefined
anchors.verticalCenter: parent.verticalCenter
}
}
]
height: contentLayout.atBottom ? implicitHeight : implicitHeight
text: qsTr("Close")
display: QQC2.ToolButton.IconOnly
icon.name: "dialog-close"
onClicked: root.visible = false
Accessible.ignored: !root.visible
}
Component.onCompleted: complete = true
}
}
@@ -0,0 +1,385 @@
/*
* SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Templates as T
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import "private" as KTP
/**
* Overlay Drawers are used to expose additional UI elements needed for
* small secondary tasks for which the main UI elements are not needed.
* For example in Okular Mobile, an Overlay Drawer is used to display
* thumbnails of all pages within a document along with a search field.
* This is used for the distinct task of navigating to another page.
*
* @inherit QtQuick.Controls.Drawer
*/
T.Drawer {
id: root
//BEGIN properties
/**
* @brief This property tells whether the drawer is open and visible.
*
* default: ``false``
*/
property bool drawerOpen: false
/**
* @brief This property tells whether the drawer is in a state between open
* and closed.
*
* The drawer is visible but not completely open. This is usually the case when
* the user is dragging the drawer from a screen edge, so the user is "peeking"
* at what's in the drawer.
*
* default: ``false``
*/
property bool peeking: false
/**
* @brief This property tells whether the drawer is currently opening or closing itself.
*/
readonly property bool animating : enterAnimation.animating || exitAnimation.animating || positionResetAnim.running
/**
* @brief This property holds whether the drawer can be collapsed to a
* very thin, usually icon only sidebar.
*
* Only modal drawers are collapsible. Collapsible is not supported in
* the mobile mode.
*
* @since 2.5
*/
property bool collapsible: false
/**
* @brief This property tells whether the drawer is collapsed to a
* very thin sidebar, usually icon only.
*
* When true, the drawer will be collapsed to a very thin sidebar,
* usually icon only.
*
* default: ``false``
*
* @see collapsible Only collapsible drawers can be collapsed.
*/
property bool collapsed: false
/**
* @brief This property holds the size of the collapsed drawer.
*
* For vertical drawers this will be the width of the drawer and for horizontal
* drawers this will be the height of the drawer.
*
* default: ``Units.iconSizes.medium``, just enough to accommodate medium sized icons
*/
property int collapsedSize: Kirigami.Units.iconSizes.medium
/**
* @brief This property holds the options for handle's open icon.
*
* This is a grouped property with following components:
*
* * ``source: var``: The name of a freedesktop-compatible icon.
* * ``color: color``: An optional tint color for the icon.
*
* If no custom icon is set, a menu icon is shown for the application globalDrawer
* and an overflow menu icon is shown for the contextDrawer.
* That's the default for the GlobalDrawer and ContextDrawer components respectively.
*
* For OverlayDrawer the default is view-right-close or view-left-close depending on
* the drawer location
*
* @since 2.5
*/
readonly property KTP.IconPropertiesGroup handleOpenIcon: KTP.IconPropertiesGroup {
source: root.edge === Qt.RightEdge ? "view-right-close" : "view-left-close"
}
/**
* @brief This property holds the options for the handle's close icon.
*
* This is a grouped property with the following components:
* * ``source: var``: The name of a freedesktop-compatible icon.
* * ``color: color``: An optional tint color for the icon.
*
* If no custom icon is set, an X icon is shown,
* which will morph into the Menu or overflow icons.
*
* For OverlayDrawer the default is view-right-new or view-left-new depending on
* the drawer location.
*
* @since 2.5
*/
property KTP.IconPropertiesGroup handleClosedIcon: KTP.IconPropertiesGroup {
source: root.edge === Qt.RightEdge ? "view-right-new" : "view-left-new"
}
/**
* @brief This property holds the tooltip displayed when the drawer is open.
* @since 2.15
*/
property string handleOpenToolTip: qsTr("Close drawer")
/**
* @brief This property holds the tooltip displayed when the drawer is closed.
* @since 2.15
*/
property string handleClosedToolTip: qsTr("Open drawer")
/**
* @brief This property holds whether the handle is visible, to make opening the
* drawer easier.
*
* Currently supported only on left and right drawers.
*/
property bool handleVisible: {
if (typeof applicationWindow === "function") {
const w = applicationWindow();
if (w) {
return w.controlsVisible;
}
}
// For a generic-purpose OverlayDrawer its handle is visible by default
return true;
}
/**
* @brief Readonly property that points to the item that will act as a physical
* handle for the Drawer.
* @property MouseArea handle
**/
readonly property Item handle: KTP.DrawerHandle {
drawer: root
}
//END properties
interactive: modal
z: Kirigami.OverlayZStacking.z
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: modal ? Kirigami.Theme.View : Kirigami.Theme.Window
Kirigami.Theme.onColorSetChanged: {
contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet
}
//BEGIN reassign properties
//default paddings
leftPadding: Kirigami.Units.smallSpacing
topPadding: Kirigami.Units.smallSpacing
rightPadding: Kirigami.Units.smallSpacing
bottomPadding: Kirigami.Units.smallSpacing
y: modal ? 0 : ((T.ApplicationWindow.menuBar && T.ApplicationWindow.menuBar.visible ? T.ApplicationWindow.menuBar.height : 0) + (T.ApplicationWindow.header && T.ApplicationWindow.header.visible ? T.ApplicationWindow.header.height : 0))
height: parent && (root.edge === Qt.LeftEdge || root.edge === Qt.RightEdge) ? (modal ? parent.height : (parent.height - y - (T.ApplicationWindow.footer ? T.ApplicationWindow.footer.height : 0))) : implicitHeight
parent: modal || edge === Qt.LeftEdge || edge === Qt.RightEdge ? T.ApplicationWindow.overlay : T.ApplicationWindow.contentItem
edge: Qt.LeftEdge
modal: true
dim: modal
QQC2.Overlay.modal: Rectangle {
color: Qt.rgba(0, 0, 0, 0.35)
}
dragMargin: enabled && (edge === Qt.LeftEdge || edge === Qt.RightEdge) ? Math.min(Kirigami.Units.gridUnit, Qt.styleHints.startDragDistance) : 0
contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
implicitWidth: contentWidth + leftPadding + rightPadding
implicitHeight: contentHeight + topPadding + bottomPadding
enter: Transition {
SequentialAnimation {
id: enterAnimation
/* NOTE: why this? the running status of the enter transition is not relaible and
* the SmoothedAnimation is always marked as non running,
* so the only way to get to a reliable animating status is with this
*/
property bool animating
ScriptAction {
script: {
enterAnimation.animating = true
// on non modal dialog we don't want drawers in the overlay
if (!root.modal) {
root.background.parent.parent = applicationWindow().overlay.parent
}
}
}
SmoothedAnimation {
velocity: 5
}
ScriptAction {
script: enterAnimation.animating = false
}
}
}
exit: Transition {
SequentialAnimation {
id: exitAnimation
property bool animating
ScriptAction {
script: exitAnimation.animating = true
}
SmoothedAnimation {
velocity: 5
}
ScriptAction {
script: exitAnimation.animating = false
}
}
}
//END reassign properties
//BEGIN signal handlers
onCollapsedChanged: {
if (Kirigami.Settings.isMobile) {
collapsed = false;
}
if (!__internal.completed) {
return;
}
if ((!collapsible || modal) && collapsed) {
collapsed = true;
}
}
onCollapsibleChanged: {
if (Kirigami.Settings.isMobile) {
collapsible = false;
}
if (!__internal.completed) {
return;
}
if (!collapsible) {
collapsed = false;
} else if (modal) {
collapsible = false;
}
}
onModalChanged: {
if (!__internal.completed) {
return;
}
if (modal) {
collapsible = false;
}
}
onPositionChanged: {
if (peeking) {
visible = true
}
}
onVisibleChanged: {
if (peeking) {
visible = true
} else {
drawerOpen = visible;
}
}
onPeekingChanged: {
if (peeking) {
root.enter.enabled = false;
root.exit.enabled = false;
} else {
drawerOpen = position > 0.5 ? 1 : 0;
positionResetAnim.running = true
root.enter.enabled = true;
root.exit.enabled = true;
}
}
onDrawerOpenChanged: {
// sync this property only when the component is properly loaded
if (!__internal.completed) {
return;
}
positionResetAnim.running = false;
if (drawerOpen) {
open();
} else {
close();
}
Qt.callLater(() => root.handle.displayToolTip = true)
}
Component.onCompleted: {
// if defined as drawerOpen by default in QML, don't animate
if (root.drawerOpen) {
root.enter.enabled = false;
root.visible = true;
root.position = 1;
root.enter.enabled = true;
}
__internal.completed = true;
contentItem.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
background.Kirigami.Theme.colorSet = Kirigami.Theme.colorSet;
}
//END signal handlers
// this is as hidden as it can get here
property QtObject __internal: QtObject {
//here in order to not be accessible from outside
property bool completed: false
property SequentialAnimation positionResetAnim: SequentialAnimation {
id: positionResetAnim
property alias to: internalAnim.to
NumberAnimation {
id: internalAnim
target: root
to: drawerOpen ? 1 : 0
property: "position"
duration: (root.position)*Kirigami.Units.longDuration
}
ScriptAction {
script: {
root.drawerOpen = internalAnim.to !== 0;
}
}
}
readonly property Item statesItem: Item {
states: [
State {
when: root.collapsed
PropertyChanges {
target: root
implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(collapsedSize + leftPadding + rightPadding, Math.round(applicationWindow().width*0.8))
implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(collapsedSize + topPadding + bottomPadding, Math.round(applicationWindow().height*0.8))
}
},
State {
when: !root.collapsed
PropertyChanges {
target: root
implicitWidth: edge === Qt.TopEdge || edge === Qt.BottomEdge ? applicationWindow().width : Math.min(contentItem.implicitWidth, Math.round(applicationWindow().width*0.8))
implicitHeight: edge === Qt.LeftEdge || edge === Qt.RightEdge ? applicationWindow().height : Math.min(contentHeight + topPadding + bottomPadding, Math.round(applicationWindow().height*0.4))
contentWidth: contentItem.implicitWidth || (contentChildren.length === 1 ? contentChildren[0].implicitWidth : 0)
contentHeight: contentItem.implicitHeight || (contentChildren.length === 1 ? contentChildren[0].implicitHeight : 0)
}
}
]
transitions: Transition {
reversible: true
NumberAnimation {
properties: root.edge === Qt.TopEdge || root.edge === Qt.BottomEdge ? "implicitHeight" : "implicitWidth"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
}
}
}
@@ -0,0 +1,437 @@
/*
* SPDX-FileCopyrightText: 2016-2023 Marco Martin <notmart@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
/**
* @brief An overlay sheet that covers the current Page content.
*
* Its contents can be scrolled up or down, scrolling all the way up or
* all the way down, dismisses it.
* Use this for big, modal dialogs or information display, that can't be
* logically done as a new separate Page, even if potentially
* are taller than the screen space.
*
* Example usage:
* @code
* Kirigami.OverlaySheet {
* ColumnLayout { ... }
* }
* Kirigami.OverlaySheet {
* ListView { ... }
* }
* @endcode
*
* It needs a single element declared inside, do *not* override its contentItem
*
* @inherit QtQuick.Templates.Popup
*/
T.Popup {
id: root
Kirigami.OverlayZStacking.layer: Kirigami.OverlayZStacking.FullScreen
z: Kirigami.OverlayZStacking.z
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
//BEGIN Own Properties
/**
* @brief A title to be displayed in the header of this Sheet
*/
property string title
/**
* @brief This property sets the visibility of the close button in the top-right corner.
*
* default: `Only shown in desktop mode`
*
*/
property bool showCloseButton: !Kirigami.Settings.isMobile
/**
* @brief This property holds an optional item which will be used as the sheet's header,
* and will always be displayed.
*/
property Item header: Kirigami.Heading {
level: 2
text: root.title
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
// use tooltip for long text that is elided
T.ToolTip.visible: truncated && titleHoverHandler.hovered
T.ToolTip.text: root.title
HoverHandler {
id: titleHoverHandler
}
}
/**
* @brief An optional item which will be used as the sheet's footer,
* always kept on screen.
*/
property Item footer
default property alias flickableContentData: scrollView.contentData
//END Own Properties
//BEGIN Reimplemented Properties
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
}
}
}
modal: true
dim: true
leftInset: -1
rightInset: -1
topInset: -1
bottomInset: -1
closePolicy: T.Popup.CloseOnEscape
x: parent ? Math.round(parent.width / 2 - width / 2) : 0
y: {
if (!parent) {
return 0;
}
const visualParentAdjust = sheetHandler.visualParent?.y ?? 0;
const wantedPosition = parent.height / 2 - implicitHeight / 2;
return Math.round(Math.max(visualParentAdjust, wantedPosition, Kirigami.Units.gridUnit * 3));
}
width: root.parent ? Math.min(root.parent.width, implicitWidth) : implicitWidth
implicitWidth: {
let width = parent?.width ?? 0;
if (!scrollView.itemForSizeHints) {
return width;
} else if (scrollView.itemForSizeHints.Layout.preferredWidth > 0) {
return Math.min(width, scrollView.itemForSizeHints.Layout.preferredWidth);
} else if (scrollView.itemForSizeHints.implicitWidth > 0) {
return Math.min(width, scrollView.itemForSizeHints.implicitWidth);
} else {
return width;
}
}
implicitHeight: {
let h = parent?.height ?? 0;
if (!scrollView.itemForSizeHints) {
return h - y;
} else if (scrollView.itemForSizeHints.Layout.preferredHeight > 0) {
h = scrollView.itemForSizeHints.Layout.preferredHeight;
} else if (scrollView.itemForSizeHints.implicitHeight > 0) {
h = scrollView.itemForSizeHints.implicitHeight + Kirigami.Units.largeSpacing * 2;
} else if (scrollView.itemForSizeHints instanceof Flickable && scrollView.itemForSizeHints.contentHeight > 0) {
h = scrollView.itemForSizeHints.contentHeight + Kirigami.Units.largeSpacing * 2;
} else {
h = scrollView.itemForSizeHints.height;
}
h += headerItem.implicitHeight + footerParent.implicitHeight + topPadding + bottomPadding;
return parent ? Math.min(h, parent.height - y) : h
}
//END Reimplemented Properties
//BEGIN Signal handlers
onVisibleChanged: {
const flickable = scrollView.contentItem;
flickable.contentY = flickable.originY - flickable.topMargin;
}
Component.onCompleted: {
Qt.callLater(() => {
if (!root.parent && typeof applicationWindow !== "undefined") {
root.parent = applicationWindow().overlay
}
});
}
Connections {
target: parent
function onVisibleChanged() {
if (!parent.visible) {
root.close();
}
}
}
//END Signal handlers
//BEGIN UI
contentItem: MouseArea {
implicitWidth: mainLayout.implicitWidth
implicitHeight: mainLayout.implicitHeight
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
Kirigami.Theme.inherit: false
property real scenePressY
property real lastY
property bool dragStarted
drag.filterChildren: true
DragHandler {
id: mouseDragBlocker
target: null
dragThreshold: 0
acceptedDevices: PointerDevice.Mouse
onActiveChanged: {
if (active) {
parent.dragStarted = false;
}
}
}
onPressed: mouse => {
scenePressY = mapToItem(null, mouse.x, mouse.y).y;
lastY = scenePressY;
dragStarted = false;
}
onPositionChanged: mouse => {
if (mouseDragBlocker.active) {
return;
}
const currentY = mapToItem(null, mouse.x, mouse.y).y;
if (dragStarted && currentY !== lastY) {
translation.y += currentY - lastY;
}
if (Math.abs(currentY - scenePressY) > Qt.styleHints.startDragDistance) {
dragStarted = true;
}
lastY = currentY;
}
onCanceled: restoreAnim.restart();
onReleased: mouse => {
if (mouseDragBlocker.active) {
return;
}
if (Math.abs(mapToItem(null, mouse.x, mouse.y).y - scenePressY) > Kirigami.Units.gridUnit * 5) {
root.close();
} else {
restoreAnim.restart();
}
}
ColumnLayout {
id: mainLayout
anchors.fill: parent
spacing: 0
// Even though we're not actually using any shadows here,
// we're using a ShadowedRectangle instead of a regular
// rectangle because it allows fine-grained control over which
// corners to round, which we need here
Item {
id: headerItem
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
//Layout.margins: 1
visible: root.header || root.showCloseButton
implicitHeight: Math.max(headerParent.implicitHeight, closeIcon.height)// + Kirigami.Units.smallSpacing * 2
z: 2
Rectangle {
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: Kirigami.Units.smallSpacing
}
width: Math.round(Kirigami.Units.gridUnit * 3)
height: Math.round(Kirigami.Units.gridUnit / 4)
radius: height
color: Kirigami.Theme.textColor
opacity: 0.4
visible: Kirigami.Settings.hasTransientTouchInput
}
Kirigami.Padding {
id: headerParent
readonly property real leadingPadding: Kirigami.Units.largeSpacing
readonly property real trailingPadding: (root.showCloseButton ? closeIcon.width : 0) + Kirigami.Units.smallSpacing
anchors.fill: parent
verticalPadding: Kirigami.Units.largeSpacing
leftPadding: root.mirrored ? trailingPadding : leadingPadding
rightPadding: root.mirrored ? leadingPadding : trailingPadding
contentItem: root.header
}
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
anchors {
verticalCenter: !tallHeader ? undefined : parent.verticalCenter
right: parent.right
margins: Kirigami.Units.largeSpacing
}
z: 3
visible: root.showCloseButton
icon.name: closeIcon.hovered ? "window-close" : "window-close-symbolic"
text: qsTr("Close", "@action:button close dialog")
onClicked: root.close()
display: QQC2.AbstractButton.IconOnly
}
Kirigami.Separator {
anchors {
right: parent.right
left: parent.left
top: parent.bottom
}
visible: scrollView.T.ScrollBar.vertical.visible
}
}
// Here goes the main Sheet content
QQC2.ScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
T.ScrollBar.horizontal.policy: T.ScrollBar.AlwaysOff
property bool initialized: false
property Item itemForSizeHints
// Important to not even access contentItem before it has been spontaneously created
contentWidth: initialized ? contentItem.width : width
contentHeight: itemForSizeHints?.implicitHeight ?? 0
onContentItemChanged: {
initialized = true;
const flickable = contentItem as Flickable;
flickable.boundsBehavior = Flickable.StopAtBounds;
if ((flickable instanceof ListView) || (flickable instanceof GridView)) {
itemForSizeHints = flickable;
return;
}
const content = flickable.contentItem;
content.childrenChanged.connect(() => {
for (const item of content.children) {
item.anchors.margins = Kirigami.Units.largeSpacing;
item.anchors.top = content.top;
item.anchors.left = content.left;
item.anchors.right = content.right;
}
itemForSizeHints = content.children?.[0] ?? null;
});
}
}
// Optional footer
Kirigami.Separator {
Layout.fillWidth: true
visible: footerParent.visible
}
Kirigami.Padding {
id: footerParent
Layout.fillWidth: true
padding: Kirigami.Units.smallSpacing
contentItem: root.footer
visible: contentItem !== null
}
}
Translate {
id: translation
}
MouseArea {
id: sheetHandler
readonly property Item visualParent: root.parent?.contentItem ?? root.parent
x: -root.x
y: -root.y
z: -1
width: visualParent?.width ?? 0
height: (visualParent?.height ?? 0) * 2
property var pressPos
onPressed: mouse => {
pressPos = mapToItem(null, mouse.x, mouse.y)
}
onReleased: mouse => {
// onClicked is emitted even if the mouse was dragged a lot, so we have to check the Manhattan length by hand
// https://en.wikipedia.org/wiki/Taxicab_geometry
let pos = mapToItem(null, mouse.x, mouse.y)
if (Math.abs(pos.x - pressPos.x) + Math.abs(pos.y - pressPos.y) < Qt.styleHints.startDragDistance) {
root.close();
}
}
NumberAnimation {
id: restoreAnim
target: translation
property: "y"
from: translation.y
to: 0
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
Component.onCompleted: {
root.contentItem.parent.transform = translation
root.contentItem.parent.clip = false
}
}
}
//END UI
//BEGIN Transitions
enter: Transition {
ParallelAnimation {
NumberAnimation {
property: "opacity"
from: 0
to: 1
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
NumberAnimation {
target: translation
property: "y"
from: Kirigami.Units.gridUnit * 5
to: 0
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
}
}
exit: Transition {
ParallelAnimation {
NumberAnimation {
property: "opacity"
from: 1
to: 0
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
NumberAnimation {
target: translation
property: "y"
from: translation.y
to: translation.y >= 0 ? translation.y + Kirigami.Units.gridUnit * 5 : translation.y - Kirigami.Units.gridUnit * 5
easing.type: Easing.InOutQuad
duration: Kirigami.Units.longDuration
}
}
}
//END Transitions
}
@@ -0,0 +1,13 @@
pragma Singleton
/*
* SPDX-FileCopyrightText: 2020 Carson Black
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import org.kde.kirigami as Kirigami
Kirigami.SizeGroup {
mode: Kirigami.SizeGroup.Height
}
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ToolButton {
id: button
icon.name: (LayoutMirroring.enabled ? "go-previous-symbolic-rtl" : "go-previous-symbolic")
enabled: {
const pageStack = applicationWindow().pageStack;
if (pageStack.layers.depth > 1) {
return true;
}
if (pageStack.depth > 1) {
if (pageStack.currentIndex > 0) {
return true;
}
const view = pageStack.columnView;
if (LayoutMirroring.enabled) {
return view.contentWidth - view.width < view.contentX
} else {
return view.contentX > 0;
}
}
return false;
}
// The gridUnit wiggle room is used to not flicker the button visibility during an animated resize for instance due to a sidebar collapse
visible: {
const pageStack = applicationWindow().pageStack;
const showNavButtons = globalToolBar?.showNavigationButtons ?? Kirigami.ApplicationHeaderStyle.NoNavigationButtons;
return pageStack.layers.depth > 1 || (pageStack.contentItem.contentWidth > pageStack.width + Kirigami.Units.gridUnit && (showNavButtons & Kirigami.ApplicationHeaderStyle.ShowBackButton));
}
onClicked: {
applicationWindow().pageStack.goBack();
}
text: qsTr("Navigate Back")
display: QQC2.ToolButton.IconOnly
QQC2.ToolTip {
visible: button.hovered
text: button.text
delay: Kirigami.Units.toolTipDelay
timeout: 5000
y: button.height
}
}
@@ -0,0 +1,19 @@
/*
* SPDX-FileCopyrightText: 2020 Carson Black <uhhadd@gmail.com>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
QtObject {
/**
* @brief This property holds the color of this border.
*/
property color color
/**
* @brief This property holds the width of this border.
*/
property real width
}
@@ -0,0 +1,68 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
id: canvas
property Kirigami.OverlayDrawer drawer
property color color: Kirigami.Theme.textColor
property int thickness: 2
property real position: drawer?.position ?? 0
width: Kirigami.Units.iconSizes.smallMedium
height: Kirigami.Units.iconSizes.smallMedium
opacity: 0.8
layer.enabled: true
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
Item {
id: iconRoot
anchors {
fill: parent
margins: Kirigami.Units.smallSpacing
}
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
top: parent.top
topMargin: (parent.height / 2 - canvas.thickness / 2) * canvas.position
}
antialiasing: canvas.position !== 0
transformOrigin: Item.Center
width: (1 - canvas.position) * height + canvas.position * Math.sqrt(2 * parent.width * parent.width)
height: canvas.thickness
color: canvas.color
rotation: 45 * canvas.position
}
Rectangle {
anchors.centerIn: parent
width: height
height: canvas.thickness
color: canvas.color
}
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: (parent.height / 2 - canvas.thickness / 2) * canvas.position
}
antialiasing: canvas.position !== 0
transformOrigin: Item.Center
width: (1 - canvas.position) * height + canvas.position * Math.sqrt(2 * parent.width * parent.width)
height: canvas.thickness
color: canvas.color
rotation: -45 * canvas.position
}
}
}
@@ -0,0 +1,208 @@
/*
* SPDX-FileCopyrightText: 2023 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Templates as T
import org.kde.kirigami as Kirigami
MouseArea {
id: root
/*
* This property is used to set when the tooltip is visible.
* It exists because the text is changed while the tooltip is still visible.
*/
property bool displayToolTip: true
/**
* The drawer this handle will control
*/
// Should be KT.OverlayDrawer, but can't due to "Cyclic dependency"
property T.Drawer drawer
readonly property T.Overlay overlay: drawer.T.Overlay.overlay
// Above the Overlay when modal but below when non-modal and when the drawer is closed
// so that other overlays can be above it.
parent: overlay?.parent ?? null
z: overlay ? overlay.z + (drawer?.modal && drawer?.drawerOpen ? 1 : - 1) : 0
preventStealing: true
hoverEnabled: handleAnchor?.visible ?? false
QQC2.ToolButton {
anchors.centerIn: parent
width: parent.height - Kirigami.Units.smallSpacing * 1.5
height: parent.height - Kirigami.Units.smallSpacing * 1.5
visible: !Kirigami.Settings.tabletMode && !Kirigami.Settings.hasTransientTouchInput
Accessible.name: root.drawer.drawerOpen ? root.drawer.handleOpenToolTip : root.drawer.handleClosedToolTip
onClicked: {
root.displayToolTip = false;
Qt.callLater(() => {
root.drawer.drawerOpen = !root.drawer.drawerOpen;
})
}
Keys.onEscapePressed: {
if (root.drawer.closePolicy & T.Popup.CloseOnEscape) {
root.drawer.drawerOpen = false;
}
}
}
QQC2.ToolTip.visible: displayToolTip && containsMouse
QQC2.ToolTip.text: drawer.drawerOpen ? handleOpenToolTip : handleClosedToolTip
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
property Item handleAnchor: {
if (typeof applicationWindow === "undefined") {
return null;
}
const window = applicationWindow();
const globalToolBar = window.pageStack?.globalToolBar;
if (!globalToolBar) {
return null;
}
return (drawer.edge === Qt.LeftEdge && !drawer.mirrored) || (drawer.edge === Qt.RightEdge && drawer.mirrored)
? globalToolBar.leftHandleAnchor
: globalToolBar.rightHandleAnchor;
}
property int startX
property int mappedStartX
enabled: drawer.handleVisible
onPressed: mouse => {
drawer.peeking = true;
startX = mouse.x;
mappedStartX = mapToItem(parent, startX, 0).x;
}
onPositionChanged: mouse => {
if (!pressed) {
return;
}
const pos = mapToItem(parent, mouse.x - startX, mouse.y);
switch (drawer.edge) {
case Qt.LeftEdge:
drawer.position = pos.x / drawer.contentItem.width;
break;
case Qt.RightEdge:
drawer.position = (drawer.parent.width - pos.x - width) / drawer.contentItem.width;
break;
default:
}
}
onReleased: mouse => {
drawer.peeking = false;
if (Math.abs(mapToItem(parent, mouse.x, 0).x - mappedStartX) < Qt.styleHints.startDragDistance) {
if (!drawer.drawerOpen) {
drawer.close();
}
drawer.drawerOpen = !drawer.drawerOpen;
}
}
onCanceled: {
drawer.peeking = false
}
x: {
switch (drawer.edge) {
case Qt.LeftEdge:
return drawer.background.width * drawer.position + Kirigami.Units.smallSpacing;
case Qt.RightEdge:
return parent.width - (drawer.background.width * drawer.position) - width - Kirigami.Units.smallSpacing;
default:
return 0;
}
}
Binding {
when: root.handleAnchor && root.anchors.bottom
target: root
property: "y"
value: root.handleAnchor ? root.handleAnchor.Kirigami.ScenePosition.y : 0
restoreMode: Binding.RestoreBinding
}
anchors {
bottom: handleAnchor ? undefined : parent.bottom
bottomMargin: {
if (typeof applicationWindow === "undefined") {
return undefined;
}
const window = applicationWindow();
let margin = Kirigami.Units.smallSpacing;
if (window.footer) {
margin = window.footer.height + Kirigami.Units.smallSpacing;
}
if (drawer.parent && drawer.height < drawer.parent.height) {
margin = drawer.parent.height - drawer.height - drawer.y + Kirigami.Units.smallSpacing;
}
if (!window || !window.pageStack ||
!window.pageStack.contentItem ||
!window.pageStack.contentItem.itemAt) {
return margin;
}
let item;
if (window.pageStack.layers.depth > 1) {
item = window.pageStack.layers.currentItem;
} else {
item = window.pageStack.contentItem.itemAt(window.pageStack.contentItem.contentX + x, 0);
}
// try to take the last item
if (!item) {
item = window.pageStack.lastItem;
}
let pageFooter = item && item.page ? item.page.footer : (item ? item.footer : undefined);
if (pageFooter && drawer.parent) {
margin = drawer.height < drawer.parent.height ? margin : margin + pageFooter.height
}
return margin;
}
Behavior on bottomMargin {
NumberAnimation {
duration: Kirigami.Units.shortDuration
easing.type: Easing.InOutQuad
}
}
}
visible: drawer.enabled && (drawer.edge === Qt.LeftEdge || drawer.edge === Qt.RightEdge) && opacity > 0
width: handleAnchor?.visible ? handleAnchor.width : Kirigami.Units.iconSizes.smallMedium + Kirigami.Units.smallSpacing * 2
height: handleAnchor?.visible ? handleAnchor.height : width
opacity: drawer.handleVisible ? 1 : 0
Behavior on opacity {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutQuad
}
}
transform: Translate {
x: root.drawer.handleVisible ? 0 : (root.drawer.edge === Qt.LeftEdge ? -root.width : root.width)
Behavior on x {
NumberAnimation {
duration: Kirigami.Units.longDuration
easing.type: !root.drawer.handleVisible ? Easing.OutQuad : Easing.InQuad
}
}
}
}
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
QQC2.ToolButton {
id: button
icon.name: (LayoutMirroring.enabled ? "go-next-symbolic-rtl" : "go-next-symbolic")
enabled: applicationWindow().pageStack.currentIndex < applicationWindow().pageStack.depth-1
// The gridUnit wiggle room is used to not flicker the button visibility during an animated resize for instance due to a sidebar collapse
visible: {
const pageStack = applicationWindow().pageStack;
const showNavButtons = globalToolBar?.showNavigationButtons ?? Kirigami.ApplicationHeaderStyle.NoNavigationButtons;
return pageStack.layers.depth === 1 && pageStack.contentItem.contentWidth > pageStack.width + Kirigami.Units.gridUnit && (showNavButtons & Kirigami.ApplicationHeaderStyle.ShowForwardButton);
}
onClicked: applicationWindow().pageStack.goForward();
text: qsTr("Navigate Forward")
display: QQC2.ToolButton.IconOnly
QQC2.ToolTip {
visible: button.hovered
text: button.text
delay: Kirigami.Units.toolTipDelay
y: button.height
}
}
@@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
width: height
height: Kirigami.Units.iconSizes.smallMedium
property Kirigami.OverlayDrawer drawer
property color color: Kirigami.Theme.textColor
opacity: 0.8
layer.enabled: true
Kirigami.Icon {
selected: drawer.handle.pressed
opacity: 1 - drawer.position
anchors.fill: parent
source: drawer.handleClosedIcon.name ? drawer.handleClosedIcon.name : drawer.handleClosedIcon.source
color: drawer.handleClosedIcon.color
}
Kirigami.Icon {
selected: drawer.handle.pressed
opacity: drawer.position
anchors.fill: parent
source: drawer.handleOpenIcon.name ? drawer.handleOpenIcon.name : drawer.handleOpenIcon.source
color: drawer.handleOpenIcon.color
}
}
@@ -0,0 +1,69 @@
/*
* SPDX-FileCopyrightText: 2017 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQml
/**
* @brief Group of icon properties.
*
* This is a subset of those used in QQC2, Kirigami.Action still needs the full one as needs 100% api compatibility
*/
QtObject {
/**
* @brief This property holds icon name.
*
* The icon will be loaded from the platform theme. If the icon is found
* in the theme, it will always be used; even if icon.source is also set.
* If the icon is not found, icon.source will be used instead.
*/
property string name
/**
* @brief This property holds the icon source.
*
* The icon will be loaded as a regular image.
*
* @see QtQuick.Image::source
*/
property var source
/**
* @brief This property holds the icon tint color.
*
* The icon is tinted with the specified color, unless the color is set to "transparent".
*/
property color color: Qt.rgba(0, 0, 0, 0)
/**
* This property holds the width of the icon.
*/
property real width
/**
* This property holds the height of the icon.
*/
property real height
/**
* Bind this icon to all matching properties of a Controls icon group.
*
* This function automatically binds all properties to matching properties
* of a controls icon group, since we cannot just reuse the Controls icon
* group.
*
* To use it, you can assign the result to an IconPropertiesGroup, like so:
* `icon: icon.fromControlsIcon(control.icon)`.
*/
function fromControlsIcon(icon) {
name = Qt.binding(() => icon.name)
source = Qt.binding(() => icon.source)
color = Qt.binding(() => icon.color)
width = Qt.binding(() => icon.width)
height = Qt.binding(() => icon.height)
return this
}
}
@@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami as Kirigami
Item {
id: canvas
width: height
height: Kirigami.Units.iconSizes.smallMedium
property Kirigami.OverlayDrawer drawer
property color color: Kirigami.Theme.textColor
opacity: 0.8
layer.enabled: true
LayoutMirroring.enabled: false
LayoutMirroring.childrenInherit: true
Item {
id: iconRoot
anchors {
fill: parent
margins: Kirigami.Units.smallSpacing
}
readonly property int thickness: 2
readonly property real drawerPosition: drawer ? drawer.position : 0
Rectangle {
anchors {
right: parent.right
top: parent.top
topMargin: -iconRoot.thickness/2 * iconRoot.drawerPosition
}
antialiasing: iconRoot.drawerPosition !== 0
transformOrigin: Item.Right
width: (1 - iconRoot.drawerPosition) * parent.width + iconRoot.drawerPosition * (Math.sqrt(2*(parent.width*parent.width)))
height: iconRoot.thickness
color: canvas.color
rotation: -45 * iconRoot.drawerPosition
}
Rectangle {
anchors.centerIn: parent
width: parent.width - parent.width * iconRoot.drawerPosition
height: iconRoot.thickness
color: canvas.color
}
Rectangle {
anchors {
right: parent.right
bottom: parent.bottom
bottomMargin: -iconRoot.thickness/2 * iconRoot.drawerPosition
}
antialiasing: iconRoot.drawerPosition !== 0
transformOrigin: Item.Right
width: (1 - iconRoot.drawerPosition) * parent.width + iconRoot.drawerPosition * (Math.sqrt(2*(parent.width*parent.width)))
height: iconRoot.thickness
color: canvas.color
rotation: 45 * iconRoot.drawerPosition
}
}
}
@@ -0,0 +1,262 @@
/*
* SPDX-FileCopyrightText: 2015 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
/**
* @brief PassiveNotificationManager is meant to display small, passive and inline notifications in the app.
*
* It is used to show messages of limited importance that make sense only when
* the user is using the application and wouldn't be suited as a global
* system-wide notification.
*/
Item {
id: root
readonly property int maximumNotificationWidth: {
if (Kirigami.Settings.isMobile) {
return applicationWindow().width - Kirigami.Units.largeSpacing * 4
} else {
return Math.min(Kirigami.Units.gridUnit * 25, applicationWindow().width / 1.5)
}
}
readonly property int maximumNotificationCount: 4
function showNotification(message, timeout, actionText, callBack) {
if (!message) {
return;
}
let interval = 7000;
if (timeout === "short") {
interval = 4000;
} else if (timeout === "long") {
interval = 12000;
} else if (timeout > 0) {
interval = timeout;
}
// this wrapper is necessary because of Qt casting a function into an object
const callBackWrapperObj = callBackWrapper.createObject(listView, { callBack })
// set empty string & function for qml not to complain
notificationsModel.append({
text: message,
actionButtonText: actionText || "",
closeInterval: interval,
callBackWrapper: callBackWrapperObj
})
// remove the oldest notification if new notification count would exceed 3
if (notificationsModel.count === maximumNotificationCount) {
if (listView.itemAtIndex(0).hovered === true) {
hideNotification(1)
} else {
hideNotification()
}
}
}
/**
* @brief Remove a notification at specific index. By default, index is set to 0.
*/
function hideNotification(index = 0) {
if (index >= 0 && notificationsModel.count > index) {
const callBackWrapperObj = notificationsModel.get(index).callBackWrapper
if (callBackWrapperObj) {
callBackWrapperObj.destroy()
}
notificationsModel.remove(index)
}
}
// we have to set height to show more than one notification
height: Math.min(applicationWindow().height, Kirigami.Units.gridUnit * 10)
implicitHeight: listView.implicitHeight
implicitWidth: listView.implicitWidth
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Kirigami.Theme.Complementary
ListModel {
id: notificationsModel
}
ListView {
id: listView
anchors.fill: parent
anchors.bottomMargin: Kirigami.Units.largeSpacing
implicitWidth: root.maximumNotificationWidth
spacing: Kirigami.Units.smallSpacing
model: notificationsModel
verticalLayoutDirection: ListView.BottomToTop
keyNavigationEnabled: false
reuseItems: false // do not resue items, otherwise delegates do not hide themselves properly
focus: false
interactive: false
add: Transition {
id: addAnimation
ParallelAnimation {
alwaysRunToEnd: true
NumberAnimation {
property: "opacity"
from: 0
to: 1
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
NumberAnimation {
property: "y"
from: addAnimation.ViewTransition.destination.y - Kirigami.Units.gridUnit * 3
duration: Kirigami.Units.longDuration
easing.type: Easing.OutCubic
}
}
}
displaced: Transition {
ParallelAnimation {
alwaysRunToEnd: true
NumberAnimation {
property: "y"
duration: Kirigami.Units.longDuration
easing.type: Easing.InOutCubic
}
NumberAnimation {
property: "opacity"
duration: 0
to: 1
}
}
}
remove: Transition {
ParallelAnimation {
alwaysRunToEnd: true
NumberAnimation {
property: "opacity"
from: 1
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InCubic
}
NumberAnimation {
property: "y"
to: Kirigami.Units.gridUnit * 3
duration: Kirigami.Units.longDuration
easing.type: Easing.InCubic
}
PropertyAction {
property: "transformOrigin"
value: Item.Bottom
}
PropertyAnimation {
property: "scale"
from: 1
to: 0
duration: Kirigami.Units.longDuration
easing.type: Easing.InCubic
}
}
}
delegate: QQC2.Control {
id: delegate
hoverEnabled: true
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
width: Math.min(implicitWidth, maximumNotificationWidth)
implicitHeight: {
// HACK: contentItem.implicitHeight needs to be updated manually for some reason
void contentItem.implicitHeight;
return Math.max(implicitBackgroundHeight + topInset + bottomInset,
implicitContentHeight + topPadding + bottomPadding);
}
z: {
if (delegate.hovered) {
return 2;
} else if (delegate.index === 0) {
return 1;
} else {
return 0;
}
}
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
contentItem: RowLayout {
id: mainLayout
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
spacing: Kirigami.Units.mediumSpacing
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: eventPoint => hideNotification(index)
}
Timer {
id: timer
interval: model.closeInterval
running: !delegate.hovered
onTriggered: hideNotification(index)
}
QQC2.Label {
id: label
text: model.text
elide: Text.ElideRight
wrapMode: Text.Wrap
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
QQC2.Button {
id: actionButton
text: model.actionButtonText
visible: text.length > 0
Layout.alignment: Qt.AlignVCenter
onClicked: {
const callBack = model.callBackWrapper.callBack
hideNotification(index)
if (callBack && (typeof callBack === "function")) {
callBack();
}
}
}
}
background: Kirigami.ShadowedRectangle {
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: root.Kirigami.Theme.colorSet
shadow {
size: Kirigami.Units.gridUnit/2
color: Qt.rgba(0, 0, 0, 0.4)
yOffset: 2
}
radius: Kirigami.Units.cornerRadius
color: Kirigami.Theme.backgroundColor
opacity: 0.9
}
}
}
Component {
id: callBackWrapper
QtObject {
property var callBack
}
}
}
@@ -0,0 +1,11 @@
module org.kde.kirigami.templates.private
BackButton 1.0 BackButton.qml
BorderPropertiesGroup 1.0 BorderPropertiesGroup.qml
ContextIcon 1.0 ContextIcon.qml
DrawerHandle 1.0 DrawerHandle.qml
ForwardButton 1.0 ForwardButton.qml
GenericDrawerIcon 1.0 GenericDrawerIcon.qml
IconPropertiesGroup 1.0 IconPropertiesGroup.qml
MenuIcon 1.0 MenuIcon.qml
PassiveNotificationsManager 1.0 PassiveNotificationsManager.qml
@@ -0,0 +1,9 @@
module org.kde.kirigami.templates
AbstractApplicationHeader 2.2 AbstractApplicationHeader.qml
AbstractCard 2.4 AbstractCard.qml
singleton AppHeaderSizeGroup 2.2 SingletonHeaderSizeGroup.qml
Chip 2.19 Chip.qml
InlineMessage 2.4 InlineMessage.qml
OverlayDrawer 2.2 OverlayDrawer.qml
OverlaySheet 2.2 OverlaySheet.qml
@@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2009 Alan Alpert <alan.alpert@nokia.com>
* SPDX-FileCopyrightText: 2010 Ménard Alexis <menard@kde.org>
* SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "copyhelper.h"
#include <QClipboard>
#include <QGuiApplication>
void CopyHelperPrivate::copyTextToClipboard(const QString &text)
{
qGuiApp->clipboard()->setText(text);
}
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2009 Alan Alpert <alan.alpert@nokia.com>
* SPDX-FileCopyrightText: 2010 Ménard Alexis <menard@kde.org>
* SPDX-FileCopyrightText: 2010 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef COPYHELPER_H
#define COPYHELPER_H
#include <QObject>
#include <qqmlregistration.h>
class CopyHelperPrivate : public QObject
{
Q_OBJECT
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF
public:
Q_INVOKABLE void copyTextToClipboard(const QString &text);
};
#endif
@@ -0,0 +1,27 @@
add_library(KirigamiDelegates)
ecm_add_qml_module(KirigamiDelegates URI "org.kde.kirigami.delegates"
GENERATE_PLUGIN_SOURCE
INSTALLED_PLUGIN_TARGET KF6KirigamiDelegates
DEPENDENCIES QtQuick org.kde.kirigami.platform org.kde.kirigami.primitives
)
ecm_target_qml_sources(KirigamiDelegates SOURCES
IconTitleSubtitle.qml
TitleSubtitle.qml
SubtitleDelegate.qml
CheckSubtitleDelegate.qml
RadioSubtitleDelegate.qml
SwitchSubtitleDelegate.qml
)
set_target_properties(KirigamiDelegates PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION 6
EXPORT_NAME "KirigamiDelegates"
)
ecm_finalize_qml_module(KirigamiDelegates EXPORT KirigamiTargets)
install(TARGETS KirigamiDelegates EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami.platform as Platform
/**
* A convenience wrapper combining QtQuick Controls CheckDelegate and IconTitleSubtitle
*
* This is an intentionally minimal wrapper that replaces the CheckDelegate's
* contentItem with an IconTitleSubtitle and adds a subtitle property.
*
* If you wish to customize the layout further, create your own `CheckDelegate`
* subclass with the `contentItem:` property set to the content of your choice.
* This can include `IconTitleSubtitle` inside a Layout, for example.
*
* \note If you don't need a subtitle, use `CheckDelegate` directly.
*
* \sa Kirigami::Delegates::TitleSubtitle
* \sa Kirigami::Delegates::IconTitleSubtitle
*/
QQC2.CheckDelegate {
id: delegate
// Please see the developer note in ItemDelegate
/**
* The subtitle to display.
*/
property string subtitle
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
contentItem: IconTitleSubtitle {
icon: icon.fromControlsIcon(delegate.icon)
title: delegate.text
subtitle: delegate.subtitle
selected: delegate.highlighted || delegate.down
font: delegate.font
}
}
@@ -0,0 +1,164 @@
/*
* SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami.platform as Platform
import org.kde.kirigami.primitives as Primitives
import org.kde.kirigami.templates.private as KTP
/**
* A simple item containing an icon, title and subtitle.
*
* This is an extension of TitleSubtitle that adds an icon to the side.
* It is intended as a contentItem for ItemDelegate and related controls.
*
* When using it as a contentItem, make sure to bind the appropriate properties
* to those of the Control. Prefer binding to the Control's properties over
* setting the properties directly, as the Control's properties may affect other
* things like setting accessible names.
*
* This (and TitleSubtitle) can be combined with other controls in a layout to
* create complex content items for controls.
*
* Example usage creating a CheckDelegate with an extra button on the side:
*
* ```qml
* CheckDelegate {
* id: delegate
*
* text: "Example"
* icon.name: "document-new"
*
* contentItem: RowLayout {
* spacing: Kirigami.Units.smallSpacing
*
* Kirigami.IconTitleSubtitle {
* Layout.fillWidth: true
*
* icon: icon.fromControlsIcon(delegate.icon)
* title: delegate.text
* selected: delegate.highlighted || delegate.down
* font: delegate.font
* }
*
* Button {
* icon.name: "document-open"
* text: "Extra Action"
* }
* }
* }
* ```
*
* \sa Kirigami::Delegates::TitleSubtitle
* \sa Kirigami::Delegates::ItemDelegate
*/
Item {
id: root
/**
* @copydoc Kirigami::TitleSubtitle::title
*/
required property string title
/**
* @copydoc Kirigami::TitleSubtitle::subtitle
*/
property alias subtitle: titleSubtitle.subtitle
/**
* @copydoc Kirigami::TitleSubtitle::color
*/
property alias color: titleSubtitle.color
/**
* @copydoc Kirigami::TitleSubtitle::subtitleColor
*/
property alias subtitleColor: titleSubtitle.subtitleColor
/**
* @copydoc Kirigami::TitleSubtitle::font
*/
property alias font: titleSubtitle.font
/**
* @copydoc Kirigami::TitleSubtitle::subtitleFont
*/
property alias subtitleFont: titleSubtitle.subtitleFont
/**
* @copydoc Kirigami::TitleSubtitle::reserveSpaceForSubtitle
*/
property alias reserveSpaceForSubtitle: titleSubtitle.reserveSpaceForSubtitle
/**
* @copydoc Kirigami::TitleSubtitle::selected
*/
property alias selected: titleSubtitle.selected
/**
* @copydoc Kirigami::TitleSubtitle::elide
*/
property alias elide: titleSubtitle.elide
/**
* @copydoc Kirigami::TitleSubtitle::wrapMode
*/
property alias wrapMode: titleSubtitle.wrapMode
/**
* @copydoc Kirigami::TitleSubtitle::truncated
*/
property alias truncated: titleSubtitle.truncated
/**
* Grouped property for icon properties.
*
* \note By default, IconTitleSubtitle will reserve the space for the icon,
* even if it is not set. To remove that space, set `icon.width` to 0.
*/
property KTP.IconPropertiesGroup icon: KTP.IconPropertiesGroup {
width: titleSubtitle.subtitleVisible ? Platform.Units.iconSizes.medium : Platform.Units.iconSizes.smallMedium
height: width
}
/**
* @copydoc Kirigami::TitleSubtitle::linkActivated
*/
signal linkActivated(string link)
/**
* @copydoc Kirigami::TitleSubtitle::linkHovered
*/
signal linkHovered(string link)
implicitWidth: iconItem.implicitWidth + titleSubtitle.anchors.leftMargin + titleSubtitle.implicitWidth
implicitHeight: Math.max(iconItem.implicitHeight, titleSubtitle.implicitHeight)
Primitives.Icon {
id: iconItem
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
}
source: root.icon.name.length > 0 ? root.icon.name : root.icon.source
implicitWidth: root.icon.width
implicitHeight: root.icon.height
selected: root.selected
color: root.icon.color
}
TitleSubtitle {
id: titleSubtitle
anchors {
left: iconItem.right
leftMargin: root.icon.width > 0 ? Platform.Units.mediumSpacing : 0
top: parent.top
bottom: parent.bottom
right: parent.right
}
title: root.title
onLinkActivated: link => root.linkActivated(link)
onLinkHovered: link => root.linkHovered(link)
}
}
@@ -0,0 +1,12 @@
# Kirigami Delegates Module
This module contains custom delegates and types used to build delegates from.
# What goes here
The following criteria should be used to determine if a type belongs here:
- Custom delegate types.
- Types used to build custom delegate types.
- Types are allowed to depend on QtQuick Controls.
- Types are only allowed to depend on the Platform and Primitives submodules.
@@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami.platform as Platform
/**
* A convenience wrapper combining QtQuick Controls RadioDelegate and IconTitleSubtitle
*
* This is an intentionally minimal wrapper that replaces the RadioDelegate's
* contentItem with an IconTitleSubtitle and adds a subtitle property.
*
* If you wish to customize the layout further, create your own `RadioDelegate`
* subclass with the `contentItem:` property set to the content of your choice.
* This can include `IconTitleSubtitle` inside a Layout, for example.
*
* \note If you don't need a subtitle, use `RadioDelegate` directly.
*
* \sa Kirigami::Delegates::TitleSubtitle
* \sa Kirigami::Delegates::IconTitleSubtitle
*/
QQC2.RadioDelegate {
id: delegate
// Please see the developer note in ItemDelegate
/**
* The subtitle to display.
*/
property string subtitle
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
contentItem: IconTitleSubtitle {
icon: icon.fromControlsIcon(delegate.icon)
title: delegate.text
subtitle: delegate.subtitle
selected: delegate.highlighted || delegate.down
font: delegate.font
}
}
@@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami.platform as Platform
/**
* A convenience wrapper combining QtQuick Controls ItemDelegate and IconTitleSubtitle
*
* This is an intentionally minimal wrapper that replaces the ItemDelegate's
* contentItem with an IconTitleSubtitle and adds a subtitle property.
*
* If you wish to customize the layout further, create your own `ItemDelegate`
* subclass with the `contentItem:` property set to the content of your choice.
* This can include `IconTitleSubtitle` inside a Layout, for example.
*
* \note If you don't need a subtitle, use `ItemDelegate` directly.
*
* \sa Kirigami::Delegates::TitleSubtitle
* \sa Kirigami::Delegates::IconTitleSubtitle
*/
QQC2.ItemDelegate {
id: delegate
// Developer note: This is intentional kept incredibly minimal as we want to
// reuse as much of upstream ItemDelegate as possible, the only extra thing
// being the subtitle property. Should that ever become an upstream feature,
// these controls will be removed in favour of using upstream's implementation
// directly.
/**
* The subtitle to display.
*/
property string subtitle
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
contentItem: IconTitleSubtitle {
icon: icon.fromControlsIcon(delegate.icon)
title: delegate.text
subtitle: delegate.subtitle
selected: delegate.highlighted || delegate.down
font: delegate.font
}
}
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami.platform as Platform
/**
* A convenience wrapper combining QtQuick Controls SwitchDelegate and IconTitleSubtitle
*
* This is an intentionally minimal wrapper that replaces the SwitchDelegate's
* contentItem with an IconTitleSubtitle and adds a subtitle property.
*
* If you wish to customize the layout further, create your own `SwitchDelegate`
* subclass with the `contentItem:` property set to the content of your choice.
* This can include `IconTitleSubtitle` inside a Layout, for example.
*
* \note If you don't need a subtitle, use `SwitchDelegate` directly.
*
* \sa Kirigami::Delegates::TitleSubtitle
* \sa Kirigami::Delegates::IconTitleSubtitle
*/
QQC2.SwitchDelegate {
id: delegate
// Please see the developer note in ItemDelegate
/**
* The subtitle to display.
*/
property string subtitle
QQC2.ToolTip.text: text + (subtitle.length > 0 ? "\n\n" + subtitle : "")
QQC2.ToolTip.visible: (Platform.Settings.tabletMode ? down : hovered) && (contentItem?.truncated ?? false)
QQC2.ToolTip.delay: Platform.Units.toolTipDelay
contentItem: IconTitleSubtitle {
icon: icon.fromControlsIcon(delegate.icon)
title: delegate.text
subtitle: delegate.subtitle
selected: delegate.highlighted || delegate.down
font: delegate.font
}
}
@@ -0,0 +1,185 @@
/*
* SPDX-FileCopyrightText: 2010 Marco Martin <notmart@gmail.com>
* SPDX-FileCopyrightText: 2022 ivan tkachenko <me@ratijas.tk>
* SPDX-FileCopyrightText: 2023 Arjen Hiemstra <ahiemstra@heimr.nl>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
import QtQuick
import org.kde.kirigami.platform as Platform
/**
* A simple item containing a title and subtitle label.
*
* This is mainly intended as a replacement for a list delegate content item,
* but can be used as a replacement for other content items as well.
*
* When using it as a contentItem, make sure to bind the appropriate properties
* to those of the Control. Prefer binding to the Control's properties over
* setting the properties directly, as the Control's properties may affect other
* things like setting accessible names.
*
* Example usage as contentItem of an ItemDelegate:
*
* ```qml
* ItemDelegate {
* id: delegate
*
* text: "Example"
*
* contentItem: Kirigami.TitleSubtitle {
* title: delegate.text
* subtitle: "This is an example."
* font: delegate.font
* selected: delegate.highlighted || delegate.down
* }
* }
* ```
*
* \sa Kirigami::Delegates::IconTitleSubtitle
* \sa Kirigami::Delegates::ItemDelegate
*/
Item {
id: root
/**
* The title to display.
*/
required property string title
/**
* The subtitle to display.
*/
property string subtitle
/**
* The color to use for the title.
*
* By default this is `Kirigami.Theme.textColor` unless `selected` is true
* in which case this is `Kirigami.Theme.highlightedTextColor`.
*/
property color color: selected ? Platform.Theme.highlightedTextColor : Platform.Theme.textColor
/**
* The color to use for the subtitle.
*
* By default this is `color` mixed with the background color.
*/
property color subtitleColor: selected
? Platform.Theme.highlightedTextColor
: Platform.ColorUtils.linearInterpolation(color, Platform.Theme.backgroundColor, 0.3)
/**
* The font used to display the title.
*/
property font font: Platform.Theme.defaultFont
/**
* The font used to display the subtitle.
*/
property font subtitleFont: Platform.Theme.smallFont
/**
* The text elision mode used for both the title and subtitle.
*/
property int elide: Text.ElideRight
/**
* The text wrap mode used for both the title and subtitle.
*/
property int wrapMode: Text.NoWrap
/**
* Make the implicit height use the subtitle's height even if no subtitle is set.
*/
property bool reserveSpaceForSubtitle: false
/**
* Should this item be displayed in a selected style?
*/
property bool selected: false
/**
* Is the subtitle visible?
*/
// Note: Don't rely on subtitleItem.visible because visibility is an
// implicitly propagated property, and we don't wanna re-layout on
// hide/show events. Copy-paste its bound expression instead.
readonly property bool subtitleVisible: subtitle.length > 0 || reserveSpaceForSubtitle
/**
* Is the title or subtitle truncated?
*/
readonly property bool truncated: labelItem.truncated || subtitleItem.truncated
/**
* @brief Emitted when the user clicks on a link embedded in the text of the title or subtitle.
*/
signal linkActivated(string link)
/**
* @brief Emitted when the user hovers on a link embedded in the text of the title or subtitle.
*/
signal linkHovered(string link)
implicitWidth: Math.max(labelItem.implicitWidth, subtitleItem.implicitWidth)
implicitHeight: labelItem.implicitHeight + (subtitleVisible ? subtitleItem.implicitHeight : 0)
Text {
id: labelItem
anchors {
left: parent.left
right: parent.right
verticalCenter: parent.verticalCenter
}
// Switch off here as this is expected to be set in the base component.
Accessible.ignored: true
text: root.title
color: root.color
font: root.font
elide: root.elide
wrapMode: root.wrapMode
onLinkActivated: link => root.linkActivated(link)
onLinkHovered: link => root.linkHovered(link)
// Work around Qt bug where left aligned text is not right aligned
// in RTL mode unless horizontalAlignment is explicitly set.
// https://bugreports.qt.io/browse/QTBUG-95873
horizontalAlignment: Text.AlignLeft
// Note: Can't do this through ordinary bindings as the order between
// binding evaluation is not defined which leads to incorrect sizing or
// the QML engine complaining about not being able to anchor to null items.
states: State {
// Note: Same thing about visibility as in subtitleVisible above.
when: root.subtitle.length > 0
AnchorChanges {
target: labelItem
anchors.verticalCenter: undefined
anchors.bottom: subtitleItem.top
}
}
}
Text {
id: subtitleItem
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
text: root.subtitle
color: root.subtitleColor
font: root.subtitleFont
elide: root.elide
wrapMode: root.wrapMode
visible: text.length > 0
onLinkActivated: link => root.linkActivated(link)
onLinkHovered: link => root.linkHovered(link)
// Work around Qt bug where left aligned text is not right aligned
// in RTL mode unless horizontalAlignment is explicitly set.
// https://bugreports.qt.io/browse/QTBUG-95873
horizontalAlignment: Text.AlignLeft
renderType: Text.NativeRendering
}
}
@@ -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
}
}
}
}
}
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2016 Marco Martin <mart@kde.org>
*
* SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef ENUMS_H
#define ENUMS_H
#include <QObject>
#include <QQmlEngine>
namespace ApplicationHeaderStyle
{
Q_NAMESPACE
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
enum Status {
Auto = 0,
Breadcrumb,
Titles,
ToolBar, ///@since 5.48
None, ///@since 5.48
};
Q_ENUM_NS(Status)
enum NavigationButton {
NoNavigationButtons = 0,
ShowBackButton = 0x1,
ShowForwardButton = 0x2,
};
Q_ENUM_NS(NavigationButton)
Q_DECLARE_FLAGS(NavigationButtons, NavigationButton)
}
namespace MessageType
{
Q_NAMESPACE
QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF
enum Type {
Information = 0,
Positive,
Warning,
Error,
};
Q_ENUM_NS(Type)
};
#endif // ENUMS_H

Some files were not shown because too many files have changed in this diff Show More