adding in a video model and editor

This commit is contained in:
Chris Cochrun 2022-03-15 15:08:17 -05:00
parent fab9f86b41
commit c35c0f6550
22 changed files with 972 additions and 160 deletions

View file

@ -3,8 +3,8 @@ add_executable(presenter)
target_sources(presenter
PRIVATE
main.cpp resources.qrc
songlistmodel.cpp songlistmodel.h
songsqlmodel.cpp songsqlmodel.h
videosqlmodel.cpp videosqlmodel.h
mpv/mpvobject.h mpv/mpvobject.cpp
mpv/qthelper.hpp mpv/mpvhelpers.h
)

View file

@ -30,9 +30,9 @@
#include <qsqlquery.h>
#include <qstringliteral.h>
#include "songlistmodel.h"
#include "mpv/mpvobject.h"
#include "songsqlmodel.h"
#include "videosqlmodel.h"
static void connectToDatabase() {
// let's setup our sql database
@ -44,6 +44,7 @@ static void connectToDatabase() {
}
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
qDebug() << "dir location " << writeDir.absolutePath();
if (!writeDir.mkpath(".")) {
qFatal("Failed to create writable location at %s", qPrintable(writeDir.absolutePath()));
@ -79,21 +80,21 @@ int main(int argc, char *argv[])
#endif
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("system-config-display")));
// apparently mpv needs this class set
// let's register mpv as well
std::setlocale(LC_NUMERIC, "C");
qmlRegisterType<MpvObject>("mpv", 1, 0, "MpvObject");
//register our song model from sql
//register our models
qmlRegisterType<SongSqlModel>("org.presenter", 1, 0, "SongSqlModel");
SongListModel songListModel;
qmlRegisterType<VideoSqlModel>("org.presenter", 1, 0, "VideoSqlModel");
connectToDatabase();
QQmlApplicationEngine engine;
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
engine.rootContext()->setContextProperty("_songListModel", &songListModel);
engine.load(QUrl(QStringLiteral("qrc:qml/main.qml")));
// QQuickView *view = new QQuickView;

View file

