lumina/src/qml/presenter/SongEditor.qml
2024-09-17 10:07:36 -05:00

654 lines
24 KiB
QML

import QtQuick 2.13
import QtQuick.Controls 2.15 as Controls
import Qt.labs.platform 1.1 as Labs
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
import org.presenter 1.0
Item {
id: root
property int songID
property var song: songEditorModel
GridLayout {
id: mainLayout
anchors.fill: parent
columns: 2
rowSpacing: 5
columnSpacing: 0
Controls.ToolBar {
Layout.fillWidth: true
Layout.columnSpan: 2
id: toolbar
RowLayout {
anchors.fill: parent
Controls.ComboBox {
id: fontBox
model: Qt.fontFamilies()
implicitWidth: root.width / 5 > 300 ? 300 : root.width / 5
editable: true
hoverEnabled: true
/* flat: true */
onActivated: updateFont(currentText)
onAccepted: updateFont(currentText)
background: Presenter.TextBackground {
control: fontBox
}
indicator: Kirigami.Icon {
anchors {right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: 2}
source: "arrow-down"
rotation: fontBox.down ? 180 : 0
color: fontBox.pressed ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
}
}
Controls.SpinBox {
id: fontSizeBox
editable: true
from: 5
to: 150
height: parent.height
hoverEnabled: true
onValueModified: updateFontSize(value)
background: Presenter.TextBackground {
control: fontSizeBox
}
}
Controls.ComboBox {
id: hAlignmentBox
model: ["Left", "Center", "Right", "Justify"]
implicitWidth: 100
hoverEnabled: true
/* flat: true */
onActivated: updateHorizontalTextAlignment(currentText.toLowerCase());
background: Presenter.TextBackground {
control: hAlignmentBox
}
indicator: Kirigami.Icon {
anchors {right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: 2}
source: "arrow-down"
rotation: hAlignmentBox.down ? 180 : 0
color: hAlignmentBox.pressed ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
}
contentItem: Text {
leftPadding: 0
rightPadding: hAlignmentBox.indicator.width + hAlignmentBox.spacing
text: hAlignmentBox.displayText
font: hAlignmentBox.font
color: hAlignmentBox.pressed ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor;
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
Controls.ComboBox {
id: vAlignmentBox
model: ["Top", "Center", "Bottom"]
implicitWidth: 100
hoverEnabled: true
/* flat: true */
onActivated: updateVerticalTextAlignment(currentText.toLowerCase());
background: Presenter.TextBackground {
control: vAlignmentBox
}
indicator: Kirigami.Icon {
anchors {right: parent.right
verticalCenter: parent.verticalCenter
rightMargin: 2}
source: "arrow-down"
rotation: vAlignmentBox.down ? 180 : 0
color: vAlignmentBox.pressed ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
}
contentItem: Text {
leftPadding: 0
rightPadding: vAlignmentBox.indicator.width + vAlignmentBox.spacing
text: vAlignmentBox.displayText
font: vAlignmentBox.font
color: vAlignmentBox.pressed ? Kirigami.Theme.focusColor : Kirigami.Theme.textColor;
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
}
}
Controls.ToolButton {
text: "B"
hoverEnabled: true
visible: false
}
Controls.ToolButton {
text: "I"
hoverEnabled: true
visible: false
}
Controls.ToolButton {
text: "U"
hoverEnabled: true
visible: false
}
Controls.ToolSeparator {}
Item { Layout.fillWidth: true }
Controls.ToolSeparator {}
Controls.ToolButton {
id: backgroundButton
text: "Background"
icon.name: "fileopen"
hoverEnabled: true
onClicked: backgroundTypePopup.open()
}
Controls.ToolButton {
text: "Effects"
icon.name: "image-auto-adjust"
hoverEnabled: true
onClicked: {}
}
Controls.Popup {
id: backgroundTypePopup
x: backgroundButton.x
y: backgroundButton.y + backgroundButton.height + 20
modal: true
focus: true
dim: false
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.Tooltip
color: Kirigami.Theme.backgroundColor
radius: 10
border.color: Kirigami.Theme.activeBackgroundColor
border.width: 2
}
closePolicy: Controls.Popup.CloseOnEscape | Controls.Popup.CloseOnPressOutsideParent
ColumnLayout {
anchors.fill: parent
Controls.ToolButton {
Layout.fillHeight: true
Layout.fillWidth: true
text: "Video"
icon.name: "emblem-videos-symbolic"
onClicked: updateBackground("video") & backgroundTypePopup.close()
hoverEnabled: true
}
Controls.ToolButton {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Image"
icon.name: "folder-pictures-symbolic"
onClicked: updateBackground("image") & backgroundTypePopup.close()
hoverEnabled: true
}
}
}
}
}
Controls.SplitView {
id: songSplitView
Layout.fillHeight: true
Layout.fillWidth: true
Layout.columnSpan: 2
handle: Item {
implicitWidth: 6
Rectangle {
height: parent.height
anchors.horizontalCenter: parent.horizontalCenter
width: 1
color: parent.Controls.SplitHandle.hovered ? Qt.lighter(Kirigami.Theme.backgroundColor, 1.5) : Qt.darker(Kirigami.Theme.backgroundColor, 1.5)
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 400
Controls.SplitView.minimumWidth: 300
Controls.Label {
id: songTitleLabel
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
leftPadding: 10
text: "Title"
Rectangle {
anchors.top: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: Kirigami.Units.smallSpacing / 3
color: Kirigami.Theme.disabledTextColor
}
}
Controls.TextField {
id: songTitleField
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "Song Title..."
text: song.title
padding: 10
onEditingFinished: updateTitle(text);
background: Presenter.TextBackground {
control: songTitleField
errorCondition: song.title.length === 0
}
}
Controls.Label {
id: songVorderLabel
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
leftPadding: 10
text: "Verse Order <font color=\"Gray\"><i>format: V1 C1 V2 B1</i></font>"
Rectangle {
anchors.top: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: Kirigami.Units.smallSpacing / 3
color: Kirigami.Theme.disabledTextColor
}
}
Controls.TextField {
id: songVorderField
/* Layout.preferredWidth: 300 */
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "verse order..."
text: song.verseOrder
padding: 10
onEditingFinished: updateVerseOrder(text);
background: Presenter.TextBackground {
control: songVorderField
errorCondition: song.verseOrderError
}
}
Controls.Label {
id: songLyricsLabel
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
leftPadding: 10
text: "Lyrics"
Rectangle {
anchors.top: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: Kirigami.Units.smallSpacing / 3
color: Kirigami.Theme.disabledTextColor
}
}
Controls.ScrollView {
id: songLyricsField
Layout.preferredHeight: 2000
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: 20
rightPadding: 20
Controls.TextArea {
id: lyricsEditor
width: parent.width
placeholderText: "Put lyrics here..."
persistentSelection: true
text: song.lyrics
textFormat: TextEdit.PlainText
padding: 10
onEditingFinished: {
songEditorModel.lyrics = text
/* editorTimer.running = false; */
}
onPressed: editorTimer.running = true
background: Presenter.TextBackground {
control: lyricsEditor
}
}
}
Controls.Label {
id: songAuthorLabel
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
leftPadding: 10
text: "Artist"
Rectangle {
anchors.top: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: Kirigami.Units.smallSpacing / 3
color: Kirigami.Theme.disabledTextColor
}
}
Controls.TextField {
id: songAuthorField
Layout.fillWidth: true
Layout.preferredWidth: 300
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "Author..."
text: song.author
padding: 10
onEditingFinished: updateAuthor(text)
background: Presenter.TextBackground {
control: songAuthorField
}
}
Controls.Label {
id: songAudioLabel
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
leftPadding: 10
text: "Audio File"
Rectangle {
anchors.top: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: Kirigami.Units.smallSpacing / 3
color: Kirigami.Theme.disabledTextColor
}
}
RowLayout {
Layout.fillWidth: true
Layout.preferredWidth: 300
Layout.leftMargin: 20
Layout.rightMargin: 20
Controls.TextField {
id: songAudioField
Layout.fillWidth: true
placeholderText: "Audio File..."
text: song.audio
padding: 10
onEditingFinished: showPassiveNotification(text)
background: Presenter.TextBackground {
control: songAudioField
errorCondition: !song.audioExists
}
Controls.ToolTip {
text: song.audioExists ? "The audio that will be played for this song" : "The audio is missing or does not exists"
}
}
Controls.ToolButton {
id: audioPickerButton
Layout.fillHeight: true
text: "Audio"
icon.name: "folder-music-symbolic"
onClicked: updateAudioFile()
hoverEnabled: true
/* background: Presenter.TextBackground { */
/* control: audioPickerButton */
/* } */
}
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 700
Controls.SplitView.minimumWidth: 300
Presenter.SongEditorSlideList {
id: songList
imageBackground: songEditorModel.backgroundType === "image" ? song.background : ""
videoBackground: songEditorModel.backgroundType === "video" ? song.background : ""
font: songEditorModel.font
fontSize: songEditorModel.fontSize
/* hTextAlignment: songEditorModel.horizontalTextAlignment */
/* vTextAlignment: songEditorModel.verticalTextAlignment */
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.fillHeight: true
Layout.bottomMargin: 20
Layout.topMargin: 10
Layout.rightMargin: 0
Layout.leftMargin: 10
}
}
}
}
Timer {
id: editorTimer
interval: 1000
repeat: true
running: false
onTriggered: {
if (lyricsEditor.text === songEditorModel.lyrics) {
return;
}
else {
Utils.dbg("Changing Lyrics");
/* songEditorModel.lyrics = lyricsEditor.text; */
/* showPassiveNotification("Lyrics changed"); */
songModel.updateLyrics(songID, lyricsEditor.text);
clearSlides();
changeSlideText(songID);
Utils.dbg("Lyrics changed");
}
/* updateLyrics(lyricsEditor.text); */
}
}
function changeSong(index) {
Utils.dbg("Preparing to change song: " + index + 1 + " out of " + songModel.count);
editorTimer.running = false;
clearSlides();
const updatedSong = songModel.getItem(index);
Utils.dbg(updatedSong.vorder + " " + updatedSong.title + " " + updatedSong.audio);
songEditorModel.loadSong(index);
songEditorModel.checkVerseOrder();
songEditorModel.checkFiles();
songID = updatedSong.id;
changeSlideHAlignment(songEditorModel.horizontalTextAlignment);
changeSlideVAlignment(songEditorModel.verticalTextAlignment);
changeSlideFont(songEditorModel.font, true);
changeSlideFontSize(songEditorModel.fontSize, true)
changeSlideText(index);
Utils.dbg("Changing to song: " + songEditorModel.title + " with ID: " + songID);
footerFirstText = "Song: ";
footerSecondText = songEditorModel.title;
songList.loadVideo();
}
Connections {
target: songEditorModel
function onLyricsChanged() {
}
}
/* function updateLyrics(lyrics) { */
/* songModel.updateLyrics(songID, lyrics); */
/* /\* songLyrics = lyrics; *\/ */
/* clearSlides(); */
/* changeSlideText(songID); */
/* } */
function updateTitle(title) {
songModel.updateTitle(songID, title)
song.title = title;
}
function updateAuthor(author) {
songModel.updateAuthor(songID, author)
}
function updateAudio(audio) {
songModel.updateAudio(songID, audio)
}
function updateCcli(ccli) {
songModel.updateCcli(songID, ccli)
}
function updateVerseOrder(vorder) {
songModel.updateVerseOrder(songID, vorder)
songEditorModel.verseOrder = vorder;
songEditorModel.checkVerseOrder();
}
function updateAudioFile() {
const file = fileHelper.loadFile("Pick Audio", "audio");
Utils.dbg("song: " + songID);
Utils.dbg("song-title: " + songEditorModel.title)
songEditorModel.audio = file;
songModel.updateAudio(songID, file);
songEditorModel.checkFiles();
}
function updateBackground(backgroundType) {
songEditorModel.backgroundType = backgroundType;
const file = fileHelper.loadFile("Pick Background", backgroundType);
songEditorModel.background = file;
songModel.updateBackground(songID, file);
songModel.updateBackgroundType(songID, backgroundType);
Utils.dbg("changed background");
}
function updateHorizontalTextAlignment(textAlignment) {
changeSlideHAlignment(textAlignment);
songModel.updateHorizontalTextAlignment(songID, textAlignment);
}
function updateVerticalTextAlignment(textAlignment) {
changeSlideVAlignment(textAlignment);
songModel.updateVerticalTextAlignment(songID, textAlignment)
}
function updateFont(font) {
showPassiveNotification(font);
changeSlideFont(font, false);
songModel.updateFont(songID, font);
song.font = font;
}
function updateFontSize(fontSize) {
changeSlideFontSize(fontSize, false);
songModel.updateFontSize(songID, fontSize);
song.fontSize = fontSize;
}
function changeSlideHAlignment(alignment) {
switch (alignment) {
case "left" :
hAlignmentBox.currentIndex = 0;
songList.hTextAlignment = Text.AlignLeft;
break;
case "center" :
hAlignmentBox.currentIndex = 1;
songList.hTextAlignment = Text.AlignHCenter;
break;
case "right" :
hAlignmentBox.currentIndex = 2;
songList.hTextAlignment = Text.AlignRight;
break;
case "justify" :
hAlignmentBox.currentIndex = 3;
songList.hTextAlignment = Text.AlignJustify;
break;
}
}
function changeSlideVAlignment(alignment) {
switch (alignment) {
case "top" :
vAlignmentBox.currentIndex = 0;
songList.vTextAlignment = Text.AlignTop;
break;
case "center" :
vAlignmentBox.currentIndex = 1;
songList.vTextAlignment = Text.AlignVCenter;
break;
case "bottom" :
vAlignmentBox.currentIndex = 2;
songList.vTextAlignment = Text.AlignBottom;
break;
}
}
function changeSlideFont(font, updateBox) {
const fontIndex = fontBox.find(font);
if (updateBox)
fontBox.currentIndex = fontIndex;
songList.font = font;
}
function changeSlideFontSize(fontSize, updateBox) {
if (updateBox)
fontSizeBox.value = fontSize;
songList.fontSize = fontSize;
}
function changeSlideText(id) {
const verses = songModel.getLyricList(id);
verses.forEach(songList.appendVerse);
/* songList.loadVideo(); */
}
function clearSlides() {
songList.clear()
}
}