Files
NowPlaying/package/contents/ui/configGeneral.qml

435 lines
13 KiB
QML

import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Dialogs as Dialogs
import QtQuick.Layouts
import org.kde.kcmutils as KCM
import org.kde.kirigami as Kirigami
import org.kde.plasma.private.mpris as Mpris
KCM.SimpleKCM {
id: configRoot
property alias cfg_opacity: opacitySpinBox.value
property string cfg_fontFamily
property alias cfg_labelFontSize: labelFontSizeSpinBox.value
property alias cfg_titleFontSize: titleFontSizeSpinBox.value
property alias cfg_artistFontSize: artistFontSizeSpinBox.value
property string cfg_labelVisibilityMode
property string cfg_labelPlacement
property alias cfg_labelText: labelTextArea.text
property alias cfg_useLabelArtwork: useLabelArtworkCheckBox.checked
property alias cfg_imageBorderRadius: imageBorderRadiusSpinBox.value
property string cfg_backgroundStyle
property alias cfg_backgroundColor: backgroundColorField.text
property alias cfg_backgroundRadius: backgroundRadiusSpinBox.value
property alias cfg_foregroundColor: foregroundColorField.text
property alias cfg_textShadowEnabled: textShadowCheckBox.checked
property alias cfg_showMediaControls: showMediaControlsCheckBox.checked
property alias cfg_sourcePriority: sourcePriorityField.text
property alias cfg_sourceWhitelist: sourceWhitelistCheckBox.checked
property alias cfg_trackTextVerticalSpacing: trackTextVerticalSpacingSpinBox.value
property alias cfg_labelVerticalSpacing: labelVerticalSpacingSpinBox.value
property alias cfg_separatorGapLabel: separatorGapLabelSpinBox.value
property alias cfg_separatorGapTrack: separatorGapTrackSpinBox.value
property alias cfg_separatorHeight: separatorHeightSpinBox.value
property alias cfg_hideSeparator: hideSeparatorCheckBox.checked
readonly property int containerRole: Qt.UserRole + 1
readonly property var availableFonts: Qt.fontFamilies()
property var detectedSources: []
readonly property var labelVisibilityOptions: [{
"text": i18n("Auto hide when idle"),
"value": "auto"
}, {
"text": i18n("Always show"),
"value": "always"
}, {
"text": i18n("Always hide"),
"value": "never"
}]
readonly property var labelPlacementOptions: [{
"text": i18n("Left"),
"value": "left"
}, {
"text": i18n("Right"),
"value": "right"
}]
readonly property var backgroundStyleOptions: [{
"text": i18n("Default"),
"value": "custom"
}, {
"text": i18n("Solid background"),
"value": "default"
}]
readonly property bool hasSourcePriorityEntries: sourcePriorityField.text.split(",").some((entry) => {
return normalizeDesktopEntry(entry).length > 0;
})
function syncComboByValue(comboBox, options, configuredValue, fallbackValue) {
const requestedValue = configuredValue || fallbackValue;
const requestedIndex = options.findIndex((option) => {
return option.value === requestedValue;
});
const fallbackIndex = options.findIndex((option) => {
return option.value === fallbackValue;
});
const resolvedIndex = requestedIndex >= 0 ? requestedIndex : (fallbackIndex >= 0 ? fallbackIndex : 0);
if (comboBox.currentIndex !== resolvedIndex)
comboBox.currentIndex = resolvedIndex;
return options[resolvedIndex].value;
}
function normalizeDesktopEntry(value) {
return (value || "").toString().trim();
}
function refreshDetectedSources() {
const sources = [];
const seen = {
};
const playerCount = detectedSourcesModel.rowCount();
for (let row = 0; row < playerCount; ++row) {
const player = detectedSourcesModel.data(detectedSourcesModel.index(row, 0), containerRole);
const desktopEntry = normalizeDesktopEntry(player ? player.desktopEntry : "");
const desktopEntryKey = desktopEntry.toLowerCase();
if (!desktopEntry || seen[desktopEntryKey])
continue;
seen[desktopEntryKey] = true;
sources.push(desktopEntry);
}
detectedSources = sources;
}
Component.onCompleted: cfg_sourceWhitelist = hasSourcePriorityEntries && cfg_sourceWhitelist
onCfg_fontFamilyChanged: {
const index = availableFonts.indexOf(cfg_fontFamily);
if (index >= 0 && fontFamilyComboBox.currentIndex !== index)
fontFamilyComboBox.currentIndex = index;
}
onCfg_labelVisibilityModeChanged: {
const resolvedValue = syncComboByValue(labelVisibilityModeComboBox, labelVisibilityOptions, cfg_labelVisibilityMode, "auto");
if (cfg_labelVisibilityMode !== resolvedValue)
cfg_labelVisibilityMode = resolvedValue;
}
onCfg_labelPlacementChanged: {
const resolvedValue = syncComboByValue(labelPlacementComboBox, labelPlacementOptions, cfg_labelPlacement, "left");
if (cfg_labelPlacement !== resolvedValue)
cfg_labelPlacement = resolvedValue;
}
onCfg_backgroundStyleChanged: {
const resolvedValue = syncComboByValue(backgroundStyleComboBox, backgroundStyleOptions, cfg_backgroundStyle, "custom");
if (cfg_backgroundStyle !== resolvedValue)
cfg_backgroundStyle = resolvedValue;
}
Kirigami.FormLayout {
SectionHeader {
title: i18n("Global")
}
QQC2.SpinBox {
id: opacitySpinBox
Kirigami.FormData.label: i18n("Opacity percent:")
from: 0
to: 100
}
QQC2.ComboBox {
id: fontFamilyComboBox
Kirigami.FormData.label: i18n("Font:")
model: configRoot.availableFonts
onActivated: configRoot.cfg_fontFamily = currentText
}
QQC2.ComboBox {
id: backgroundStyleComboBox
Kirigami.FormData.label: i18n("Background style:")
model: configRoot.backgroundStyleOptions
textRole: "text"
valueRole: "value"
onActivated: configRoot.cfg_backgroundStyle = currentValue
}
RowLayout {
Kirigami.FormData.label: i18n("Background color:")
enabled: configRoot.cfg_backgroundStyle === "custom"
QQC2.TextField {
id: backgroundColorField
Layout.fillWidth: true
placeholderText: i18n("transparent or #AARRGGBB")
}
QQC2.Button {
text: i18n("Pick...")
onClicked: {
backgroundColorDialog.selectedColor = backgroundColorField.text || "transparent";
backgroundColorDialog.open();
}
}
}
RowLayout {
Kirigami.FormData.label: i18n("Foreground color:")
QQC2.TextField {
id: foregroundColorField
Layout.fillWidth: true
placeholderText: i18n("white or #RRGGBB")
}
QQC2.Button {
text: i18n("Pick...")
onClicked: {
foregroundColorDialog.selectedColor = foregroundColorField.text || "white";
foregroundColorDialog.open();
}
}
}
QQC2.SpinBox {
id: backgroundRadiusSpinBox
Kirigami.FormData.label: i18n("Background radius:")
enabled: configRoot.cfg_backgroundStyle === "custom"
from: 0
to: 100
}
QQC2.CheckBox {
id: textShadowCheckBox
Kirigami.FormData.label: i18n("Text shadow:")
text: i18n("Enable")
}
QQC2.CheckBox {
id: showMediaControlsCheckBox
Kirigami.FormData.label: i18n("Media controls:")
text: i18n("Show media controls")
}
QQC2.TextField {
id: sourcePriorityField
Kirigami.FormData.label: i18n("Source priority:")
Layout.fillWidth: true
placeholderText: i18n("example: spotify, firefox")
onTextChanged: configRoot.cfg_sourceWhitelist = configRoot.hasSourcePriorityEntries && configRoot.cfg_sourceWhitelist
}
QQC2.CheckBox {
id: sourceWhitelistCheckBox
Kirigami.FormData.label: ""
text: i18n("Only use listed sources")
enabled: configRoot.hasSourcePriorityEntries
}
QQC2.Label {
id: detectedSourcesField
Kirigami.FormData.label: i18n("Current sources being detected:")
Layout.fillWidth: true
Layout.preferredHeight: Math.max(implicitHeight, Kirigami.Units.gridUnit * 2)
textFormat: TextEdit.PlainText
wrapMode: TextEdit.WrapAnywhere
text: configRoot.detectedSources.length > 0 ? configRoot.detectedSources.join("\n") : i18n("No active MPRIS desktopEntry values detected")
}
QQC2.SpinBox {
id: separatorHeightSpinBox
Kirigami.FormData.label: i18n("Separator height (%):")
from: 0
to: 100
}
QQC2.CheckBox {
id: hideSeparatorCheckBox
Kirigami.FormData.label: i18n("Separator:")
text: i18n("Hide separator")
}
SectionHeader {
title: i18n("Label")
}
QQC2.ComboBox {
id: labelVisibilityModeComboBox
Kirigami.FormData.label: i18n("Now Playing labels:")
model: configRoot.labelVisibilityOptions
textRole: "text"
valueRole: "value"
onActivated: configRoot.cfg_labelVisibilityMode = currentValue
}
QQC2.ComboBox {
id: labelPlacementComboBox
Kirigami.FormData.label: i18n("Label placement:")
model: configRoot.labelPlacementOptions
textRole: "text"
valueRole: "value"
onActivated: configRoot.cfg_labelPlacement = currentValue
}
QQC2.CheckBox {
id: useLabelArtworkCheckBox
Kirigami.FormData.label: i18n("Label image:")
text: i18n("Show track image when available")
}
QQC2.SpinBox {
id: imageBorderRadiusSpinBox
Kirigami.FormData.label: i18n("Image border radius:")
from: 0
to: 100
}
QQC2.TextArea {
id: labelTextArea
Kirigami.FormData.label: i18n("Label text:")
Layout.fillWidth: true
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
placeholderText: i18n("Use a line break for multiple lines")
wrapMode: TextEdit.NoWrap
}
QQC2.SpinBox {
id: labelFontSizeSpinBox
Kirigami.FormData.label: i18n("Label font size:")
from: 8
to: 72
}
QQC2.SpinBox {
id: labelVerticalSpacingSpinBox
Kirigami.FormData.label: i18n("Label line spacing (negatives possible):")
from: -200
to: 200
}
QQC2.SpinBox {
id: separatorGapLabelSpinBox
Kirigami.FormData.label: i18n("Separator gap from label:")
from: 0
to: 24
}
SectionHeader {
title: i18n("Track & Artist")
}
QQC2.SpinBox {
id: titleFontSizeSpinBox
Kirigami.FormData.label: i18n("Title font size:")
from: 8
to: 96
}
QQC2.SpinBox {
id: artistFontSizeSpinBox
Kirigami.FormData.label: i18n("Artist font size:")
from: 8
to: 96
}
QQC2.SpinBox {
id: trackTextVerticalSpacingSpinBox
Kirigami.FormData.label: i18n("Title/artist spacing (negatives possible):")
from: -200
to: 200
}
QQC2.SpinBox {
id: separatorGapTrackSpinBox
Kirigami.FormData.label: i18n("Separator gap from track:")
from: 0
to: 24
}
}
Dialogs.ColorDialog {
id: backgroundColorDialog
title: i18n("Select background color")
options: Dialogs.ColorDialog.ShowAlphaChannel
onAccepted: configRoot.cfg_backgroundColor = selectedColor.toString()
}
Dialogs.ColorDialog {
id: foregroundColorDialog
title: i18n("Select foreground color")
options: Dialogs.ColorDialog.ShowAlphaChannel
onAccepted: configRoot.cfg_foregroundColor = selectedColor.toString()
}
Mpris.Mpris2Model {
id: detectedSourcesModel
onRowsInserted: configRoot.refreshDetectedSources()
onRowsRemoved: configRoot.refreshDetectedSources()
onModelReset: configRoot.refreshDetectedSources()
onDataChanged: configRoot.refreshDetectedSources()
Component.onCompleted: configRoot.refreshDetectedSources()
}
component SectionHeader: Item {
required property string title
Kirigami.FormData.isSection: true
implicitHeight: sectionLayout.implicitHeight
ColumnLayout {
id: sectionLayout
anchors.left: parent.left
anchors.right: parent.right
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
text: parent.parent.title
font.bold: true
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: Kirigami.Theme.textColor
opacity: 0.25
}
}
}
}