@ -1,6 +1,8 @@
#include "mpvobject.h"
// std
#include <qdir.h>
#include <qvariant.h>
#include <stdexcept>
#include <clocale>
@ -19,6 +21,7 @@
#include <QtX11Extras/QX11Info>
#include <QDebug>
#include <QStandardPaths>
// libmpv
#include <mpv/client.h>
@ -28,6 +31,7 @@
#include "qthelper.hpp"
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
//--- MpvRenderer
void* MpvRenderer::get_proc_address(void *ctx, const char *name) {
@ -261,11 +265,13 @@ void MpvObject::doUpdate()
void MpvObject::command(const QVariant& params)
{
// qDebug() << params;
mpv::qt::command(mpv, params);
}
void MpvObject::commandAsync(const QVariant& params)
{
qDebug() << params;
mpv::qt::command_async(mpv, params);
}
@ -522,6 +528,17 @@ void MpvObject::loadFile(QVariant urls)
command(QVariantList() << "loadfile" << urls);
}
void MpvObject::screenshotToFile(QUrl url) {
qDebug() << "Url of screenshot to be taken: " << url;
QDir dir = writeDir.absolutePath() + "/presenter/Church Presenter/thumbnails";
qDebug() << "thumbnails dir: " << dir;
QDir absDir = writeDir.absolutePath() + "/presenter/Church Presenter";
if (!dir.exists())
absDir.mkdir("thumbnails");
QString file = url.path() + ".jpg";
commandAsync(QVariantList() << "screenshot-to-file" << file << "video");
}
void MpvObject::subAdd(QVariant urls)
{
command(QVariantList() << "sub-add" << urls);

View file

@ -135,6 +135,7 @@ public slots:
void stepForward();
void seek(double pos);
void loadFile(QVariant urls);
void screenshotToFile(QUrl url);
void subAdd(QVariant urls);
bool enableAudio() const { return m_enableAudio; }

View file

@ -2,7 +2,7 @@
Type=Application
Name=present
GenericName=Church Presentation
Comment=A Kirigami base church presenter
Comment=A Kirigami based church presenter
Exec=present %U
TryExec=present
Icon=present

View file

@ -26,28 +26,28 @@ Kirigami.ApplicationWindow {
pageStack.initialPage: mainPage
header: Presenter.Header {}
menuBar: Controls.MenuBar {
Controls.Menu {
title: qsTr("File")
Controls.MenuItem { text: qsTr("New...") }
Controls.MenuItem { text: qsTr("Open...") }
Controls.MenuItem { text: qsTr("Save") }
Controls.MenuItem { text: qsTr("Save As...") }
Controls.MenuSeparator { }
Controls.MenuItem { text: qsTr("Quit") }
}
Controls.Menu {
title: qsTr("Settings")
Controls.MenuItem {
text: qsTr("Configure")
onTriggered: openSettings()
}
}
Controls.Menu {
title: qsTr("Help")
Controls.MenuItem { text: qsTr("About") }
}
}
/* menuBar: Qt.platform.os !== "linux" ? Controls.MenuBar { */
/* Controls.Menu { */
/* title: qsTr("File") */
/* Controls.MenuItem { text: qsTr("New...") } */
/* Controls.MenuItem { text: qsTr("Open...") } */
/* Controls.MenuItem { text: qsTr("Save") } */
/* Controls.MenuItem { text: qsTr("Save As...") } */
/* Controls.MenuSeparator { } */
/* Controls.MenuItem { text: qsTr("Quit") } */
/* } */
/* Controls.Menu { */
/* title: qsTr("Settings") */
/* Controls.MenuItem { */
/* text: qsTr("Configure") */
/* onTriggered: openSettings() */
/* } */
/* } */
/* Controls.Menu { */
/* title: qsTr("Help") */
/* Controls.MenuItem { text: qsTr("About") } */
/* } */
/* } : null */
Labs.MenuBar {
Labs.Menu {
@ -81,7 +81,7 @@ Kirigami.ApplicationWindow {
function toggleEditMode() {
editMode = !editMode;
mainPage.editSwitch(editMode);
mainPage.editSwitch();
}
function toggleLibrary() {

View file

@ -0,0 +1,230 @@
import QtQuick 2.13
import QtQuick.Controls 2.15 as Controls
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
Item {
id: root
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 {
model: Qt.fontFamilies()
implicitWidth: 300
editable: true
hoverEnabled: true
onCurrentTextChanged: showPassiveNotification(currentText)
}
Controls.SpinBox {
editable: true
from: 5
to: 72
hoverEnabled: true
}
Controls.ComboBox {
model: ["IMAGES", "Center", "Right", "Justify"]
implicitWidth: 100
hoverEnabled: true
}
Controls.ToolButton {
text: "B"
hoverEnabled: true
}
Controls.ToolButton {
text: "I"
hoverEnabled: true
}
Controls.ToolButton {
text: "U"
hoverEnabled: true
}
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: backgroundType.open()
}
Controls.Popup {
id: backgroundType
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: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
ColumnLayout {
anchors.fill: parent
Controls.ToolButton {
Layout.fillHeight: true
Layout.fillWidth: true
text: "Video"
icon.name: "emblem-videos-symbolic"
onClicked: videoFileDialog.open() & backgroundType.close()
}
Controls.ToolButton {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Image"
icon.name: "folder-pictures-symbolic"
onClicked: imageFileDialog.open() & backgroundType.close()
}
}
}
}
}
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: Controls.SplitHandle.hovered ? Kirigami.Theme.hoverColor : Kirigami.Theme.backgroundColor
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 500
Controls.SplitView.minimumWidth: 500
Controls.TextField {
id: songTitleField
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "Song Title..."
text: songTitle
padding: 10
onEditingFinished: updateTitle(text);
}
Controls.TextField {
id: songVorderField
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "verse order..."
text: songVorder
padding: 10
onEditingFinished: updateVerseOrder(text);
}
Controls.ScrollView {
id: songLyricsField
Layout.preferredHeight: 3000
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: songLyrics
textFormat: TextEdit.MarkdownText
padding: 10
onEditingFinished: {
updateLyrics(text);
editorTimer.running = false;
}
onPressed: editorTimer.running = true
}
}
Controls.TextField {
id: songAuthorField
Layout.fillWidth: true
Layout.preferredWidth: 300
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "Author..."
text: songAuthor
padding: 10
onEditingFinished: updateAuthor(text)
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 700
Controls.SplitView.minimumWidth: 300
Rectangle {
id: slideBar
color: Kirigami.Theme.highlightColor
Layout.preferredWidth: 500
Layout.preferredHeight: songTitleField.height
Layout.rightMargin: 20
Layout.leftMargin: 20
}
Presenter.SlideEditor {
id: slideEditor
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.preferredHeight: slideEditor.width / 16 * 9
Layout.bottomMargin: 30
Layout.rightMargin: 20
Layout.leftMargin: 20
}
}
}
}
Timer {
id: editorTimer
interval: 1000
repeat: true
running: false
onTriggered: updateLyrics(lyricsEditor.text)
}
}

