diff --git a/Mainpage.dox b/Mainpage.dox
--- a/Mainpage.dox
+++ b/Mainpage.dox
@@ -37,6 +37,7 @@
- \link org::kde::kirigami::SwipeListItem SwipeListItem \endlink
- \link org::kde::kirigami::Heading Heading \endlink
- \link org::kde::kirigami::Label Label \endlink
+- \link org::kde::kirigami::PlaceholderMessage PlaceholderMessage \endlink
\section example Minimal Example
diff --git a/kirigami.qrc b/kirigami.qrc
--- a/kirigami.qrc
+++ b/kirigami.qrc
@@ -90,5 +90,6 @@
src/styles/org.kde.desktop/SwipeListItem.qml
src/styles/org.kde.desktop/ApplicationWindow.qml
src/styles/org.kde.desktop/AbstractApplicationHeader.qml
+ src/controls/PlaceholderMessage.qml
diff --git a/kirigami.qrc.in b/kirigami.qrc.in
--- a/kirigami.qrc.in
+++ b/kirigami.qrc.in
@@ -92,5 +92,6 @@
@kirigami_QML_DIR@/src/styles/org.kde.desktop/SwipeListItem.qml
@kirigami_QML_DIR@/src/styles/org.kde.desktop/ApplicationWindow.qml
@kirigami_QML_DIR@/src/styles/org.kde.desktop/AbstractApplicationHeader.qml
+ @kirigami_QML_DIR@/src/controls/PlaceholderMessage.qml
diff --git a/src/controls/PlaceholderMessage.qml b/src/controls/PlaceholderMessage.qml
new file mode 100644
--- /dev/null
+++ b/src/controls/PlaceholderMessage.qml
@@ -0,0 +1,167 @@
+/*
+ * SPDX-FileCopyrightText: 2020 by Nate Graham
+ *
+ * SPDX-License-Identifier: LGPL-2.0-or-later
+ */
+
+import QtQuick 2.0
+import QtQuick.Controls 2.4 as QQC2
+import QtQuick.Layouts 1.13
+import org.kde.kirigami 2.12 as Kirigami
+
+import "private"
+
+/**
+ * A placeholder message indicating that a list view is empty. The message
+ * comprises a label with lightened text, an optional icon above the text, and
+ * an optional button below the text which can be used to easily show the user
+ * what to do next to add content to the view.
+ *
+ * Example usage:
+ *
+ * @code{.qml}
+ * import org.kde.kirigami 2.11 as Kirigami
+ ** used as a "this view is empty" message
+ * ListView {
+ * id: listView
+ * model: [...]
+ * delegate: [...]
+ *
+ * Kirigami.PlaceholderMessage {
+ * anchors.centerIn: parent
+ * anchors.left: parent.left
+ * anchors.right: parent.right
+ *
+ * visible: listView.count == 0
+ *
+ * text: "There are no items in this list"
+ * }
+ * }
+ * @endcode
+ * @code{.qml}
+ ** Used as a "here's how to proceed" message
+ * ListView {
+ * id: listView
+ * model: [...]
+ * delegate: [...]
+ *
+ * Kirigami.PlaceholderMessage {
+ * anchors.centerIn: parent
+ * anchors.left: parent.left
+ * anchors.right: parent.right
+ *
+ * visible: listView.count == 0
+ *
+ * text: "Add an item to proceed"
+ *
+ * action: Kirigami.Action {
+ * iconName: "list-add"
+ * text: "Add item..."
+ * onTriggered: {
+ * [...]
+ * }
+ * }
+ * }
+ * [...]
+ * }
+ * @endcode
+ * @code{.qml}
+ ** Used as a "there was a problem here" message
+ * Kirigami.Page {
+ * id: root
+ * readonly property bool networkConnected: [...]
+ *
+ * Kirigami.PlaceholderMessage {
+ * anchors.centerIn: parent
+ * anchors.left: parent.left
+ * anchors.right: parent.right
+ *
+ * visible: root.networkConnected
+ *
+ * icon: "network-disconnect"
+ * text: "Network disconnected; unable to load content"
+ * }
+ * }
+ * @endcode
+ * @since 2.12
+ */
+ColumnLayout {
+ id: root
+
+ /**
+ * text: string
+ * The text to show as a placeholder label
+ *
+ * Mandatory; no default value.
+ *
+ * @since 5.70
+ */
+ property alias text: label.text
+
+ /**
+ * icon: QVariant
+ * The icon to show above the text label. Accepts "icon.name" and
+ * "icon.source"
+ *
+ * Optional; if undefined, the message will have no icon.
+ * Falls back to `undefined` if the specified icon is not valid or cannot
+ * be loaded.
+ *
+ * @since 5.70
+ * @see Icon::source
+ */
+ property ActionIconGroup icon: ActionIconGroup {}
+
+ /**
+ * helperAction: QtQuickControls2 Action
+ * 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.
+ *
+ * @since 5.70
+ */
+ property alias helpfulAction: actionButton.action
+
+ spacing: units.largeSpacing
+
+ Kirigami.Icon {
+
+ Layout.alignment: Qt.AlignHCenter
+ Layout.preferredWidth: Kirigami.Units.iconSizes.huge
+ Layout.preferredHeight: Kirigami.Units.iconSizes.huge
+
+ source: {
+ if (root.icon.source && root.icon.source.length > 0) {
+ return root.icon.source
+ } else if (root.icon.name && root.icon.name.length > 0) {
+ return root.icon.name
+ }
+ return undefined
+ }
+
+ visible: source != undefined
+ opacity: 0.5
+ }
+
+ Kirigami.Heading {
+ id: label
+
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignHCenter
+ horizontalAlignment: Qt.AlignHCenter
+
+ level: 2
+ opacity: 0.5
+
+ wrapMode: Text.WordWrap
+ }
+
+ QQC2.Button {
+ id: actionButton
+
+ Layout.alignment: Qt.AlignHCenter
+
+ visible: action && action.visible
+ }
+}
diff --git a/src/controls/plugins.qmltypes b/src/controls/plugins.qmltypes
--- a/src/controls/plugins.qmltypes
+++ b/src/controls/plugins.qmltypes
@@ -549,6 +549,17 @@
Property { name: "containsMouse"; type: "bool"; isReadonly: true }
Property { name: "_default"; type: "QQuickItem"; isPointer: true }
}
+ Component {
+ prototype: "QQuickItem"
+ name: "org.kde.kirigami/PlaceholderMessage 2.12"
+ exports: ["org.kde.kirigami/PlaceholderMessage 2.12"]
+ exportMetaObjectRevisions: [0]
+ isComposite: true
+ defaultProperty: "data"
+ Property { name: "text"; type: "string" }
+ Property { name: "icon"; type: "string" }
+ Property { name: "fadedIcon"; type: "bool" }
+ }
Component {
prototype: "QQuickItem"
name: "QtQuick.Controls.Material.impl/BoxShadow 2.0"
diff --git a/src/kirigamiplugin.cpp b/src/kirigamiplugin.cpp
--- a/src/kirigamiplugin.cpp
+++ b/src/kirigamiplugin.cpp
@@ -243,6 +243,7 @@
qmlRegisterType(uri, 2, 12, "ShadowedRectangle");
qmlRegisterType(uri, 2, 12, "ShadowedTexture");
qmlRegisterType(componentUrl(QStringLiteral("ShadowedImage.qml")), uri, 2, 12, "ShadowedImage");
+ qmlRegisterType(componentUrl(QStringLiteral("PlaceholderMessage.qml")), uri, 2, 12, "PlaceholderMessage");
qmlRegisterUncreatableType(uri, 2, 12, "BorderGroup", QStringLiteral("Used as grouped property"));
qmlRegisterUncreatableType(uri, 2, 12, "ShadowGroup", QStringLiteral("Used as grouped property"));
diff --git a/src/qmldir b/src/qmldir
--- a/src/qmldir
+++ b/src/qmldir
@@ -51,3 +51,4 @@
SearchField 2.8 SearchField.qml
PasswordField 2.8 PasswordField.qml
ListSectionHeader 2.10 ListSectionHeader.qml
+PlaceholderMessage 2.12 PlaceholderMessage.qml