lumina-qml/src/qml/presenter/SongEditor.qml

641 lines
23 KiB
QML

import QtQuick 2.13
import QtQuick.Controls 2.15 as Controls
import Qt.labs.platform 1.1 as Labs
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
Item {
id: root
property int songIndex
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: 300
editable: true
hoverEnabled: true
/* flat: true */
onActivated: 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 {
text: "Effects"
icon.name: "image-auto-adjust"
hoverEnabled: true
onClicked: {}
}
Controls.ToolButton {
id: backgroundButton
text: "Background"
icon.name: "fileopen"
hoverEnabled: true
onClicked: backgroundTypePopup.open()
}
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 {
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 ? Kirigami.Theme.hoverColor : Kirigami.Theme.backgroundColor
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 500
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.vorder
padding: 10
onEditingFinished: updateVerseOrder(text);
background: Presenter.TextBackground {
control: songVorderField
errorCondition: song.vorder.length === 0
}
}
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: {
updateLyrics(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
}
}
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" ? songEditor.background : ""
videoBackground: songEditorModel.backgroundType === "video" ? songEditor.background : ""
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 === song.lyrics)
return;
updateLyrics(lyricsEditor.text);
}
}
function newSong(index) {
clearSlides();
song = songProxyModel.getSong(index);
changeSlideHAlignment("Center");
changeSlideVAlignment("Center");
changeSlideFont("Noto Sans", true);
changeSlideFontSize(50, true)
changeSlideText(songProxyModel.modelIndex(index).row);
songList.loadVideo();
console.log("New song with ID: " + song.id);
}
function changeSong(index) {
clearSlides();
const updatedSong = songProxyModel.getSong(index);
console.log(updatedSong.verseOrder + " " + updatedSong.title);
songEditorModel.title = updatedSong.title;
songEditorModel.lyrics = updatedSong.lyrics;
songEditorModel.author = updatedSong.author;
songEditorModel.ccli = updatedSong.ccli;
songEditorModel.audio = updatedSong.audio;
songEditorModel.verseOrder = updatedSong.verseOrder;
songEditorModel.background = updatedSong.background;
songEditorModel.backgroundType = updatedSong.backgroundType;
songEditorModel.horizontalTextAlignment = updatedSong.horizontalTextAlignment;
songEditorModel.verticalTextAlignment = updatedSong.verticalTextAlignment;
songEditorModel.font = updatedSong.font;
songEditorModel.fontSize = updatedSong.fontSize;
songIndex = song.id;
changeSlideHAlignment(song.horizontalTextAlignment);
changeSlideVAlignment(song.verticalTextAlignment);
changeSlideFont(song.font, true);
changeSlideFontSize(song.fontSize, true)
changeSlideText(songProxyModel.modelIndex(index).row);
console.log("Changing to song: " + song.title + " with ID: " + song.id);
footerFirstText = "Song: ";
footerSecondText = song.title;
songList.loadVideo();
}
function updateLyrics(lyrics) {
songProxyModel.songModel.updateLyrics(songIndex, lyrics);
/* songLyrics = lyrics; */
clearSlides();
changeSlideText(songIndex);
}
function updateTitle(title) {
songProxyModel.songModel.updateTitle(songIndex, title)
}
function updateAuthor(author) {
songProxyModel.songModel.updateAuthor(songIndex, author)
}
function updateAudio(audio) {
songProxyModel.songModel.updateAudio(songIndex, audio)
}
function updateCcli(ccli) {
songProxyModel.songModel.updateCcli(songIndex, ccli)
}
function updateVerseOrder(vorder) {
songProxyModel.songModel.updateVerseOrder(songIndex, vorder)
}
function updateAudioFile() {
const file = fileHelper.loadFile("Pick Audio", "audio");
songEditorModel.audio = file
songProxyModel.songModel.updateAudio(songIndex, file);
}
function updateBackground(backgroundType) {
songEditorModel.backgroundType = backgroundType;
const file = fileHelper.loadFile("Pick Background", backgroundType);
songEditorModel.background = file;
songProxyModel.songModel.updateBackground(songIndex, file);
songProxyModel.songModel.updateBackgroundType(songIndex, backgroundType);
console.log("changed background");
}
function updateHorizontalTextAlignment(textAlignment) {
changeSlideHAlignment(textAlignment);
songProxyModel.songModel.updateHorizontalTextAlignment(songIndex, textAlignment);
}
function updateVerticalTextAlignment(textAlignment) {
changeSlideVAlignment(textAlignment);
songProxyModel.songModel.updateVerticalTextAlignment(songIndex, textAlignment)
}
function updateFont(font) {
changeSlideFont(font, false);
songProxyModel.songModel.updateFont(songIndex, font);
song.font = font;
}
function updateFontSize(fontSize) {
changeSlideFontSize(fontSize, false);
songProxyModel.songModel.updateFontSize(songIndex, 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 = songProxyModel.getLyricList(id);
verses.forEach(songList.appendVerse);
/* songList.loadVideo(); */
}
function clearSlides() {
songList.clear()
}
}