View file

@ -109,6 +109,7 @@ ColumnLayout {
showPassiveNotification(serviceItemList.currentIndex);
changeSlideBackground(background, backgroundType);
changeSlideText(text);
changeSlideType(type);
}
}

View file

@ -1,14 +1,17 @@
import QtQuick 2.13
import QtQuick.Controls 2.0 as Controls
import QtQuick.Layouts 1.2
import Qt.labs.platform 1.1 as Labs
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
import org.presenter 1.0
import mpv 1.0
Item {
id: root
property string selectedLibrary: "songs"
property bool overlay: false
Kirigami.Theme.colorSet: Kirigami.Theme.View
@ -292,10 +295,37 @@ Item {
opacity: 1.0
Controls.Label {
id: videoLabel
anchors.centerIn: parent
text: "Videos"
}
Controls.Label {
id: videoCount
anchors {left: videoLabel.right
verticalCenter: videoLabel.verticalCenter
leftMargin: 15}
text: videosqlmodel.rowCount()
font.pixelSize: 15
color: Kirigami.Theme.disabledTextColor
}
Kirigami.Icon {
id: videoDrawerArrow
anchors {right: parent.right
verticalCenter: videoCount.verticalCenter
rightMargin: 10}
source: "arrow-down"
rotation: selectedLibrary == "videos" ? 0 : 180
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
@ -313,6 +343,9 @@ Item {
Layout.preferredHeight: parent.height - 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
model: videosqlmodel
delegate: videoDelegate
clip: true
state: "deselected"
states: [
@ -339,6 +372,109 @@ Item {
duration: 300
}
}
Component {
id: videoDelegate
Item{
implicitWidth: ListView.view.width
height: selectedLibrary == "videos" ? 50 : 0
Kirigami.BasicListItem {
id: videoListItem
property bool rightMenu: false
implicitWidth: videoLibraryList.width
height: selectedLibrary == "videos" ? 50 : 0
clip: true
label: title
/* subtitle: author */
supportsMouseEvents: false
backgroundColor: {
if (parent.ListView.isCurrentItem) {
Kirigami.Theme.highlightColor;
} else if (videoDragHandler.containsMouse){
Kirigami.Theme.highlightColor;
} else {
Kirigami.Theme.backgroundColor;
}
}
textColor: {
if (parent.ListView.isCurrentItem || videoDragHandler.containsMouse)
activeTextColor;
else
Kirigami.Theme.textColor;
}
Behavior on height {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
Drag.active: videoDragHandler.drag.active
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Drag.keys: [ "library" ]
states: State {
name: "dragged"
when: videoListItem.Drag.active
PropertyChanges {
target: videoListItem
x: x
y: y
}
}
}
MouseArea {
id: videoDragHandler
anchors.fill: parent
hoverEnabled: true
drag {
target: videoListItem
onActiveChanged: {
if (videoDragHandler.drag.active) {
dragVideoTitle = title
showPassiveNotification(dragVideoTitle)
} else {
videoListItem.Drag.drop()
}
}
filterChildren: true
threshold: 10
}
MouseArea {
id: videoClickHandler
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(mouse.button == Qt.RightButton)
rightClickVideoMenu.popup()
else{
videoLibraryList.currentIndex = index
const video = videosqlmodel.getVideo(videoLibraryList.currentIndex);
/* showPassiveNotification("selected video: " + video); */
if (!editMode)
editMode = true;
editSwitch("video", video);
}
}
}
}
Controls.Menu {
id: rightClickVideoMenu
x: videoClickHandler.mouseX
y: videoClickHandler.mouseY + 10
Kirigami.Action {
text: "delete"
onTriggered: videosqlmodel.deleteVideo(index)
}
}
}
}
}
Rectangle {
@ -513,5 +649,59 @@ Item {
}
}
DropArea {
id: fileDropArea
anchors.fill: parent
onDropped: drop => {
overlay = false;
showPassiveNotification("dropped");
print(drop.urls);
/* thumbnailer.loadFile(drop.urls[0]); */
addVideo(drop.urls[0]);
}
onEntered: overlay = true
onExited: overlay = false
function addVideo(url) {
videosqlmodel.newVideo(url);
selectedLibrary = "videos";
videoLibraryList.currentIndex = videosqlmodel.rowCount();
print(videosqlmodel.getVideo(videoLibraryList.currentIndex));
const video = videosqlmodel.getVideo(videoLibraryList.currentIndex);
showPassiveNotification("newest video: " + video);
if (!editMode)
editMode = true;
editSwitch("video", video);
}
}
Rectangle {
id: fileDropOverlay
color: overlay ? Kirigami.Theme.highlightColor : "#00000000"
anchors.fill: parent
border.width: 8
border.color: overlay ? Kirigami.Theme.hoverColor : "#00000000"
}
MpvObject {
id: thumbnailer
useHwdec: true
enableAudio: false
width: 0
height: 0
Component.onCompleted: print("ready")
onFileLoaded: {
thumbnailer.pause();
print("FILE: " + thumbnailer.mediaTitle);
thumbnailer.screenshotToFile(thumbnailFile(thumbnailer.mediaTitle));
showPassiveNotification("Screenshot Taken to: " + thumbnailFile(thumbnailer.mediaTitle));
thumbnailer.stop();
}
function thumbnailFile(title) {
const thumbnailFolder = Labs.StandardPaths.writableLocation(Labs.StandardPaths.AppDataLocation) + "/thumbnails/";
return Qt.resolvedUrl(thumbnailFolder + title);
}
}
}
}

View file

@ -20,6 +20,8 @@ Controls.Page {
property string songVorder: ""
property int blurRadius: 0
/* property var video */
property string dragSongTitle: ""
property bool editing: true
@ -78,6 +80,20 @@ Controls.Page {
}
}
Component {
id: videoEditorComp
Presenter.VideoEditor {
id: videoEditor
}
}
Component {
id: imageEditorComp
Presenter.ImageEditor {
id: imageEditor
}
}
Loader {
id: presentLoader
active: presenting
@ -152,6 +168,19 @@ Controls.Page {
id: songsqlmodel
}
VideoSqlModel {
id: videosqlmodel
}
function changeSlideType(type) {
/* showPassiveNotification("used to be: " + presentation.text); */
presentation.itemType = type;
/* showPassiveNotification("next"); */
if (slideItem)
slideItem.itemType = type;
/* showPassiveNotification("last"); */
}
function changeSlideText(text) {
/* showPassiveNotification("used to be: " + presentation.text); */
presentation.text = text;
@ -193,11 +222,24 @@ Controls.Page {
showPassiveNotification("previous slide please")
}
function editSwitch(edit) {
if (edit)
mainPageArea.push(songEditorComp, Controls.StackView.Immediate)
else
mainPageArea.pop(Controls.StackView.Immediate)
function editSwitch(editType, item) {
if (editMode) {
switch (editType) {
case "song" :
mainPageArea.push(songEditorComp, Controls.StackView.Immediate);
break;
case "video" :
mainPageArea.push(videoEditorComp, {"video": item}, Controls.StackView.Immediate);
break;
case "image" :
mainPageArea.push(imageEditorComp, Controls.StackView.Immediate);
break;
default:
mainPageArea.pop(Controls.StackView.Immediate);
editMode = false;
}
} else
mainPageArea.pop(Controls.StackView.Immediate);
}
function present(present) {

View file

@ -11,6 +11,7 @@ Item {
id: root
property string text
property string itemType
property url imagebackground
property url vidbackground
@ -80,6 +81,7 @@ Item {
Layout.alignment: Qt.AlignCenter
textSize: width / 15
text: root.text
itemType: root.itemType
imageSource: imagebackground
videoSource: vidbackground
preview: true

View file

@ -45,10 +45,10 @@ Item {
enableAudio: !preview
Component.onCompleted: mpvLoadingTimer.start()
onFileLoaded: {
print(videoSource + " has been loaded");
showPassiveNotification(videoSource + " has been loaded");
if (itemType == "song")
mpv.setProperty("loop", "inf");
print(mpv.getProperty("loop"));
showPassiveNotification(mpv.getProperty("loop"));
}
MouseArea {

View file

@ -0,0 +1,227 @@
import QtQuick 2.13
import QtQuick.Controls 2.15 as Controls
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.2
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
import mpv 1.0
Item {
id: root
property var video
property bool audioOn: true
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 {
model: Qt.fontFamilies()
implicitWidth: 300
editable: true
hoverEnabled: true
onCurrentTextChanged: showPassiveNotification(currentText)
}
Controls.SpinBox {
editable: true
from: 5
to: 72
hoverEnabled: true
}
Controls.ComboBox {
model: ["VIDEOS", "Center", "Right", "Justify"]
implicitWidth: 100
hoverEnabled: true
}
Controls.ToolButton {
text: "B"
hoverEnabled: true
}
Controls.ToolButton {
text: "I"
hoverEnabled: true
}
Controls.ToolButton {
text: "U"
hoverEnabled: true
}
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: backgroundType.open()
}
Controls.Popup {
id: backgroundType
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: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
ColumnLayout {
anchors.fill: parent
Controls.ToolButton {
Layout.fillHeight: true
Layout.fillWidth: true
text: "Video"
icon.name: "emblem-videos-symbolic"
onClicked: videoFileDialog.open() & backgroundType.close()
}
Controls.ToolButton {
Layout.fillWidth: true
Layout.fillHeight: true
text: "Image"
icon.name: "folder-pictures-symbolic"
onClicked: imageFileDialog.open() & backgroundType.close()
}
}
}
}
}
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: Controls.SplitHandle.hovered ? Kirigami.Theme.hoverColor : Kirigami.Theme.backgroundColor
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 300
Controls.SplitView.minimumWidth: 100
Controls.TextField {
id: videoTitleField
Layout.preferredWidth: 300
Layout.fillWidth: true
Layout.leftMargin: 20
Layout.rightMargin: 20
placeholderText: "Song Title..."
text: video[0]
padding: 10
/* onEditingFinished: updateTitle(text); */
}
Item {
id: empty
Layout.fillHeight: true
}
}
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 700
Controls.SplitView.minimumWidth: 300
spacing: 5
Item {
id: topEmpty
Layout.fillHeight: true
}
MpvObject {
id: videoPreview
objectName: "mpv"
Layout.preferredWidth: 600
Layout.preferredHeight: Layout.preferredWidth / 16 * 9
Layout.alignment: Qt.AlignCenter
useHwdec: true
enableAudio: audioOn
Component.onCompleted: mpvLoadingTimer.start()
onPositionChanged: videoSlider.value = position
onFileLoaded: {
showPassiveNotification(video.title + " has been loaded");
videoPreview.pause();
/* showPassiveNotification(mpv.getProperty("loop")); */
}
}
Rectangle {
id: videoBg
color: Kirigami.Theme.alternateBackgroundColor
Layout.preferredWidth: videoPreview.Layout.preferredWidth
Layout.preferredHeight: videoTitleField.height
Layout.alignment: Qt.AlignHCenter
RowLayout {
anchors.fill: parent
spacing: 2
Kirigami.Icon {
source: videoPreview.isPlaying ? "media-pause" : "media-play"
Layout.preferredWidth: 25
Layout.preferredHeight: 25
MouseArea {
anchors.fill: parent
onPressed: videoPreview.playPause()
cursorShape: Qt.PointingHandCursor
}
}
Controls.Slider {
id: videoSlider
Layout.fillWidth: true
Layout.preferredHeight: 25
from: 0
to: videoPreview.duration
/* value: videoPreview.postion */
live: false
onMoved: videoPreview.seek(value);
}
}
}
Item {
id: botEmpty
Layout.fillHeight: true
}
}
}
}
Timer {
id: mpvLoadingTimer
interval: 100
onTriggered: {
videoPreview.loadFile(video[1].toString());
/* showPassiveNotification(video[0]); */
}
}
}

View file

@ -9,6 +9,8 @@
<file>qml/presenter/Actions.qml</file>
<file>qml/presenter/PanelItem.qml</file>
<file>qml/presenter/SongEditor.qml</file>
<file>qml/presenter/VideoEditor.qml</file>
<file>qml/presenter/ImageEditor.qml</file>
<file>qml/presenter/Slide.qml</file>
<file>qml/presenter/SlideEditor.qml</file>
<file>qml/presenter/DragHandle.qml</file>

View file

@ -1,70 +0,0 @@
#include "songlistmodel.h"
#include <QTextStream>
#include <QDebug>
SongListModel::SongListModel(QObject *parent)
: QAbstractListModel(parent)
{
m_data
<< Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")
<< Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")
<< Data("River", "I'm going down to the river", "Jordan Feliz", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")
<< Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")
<< Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "")
<< Data("10,000 Reasons", "10,000 reasons for my heart to sing", "Matt Redman", "13470183", "")
<< Data("Marvelous Light", "Into marvelous light I'm running", "Chris Tomlin", "13470183", "");
}
int SongListModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;
return m_data.count();
}
QVariant SongListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
// this is the returning of song data
const Data &data = m_data.at(index.row());
if ( role == TitleRole )
return data.title;
else if (role == LyricsRole)
return data.lyrics;
else if (role == AuthorRole)
return data.author;
else if (role == CCLINumRole)
return data.ccli;
else if (role == AudioRole)
return data.audio;
else
return QVariant();
}
QHash<int, QByteArray> SongListModel::roleNames() const
{
static QHash<int, QByteArray> mapping {
{TitleRole, "title"},
{LyricsRole, "lyrics"},
{AuthorRole, "author"},
{CCLINumRole, "ccli"},
{AudioRole, "audio"}
};
return mapping;
}
void SongListModel::lyricsSlides(QString lyrics)
{
QTextStream stream(&lyrics);
QString line = stream.readLine();
qDebug() << line;
}

