basic imagemodel and editor

This commit is contained in:
Chris Cochrun 2022-04-08 17:24:18 -05:00
parent 09b6370153
commit 0cee5db60d
11 changed files with 432 additions and 102 deletions

View file

@ -4,12 +4,8 @@
:END: :END:
* Inbox * Inbox
** TODO BUG in dropping and then selecting song will duplicate entries :dev: ** TODO Make an image sql model
SCHEDULED: <2022-04-05 Tue> [[file:~/dev/church-presenter/src/videosqlmodel.h::ifndef VIDEOSQLMODEL_H]]
[[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?
** TODO Build out a slide preview system so we can see each slide in the song or image slideshow ** TODO Build out a slide preview system so we can see each slide in the song or image slideshow
[[file:~/dev/church-presenter/src/qml/presenter/SongEditor.qml::Presenter.SlideEditor {]] [[file:~/dev/church-presenter/src/qml/presenter/SongEditor.qml::Presenter.SlideEditor {]]
@ -53,6 +49,12 @@ or at least turns the entry above it into the same as itself while retaining it'
** DONE Parse Lyrics to create a list of strings for slides ** DONE Parse Lyrics to create a list of strings for slides
SCHEDULED: <2022-03-23 Wed 10:00> 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 ** DONE Make nextSlideText a nextAction function to incorporate other types of items
[[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::function nextSlideText() {]] [[file:~/dev/church-presenter/src/qml/presenter/Presentation.qml::function nextSlideText() {]]

View file

@ -7,6 +7,7 @@ target_sources(presenter
serviceitemmodel.cpp serviceitemmodel.h serviceitemmodel.cpp serviceitemmodel.h
serviceitem.cpp serviceitem.h serviceitem.cpp serviceitem.h
videosqlmodel.cpp videosqlmodel.h videosqlmodel.cpp videosqlmodel.h
imagesqlmodel.cpp imagesqlmodel.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
) )

169
src/imagesqlmodel.cpp Normal file
View file

@ -0,0 +1,169 @@
#include "imagesqlmodel.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 *imagesTableName = "images";
static void createImageTable()
{
if(QSqlDatabase::database().tables().contains(imagesTableName)) {
return;
}
QSqlQuery query;
if (!query.exec("CREATE TABLE IF NOT EXISTS 'images' ("
" '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 images";
query.exec("INSERT INTO images (title, filePath) VALUES ('Dec 180', '/home/chris/nextcloud/tfc/openlp/180-dec.png')");
qDebug() << query.lastQuery();
query.exec("INSERT INTO images (title, filePath) VALUES ('No TFC', "
"'/home/chris/nextcloud/tfc/openlp/No TFC.png')");
query.exec("select * from images");
qDebug() << query.lastQuery();
}
ImageSqlModel::ImageSqlModel(QObject *parent) : QSqlTableModel(parent) {
qDebug() << "creating image table";
createImageTable();
setTable(imagesTableName);
setEditStrategy(QSqlTableModel::OnManualSubmit);
// make sure to call select else the model won't fill
select();
}
QVariant ImageSqlModel::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> ImageSqlModel::roleNames() const
{
QHash<int, QByteArray> names;
names[Qt::UserRole] = "id";
names[Qt::UserRole + 1] = "title";
names[Qt::UserRole + 2] = "filePath";
return names;
}
void ImageSqlModel::newImage(const QUrl &filePath) {
qDebug() << "adding new image";
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 ImageSqlModel::deleteImage(const int &row) {
QSqlRecord recordData = record(row);
if (recordData.isEmpty())
return;
removeRow(row);
submitAll();
}
int ImageSqlModel::id() const {
return m_id;
}
QString ImageSqlModel::title() const {
return m_title;
}
void ImageSqlModel::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 ImageSqlModel::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 ImageSqlModel::filePath() const {
return m_filePath;
}
void ImageSqlModel::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 ImageSqlModel::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();
}
QUrl ImageSqlModel::getImage(const int &row) {
qDebug() << "Row we are getting is " << row;
QUrl image;
QSqlRecord rec = record(row);
qDebug() << rec.value("filePath").toUrl();
// image.append(rec.value("title"));
// image.append(rec.value("filePath"));
image = rec.value("filePath").toUrl();
return image;
}

49
src/imagesqlmodel.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef IMAGESQLMODEL_H
#define IMAGESQLMODEL_H
#include <QSqlTableModel>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qqml.h>
#include <qurl.h>
#include <qvariant.h>
class ImageSqlModel : 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:
ImageSqlModel(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 newImage(const QUrl &filePath);
Q_INVOKABLE void deleteImage(const int &row);
Q_INVOKABLE QUrl getImage(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 //IMAGESQLMODEL_H

View file

@ -37,6 +37,7 @@
#include "serviceitemmodel.h" #include "serviceitemmodel.h"
#include "songsqlmodel.h" #include "songsqlmodel.h"
#include "videosqlmodel.h" #include "videosqlmodel.h"
#include "imagesqlmodel.h"
static void connectToDatabase() { static void connectToDatabase() {
// let's setup our sql database // let's setup our sql database
@ -98,6 +99,7 @@ int main(int argc, char *argv[])
//register our models //register our models
qmlRegisterType<SongSqlModel>("org.presenter", 1, 0, "SongSqlModel"); qmlRegisterType<SongSqlModel>("org.presenter", 1, 0, "SongSqlModel");
qmlRegisterType<VideoSqlModel>("org.presenter", 1, 0, "VideoSqlModel"); qmlRegisterType<VideoSqlModel>("org.presenter", 1, 0, "VideoSqlModel");
qmlRegisterType<ImageSqlModel>("org.presenter", 1, 0, "ImageSqlModel");
qmlRegisterType<ServiceItemModel>("org.presenter", 1, 0, "ServiceItemModel"); qmlRegisterType<ServiceItemModel>("org.presenter", 1, 0, "ServiceItemModel");
connectToDatabase(); connectToDatabase();

View file

@ -8,6 +8,9 @@ import "./" as Presenter
Item { Item {
id: root id: root
property string type: "image"
property var image
GridLayout { GridLayout {
id: mainLayout id: mainLayout
anchors.fill: parent anchors.fill: parent
@ -83,15 +86,15 @@ Item {
border.color: Kirigami.Theme.activeBackgroundColor border.color: Kirigami.Theme.activeBackgroundColor
border.width: 2 border.width: 2
} }
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent closePolicy: Controls.Popup.CloseOnEscape | Controls.Popup.CloseOnPressOutsideParent
ColumnLayout { ColumnLayout {
anchors.fill: parent anchors.fill: parent
Controls.ToolButton { Controls.ToolButton {
Layout.fillHeight: true Layout.fillHeight: true
Layout.fillWidth: true Layout.fillWidth: true
text: "Video" text: "Image"
icon.name: "emblem-videos-symbolic" icon.name: "emblem-images-symbolic"
onClicked: videoFileDialog.open() & backgroundType.close() onClicked: imageFileDialog.open() & backgroundType.close()
} }
Controls.ToolButton { Controls.ToolButton {
Layout.fillWidth: true Layout.fillWidth: true
@ -121,11 +124,11 @@ Item {
ColumnLayout { ColumnLayout {
Controls.SplitView.fillHeight: true Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 500 Controls.SplitView.preferredWidth: 300
Controls.SplitView.minimumWidth: 500 Controls.SplitView.minimumWidth: 100
Controls.TextField { Controls.TextField {
id: songTitleField id: imageTitleField
Layout.preferredWidth: 300 Layout.preferredWidth: 300
Layout.fillWidth: true Layout.fillWidth: true
@ -133,98 +136,46 @@ Item {
Layout.rightMargin: 20 Layout.rightMargin: 20
placeholderText: "Song Title..." placeholderText: "Song Title..."
text: songTitle text: "idk"
padding: 10 padding: 10
onEditingFinished: updateTitle(text); /* 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 { Item {
id: songLyricsField id: empty
Layout.preferredHeight: 3000
Layout.fillWidth: true
Layout.fillHeight: 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 { ColumnLayout {
Controls.SplitView.fillHeight: true Controls.SplitView.fillHeight: true
Controls.SplitView.preferredWidth: 700 Controls.SplitView.preferredWidth: 700
Controls.SplitView.minimumWidth: 300 Controls.SplitView.minimumWidth: 300
spacing: 5
Rectangle { Item {
id: slideBar id: topEmpty
color: Kirigami.Theme.highlightColor Layout.fillHeight: true
Layout.preferredWidth: 500
Layout.preferredHeight: songTitleField.height
Layout.rightMargin: 20
Layout.leftMargin: 20
} }
Presenter.SlideEditor { Image {
id: slideEditor id: imagePreview
Layout.preferredWidth: 500 Layout.preferredWidth: 600
Layout.fillWidth: true Layout.preferredHeight: Layout.preferredWidth / 16 * 9
Layout.preferredHeight: slideEditor.width / 16 * 9 Layout.alignment: Qt.AlignCenter
Layout.bottomMargin: 30 fillMode: Image.PreserveAspectFit
Layout.rightMargin: 20 source: "file://" + image.toString()
Layout.leftMargin: 20 }
Item {
id: botEmpty
Layout.fillHeight: true
} }
} }
}
} }
function changeImage(image) {
root.image = image;
print(image.toString());
} }
Timer {
id: editorTimer
interval: 1000
repeat: true
running: false
onTriggered: updateLyrics(lyricsEditor.text)
}
} }

View file

@ -485,12 +485,40 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop
color: Kirigami.Theme.backgroundColor color: Kirigami.Theme.backgroundColor
opacity: 1.0
Controls.Label { Controls.Label {
id: imageLabel
anchors.centerIn: parent anchors.centerIn: parent
text: "Images" 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 { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: {
@ -508,6 +536,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: imagesqlmodel
delegate: imageDelegate
clip: true
state: "deselected" state: "deselected"
states: [ states: [
@ -534,6 +565,118 @@ Item {
duration: 300 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 { Rectangle {

View file

@ -66,6 +66,7 @@ Controls.Page {
id: presentation id: presentation
anchors.fill: parent anchors.fill: parent
} }
Presenter.SongEditor { Presenter.SongEditor {
id: songEditor id: songEditor
visible: false visible: false
@ -77,6 +78,12 @@ Controls.Page {
visible: false visible: false
anchors.fill: parent anchors.fill: parent
} }
Presenter.ImageEditor {
id: imageEditor
visible: false
anchors.fill: parent
}
} }
Presenter.Library { Presenter.Library {
@ -102,6 +109,10 @@ Controls.Page {
id: videosqlmodel id: videosqlmodel
} }
ImageSqlModel {
id: imagesqlmodel
}
ServiceItemModel { ServiceItemModel {
id: serviceItemModel id: serviceItemModel
} }
@ -141,19 +152,24 @@ Controls.Page {
presentation.visible = false; presentation.visible = false;
videoEditor.visible = false; videoEditor.visible = false;
videoEditor.stop(); videoEditor.stop();
imageEditor.visible = false;
songEditor.visible = true; songEditor.visible = true;
songEditor.changeSong(item); songEditor.changeSong(item);
break; break;
case "video" : case "video" :
presentation.visible = false; presentation.visible = false;
songEditor.visible = false; songEditor.visible = false;
imageEditor.visible = false;
videoEditor.visible = true; videoEditor.visible = true;
videoEditor.changeVideo(item); videoEditor.changeVideo(item);
break; break;
case "image" : case "image" :
mainPageArea.pop(Controls.StackView.Immediate); presentation.visible = false;
mainPageArea.push(imageEditorComp, Controls.StackView.Immediate); videoEditor.visible = false;
videoEditor.stop(); videoEditor.stop();
songEditor.visible = false;
imageEditor.visible = true;
imageEditor.changeImage(item);
break; break;
default: default:
videoEditor.visible = false; videoEditor.visible = false;

View file

@ -116,15 +116,12 @@ Qt::ItemFlags ServiceItemModel::flags(const QModelIndex &index) const {
void ServiceItemModel::addItem(ServiceItem *item) { void ServiceItemModel::addItem(ServiceItem *item) {
const int index = m_items.size(); const int index = m_items.size();
qDebug() << index; // qDebug() << index;
// foreach (item, m_items) { // foreach (item, m_items) {
// qDebug() << item; // qDebug() << item;
// } // }
beginInsertRows(QModelIndex(), index, index); beginInsertRows(QModelIndex(), index, index);
m_items.append(item); m_items.append(item);
foreach (item, m_items) {
qDebug() << item;
}
endInsertRows(); endInsertRows();
} }

View file

@ -39,14 +39,14 @@ static void createTable()
qFatal("Failed to query database: %s", qFatal("Failed to query database: %s",
qPrintable(query.lastError().text())); qPrintable(query.lastError().text()));
} }
qDebug() << query.lastQuery(); // qDebug() << query.lastQuery();
qDebug() << "inserting into songs"; // qDebug() << "inserting into songs";
query.exec( query.exec(
"INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, " "INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, "
"background, backgroundType, textAlignment) VALUES ('10,000 Reasons', '10,000 reasons " "background, backgroundType, textAlignment) VALUES ('10,000 Reasons', '10,000 reasons "
"for my heart to sing', 'Matt Redman', '13470183', '', '', '', '', 'center')"); "for my heart to sing', 'Matt Redman', '13470183', '', '', '', '', 'center')");
qDebug() << query.lastQuery(); // qDebug() << query.lastQuery();
query.exec("INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, " query.exec("INSERT INTO songs (title, lyrics, author, ccli, audio, vorder, "
"background, backgroundType, textAlignment) VALUES ('River', 'Im going down to " "background, backgroundType, textAlignment) VALUES ('River', 'Im going down to "
"the river', 'Jordan Feliz', '13470183', '', '', '', '', 'center')"); "the river', 'Jordan Feliz', '13470183', '', '', '', '', 'center')");
@ -55,15 +55,15 @@ static void createTable()
"background, backgroundType, textAlignment) VALUES ('Marvelous Light', 'Into marvelous " "background, backgroundType, textAlignment) VALUES ('Marvelous Light', 'Into marvelous "
"light Im running', 'Chris Tomlin', '13470183', '', '', '', '', 'center')"); "light Im running', 'Chris Tomlin', '13470183', '', '', '', '', 'center')");
qDebug() << query.lastQuery(); // qDebug() << query.lastQuery();
query.exec("select * from songs"); query.exec("select * from songs");
qDebug() << query.lastQuery(); // qDebug() << query.lastQuery();
} }
SongSqlModel::SongSqlModel(QObject *parent) SongSqlModel::SongSqlModel(QObject *parent)
: QSqlTableModel(parent) : QSqlTableModel(parent)
{ {
qDebug() << "creating table"; // qDebug() << "creating table";
createTable(); createTable();
setTable(songsTableName); setTable(songsTableName);
setEditStrategy(QSqlTableModel::OnManualSubmit); setEditStrategy(QSqlTableModel::OnManualSubmit);

View file

@ -46,4 +46,4 @@ private:
QUrl m_filePath; QUrl m_filePath;
}; };
#endif //SONGSQLMODEL_H #endif //VIDEOSQLMODEL_H