From a50da395dbd4f5ce39fabdb1017fc62097888c55 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 28 Sep 2022 20:54:43 -0700 Subject: [PATCH 01/23] Bible & songs over http with websocket --- biblewidget.cpp | 2 +- generalsettingwidget.cpp | 11 ++ generalsettingwidget.ui | 223 ++++++++++++++++++++++++++------- httpserver.cpp | 91 ++++++++++++++ httpserver.hpp | 52 ++++++++ httpserver.html | 263 +++++++++++++++++++++++++++++++++++++++ settings.cpp | 18 +++ settings.hpp | 4 + settingsdialog.cpp | 13 ++ settingsdialog.hpp | 5 + softProjector.pro | 13 +- softprojector.cpp | 52 +++++++- softprojector.hpp | 5 + softprojector.qrc | 1 + softprojector.ui | 2 +- websocketserver.cpp | 131 +++++++++++++++++++ websocketserver.hpp | 74 +++++++++++ 17 files changed, 911 insertions(+), 49 deletions(-) create mode 100644 httpserver.cpp create mode 100644 httpserver.hpp create mode 100644 httpserver.html create mode 100644 websocketserver.cpp create mode 100644 websocketserver.hpp diff --git a/biblewidget.cpp b/biblewidget.cpp index 85e89e3..169f845 100644 --- a/biblewidget.cpp +++ b/biblewidget.cpp @@ -376,7 +376,7 @@ void BibleWidget::on_search_button_clicked() if (!search_results.isEmpty()) // If have results, then show them { - if( not ui->result_label->isVisible() ) + if( ! ui->result_label->isVisible() ) { ui->lineEditBook->clear(); hidden_splitter_state = ui->results_splitter->saveState(); diff --git a/generalsettingwidget.cpp b/generalsettingwidget.cpp index e582cb6..7f87285 100644 --- a/generalsettingwidget.cpp +++ b/generalsettingwidget.cpp @@ -101,6 +101,12 @@ void GeneralSettingWidget::loadSettings() ui->comboBoxControlsAlignV->setCurrentIndex(mySettings.displayControls.alignmentV); ui->comboBoxControlsAlignH->setCurrentIndex(mySettings.displayControls.alignmentH); ui->horizontalSliderOpacity->setValue(mySettings.displayControls.opacity*100); + + // HTTP server + ui->checkBoxEnableHttpServer->setChecked(mySettings.httpServerEnabled); + ui->spinBoxHttpPort->setValue(mySettings.httpServerPort); + ui->spinBoxWebSocketPort->setValue(mySettings.webSocketServerPort); + ui->checkBoxDisableScreens->setChecked(mySettings.disableScreens); } void GeneralSettingWidget::loadThemes() @@ -139,6 +145,11 @@ GeneralSettings GeneralSettingWidget::getSettings() r = r/100; mySettings.displayControls.opacity = r; + mySettings.httpServerEnabled = ui->checkBoxEnableHttpServer->isChecked(); + mySettings.httpServerPort = ui->spinBoxHttpPort->value(); + mySettings.webSocketServerPort = ui->spinBoxWebSocketPort->value(); + mySettings.disableScreens = ui->checkBoxDisableScreens->isChecked(); + return mySettings; } diff --git a/generalsettingwidget.ui b/generalsettingwidget.ui index f2d843a..c98a28a 100644 --- a/generalsettingwidget.ui +++ b/generalsettingwidget.ui @@ -6,8 +6,8 @@ 0 0 - 412 - 440 + 354 + 448 @@ -59,31 +59,21 @@ Display Screen Selection - - - - Primary Display Screen: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - 0 - 0 - + + + + Qt::Horizontal - - Select onto which screen to display + + + 162 + 20 + - + - - + + Qt::Horizontal @@ -95,10 +85,10 @@ - - + + - Secondary Display Screen: + Primary Display Screen: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -118,18 +108,28 @@ - - - - Qt::Horizontal + + + + + 0 + 0 + - - - 162 - 20 - + + Select onto which screen to display - + + + + + + Secondary Display Screen: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + @@ -147,6 +147,16 @@ Primary Display Screen Controls + + + + NOTE: Display screen controls will be visible on the primary display screen only when one monitor is avaliable. + + + true + + + @@ -285,14 +295,143 @@ - - + + + + + + + HTTP Server + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 60 + 0 + + + + true + + + QAbstractSpinBox::NoButtons + + + 9999 + + + 8087 + + + + + + + true + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777213 + 16777215 + + - NOTE: Display screen controls will be visible on the primary display screen only when one monitor is avaliable. + HTTP Port: - + + + + + + Enable HTTP Server + + + + + + + Disable display forms for HTTP-only use (restart required). + + + Disable Screens + + + + + + + + 60 + 0 + + + true + + QAbstractSpinBox::NoButtons + + + 9999 + + + 8089 + + + + + + + WebSocket Port: + @@ -306,7 +445,7 @@ 20 - 14 + 40 diff --git a/httpserver.cpp b/httpserver.cpp new file mode 100644 index 0000000..cbcbefb --- /dev/null +++ b/httpserver.cpp @@ -0,0 +1,91 @@ +/*************************************************************************** +// +// softProjector - an open source media projection software +// Copyright (C) 2022 Vladislav Kobzar +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +***************************************************************************/ + +#include "httpserver.hpp" + +#include +#include +#include + +HttpServer::HttpServer() { } + +void HttpServer::startServer(quint16 httpServerPort, quint16 webSocketServerPort) +{ + httpPort = httpServerPort; + webSocketPort = webSocketServerPort; + server = new QTcpServer(); + + // waiting for the web brower to make contact, this will emit signal + connect(server, SIGNAL(newConnection()), this, SLOT(startConnection())); + + if(server->listen(QHostAddress::Any, httpPort)) { + isRunning = true; + qDebug() << "HTTP server listening on port: " << httpPort; + } else { + qDebug() << "HTTP server failed to start on port:" << httpPort; + } +} + +void HttpServer::startConnection() +{ + QTcpSocket *socket = server->nextPendingConnection(); + connect(socket, SIGNAL(readyRead()), this, SLOT(sendData())); + connect(socket, SIGNAL(disconnected()), this, SLOT(closingClient())); +} + +void HttpServer::closingClient() +{ + QTcpSocket* socket = (QTcpSocket*)sender(); + socket->deleteLater(); +} + +void HttpServer::sendData() +{ + QTcpSocket* socket = (QTcpSocket*)sender(); + + QFile file(":/httpserver.html"); + if(!file.open(QFile::ReadOnly | + QFile::Text)) + { + qDebug() << "Could not open httpserver.html for reading"; + return; + } + + QTextStream in(&file); + QString html = in.readAll(); + file.close(); + + html.replace("${webSocketPort}", QString::number(webSocketPort)); + + socket->write("HTTP/1.1 200 OK\r\n"); // \r needs to be before \n + socket->write("Content-Type: text/html\r\n"); + socket->write("Connection: close\r\n"); // Require two \r\n. + socket->write("\r\n"); + + socket->write(html.toUtf8()); + + socket->disconnectFromHost(); +} + +void HttpServer::stopServer() +{ + server->close(); + delete server; + isRunning = false; +} diff --git a/httpserver.hpp b/httpserver.hpp new file mode 100644 index 0000000..9c95061 --- /dev/null +++ b/httpserver.hpp @@ -0,0 +1,52 @@ +/*************************************************************************** +// +// softProjector - an open source media projection software +// Copyright (C) 2022 Vladislav Kobzar +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +***************************************************************************/ + +#include + +// Simple HTTP server implementation in Qt taken from https://stackoverflow.com/a/32931195 + +#ifndef HTTPSERVER_H +#define HTTPSERVER_H + +class HttpServer : public QObject +{ + Q_OBJECT + +public: + HttpServer(); + void startServer(quint16 httpServerPort, quint16 webSocketServerPort); + void stopServer(); + bool isRunning; + +public slots: + void startConnection(); + void sendData(); + void closingClient(); + +private: + QTcpServer *server = new QTcpServer(); + quint16 httpPort; + quint16 webSocketPort; + +private slots: + +signals: +}; + +#endif diff --git a/httpserver.html b/httpserver.html new file mode 100644 index 0000000..209fc2b --- /dev/null +++ b/httpserver.html @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + +
+
+

+

+
+
+

+

+
+
+
+