View file

@ -1,47 +0,0 @@
#ifndef SONGLISTMODEL_H
#define SONGLISTMODEL_H
#include <QAbstractListModel>
struct Data {
Data () {}
Data ( const QString& title, const QString& lyrics, const QString& author,
const QString& ccli, const QString& audio)
: title(title), lyrics(lyrics), author(author), ccli(ccli), audio(audio) {}
QString title;
QString lyrics;
QString author;
QString ccli;
QString audio;
};
class SongListModel : public QAbstractListModel
{
Q_OBJECT
public:
enum Roles {
TitleRole = Qt::UserRole,
LyricsRole,
AuthorRole,
CCLINumRole,
AudioRole,
};
explicit SongListModel(QObject *parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
private:
QVector< Data > m_data;
public slots:
void lyricsSlides(QString lyrics);
};
#endif // SONGLISTMODEL_H

View file

@ -92,9 +92,9 @@ void SongSqlModel::newSong() {
if (insertRecord(rows, recorddata)) {
submitAll();
}else {
} else {
qDebug() << lastError();
}
};
}
void SongSqlModel::deleteSong(const int &row) {

View file

@ -2,7 +2,6 @@
#define SONGSQLMODEL_H
#include <QSqlTableModel>
#include <qabstractitemmodel.h>
#include <qobjectdefs.h>
#include <qqml.h>
#include <qvariant.h>

167
src/videosqlmodel.cpp Normal file
View file

@ -0,0 +1,167 @@
#include "videosqlmodel.h"
#include <QDateTime>
#include <QDebug>
#include <QSqlError>
#include <QSqlRecord>
#include <QSqlQuery>
#include <QSql>
#include <QSqlDatabase>
#include <QFileInfo>
#include <qabstractitemmodel.h>
#include <qdebug.h>
#include <qnamespace.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qsqlrecord.h>
#include <qurl.h>
#include <qvariant.h>
static const char *videosTableName = "videos";
static void createVideoTable()
{
if(QSqlDatabase::database().tables().contains(videosTableName)) {
return;
}
QSqlQuery query;
if (!query.exec("CREATE TABLE IF NOT EXISTS 'videos' ("
" 'id' INTEGER NOT NULL,"
" 'title' TEXT NOT NULL,"
" 'filePath' TEXT NOT NULL,"
" PRIMARY KEY(id))")) {
qFatal("Failed to query database: %s",
qPrintable(query.lastError().text()));
}
qDebug() << query.lastQuery();
qDebug() << "inserting into videos";
query.exec("INSERT INTO videos (title, filePath) VALUES ('The Test', '/home/chris/nextcloud/tfc/openlp/videos/test.mp4')");
qDebug() << query.lastQuery();
query.exec("INSERT INTO videos (title, filePath) VALUES ('Sabbath', '/home/chris/nextcloud/tfc/openlp/videos/Sabbath.mp4')");
query.exec("select * from videos");
qDebug() << query.lastQuery();
}
VideoSqlModel::VideoSqlModel(QObject *parent) : QSqlTableModel(parent) {
qDebug() << "creating video table";
createVideoTable();
setTable(videosTableName);
setEditStrategy(QSqlTableModel::OnManualSubmit);
// make sure to call select else the model won't fill
select();
}
QVariant VideoSqlModel::data(const QModelIndex &index, int role) const {
if (role < Qt::UserRole) {
return QSqlTableModel::data(index, role);
}
// qDebug() << role;
const QSqlRecord sqlRecord = record(index.row());
return sqlRecord.value(role - Qt::UserRole);
}
QHash<int, QByteArray> VideoSqlModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole] = "id";
names[Qt::UserRole + 1] = "title";
names[Qt::UserRole + 2] = "filePath";
return names;
}
void VideoSqlModel::newVideo(const QUrl &filePath) {
qDebug() << "adding new video";
int rows = rowCount();
qDebug() << rows;
QSqlRecord recordData = record();
QFileInfo fileInfo = filePath.toString();
QString title = fileInfo.baseName();
recordData.setValue("title", title);
recordData.setValue("filePath", filePath);
if (insertRecord(rows, recordData)) {
submitAll();
} else {
qDebug() << lastError();
};
}
void VideoSqlModel::deleteVideo(const int &row) {
QSqlRecord recordData = record(row);
if (recordData.isEmpty())
return;
removeRow(row);
submitAll();
}
int VideoSqlModel::id() const {
return m_id;
}
QString VideoSqlModel::title() const {
return m_title;
}
void VideoSqlModel::setTitle(const QString &title) {
if (title == m_title)
return;
m_title = title;
select();
emit titleChanged();
}
// This function is for updating the title from outside the delegate
void VideoSqlModel::updateTitle(const int &row, const QString &title) {
qDebug() << "Row is " << row;
QSqlRecord rowdata = record(row);
qDebug() << rowdata;
rowdata.setValue("title", title);
setRecord(row, rowdata);
qDebug() << rowdata;
submitAll();
emit titleChanged();
}
QUrl VideoSqlModel::filePath() const {
return m_filePath;
}
void VideoSqlModel::setFilePath(const QUrl &filePath) {
if (filePath == m_filePath)
return;
m_filePath = filePath;
select();
emit filePathChanged();
}
// This function is for updating the filepath from outside the delegate
void VideoSqlModel::updateFilePath(const int &row, const QUrl &filePath) {
qDebug() << "Row is " << row;
QSqlRecord rowdata = record(row);
qDebug() << rowdata;
rowdata.setValue("filePath", filePath);
setRecord(row, rowdata);
qDebug() << rowdata;
submitAll();
emit filePathChanged();
}
QVariantList VideoSqlModel::getVideo(const int &row) {
qDebug() << "Row we are getting is " << row;
QVariantList video;
QSqlRecord rec = record(row - 1);
qDebug() << rec.value("title");
video.append(rec.value("title"));
video.append(rec.value("filePath"));
return video;
}

49
src/videosqlmodel.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef VIDEOSQLMODEL_H
#define VIDEOSQLMODEL_H
#include <QSqlTableModel>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qqml.h>
#include <qurl.h>
#include <qvariant.h>
class VideoSqlModel : public QSqlTableModel
{
Q_OBJECT
Q_PROPERTY(int id READ id)
Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged)
Q_PROPERTY(QUrl filePath READ filePath WRITE setFilePath NOTIFY filePathChanged)
QML_ELEMENT
public:
VideoSqlModel(QObject *parent = 0);
int id() const;
QString title() const;
QUrl filePath() const;
void setTitle(const QString &title);
void setFilePath(const QUrl &filePath);
Q_INVOKABLE void updateTitle(const int &row, const QString &title);
Q_INVOKABLE void updateFilePath(const int &row, const QUrl &filePath);
Q_INVOKABLE void newVideo(const QUrl &filePath);
Q_INVOKABLE void deleteVideo(const int &row);
Q_INVOKABLE QVariantList getVideo(const int &row);
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
signals:
void titleChanged();
void filePathChanged();
private:
int m_id;
QString m_title;
QUrl m_filePath;
};
#endif //SONGSQLMODEL_H