Merge remote-tracking branch 'origin/master'

This commit is contained in:
Chris Cochrun 2022-05-24 10:11:27 -05:00
commit f4a41cd4c6
28 changed files with 1248 additions and 349 deletions

View file

@ -13,7 +13,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(KDEInstallDirs)
include(KDECMakeSettings)
@ -27,6 +27,10 @@ kde_enable_exceptions()
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Test Gui QuickControls2 Widgets Sql X11Extras)
find_package(KF5 ${KF_MIN_VERSION} REQUIRED COMPONENTS Kirigami2 I18n CoreAddons)
find_package(Libmpv)
set_package_properties(Libmpv PROPERTIES TYPE REQUIRED)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

View file

@ -7,12 +7,12 @@ This is an attempt at building a church presentation application in Qt/QML. QML
** Features (planned are in parentheses)
- Presents songs lyrics with image and video backgrounds
- Use MPV as a rendering system for videos
- Simple song creation with a powerful text parser - Almost fully finished
- (Present Slides)
- (Custom slide builder)
- (Simple song creation with a powerful text parser)
** MPV
You will need MPV installed in order to use this. All videos run through it. This, however, enables us to make videos work very well and with a lot of control and since it uses ffmpeg underneath, nearly any codec regardless of underlying system. This prevents the need for the user to go and install other codecs to work with Windows or MacOS. It also means a much easier control system and the potential to stream web content as well.
You will need MPV installed in order to use this. All videos run through it. This, however, enables us to make videos work very well and with a lot of control and since it uses ffmpeg underneath, nearly any codec regardless of underlying system. This prevents the need for the user to install other codecs to work with Windows or MacOS. It also means a much easier control system and the potential to stream web content as well without downloading first.
* Build and Run
First get the source code
@ -33,4 +33,4 @@ Then run.
#+END_SRC
* Contact Me
If, for whatever reason, you need to contact me and get something ironed out, please do so at [[mailto:chris@tfcconnection.org][chris@tfcconnection.org]]
If, for whatever reason, you need to contact me and get something ironed out, please do so at [[mailto:chris@tfcconnection.org][chris@cochrun.xyz]]

100
TODO.org
View file

