[frameworks/kirigami] src/controls: Port AboutItem to the new form layout

Marco Martin <[email protected]> Wed, 5 Aug 2026 09:08:21 +0000 (UTC)
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit e0ed0530bed792e9879026b882f8f89ee3d2cb60 by Marco Martin.
Committed on 04/08/2026 at 08:33.
Pushed by mart into branch 'master'.

Port AboutItem to the new form layout

M  +204  -237  src/controls/AboutItem.qml

https://invent.kde.org/frameworks/kirigami/-/commit/e0ed0530bed792e9879026b882f8f89ee3d2cb60

diff --git a/src/controls/AboutItem.qml b/src/controls/AboutItem.qml
index 310fe6318..197aa36c5 100644
--- a/src/controls/AboutItem.qml
+++ b/src/controls/AboutItem.qml
@@ -12,6 +12,7 @@ import QtQuick.Layouts
 import org.kde.kirigami.platform as Platform
 import org.kde.kirigami.primitives as Primitives
 import org.kde.kirigami.layouts as KL
+import org.kde.kirigami.forms as KF
 import org.kde.kirigami.controls as KC
 
 //TODO: Kf6: move somewhere else which can depend from KAboutData?
@@ -87,9 +88,9 @@ Item {
 
     /*!
        \qmlproperty bool wideMode
-       \sa FormLayout::wideMode
+       Deprecated: this property has no effect
      */
-    property alias wideMode: form.wideMode
+    property bool wideMode: false
 
     default property alias _content: form.data
 
@@ -117,7 +118,7 @@ Item {
     Component {
         id: personDelegate
 
-        RowLayout {
+        KF.FormEntry {
             id: delegate
 
             // type: KAboutPerson | { name?, task?, emailAddress?, webAddress?, avatarUrl? }
@@ -125,262 +126,217 @@ Item {
 
             property bool hasAvatar: aboutItem.__hasAvatar(modelData)
 
-            Layout.fillWidth: true
-
-            spacing: Platform.Units.smallSpacing * 2
-
-            Primitives.Icon {
-                id: avatarIcon
 
+            leadingItems: Item {
                 implicitWidth: Platform.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(delegate.modelData.avatarUrl);
-                        const params = new URLSearchParams(url.search);
-                        params.append("s", width);
-                        url.search = params.toString();
-                        return url;
-                    } else {
-                        return "user"
+                Primitives.Icon {
+                    id: avatarIcon
+
+                    anchors.fill: parent
+
+                    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(delegate.modelData.avatarUrl);
+                            const params = new URLSearchParams(url.search);
+                            params.append("s", width);
+                            url.search = params.toString();
+                            return url;
+                        } else {
+                            return "user"
+                        }
                     }
+                    visible: status !== Primitives.Icon.Loading
                 }
-                visible: status !== Primitives.Icon.Loading
-            }
 
-            // So it's clear that something is happening while avatar images are loaded
-            QQC2.BusyIndicator {
-                implicitWidth: Platform.Units.iconSizes.medium
-                implicitHeight: implicitWidth
+                // So it's clear that something is happening while avatar images are loaded
+                QQC2.BusyIndicator {
+                    anchors.centerIn: parent
+                    implicitWidth: Platform.Units.iconSizes.medium
+                    implicitHeight: implicitWidth
 
-                visible: avatarIcon.status === Primitives.Icon.Loading
-                running: visible
+                    visible: avatarIcon.status === Primitives.Icon.Loading
+                    running: visible
+                }
             }
 
-            QQC2.Label {
-                Layout.fillWidth: true
+            contentItem: QQC2.Label {
                 readonly property bool withTask: typeof(delegate.modelData.task) !== "undefined" && delegate.modelData.task.length > 0
-                text: withTask ? qsTr("%1 (%2)").arg(delegate.modelData.name).arg(delegate.modelData.task) : delegate.modelData.name
+                text: delegate.modelData.name
                 wrapMode: Text.WordWrap
             }
 
-            QQC2.ToolButton {
-                enabled: typeof(delegate.modelData.webAddress) !== "undefined" && delegate.modelData.webAddress.length > 0
-                opacity: enabled ? 1 : 0
-                icon.name: "globe"
-                QQC2.ToolTip.delay: Platform.Units.toolTipDelay
-                QQC2.ToolTip.visible: hovered
-                QQC2.ToolTip.text: (typeof(delegate.modelData.webAddress) === "undefined" && delegate.modelData.webAddress.length > 0) ? "" : delegate.modelData.webAddress
-                onClicked: Qt.openUrlExternally(delegate.modelData.webAddress)
-            }
-
-            QQC2.ToolButton {
-                enabled: typeof(delegate.modelData.emailAddress) !== "undefined" && delegate.modelData.emailAddress.length > 0
-                opacity: enabled ? 1 : 0
-                icon.name: "mail-sent"
-                QQC2.ToolTip.delay: Platform.Units.toolTipDelay
-                QQC2.ToolTip.visible: hovered
-                QQC2.ToolTip.text: qsTr("Send an email to %1").arg(delegate.modelData.emailAddress)
-                onClicked: Qt.openUrlExternally("mailto:%1".arg(delegate.modelData.emailAddress))
-            }
+            subtitle: delegate.modelData.task
+
+            trailingItems: [
+                QQC2.ToolButton {
+                    visible: typeof(delegate.modelData.ocsUsername) !== "undefined" && modelData.ocsUsername.length > 0
+                    icon.name: "get-hot-new-stuff-symbolic"
+                    QQC2.ToolTip.delay: Platform.Units.toolTipDelay
+                    QQC2.ToolTip.visible: hovered
+                    QQC2.ToolTip.text: qsTr("Visit %1's KDE Store page").arg(modelData.name)
+                    onClicked: Qt.openUrlExternally("https://store.kde.org/u/%1".arg(modelData.ocsUsername))
+                },
+                QQC2.ToolButton {
+                    visible: typeof(delegate.modelData.webAddress) !== "undefined" && delegate.modelData.webAddress.length > 0
+                    icon.name: "globe"
+                    QQC2.ToolTip.delay: Platform.Units.toolTipDelay
+                    QQC2.ToolTip.visible: hovered
+                    QQC2.ToolTip.text: (typeof(delegate.modelData.webAddress) === "undefined" && delegate.modelData.webAddress.length > 0) ? "" : delegate.modelData.webAddress
+                    onClicked: Qt.openUrlExternally(delegate.modelData.webAddress)
+                },
+                QQC2.ToolButton {
+                    visible: typeof(delegate.modelData.emailAddress) !== "undefined" && delegate.modelData.emailAddress.length > 0
+                    icon.name: "mail-sent"
+                    QQC2.ToolTip.delay: Platform.Units.toolTipDelay
+                    QQC2.ToolTip.visible: hovered
+                    QQC2.ToolTip.text: qsTr("Send an email to %1").arg(delegate.modelData.emailAddress)
+                    onClicked: Qt.openUrlExternally("mailto:%1".arg(delegate.modelData.emailAddress))
+                }
+            ]
         }
     }
 
-    KL.FormLayout {
+    KF.Form {
         id: form
 
         anchors.fill: parent
 
-        GridLayout {
-            columns: 2
-            Layout.fillWidth: true
-
-            Primitives.Icon {
-                Layout.rowSpan: 3
-                Layout.preferredHeight: Platform.Units.iconSizes.huge
-                Layout.preferredWidth: height
-                Layout.maximumWidth: aboutItem.width / 3;
-                Layout.rightMargin: Platform.Units.largeSpacing
-                source: aboutItem.aboutData.programLogo || Platform.Settings.applicationWindowIcon || aboutItem.aboutData.componentName
+        KF.FormGroup {
+            KF.FormEntry {
+                contentItem: ColumnLayout {
+                    KC.Heading {
+                        Layout.fillWidth: true
+                        text: aboutItem.aboutData.displayName + " " + aboutItem.aboutData.version
+                    }
+                    KC.Heading {
+                        Layout.fillWidth: true
+                        level: 3
+                        type: KC.Heading.Type.Secondary
+                        wrapMode: Text.WordWrap
+                        text: aboutItem.aboutData.shortDescription
+                    }
+                }
+                leadingItems: Primitives.Icon {
+                    Layout.preferredHeight: Platform.Units.iconSizes.huge
+                    Layout.preferredWidth: height
+                    Layout.maximumWidth: aboutItem.width / 3;
+                    Layout.rightMargin: Platform.Units.largeSpacing
+                    source: aboutItem.aboutData.programLogo || Platform.Settings.applicationWindowIcon || aboutItem.aboutData.componentName
+                }
             }
-
-            KC.Heading {
-                Layout.fillWidth: true
-                text: aboutItem.aboutData.displayName + " " + aboutItem.aboutData.version
-                wrapMode: Text.WordWrap
+            KF.FormSeparator {}
+            KF.FormEntry {
+                contentItem: QQC2.Label {
+                    text: qsTr("Copyright")
+                }
+                // FIXME
+                subtitle: aboutItem.aboutData.copyrightStatement
+                visible: subtitle.length > 0
             }
+        }
 
-            KC.Heading {
-                Layout.fillWidth: true
-                level: 3
-                type: KC.Heading.Type.Secondary
-                wrapMode: Text.WordWrap
-                text: aboutItem.aboutData.shortDescription
+        KF.FormGroup {
+            title: qsTr("License")
+            Repeater {
+                model: aboutItem.aboutData.licenses
+                delegate: KF.FormAction {
+                    id: licenseLinkButton
+                    required property var modelData
+                    action: KC.Action {
+                        text: licenseLinkButton.modelData.name
+                        onTriggered: {
+                            licenseSheet.text = licenseLinkButton.modelData.text
+                            licenseSheet.title = licenseLinkButton.modelData.name
+                            licenseSheet.open()
+                        }
+                    }
+                }
             }
+        }
 
-            RowLayout {
-                spacing: Platform.Units.largeSpacing * 2
-
-                UrlButton {
-                    text: qsTr("Get Involved")
-                    url: aboutItem.getInvolvedUrl
-                    visible: url.toString().length > 0
+        KF.FormGroup {
+            KF.FormAction {
+                action: KC.Action {
+                    icon.name: "globe-symbolic"
+                    text: qsTr("Homepage")
+                    onTriggered: {
+                        Qt.openUrlExternally(aboutData.homepage)
+                    }
                 }
-
-                UrlButton {
+                triggerIcon.name: "open-link-symbolic"
+                visible: aboutData.homepage.toString().length > 0
+            }
+            KF.FormSeparator {
+                visible: aboutData.homepage.toString().length > 0
+            }
+            KF.FormAction {
+                action: KC.Action {
+                    icon.name: "donate-symbolic"
                     text: qsTr("Donate")
-                    url: aboutItem.donateUrl
-                    visible: url.toString().length > 0
+                    onTriggered: {
+                        Qt.openUrlExternally(donateUrl + "?app=" + page.aboutData.componentName)
+                    }
                 }
-
-                UrlButton {
-                    readonly property string theUrl: {
-                        if (aboutItem.aboutData.bugAddress !== "[email protected]") {
-                            return aboutItem.aboutData.bugAddress
+                triggerIcon.name: "open-link-symbolic"
+                visible: aboutItem.donateUrl.toString().length > 0
+            }
+            KF.FormSeparator {
+                visible: aboutItem.donateUrl.toString().length > 0
+            }
+            KF.FormAction {
+                action: KC.Action {
+                    icon.name: "applications-development-symbolic"
+                    text: qsTr("Get Involved")
+                    onTriggered: {
+                        Qt.openUrlExternally(getInvolvedUrl)
+                    }
+                }
+                triggerIcon.name: "open-link-symbolic"
+                visible: aboutItem.getInvolvedUrl.toString().length > 0
+            }
+            KF.FormSeparator {
+                visible: aboutItem.getInvolvedUrl.toString().length > 0
+            }
+            KF.FormAction {
+                action: KC.Action {
+                    icon.name: "tools-report-bug-symbolic"
+                    text: qsTr("Report a bug")
+                    onTriggered: {
+                        if (aboutData.bugAddress !== "[email protected]") {
+                            Qt.openUrlExternally(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}`;
+                        const elements = aboutData.productName.split('/');
+                        let url = `https://bugs.kde.org/enter_bug.cgi?format=guided&product=${elements[0]}&version=${aboutData.version}`;
                         if (elements.length === 2) {
                             url += "&component=" + elements[1];
                         }
-                        return url;
+                        Qt.openUrlExternally(url)
                     }
-                    text: qsTr("Report a Bug")
-                    url: theUrl
-                    visible: theUrl.toString().length > 0
                 }
+                triggerIcon.name: "open-link-symbolic"
             }
         }
 
-        Primitives.Separator {
-            Layout.fillWidth: true
-        }
-
-        KC.Heading {
-            KL.FormData.isSection: true
-            text: qsTr("Copyright")
-        }
-
-        QQC2.Label {
-            Layout.leftMargin: Platform.Units.gridUnit
-            text: aboutItem.aboutData.otherText
-            visible: text.length > 0
-            wrapMode: Text.WordWrap
-            Layout.fillWidth: true
-        }
-
-        QQC2.Label {
-            Layout.leftMargin: Platform.Units.gridUnit
-            text: aboutItem.aboutData.copyrightStatement
-            visible: text.length > 0
-            wrapMode: Text.WordWrap
-            Layout.fillWidth: true
-        }
-
-        UrlButton {
-            Layout.leftMargin: Platform.Units.gridUnit
-            url: aboutItem.aboutData.homepage
-            visible: url.length > 0
-            wrapMode: Text.Wrap
-            Layout.fillWidth: true
-            Layout.maximumWidth: aboutItem.width
-        }
-
-        OverlaySheet {
-            id: licenseSheet
-            property alias text: bodyLabel.text
-
-            SelectableLabel {
-                id: bodyLabel
-                text: licenseSheet.text
-                wrapMode: Text.Wrap
-            }
-        }
-
-        Component {
-            id: licenseLinkButton
-
-            RowLayout {
-                id: licenseLinkLayout
-
-                required property var modelData
-
-                Layout.leftMargin: Platform.Units.smallSpacing
-
-                QQC2.Label { text: qsTr("License:") }
-
-                LinkButton {
-                    Layout.fillWidth: true
-                    wrapMode: Text.WordWrap
-                    text: licenseLinkLayout.modelData.name
-                    onClicked: mouse => {
-                        licenseSheet.text = licenseLinkLayout.modelData.text
-                        licenseSheet.title = licenseLinkLayout.modelData.name
-                        licenseSheet.open()
+        KF.FormGroup {
+            title: qsTr("Libraries in use")
+            Repeater {
+                model: Platform.Settings.information
+                delegate: KF.FormEntry {
+                    id: delegate
+                    required property string modelData
+                    contentItem:  QQC2.Label {
+                        wrapMode: Text.WordWrap
+                        id: libraries
+                        text: delegate.modelData
                     }
                 }
             }
         }
 
-        Component {
-            id: licenseTextItem
-
-            QQC2.Label {
-                required property var modelData
-                Layout.leftMargin: Platform.Units.smallSpacing
-                Layout.fillWidth: true
-                wrapMode: Text.WordWrap
-                text: qsTr("License: %1").arg(modelData.name)
-            }
-        }
-
-        Repeater {
-            model: aboutItem.aboutData.licenses
-            delegate: aboutItem._usePageStack ? licenseLinkButton : licenseTextItem
-        }
-
-        KC.Heading {
-            KL.FormData.isSection: visible
-            text: qsTr("Libraries in use")
-            Layout.fillWidth: true
-            wrapMode: Text.WordWrap
-            visible: Platform.Settings.information
-        }
-
-        Repeater {
-            model: Platform.Settings.information
-            delegate: QQC2.Label {
-                required property string modelData
-                Layout.leftMargin: Platform.Units.gridUnit
-                Layout.fillWidth: true
-                wrapMode: Text.WordWrap
-                id: libraries
-                text: modelData
-            }
-        }
-
-        Repeater {
-            model: aboutItem.aboutData.components
-            delegate: QQC2.Label {
-                required property var modelData
-                Layout.fillWidth: true
-                wrapMode: Text.WordWrap
-                Layout.leftMargin: Platform.Units.gridUnit
-                text: modelData.name + (modelData.version.length === 0 ? "" : " %1".arg(modelData.version))
-            }
-        }
-
-        KC.Heading {
-            Layout.fillWidth: true
-            KL.FormData.isSection: visible
-            text: qsTr("Authors")
-            wrapMode: Text.WordWrap
-            visible: aboutItem.aboutData.authors.length > 0
-        }
-
         QQC2.CheckBox {
             id: remoteAvatars
             visible: aboutItem.__hasAvatars
@@ -388,35 +344,46 @@ Item {
             onToggled: aboutItem.loadAvatars = checked
             text: qsTr("Show author photos")
         }
-
-        Repeater {
-            id: authorsRepeater
-            model: aboutItem.aboutData.authors
-            delegate: personDelegate
+        KF.FormGroup {
+            title: qsTr("Authors")
+            visible: repAuthors.count > 0
+            Repeater {
+                id: repAuthors
+                model: aboutItem.aboutData.authors
+                delegate: personDelegate
+            }
         }
 
-        KC.Heading {
-            KL.FormData.isSection: visible
-            text: qsTr("Credits")
+        KF.FormGroup {
+            title: qsTr("Credits")
             visible: repCredits.count > 0
+            Repeater {
+                id: repCredits
+                model: aboutItem.aboutData.credits
+                delegate: personDelegate
+            }
         }
 
-        Repeater {
-            id: repCredits
-            model: aboutItem.aboutData.credits
-            delegate: personDelegate
-        }
-
-        KC.Heading {
-            KL.FormData.isSection: visible
-            text: qsTr("Translators")
+        KF.FormGroup {
+            title: qsTr("Translators")
             visible: repTranslators.count > 0
+            Repeater {
+                id: repTranslators
+                model: aboutItem.aboutData.translators
+                delegate: personDelegate
+            }
         }
 
-        Repeater {
-            id: repTranslators
-            model: aboutItem.aboutData.translators
-            delegate: personDelegate
+        OverlaySheet {
+            id: licenseSheet
+            width: Math.min(aboutItem.width - Platform.Units.gridUnit * 2, bodyLabel.implicitWidth)
+            property alias text: bodyLabel.text
+
+            SelectableLabel {
+                id: bodyLabel
+                text: licenseSheet.text
+                wrapMode: Text.Wrap
+            }
         }
     }
 }