+ +
+ + + + + + diff --git a/settings.cpp b/settings.cpp index fe711a2..cd7bdba 100644 --- a/settings.cpp +++ b/settings.cpp @@ -800,6 +800,14 @@ void Settings::loadSettings() } else if (n == "dcOpacity") general.displayControls.opacity = v.toDouble(); + else if (n == "httpEnabled") + general.httpServerEnabled = (v=="true"); + else if (n == "httpPort") + general.httpServerPort = v.toInt(); + else if (n == "webSocketPort") + general.webSocketServerPort = v.toInt(); + else if (n == "disableScreens") + general.disableScreens = (v=="true"); } } else if(t == "spMain") @@ -914,6 +922,16 @@ void Settings::saveSettings() gset += "\ndcIconSize = " + QString::number(general.displayControls.buttonSize); gset += QString("\ndcAlignment = %1,%2").arg(general.displayControls.alignmentV).arg(general.displayControls.alignmentH); gset += "\ndcOpacity = " + QString::number(general.displayControls.opacity); + if(general.httpServerEnabled) + gset += "\nhttpEnabled = true"; + else + gset += "\nhttpEnabled = false"; + gset += "\nhttpPort = " + QString::number(general.httpServerPort); + gset += "\nwebSocketPort = " + QString::number(general.webSocketServerPort); + if(general.disableScreens) + gset += "\ndisableScreens = true"; + else + gset += "\ndisableScreens = false"; // **** prepare softProjector main settings spset += "spSplitter = " + spMain.spSplitter.toHex(); diff --git a/settings.hpp b/settings.hpp index b3d2572..e6c7801 100644 --- a/settings.hpp +++ b/settings.hpp @@ -286,6 +286,10 @@ class GeneralSettings bool settingsChangedAll; bool settingsChangedMulti; bool settingsChangedSingle; + bool httpServerEnabled; + int webSocketServerPort; + int httpServerPort; + bool disableScreens; }; class DisplaySettings diff --git a/settingsdialog.cpp b/settingsdialog.cpp index e9cb1c0..94d27c4 100644 --- a/settingsdialog.cpp +++ b/settingsdialog.cpp @@ -72,6 +72,9 @@ void SettingsDialog::loadSettings(GeneralSettings &sets, Theme &thm, SlideShowSe is_always_on_top = gsettings.displayIsOnTop; current_display_screen = gsettings.displayScreen; currentDisplayScreen2 = gsettings.displayScreen2; + currentHTTPEnabled = gsettings.httpServerEnabled; + currentHTTPPort = gsettings.httpServerPort; + currentWebSocketPort = gsettings.webSocketServerPort; // Set individual items generalSettingswidget->setSettings(gsettings); @@ -154,6 +157,14 @@ void SettingsDialog::applySettings() // Redraw the screen: emit updateScreen(); + // Update HTTP server state + if(currentHTTPPort != gsettings.httpServerPort + || currentWebSocketPort != gsettings.webSocketServerPort + || currentHTTPEnabled != gsettings.httpServerEnabled) + { + emit httpServerState(gsettings.httpServerEnabled,gsettings.httpServerPort,gsettings.webSocketServerPort); + } + // Save Settings theme.saveThemeUpdate(); @@ -161,6 +172,8 @@ void SettingsDialog::applySettings() is_always_on_top = gsettings.displayIsOnTop; current_display_screen = gsettings.displayScreen; currentDisplayScreen2 = gsettings.displayScreen2; + currentHTTPEnabled = gsettings.httpServerEnabled; + currentWebSocketPort = gsettings.webSocketServerPort; } void SettingsDialog::getThemes() diff --git a/settingsdialog.hpp b/settingsdialog.hpp index 82f58b0..4e347e6 100644 --- a/settingsdialog.hpp +++ b/settingsdialog.hpp @@ -52,6 +52,7 @@ public slots: BibleVersionSettings& bsets, BibleVersionSettings& bsets2); void positionsDisplayWindow(); void updateScreen(); + void httpServerState(bool& state, int& httpServerPort, int& webSocketServerPort); private: Ui::SettingsDialog *ui; @@ -60,6 +61,10 @@ public slots: int currentDisplayScreen2; bool is_always_on_top; + bool currentHTTPEnabled; + int currentHTTPPort; + int currentWebSocketPort; + GeneralSettings gsettings; Theme theme; BibleVersionSettings bsettings; diff --git a/softProjector.pro b/softProjector.pro index 3d2129f..4ccf696 100644 --- a/softProjector.pro +++ b/softProjector.pro @@ -27,7 +27,8 @@ QT += core \ quick \ printsupport \ multimedia \ - multimediawidgets + multimediawidgets \ + websockets TARGET = SoftProjector TEMPLATE = app @@ -88,7 +89,9 @@ SOURCES += main.cpp \ projectordisplayscreen.cpp \ imagegenerator.cpp \ spimageprovider.cpp \ - mediacontrol.cpp + mediacontrol.cpp \ + httpserver.cpp \ + websocketserver.cpp HEADERS += softprojector.hpp \ songwidget.hpp \ biblewidget.hpp \ @@ -131,7 +134,9 @@ HEADERS += softprojector.hpp \ projectordisplayscreen.hpp \ imagegenerator.hpp \ spimageprovider.hpp \ - mediacontrol.hpp + mediacontrol.hpp \ + httpserver.hpp \ + websocketserver.hpp FORMS += softprojector.ui \ songwidget.ui \ biblewidget.ui \ @@ -170,3 +175,5 @@ RESOURCES += softprojector.qrc win32 { RC_FILE = softprojector.rc } + +DISTFILES += diff --git a/softprojector.cpp b/softprojector.cpp index be4ef7b..367c49a 100644 --- a/softprojector.cpp +++ b/softprojector.cpp @@ -56,6 +56,13 @@ SoftProjector::SoftProjector(QWidget *parent) mediaPlayer = new MediaWidget; mediaControls = new MediaControl(this); + if (mySettings.general.httpServerEnabled) { + webSocketServer = new WebSocketServer(); + webSocketServer->startServer(mySettings.general.webSocketServerPort); + httpServer = new HttpServer(); + httpServer->startServer(mySettings.general.httpServerPort, mySettings.general.webSocketServerPort); + } + ui->setupUi(this); // Create action group for language slections @@ -71,7 +78,9 @@ SoftProjector::SoftProjector(QWidget *parent) // Apply Settings applySetting(mySettings.general, theme, mySettings.slideSets, mySettings.bibleSets, mySettings.bibleSets2); - positionDisplayWindow(); + if (!mySettings.general.disableScreens) { + positionDisplayWindow(); + } showing = false; @@ -110,6 +119,7 @@ SoftProjector::SoftProjector(QWidget *parent) BibleVersionSettings&,BibleVersionSettings&))); connect(settingsDialog,SIGNAL(positionsDisplayWindow()),this,SLOT(positionDisplayWindow())); connect(settingsDialog,SIGNAL(updateScreen()),this,SLOT(updateScreen())); + connect(settingsDialog,SIGNAL(httpServerState(bool&,int&,int&)),this,SLOT(httpServerState(bool&,int&,int&))); connect(songWidget,SIGNAL(addToSchedule(Song&)),this,SLOT(addToShcedule(Song&))); connect(announceWidget,SIGNAL(addToSchedule(Announcement&)),this,SLOT(addToShcedule(Announcement&))); @@ -207,6 +217,8 @@ SoftProjector::~SoftProjector() delete shSart2; delete helpDialog; delete ui; + delete webSocketServer; + delete httpServer; } void SoftProjector::positionDisplayWindow() @@ -290,6 +302,9 @@ void SoftProjector::positionDisplayWindow() void SoftProjector::showDisplayScreen(bool show) { + if (mySettings.general.disableScreens) + return; + if(show) { pds1->showFullScreen(); @@ -390,6 +405,20 @@ void SoftProjector::applySetting(GeneralSettings &g, Theme &t, SlideShowSettings retranslateUis(); } +void SoftProjector::httpServerState(bool &state, int &httpServerPort, int &webSocketServerPort) +{ + if (httpServer->isRunning) + httpServer->stopServer(); + + if (webSocketServer->isRunning) + webSocketServer->stopServer(); + + if (state) { + httpServer->startServer(httpServerPort, webSocketServerPort); + webSocketServer->startServer(webSocketServerPort); + } +} + void SoftProjector::closeEvent(QCloseEvent *event) { if(is_schedule_saved || schedule_file_path.isEmpty()) @@ -695,6 +724,11 @@ void SoftProjector::updateScreen() ui->actionShow->setEnabled(true); ui->actionHide->setEnabled(false); ui->actionClear->setEnabled(false); + + if (mySettings.general.httpServerEnabled) + { + webSocketServer->setBlank(); + } } else if ((currentRow >=0 || pType == VIDEO) && !new_list) { @@ -776,6 +810,11 @@ void SoftProjector::showBible() mySettings.bibleSets),theme.bible); } } + + if(mySettings.general.httpServerEnabled) + { + webSocketServer->setBibleText(bibleWidget->bible.getCurrentVerseAndCaption(currentRows,theme.bible, mySettings.bibleSets)); + } } void SoftProjector::showSong(int currentRow) @@ -803,6 +842,11 @@ void SoftProjector::showSong(int currentRow) pds2->renderSongText(current_song.getStanza(currentRow),s1); } } + + if(mySettings.general.httpServerEnabled) + { + webSocketServer->setSongText(current_song.getStanza(currentRow)); + } } void SoftProjector::showAnnounce(int currentRow) @@ -864,11 +908,15 @@ void SoftProjector::on_actionClear_triggered() ui->actionClear->setEnabled(false); ui->actionShow->setEnabled(true); // ui->actionHide->setEnabled(false); + if (mySettings.general.httpServerEnabled) + { + webSocketServer->setBlank(); + } } void SoftProjector::on_actionCloseDisplay_triggered() { - if(ui->actionCloseDisplay->isChecked()) + if(ui->actionCloseDisplay->isChecked() && !mySettings.general.disableScreens) { pds1->showFullScreen(); if(hasDisplayScreen2) diff --git a/softprojector.hpp b/softprojector.hpp index 3bcd1ee..361bfc9 100644 --- a/softprojector.hpp +++ b/softprojector.hpp @@ -40,6 +40,8 @@ #include "videoinfo.hpp" #include "slideshoweditor.hpp" #include "schedule.hpp" +#include "httpserver.hpp" +#include "websocketserver.hpp" class QActionGroup; @@ -75,6 +77,8 @@ class SoftProjector : public QMainWindow PictureWidget *pictureWidget; MediaWidget *mediaPlayer; MediaControl *mediaControls; + WebSocketServer *webSocketServer; + HttpServer *httpServer; bool showing; // whether we are currently showing to the projector Song current_song; @@ -93,6 +97,7 @@ public slots: void saveSettings(); void positionDisplayWindow(); void updateScreen(); + void httpServerState(bool &state, int &httpServerPort, int &webSocketServerPort); void setWaitCursor(); void setArrowCursor(); diff --git a/softprojector.qrc b/softprojector.qrc index a48f60a..f43a588 100644 --- a/softprojector.qrc +++ b/softprojector.qrc @@ -102,5 +102,6 @@ DisplayArea.qml + httpserver.html diff --git a/softprojector.ui b/softprojector.ui index 39799c5..a29b06c 100644 --- a/softprojector.ui +++ b/softprojector.ui @@ -216,7 +216,7 @@ 0 0 1055 - 26 + 21 diff --git a/websocketserver.cpp b/websocketserver.cpp new file mode 100644 index 0000000..3749861 --- /dev/null +++ b/websocketserver.cpp @@ -0,0 +1,131 @@ +/*************************************************************************** +// +// softProjector - an open source media projection software +// Copyright (C) 2022 Vladislav Kobzar +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +***************************************************************************/ + +#include "QtWebSockets/qwebsocketserver.h" +#include "QtWebSockets/qwebsocket.h" + +#include "websocketserver.hpp" + +#include + +WebSocketServer::WebSocketServer() { } + +void WebSocketServer::startServer(quint16 port) { + server = new QWebSocketServer(QStringLiteral("WebSocket Server"), QWebSocketServer::NonSecureMode, this); + if (server->listen(QHostAddress::Any, port)) { + qDebug() << "Websocket server listening on port" << port; + connect(server, &QWebSocketServer::newConnection, this, &WebSocketServer::onNewConnection); + connect(server, &QWebSocketServer::closed, this, &WebSocketServer::closed); + isRunning = true; + } +} + +void WebSocketServer::onNewConnection() +{ + QWebSocket *pSocket = server->nextPendingConnection(); + + connect(pSocket, &QWebSocket::disconnected, this, &WebSocketServer::socketDisconnected); + + clients << pSocket; + + sendToClients(); +} + +void WebSocketServer::sendToClients() +{ + QString output = QJsonDocument(jsonOutput).toJson(QJsonDocument::Compact); + for (int i = 0; i < clients.size(); i++) { + clients[i]->sendTextMessage(output); + } +} + +void WebSocketServer::socketDisconnected() +{ + QWebSocket *pClient = qobject_cast(sender()); + qDebug() << "socketDisconnected:" << pClient; + if (pClient) { + clients.removeAll(pClient); + pClient->deleteLater(); + } +} + +void WebSocketServer::closingClient() +{ + QTcpSocket* socket = (QTcpSocket*)sender(); + socket->deleteLater(); +} + +void WebSocketServer::stopServer() +{ // TODO: test changing port if delete server needed. + server->close(); + qDeleteAll(clients.begin(), clients.end()); + isRunning = false; +} + +void WebSocketServer::setBibleText(Verse verse) +{ + jsonOutput = QJsonObject(); + + QJsonObject bible; + + QJsonObject primary; + primary["verse"] = verse.primary_text; + primary["reference"] = verse.primary_caption; + bible["primary"] = primary; + + QJsonObject secondary; + secondary["verse"] = verse.secondary_text; + secondary["reference"] = verse.secondary_caption; + bible["secondary"] = secondary; + + QJsonObject trinary; + trinary["verse"] = verse.trinary_text; + trinary["reference"] = verse.trinary_caption; + bible["trinary"] = trinary; + + jsonOutput["bible"] = bible; + + sendToClients(); +} + +void WebSocketServer::setSongText(Stanza song) +{ + jsonOutput = QJsonObject(); + + QJsonObject jsonSong; + + jsonSong["stanza"] = song.stanza; + jsonSong["title"] = song.stanzaTitle; + jsonSong["tune"] = song.tune; + jsonSong["wordsBy"] = song.wordsBy; + jsonSong["musicBy"] = song.musicBy; + jsonSong["number"] = song.number; + jsonSong["isLast"] = song.isLast; + + jsonOutput["song"] = jsonSong; + + sendToClients(); +} + +void WebSocketServer::setBlank() +{ + jsonOutput = QJsonObject(); + + sendToClients(); +} diff --git a/websocketserver.hpp b/websocketserver.hpp new file mode 100644 index 0000000..eea20b2 --- /dev/null +++ b/websocketserver.hpp @@ -0,0 +1,74 @@ +/*************************************************************************** +// +// softProjector - an open source media projection software +// Copyright (C) 2022 Vladislav Kobzar +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 3 of the License. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +***************************************************************************/ + +#ifndef WEBSOCKETSERVER_HPP +#define WEBSOCKETSERVER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bible.hpp" +#include "song.hpp" +#include "announcement.hpp" + +// WebSocket implementation taken from https://code.qt.io/cgit/qt/qtwebsockets.git/tree/examples/websockets?h=5.15 + +QT_FORWARD_DECLARE_CLASS(QWebSocketServer) +QT_FORWARD_DECLARE_CLASS(QWebSocket) + +class WebSocketServer : public QObject +{ + Q_OBJECT + +public: + WebSocketServer(); + void startServer(quint16 port); + void stopServer(); + void setBibleText(Verse verse); + void setSongText(Stanza song); + void setBlank(); + bool isRunning; + +public slots: + void closingClient(); + +private: + QWebSocketServer *server; + QList clients; + qint64 bytesAvailable() const; + QJsonObject jsonOutput; + void establishConnection(); + bool servingConnection; + void sendToClients(); + void onNewConnection(); + void socketDisconnected(); + +signals: + void closed(); +}; + +#endif From 3978e500d82f6d038c3e451e3920a9ea755a0954 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Fri, 30 Sep 2022 21:04:25 -0700 Subject: [PATCH 02/23] Added IP Address config --- generalsettingwidget.cpp | 2 + generalsettingwidget.ui | 90 ++++++++++++++++++++++------------------ httpserver.cpp | 10 +++-- httpserver.hpp | 3 +- httpserver.html | 2 +- settings.cpp | 3 ++ settings.hpp | 1 + settingsdialog.cpp | 8 +++- settingsdialog.hpp | 3 +- softprojector.cpp | 12 +++--- softprojector.hpp | 2 +- websocketserver.cpp | 8 ++-- websocketserver.hpp | 2 +- 13 files changed, 85 insertions(+), 61 deletions(-) diff --git a/generalsettingwidget.cpp b/generalsettingwidget.cpp index 7f87285..2721270 100644 --- a/generalsettingwidget.cpp +++ b/generalsettingwidget.cpp @@ -104,6 +104,7 @@ void GeneralSettingWidget::loadSettings() // HTTP server ui->checkBoxEnableHttpServer->setChecked(mySettings.httpServerEnabled); + ui->lineEditHTTPServerIPAddress->setText(mySettings.httpServerIPAddress); ui->spinBoxHttpPort->setValue(mySettings.httpServerPort); ui->spinBoxWebSocketPort->setValue(mySettings.webSocketServerPort); ui->checkBoxDisableScreens->setChecked(mySettings.disableScreens); @@ -146,6 +147,7 @@ GeneralSettings GeneralSettingWidget::getSettings() mySettings.displayControls.opacity = r; mySettings.httpServerEnabled = ui->checkBoxEnableHttpServer->isChecked(); + mySettings.httpServerIPAddress = ui->lineEditHTTPServerIPAddress->text(); mySettings.httpServerPort = ui->spinBoxHttpPort->value(); mySettings.webSocketServerPort = ui->spinBoxWebSocketPort->value(); mySettings.disableScreens = ui->checkBoxDisableScreens->isChecked(); diff --git a/generalsettingwidget.ui b/generalsettingwidget.ui index c98a28a..edab285 100644 --- a/generalsettingwidget.ui +++ b/generalsettingwidget.ui @@ -7,7 +7,7 @@ 0 0 354 - 448 + 453 @@ -316,30 +316,8 @@ 9 - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - - 0 - 0 - - + + 60 @@ -356,11 +334,27 @@ 9999 - 8087 + 8089 - + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 20 + + + + + true @@ -388,14 +382,7 @@ - - - - Enable HTTP Server - - - - + Disable display forms for HTTP-only use (restart required). @@ -405,8 +392,21 @@ - - + + + + WebSocket Port: + + + + + + + + 0 + 0 + + 60 @@ -423,17 +423,27 @@ 9999 - 8089 + 8087 + + + + + + + Enable HTTP Server - + - WebSocket Port: + IP Address: + + +
diff --git a/httpserver.cpp b/httpserver.cpp index cbcbefb..aa6d19b 100644 --- a/httpserver.cpp +++ b/httpserver.cpp @@ -25,8 +25,9 @@ HttpServer::HttpServer() { } -void HttpServer::startServer(quint16 httpServerPort, quint16 webSocketServerPort) +void HttpServer::startServer(QString httpServerIPAddress, quint16 httpServerPort, quint16 webSocketServerPort) { + ipAddress = httpServerIPAddress; httpPort = httpServerPort; webSocketPort = webSocketServerPort; server = new QTcpServer(); @@ -34,11 +35,11 @@ void HttpServer::startServer(quint16 httpServerPort, quint16 webSocketServerPort // waiting for the web brower to make contact, this will emit signal connect(server, SIGNAL(newConnection()), this, SLOT(startConnection())); - if(server->listen(QHostAddress::Any, httpPort)) { + if(server->listen(QHostAddress(ipAddress), httpPort)) { isRunning = true; - qDebug() << "HTTP server listening on port: " << httpPort; + qDebug() << "HTTP server listening on " << ipAddress << ":" << httpPort; } else { - qDebug() << "HTTP server failed to start on port:" << httpPort; + qDebug() << "HTTP server failed listening on " << ipAddress << ":" << httpPort; } } @@ -71,6 +72,7 @@ void HttpServer::sendData() QString html = in.readAll(); file.close(); + html.replace("${ipAddress}", ipAddress); html.replace("${webSocketPort}", QString::number(webSocketPort)); socket->write("HTTP/1.1 200 OK\r\n"); // \r needs to be before \n diff --git a/httpserver.hpp b/httpserver.hpp index 9c95061..c640111 100644 --- a/httpserver.hpp +++ b/httpserver.hpp @@ -30,7 +30,7 @@ class HttpServer : public QObject public: HttpServer(); - void startServer(quint16 httpServerPort, quint16 webSocketServerPort); + void startServer(QString httpServerIPAddress, quint16 httpServerPort, quint16 webSocketServerPort); void stopServer(); bool isRunning; @@ -41,6 +41,7 @@ public slots: private: QTcpServer *server = new QTcpServer(); + QString ipAddress; quint16 httpPort; quint16 webSocketPort; diff --git a/httpserver.html b/httpserver.html index 209fc2b..1e463aa 100644 --- a/httpserver.html +++ b/httpserver.html @@ -185,7 +185,7 @@ diff --git a/softProjector.pro.user.9ab80e4 b/softProjector.pro.user.9ab80e4 new file mode 100644 index 0000000..599fd8c --- /dev/null +++ b/softProjector.pro.user.9ab80e4 @@ -0,0 +1,747 @@ + + + + + + EnvironmentId + {9ab80e4a-14b4-4125-8702-64ff78555811} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + false + true + false + 0 + true + true + 0 + 8 + true + false + 1 + true + true + true + *.md, *.MD, Makefile + false + true + + + + ProjectExplorer.Project.PluginSettings + + + true + false + true + true + true + true + + + 0 + true + + -fno-delayed-template-parsing + + true + Builtin.BuildSystem + + true + true + Builtin.DefaultTidyAndClazy + 2 + + + + true + + + + + ProjectExplorer.Project.Target.0 + + Desktop + Desktop Qt 5.12.12 MinGW 64-bit + Desktop Qt 5.12.12 MinGW 64-bit + qt.qt5.51212.win64_mingw73_kit + 0 + 0 + 0 + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Debug + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Release + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Release + + + true + QtProjectManager.QMakeBuildStep + false + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 1 + + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Profile + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + false + true + true + false + true + C:/Users/presentation/OneDrive/Documents/Software/softprojector/win32_build/bin + + 1 + + + + ProjectExplorer.Project.Target.1 + + Desktop + Desktop Qt 5.12.12 MSVC2017 64bit + Desktop Qt 5.12.12 MSVC2017 64bit + qt.qt5.51212.win64_msvc2017_64_kit + 1 + 0 + 0 + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Debug + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Release + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Profile + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + false + true + true + false + true + + 1 + + + + ProjectExplorer.Project.Target.2 + + Desktop + Desktop Qt 5.12.12 MSVC2015 64bit + Desktop Qt 5.12.12 MSVC2015 64bit + qt.qt5.51212.win64_msvc2015_64_kit + 1 + 0 + 0 + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Debug + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Release + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Release + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Profile + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + false + true + true + false + true + + 1 + + + + ProjectExplorer.Project.Target.3 + + Desktop + Desktop Qt 5.12.12 MinGW 32-bit + Desktop Qt 5.12.12 MinGW 32-bit + qt.qt5.51212.win32_mingw73_kit + 0 + 0 + 0 + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Debug + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Debug + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + + + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Release + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Release + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + + + 0 + C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Profile + C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Profile + + + true + QtProjectManager.QMakeBuildStep + true + + + + true + Qt4ProjectManager.MakeStep + + 2 + Build + Build + ProjectExplorer.BuildSteps.Build + + + + true + Qt4ProjectManager.MakeStep + clean + + 1 + Clean + Clean + ProjectExplorer.BuildSteps.Clean + + 2 + false + + + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + 0 + 0 + + 3 + + + 0 + Deploy + Deploy + ProjectExplorer.BuildSteps.Deploy + + 1 + + false + ProjectExplorer.DefaultDeployConfiguration + + 1 + + true + true + true + + 2 + + Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro + false + true + true + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 4 + + + ProjectExplorer.Project.Updater.FileVersion + 22 + + + Version + 22 + + From 466d5ac342bab9077d0c471f5b3ddfee71f09a76 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Thu, 18 Jul 2024 18:27:39 -0700 Subject: [PATCH 08/23] Parallel song translation for verse split --- editwidget.cpp | 47 +++++++- editwidget.hpp | 1 + generalsettingwidget.ui | 6 +- highlight.cpp | 7 ++ highlight.hpp | 2 +- httpserver.html | 163 +++++++++++++++++++++------ softprojector.cpp | 237 ++++++++++++++++++++++++++++++---------- softprojector.hpp | 9 +- song.cpp | 64 ++++++----- song.hpp | 4 +- songwidget.cpp | 2 +- websocketserver.cpp | 15 +-- websocketserver.hpp | 2 +- 13 files changed, 418 insertions(+), 141 deletions(-) diff --git a/editwidget.cpp b/editwidget.cpp index ef77b66..ace1e8c 100644 --- a/editwidget.cpp +++ b/editwidget.cpp @@ -20,7 +20,7 @@ #include "editwidget.hpp" #include "ui_editwidget.h" #include "song.hpp" - +#include "song.hpp" EditWidget::EditWidget(QWidget *parent) : QWidget(parent), ui(new Ui::EditWidget) @@ -56,7 +56,7 @@ void EditWidget::changeEvent(QEvent *e) } } -void EditWidget::on_btnSave_clicked() +bool EditWidget::validateSong() { // Check if song title exists. A song title MUST exits QString song_title = ui->lineEditTitle->text(); @@ -70,10 +70,50 @@ void EditWidget::on_btnSave_clicked() mb.setIcon(QMessageBox::Warning); mb.exec(); ui->lineEditTitle->setFocus(); + return false; + } + + // Validate translation lyrics line count matches original. + // Must be same for showing split verse. + QStringList lyricLines = ui->textEditSong->toPlainText().trimmed().split("\n"); + QStringList lyrics; // Create the verse blocks. + for (int i = 0; i < lyricLines.length(); i++) { + if (isStanzaTitle(lyricLines[i])) { + lyrics.append(getStanzaBlock(i, lyricLines, true)); + } + } + + for (const QString &verse : qAsConst(lyrics)) { + if (!verse.contains("=")) { continue; } + + QStringList split = verse.split("="); + if (split.length() == 2) { + // Validate amount of lines. + QStringList original = split[0].split("\n"); + original.removeFirst(); // Remove the "Verse/Chorus" titles. + QStringList translation = split[1].split("\n"); + if (original.count() == translation.count()) { + continue; + } + } + QMessageBox mb(this); + mb.setText(tr("The translated text must have the same amount of lines as the original. Must have same amount of lines between '='")); + mb.setWindowTitle(tr("Validation error")); + mb.setIcon(QMessageBox::Warning); + mb.exec(); + return false; + } + + return true; +} + +void EditWidget::on_btnSave_clicked() +{ + if (!validateSong()) { return; } - setSave(); + setSave(); setWaitCursor(); if (is_new) { @@ -201,7 +241,6 @@ void EditWidget::setSave(){ newSong.tune = ui->lineEditKey->text(); newSong.wordsBy = ui->lineEditWordsBy->text(); newSong.musicBy = ui->lineEditMusicBy->text(); -// newSong.songText = resetLyric(ui->textEditSong->toPlainText()); newSong.songText = ui->textEditSong->toPlainText().trimmed(); newSong.alignmentV = ui->comboBoxVAlignment->currentIndex(); newSong.alignmentH = ui->comboBoxHAlignment->currentIndex(); diff --git a/editwidget.hpp b/editwidget.hpp index 239fb9b..157ae1b 100644 --- a/editwidget.hpp +++ b/editwidget.hpp @@ -69,6 +69,7 @@ public slots: private slots: void addNewSong(Song song, QString msgNewSongbook, QString msgCaption); void addSongbook(); + bool validateSong(); void on_btnCancel_clicked(); void on_btnSave_clicked(); void resetUiItems(); diff --git a/generalsettingwidget.ui b/generalsettingwidget.ui index edab285..e3885d7 100644 --- a/generalsettingwidget.ui +++ b/generalsettingwidget.ui @@ -6,8 +6,8 @@ 0 0 - 354 - 453 + 371 + 519 @@ -301,7 +301,7 @@ - HTTP Server + HTTP Server (changes require restart) diff --git a/highlight.cpp b/highlight.cpp index a7c6670..0505d28 100644 --- a/highlight.cpp +++ b/highlight.cpp @@ -78,6 +78,13 @@ Highlight::Highlight(QTextDocument *parent) rule.format = vstavkaFormat; highlightingRules.append(rule); } + + // Translation formating + translationFormat.setForeground(Qt::black); + translationFormat.setBackground(Qt::green); + rule.pattern = QRegExp("^=[^\n]*"); + rule.format = translationFormat; + highlightingRules.append(rule); } void Highlight::highlightBlock(const QString &text) diff --git a/highlight.hpp b/highlight.hpp index 1a24fa5..ec164dd 100644 --- a/highlight.hpp +++ b/highlight.hpp @@ -43,7 +43,7 @@ class Highlight : public QSyntaxHighlighter }; QVector highlightingRules; - QTextCharFormat verseFormat, chorusFormat, vstavkaFormat; + QTextCharFormat verseFormat, chorusFormat, vstavkaFormat, translationFormat; }; // *** Announcement editor highlighter diff --git a/httpserver.html b/httpserver.html index d32a49a..197d827 100644 --- a/httpserver.html +++ b/httpserver.html @@ -18,6 +18,7 @@ ***************************************************************************/--> + @@ -25,30 +26,34 @@ + #song-translation { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + height: 122px; + width: 1860px; + display: none; + } + + #song-translation p { + white-space: normal; + } + + #song-translation-divider { + border-top: 3px solid #000; + position: absolute; + right: 0; + left: 0; + bottom: 0; + height: 122px; + width: 1920px; + background: rgba(0, 0, 0, 0.3); + z-index: -1; + display: none; + } + --> +
-

&nbps;&nbps;&nbps;&nbps;

-

&nbps;&nbps;

+

    

+

  

-

&nbps;&nbps;&nbps;&nbps;

-

&nbps;&nbps;

+

    

+

  

+
+
+

+
+
+

+
- + - + + \ No newline at end of file diff --git a/softprojector.cpp b/softprojector.cpp index fc49a3e..450bc7f 100644 --- a/softprojector.cpp +++ b/softprojector.cpp @@ -150,9 +150,11 @@ SoftProjector::SoftProjector(QWidget *parent) ui->toolBarEdit->addAction(ui->action_Help); ui->toolBarShow->addAction(ui->actionShow); - ui->toolBarShow->addAction(ui->actionClear); ui->toolBarShow->addAction(ui->actionHide); - ui->toolBarShow->addAction(ui->actionCloseDisplay); + if (!mySettings.general.disableScreens) { + ui->toolBarShow->addAction(ui->actionCloseDisplay); + ui->toolBarShow->addAction(ui->actionClear); + } ui->actionShow->setEnabled(false); ui->actionHide->setEnabled(false); @@ -529,20 +531,41 @@ void SoftProjector::setAnnounceText(Announcement announce, int row) updateScreen(); } +/** + * @brief SoftProjector::setSongList called on go live, from songwidget preview list or schedule. + * @param row the selected verse index of the song preview list. + */ void SoftProjector::setSongList(Song song, int row) { - QStringList song_list = song.getSongTextList(); current_song = song; - current_song_verse = row; + QStringList songTextList = song.getSongTextList(true); // The complete song text with verse titles and translations. + + // Create a list for the rightmost ui list (without translations), + // for the http-server a list without verse titles, and a list with just the translation. + QStringList song_list; + currentSongVerses.clear(); + currentSongTranslatedVerses.clear(); + + foreach (QString verseText, songTextList) { + QStringList split = verseText.split("="); + song_list.append(split[0].trimmed()); // Add the original language (with verse title) to the ui list. + if (split.length() == 2) { + currentSongTranslatedVerses.append(split[1].trimmed()); // Add the translation to the translation list. + } else { + currentSongTranslatedVerses.append(""); // Empty translation because not every verse may be translated. + } + // Remove verse title and add to currentSongVerses. + QStringList splitLines = split[0].split("\n"); + splitLines.removeFirst(); + currentSongVerses.append(splitLines.join("\n").trimmed()); + } // Display the specified song text in the right-most column of softProjector pType = SONG; + ui->widgetMultiVerse->setVisible(false); ui->rbMultiVerse->setChecked(false); mediaControls->setVisible(false); - showing = true; - new_list = true; - ui->listShow->clear(); ui->labelIcon->setPixmap(QPixmap(":/icons/icons/song_tab.png").scaled(16,16,Qt::IgnoreAspectRatio,Qt::SmoothTransformation)); ui->labelShow->setText(song.title); if(song.notes.isEmpty()) @@ -552,13 +575,26 @@ void SoftProjector::setSongList(Song song, int row) ui->labelSongNotes->setText(QString("%1\n%2").arg(tr("Notes:","Notes to songs")).arg(song.notes)); ui->labelSongNotes->setVisible(true); } + + ui->listShow->blockSignals(true); // So itemSelectionChanged on clear doesnt call updateScreen again, like the old new_list flag. + ui->listShow->clear(); ui->listShow->setSpacing(5); ui->listShow->setWordWrap(false); ui->listShow->addItems(song_list); ui->listShow->setCurrentRow(row); ui->listShow->setFocus(); - new_list = false; - updateScreen(); + ui->listShow->blockSignals(false); + + showing = true; + + if (mySettings.general.httpServerEnabled) + { + sendSongVerseToServer(); + ui->actionShow->setEnabled(false); + ui->actionHide->setEnabled(true); + } else { + updateScreen(); + } } void SoftProjector::setChapterList(QStringList chapter_list, QString caption, QItemSelection selectedItems) @@ -568,10 +604,10 @@ void SoftProjector::setChapterList(QStringList chapter_list, QString caption, QI ui->widgetMultiVerse->setVisible(true); mediaControls->setVisible(false); showing = true; - new_list = true; ui->labelIcon->setPixmap(QPixmap(":/icons/icons/book.png").scaled(16,16,Qt::IgnoreAspectRatio,Qt::SmoothTransformation)); ui->labelShow->setText(caption); ui->labelSongNotes->setVisible(false); + ui->listShow->blockSignals(true); ui->listShow->clear(); ui->listShow->setSpacing(2); ui->listShow->setWordWrap(true); @@ -584,8 +620,16 @@ void SoftProjector::setChapterList(QStringList chapter_list, QString caption, QI ui->listShow->setCurrentRow(selectedItems.first().top()); ui->listShow->selectionModel()->select(selectedItems,QItemSelectionModel::Select); ui->listShow->setFocus(); - new_list = false; - updateScreen(); + ui->listShow->blockSignals(false); + + if (mySettings.general.httpServerEnabled) + { + sendBibleVerseToServer(); + ui->actionShow->setEnabled(false); + ui->actionHide->setEnabled(true); + } else { + updateScreen(); + } } void SoftProjector::setPictureList(QList &image_list,int row,QString name) @@ -693,10 +737,22 @@ void SoftProjector::on_listShow_itemSelectionChanged() // Called when the user selects a different row in the show (right-most) list. // First check if ratio button "Multi Verse" is check. If so, make button "Show" // enable and update screen only after show_botton is clicked. - if(ui->rbMultiVerse->isChecked()) + if (ui->rbMultiVerse->isChecked()) { ui->actionShow->setEnabled(true); - else - updateScreen(); + return; + } + if (mySettings.general.httpServerEnabled) + { + if (showing) { + if (pType == BIBLE) { + sendBibleVerseToServer(); + } else if (pType == SONG) { + sendSongVerseToServer(); + } + } + return; + } + updateScreen(); } void SoftProjector::updateScreen() @@ -729,11 +785,6 @@ void SoftProjector::updateScreen() ui->actionShow->setEnabled(true); ui->actionHide->setEnabled(false); ui->actionClear->setEnabled(false); - - if (mySettings.general.httpServerEnabled) - { - webSocketServer->setBlank(); - } } else if ((currentRow >=0 || pType == VIDEO) && !new_list) { @@ -768,7 +819,6 @@ void SoftProjector::updateScreen() switch (pType) { case BIBLE: - showSongVerseSplit(false); // Don't show during bible. showBible(); break; case SONG: @@ -817,13 +867,11 @@ void SoftProjector::showBible() mySettings.bibleSets),theme.bible); } } - - if(mySettings.general.httpServerEnabled) - { - webSocketServer->setBibleText(bibleWidget->bible.getCurrentVerseAndCaption(currentRows,theme.bible, mySettings.bibleSets)); - } } +/* + * @param currentRow the selected verse index of the song preview or rightmost lists. + */ void SoftProjector::showSong(int currentRow) { // Get Song Settings @@ -849,13 +897,6 @@ void SoftProjector::showSong(int currentRow) pds2->renderSongText(current_song.getStanza(currentRow),s1); } } - - if(mySettings.general.httpServerEnabled && !songSplitVerse) - { // Sends just the full verse. - webSocketServer->setSongText(current_song.getStanza(currentRow), ""); - } else { - splitSongVerse(current_song.getStanza(currentRow).stanza); - } } void SoftProjector::showAnnounce(int currentRow) @@ -898,18 +939,38 @@ void SoftProjector::showVideo() void SoftProjector::on_actionShow_triggered() { showing = true; - if (ui->songVerseSplitListWidget->hasFocus()) { + if (mySettings.general.httpServerEnabled) + { + if (pType == BIBLE) { + sendBibleVerseToServer(); + } else if (pType == SONG) { + if (ui->songVerseSplitListWidget->hasFocus()) { + sendSongVerseSplit(); // Resume song verse split display. + } else { + sendSongVerseToServer(); + } + } + ui->actionShow->setEnabled(false); ui->actionHide->setEnabled(true); - sendSongVerseSplit(); // Resume song verse split display. - } else { - updateScreen(); + return; } + + updateScreen(); } void SoftProjector::on_actionHide_triggered() { showing = false; + if (mySettings.general.httpServerEnabled) + { + webSocketServer->setBlank(); + + ui->actionShow->setEnabled(true); + ui->actionHide->setEnabled(false); + return; + } + updateScreen(); } @@ -922,11 +983,6 @@ void SoftProjector::on_actionClear_triggered() } ui->actionClear->setEnabled(false); ui->actionShow->setEnabled(true); -// ui->actionHide->setEnabled(false); - if (mySettings.general.httpServerEnabled) - { - webSocketServer->setBlank(); - } } void SoftProjector::on_actionCloseDisplay_triggered() @@ -968,8 +1024,21 @@ void SoftProjector::on_actionSettings_triggered() void SoftProjector::on_listShow_doubleClicked(QModelIndex index) { - // Called when the user double clicks on a row in the preview table. + // Called when the user double clicks on a row in the rightmost table. showing = true; + if (mySettings.general.httpServerEnabled) + { + if (pType == BIBLE) { + sendBibleVerseToServer(); + } else if (pType == SONG) { + sendSongVerseToServer(); + } + + ui->actionShow->setEnabled(false); + ui->actionHide->setEnabled(true); + return; + } + updateScreen(); } @@ -2555,6 +2624,41 @@ void SoftProjector::openScheduleItem(QSqlQuery &q, const int scid, Announcement a.alignmentH = q.value(13).toInt(); } + +void SoftProjector::sendBibleVerseToServer() +{ + showSongVerseSplit(false); // Don't show song split ui during bible. + + + int srows(ui->listShow->count()); + QList currentRows; + for(int i(0); ilistShow->item(i)->isSelected()) + currentRows.append(i); + } + webSocketServer->setBibleText(bibleWidget->bible.getCurrentVerseAndCaption(currentRows,theme.bible, mySettings.bibleSets)); +} + +/* + * Called on go live with http server enabled. + */ +void SoftProjector::sendSongVerseToServer() +{ + int currentVerseIndex = ui->listShow->currentRow(); + + if(!songSplitVerse) + { // Sends the full verse. + webSocketServer->setSongText( + currentSongVerses[currentVerseIndex], + currentSongTranslatedVerses[currentVerseIndex], + "", "", (currentSongVerses.count() == currentVerseIndex + 1) + ); + } else { + setSongVerseSplitList(currentVerseIndex); + } +} + void SoftProjector::showSongVerseSplit(bool enabled) { songSplitVerse = enabled; @@ -2564,15 +2668,10 @@ void SoftProjector::showSongVerseSplit(bool enabled) } } -/* - * Split the current song verse into lines. - * Sends a couple of lines at a time for output display. -*/ -void SoftProjector::splitSongVerse(QString stanza, QString selectRow) -{ - // Group into 2's. If verse has odd amount of lines, last group will include 3. +QStringList SoftProjector::splitSongVerse(QString verse) +{ // Group into 2's. If verse has odd amount of lines, last group will include 3. QStringList splitList; - QStringList oneLines = stanza.split("\n"); + QStringList oneLines = verse.split("\n"); int splitListCount = oneLines.count(); int evenGroups = splitListCount / 2; int remaining = splitListCount % 2; @@ -2598,14 +2697,32 @@ void SoftProjector::splitSongVerse(QString stanza, QString selectRow) line = line + 2; } + return splitList; +} + +/** + * @param currentVerseIndex the selected verse index in ui->listShow + * @param selectRow "last" to select last split lines (when navigating back). + */ +void SoftProjector::setSongVerseSplitList(int currentVerseIndex, QString selectRow) +{ + currentSongVerseSplitList = splitSongVerse(currentSongVerses[currentVerseIndex]); + QString translatedVerse = currentSongTranslatedVerses[currentVerseIndex]; + if (translatedVerse == "") { + // Not every verse is translated, fill with empty strings. + currentSongTranslatedVerseSplitList = QVector(currentSongVerseSplitList.count(), "").toList(); + } else { + currentSongTranslatedVerseSplitList = splitSongVerse(currentSongTranslatedVerses[currentVerseIndex]); + } + ui->songVerseSplitLayoutWidget->setVisible(true); ui->songVerseSplitListWidget->blockSignals(true); ui->songVerseSplitListWidget->clear(); ui->songVerseSplitListWidget->blockSignals(false); ui->songVerseSplitListWidget->setSpacing(5); ui->songVerseSplitListWidget->setWordWrap(false); - ui->songVerseSplitListWidget->addItems(splitList); - ui->songVerseSplitListWidget->setCurrentRow(selectRow == "last" ? (splitList.count() - 1) : 0); + ui->songVerseSplitListWidget->addItems(currentSongVerseSplitList); // signals itemSelectionChanged() to send split to server. + ui->songVerseSplitListWidget->setCurrentRow(selectRow == "last" ? (currentSongVerseSplitList.count() - 1) : 0); ui->songVerseSplitListWidget->setFocus(); } @@ -2613,9 +2730,15 @@ void SoftProjector::splitSongVerse(QString stanza, QString selectRow) * Send the current song verse lines to the server. */ void SoftProjector::sendSongVerseSplit() { - int currentVerseRow = ui->listShow->currentRow(); - QString verseSplit = ui->songVerseSplitListWidget->currentItem()->text(); - webSocketServer->setSongText(current_song.getStanza(currentVerseRow), verseSplit); + int currentVerseIndex = ui->listShow->currentRow(); + int currentVerseSplitIndex = ui->songVerseSplitListWidget->currentRow(); + webSocketServer->setSongText( + currentSongVerses[currentVerseIndex], + currentSongTranslatedVerses[currentVerseIndex], + currentSongVerseSplitList[currentVerseSplitIndex], + currentSongTranslatedVerseSplitList[currentVerseSplitIndex], + (currentSongVerses.count() == currentVerseIndex + 1) + ); } void SoftProjector::on_songVerseSplitListWidget_itemSelectionChanged() @@ -2647,7 +2770,7 @@ void SoftProjector::on_songVerseSplitListWidgetNavigation(Qt::Key key) { ui->listShow->blockSignals(true); // Block itemSelectionChanged(), which would show the first split line. ui->listShow->setCurrentRow(ui->listShow->currentRow() - 1); ui->listShow->blockSignals(false); - splitSongVerse(current_song.getStanza((ui->listShow->currentRow())).stanza, "last"); + setSongVerseSplitList(ui->listShow->currentRow(), "last"); } else if (key == Qt::Key_Down && (ui->songVerseSplitListWidget->currentRow() == (ui->songVerseSplitListWidget->count() - 1))) { // Go to the next verse. nextSlide(); // Same as pressing the Right key. diff --git a/softprojector.hpp b/softprojector.hpp index d4cc830..65b24c9 100644 --- a/softprojector.hpp +++ b/softprojector.hpp @@ -83,6 +83,10 @@ class SoftProjector : public QMainWindow bool showing; // whether we are currently showing to the projector Song current_song; int current_song_verse; + QStringList currentSongVerses; // List of song verse blocks. + QStringList currentSongTranslatedVerses; // List of translation-only verse blocks. + QStringList currentSongVerseSplitList; // List of the current verse split into lines. + QStringList currentSongTranslatedVerseSplitList; // List of the current translation-only verse split into lines. Verse current_verse; Announcement currentAnnounce; QString version_string; @@ -251,9 +255,12 @@ private slots: void on_actionCloseDisplay_triggered(); void updateCloseDisplayButtons(bool isOn); + void sendBibleVerseToServer(); + void sendSongVerseToServer(); void showSongVerseSplit(bool enabled); void sendSongVerseSplit(); - void splitSongVerse(QString stanza, QString selectRow = "first"); + QStringList splitSongVerse(QString verse); + void setSongVerseSplitList(int currentVerseIndex, QString selectRow = "first"); void on_songVerseSplitListWidget_itemSelectionChanged(); void on_songVerseSplitListWidget_itemDoubleClicked(QListWidgetItem *item); void on_songVerseSplitListWidgetNavigation(Qt::Key); diff --git a/song.cpp b/song.cpp index ccfad66..d3ae444 100644 --- a/song.cpp +++ b/song.cpp @@ -161,6 +161,30 @@ bool isStanzaSlideTitle(QString string) return false; } +QString getStanzaBlock(int &i, QStringList &list, bool includeTranslation) +{ + QString line,block; + int j(i); + + while(i < list.count()) + { + line = list.at(i); + if(includeTranslation == false && line.contains(QRegExp("^="))) { break; } // Doesn't include translations in the ui lists. + if(line.contains(QRegExp("^&"))) + line.remove("&"); + + if(isStanzaTitle(line) && (i!=j)) + { + i--; + break; + } + block += line + "\n"; + ++i; + } + + return block.trimmed(); +} + Song::Song() { // initialize songId to be zero at start; @@ -248,7 +272,7 @@ void Song::readData() background.loadFromData(sq.value(20).toByteArray()); } -QStringList Song::getSongTextList() +QStringList Song::getSongTextList(bool includeTranslation) { // This function prepares a song list that will be shown in the song preview and show list. // It will it will automatically prepare correct sining order of verses and choruses. @@ -270,7 +294,7 @@ QStringList Song::getSongTextList() if(isStanzaVerseTitle(line)) { // Fill Verse - text = getStanzaBlock(pnum,songlist); + text = getStanzaBlock(pnum,songlist,includeTranslation); formatedSong.append(text); if (has_chorus)// add Chorus stansa to the formated list if it exists @@ -282,7 +306,7 @@ QStringList Song::getSongTextList() { // Fill Additional parts of the verse - text = getStanzaBlock(pnum,songlist); + text = getStanzaBlock(pnum,songlist,includeTranslation); // it chorus esits, this means that it was added to the formated list // and needs to be removed before adding addintion Veres stansas to formated list if(has_chorus) @@ -298,7 +322,7 @@ QStringList Song::getSongTextList() else if (isStanzaSlideTitle(line)) { // Fill Insert - text = getStanzaBlock(pnum,songlist); + text = getStanzaBlock(pnum,songlist,includeTranslation); formatedSong.append(text); // Chorus is not added to Insert, if one is needed, @@ -309,7 +333,7 @@ QStringList Song::getSongTextList() else if (isStanzaRefrainTitle(line)) { // Fill Chorus - text = getStanzaBlock(pnum,songlist); + text = getStanzaBlock(pnum,songlist,includeTranslation); QStringList chorusold = chorus; chorus.clear(); chorus.append(text); @@ -338,7 +362,7 @@ QStringList Song::getSongTextList() else if(isStanzaAndRefrainTitle(line)) { // Fill other chorus parts to Chorus block - text = getStanzaBlock(pnum,songlist); + text = getStanzaBlock(pnum,songlist,includeTranslation); removeLastChorus(chorus,formatedSong); chorus.append(text); @@ -350,39 +374,19 @@ QStringList Song::getSongTextList() return formatedSong; } -QString Song::getStanzaBlock(int &i, QStringList &list) -{ - QString line,block; - int j(i); - - while(i < list.count()) - { - line = list.at(i); - if(line.contains(QRegExp("^&"))) - line.remove("&"); - - if(isStanzaTitle(line) && (i!=j)) - { - i--; - break; - } - block += line + "\n"; - ++i; - } - - return block.trimmed(); -} - void Song::removeLastChorus(QStringList ct, QStringList &list) { for(int i(0);iaddSong(song); allSongs.append(song); diff --git a/websocketserver.cpp b/websocketserver.cpp index ca8d6a6..d50f365 100644 --- a/websocketserver.cpp +++ b/websocketserver.cpp @@ -104,20 +104,17 @@ void WebSocketServer::setBibleText(Verse verse) sendToClients(); } -void WebSocketServer::setSongText(Stanza song, QString verseSplit) +void WebSocketServer::setSongText(QString verse, QString verseTranslation, QString splitVerse, QString splitVerseTranslation, bool isLast) { jsonOutput = QJsonObject(); QJsonObject jsonSong; - jsonSong["stanza"] = song.stanza; - jsonSong["title"] = song.stanzaTitle; - jsonSong["tune"] = song.tune; - jsonSong["wordsBy"] = song.wordsBy; - jsonSong["musicBy"] = song.musicBy; - jsonSong["number"] = song.number; - jsonSong["isLast"] = song.isLast; - jsonSong["split"] = verseSplit; + jsonSong["verse"] = verse; + jsonSong["verseTranslation"] = verseTranslation; + jsonSong["split"] = splitVerse; + jsonSong["splitTranslation"] = splitVerseTranslation; + jsonSong["isLast"] = isLast; jsonOutput["song"] = jsonSong; diff --git a/websocketserver.hpp b/websocketserver.hpp index 1fe0484..2877fa9 100644 --- a/websocketserver.hpp +++ b/websocketserver.hpp @@ -49,7 +49,7 @@ class WebSocketServer : public QObject void startServer(QString httpServerIPAddress, quint16 port); void stopServer(); void setBibleText(Verse verse); - void setSongText(Stanza song, QString splitVerse); + void setSongText(QString verse, QString verseTranslation = "", QString splitVerse = "", QString splitVerseTranslation = "", bool isLast = false); void setBlank(); bool isRunning; From 15d9ba39ec9bf0aaa845f821f22baa20bab6a884 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Thu, 18 Jul 2024 20:39:41 -0700 Subject: [PATCH 09/23] Update README.md --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a667d5..45d209e 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# softprojector \ No newline at end of file +# softprojector +#### A version of SoftProjector which uses HTTP pages for bible/song displays instead of Windows forms. +If you've ever had your mouse or a Windows application open up on the projector during live service, and everyone seeing, then you know the downsides of projecting a computer display. This version of softprojector outputs bible & songs as a webpage, which allows you to add them as scene in OBS. From there you can send the OBS' program mix to Decklink Ouput into your video switcher. + +This http implementation opened up different style options of displaying bible text and lyrics. By editing the httpserver.html or running a local file copy, any amount of computers can show and style bible and songs however wanted. Your stream PC may want to show just the primary bible in a lower-thirds style for livestream, and your projection pc output shows parallel. + +Furthermore, this now allowed me to add features such as song verse split, where one screen can show the full verse and another shows 2 lines at a time. Perfect for readability on livestream. + +Lastly, for us multilinguals, I also added parallel song verse translations which works alongside the verse split feature. This is great because only one person/computer is needed to show different lyrics. + +#### To use this, configure your local IP in the settings +Add a browser source in OBS, and then style it using the css setting (example css in :/httpserver.html). Alternatively, copy the .html file to your local machine and edit it as you like. +![tempsnip](https://github.com/SoftProjector/softprojector/assets/23706821/816e8742-cbf1-4967-b50e-5ee511d69a4f) +#### To use the song verse split, check the 'Split Verse Text' checkbox above the song preview list. +When you go live, the split list will show below the full verses. Simply use your keyboard arrows to navigate. +![267142147-35343950-036b-433e-bbba-28600d464c01](https://github.com/user-attachments/assets/8c24ea6c-ea59-4b38-8b7d-36c22f68e7a1) +#### For parallel verses, add the translation to your song using '=' +![vers-tra](https://github.com/user-attachments/assets/5e2d0975-2fb9-4d0a-be0f-17471f30f009) From 5d97223bb3c53615fb55c16c08234cf06fd26b4f Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Fri, 19 Jul 2024 13:27:56 -0700 Subject: [PATCH 10/23] Display text fade-in-out example --- httpserver-fades-example.html | 527 ++++++++++++++++++++++++++++++++++ 1 file changed, 527 insertions(+) create mode 100644 httpserver-fades-example.html diff --git a/httpserver-fades-example.html b/httpserver-fades-example.html new file mode 100644 index 0000000..4ff1f8e --- /dev/null +++ b/httpserver-fades-example.html @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + +
+
+

    

+

  

+
+
+

    

+

  

+
+
+
+

+ +
+
+
+

+
+
+

+
+ + + + + + + \ No newline at end of file From 4fb6aea7955471e22ccf0a225afaa46278467993 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Sat, 27 Jul 2024 19:37:35 -0700 Subject: [PATCH 11/23] Optionally use/show song translation - new song checkbox field --- editwidget.cpp | 3 +++ editwidget.ui | 35 ++++++++++++++++++++++++++++++++++- main.cpp | 2 +- managedatadialog.cpp | 19 +++++++++++++------ softprojector.cpp | 10 ++++++---- song.cpp | 17 +++++++++++------ song.hpp | 1 + songwidget.cpp | 8 ++------ 8 files changed, 71 insertions(+), 24 deletions(-) diff --git a/editwidget.cpp b/editwidget.cpp index ace1e8c..585bbe6 100644 --- a/editwidget.cpp +++ b/editwidget.cpp @@ -190,6 +190,7 @@ void EditWidget::resetUiItems() ui->lineEditKey->setText(ss.tune); ui->comboBoxCategory->setCurrentIndex(cat_ids.indexOf(ss.category)); ui->checkBoxSongSettings->setChecked(ss.usePrivateSettings); + ui->checkBoxUseTranslation->setChecked(ss.useTranslation); ui->groupBoxSettings->setVisible(ss.usePrivateSettings); ui->comboBoxVAlignment->setCurrentIndex(ss.alignmentV); ui->comboBoxHAlignment->setCurrentIndex(ss.alignmentH); @@ -215,6 +216,7 @@ void EditWidget::setUiItems() ui->lineEditKey->setText(editSong.tune); ui->comboBoxCategory->setCurrentIndex(cat_ids.indexOf(editSong.category)); setSongbook(editSong.songID); + ui->checkBoxUseTranslation->setChecked(editSong.useTranslation); ui->checkBoxSongSettings->setChecked(editSong.usePrivateSettings); ui->groupBoxSettings->setVisible(editSong.usePrivateSettings); ui->comboBoxVAlignment->setCurrentIndex(editSong.alignmentV); @@ -244,6 +246,7 @@ void EditWidget::setSave(){ newSong.songText = ui->textEditSong->toPlainText().trimmed(); newSong.alignmentV = ui->comboBoxVAlignment->currentIndex(); newSong.alignmentH = ui->comboBoxHAlignment->currentIndex(); + newSong.useTranslation = ui->checkBoxUseTranslation->isChecked(); newSong.usePrivateSettings = ui->checkBoxSongSettings->isChecked(); newSong.useBackground = ui->checkBoxUseBackground->isChecked(); newSong.backgroundName = ui->lineEditBackgroundPath->text(); diff --git a/editwidget.ui b/editwidget.ui index fd04644..2bee91c 100644 --- a/editwidget.ui +++ b/editwidget.ui @@ -169,6 +169,30 @@
+ + + + + + Use Translation + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + @@ -202,7 +226,16 @@ true - + + 0 + + + 0 + + + 0 + + 0 diff --git a/main.cpp b/main.cpp index bf4f123..67df309 100644 --- a/main.cpp +++ b/main.cpp @@ -76,7 +76,7 @@ bool connect(QString database_file) sq.exec("CREATE TABLE 'Songbooks' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 'name' TEXT, 'info' TEXT)"); sq.exec("CREATE TABLE 'Songs' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , " "'songbook_id' INTEGER, 'number' INTEGER, 'title' TEXT, 'category' INTEGER DEFAULT 0, " - "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, " + "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, 'use_translation BOOL'" "'use_private' BOOL, 'alignment_v' INTEGER, 'alignment_h' INTEGER, 'color' INTEGER, 'font' TEXT, " "'info_color' INTEGER, 'info_font' TEXT, 'ending_color' INTEGER, 'ending_font' TEXT, " "'use_background' BOOL, 'background_name' TEXT, 'background' BLOB, 'count' INTEGER DEFAULT 0, 'date' TEXT)"); diff --git a/managedatadialog.cpp b/managedatadialog.cpp index ec96181..04c9e2a 100644 --- a/managedatadialog.cpp +++ b/managedatadialog.cpp @@ -357,7 +357,7 @@ void ManageDataDialog::importSongbook(QString path) // Prepare to import Songs QSqlDatabase::database().transaction(); sq.prepare("INSERT INTO Songs (songbook_id, number, title, category, tune, words, music, " - "song_text, notes, use_private, alignment_v, alignment_h, color, font, " + "song_text, notes, use_translation, use_private, alignment_v, alignment_h, color, font, " "background_name, count, date)" "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); @@ -401,7 +401,7 @@ void ManageDataDialog::importSongbook(QString path) } else if (xml.StartElement && xml.name() == "Song") { - QString xnum,xtitle,xcat,xtune,xwords,xmusic,xtext,xnotes, + QString xnum,xtitle,xcat,xtune,xwords,xmusic,xtext,xnotes,xusetranslation, xuse,xalign,xcolor,xfont,xback,xcount,xdate; // Read song data xnum = xml.attributes().value("number").toString(); @@ -444,6 +444,11 @@ void ManageDataDialog::importSongbook(QString path) xnotes = xml.readElementText(); xml.readNext(); } + else if(xml.StartElement && xml.name() == "use_translation") + { + xusetranslation = xml.readElementText(); + xml.readNext(); + } else if(xml.StartElement && xml.name() == "use_private") { xuse = xml.readElementText(); @@ -569,7 +574,7 @@ void ManageDataDialog::importSongbook(QString path) // Get and insert songs q.exec("SELECT * FROM Songs"); - sq.prepare("INSERT INTO Songs (songbook_id,number,title,category,tune,words,music,song_text,notes," + sq.prepare("INSERT INTO Songs (songbook_id,number,title,category,tune,words,music,song_text,notes,use_translation," "use_private,alignment_v,alignment_h,color,font,info_color,info_font,ending_color," "ending_font,use_background,background_name,background,count,date) " "VALUES(:id, :num, :ti, :ca, :tu, :wo, :mu, :st, :no, :up, :av, :ah, :tc, :tf, " @@ -588,6 +593,7 @@ void ManageDataDialog::importSongbook(QString path) st = cleanSongLines(st); sq.bindValue(":st",st); sq.bindValue(":no",q.record().value("notes")); + sq.bindValue(":ut",q.record().value("use_translation")); sq.bindValue(":up",q.record().value("use_private")); sq.bindValue(":av",q.record().value("alignment_v")); sq.bindValue(":ah",q.record().value("alignment_h")); @@ -721,7 +727,7 @@ void ManageDataDialog::exportSongbook(QString path) q.exec("PRAGMA user_version = 2"); q.exec("CREATE TABLE 'SongBook' ('title' TEXT, 'info' TEXT)"); q.exec("CREATE TABLE 'Songs' ('number' INTEGER, 'title' TEXT, 'category' INTEGER DEFAULT 0, " - "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, " + "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, use_translation BOOL" "'use_private' BOOL, 'alignment_v' INTEGER, 'alignment_h' INTEGER, 'color' INTEGER, 'font' TEXT, " "'info_color' INTEGER, 'info_font' TEXT, 'ending_color' INTEGER, 'ending_font' TEXT, " "'use_background' BOOL, 'background_name' TEXT, 'background' BLOB, 'count' INTEGER DEFAULT 0, 'date' TEXT)"); @@ -740,10 +746,10 @@ void ManageDataDialog::exportSongbook(QString path) // Write Songs sq.exec("SELECT * FROM Songs WHERE songbook_id = " + songbook_id); - q.prepare("INSERT INTO Songs (number,title,category,tune,words,music,song_text,notes," + q.prepare("INSERT INTO Songs (number,title,category,tune,words,music,song_text,notes,use_translation" "use_private,alignment_v,alignment_h,color,font,info_color,info_font,ending_color,ending_font," "use_background,background_name,background,count,date) " - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); while(sq.next()) { q.addBindValue(sq.record().value("number")); @@ -754,6 +760,7 @@ void ManageDataDialog::exportSongbook(QString path) q.addBindValue(sq.record().value("music")); q.addBindValue(sq.record().value("song_text")); q.addBindValue(sq.record().value("notes")); + q.addBindValue(sq.record().value("use_translation")); q.addBindValue(sq.record().value("use_private")); q.addBindValue(sq.record().value("alignment_v")); q.addBindValue(sq.record().value("alignment_h")); diff --git a/softprojector.cpp b/softprojector.cpp index 450bc7f..25788d5 100644 --- a/softprojector.cpp +++ b/softprojector.cpp @@ -549,7 +549,7 @@ void SoftProjector::setSongList(Song song, int row) foreach (QString verseText, songTextList) { QStringList split = verseText.split("="); song_list.append(split[0].trimmed()); // Add the original language (with verse title) to the ui list. - if (split.length() == 2) { + if (song.useTranslation && split.length() == 2) { currentSongTranslatedVerses.append(split[1].trimmed()); // Add the translation to the translation list. } else { currentSongTranslatedVerses.append(""); // Empty translation because not every verse may be translated. @@ -2198,7 +2198,7 @@ void SoftProjector::saveSchedule(bool overWrite) sq.exec("CREATE TABLE IF NOT EXISTS 'bible' ('scid' INTEGER, 'verseIds' TEXT, 'caption' TEXT, 'captionLong' TEXT)"); sq.exec("CREATE TABLE IF NOT EXISTS 'song' ('scid' INTEGER, 'songid' INTEGER, 'sbid' INTEGER, 'sbName' TEXT, " "'number' INTEGER, 'title' TEXT, 'category' INTEGER, 'tune' TEXT, 'wordsBy' TEXT, 'musicBy' TEXT, " - "'songText' TEXT, 'notes' TEXT, 'usePrivate' BOOL, 'alignV' INTEGER, 'alignH' INTEGER, 'color' INTEGER, " + "'songText' TEXT, 'notes' TEXT, 'useTranslation' BOOL, 'usePrivate' BOOL, 'alignV' INTEGER, 'alignH' INTEGER, 'color' INTEGER, " "'font' TEXT, 'infoColor' INTEGER, 'infoFont' TEXT, 'endingColor' INTEGER, 'endingFont' TEXT, " "'useBack' BOOL, 'backImage' BLOB, 'backName' TEXT)"); sq.exec("CREATE TABLE IF NOT EXISTS 'slideshow' ('scid' INTEGER, 'ssid' INTEGER, 'name' TEXT, 'info' TEXT)"); @@ -2257,9 +2257,9 @@ void SoftProjector::saveScheduleItemNew(QSqlQuery &q, int scid, const BibleHisto void SoftProjector::saveScheduleItemNew(QSqlQuery &q, int scid, const Song &s) { q.prepare("INSERT INTO song (scid,songid,sbid,sbName,number,title,category,tune,wordsBy,musicBy," - "songText,notes,usePrivate,alignV,alignH,color,font,infoColor,infoFont,endingColor," + "songText,notes,useTranslation,usePrivate,alignV,alignH,color,font,infoColor,infoFont,endingColor," "endingFont,useBack,backImage,backName) " - "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); + "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); q.addBindValue(scid); q.addBindValue(s.songID); q.addBindValue(s.songbook_id); @@ -2272,6 +2272,7 @@ void SoftProjector::saveScheduleItemNew(QSqlQuery &q, int scid, const Song &s) q.addBindValue(s.musicBy); q.addBindValue(s.songText); q.addBindValue(s.notes); + q.addBindValue(s.useTranslation); q.addBindValue(s.usePrivateSettings); q.addBindValue(s.alignmentV); q.addBindValue(s.alignmentH); @@ -2557,6 +2558,7 @@ void SoftProjector::openScheduleItem(QSqlQuery &q, const int scid, Song &s) s.musicBy = r.field("musicBy").value().toString(); s.songText = r.field("songText").value().toString(); s.notes = r.field("notes").value().toString(); + s.useTranslation = r.field("useTranslation").value().toBool(); s.usePrivateSettings = r.field("usePrivate").value().toBool(); s.alignmentV = r.field("alignV").value().toInt(); s.alignmentH = r.field("alignH").value().toInt(); diff --git a/song.cpp b/song.cpp index d3ae444..7c95e10 100644 --- a/song.cpp +++ b/song.cpp @@ -229,6 +229,7 @@ void Song::setDefaults() backgroundName = ""; background = QPixmap(); notes = ""; + useTranslation = false; } void Song::readData() @@ -236,10 +237,10 @@ void Song::readData() QSqlQuery sq; // 0 1 2 3 4 5 6 7 8 // 9 10 11 12 13 14 15 16 17 - // 18 19 20 + // 18 19 20 21 sq.exec("SELECT songbook_id, number, title, category, tune, words, music, song_text, notes, " "use_private, alignment_v, alignment_h, color, font, info_color, info_font, ending_color, ending_font, " - "use_background, background_name, background FROM Songs WHERE id = " + QString::number(songID)); + "use_background, background_name, background, use_translation FROM Songs WHERE id = " + QString::number(songID)); sq.first(); songbook_id = sq.value(0).toString(); number = sq.value(1).toInt(); @@ -250,6 +251,7 @@ void Song::readData() musicBy = sq.value(6).toString(); songText = sq.value(7).toString(); notes = sq.value(8).toString(); + useTranslation = sq.value(21).toBool(); usePrivateSettings = sq.value(9).toBool(); if(!sq.value(10).isNull()) alignmentV = sq.value(10).toInt(); @@ -656,7 +658,7 @@ void Song::saveUpdate() // Update song information QSqlQuery sq; sq.prepare("UPDATE Songs SET songbook_id = ?, number = ?, title = ?, category = ?, tune = ?, words = ?, music = ?, " - "song_text = ?, notes = ?, use_private = ?, alignment_v = ?, alignment_h = ?, color = ?, font = ?, " + "song_text = ?, notes = ?, use_translation = ?, use_private = ?, alignment_v = ?, alignment_h = ?, color = ?, font = ?, " "info_color = ?, info_font = ?, ending_color = ?, ending_font = ?, use_background = ?, " "background_name = ?, background = ? WHERE id = ?"); sq.addBindValue(songbook_id); @@ -668,6 +670,7 @@ void Song::saveUpdate() sq.addBindValue(musicBy); sq.addBindValue(songText); sq.addBindValue(notes); + sq.addBindValue(useTranslation); sq.addBindValue(usePrivateSettings); sq.addBindValue(alignmentV); sq.addBindValue(alignmentH); @@ -688,7 +691,7 @@ void Song::saveNew() { // Add a new song QSqlQuery sq; - sq.prepare("INSERT INTO Songs (songbook_id,number,title,category,tune,words,music,song_text,notes," + sq.prepare("INSERT INTO Songs (songbook_id,number,title,category,tune,words,music,song_text,notes,use_translation" "use_private,alignment_v,alignment_h,color,font,info_color,info_font,ending_color," "ending_font,use_background,background_name,background) " "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); @@ -701,6 +704,7 @@ void Song::saveNew() sq.addBindValue(musicBy); sq.addBindValue(songText); sq.addBindValue(notes); + sq.addBindValue(useTranslation); sq.addBindValue(usePrivateSettings); sq.addBindValue(alignmentV); sq.addBindValue(alignmentH); @@ -742,10 +746,10 @@ QList SongDatabase::getSongs() // get songs // 0 1 2 3 4 5 6 7 8 // 9 10 11 12 13 14 15 16 17 - // 18 19 20 + // 18 19 20 22 sq.exec("SELECT id, songbook_id, number, title, category, tune, words, music, song_text, notes, " "use_private, alignment_v, alignment_h, color, font, info_color, info_font, ending_color, ending_font, " - "use_background, background_name, background FROM Songs"); + "use_background, background_name, background, use_translation FROM Songs"); while(sq.next()) { Song song; @@ -759,6 +763,7 @@ QList SongDatabase::getSongs() song.musicBy = sq.value(7).toString(); song.songText = sq.value(8).toString(); song.notes = sq.value(9).toString(); + song.useTranslation = sq.value(22).toBool(); song.usePrivateSettings = sq.value(10).toBool(); if(!sq.value(11).isNull()) song.alignmentV = sq.value(11).toInt(); diff --git a/song.hpp b/song.hpp index 1858ffd..1226de0 100644 --- a/song.hpp +++ b/song.hpp @@ -87,6 +87,7 @@ class Song QString musicBy; QString songText; QString notes; + bool useTranslation; // Show verse translations or not. bool usePrivateSettings; int alignmentV; int alignmentH; diff --git a/songwidget.cpp b/songwidget.cpp index 01e21cf..5c2e16c 100644 --- a/songwidget.cpp +++ b/songwidget.cpp @@ -676,14 +676,10 @@ void SongWidget::setSearchActive() } /* - * Hide the song verse split list. - * It is only initially shown when going live through sendToProjector() - * This allows split to be disabled during live for if not intended. + * Show/Hide the song verse split list. */ void SongWidget::on_enableSplitVerseCheckBox_toggled(bool checked) { - if (!checked) { - emit enableSongSplitVerse(false); - } + emit enableSongSplitVerse(checked); } From 0b0b001f9858ce6e6db1cd7d5f53179aa2f8defd Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:34:57 -0700 Subject: [PATCH 12/23] Allow edit of song if multiple of same song number in same songbook --- editwidget.cpp | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/editwidget.cpp b/editwidget.cpp index 585bbe6..f6ccdfa 100644 --- a/editwidget.cpp +++ b/editwidget.cpp @@ -459,34 +459,13 @@ void EditWidget::loadCategories(bool ui_update) int EditWidget::isInDatabase(Song *song) { - QString s_title(""), s_id("0"), sb_id("0"); QSqlQuery sq; - - // check if song is part of songbook - sq.exec("SELECT id FROM Songbooks WHERE name = '" + song->songbook_name + "'"); - while(sq.next()) - sb_id = sq.value(0).toString().trimmed(); - sq.clear(); - if(sb_id == "0") - return 0; // no such songbook in database - - // get song id - sq.exec("SELECT id, title from Songs WHERE songbook_id = '" + sb_id +"' AND number = '" + QString::number(song->number) +"'"); - while(sq.next()) - { - s_id = sq.value(0).toString().trimmed(); - s_title = sq.value(1).toString().trimmed(); + sq.exec("SELECT id from Songs WHERE id = " + QString::number(song->songID)); + if (sq.first()) { + int s_id = sq.value(0).toInt(); + return s_id; } - sq.clear(); - if(s_id == "0") - return 0; // no matching song - song->songID = s_id.toInt(); - - // get song title - if(s_title!=song->title.trimmed()) - return 0; - else - return s_id.toInt(); + return 0; } void EditWidget::on_checkBoxSongSettings_toggled(bool checked) From 3a87eebb38e065e5fb1196fb47b3f48b31c05cf9 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:51:45 -0700 Subject: [PATCH 13/23] songs list fixed songbook column horizontal sizing --- songwidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/songwidget.cpp b/songwidget.cpp index 5c2e16c..08575b4 100644 --- a/songwidget.cpp +++ b/songwidget.cpp @@ -43,7 +43,7 @@ SongWidget::SongWidget(QWidget *parent) : ui->songs_view->setColumnHidden(0, true); // Hide category ui->songs_view->resizeColumnToContents(1); // Song Number ui->songs_view->setColumnWidth(2, 150);//Song Title - ui->songs_view->resizeColumnToContents(3); // Songbook + ui->songs_view->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); // Songbook ui->songs_view->setColumnWidth(4, 50);//Tune proxy_model->setSongbookFilter("ALL"); From 296cbeb5c17545b075e5e61f2305940c6a72e3e3 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:57:01 -0700 Subject: [PATCH 14/23] Show song split from schedule fix --- softprojector.cpp | 6 ++++-- songwidget.cpp | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/softprojector.cpp b/softprojector.cpp index 25788d5..09e927e 100644 --- a/softprojector.cpp +++ b/softprojector.cpp @@ -478,6 +478,9 @@ void SoftProjector::keyPressEvent(QKeyEvent *event) { ui->projectTab->setCurrentWidget(songWidget); songWidget->setSearchActive(); + if (songSplitVerse) { + ui->songVerseSplitLayoutWidget->setVisible(false); // re-show the split list ui. + } } else if(key == Qt::Key_F8) ui->projectTab->setCurrentWidget(announceWidget); @@ -2629,8 +2632,7 @@ void SoftProjector::openScheduleItem(QSqlQuery &q, const int scid, Announcement void SoftProjector::sendBibleVerseToServer() { - showSongVerseSplit(false); // Don't show song split ui during bible. - + ui->songVerseSplitLayoutWidget->setVisible(false); // Hide the song split list if visible. int srows(ui->listShow->count()); QList currentRows; diff --git a/songwidget.cpp b/songwidget.cpp index 08575b4..8e46c88 100644 --- a/songwidget.cpp +++ b/songwidget.cpp @@ -182,6 +182,7 @@ void SongWidget::sendToPreview(Song song) ui->label_notes->setVisible(true); } preview_song = song; + ui->btnLive->setEnabled(true); ui->enableSplitVerseCheckBox->setEnabled(true); } From bb10c7d1621c5d56c049b7c1557cb7d4e1b01dcd Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Sat, 10 Aug 2024 21:01:13 -0700 Subject: [PATCH 15/23] httpserver.html ex. font fix --- httpserver.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/httpserver.html b/httpserver.html index 197d827..4734e38 100644 --- a/httpserver.html +++ b/httpserver.html @@ -131,7 +131,7 @@ - - - - - - - - - - - - - -
-
-

    

-

  

-
-
-

    

-

  

-
-
-
-

- -
-
-
-

-
-
-

-
- - - - - - - \ No newline at end of file diff --git a/httpserver.html b/httpserver.html index 4734e38..64afe8a 100644 --- a/httpserver.html +++ b/httpserver.html @@ -53,7 +53,12 @@ flex-direction: column; height: 1040px; width: 1860px; - display: none; + display: flex; + position: absolute; + top: 20px; + right: 30px; + bottom: 20px; + left: 20px; } #bible #primary, @@ -81,7 +86,7 @@ text-align: center; height: 1040px; width: 1860px; - display: none; + display: flex; } #song .textFitted { @@ -108,7 +113,11 @@ text-align: center; height: 122px; width: 1860px; - display: none; + position: absolute; + right: 0; + left: 0; + bottom: 0; + margin: 0 auto; } #song-translation p { @@ -127,6 +136,38 @@ z-index: -1; display: none; } + + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + @keyframes fadeOut { + from { + opacity: 1; + } + + to { + opacity: 0; + } + } + + .fadeIn { + animation-name: fadeIn; + animation-duration: 0.5s; + animation-fill-mode: forwards; + } + + .fadeOut { + animation-name: fadeOut; + animation-duration: 0.5s; + animation-fill-mode: forwards; + } --> @@ -267,26 +349,43 @@ - \ No newline at end of file + From b3ad3825126f91ce21b56d5c94160e378df8690e Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:10:58 -0700 Subject: [PATCH 19/23] Qt6 on Win11 --- .gitignore | 2 ++ 3rdparty/headers/qmediaplaylist.h | 6 +++--- 3rdparty/sources/qmediaplaylist.cpp | 6 +++--- 3rdparty/sources/qmediaplaylist_p.cpp | 2 +- 3rdparty/sources/qplaylistfileparser.cpp | 2 +- src/softProjector.pro | 6 ++++++ 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 790be03..2ec412e 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ unknownsys_build/* win32_build/* unix_build/* mac_build/* +/src/build +/src/win32_build diff --git a/3rdparty/headers/qmediaplaylist.h b/3rdparty/headers/qmediaplaylist.h index 8342712..1205332 100644 --- a/3rdparty/headers/qmediaplaylist.h +++ b/3rdparty/headers/qmediaplaylist.h @@ -6,7 +6,7 @@ #include -#include +// #include QT_BEGIN_NAMESPACE @@ -89,7 +89,7 @@ public slots: QT_END_NAMESPACE -Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode) -Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error) +// Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode) +// Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error) #endif // QMEDIAPLAYLIST_H diff --git a/3rdparty/sources/qmediaplaylist.cpp b/3rdparty/sources/qmediaplaylist.cpp index 584b4c7..2012967 100644 --- a/3rdparty/sources/qmediaplaylist.cpp +++ b/3rdparty/sources/qmediaplaylist.cpp @@ -1,9 +1,9 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "../include/qmediaplaylist.h" -#include "../include/qmediaplaylist_p.h" -#include "qplaylistfileparser.h" +#include "../headers/qmediaplaylist.h" +#include "../headers/qmediaplaylist_p.h" +#include "../headers/qplaylistfileparser.h" #include #include diff --git a/3rdparty/sources/qmediaplaylist_p.cpp b/3rdparty/sources/qmediaplaylist_p.cpp index 47765f0..a0c1edd 100644 --- a/3rdparty/sources/qmediaplaylist_p.cpp +++ b/3rdparty/sources/qmediaplaylist_p.cpp @@ -1,7 +1,7 @@ // Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only -#include "../include/qmediaplaylist_p.h" +#include "../headers/qmediaplaylist_p.h" QT_BEGIN_NAMESPACE diff --git a/3rdparty/sources/qplaylistfileparser.cpp b/3rdparty/sources/qplaylistfileparser.cpp index 0a0be9d..6c3d6ea 100644 --- a/3rdparty/sources/qplaylistfileparser.cpp +++ b/3rdparty/sources/qplaylistfileparser.cpp @@ -3,7 +3,7 @@ #include "qmediametadata.h" #include "qmediaplayer.h" -#include "qplaylistfileparser.h" +#include "../headers/qplaylistfileparser.h" #include #include diff --git a/src/softProjector.pro b/src/softProjector.pro index dc6a26f..15a6a71 100644 --- a/src/softProjector.pro +++ b/src/softProjector.pro @@ -46,6 +46,9 @@ RCC_DIR = $${RES_DIR}/rcc OUT_PWD = $${RES_DIR}/bin SOURCES += sources/main.cpp \ + ../3rdparty/sources/qmediaplaylist.cpp \ + ../3rdparty/sources/qmediaplaylist_p.cpp \ + ../3rdparty/sources/qplaylistfileparser.cpp \ sources/softprojector.cpp \ sources/songwidget.cpp \ sources/biblewidget.cpp \ @@ -90,6 +93,9 @@ SOURCES += sources/main.cpp \ sources/spimageprovider.cpp \ sources/mediacontrol.cpp HEADERS += headers/softprojector.hpp \ + ../3rdparty/headers/qmediaplaylist.h \ + ../3rdparty/headers/qmediaplaylist_p.h \ + ../3rdparty/headers/qplaylistfileparser.h \ headers/songwidget.hpp \ headers/biblewidget.hpp \ headers/editwidget.hpp \ From 68998262afab1094e9dc03d8a119ab3e5b6733e4 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 18 Mar 2026 20:39:43 -0700 Subject: [PATCH 20/23] Fixed use_translation field sql --- main.cpp | 2 +- managedatadialog.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 67df309..7ca6bcc 100644 --- a/main.cpp +++ b/main.cpp @@ -76,7 +76,7 @@ bool connect(QString database_file) sq.exec("CREATE TABLE 'Songbooks' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , 'name' TEXT, 'info' TEXT)"); sq.exec("CREATE TABLE 'Songs' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL , " "'songbook_id' INTEGER, 'number' INTEGER, 'title' TEXT, 'category' INTEGER DEFAULT 0, " - "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, 'use_translation BOOL'" + "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, 'use_translation' BOOL, " "'use_private' BOOL, 'alignment_v' INTEGER, 'alignment_h' INTEGER, 'color' INTEGER, 'font' TEXT, " "'info_color' INTEGER, 'info_font' TEXT, 'ending_color' INTEGER, 'ending_font' TEXT, " "'use_background' BOOL, 'background_name' TEXT, 'background' BLOB, 'count' INTEGER DEFAULT 0, 'date' TEXT)"); diff --git a/managedatadialog.cpp b/managedatadialog.cpp index 04c9e2a..93faadd 100644 --- a/managedatadialog.cpp +++ b/managedatadialog.cpp @@ -727,7 +727,7 @@ void ManageDataDialog::exportSongbook(QString path) q.exec("PRAGMA user_version = 2"); q.exec("CREATE TABLE 'SongBook' ('title' TEXT, 'info' TEXT)"); q.exec("CREATE TABLE 'Songs' ('number' INTEGER, 'title' TEXT, 'category' INTEGER DEFAULT 0, " - "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, use_translation BOOL" + "'tune' TEXT, 'words' TEXT, 'music' TEXT, 'song_text' TEXT, 'notes' TEXT, 'use_translation' BOOL, " "'use_private' BOOL, 'alignment_v' INTEGER, 'alignment_h' INTEGER, 'color' INTEGER, 'font' TEXT, " "'info_color' INTEGER, 'info_font' TEXT, 'ending_color' INTEGER, 'ending_font' TEXT, " "'use_background' BOOL, 'background_name' TEXT, 'background' BLOB, 'count' INTEGER DEFAULT 0, 'date' TEXT)"); @@ -746,7 +746,7 @@ void ManageDataDialog::exportSongbook(QString path) // Write Songs sq.exec("SELECT * FROM Songs WHERE songbook_id = " + songbook_id); - q.prepare("INSERT INTO Songs (number,title,category,tune,words,music,song_text,notes,use_translation" + q.prepare("INSERT INTO Songs (number,title,category,tune,words,music,song_text,notes,use_translation," "use_private,alignment_v,alignment_h,color,font,info_color,info_font,ending_color,ending_font," "use_background,background_name,background,count,date) " "VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"); From 65cafb954766234bac5978ed5f2ab9814b303729 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:49:44 -0700 Subject: [PATCH 21/23] Fixed use_translation field sql --- managedatadialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/managedatadialog.cpp b/managedatadialog.cpp index 93faadd..c359fb7 100644 --- a/managedatadialog.cpp +++ b/managedatadialog.cpp @@ -577,7 +577,7 @@ void ManageDataDialog::importSongbook(QString path) sq.prepare("INSERT INTO Songs (songbook_id,number,title,category,tune,words,music,song_text,notes,use_translation," "use_private,alignment_v,alignment_h,color,font,info_color,info_font,ending_color," "ending_font,use_background,background_name,background,count,date) " - "VALUES(:id, :num, :ti, :ca, :tu, :wo, :mu, :st, :no, :up, :av, :ah, :tc, :tf, " + "VALUES(:id, :num, :ti, :ca, :tu, :wo, :mu, :st, :no, :ut, :up, :av, :ah, :tc, :tf, " ":ic, :if, :ec, :ef, :ub, :bn, :b, :ct, :d)"); while(q.next()) { From 7d65cc64ce9a6e49f737b3e96815cabe3d1468c5 Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Fri, 3 Apr 2026 19:42:54 -0700 Subject: [PATCH 22/23] httpserver qt6 merge --- softProjector.pro | 179 ----- softProjector.pro.user.9ab80e4 | 747 ------------------ httpserver.hpp => src/headers/httpserver.hpp | 0 .../headers/websocketserver.hpp | 0 httpserver.html => src/html/httpserver.html | 0 src/qml/DisplayArea.qml | 1 + src/softProjector.pro | 11 +- src/sources/highlight.cpp | 2 +- httpserver.cpp => src/sources/httpserver.cpp | 5 +- src/sources/song.cpp | 4 +- .../sources/websocketserver.cpp | 2 +- 11 files changed, 15 insertions(+), 936 deletions(-) delete mode 100644 softProjector.pro delete mode 100644 softProjector.pro.user.9ab80e4 rename httpserver.hpp => src/headers/httpserver.hpp (100%) rename websocketserver.hpp => src/headers/websocketserver.hpp (100%) rename httpserver.html => src/html/httpserver.html (100%) rename httpserver.cpp => src/sources/httpserver.cpp (97%) rename websocketserver.cpp => src/sources/websocketserver.cpp (98%) diff --git a/softProjector.pro b/softProjector.pro deleted file mode 100644 index 4ccf696..0000000 --- a/softProjector.pro +++ /dev/null @@ -1,179 +0,0 @@ -##************************************************************************** -## -## softProjector - an open source media projection software -## Copyright (C) 2017 Vladislav Kobzar -## -## This program is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation version 3 of the License. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with this program. If not, see . -## -##************************************************************************** - - -QT += core \ - gui \ - widgets \ - network \ - sql \ - qml \ - quick \ - printsupport \ - multimedia \ - multimediawidgets \ - websockets - -TARGET = SoftProjector -TEMPLATE = app -CONFIG += x86 ppc x86_64 ppc64 # Compile a universal build - -RES_DIR = $${PWD}/unknownsys_build -win32: RES_DIR = $${PWD}/win32_build -unix: RES_DIR = $${PWD}/unix_build -macx: RES_DIR = $${PWD}/mac_build - -DESTDIR = $${RES_DIR}/bin -OBJECTS_DIR = $${RES_DIR}/obj -MOC_DIR = $${RES_DIR}/moc -UI_DIR = $${RES_DIR}/ui -RCC_DIR = $${RES_DIR}/rcc -OUT_PWD = $${RES_DIR}/bin - -SOURCES += main.cpp \ - softprojector.cpp \ - songwidget.cpp \ - biblewidget.cpp \ - editwidget.cpp \ - song.cpp \ - bible.cpp \ - settingsdialog.cpp \ - aboutdialog.cpp \ - addsongbookdialog.cpp \ - highlight.cpp \ - managedatadialog.cpp \ - managedata.cpp \ - announcewidget.cpp \ - helpdialog.cpp \ - songcounter.cpp \ - bibleinformationdialog.cpp \ - settings.cpp \ - generalsettingwidget.cpp \ - biblesettingwidget.cpp \ - songsettingwidget.cpp \ - announcementsettingwidget.cpp \ - printpreviewdialog.cpp \ - controlbutton.cpp \ - passivesettingwidget.cpp \ - theme.cpp \ - picturewidget.cpp \ - slideshow.cpp \ - mediawidget.cpp \ - videoplayerwidget.cpp \ - videoinfo.cpp \ - spfunctions.cpp \ - slideshoweditor.cpp \ - editannouncementdialog.cpp \ - announcement.cpp \ - schedule.cpp \ - picturesettingwidget.cpp \ - moduledownloaddialog.cpp \ - moduleprogressdialog.cpp \ - displaysetting.cpp \ - projectordisplayscreen.cpp \ - imagegenerator.cpp \ - spimageprovider.cpp \ - mediacontrol.cpp \ - httpserver.cpp \ - websocketserver.cpp -HEADERS += softprojector.hpp \ - songwidget.hpp \ - biblewidget.hpp \ - editwidget.hpp \ - song.hpp \ - bible.hpp \ - settingsdialog.hpp \ - aboutdialog.hpp \ - addsongbookdialog.hpp \ - highlight.hpp \ - managedatadialog.hpp \ - managedata.hpp \ - announcewidget.hpp \ - helpdialog.hpp \ - songcounter.hpp \ - bibleinformationdialog.hpp \ - settings.hpp \ - generalsettingwidget.hpp \ - biblesettingwidget.hpp \ - songsettingwidget.hpp \ - announcementsettingwidget.hpp \ - printpreviewdialog.hpp \ - controlbutton.hpp \ - passivesettingwidget.hpp \ - theme.hpp \ - picturewidget.hpp \ - slideshow.hpp \ - mediawidget.hpp \ - videoplayerwidget.hpp \ - videoinfo.hpp \ - spfunctions.hpp \ - slideshoweditor.hpp \ - editannouncementdialog.hpp \ - announcement.hpp \ - schedule.hpp \ - picturesettingwidget.hpp \ - moduledownloaddialog.hpp \ - moduleprogressdialog.hpp \ - displaysetting.hpp \ - projectordisplayscreen.hpp \ - imagegenerator.hpp \ - spimageprovider.hpp \ - mediacontrol.hpp \ - httpserver.hpp \ - websocketserver.hpp -FORMS += softprojector.ui \ - songwidget.ui \ - biblewidget.ui \ - editwidget.ui \ - settingsdialog.ui \ - aboutdialog.ui \ - addsongbookdialog.ui \ - managedatadialog.ui \ - announcewidget.ui \ - helpdialog.ui \ - songcounter.ui \ - bibleinformationdialog.ui \ - generalsettingwidget.ui \ - biblesettingwidget.ui \ - songsettingwidget.ui \ - announcementsettingwidget.ui \ - printpreviewdialog.ui \ - passivesettingwidget.ui \ - picturewidget.ui \ - mediawidget.ui \ - slideshoweditor.ui \ - editannouncementdialog.ui \ - picturesettingwidget.ui \ - moduledownloaddialog.ui \ - moduleprogressdialog.ui \ - projectordisplayscreen.ui \ - mediacontrol.ui -TRANSLATIONS += translations/softpro_de.ts\ - translations/softpro_ru.ts\ - translations/softpro_cs.ts\ - translations/softpro_ua.ts\ - translations/softpro_hy.ts -CODECFORTR = UTF-8 -RESOURCES += softprojector.qrc - -win32 { - RC_FILE = softprojector.rc -} - -DISTFILES += diff --git a/softProjector.pro.user.9ab80e4 b/softProjector.pro.user.9ab80e4 deleted file mode 100644 index 599fd8c..0000000 --- a/softProjector.pro.user.9ab80e4 +++ /dev/null @@ -1,747 +0,0 @@ - - - - - - EnvironmentId - {9ab80e4a-14b4-4125-8702-64ff78555811} - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - false - true - false - 0 - true - true - 0 - 8 - true - false - 1 - true - true - true - *.md, *.MD, Makefile - false - true - - - - ProjectExplorer.Project.PluginSettings - - - true - false - true - true - true - true - - - 0 - true - - -fno-delayed-template-parsing - - true - Builtin.BuildSystem - - true - true - Builtin.DefaultTidyAndClazy - 2 - - - - true - - - - - ProjectExplorer.Project.Target.0 - - Desktop - Desktop Qt 5.12.12 MinGW 64-bit - Desktop Qt 5.12.12 MinGW 64-bit - qt.qt5.51212.win64_mingw73_kit - 0 - 0 - 0 - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Debug - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Debug - - - true - QtProjectManager.QMakeBuildStep - false - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - - - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Release - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Release - - - true - QtProjectManager.QMakeBuildStep - false - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 1 - - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Profile - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_64_bit-Profile - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - 0 - - 3 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - true - true - true - - 2 - - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - false - true - true - false - true - C:/Users/presentation/OneDrive/Documents/Software/softprojector/win32_build/bin - - 1 - - - - ProjectExplorer.Project.Target.1 - - Desktop - Desktop Qt 5.12.12 MSVC2017 64bit - Desktop Qt 5.12.12 MSVC2017 64bit - qt.qt5.51212.win64_msvc2017_64_kit - 1 - 0 - 0 - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Debug - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Debug - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - - - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Release - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Release - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Profile - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2017_64bit-Profile - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - 0 - - 3 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - true - true - true - - 2 - - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - false - true - true - false - true - - 1 - - - - ProjectExplorer.Project.Target.2 - - Desktop - Desktop Qt 5.12.12 MSVC2015 64bit - Desktop Qt 5.12.12 MSVC2015 64bit - qt.qt5.51212.win64_msvc2015_64_kit - 1 - 0 - 0 - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Debug - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Debug - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - - - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Release - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Release - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Profile - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MSVC2015_64bit-Profile - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - 0 - - 3 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - true - true - true - - 2 - - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - false - true - true - false - true - - 1 - - - - ProjectExplorer.Project.Target.3 - - Desktop - Desktop Qt 5.12.12 MinGW 32-bit - Desktop Qt 5.12.12 MinGW 32-bit - qt.qt5.51212.win32_mingw73_kit - 0 - 0 - 0 - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Debug - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Debug - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Debug - Qt4ProjectManager.Qt4BuildConfiguration - 2 - - - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Release - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Release - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Release - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - - - 0 - C:\Users\presentation\OneDrive\Documents\Software\build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Profile - C:/Users/presentation/OneDrive/Documents/Software/build-softProjector-Desktop_Qt_5_12_12_MinGW_32_bit-Profile - - - true - QtProjectManager.QMakeBuildStep - true - - - - true - Qt4ProjectManager.MakeStep - - 2 - Build - Build - ProjectExplorer.BuildSteps.Build - - - - true - Qt4ProjectManager.MakeStep - clean - - 1 - Clean - Clean - ProjectExplorer.BuildSteps.Clean - - 2 - false - - - Profile - Qt4ProjectManager.Qt4BuildConfiguration - 0 - 0 - 0 - - 3 - - - 0 - Deploy - Deploy - ProjectExplorer.BuildSteps.Deploy - - 1 - - false - ProjectExplorer.DefaultDeployConfiguration - - 1 - - true - true - true - - 2 - - Qt4ProjectManager.Qt4RunConfiguration:C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - C:/Users/presentation/OneDrive/Documents/Software/softprojector/softProjector.pro - false - true - true - false - true - - 1 - - - - ProjectExplorer.Project.TargetCount - 4 - - - ProjectExplorer.Project.Updater.FileVersion - 22 - - - Version - 22 - - diff --git a/httpserver.hpp b/src/headers/httpserver.hpp similarity index 100% rename from httpserver.hpp rename to src/headers/httpserver.hpp diff --git a/websocketserver.hpp b/src/headers/websocketserver.hpp similarity index 100% rename from websocketserver.hpp rename to src/headers/websocketserver.hpp diff --git a/httpserver.html b/src/html/httpserver.html similarity index 100% rename from httpserver.html rename to src/html/httpserver.html diff --git a/src/qml/DisplayArea.qml b/src/qml/DisplayArea.qml index 5529392..bc327a1 100644 --- a/src/qml/DisplayArea.qml +++ b/src/qml/DisplayArea.qml @@ -19,6 +19,7 @@ import QtQuick import QtMultimedia +import QtWebSockets Rectangle { id: dispArea diff --git a/src/softProjector.pro b/src/softProjector.pro index 15a6a71..163f5ac 100644 --- a/src/softProjector.pro +++ b/src/softProjector.pro @@ -27,7 +27,8 @@ QT += core \ quick \ printsupport \ multimedia \ - multimediawidgets + multimediawidgets \ + websockets TARGET = SoftProjector TEMPLATE = app @@ -91,7 +92,9 @@ SOURCES += sources/main.cpp \ sources/projectordisplayscreen.cpp \ sources/imagegenerator.cpp \ sources/spimageprovider.cpp \ - sources/mediacontrol.cpp + sources/mediacontrol.cpp \ + sources/httpserver.cpp \ + sources/websocketserver.cpp HEADERS += headers/softprojector.hpp \ ../3rdparty/headers/qmediaplaylist.h \ ../3rdparty/headers/qmediaplaylist_p.h \ @@ -137,7 +140,9 @@ HEADERS += headers/softprojector.hpp \ headers/projectordisplayscreen.hpp \ headers/imagegenerator.hpp \ headers/spimageprovider.hpp \ - headers/mediacontrol.hpp + headers/mediacontrol.hpp \ + headers/httpserver.hpp \ + headers/websocketserver.hpp FORMS += ui/softprojector.ui \ ui/songwidget.ui \ ui/biblewidget.ui \ diff --git a/src/sources/highlight.cpp b/src/sources/highlight.cpp index 119c87f..fc85ea8 100644 --- a/src/sources/highlight.cpp +++ b/src/sources/highlight.cpp @@ -82,7 +82,7 @@ Highlight::Highlight(QTextDocument *parent) // Translation formating translationFormat.setForeground(Qt::black); translationFormat.setBackground(Qt::green); - rule.pattern = QRegExp("^=[^\n]*"); + rule.pattern = QRegularExpression("^=[^\n]*"); rule.format = translationFormat; highlightingRules.append(rule); } diff --git a/httpserver.cpp b/src/sources/httpserver.cpp similarity index 97% rename from httpserver.cpp rename to src/sources/httpserver.cpp index aa6d19b..079cab2 100644 --- a/httpserver.cpp +++ b/src/sources/httpserver.cpp @@ -17,7 +17,7 @@ // ***************************************************************************/ -#include "httpserver.hpp" +#include "../headers/httpserver.hpp" #include #include @@ -60,7 +60,7 @@ void HttpServer::sendData() { QTcpSocket* socket = (QTcpSocket*)sender(); - QFile file(":/httpserver.html"); + QFile file(":/html/html/httpserver.html"); if(!file.open(QFile::ReadOnly | QFile::Text)) { @@ -88,6 +88,5 @@ void HttpServer::sendData() void HttpServer::stopServer() { server->close(); - delete server; isRunning = false; } diff --git a/src/sources/song.cpp b/src/sources/song.cpp index 1f82a9a..4ad55ca 100644 --- a/src/sources/song.cpp +++ b/src/sources/song.cpp @@ -169,8 +169,8 @@ QString getStanzaBlock(int &i, QStringList &list, bool includeTranslation) while(i < list.count()) { line = list.at(i); - if(includeTranslation == false && line.contains(QRegExp("^="))) { break; } // Doesn't include translations in the ui lists. - if(line.contains(QRegExp("^&"))) + if(includeTranslation == false && line.contains(QRegularExpression("^="))) { break; } // Doesn't include translations in the ui lists. + if(line.contains(QRegularExpression("^&"))) line.remove("&"); if(isStanzaTitle(line) && (i!=j)) diff --git a/websocketserver.cpp b/src/sources/websocketserver.cpp similarity index 98% rename from websocketserver.cpp rename to src/sources/websocketserver.cpp index d50f365..64813fd 100644 --- a/websocketserver.cpp +++ b/src/sources/websocketserver.cpp @@ -20,7 +20,7 @@ #include "QtWebSockets/qwebsocketserver.h" #include "QtWebSockets/qwebsocket.h" -#include "websocketserver.hpp" +#include "../headers/websocketserver.hpp" #include From f24d0c86b1030ffd8a23cd243d6b4df36ba0e33a Mon Sep 17 00:00:00 2001 From: ge022 <23706821+ge022@users.noreply.github.com> Date: Wed, 8 Apr 2026 19:58:35 -0700 Subject: [PATCH 23/23] Songs search fixes - Fixed cyrillic (russian) characters search on enter. - Added search debouncing for laggy search when typing --- src/headers/songwidget.hpp | 3 +++ src/sources/song.cpp | 7 ++++++- src/sources/songwidget.cpp | 20 +++++++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/headers/songwidget.hpp b/src/headers/songwidget.hpp index e2cbe4e..a988ee8 100644 --- a/src/headers/songwidget.hpp +++ b/src/headers/songwidget.hpp @@ -102,6 +102,9 @@ private slots: QList cat_ids; QList allSongs; HighlighterDelegate *highlight; + QTimer *searchTimer; + QString searchText; + void processSearch(); }; #endif // SONGWIDGET_HPP diff --git a/src/sources/song.cpp b/src/sources/song.cpp index 4ad55ca..01fb4bc 100644 --- a/src/sources/song.cpp +++ b/src/sources/song.cpp @@ -28,7 +28,11 @@ QString clean(QString str) { //Removes all none alphanumeric characters from the string - str.replace(QRegularExpression("[\\W*]")," "); + + // Matches anything that is NOT a Letter (L) or a Number (N). Supports Cyrillic. + QRegularExpression re("[^\\p{L}\\p{N}]+"); + + str.replace(re, " "); str = str.simplified(); return str; } @@ -578,6 +582,7 @@ void SongProxyModel::setFilterString(QString new_string, bool new_match_beginnin filter_string = new_string; match_beginning = new_match_beginning; exact_match = new_exact_match; + this->endFilterChange(); } void SongProxyModel::setSongbookFilter(QString new_songbook) diff --git a/src/sources/songwidget.cpp b/src/sources/songwidget.cpp index 8dfc016..2589d2a 100644 --- a/src/sources/songwidget.cpp +++ b/src/sources/songwidget.cpp @@ -56,6 +56,11 @@ SongWidget::SongWidget(QWidget *parent) : // set highligher highlight = new HighlighterDelegate(ui->listPreview); ui->listWidgetDummy->setVisible(false); + + searchTimer = new QTimer(this); + searchTimer->setSingleShot(true); + connect(searchTimer, &QTimer::timeout, this, &SongWidget::processSearch); + } SongWidget::~SongWidget() @@ -306,6 +311,13 @@ void SongWidget::on_btnLive_clicked() } void SongWidget::on_lineEditSearch_textEdited(QString text) +{ + searchText = text; + // The search only actually runs when the user stops typing for 0.2 seconds. + searchTimer->start(200); +} + +void SongWidget::processSearch() { // Check if full-text search is in progress // If no full-text search is in progress, then filter @@ -314,7 +326,7 @@ void SongWidget::on_lineEditSearch_textEdited(QString text) // If search text is numeric, sort by the number, else sort by title bool ok; - text.toInt(&ok); + searchText.toInt(&ok); if(ok) { proxy_model->sort(1); @@ -328,9 +340,7 @@ void SongWidget::on_lineEditSearch_textEdited(QString text) bool match_beginning = (ui->comboBoxFilterType->currentIndex() == 1); bool exact_match = (ui->comboBoxFilterType->currentIndex() == 2); - songs_model->emitLayoutAboutToBeChanged(); // prepares view to be redrawn - proxy_model->setFilterString(text, match_beginning, exact_match); - songs_model->emitLayoutChanged(); // forces the view to redraw + proxy_model->setFilterString(searchText, match_beginning, exact_match); // Select the first row that matches the new filter: ui->songs_view->selectRow(0); @@ -559,7 +569,7 @@ void SongWidget::on_pushButtonSearch_clicked() int type = ui->comboBoxSearchType->currentIndex(); // Make sure that there is some text to do a search for, if none, then return - if(search_text.count()<1) + if(search_text.length()<1) { ui->lineEditSearch->clear(); ui->lineEditSearch->setPlaceholderText(tr("Please enter search text"));