@ -1,11 +1,105 @@
#+TITLE: Todo List
:PROPERTIES:
:CATEGORY: dev
:END:
* Inbox
** TODO Need to make ListModel capable of bringing in a string list [2/4] [50%]
** TODO Need to make =getLyricList= give back the verses with empty lines as separate slides :core:
[[file:~/dev/church-presenter/src/songsqlmodel.cpp:://TODO make sure to split empty line in verse into two slides]]
** TODO Make toolbar functional for =songeditor= [3/4] [75%] :core:
[[file:~/dev/church-presenter/src/qml/presenter/SongEditor.qml::Controls.ToolBar {]]
- [X] alignment
- [X] font - Need to finish the UI portion of it
- [X] fontsize - Need to finish the UI portion of it
- [ ] effects?
For effects, I'm not 100% sure how to do this in an easy to build out way. Should I just do them the same as the other attributes or have effects be individually stored? Which effects to use?
I'm thinking shadows for sure for readability on slides. Also, maybe I should have an effect of like glow? But maybe I'll come back to this after more of the core system is finished.
** TODO bug in changing slides with the arrows :core:
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::function changeSlide() {]]
slides are inconsistent in changing from one slide to the next or previous. Both functions need looked at.
Maybe my best solution would be to architect a model or class for both the presentation controller and the presentation window to follow and do all the heavy lifting in there.
** TODO Check for edge cases in inputing wrong vorder and lyrics :core:
[[file:~/dev/church-presenter/TODO.org::*Fix broken append when importing River song][Fix broken append when importing River song]]
** TODO Images stored in sql need to have aspect saved and applied dynamically here :core:
[[file:~/dev/church-presenter/src/qml/presenter/Slide.qml::fillMode: Image.PreserveAspectCrop]]
** TODO Build out a slide preview system so we can see each slide in the song or image slideshow :ui:
[[file:~/dev/church-presenter/src/qml/presenter/SongEditor.qml::Presenter.SlideEditor {]]
- [X] Initial ListView with text coming from =getLyricList=
- [ ] Depending on this [[*Need to make getLyricList give back the verses with empty lines as separate slides][Need to make getLyricList give back the verses with empty lines as separate slides]]
- [ ] Need to perhaps address the MPV crashing problem for a smoother experience.
Essentially, mpv objects cause a seg fault when we remove them from the qml graph scene and are somehow re-referencing them. Using =reuseItems=, I can prevent the seg fault but then we are storing a lot of things in memory and will definitely cause slowdowns on older hardware.
** TODO Fix possible bug in arrangingItems in draghandler [1/3] [33%] :bug:
[[file:~/dev/church-presenter/src/qml/presenter/DragHandle.qml::function arrangeItem() {]]
- [X] Basic fixed drag n drop
- [ ] Allow for a less buggy interaction
- [ ] Need to check for edge cases
** PROJ [#A] Make Presentation Window follow the presenter component :core:
[[file:~/dev/church-presenter/src/qml/presenter/MainWindow.qml::Presenter.Slide {]]
** TODO Finish toolbar in presentation display :ui:
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::Controls.ToolBar {]]
** TODO Find a way to maths the textsize :slide:
[[file:~/dev/church-presenter/src/qml/presenter/Slide.qml::property real textSize: 50]]
This may not be as needed. Apparently the text shrinks to fit it's space.
** TODO Create a nextslide function to be used after the end of the list of slides :slide:
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::function nextSlide() {]]
- [ ] Check to make sure this works in all conditions but I believe it works ok.
** TODO Make sure the video gets changed in a proper manner to not have left over video showing from previous items :video:slide:
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::currentServiceItem++;]]
- [X] Build a basic system that changes to black first and then switches to the video
- [ ] Build out a loading system that will load the next video if it needs to and then the switch can be instant.
The second option is the best, but requires a lot more work. I have the first already working so I'll come back to this once I have more of an idea of how to do it.
** DONE images and videos need a better get system
[[file:~/dev/church-presenter/src/videosqlmodel.cpp::QVariantList VideoSqlModel::getVideo(const int &row) {]]
** DONE Fix broken append when importing River song
[[file:~/dev/church-presenter/src/qml/presenter/LeftDock.qml::function appendItem(name, type, background, backgroundType, text, itemID) {]]
This was due to the song not having a vorder. Need to protect from edge cases of the user inputing the formatted text that doesn't fit what's expected in code.
** DONE implement previousSlide and previousAction
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::function nextSlide() {]]
** DONE Need to make ListModel capable of bringing in a string list [2/2] [100%]
- [X] Create a Model
- [X] Create a class that we'll make a list of in the model
- [ ] Implement move functions
- [ ] Implement insert functions
** DONE Make an image sql model
[[file:~/dev/church-presenter/src/videosqlmodel.h::ifndef VIDEOSQLMODEL_H]]
** DONE Parse Lyrics to create a list of strings for slides
SCHEDULED: <2022-03-23 Wed 10:00>
** DONE BUG in dropping and then selecting song will duplicate entries :dev:
SCHEDULED: <2022-04-05 Tue>
[[file:~/dev/church-presenter/src/qml/presenter/LeftDock.qml::Layout.fillHeight: true]]
or at least turns the entry above it into the same as itself while retaining it's title?
** DONE Make nextSlideText a nextAction function to incorporate other types of items
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::function nextSlideText() {]]
** DONE Fix file dialog using basic QT theme
[[file:~/dev/church-presenter/src/qml/presenter/SongEditor.qml::FileDialog {]]

41
flake.lock generated Normal file
View file

@ -0,0 +1,41 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1652776076,
"narHash": "sha256-gzTw/v1vj4dOVbpBSJX4J0DwUR6LIyXo7/SuuTJp1kM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "04c1b180862888302ddfb2e3ad9eaa63afc60cf8",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1653117584,
"narHash": "sha256-5uUrHeHBIaySBTrRExcCoW8fBBYVSDjDYDU5A6iOl+k=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f4dfed73ee886b115a99e5b85fdfbeb683290d83",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

14
flake.nix Normal file
View file

@ -0,0 +1,14 @@
{
description = "A Church Presentation Application";
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem
(system:
let pkgs = nixpkgs.legacyPackages.${system}; in
{
devShell = import ./shell.nix { inherit pkgs; };
}
);
}

42
shell.nix Normal file
View file

@ -0,0 +1,42 @@
{ pkgs ? <nixpkgs> { } }:
with pkgs;
mkShell {
name = "presenter-env";
nativeBuildInputs = [
gcc
gnumake
clang
cmake
extra-cmake-modules
pkg-config
libsForQt5.wrapQtAppsHook
# gccStdenv
# stdenv
];
buildInputs = [
qt5.qtbase
qt5.qttools
qt5.qtquickcontrols2
qt5.qtx11extras
qt5.qtmultimedia
libsForQt5.kirigami2
libsForQt5.ki18n
libsForQt5.kcoreaddons
mpv
# libsForQt5.kconfig
# ffmpeg-full
# yt-dlp
];
# This creates the proper qt env so that plugins are found right.
shellHook = ''
setQtEnvironment=$(mktemp --suffix .setQtEnvironment.sh)
echo "shellHook: setQtEnvironment = $setQtEnvironment"
makeWrapper "/bin/sh" "$setQtEnvironment" "''${qtWrapperArgs[@]}"
sed "/^exec/d" -i "$setQtEnvironment"
source "$setQtEnvironment"
fish
'';
}

View file

@ -7,6 +7,7 @@ target_sources(presenter
serviceitemmodel.cpp serviceitemmodel.h
serviceitem.cpp serviceitem.h
videosqlmodel.cpp videosqlmodel.h
imagesqlmodel.cpp imagesqlmodel.h
mpv/mpvobject.h mpv/mpvobject.cpp
mpv/qthelper.hpp mpv/mpvhelpers.h
)

View file

@ -22,10 +22,13 @@
#include <QtQuick/QQuickWindow>
#include <QtQuick/QQuickView>
#include <qapplication.h>
#include <qcoreapplication.h>
#include <qdir.h>
#include <qglobal.h>
#include <qguiapplication.h>
#include <qqml.h>
#include <qquickstyle.h>
#include <qsqldatabase.h>
#include <qsqlquery.h>
#include <qstringliteral.h>
@ -34,6 +37,7 @@
#include "serviceitemmodel.h"
#include "songsqlmodel.h"
#include "videosqlmodel.h"
#include "imagesqlmodel.h"
static void connectToDatabase() {
// let's setup our sql database
@ -67,20 +71,25 @@ static void connectToDatabase() {
int main(int argc, char *argv[])
{
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
KLocalizedString::setApplicationDomain("presenter");
QCoreApplication::setOrganizationName(QStringLiteral("presenter"));
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain("librepresenter");
QCoreApplication::setOrganizationName(QStringLiteral("librepresenter"));
QCoreApplication::setOrganizationDomain(QStringLiteral("tfcconnection.org"));
QCoreApplication::setApplicationName(QStringLiteral("Church Presenter"));
QCoreApplication::setApplicationName(QStringLiteral("Libre Presenter"));
#ifdef Q_OS_WINDOWS
QIcon::setFallbackThemeName("breeze");
QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
// QApplication::setStyle(QStringLiteral("breeze"));
#else
QIcon::setFallbackThemeName("breeze");
QQuickStyle::setStyle(QStringLiteral("org.kde.desktop"));
QQuickStyle::setFallbackStyle(QStringLiteral("breeze"));
#endif
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("system-config-display")));
qDebug() << QQuickStyle::availableStyles();
qDebug() << QIcon::themeName();
// apparently mpv needs this class set
// let's register mpv as well
@ -90,6 +99,7 @@ int main(int argc, char *argv[])
//register our models
qmlRegisterType<SongSqlModel>("org.presenter", 1, 0, "SongSqlModel");
qmlRegisterType<VideoSqlModel>("org.presenter", 1, 0, "VideoSqlModel");
qmlRegisterType<ImageSqlModel>("org.presenter", 1, 0, "ImageSqlModel");
qmlRegisterType<ServiceItemModel>("org.presenter", 1, 0, "ServiceItemModel");
connectToDatabase();

View file

@ -87,7 +87,7 @@ MpvRenderer::~MpvRenderer() {
if (mpv_gl)
mpv_render_context_free(mpv_gl);
mpv_terminate_destroy(obj->mpv);
// mpv_destroy(obj->mpv);
}
void MpvRenderer::render() {
@ -242,7 +242,7 @@ MpvObject::MpvObject(QQuickItem *parent)
MpvObject::~MpvObject()
{
// quit();
}
QQuickFramebufferObject::Renderer *MpvObject::createRenderer() const
@ -340,6 +340,10 @@ void MpvObject::handle_mpv_event(mpv_event *event)
// See: https://github.com/mpv-player/mpv/blob/master/player/lua.c#L471
switch (event->event_id) {
case MPV_EVENT_SHUTDOWN: {
mpv_destroy(mpv);
break;
}
case MPV_EVENT_LOG_MESSAGE: {
mpv_event_log_message *logData = (mpv_event_log_message *)event->data;
Q_EMIT logMessage(
@ -487,7 +491,7 @@ void MpvObject::pause()
{
// qDebug() << "pause";
if (isPlaying()) {
// qDebug() << "!isPlaying";
qDebug() << "!isPlaying";
set_paused(true);
}
}

View file

@ -5,7 +5,7 @@ import Qt.labs.platform 1.1 as Labs
import QtQuick.Window 2.13
import QtQuick.Layouts 1.2
import QtMultimedia 5.15
import QtAudioEngine 1.15
/* import QtAudioEngine 1.15 */
import org.kde.kirigami 2.13 as Kirigami
import "./presenter" as Presenter
@ -121,6 +121,7 @@ Kirigami.ApplicationWindow {
/* print(Qt.application.state); */
screens = Qt.application.screens;
presentationScreen = screens[1]
print(Kirigami.Settings.Style);
for (let i = 0; i < screens.length; i++) {
/* print(screens[i]); */
/* print(screens[i].name); */

View file

@ -4,7 +4,7 @@ import QtQuick.Controls 2.15 as Controls
import QtQuick.Window 2.13
import QtQuick.Layouts 1.2
import QtMultimedia 5.15
import QtAudioEngine 1.15
/* import QtAudioEngine 1.15 */
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter

View file

@ -38,6 +38,7 @@ Item {
// Emitted when clicking to activate underneath mousearea
signal clicked()
signal rightClicked()
MouseArea {
id: mouseArea
@ -55,12 +56,15 @@ Item {
property int mouseDownY
property Item originalParent
property int autoScrollThreshold: (listView.contentHeight > listView.height) ? listItem.height * 3 : 0
opacity: mouseArea.pressed || (!Kirigami.Settings.tabletMode && listItem.hovered) ? 1 : 0.6
function arrangeItem() {
var newIndex = listView.indexAt(1, listView.contentItem.mapFromItem(listItem, 0, 0).y + mouseArea.mouseDownY);
var newIndex = listView.indexAt(1,
listView.contentItem.mapFromItem(listItem, 0, 0).y +
mouseArea.mouseDownY);
if (Math.abs(listItem.y - mouseArea.startY) > height && newIndex > -1 && newIndex !== index) {
if (Math.abs(listItem.y - mouseArea.startY) > height && newIndex > -1 &&
newIndex !== index) {
print("old index is: " + index + " and new index is: " + newIndex);
root.moveRequested(index, newIndex);
}
}
@ -127,10 +131,16 @@ Item {
MouseArea {
id: clickArea
anchors.fill: parent
onClicked: root.clicked()
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onEntered: root.containsMouse = true
onExited: root.containsMouse = false
onClicked: {
if (mouse.button === Qt.RightButton)
root.rightClicked();
else
root.clicked();
}
}
}
}

View file

@ -8,6 +8,9 @@ import "./" as Presenter
Item {
id: root
property string type: "image"
property var image
GridLayout {
id: mainLayout
anchors.fill: parent
@ -27,7 +30,7 @@ Item {
implicitWidth: 300
editable: true
hoverEnabled: true
onCurrentTextChanged: showPassiveNotification(currentText)
/* onCurrentTextChanged: showPassiveNotification(currentText) */
}
Controls.SpinBox {
editable: true
@ -40,18 +43,6 @@ Item {
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 {}
@ -63,7 +54,7 @@ Item {
}
Controls.ToolButton {
id: backgroundButton
text: "Background"
text: "Select Image"
icon.name: "fileopen"
hoverEnabled: true
onClicked: backgroundType.open()
@ -83,15 +74,15 @@ Item {
border.color: Kirigami.Theme.activeBackgroundColor
border.width: 2
}
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
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: videoFileDialog.open() & backgroundType.close()
text: "Image"
icon.name: "emblem-images-symbolic"
onClicked: imageFileDialog.open() & backgroundType.close()
}
Controls.ToolButton {
Layout.fillWidth: true
@ -121,11 +112,11 @@ Item {
ColumnLayout {
Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 500
Controls.SplitView.minimumWidth: 500
Controls.SplitView.preferredWidth: 300
Controls.SplitView.minimumWidth: 100
Controls.TextField {
id: songTitleField
id: imageTitleField
Layout.preferredWidth: 300
Layout.fillWidth: true
@ -133,98 +124,46 @@ Item {
Layout.rightMargin: 20
placeholderText: "Song Title..."
text: songTitle
text: image.title
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);
/* onEditingFinished: updateTitle(text); */
}
Controls.ScrollView {
id: songLyricsField
Layout.preferredHeight: 3000
Layout.fillWidth: true
Item {
id: empty
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
spacing: 5
Rectangle {
id: slideBar
color: Kirigami.Theme.highlightColor
Layout.preferredWidth: 500
Layout.preferredHeight: songTitleField.height
Layout.rightMargin: 20
Layout.leftMargin: 20
Item {
id: topEmpty
Layout.fillHeight: true
}
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
Image {
id: imagePreview
Layout.preferredWidth: 600
Layout.preferredHeight: Layout.preferredWidth / 16 * 9
Layout.alignment: Qt.AlignCenter
fillMode: Image.PreserveAspectFit
source: image.filePath
}
Item {
id: botEmpty
Layout.fillHeight: true
}
}
}
}
}
Timer {
id: editorTimer
interval: 1000
repeat: true
running: false
onTriggered: updateLyrics(lyricsEditor.text)
function changeImage(image) {
root.image = image;
print(image.filePath.toString());
}
}

View file

@ -3,8 +3,9 @@ import QtQuick.Dialogs 1.0
import QtQuick.Controls 2.0 as Controls
import QtQuick.Window 2.13
import QtQuick.Layouts 1.2
import QtQml.Models 2.12
import QtMultimedia 5.15
import QtAudioEngine 1.15
/* import QtAudioEngine 1.15 */
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
@ -34,6 +35,7 @@ ColumnLayout {
Layout.fillHeight: true
Layout.fillWidth: true
onDropped: {
print("DROPPED AT END");
appendItem(dragItemTitle,
dragItemType,
dragItemBackground,
@ -46,13 +48,14 @@ ColumnLayout {
ListView {
id: serviceItemList
anchors.fill: parent
model: serviceItemModel
delegate: Kirigami.DelegateRecycler {
width: serviceItemList.width
sourceComponent: itemDelegate
}
/* model: serviceItemModel */
/* delegate: Kirigami.DelegateRecycler { */
/* width: serviceItemList.width */
/* sourceComponent: itemDelegate */
/* } */
clip: true
spacing: 3
property int dragItemIndex
addDisplaced: Transition {
NumberAnimation {properties: "x, y"; duration: 100}
@ -73,68 +76,182 @@ ColumnLayout {
NumberAnimation {properties: "x, y"; duration: 100}
}
Component {
id: itemDelegate
Item {
id: serviceItem
model: DelegateModel {
id: visualModel
model: serviceItemModel
delegate: DropArea {
id: serviceDrop
implicitWidth: serviceItemList.width
height: 50
onEntered: (drag) => {
/* dropPlacement(drag); */
const from = (drag.source as visServiceItem)
visualModel.items.move(dragItemIndex, index);
}
onDropped: (drag) => {
print("DROPPED IN ITEM AREA: " + drag.keys);
print(dragItemIndex + " " + index);
const hlIndex = serviceItemList.currentIndex;
if (drag.keys === ["library"]) {
addItem(index,
dragItemTitle,
dragItemType,
dragItemBackground,
dragItemBackgroundType,
dragItemText,
dragItemIndex);
} else if (drag.keys === ["serviceitem"]) {
moveRequested(dragItemIndex, index);
if (hlIndex === dragItemIndex)
serviceItemList.currentIndex = index;
else if (hlIndex === index)
serviceItemList.currentIndex = index + 1;
}
}
keys: ["library","serviceitem"]
Kirigami.BasicListItem {
anchors.fill: parent
id: visServiceItem
width: serviceDrop.width
height: serviceDrop.height
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
label: name
subtitle: type
hoverEnabled: true
hoverEnabled: false
supportsMouseEvents: false
backgroundColor: {
if (parent.ListView.isCurrentItem) {
if (serviceItemList.currentIndex === index ||
mouseHandler.containsMouse)
Kirigami.Theme.highlightColor;
/* } else if (serviceDrop.constainsDrag){ */
/* Kirigami.Theme.hoverColor; */
} else if (mouseHandler.containsMouse){
Kirigami.Theme.highlightColor;
} else {
else
Kirigami.Theme.backgroundColor;
}
}
textColor: {
if (parent.ListView.isCurrentItem || mouseHandler.containsMouse)
if (serviceItemList.currentIndex === index ||
mouseHandler.containsMouse)
activeTextColor;
else
Kirigami.Theme.textColor;
}
states: [
State {
when: mouseHandler.drag.active
ParentChange {
target: visServiceItem
parent: serviceItemList
}
Presenter.DragHandle {
PropertyChanges {
target: visServiceItem
backgroundColor: Kirigami.Theme.backgroundColor
textColor: Kirigami.Theme.textColor
anchors.verticalCenter: undefined
anchors.horizontalCenter: undefined
}
}
]
/* Drag.dragType: Drag.Automatic */
Drag.active: mouseHandler.drag.active
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Drag.keys: ["serviceitem"]
MouseArea {
id: mouseHandler
anchors.fill: parent
listItem: serviceItem
listView: serviceItemList
onMoveRequested: serviceItemModel.move(oldIndex, newIndex)
onClicked: {
serviceItemList.currentIndex = index;
/* showPassiveNotification(serviceItemList.currentIndex); */
changeSlideBackground(background, backgroundType);
changeSlideText(text);
changeSlideType(type);
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
preventStealing: true
drag {
target: visServiceItem
axis: Drag.YAxis
/* minimumY: root.y */
/* maximumY: serviceItemList.height - serviceDrop.height */
smoothed: false
}
drag.onActiveChanged: {
if (mouseHandler.drag.active) {
dragItemIndex = index;
}
}
DropArea {
id: serviceDrop
anchors.fill: parent
onDropped: {
addItem(index,
dragItemTitle,
dragItemType,
dragItemText,
dragItemBackgroundType,
dragItemBackground,
dragItemIndex);
/* onPositionChanged: { */
/* if (!pressed) { */
/* return; */
/* } */
/* mouseArea.arrangeItem(); */
/* } */
onPressed: {
serviceItemList.interactive = false;
}
keys: ["library"]
onClicked: {
if (mouse.button === Qt.RightButton)
rightClickMenu.popup();
else {
serviceItemList.currentIndex = index;
currentServiceItem = index;
changeServiceItem(index);
}
}
onReleased: {
print("should drop");
visServiceItem.Drag.drop();
}
}
}
/* Presenter.DragHandle { */
/* id: mouseHandler */
/* anchors.fill: parent */
/* listItem: serviceItem */
/* listView: serviceItemList */
/* onMoveRequested: { */
/* print(oldIndex, newIndex); */
/* serviceItemModel.move(oldIndex, newIndex); */
/* } */
/* onDropped: { */
/* } */
/* onClicked: { */
/* serviceItemList.currentIndex = index; */
/* currentServiceItem = index; */
/* changeServiceItem(index); */
/* } */
/* onRightClicked: rightClickMenu.popup() */
/* } */
Controls.Menu {
id: rightClickMenu
x: mouseHandler.mouseX
y: mouseHandler.mouseY + 10
Kirigami.Action {
text: "delete"
onTriggered: serviceItemModel.removeItem(index)
}
}
function moveRequested(oldIndex, newIndex) {
serviceItemModel.move(oldIndex, newIndex);
}
function dropPlacement(drag) {
print(drag.y);
}
}
}
Kirigami.WheelHandler {
id: wheelHandler
target: serviceItemList
@ -150,16 +267,21 @@ ColumnLayout {
}
}
function addItem(index, name, type,
background, backgroundType, text, itemID) {
const newtext = songsqlmodel.getLyricList(itemID);
print("adding: " + name + " of type " + type);
serviceItemModel.insertItem(index, name,
type, text, background,
backgroundType)
type, background,
backgroundType, newtext);
}
function appendItem(name, type, background, backgroundType, text, itemID) {
print("adding: " + name + " of type " + type);
let lyrics;
if (type == "song") {
if (type === "song") {
print(itemID);
lyrics = songsqlmodel.getLyricList(itemID);
print(lyrics);
}

View file

@ -195,9 +195,14 @@ Item {
target: songListItem
x: x
y: y
width: width
height: height
}
ParentChange {
target: videoListItem
parent: rootApp.overlay
}
}
}
MouseArea {
@ -411,6 +416,12 @@ Item {
target: videoListItem
x: x
y: y
width: width
height: height
}
ParentChange {
target: videoListItem
parent: rootApp.overlay
}
}
@ -474,12 +485,40 @@ Item {
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
color: Kirigami.Theme.backgroundColor
opacity: 1.0
Controls.Label {
id: imageLabel
anchors.centerIn: parent
text: "Images"
}
Controls.Label {
id: imageCount
anchors {left: imageLabel.right
verticalCenter: imageLabel.verticalCenter
leftMargin: 15}
text: imagesqlmodel.rowCount()
font.pixelSize: 15
color: Kirigami.Theme.disabledTextColor
}
Kirigami.Icon {
id: imageDrawerArrow
anchors {right: parent.right
verticalCenter: imageCount.verticalCenter
rightMargin: 10}
source: "arrow-down"
rotation: selectedLibrary == "images" ? 0 : 180
Behavior on rotation {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
@ -497,6 +536,9 @@ Item {
Layout.preferredHeight: parent.height - 200
Layout.fillWidth: true
Layout.alignment: Qt.AlignTop
model: imagesqlmodel
delegate: imageDelegate
clip: true
state: "deselected"
states: [
@ -523,6 +565,118 @@ Item {
duration: 300
}
}
Component {
id: imageDelegate
Item{
implicitWidth: ListView.view.width
height: selectedLibrary == "images" ? 50 : 0
Kirigami.BasicListItem {
id: imageListItem
property bool rightMenu: false
implicitWidth: imageLibraryList.width
height: selectedLibrary == "images" ? 50 : 0
clip: true
label: title
/* subtitle: author */
supportsMouseEvents: false
backgroundColor: {
if (parent.ListView.isCurrentItem) {
Kirigami.Theme.highlightColor;
} else if (imageDragHandler.containsMouse){
Kirigami.Theme.highlightColor;
} else {
Kirigami.Theme.backgroundColor;
}
}
textColor: {
if (parent.ListView.isCurrentItem || imageDragHandler.containsMouse)
activeTextColor;
else
Kirigami.Theme.textColor;
}
Behavior on height {
NumberAnimation {
easing.type: Easing.OutCubic
duration: 300
}
}
Drag.active: imageDragHandler.drag.active
Drag.hotSpot.x: width / 2
Drag.hotSpot.y: height / 2
Drag.keys: [ "library" ]
states: State {
name: "dragged"
when: imageListItem.Drag.active
PropertyChanges {
target: imageListItem
x: x
y: y
width: width
height: height
}
ParentChange {
target: imageListItem
parent: rootApp.overlay
}
}
}
MouseArea {
id: imageDragHandler
anchors.fill: parent
hoverEnabled: true
drag {
target: imageListItem
onActiveChanged: {
if (imageDragHandler.drag.active) {
dragItemTitle = title;
dragItemType = "image";
dragItemText = "";
dragItemBackgroundType = "image";
dragItemBackground = filePath;
} else {
imageListItem.Drag.drop()
}
}
filterChildren: true
threshold: 10
}
MouseArea {
id: imageClickHandler
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if(mouse.button == Qt.RightButton)
rightClickImageMenu.popup()
else{
imageLibraryList.currentIndex = index
const image = imagesqlmodel.getImage(imageLibraryList.currentIndex);
if (!editMode)
editMode = true;
editType = "image";
editSwitch(image);
}
}
}
}
Controls.Menu {
id: rightClickImageMenu
x: imageClickHandler.mouseX
y: imageClickHandler.mouseY + 10
Kirigami.Action {
text: "delete"
onTriggered: imagesqlmodel.deleteImage(index)
}
}
}
}
}
Rectangle {
@ -666,7 +820,7 @@ Item {
videoLibraryList.currentIndex = videosqlmodel.rowCount();
print(videosqlmodel.getVideo(videoLibraryList.currentIndex));
const video = videosqlmodel.getVideo(videoLibraryList.currentIndex);
showPassiveNotification("newest video: " + video);
showPassiveNotification("newest video: " + video.title);
if (!editMode)
editMode = true;
editSwitch("video", video);

View file

@ -12,6 +12,7 @@ Controls.Page {
padding: 0
// properties passed around for the slides
property int currentServiceItem
property url imageBackground: ""
property url videoBackground: ""
property int blurRadius: 0
@ -65,6 +66,7 @@ Controls.Page {
id: presentation
anchors.fill: parent
}
Presenter.SongEditor {
id: songEditor
visible: false
@ -76,6 +78,12 @@ Controls.Page {
visible: false
anchors.fill: parent
}
Presenter.ImageEditor {
id: imageEditor
visible: false
anchors.fill: parent
}
}
Presenter.Library {
@ -90,35 +98,7 @@ Controls.Page {
Loader {
id: presentLoader
active: presenting
sourceComponent: presentWindowComp
}
Component {
id: presentWindowComp
Window {
id: presentationWindow
title: "presentation-window"
height: maximumHeight
width: maximumWidth
screen: presentationScreen
flags: Qt.X11BypassWindowManagerHint
onClosing: presenting = false
Component.onCompleted: {
presentationWindow.showFullScreen();
print(screen.name);
}
Presenter.Slide {
id: presentationSlide
anchors.fill: parent
imageSource: imageBackground
videoSource: videoBackground
text: ""
Component.onCompleted: slideItem = presentationSlide
}
}
source: "PresentationWindow.qml"
}
SongSqlModel {
@ -129,52 +109,41 @@ Controls.Page {
id: videosqlmodel
}
ImageSqlModel {
id: imagesqlmodel
}
ServiceItemModel {
id: serviceItemModel
}
function changeSlideType(type) {
presentation.itemType = type;
if (slideItem)
slideItem.itemType = type;
}
function changeServiceItem(index) {
const item = serviceItemModel.getItem(index);
print("index grabbed: " + index);
function changeSlideText(text) {
presentation.text = text;
if (slideItem)
slideItem.text = text;
}
presentation.stopVideo()
presentation.itemType = item.type;
print("Time to start changing");
function changeSlideBackground(background, type) {
showPassiveNotification("starting background change..");
showPassiveNotification(background);
showPassiveNotification(type);
if (type == "image") {
if (item.backgroundType === "image") {
presentation.vidbackground = "";
presentation.imagebackground = background;
if (slideItem) {
slideItem.videoSource = "";
slideItem.stopVideo();
slideItem.imageSource = background;
}
presentation.imagebackground = item.background;
} else {
presentation.imagebackground = "";
presentation.vidbackground = background;
presentation.vidbackground = item.background;
presentation.loadVideo()
if (slideItem) {
slideItem.imageSource = "";
slideItem.videoSource = background;
slideItem.loadVideo()
}
}
}
function changeSlideNext() {
showPassiveNotification("next slide please")
print(item.text.length)
if (item.text.length === 0) {
presentation.text = [""];
}
else
presentation.text = item.text;
presentation.textIndex = 0;
presentation.changeSlide();
function changeSlidePrevious() {
showPassiveNotification("previous slide please")
print("Slide changed to: " + item.name);
}
function editSwitch(item) {
@ -184,24 +153,30 @@ Controls.Page {
presentation.visible = false;
videoEditor.visible = false;
videoEditor.stop();
imageEditor.visible = false;
songEditor.visible = true;
songEditor.changeSong(item);
break;
case "video" :
presentation.visible = false;
songEditor.visible = false;
imageEditor.visible = false;
videoEditor.visible = true;
videoEditor.changeVideo(item);
break;
case "image" :
mainPageArea.pop(Controls.StackView.Immediate);
mainPageArea.push(imageEditorComp, Controls.StackView.Immediate);
presentation.visible = false;
videoEditor.visible = false;
videoEditor.stop();
songEditor.visible = false;
imageEditor.visible = true;
imageEditor.changeImage(item);
break;
default:
videoEditor.visible = false;
videoEditor.stop();
songEditor.visible = false;
imageEditor.visible = false;
presentation.visible = true;
editMode = false;
}
@ -209,6 +184,7 @@ Controls.Page {
videoEditor.visible = false;
videoEditor.stop();
songEditor.visible = false;
imageEditor.visible = false;
presentation.visible = true;
editMode = false;
}

View file

@ -3,18 +3,21 @@ import QtQuick.Dialogs 1.0
import QtQuick.Controls 2.15 as Controls
import QtQuick.Window 2.13
import QtQuick.Layouts 1.2
import QtAudioEngine 1.15
/* import QtAudioEngine 1.15 */
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
Item {
id: root
property string text
property var text
property int textIndex: 0
property string itemType
property url imagebackground
property url vidbackground
Component.onCompleted: nextSlideAction()
GridLayout {
anchors.fill: parent
columns: 3
@ -29,11 +32,18 @@ Item {
anchors.fill: parent
Controls.ToolButton {
text: "Grid"
text: "Solo"
icon.name: "viewimage"
hoverEnabled: true
}
Controls.ToolButton {
text: "Solo"
text: "Grid"
icon.name: "view-app-grid-symbolic"
hoverEnabled: true
}
Controls.ToolButton {
text: "Details"
icon.name: "view-list-details"
hoverEnabled: true
}
Controls.ToolSeparator {}
@ -45,13 +55,6 @@ Item {
hoverEnabled: true
onClicked: {}
}
Controls.ToolButton {
id: backgroundButton
text: "Background"
icon.name: "fileopen"
hoverEnabled: true
onClicked: backgroundType.open()
}
}
}
@ -69,7 +72,7 @@ Item {
Layout.alignment: Qt.AlignRight
MouseArea {
anchors.fill: parent
onPressed: changeSlidePrevious()
onPressed: previousSlideAction()
cursorShape: Qt.PointingHandCursor
}
}
@ -81,7 +84,6 @@ Item {
Layout.minimumWidth: 300
Layout.alignment: Qt.AlignCenter
textSize: width / 15
text: root.text
itemType: root.itemType
imageSource: imagebackground
videoSource: vidbackground
@ -95,7 +97,7 @@ Item {
Layout.alignment: Qt.AlignLeft
MouseArea {
anchors.fill: parent
onPressed: changeSlideNext()
onPressed: nextSlideAction()
cursorShape: Qt.PointingHandCursor
}
}
@ -112,4 +114,83 @@ Item {
function loadVideo() {
previewSlide.loadVideo();
}
function stopVideo() {
previewSlide.stopVideo()
}
function nextSlideAction() {
print(textIndex);
if (itemType === "song") {
if (textIndex === 0) {
previewSlide.text = root.text[textIndex];
print(root.text[textIndex]);
textIndex++;
} else if (textIndex < root.text.length) {
previewSlide.text = root.text[textIndex];
print(root.text[textIndex]);
textIndex++;
} else {
print("Next slide time");
textIndex = 0;
clearText();
nextSlide();
}
} else if (itemType === "video") {
clearText();
nextSlide();
}
else if (itemType === "image") {
clearText();
nextSlide();
}
}
function nextSlide() {
changeServiceItem(currentServiceItem++);
print(slideItem);
}
function previousSlideAction() {
print(textIndex);
if (itemType === "song") {
if (textIndex === 0) {
clearText();
nextSlide();
} else if (textIndex <= root.text.length) {
previewSlide.text = root.text[textIndex];
print(root.text[textIndex]);
--textIndex;
}
} else if (itemType === "video") {
clearText();
previousSlide();
}
else if (itemType === "image") {
clearText();
previousSlide();
}
}
function previousSlide() {
changeServiceItem(--currentServiceItem);
print(slideItem);
}
function changeSlide() {
if (itemType === "song") {
previewSlide.text = root.text[textIndex];
print(root.text[textIndex]);
textIndex++;
} else if (itemType === "video") {
clearText();
}
else if (itemType === "image") {
clearText();
}
}
function clearText() {
previewSlide.text = "";
}
}

View file

@ -2,7 +2,7 @@ import QtQuick 2.13
import QtQuick.Controls 2.15 as Controls
import QtQuick.Layouts 1.2
/* import QtMultimedia 5.15 */
import QtAudioEngine 1.15
/* import QtAudioEngine 1.15 */
import QtGraphicalEffects 1.15
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
@ -15,13 +15,15 @@ Item {
property bool editMode: false
// These properties are for the slides visuals
property real textSize: 72
property real textSize: 50
property bool dropShadow: false
property url imageSource: imageBackground
property url videoSource: videoBackground
property string chosenFont: "Quicksand"
property string text: "This is demo text"
property var text: "This is demo text"
property color backgroundColor
property var hTextAlignment: Text.AlignHCenter
property var vTextAlignment: Text.AlignVCenter
//these properties are for giving video info to parents
property int mpvPosition: mpv.position
@ -32,6 +34,9 @@ Item {
property string itemType
property bool preview: false
implicitWidth: 1920
implicitHeight: 1080
Rectangle {
id: basePrColor
anchors.fill: parent
@ -45,22 +50,24 @@ Item {
enableAudio: !preview
Component.onCompleted: mpvLoadingTimer.start()
onFileLoaded: {
showPassiveNotification(videoSource + " has been loaded");
/* showPassiveNotification(videoSource + " has been loaded"); */
if (itemType == "song")
mpv.setProperty("loop", "inf");
showPassiveNotification(mpv.getProperty("loop"));
else
mpv.setProperty("loop", "no");
/* showPassiveNotification(mpv.getProperty("loop")); */
}
MouseArea {
id: playArea
anchors.fill: parent
enabled: editMode
onPressed: mpv.loadFile(videoSource.toString());
onPressed: mpv.playPause();
cursorShape: preview ? Qt.ArrowCursor : Qt.BlankCursor
}
Controls.ProgressBar {
anchors.centerIn: parent
anchors.top: parent.bottom
visible: editMode
width: parent.width - 400
value: mpv.position
@ -71,7 +78,36 @@ Item {
Timer {
id: mpvLoadingTimer
interval: 100
onTriggered: mpv.loadFile(videoSource.toString())
onTriggered: {
/* showPassiveNotification("YIPPEEE!") */
mpv.loadFile(videoSource.toString());
if (editMode) {
print("WHY AREN'T YOU PASUING!");
pauseTimer.restart();
}
blackTimer.restart();
}
}
Timer {
id: pauseTimer
interval: 200
onTriggered: mpv.pause()
}
Timer {
id: blackTimer
interval: 400
onTriggered: {
black.visible = false;
}
}
Rectangle {
id: black
color: "Black"
anchors.fill: parent
visible: false
}
Image {
@ -87,7 +123,7 @@ Item {
FastBlur {
id: imageBlue
anchors.fill: parent
source: imageSource == "" ? mpv : backgroundImage
source: imageSource === "" ? mpv : backgroundImage
radius: blurRadius
Controls.Label {
@ -97,9 +133,10 @@ Item {
/* minimumPointSize: 5 */
fontSizeMode: Text.Fit
font.family: chosenFont
horizontalAlignment: hTextAlignment
verticalAlignment: vTextAlignment
style: Text.Raised
anchors.centerIn: parent
/* width: parent.width */
anchors.fill: parent
clip: true
layer.enabled: true
@ -123,6 +160,12 @@ Item {
}
function stopVideo() {
mpv.stop()
mpv.stop();
black.visible = true;
showPassiveNotification("Black is: " + black.visible);
}
function pauseVideo() {
mpv.pause();
}
}

View file

@ -1,10 +1,10 @@
import QtQuick 2.13
import QtQuick 2.15
import QtQuick.Dialogs 1.0
import QtQuick.Controls 2.15 as Controls
import QtQuick.Window 2.13
import QtQuick.Layouts 1.2
import QtMultimedia 5.15
import QtAudioEngine 1.15
/* import QtAudioEngine 1.15 */
import org.kde.kirigami 2.13 as Kirigami
import "./" as Presenter
@ -13,14 +13,86 @@ Item {
property string imageBackground
property string videoBackground
property var hTextAlignment
property var vTextAlignment
property string font
property real fontSize
Presenter.Slide {
id: representation
property ListModel songs: songModel
ListView {
id: slideList
anchors.fill: parent
textSize: width / 15
model: songModel
clip: true
cacheBuffer: 900
reuseItems: true
spacing: Kirigami.Units.gridUnit
flickDeceleration: 4000
/* boundsMovement: Flickable.StopAtBounds */
synchronousDrag: true
delegate: Presenter.Slide {
id: representation
editMode: true
imageSource: imageBackground
videoSource: videoBackground
imageSource: root.imageBackground
videoSource: root.videoBackground
hTextAlignment: root.hTextAlignment
vTextAlignment: root.vTextAlignment
chosenFont: root.font
textSize: root.fontSize
preview: true
text: verse
implicitWidth: slideList.width
implicitHeight: width * 9 / 16
}
}
Component.onCompleted: {
}
ListModel {
id: songModel
}
function appendVerse(verse) {
print(verse);
songModel.append({"verse": verse})
}
/* function loadVideo() { */
/* representation.loadVideo(); */
/* } */
function updateHAlignment(alignment) {
switch (alignment) {
case "left" :
root.hTextAlignment = Text.AlignLeft;
break;
case "center" :
root.hTextAlignment = Text.AlignHCenter;
break;
case "right" :
root.hTextAlignment = Text.AlignRight;
break;
case "justify" :
root.hTextAlignment = Text.AlignJustify;
break;
}
}
function updateVAlignment(alignment) {
switch (alignment) {
case "top" :
root.vTextAlignment = Text.AlignTop;
break;
case "center" :
root.vTextAlignment = Text.AlignVCenter;
break;
case "bottom" :
root.vTextAlignment = Text.AlignBottom;
break;
}
}
}

View file

@ -1,5 +1,6 @@
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.2
import org.kde.kirigami 2.13 as Kirigami
@ -9,14 +10,7 @@ Item {
id: root
property int songIndex
property string songTitle
property string songLyrics
property string songAuthor
property string songCcli
property string songAudio
property string songVorder
property string songBackground
property string songBackgroundType
property var song
GridLayout {
id: mainLayout
@ -33,34 +27,52 @@ Item {
anchors.fill: parent
Controls.ComboBox {
id: fontBox
model: Qt.fontFamilies()
implicitWidth: 300
editable: true
hoverEnabled: true
onCurrentTextChanged: showPassiveNotification(currentText)
flat: true
onActivated: updateFont(currentText)
}
Controls.SpinBox {
id: fontSizeBox
editable: true
from: 5
to: 72
hoverEnabled: true
onValueModified: updateFontSize(value)
}
Controls.ComboBox {
id: hAlignmentBox
model: ["Left", "Center", "Right", "Justify"]
implicitWidth: 100
hoverEnabled: true
flat: true
onActivated: updateHorizontalTextAlignment(currentText.toLowerCase());
}
Controls.ComboBox {
id: vAlignmentBox
model: ["Top", "Center", "Bottom"]
implicitWidth: 100
hoverEnabled: true
flat: true
onActivated: updateVerticalTextAlignment(currentText.toLowerCase());
}
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 }
@ -143,7 +155,7 @@ Item {
Layout.rightMargin: 20
placeholderText: "Song Title..."
text: songTitle
text: song.title
padding: 10
onEditingFinished: updateTitle(text);
}
@ -156,7 +168,7 @@ Item {
Layout.rightMargin: 20
placeholderText: "verse order..."
text: songVorder
text: song.vorder
padding: 10
onEditingFinished: updateVerseOrder(text);
}
@ -176,7 +188,7 @@ Item {
width: parent.width
placeholderText: "Put lyrics here..."
persistentSelection: true
text: songLyrics
text: song.lyrics
textFormat: TextEdit.PlainText
padding: 10
onEditingFinished: {
@ -195,7 +207,7 @@ Item {
Layout.rightMargin: 20
placeholderText: "Author..."
text: songAuthor
text: song.author
padding: 10
onEditingFinished: updateAuthor(text)
}
@ -210,8 +222,9 @@ Item {
id: slideEditor
Layout.preferredWidth: 500
Layout.fillWidth: true
Layout.preferredHeight: slideEditor.width / 16 * 9
Layout.fillHeight: true
Layout.bottomMargin: 30
Layout.topMargin: 30
Layout.rightMargin: 20
Layout.leftMargin: 20
}
@ -262,24 +275,25 @@ Item {
}
function changeSong(index) {
const song = songsqlmodel.getSong(index);
const s = songsqlmodel.getSong(index);
song = s;
songIndex = index;
songTitle = song[0];
songLyrics = song[1];
songAuthor = song[2];
songCcli = song[3];
songAudio = song[4];
songVorder = song[5];
songBackground = song[6];
songBackgroundType = song[7];
if (songBackgroundType == "image") {
if (song.backgroundType == "image") {
slideEditor.videoBackground = "";
slideEditor.imageBackground = songBackground;
slideEditor.imageBackground = song.background;
} else {
slideEditor.imageBackground = "";
slideEditor.videoBackground = songBackground;
slideEditor.videoBackground = song.background;
/* slideEditor.loadVideo(); */
}
print(song);
changeSlideHAlignment(song.horizontalTextAlignment);
changeSlideVAlignment(song.verticalTextAlignment);
changeSlideFont(song.font, true);
changeSlideFontSize(song.fontSize, true)
changeSlideText(songIndex);
print(s.title);
}
function updateLyrics(lyrics) {
@ -312,4 +326,83 @@ Item {
songsqlmodel.updateBackgroundType(songIndex, backgroundType);
print("changed background");
}
function updateHorizontalTextAlignment(textAlignment) {
changeSlideHAlignment(textAlignment);
songsqlmodel.updateHorizontalTextAlignment(songIndex, textAlignment);
}
function updateVerticalTextAlignment(textAlignment) {
changeSlideVAlignment(textAlignment);
songsqlmodel.updateVerticalTextAlignment(songIndex, textAlignment)
}
function updateFont(font) {
changeSlideFont(font, false);
songsqlmodel.updateFont(songIndex, font);
}
function updateFontSize(fontSize) {
changeSlideFontSize(fontSize, false);
songsqlmodel.updateFontSize(songIndex, fontSize);
}
function changeSlideHAlignment(alignment) {
switch (alignment) {
case "left" :
hAlignmentBox.currentIndex = 0;
slideEditor.hTextAlignment = Text.AlignLeft;
break;
case "center" :
hAlignmentBox.currentIndex = 1;
slideEditor.hTextAlignment = Text.AlignHCenter;
break;
case "right" :
hAlignmentBox.currentIndex = 2;
slideEditor.hTextAlignment = Text.AlignRight;
break;
case "justify" :
hAlignmentBox.currentIndex = 3;
slideEditor.hTextAlignment = Text.AlignJustify;
break;
}
}
function changeSlideVAlignment(alignment) {
switch (alignment) {
case "top" :
vAlignmentBox.currentIndex = 0;
slideEditor.vTextAlignment = Text.AlignTop;
break;
case "center" :
vAlignmentBox.currentIndex = 1;
slideEditor.vTextAlignment = Text.AlignVCenter;
break;
case "bottom" :
vAlignmentBox.currentIndex = 2;
slideEditor.vTextAlignment = Text.AlignBottom;
break;
}
}
function changeSlideFont(font, updateBox) {
const fontIndex = fontBox.find(font);
if (updateBox)
fontBox.currentIndex = fontIndex;
slideEditor.font = font;
}
function changeSlideFontSize(fontSize, updateBox) {
if (updateBox)
fontSizeBox.value = fontSize;
slideEditor.fontSize = fontSize;
}
function changeSlideText(id) {
const verses = songsqlmodel.getLyricList(id);
print("Here are the verses: " + verses);
slideEditor.songs.clear()
verses.forEach(slideEditor.appendVerse);
}
}

View file

@ -32,7 +32,7 @@ Item {
implicitWidth: 300
editable: true
hoverEnabled: true
onCurrentTextChanged: showPassiveNotification(currentText)
/* onCurrentTextChanged: showPassiveNotification(currentText) */
}
Controls.SpinBox {
editable: true
@ -138,7 +138,7 @@ Item {
Layout.rightMargin: 20
placeholderText: "Song Title..."
text: video[0]
text: video.title
padding: 10
/* onEditingFinished: updateTitle(text); */
}
@ -170,7 +170,7 @@ Item {
Component.onCompleted: mpvLoadingTimer.start()
onPositionChanged: videoSlider.value = position
onFileLoaded: {
showPassiveNotification(video[0] + " has been loaded");
showPassiveNotification(video.title + " has been loaded");
videoPreview.pause();
}
}
@ -220,7 +220,7 @@ Item {
id: mpvLoadingTimer
interval: 100
onTriggered: {
videoPreview.loadFile(video[1].toString());
videoPreview.loadFile(video.filePath.toString());
/* showPassiveNotification(video[0]); */
}
}

View file

@ -17,5 +17,6 @@
<file>qml/presenter/Presentation.qml</file>
<file>qml/presenter/Settings.qml</file>
<file>assets/parallel.jpg</file>
<file>assets/black.jpg</file>
</qresource>
</RCC>

View file

@ -1,6 +1,7 @@
#include "serviceitemmodel.h"
#include "serviceitem.h"
#include <qabstractitemmodel.h>
#include <qglobal.h>
#include <qnamespace.h>
#include <qvariant.h>
#include <qdebug.h>
@ -115,13 +116,17 @@ Qt::ItemFlags ServiceItemModel::flags(const QModelIndex &index) const {
void ServiceItemModel::addItem(ServiceItem *item) {
const int index = m_items.size();
qDebug() << index;
// foreach (item, m_items) {
// qDebug() << item;
// }
beginInsertRows(QModelIndex(), index, index);
m_items.append(item);
endInsertRows();
}
void ServiceItemModel::insertItem(const int &index, ServiceItem *item) {
beginInsertRows(this->index(index), index, index);
beginInsertRows(this->index(index).parent(), index, index);
m_items.insert(index, item);
endInsertRows();
qDebug() << "Success";
@ -164,7 +169,7 @@ void ServiceItemModel::insertItem(const int &index, const QString &name, const Q
const QStringList &text) {
ServiceItem *item = new ServiceItem(name, type, background, backgroundType, text);
insertItem(index, item);
qDebug() << name << type << background;
qDebug() << name << type << background << text;
}
void ServiceItemModel::removeItem(int index) {
@ -174,17 +179,36 @@ void ServiceItemModel::removeItem(int index) {
}
bool ServiceItemModel::move(int sourceIndex, int destIndex) {
qDebug() << "starting move";
qDebug() << index(sourceIndex).row();
qDebug() << index(destIndex).row();
// beginResetModel();
QModelIndex parent = index(sourceIndex).parent();
bool begsuc = beginMoveRows(parent, sourceIndex, sourceIndex, parent, destIndex);
qDebug() << begsuc;
if (!begsuc) {
qDebug() << "Failed to start moving rows";
if (sourceIndex >= 0 && sourceIndex != destIndex && destIndex >= 0 && destIndex < rowCount() && sourceIndex < rowCount()) {
qDebug() << "starting move of: " << "source: " << sourceIndex << "dest: " << destIndex;
bool begsuc = beginMoveRows(QModelIndex(), sourceIndex, sourceIndex, QModelIndex(), destIndex);
if (begsuc)
m_items.move(sourceIndex, destIndex);
return false;
}
// bool success = moveRow(index(sourceIndex).parent(), sourceIndex, index(destIndex).parent(), destIndex);
endMoveRows();
}
// endResetModel();
// emit dataChanged(index(sourceIndex), QModelIndex());
// qDebug() << success;
return true;
}
QVariantMap ServiceItemModel::getItem(int index) const {
QVariantMap data;
const QModelIndex idx = this->index(index,0);
// qDebug() << idx;
if( !idx.isValid() )
return data;
const QHash<int,QByteArray> rn = roleNames();
// qDebug() << rn;
QHashIterator<int,QByteArray> it(rn);
while (it.hasNext()) {
it.next();
qDebug() << it.key() << ":" << it.value();
data[it.value()] = idx.data(it.key());
}
return data;
}

View file

@ -56,6 +56,7 @@ public:
const QString &backgroundType, const QStringList &text);
Q_INVOKABLE void removeItem(int index);
Q_INVOKABLE bool move(int sourceIndex, int destIndex);
Q_INVOKABLE QVariantMap getItem(int index) const;
private:
QList<ServiceItem *> m_items;

View file

@ -34,27 +34,39 @@ static void createTable()
" 'vorder' TEXT,"
" 'background' TEXT,"
" 'backgroundType' TEXT,"
" 'horizontalTextAlignment' TEXT,"
" 'verticalTextAlignment' TEXT,"
" 'font' TEXT,"
" 'fontSize' INTEGER,"
" PRIMARY KEY(id))")) {
qFatal("Failed to query database: %s",
qPrintable(query.lastError().text()));
}
qDebug() << query.lastQuery();
qDebug() << "inserting into songs";
// qDebug() << query.lastQuery();
// qDebug() << "inserting into songs";
query.exec("INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, background, backgroundType) VALUES ('10,000 Reasons', '10,000 reasons for my heart to sing', 'Matt Redman', '13470183', '', '', '', '')");
qDebug() << query.lastQuery();
query.exec("INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, background, backgroundType) VALUES ('River', 'Im going down to the river', 'Jordan Feliz', '13470183', '', '', '', '')");
query.exec("INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, background, backgroundType) VALUES ('Marvelous Light', 'Into marvelous "
"light Im running', 'Chris Tomlin', '13470183', '', '', '', '')");
query.exec(
"INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, "
"background, backgroundType, horizontalTextAlignment, verticalTextAlignment, font, fontSize) VALUES ('10,000 Reasons', '10,000 reasons "
"for my heart to sing', 'Matt Redman', '13470183', '', '', '', '', 'center', 'center', '', '')");
// qDebug() << query.lastQuery();
query.exec("INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, "
"background, backgroundType, horizontalTextAlignment, verticalTextAlignment, font, fontSize) VALUES ('River', 'Im going down to "
"the river', 'Jordan Feliz', '13470183', '', '', '', '', 'center', 'center', '', '')");
query.exec(
"INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, "
"background, backgroundType, horizontalTextAlignment, verticalTextAlignment, font, fontSize) VALUES ('Marvelous Light', 'Into marvelous "
"light Im running', 'Chris Tomlin', '13470183', '', '', '', '', 'center', 'center', '', '')");
// qDebug() << query.lastQuery();
query.exec("select * from songs");
qDebug() << query.lastQuery();
// qDebug() << query.lastQuery();
}
SongSqlModel::SongSqlModel(QObject *parent)
: QSqlTableModel(parent)
{
qDebug() << "creating table";
// qDebug() << "creating table";
createTable();
setTable(songsTableName);
setEditStrategy(QSqlTableModel::OnManualSubmit);
@ -85,6 +97,10 @@ QHash<int, QByteArray> SongSqlModel::roleNames() const
names[Qt::UserRole + 6] = "vorder";
names[Qt::UserRole + 7] = "background";
names[Qt::UserRole + 8] = "backgroundType";
names[Qt::UserRole + 9] = "horizontalTextAlignment";
names[Qt::UserRole + 10] = "verticalTextAlignment";
names[Qt::UserRole + 11] = "font";
names[Qt::UserRole + 12] = "fontSize";
return names;
}
@ -113,25 +129,24 @@ void SongSqlModel::deleteSong(const int &row) {
submitAll();
}
QVariantList SongSqlModel::getSong(const int &row) {
QSqlRecord recordData = record(row);
if (recordData.isEmpty()) {
qDebug() << "this is not a song";
QVariantList empty;
return empty;
QVariantMap SongSqlModel::getSong(const int &row) {
// this whole function returns all data in the song
// regardless of it's length. When new things are added
// it will still work without refactoring.
QVariantMap data;
const QModelIndex idx = this->index(row,0);
// qDebug() << idx;
if( !idx.isValid() )
return data;
const QHash<int,QByteArray> rn = roleNames();
// qDebug() << rn;
QHashIterator<int,QByteArray> it(rn);
while (it.hasNext()) {
it.next();
qDebug() << it.key() << ":" << it.value();
data[it.value()] = idx.data(it.key());
}
QVariantList song;
song.append(recordData.value("title"));
song.append(recordData.value("lyrics"));
song.append(recordData.value("author"));
song.append(recordData.value("ccli"));
song.append(recordData.value("audio"));
song.append(recordData.value("vorder"));
song.append(recordData.value("background"));
song.append(recordData.value("backgroundType"));
return song;
return data;
}
QStringList SongSqlModel::getLyricList(const int &row) {
@ -162,8 +177,11 @@ QStringList SongSqlModel::getLyricList(const int &row) {
QString line;
QMap<QString, QString> verses;
//TODO make sure to split empty line in verse into two slides
// This first function pulls out each verse into our verses map
foreach (line, rawLyrics) {
qDebug() << line;
if (firstItem) {
if (keywords.contains(line)) {
recordVerse = true;
@ -187,6 +205,16 @@ QStringList SongSqlModel::getLyricList(const int &row) {
}
qDebug() << verses;
// let's check to see if there is a verse order, if not return the list given
if (vorder.first().isEmpty()) {
qDebug() << "NO VORDER";
foreach (verse, verses) {
qDebug() << verse;
lyrics.append(verse);
}
qDebug() << lyrics;
return lyrics;
}
// this function appends the verse that matches the verse order from the map
foreach (const QString &vstr, vorder) {
foreach (line, rawLyrics) {
@ -394,3 +422,107 @@ void SongSqlModel::updateBackgroundType(const int &row, const QString &backgroun
submitAll();
emit backgroundTypeChanged();
}
QString SongSqlModel::horizontalTextAlignment() const {
return m_horizontalTextAlignment;
}
void SongSqlModel::setHorizontalTextAlignment(const QString &horizontalTextAlignment) {
if (horizontalTextAlignment == m_horizontalTextAlignment)
return;
m_horizontalTextAlignment = horizontalTextAlignment;
select();
emit horizontalTextAlignmentChanged();
}
// This function is for updating the lyrics from outside the delegate
void SongSqlModel::updateHorizontalTextAlignment(const int &row, const QString &horizontalTextAlignment) {
qDebug() << "Row is " << row;
QSqlRecord rowdata = record(row);
qDebug() << rowdata;
rowdata.setValue("horizontalTextAlignment", horizontalTextAlignment);
setRecord(row, rowdata);
qDebug() << rowdata;
submitAll();
emit horizontalTextAlignmentChanged();
}
QString SongSqlModel::verticalTextAlignment() const {
return m_verticalTextAlignment;
}
void SongSqlModel::setVerticalTextAlignment(const QString &verticalTextAlignment) {
if (verticalTextAlignment == m_verticalTextAlignment)
return;
m_verticalTextAlignment = verticalTextAlignment;
select();
emit verticalTextAlignmentChanged();
}
// This function is for updating the lyrics from outside the delegate
void SongSqlModel::updateVerticalTextAlignment(const int &row, const QString &verticalTextAlignment) {
qDebug() << "Row is " << row;
QSqlRecord rowdata = record(row);
qDebug() << rowdata;
rowdata.setValue("verticalTextAlignment", verticalTextAlignment);
setRecord(row, rowdata);
qDebug() << rowdata;
submitAll();
emit verticalTextAlignmentChanged();
}
QString SongSqlModel::font() const {
return m_font;
}
void SongSqlModel::setFont(const QString &font) {
if (font == m_font)
return;
m_font = font;
select();
emit fontChanged();
}
// This function is for updating the lyrics from outside the delegate
void SongSqlModel::updateFont(const int &row, const QString &font) {
qDebug() << "Row is " << row;
QSqlRecord rowdata = record(row);
qDebug() << rowdata;
rowdata.setValue("font", font);
setRecord(row, rowdata);
qDebug() << rowdata;
submitAll();
emit fontChanged();
}
int SongSqlModel::fontSize() const {
return m_fontSize;
}
void SongSqlModel::setFontSize(const int &fontSize) {
if (fontSize == m_fontSize)
return;
m_fontSize = fontSize;
select();
emit fontSizeChanged();
}
// This function is for updating the lyrics from outside the delegate
void SongSqlModel::updateFontSize(const int &row, const int &fontSize) {
qDebug() << "Row is " << row;
QSqlRecord rowdata = record(row);
qDebug() << rowdata;
rowdata.setValue("fontSize", fontSize);
setRecord(row, rowdata);
qDebug() << rowdata;
submitAll();
emit fontSizeChanged();
}

View file

@ -18,6 +18,10 @@ class SongSqlModel : public QSqlTableModel
Q_PROPERTY(QString vorder READ vorder WRITE setVerseOrder NOTIFY vorderChanged)
Q_PROPERTY(QString background READ background WRITE setBackground NOTIFY backgroundChanged)
Q_PROPERTY(QString backgroundType READ backgroundType WRITE setBackgroundType NOTIFY backgroundTypeChanged)
Q_PROPERTY(QString horizontalTextAlignment READ horizontalTextAlignment WRITE setHorizontalTextAlignment NOTIFY horizontalTextAlignmentChanged)
Q_PROPERTY(QString verticalTextAlignment READ verticalTextAlignment WRITE setVerticalTextAlignment NOTIFY verticalTextAlignmentChanged)
Q_PROPERTY(QString font READ font WRITE setFont NOTIFY fontChanged)
Q_PROPERTY(int fontSize READ fontSize WRITE setFontSize NOTIFY fontSizeChanged)
QML_ELEMENT
public:
@ -32,6 +36,10 @@ public:
QString vorder() const;
QString background() const;
QString backgroundType() const;
QString horizontalTextAlignment() const;
QString verticalTextAlignment() const;
QString font() const;
int fontSize() const;
void setTitle(const QString &title);
void setLyrics(const QString &lyrics);
@ -41,6 +49,10 @@ public:
void setVerseOrder(const QString &vorder);
void setBackground(const QString &background);
void setBackgroundType(const QString &backgroundType);
void setHorizontalTextAlignment(const QString &horizontalTextAlignment);
void setVerticalTextAlignment(const QString &verticalTextAlignment);
void setFont(const QString &font);
void setFontSize(const int &fontSize);
Q_INVOKABLE void updateTitle(const int &row, const QString &title);
Q_INVOKABLE void updateLyrics(const int &row, const QString &lyrics);
@ -50,10 +62,14 @@ public:
Q_INVOKABLE void updateVerseOrder(const int &row, const QString &vorder);
Q_INVOKABLE void updateBackground(const int &row, const QString &background);
Q_INVOKABLE void updateBackgroundType(const int &row, const QString &backgroundType);
Q_INVOKABLE void updateHorizontalTextAlignment(const int &row, const QString &horizontalTextAlignment);
Q_INVOKABLE void updateVerticalTextAlignment(const int &row, const QString &horizontalTextAlignment);
Q_INVOKABLE void updateFont(const int &row, const QString &font);
Q_INVOKABLE void updateFontSize(const int &row, const int &fontSize);
Q_INVOKABLE void newSong();
Q_INVOKABLE void deleteSong(const int &row);
Q_INVOKABLE QVariantList getSong(const int &row);
Q_INVOKABLE QVariantMap getSong(const int &row);
Q_INVOKABLE QStringList getLyricList(const int &row);
QVariant data(const QModelIndex &index, int role) const override;
@ -68,6 +84,10 @@ signals:
void vorderChanged();
void backgroundChanged();
void backgroundTypeChanged();
void horizontalTextAlignmentChanged();
void verticalTextAlignmentChanged();
void fontChanged();
void fontSizeChanged();
private:
int m_id;
@ -79,6 +99,10 @@ private:
QString m_vorder;
QString m_background;
QString m_backgroundType;
QString m_horizontalTextAlignment;
QString m_verticalTextAlignment;
QString m_font;
int m_fontSize;
};
#endif //SONGSQLMODEL_H

View file

@ -156,12 +156,28 @@ void VideoSqlModel::updateFilePath(const int &row, const QUrl &filePath) {
emit filePathChanged();
}
QVariantList VideoSqlModel::getVideo(const int &row) {
qDebug() << "Row we are getting is " << row;
QVariantList video;
QSqlRecord rec = record(row);
qDebug() << rec.value("title");
video.append(rec.value("title"));
video.append(rec.value("filePath"));
return video;
QVariantMap VideoSqlModel::getVideo(const int &row) {
// qDebug() << "Row we are getting is " << row;
// QVariantList video;
// QSqlRecord rec = record(row);
// qDebug() << rec.value("title");
// video.append(rec.value("title"));
// video.append(rec.value("filePath"));
// return video;
QVariantMap data;
const QModelIndex idx = this->index(row,0);
// qDebug() << idx;
if( !idx.isValid() )
return data;
const QHash<int,QByteArray> rn = roleNames();
// qDebug() << rn;
QHashIterator<int,QByteArray> it(rn);
while (it.hasNext()) {
it.next();
qDebug() << it.key() << ":" << it.value();
data[it.value()] = idx.data(it.key());
}
return data;
}

View file

@ -31,7 +31,7 @@ public:
Q_INVOKABLE void newVideo(const QUrl &filePath);
Q_INVOKABLE void deleteVideo(const int &row);
Q_INVOKABLE QVariantList getVideo(const int &row);
Q_INVOKABLE QVariantMap getVideo(const int &row);
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
@ -46,4 +46,4 @@ private:
QUrl m_filePath;
};
#endif //SONGSQLMODEL_H
#endif //VIDEOSQLMODEL_H