adding in a video model and editor
This commit is contained in:
parent
fab9f86b41
commit
c35c0f6550
22 changed files with 972 additions and 160 deletions
|
@ -2,4 +2,5 @@
|
||||||
;;; For more information see (info "(emacs) Directory Variables")
|
;;; For more information see (info "(emacs) Directory Variables")
|
||||||
|
|
||||||
((nil . ((projectile-project-compilation-cmd . "cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -B build/ . && make --dir build/")
|
((nil . ((projectile-project-compilation-cmd . "cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 -B build/ . && make --dir build/")
|
||||||
(projectile-project-run-cmd . "./build/bin/presenter"))))
|
(projectile-project-run-cmd . "./build/bin/presenter")))
|
||||||
|
(c++-mode . ((aggressive-indent-mode . nil))))
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
#+AUTHOR: Chris Cochrun
|
#+AUTHOR: Chris Cochrun
|
||||||
|
|
||||||
* Church Presenter
|
* Church Presenter
|
||||||
This is an attempt at building a church presentation application in Qt/QML. QML provides a very powerful and easy declarative way of creating a UI so it should also be a very simple method of creating on screen slides and presentations. This experiment is to see how difficult it is to rebuild these applications in QML as opposed to other more complicated systems. After digging through the source code of OpenLP, I discovered they are essentially created a web server and rendering a webpage onto the screen to show slides. This felt like a waste of resources and added complexity when something so simple and useful as QML exists.
|
This is an attempt at building a church presentation application in Qt/QML. QML provides a very powerful and easy declarative way of creating a UI so it should also be a very simple method of creating on screen slides and presentations. This experiment is to see how difficult it is to rebuild these applications in QML as opposed to other more complicated systems. After digging through the source code of OpenLP, I discovered they are essentially creating a web server and rendering a webpage onto the screen to show slides. This felt like a waste of resources and added complexity when something so simple and useful as QML exists.
|
||||||
|
|
||||||
** Features (planned are in brackets)
|
** Features (planned are in parentheses)
|
||||||
- Presents songs lyrics with image and video backgrounds
|
- Presents songs lyrics with image and video backgrounds
|
||||||
- Presents slides
|
- Presents slides
|
||||||
- (Custom slide builder)
|
- (Custom slide builder)
|
||||||
|
|
|
@ -3,8 +3,8 @@ add_executable(presenter)
|
||||||
target_sources(presenter
|
target_sources(presenter
|
||||||
PRIVATE
|
PRIVATE
|
||||||
main.cpp resources.qrc
|
main.cpp resources.qrc
|
||||||
songlistmodel.cpp songlistmodel.h
|
|
||||||
songsqlmodel.cpp songsqlmodel.h
|
songsqlmodel.cpp songsqlmodel.h
|
||||||
|
videosqlmodel.cpp videosqlmodel.h
|
||||||
mpv/mpvobject.h mpv/mpvobject.cpp
|
mpv/mpvobject.h mpv/mpvobject.cpp
|
||||||
mpv/qthelper.hpp mpv/mpvhelpers.h
|
mpv/qthelper.hpp mpv/mpvhelpers.h
|
||||||
)
|
)
|
||||||
|
|
11
src/main.cpp
11
src/main.cpp
|
@ -30,9 +30,9 @@
|
||||||
#include <qsqlquery.h>
|
#include <qsqlquery.h>
|
||||||
#include <qstringliteral.h>
|
#include <qstringliteral.h>
|
||||||
|
|
||||||
#include "songlistmodel.h"
|
|
||||||
#include "mpv/mpvobject.h"
|
#include "mpv/mpvobject.h"
|
||||||
#include "songsqlmodel.h"
|
#include "songsqlmodel.h"
|
||||||
|
#include "videosqlmodel.h"
|
||||||
|
|
||||||
static void connectToDatabase() {
|
static void connectToDatabase() {
|
||||||
// let's setup our sql database
|
// let's setup our sql database
|
||||||
|
@ -44,6 +44,7 @@ static void connectToDatabase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
qDebug() << "dir location " << writeDir.absolutePath();
|
||||||
|
|
||||||
if (!writeDir.mkpath(".")) {
|
if (!writeDir.mkpath(".")) {
|
||||||
qFatal("Failed to create writable location at %s", qPrintable(writeDir.absolutePath()));
|
qFatal("Failed to create writable location at %s", qPrintable(writeDir.absolutePath()));
|
||||||
|
@ -79,21 +80,21 @@ int main(int argc, char *argv[])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("system-config-display")));
|
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("system-config-display")));
|
||||||
|
|
||||||
// apparently mpv needs this class set
|
// apparently mpv needs this class set
|
||||||
|
// let's register mpv as well
|
||||||
std::setlocale(LC_NUMERIC, "C");
|
std::setlocale(LC_NUMERIC, "C");
|
||||||
qmlRegisterType<MpvObject>("mpv", 1, 0, "MpvObject");
|
qmlRegisterType<MpvObject>("mpv", 1, 0, "MpvObject");
|
||||||
|
|
||||||
//register our song model from sql
|
//register our models
|
||||||
qmlRegisterType<SongSqlModel>("org.presenter", 1, 0, "SongSqlModel");
|
qmlRegisterType<SongSqlModel>("org.presenter", 1, 0, "SongSqlModel");
|
||||||
|
qmlRegisterType<VideoSqlModel>("org.presenter", 1, 0, "VideoSqlModel");
|
||||||
SongListModel songListModel;
|
|
||||||
|
|
||||||
connectToDatabase();
|
connectToDatabase();
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||||
engine.rootContext()->setContextProperty("_songListModel", &songListModel);
|
|
||||||
engine.load(QUrl(QStringLiteral("qrc:qml/main.qml")));
|
engine.load(QUrl(QStringLiteral("qrc:qml/main.qml")));
|
||||||
|
|
||||||
// QQuickView *view = new QQuickView;
|
// QQuickView *view = new QQuickView;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#include "mpvobject.h"
|
#include "mpvobject.h"
|
||||||
|
|
||||||
// std
|
// std
|
||||||
|
#include <qdir.h>
|
||||||
|
#include <qvariant.h>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <clocale>
|
#include <clocale>
|
||||||
|
|
||||||
|
@ -19,6 +21,7 @@
|
||||||
#include <QtX11Extras/QX11Info>
|
#include <QtX11Extras/QX11Info>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
// libmpv
|
// libmpv
|
||||||
#include <mpv/client.h>
|
#include <mpv/client.h>
|
||||||
|
@ -28,6 +31,7 @@
|
||||||
#include "qthelper.hpp"
|
#include "qthelper.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
const QDir writeDir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
|
||||||
|
|
||||||
//--- MpvRenderer
|
//--- MpvRenderer
|
||||||
void* MpvRenderer::get_proc_address(void *ctx, const char *name) {
|
void* MpvRenderer::get_proc_address(void *ctx, const char *name) {
|
||||||
|
@ -261,11 +265,13 @@ void MpvObject::doUpdate()
|
||||||
|
|
||||||
void MpvObject::command(const QVariant& params)
|
void MpvObject::command(const QVariant& params)
|
||||||
{
|
{
|
||||||
|
// qDebug() << params;
|
||||||
mpv::qt::command(mpv, params);
|
mpv::qt::command(mpv, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MpvObject::commandAsync(const QVariant& params)
|
void MpvObject::commandAsync(const QVariant& params)
|
||||||
{
|
{
|
||||||
|
qDebug() << params;
|
||||||
mpv::qt::command_async(mpv, params);
|
mpv::qt::command_async(mpv, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,6 +528,17 @@ void MpvObject::loadFile(QVariant urls)
|
||||||
command(QVariantList() << "loadfile" << 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)
|
void MpvObject::subAdd(QVariant urls)
|
||||||
{
|
{
|
||||||
command(QVariantList() << "sub-add" << urls);
|
command(QVariantList() << "sub-add" << urls);
|
||||||
|
|
|
@ -135,6 +135,7 @@ public slots:
|
||||||
void stepForward();
|
void stepForward();
|
||||||
void seek(double pos);
|
void seek(double pos);
|
||||||
void loadFile(QVariant urls);
|
void loadFile(QVariant urls);
|
||||||
|
void screenshotToFile(QUrl url);
|
||||||
void subAdd(QVariant urls);
|
void subAdd(QVariant urls);
|
||||||
|
|
||||||
bool enableAudio() const { return m_enableAudio; }
|
bool enableAudio() const { return m_enableAudio; }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Type=Application
|
Type=Application
|
||||||
Name=present
|
Name=present
|
||||||
GenericName=Church Presentation
|
GenericName=Church Presentation
|
||||||
Comment=A Kirigami base church presenter
|
Comment=A Kirigami based church presenter
|
||||||
Exec=present %U
|
Exec=present %U
|
||||||
TryExec=present
|
TryExec=present
|
||||||
Icon=present
|
Icon=present
|
||||||
|
|
|
@ -26,28 +26,28 @@ Kirigami.ApplicationWindow {
|
||||||
pageStack.initialPage: mainPage
|
pageStack.initialPage: mainPage
|
||||||
header: Presenter.Header {}
|
header: Presenter.Header {}
|
||||||
|
|
||||||
menuBar: Controls.MenuBar {
|
/* menuBar: Qt.platform.os !== "linux" ? Controls.MenuBar { */
|
||||||
Controls.Menu {
|
/* Controls.Menu { */
|
||||||
title: qsTr("File")
|
/* title: qsTr("File") */
|
||||||
Controls.MenuItem { text: qsTr("New...") }
|
/* Controls.MenuItem { text: qsTr("New...") } */
|
||||||
Controls.MenuItem { text: qsTr("Open...") }
|
/* Controls.MenuItem { text: qsTr("Open...") } */
|
||||||
Controls.MenuItem { text: qsTr("Save") }
|
/* Controls.MenuItem { text: qsTr("Save") } */
|
||||||
Controls.MenuItem { text: qsTr("Save As...") }
|
/* Controls.MenuItem { text: qsTr("Save As...") } */
|
||||||
Controls.MenuSeparator { }
|
/* Controls.MenuSeparator { } */
|
||||||
Controls.MenuItem { text: qsTr("Quit") }
|
/* Controls.MenuItem { text: qsTr("Quit") } */
|
||||||
}
|
/* } */
|
||||||
Controls.Menu {
|
/* Controls.Menu { */
|
||||||
title: qsTr("Settings")
|
/* title: qsTr("Settings") */
|
||||||
Controls.MenuItem {
|
/* Controls.MenuItem { */
|
||||||
text: qsTr("Configure")
|
/* text: qsTr("Configure") */
|
||||||
onTriggered: openSettings()
|
/* onTriggered: openSettings() */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } */
|
||||||
Controls.Menu {
|
/* Controls.Menu { */
|
||||||
title: qsTr("Help")
|
/* title: qsTr("Help") */
|
||||||
Controls.MenuItem { text: qsTr("About") }
|
/* Controls.MenuItem { text: qsTr("About") } */
|
||||||
}
|
/* } */
|
||||||
}
|
/* } : null */
|
||||||
|
|
||||||
Labs.MenuBar {
|
Labs.MenuBar {
|
||||||
Labs.Menu {
|
Labs.Menu {
|
||||||
|
@ -81,7 +81,7 @@ Kirigami.ApplicationWindow {
|
||||||
|
|
||||||
function toggleEditMode() {
|
function toggleEditMode() {
|
||||||
editMode = !editMode;
|
editMode = !editMode;
|
||||||
mainPage.editSwitch(editMode);
|
mainPage.editSwitch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLibrary() {
|
function toggleLibrary() {
|
||||||
|
|
230
src/qml/presenter/ImageEditor.qml
Normal file
230
src/qml/presenter/ImageEditor.qml
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -109,6 +109,7 @@ ColumnLayout {
|
||||||
showPassiveNotification(serviceItemList.currentIndex);
|
showPassiveNotification(serviceItemList.currentIndex);
|
||||||
changeSlideBackground(background, backgroundType);
|
changeSlideBackground(background, backgroundType);
|
||||||
changeSlideText(text);
|
changeSlideText(text);
|
||||||
|
changeSlideType(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
import QtQuick 2.13
|
import QtQuick 2.13
|
||||||
import QtQuick.Controls 2.0 as Controls
|
import QtQuick.Controls 2.0 as Controls
|
||||||
import QtQuick.Layouts 1.2
|
import QtQuick.Layouts 1.2
|
||||||
|
import Qt.labs.platform 1.1 as Labs
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import org.kde.kirigami 2.13 as Kirigami
|
||||||
import "./" as Presenter
|
import "./" as Presenter
|
||||||
import org.presenter 1.0
|
import org.presenter 1.0
|
||||||
|
import mpv 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string selectedLibrary: "songs"
|
property string selectedLibrary: "songs"
|
||||||
|
property bool overlay: false
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
|
||||||
|
@ -292,10 +295,37 @@ Item {
|
||||||
opacity: 1.0
|
opacity: 1.0
|
||||||
|
|
||||||
Controls.Label {
|
Controls.Label {
|
||||||
|
id: videoLabel
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Videos"
|
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 {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
@ -313,6 +343,9 @@ Item {
|
||||||
Layout.preferredHeight: parent.height - 200
|
Layout.preferredHeight: parent.height - 200
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
|
model: videosqlmodel
|
||||||
|
delegate: videoDelegate
|
||||||
|
clip: true
|
||||||
state: "deselected"
|
state: "deselected"
|
||||||
|
|
||||||
states: [
|
states: [
|
||||||
|
@ -339,6 +372,109 @@ Item {
|
||||||
duration: 300
|
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 {
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ Controls.Page {
|
||||||
property string songVorder: ""
|
property string songVorder: ""
|
||||||
property int blurRadius: 0
|
property int blurRadius: 0
|
||||||
|
|
||||||
|
/* property var video */
|
||||||
|
|
||||||
property string dragSongTitle: ""
|
property string dragSongTitle: ""
|
||||||
|
|
||||||
property bool editing: true
|
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 {
|
Loader {
|
||||||
id: presentLoader
|
id: presentLoader
|
||||||
active: presenting
|
active: presenting
|
||||||
|
@ -152,6 +168,19 @@ Controls.Page {
|
||||||
id: songsqlmodel
|
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) {
|
function changeSlideText(text) {
|
||||||
/* showPassiveNotification("used to be: " + presentation.text); */
|
/* showPassiveNotification("used to be: " + presentation.text); */
|
||||||
presentation.text = text;
|
presentation.text = text;
|
||||||
|
@ -193,11 +222,24 @@ Controls.Page {
|
||||||
showPassiveNotification("previous slide please")
|
showPassiveNotification("previous slide please")
|
||||||
}
|
}
|
||||||
|
|
||||||
function editSwitch(edit) {
|
function editSwitch(editType, item) {
|
||||||
if (edit)
|
if (editMode) {
|
||||||
mainPageArea.push(songEditorComp, Controls.StackView.Immediate)
|
switch (editType) {
|
||||||
else
|
case "song" :
|
||||||
mainPageArea.pop(Controls.StackView.Immediate)
|
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) {
|
function present(present) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string text
|
property string text
|
||||||
|
property string itemType
|
||||||
property url imagebackground
|
property url imagebackground
|
||||||
property url vidbackground
|
property url vidbackground
|
||||||
|
|
||||||
|
@ -80,6 +81,7 @@ Item {
|
||||||
Layout.alignment: Qt.AlignCenter
|
Layout.alignment: Qt.AlignCenter
|
||||||
textSize: width / 15
|
textSize: width / 15
|
||||||
text: root.text
|
text: root.text
|
||||||
|
itemType: root.itemType
|
||||||
imageSource: imagebackground
|
imageSource: imagebackground
|
||||||
videoSource: vidbackground
|
videoSource: vidbackground
|
||||||
preview: true
|
preview: true
|
||||||
|
|
|
@ -45,10 +45,10 @@ Item {
|
||||||
enableAudio: !preview
|
enableAudio: !preview
|
||||||
Component.onCompleted: mpvLoadingTimer.start()
|
Component.onCompleted: mpvLoadingTimer.start()
|
||||||
onFileLoaded: {
|
onFileLoaded: {
|
||||||
print(videoSource + " has been loaded");
|
showPassiveNotification(videoSource + " has been loaded");
|
||||||
if (itemType == "song")
|
if (itemType == "song")
|
||||||
mpv.setProperty("loop", "inf");
|
mpv.setProperty("loop", "inf");
|
||||||
print(mpv.getProperty("loop"));
|
showPassiveNotification(mpv.getProperty("loop"));
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|
227
src/qml/presenter/VideoEditor.qml
Normal file
227
src/qml/presenter/VideoEditor.qml
Normal 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]); */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,8 @@
|
||||||
<file>qml/presenter/Actions.qml</file>
|
<file>qml/presenter/Actions.qml</file>
|
||||||
<file>qml/presenter/PanelItem.qml</file>
|
<file>qml/presenter/PanelItem.qml</file>
|
||||||
<file>qml/presenter/SongEditor.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/Slide.qml</file>
|
||||||
<file>qml/presenter/SlideEditor.qml</file>
|
<file>qml/presenter/SlideEditor.qml</file>
|
||||||
<file>qml/presenter/DragHandle.qml</file>
|
<file>qml/presenter/DragHandle.qml</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;
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -92,9 +92,9 @@ void SongSqlModel::newSong() {
|
||||||
|
|
||||||
if (insertRecord(rows, recorddata)) {
|
if (insertRecord(rows, recorddata)) {
|
||||||
submitAll();
|
submitAll();
|
||||||
}else {
|
} else {
|
||||||
qDebug() << lastError();
|
qDebug() << lastError();
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void SongSqlModel::deleteSong(const int &row) {
|
void SongSqlModel::deleteSong(const int &row) {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
#define SONGSQLMODEL_H
|
#define SONGSQLMODEL_H
|
||||||
|
|
||||||
#include <QSqlTableModel>
|
#include <QSqlTableModel>
|
||||||
#include <qabstractitemmodel.h>
|
|
||||||
#include <qobjectdefs.h>
|
#include <qobjectdefs.h>
|
||||||
#include <qqml.h>
|
#include <qqml.h>
|
||||||
#include <qvariant.h>
|
#include <qvariant.h>
|
||||||
|
|
167
src/videosqlmodel.cpp
Normal file
167
src/videosqlmodel.cpp
Normal 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
49
src/videosqlmodel.h
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue