v0.1.2: Preferences page, About page, full icon set, redesigned edit form
- Rework the add/edit form onto a generic, catalog-driven field model (JobFieldCatalog + JobEditModel), replacing the old hand-written ApplicationEditDialog with ApplicationEditPage - Add a Preferences page (reachable from the hamburger menu) with a KColorSchemeManager-backed color scheme switcher - Add an About page reading from KAboutData - Ship a full hicolor icon set (16-128px + scalable) via ecm_install_icons instead of a single flat SVG - Restyle the edit form after KDE System Settings' Audio page: Kirigami.ListSectionHeader per category, flat separated rows, no card borders - Explicitly declare Kirigami/KirigamiAddons/ColorScheme as CMake dependencies instead of relying on the QML import scanner alone - Add KDEClangFormat/KDEGitCommitHooks, a CMakePresets.json, and a REUSE + clang-format lint CI workflow - Reformat SPDX headers to include copyright, remove debug screenshot scaffolding
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
// Styled after KDE System Settings' Audio page (plasma-pa's kcm/ui/main.qml):
|
||||
// Kirigami.ListSectionHeader per category, flat rows indented under the
|
||||
// header and separated by Kirigami.Separator, no card/border around them.
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kitemmodels as KItemModels
|
||||
import org.kde.kirigamiaddons.dateandtime as DateTime
|
||||
import io.github.toservetheking.Kareer
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
required property JobsModel jobsModel
|
||||
property int editingJobId: -1
|
||||
|
||||
title: root.editingJobId < 0 ? i18nc("@title", "Add Application") : i18nc("@title", "Edit Application")
|
||||
|
||||
signal done()
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
// No topPadding: Kirigami.ListSectionHeader (the first thing on the
|
||||
// page) already carries its own top padding, and stacking ours on top
|
||||
// of that left an oversized gap above "Company", the first category.
|
||||
bottomPadding: Kirigami.Units.gridUnit
|
||||
|
||||
JobEditModel {
|
||||
id: editModel
|
||||
jobsModel: root.jobsModel
|
||||
editingJobId: root.editingJobId
|
||||
}
|
||||
|
||||
actions: [
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Cancel")
|
||||
icon.name: "dialog-cancel"
|
||||
onTriggered: root.done()
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Delete")
|
||||
icon.name: "edit-delete"
|
||||
visible: root.editingJobId >= 0
|
||||
onTriggered: deleteConfirmDialog.open()
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Save")
|
||||
icon.name: "document-save"
|
||||
onTriggered: {
|
||||
if (editModel.save()) {
|
||||
root.done();
|
||||
} else {
|
||||
errorLabel.text = editModel.lastError;
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.InlineMessage {
|
||||
id: errorLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
type: Kirigami.MessageType.Error
|
||||
visible: text.length > 0
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: editModel.categories
|
||||
|
||||
delegate: ColumnLayout {
|
||||
id: section
|
||||
|
||||
required property var modelData
|
||||
|
||||
Layout.fillWidth: true
|
||||
spacing: 0
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
Layout.fillWidth: true
|
||||
text: section.modelData.title
|
||||
}
|
||||
|
||||
KItemModels.KSortFilterProxyModel {
|
||||
id: catModel
|
||||
sourceModel: editModel
|
||||
filterRoleName: "categoryId"
|
||||
// No category id is a prefix of another, so this is an exact match.
|
||||
filterString: section.modelData.id
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Repeater {
|
||||
id: rowRepeater
|
||||
model: catModel
|
||||
|
||||
delegate: DelegateChooser {
|
||||
role: "rowType"
|
||||
|
||||
// Text
|
||||
DelegateChoice {
|
||||
roleValue: 0
|
||||
delegate: ColumnLayout {
|
||||
id: textD
|
||||
required property int index
|
||||
required property string label
|
||||
required property string value
|
||||
required property string placeholder
|
||||
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: textD.label
|
||||
}
|
||||
QQC2.TextField {
|
||||
Layout.fillWidth: true
|
||||
text: textD.value
|
||||
placeholderText: textD.placeholder
|
||||
onTextEdited: editModel.setValue(textD.sourceRow, text)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: textD.index !== rowRepeater.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combo box
|
||||
DelegateChoice {
|
||||
roleValue: 1
|
||||
delegate: ColumnLayout {
|
||||
id: comboD
|
||||
required property int index
|
||||
required property string label
|
||||
required property string value
|
||||
required property var comboOptions
|
||||
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: comboD.label
|
||||
}
|
||||
QQC2.ComboBox {
|
||||
Layout.fillWidth: true
|
||||
model: comboD.comboOptions
|
||||
currentIndex: comboD.comboOptions.indexOf(comboD.value)
|
||||
onActivated: editModel.setValue(comboD.sourceRow, currentText)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: comboD.index !== rowRepeater.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spin box
|
||||
DelegateChoice {
|
||||
roleValue: 2
|
||||
delegate: ColumnLayout {
|
||||
id: spinD
|
||||
required property int index
|
||||
required property string label
|
||||
required property var model
|
||||
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: spinD.label
|
||||
}
|
||||
QQC2.SpinBox {
|
||||
id: spinBox
|
||||
Layout.fillWidth: true
|
||||
from: spinD.model.spinMin
|
||||
to: spinD.model.spinMax
|
||||
stepSize: 1000
|
||||
// One-time init, not a persistent binding: this also
|
||||
// writes back on user edits, so binding "value" live to
|
||||
// spinD.model.value would be a binding loop.
|
||||
Component.onCompleted: value = spinD.model.value
|
||||
onValueChanged: editModel.setValue(spinD.sourceRow, value)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: spinD.index !== rowRepeater.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Date
|
||||
DelegateChoice {
|
||||
roleValue: 3
|
||||
delegate: ColumnLayout {
|
||||
id: dateD
|
||||
required property int index
|
||||
required property string label
|
||||
required property var value
|
||||
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: dateD.label
|
||||
}
|
||||
QQC2.Button {
|
||||
Layout.fillWidth: true
|
||||
text: Qt.formatDate(dateD.value, Qt.ISODate)
|
||||
icon.name: "view-calendar-day"
|
||||
onClicked: {
|
||||
datePopup.value = dateD.value;
|
||||
datePopup.open();
|
||||
}
|
||||
|
||||
DateTime.DatePopup {
|
||||
id: datePopup
|
||||
onAccepted: editModel.setValue(dateD.sourceRow, value)
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: dateD.index !== rowRepeater.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Text area
|
||||
DelegateChoice {
|
||||
roleValue: 4
|
||||
delegate: ColumnLayout {
|
||||
id: areaD
|
||||
required property int index
|
||||
required property string label
|
||||
required property string value
|
||||
readonly property int sourceRow: catModel.mapToSource(catModel.index(index, 0)).row
|
||||
Layout.fillWidth: true
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: areaD.label
|
||||
}
|
||||
QQC2.TextArea {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 5
|
||||
wrapMode: TextEdit.Wrap
|
||||
text: areaD.value
|
||||
onTextChanged: editModel.setValue(areaD.sourceRow, text)
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: areaD.index !== rowRepeater.count - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.PromptDialog {
|
||||
id: deleteConfirmDialog
|
||||
title: i18nc("@title", "Delete Application")
|
||||
subtitle: i18nc("@info", "Are you sure you want to delete this application? This cannot be undone.")
|
||||
standardButtons: QQC2.Dialog.Cancel
|
||||
|
||||
customFooterActions: [
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:button", "Delete")
|
||||
icon.name: "edit-delete"
|
||||
onTriggered: {
|
||||
editModel.deleteJob();
|
||||
deleteConfirmDialog.close();
|
||||
root.done();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user