diff --git a/.gitignore b/.gitignore
index 5473a27..9369574 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,13 @@
+.cache
.clangd
.conanbuild
.current_build
+__pycache__
CMakeLists.txt.user
Session.vim
build
compile_commands.json
+cmake-build-debug
dist
graph_info.json
tags
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 73f69e0..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 8067168..7f5d14a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,17 +1,7 @@
-
-
+
+
+
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7..35eb1dd 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/.liccor.yml b/.liccor.yml
index cca497c..9efd06c 100644
--- a/.liccor.yml
+++ b/.liccor.yml
@@ -2,7 +2,7 @@
source:
- .
copyright_notice: |-
- Copyright 2021 gary@drinkingtea.net
+ Copyright 2021 - 2023 gary@drinkingtea.net
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
diff --git a/Makefile b/Makefile
index 6d7cf18..b707ab4 100644
--- a/Makefile
+++ b/Makefile
@@ -14,4 +14,4 @@ run: install
${ENV_RUN} ${PROJECT_EXECUTABLE}
.PHONY: debug
debug: install
- ${ENV_RUN} gdb --args ${PROJECT_EXECUTABLE}
+ ${DEBUGGER} lldb -- ${PROJECT_EXECUTABLE}
diff --git a/obs_scene_switcher.py b/obs_scene_switcher.py
index 8830506..058a8f2 100644
--- a/obs_scene_switcher.py
+++ b/obs_scene_switcher.py
@@ -33,7 +33,7 @@ class RqstHandler(BaseHTTPRequestHandler):
def run(name):
- httpd = HTTPServer(('127.0.0.1', 9302), RqstHandler)
+ httpd = HTTPServer(('0.0.0.0', 9302), RqstHandler)
httpd.serve_forever()
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c729b7c..b85b551 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -8,10 +8,13 @@ find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED)
add_executable(
SlideController MACOSX_BUNDLE WIN32
+ cameraclient.cpp
main.cpp
mainwindow.cpp
obsclient.cpp
openlpclient.cpp
+ settingsdata.cpp
+ settingsdialog.cpp
slideview.cpp
)
diff --git a/src/cameraclient.cpp b/src/cameraclient.cpp
new file mode 100644
index 0000000..f820060
--- /dev/null
+++ b/src/cameraclient.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 - 2023 gary@drinkingtea.net
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include
+#include
+
+#include "settingsdata.hpp"
+#include "cameraclient.hpp"
+
+CameraClient::CameraClient(QObject *parent): QObject(parent) {
+ setBaseUrl();
+ m_pollTimer.start(1000);
+ connect(&m_pollTimer, &QTimer::timeout, this, &CameraClient::poll);
+ connect(m_pollingNam, &QNetworkAccessManager::finished, this, &CameraClient::handlePollResponse);
+}
+
+void CameraClient::setPreset(int preset) {
+ if (preset > -1) {
+ get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset));
+ }
+}
+
+void CameraClient::setBaseUrl() {
+ const auto [host, port] = getCameraConnectionData();
+ m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
+}
+
+void CameraClient::get(QString const&urlExt) {
+ QUrl url(QString(m_baseUrl) + urlExt);
+ QNetworkRequest rqst(url);
+ auto reply = m_nam->get(rqst);
+ connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
+}
+
+void CameraClient::poll() {
+ QUrl url(QString(m_baseUrl) + "/cgi-bin/param.cgi?get_device_conf");
+ QNetworkRequest rqst(url);
+ m_pollingNam->get(rqst);
+}
+
+void CameraClient::handlePollResponse(QNetworkReply *reply) {
+ reply->deleteLater();
+ if (reply->error()) {
+ qDebug() << "CameraClient error response:" << reply->errorString();
+ emit pollFailed();
+ return;
+ }
+ emit pollUpdate();
+}
diff --git a/src/cameraclient.hpp b/src/cameraclient.hpp
new file mode 100644
index 0000000..9f90d09
--- /dev/null
+++ b/src/cameraclient.hpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2021 - 2023 gary@drinkingtea.net
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "consts.hpp"
+
+class CameraClient: public QObject {
+ Q_OBJECT
+ private:
+ QString m_baseUrl;
+ QNetworkAccessManager *const m_nam = new QNetworkAccessManager(this);
+ QNetworkAccessManager *const m_pollingNam = new QNetworkAccessManager(this);
+ QTimer m_pollTimer;
+
+ public:
+ explicit CameraClient(QObject *parent = nullptr);
+
+ void setPreset(int preset);
+
+ public slots:
+ void setBaseUrl();
+
+ private:
+ void get(QString const&url);
+
+ void poll();
+
+ void handlePollResponse(QNetworkReply *reply);
+
+ signals:
+ void pollUpdate();
+
+ void pollFailed();
+
+};
+
diff --git a/src/consts.hpp b/src/consts.hpp
index 1a52fb6..5f360eb 100644
--- a/src/consts.hpp
+++ b/src/consts.hpp
@@ -8,4 +8,3 @@
#pragma once
-constexpr auto SlideHost = "192.168.254.61";
diff --git a/src/main.cpp b/src/main.cpp
index 7d49280..59e7250 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -7,10 +7,12 @@
*/
#include
+#include
#include "mainwindow.hpp"
int main(int argc, char *argv[]) {
+ QSettings::setDefaultFormat(QSettings::Format::IniFormat);
QApplication a(argc, argv);
QApplication::setApplicationName(QObject::tr("Slide Controller 9000"));
MainWindow w;
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 6ab2236..4f4c4c8 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -6,17 +6,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
+#include
#include
+#include
#include
#include
+#include "settingsdialog.hpp"
#include "slideview.hpp"
#include "mainwindow.hpp"
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
move(0, 0);
- setFixedSize(590, 555);
+ setFixedSize(610, 555);
setWindowTitle(tr("Slide Controller 9000"));
+ setupMenu();
const auto mainWidget = new QWidget(this);
const auto rootLyt = new QVBoxLayout;
const auto controlsLayout = new QGridLayout;
@@ -26,53 +30,159 @@ MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
rootLyt->addWidget(m_slideView);
rootLyt->addLayout(controlsLayout);
// setup slide controls
- const auto showHideLyt = new QHBoxLayout;
- rootLyt->addLayout(showHideLyt);
const auto btnPrevSong = new QPushButton(tr("Previous Song (Left)"), this);
const auto btnPrevSlide = new QPushButton(tr("Previous Slide (Up)"), this);
const auto btnNextSlide = new QPushButton(tr("Next Slide (Down)"), this);
const auto btnNextSong = new QPushButton(tr("Next Song (Right)"), this);
- const auto btnHideSlides = new QPushButton(tr("Hide (1)"), this);
- const auto btnOpenLpShowSlides = new QPushButton(tr("Show in OpenLP Only (2)"), this);
- const auto btnShowSlides = new QPushButton(tr("Show (3)"), mainWidget);
controlsLayout->addWidget(btnPrevSlide, 0, 1);
controlsLayout->addWidget(btnNextSlide, 0, 2);
controlsLayout->addWidget(btnPrevSong, 0, 0);
controlsLayout->addWidget(btnNextSong, 0, 3);
- showHideLyt->addWidget(btnHideSlides);
- showHideLyt->addWidget(btnOpenLpShowSlides);
- showHideLyt->addWidget(btnShowSlides);
+ controlsLayout->setSpacing(2);
btnNextSong->setShortcut(Qt::Key_Right);
btnPrevSong->setShortcut(Qt::Key_Left);
btnNextSlide->setShortcut(Qt::Key_Down);
btnPrevSlide->setShortcut(Qt::Key_Up);
- btnHideSlides->setShortcut(Qt::Key_1);
- btnOpenLpShowSlides->setShortcut(Qt::Key_2);
- btnHideSlides->setToolTip(tr("Also hides slides in OBS"));
- btnShowSlides->setShortcut(Qt::Key_3);
+ btnNextSong->setFixedWidth(135);
+ btnPrevSong->setFixedWidth(135);
+ btnNextSlide->setFixedWidth(135);
+ btnPrevSlide->setFixedWidth(135);
+ setupViewControls(rootLyt);
connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide);
connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide);
connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong);
connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong);
- connect(btnHideSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen);
- connect(btnHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
- connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
- connect(btnShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
- connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, m_slideView, &SlideView::pollUpdate);
connect(&m_openlpClient, &OpenLPClient::songListUpdate, m_slideView, &SlideView::songListUpdate);
connect(&m_openlpClient, &OpenLPClient::slideListUpdate, m_slideView, &SlideView::slideListUpdate);
connect(&m_openlpClient, &OpenLPClient::pollFailed, m_slideView, &SlideView::reset);
connect(m_slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong);
connect(m_slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide);
- // setup scene selector
- connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
// setup status bar
setStatusBar(new QStatusBar(this));
+ connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
connect(&m_openlpClient, &OpenLPClient::songChanged, this, &MainWindow::refreshStatusBar);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
refreshStatusBar();
+ connect(statusBar(), &QStatusBar::messageChanged, this, [this](QStringView const&msg) {
+ if (msg.empty()) {
+ refreshStatusBar();
+ }
+ });
+}
+
+void MainWindow::setupMenu() {
+ // file menu
+ auto const fileMenu = menuBar()->addMenu(tr("&File"));
+ auto const settingsAct = new QAction(tr("&Settings"), this);
+ auto const quitAct = new QAction(tr("E&xit"), this);
+ settingsAct->setShortcuts(QKeySequence::Preferences);
+ connect(settingsAct, &QAction::triggered, this, &MainWindow::openSettings);
+ quitAct->setShortcuts(QKeySequence::Quit);
+ quitAct->setStatusTip(tr("Exit application"));
+ connect(quitAct, &QAction::triggered, &QApplication::quit);
+ fileMenu->addAction(settingsAct);
+ fileMenu->addAction(quitAct);
+}
+
+void MainWindow::setupDefaultViewControls(QHBoxLayout *viewCtlLyt) {
+ auto const mainWidget = viewCtlLyt->parentWidget();
+ auto const btnHideSlides = new QPushButton(tr("1. Hide"), mainWidget);
+ auto const btnOpenLpShowSlides = new QPushButton(tr("2. Show in OpenLP Only"), mainWidget);
+ auto const btnShowSlides = new QPushButton(tr("3. Show"), mainWidget);
+ viewCtlLyt->addWidget(btnHideSlides);
+ viewCtlLyt->addWidget(btnOpenLpShowSlides);
+ viewCtlLyt->addWidget(btnShowSlides);
+ btnHideSlides->setShortcut(Qt::Key_1);
+ btnOpenLpShowSlides->setShortcut(Qt::Key_2);
+ btnHideSlides->setToolTip(tr("Also hides slides in OBS"));
+ btnShowSlides->setShortcut(Qt::Key_3);
+ connect(btnHideSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen);
+ connect(btnHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
+ connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
+ connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
+ connect(btnShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
+ connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
+}
+
+void MainWindow::setupCustomViewControls(QVector const&views, QGridLayout *viewCtlLyt) {
+ constexpr auto columns = 3;
+ auto const parent = viewCtlLyt->parentWidget();
+ for (auto i = 0; auto const&view : views) {
+ auto const x = i % columns;
+ auto const y = i / columns;
+ auto const name = QString("%1. %2").arg(i + 1).arg(view.name);
+ auto const btn = new QPushButton(name, parent);
+ btn->setShortcut(Qt::Key_1 + i);
+ viewCtlLyt->addWidget(btn, y, x);
+ auto const slides = view.slides;
+ auto const obsSlides = view.obsSlides;
+ auto const cameraPreset = view.cameraPreset;
+ connect(btn, &QPushButton::clicked, this, [this, slides, obsSlides, cameraPreset] {
+ m_cameraClient.setPreset(cameraPreset);
+ m_openlpClient.setSlidesVisible(slides);
+ m_obsClient.setSlidesVisible(obsSlides);
+ });
+ ++i;
+ }
+}
+
+void MainWindow::setupViewControls(QVBoxLayout *rootLyt) {
+ auto views = getViews();
+ if (!m_viewControlsParent) {
+ m_viewControlsParent = new QWidget(rootLyt->parentWidget());
+ m_viewControlsParentLyt = new QHBoxLayout(m_viewControlsParent);
+ m_viewControlsParentLyt->setContentsMargins(0, 0, 0, 0);
+ rootLyt->addWidget(m_viewControlsParent);
+ }
+ delete m_viewControls;
+ m_viewControls = new QWidget(m_viewControlsParent);
+ m_viewControlsParentLyt->addWidget(m_viewControls);
+ auto const viewCtlLyt = new QGridLayout(m_viewControls);
+ viewCtlLyt->setSpacing(5);
+ if (views.empty()) {
+ views.emplace_back(View{
+ .name = tr("Hide"),
+ .slides = false,
+ .obsSlides = false,
+ });
+ views.emplace_back(View{
+ .name = tr("Hide in OBS"),
+ .slides = false,
+ .obsSlides = false,
+ });
+ views.emplace_back(View{
+ .name = tr("Show"),
+ .slides = false,
+ .obsSlides = false,
+ });
+ }
+ setupCustomViewControls(views, viewCtlLyt);
+}
+
+void MainWindow::openSettings() {
+ SettingsDialog d(this);
+ auto const result = d.exec();
+ if (result == QDialog::Accepted) {
+ m_cameraClient.setBaseUrl();
+ m_obsClient.setBaseUrl();
+ m_openlpClient.setBaseUrl();
+ }
+}
+
+void MainWindow::cameraConnectionInit() {
+ disconnect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
+ connect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost);
+ m_cameraConnected = true;
+ refreshStatusBar();
+}
+
+void MainWindow::cameraConnectionLost() {
+ disconnect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost);
+ connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit);
+ m_cameraConnected = false;
+ refreshStatusBar();
}
void MainWindow::openLpConnectionInit() {
@@ -104,9 +214,10 @@ void MainWindow::obsConnectionLost() {
}
void MainWindow::refreshStatusBar() {
- const auto openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected");
- const auto obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected");
- const auto nextSong = m_openlpClient.getNextSong();
- const auto nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : "";
- statusBar()->showMessage(openLpStatus + " | " + obsStatus + nextSongTxt);
+ auto const cameraStatus = m_cameraConnected ? tr("Camera: Connected") : tr("Camera: Not Connected");
+ auto const openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected");
+ auto const obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected");
+ auto const nextSong = m_openlpClient.getNextSong();
+ auto const nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : "";
+ statusBar()->showMessage(cameraStatus + " | " + openLpStatus + " | " + obsStatus + nextSongTxt);
}
diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp
index e963ccc..48e5b80 100644
--- a/src/mainwindow.hpp
+++ b/src/mainwindow.hpp
@@ -12,24 +12,45 @@
#include
+#include "cameraclient.hpp"
#include "obsclient.hpp"
#include "openlpclient.hpp"
+#include "settingsdata.hpp"
class MainWindow: public QMainWindow {
Q_OBJECT
private:
+ CameraClient m_cameraClient;
OBSClient m_obsClient;
OpenLPClient m_openlpClient;
class SlideView *m_slideView = nullptr;
+ bool m_cameraConnected = false;
bool m_openLpConnected = false;
bool m_obsConnected = false;
+ class QHBoxLayout *m_viewControlsParentLyt = nullptr;
+ class QWidget *m_viewControlsParent = nullptr;
+ class QWidget *m_viewControls = nullptr;
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow() override = default;
- private slots:
+ private:
+ void setupMenu();
+
+ void setupDefaultViewControls(class QHBoxLayout *rootLyt);
+
+ void setupCustomViewControls(QVector const&views, class QGridLayout *rootLyt);
+
+ void setupViewControls(class QVBoxLayout *rootLyt);
+
+ void openSettings();
+
+ void cameraConnectionInit();
+
+ void cameraConnectionLost();
+
void openLpConnectionInit();
void openLpConnectionLost();
diff --git a/src/obsclient.cpp b/src/obsclient.cpp
index 28ff756..f54f76c 100644
--- a/src/obsclient.cpp
+++ b/src/obsclient.cpp
@@ -8,17 +8,20 @@
#include
#include
+#include
#include
+#include "settingsdata.hpp"
#include "obsclient.hpp"
OBSClient::OBSClient(QObject *parent): QObject(parent) {
+ setBaseUrl();
m_pollTimer.start(1000);
connect(&m_pollTimer, &QTimer::timeout, this, &OBSClient::poll);
connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OBSClient::handlePollResponse);
}
-void OBSClient::setScene(QString scene) {
+void OBSClient::setScene(QString const&scene) {
get(QString("/Scene?name=%1").arg(scene));
}
@@ -30,7 +33,7 @@ void OBSClient::hideSlides() {
setScene("SpeakerScene");
}
-void OBSClient::setSlidesVisible(int state) {
+void OBSClient::setSlidesVisible(bool state) {
if (state) {
setScene("MusicScene");
} else {
@@ -38,15 +41,20 @@ void OBSClient::setSlidesVisible(int state) {
}
}
-void OBSClient::get(QString urlExt) {
- QUrl url(QString(BaseUrl) + urlExt);
+void OBSClient::setBaseUrl() {
+ const auto [host, port] = getOBSConnectionData();
+ m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
+}
+
+void OBSClient::get(QString const&urlExt) {
+ QUrl url(QString(m_baseUrl) + urlExt);
QNetworkRequest rqst(url);
auto reply = m_nam->get(rqst);
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
}
void OBSClient::poll() {
- QUrl url(QString(BaseUrl) + "/ping");
+ QUrl url(QString(m_baseUrl) + "/ping");
QNetworkRequest rqst(url);
m_pollingNam->get(rqst);
}
diff --git a/src/obsclient.hpp b/src/obsclient.hpp
index 2e09860..d8c9329 100644
--- a/src/obsclient.hpp
+++ b/src/obsclient.hpp
@@ -17,7 +17,7 @@
class OBSClient: public QObject {
Q_OBJECT
private:
- const QString BaseUrl = QString("http://") + SlideHost + ":9302";
+ QString m_baseUrl;
QNetworkAccessManager *m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this);
QTimer m_pollTimer;
@@ -26,16 +26,18 @@ class OBSClient: public QObject {
explicit OBSClient(QObject *parent = nullptr);
public slots:
- void setScene(QString scene);
+ void setScene(QString const&scene);
void showSlides();
void hideSlides();
- void setSlidesVisible(int state);
+ void setSlidesVisible(bool state);
+
+ void setBaseUrl();
private:
- void get(QString url);
+ void get(QString const&url);
void poll();
diff --git a/src/openlpclient.cpp b/src/openlpclient.cpp
index e3f7da2..93600c6 100644
--- a/src/openlpclient.cpp
+++ b/src/openlpclient.cpp
@@ -11,10 +11,13 @@
#include
#include
#include
+#include
+#include "settingsdata.hpp"
#include "openlpclient.hpp"
OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) {
+ setBaseUrl();
poll();
m_pollTimer.start(250);
connect(&m_pollTimer, &QTimer::timeout, this, &OpenLPClient::poll);
@@ -57,6 +60,14 @@ void OpenLPClient::showSlides() {
get("/api/display/show");
}
+void OpenLPClient::setSlidesVisible(bool value) {
+ if (value) {
+ showSlides();
+ } else {
+ blankScreen();
+ }
+}
+
void OpenLPClient::changeSong(int it) {
auto n = QString::number(it);
auto url = "/api/service/set?data=%7B%22request%22%3A+%7B%22id%22%3A+" + n + "%7D%7D&_=1627181837297";
@@ -69,26 +80,31 @@ void OpenLPClient::changeSlide(int slide) {
get(url);
}
-void OpenLPClient::get(QString urlExt) {
- QUrl url(QString(BaseUrl) + urlExt);
+void OpenLPClient::setBaseUrl() {
+ const auto [host, port] = getOpenLPConnectionData();
+ m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
+}
+
+void OpenLPClient::get(QString const&urlExt) {
+ QUrl url(m_baseUrl + urlExt);
QNetworkRequest rqst(url);
m_nam->get(rqst);
}
void OpenLPClient::requestSongList() {
- QUrl url(QString(BaseUrl) + "/api/service/list?_=1626628079579");
+ QUrl url(m_baseUrl + "/api/service/list?_=1626628079579");
QNetworkRequest rqst(url);
m_songListNam->get(rqst);
}
void OpenLPClient::requestSlideList() {
- QUrl url(QString(BaseUrl) + "/api/controller/live/text?_=1626628079579");
+ QUrl url(m_baseUrl + "/api/controller/live/text?_=1626628079579");
QNetworkRequest rqst(url);
m_slideListNam->get(rqst);
}
void OpenLPClient::poll() {
- QUrl url(QString(BaseUrl) + "/api/poll?_=1626628079579");
+ QUrl url(m_baseUrl + "/api/poll?_=1626628079579");
QNetworkRequest rqst(url);
m_pollingNam->get(rqst);
}
diff --git a/src/openlpclient.hpp b/src/openlpclient.hpp
index d9192c5..e6b7c28 100644
--- a/src/openlpclient.hpp
+++ b/src/openlpclient.hpp
@@ -19,11 +19,7 @@ class OpenLPClient: public QObject {
Q_OBJECT
private:
- struct Song {
- QString name;
- QString id;
- };
- const QString BaseUrl = QString("http://") + SlideHost + ":4316";
+ QString m_baseUrl;
QNetworkAccessManager *m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_songListNam = new QNetworkAccessManager(this);
@@ -53,12 +49,16 @@ class OpenLPClient: public QObject {
void showSlides();
+ void setSlidesVisible(bool value);
+
void changeSong(int it);
void changeSlide(int slide);
+ void setBaseUrl();
+
private:
- void get(QString url);
+ void get(QString const&url);
void requestSongList();
diff --git a/src/settingsdata.cpp b/src/settingsdata.cpp
new file mode 100644
index 0000000..e7039c3
--- /dev/null
+++ b/src/settingsdata.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2021 - 2023 gary@drinkingtea.net
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include
+
+#include "settingsdata.hpp"
+
+void setCameraConnectionData(QSettings &settings, ConnectionData const&cd) {
+ settings.beginGroup("CameraClient");
+ settings.setValue("Host", cd.host);
+ settings.setValue("Port", cd.port);
+ settings.endGroup();
+}
+
+void setOpenLPConnectionData(QSettings &settings, ConnectionData const&cd) {
+ settings.beginGroup("OpenLPClient");
+ settings.setValue("Host", cd.host);
+ settings.setValue("Port", cd.port);
+ settings.endGroup();
+}
+
+void setOBSConnectionData(QSettings &settings, ConnectionData const&cd) {
+ settings.beginGroup("OBSClient");
+ settings.setValue("Host", cd.host);
+ settings.setValue("Port", cd.port);
+ settings.endGroup();
+}
+
+ConnectionData getCameraConnectionData(QSettings &settings) {
+ ConnectionData out;
+ settings.beginGroup("CameraClient");
+ out.host = settings.value("Host", "192.168.100.88").toString();
+ out.port = static_cast(settings.value("Port", 80).toInt());
+ settings.endGroup();
+ return out;
+}
+
+ConnectionData getOpenLPConnectionData(QSettings &settings) {
+ ConnectionData out;
+ settings.beginGroup("OpenLPClient");
+ out.host = settings.value("Host", "127.0.0.1").toString();
+ out.port = static_cast(settings.value("Port", 4316).toInt());
+ settings.endGroup();
+ return out;
+}
+
+ConnectionData getOBSConnectionData(QSettings &settings) {
+ ConnectionData out;
+ settings.beginGroup("OBSClient");
+ out.host = settings.value("Host", "127.0.0.1").toString();
+ out.port = static_cast(settings.value("Port", 9302).toInt());
+ settings.endGroup();
+ return out;
+}
+
+void setCameraConnectionData(ConnectionData const&cd) {
+ QSettings settings;
+ settings.beginGroup("CameraClient");
+ settings.setValue("Host", cd.host);
+ settings.setValue("Port", cd.port);
+ settings.endGroup();
+}
+
+void setOpenLPConnectionData(ConnectionData const&cd) {
+ QSettings settings;
+ settings.beginGroup("OpenLPClient");
+ settings.setValue("Host", cd.host);
+ settings.setValue("Port", cd.port);
+ settings.endGroup();
+}
+
+void setOBSConnectionData(ConnectionData const&cd) {
+ QSettings settings;
+ settings.beginGroup("OBSClient");
+ settings.setValue("Host", cd.host);
+ settings.setValue("Port", cd.port);
+ settings.endGroup();
+}
+
+ConnectionData getCameraConnectionData() {
+ QSettings s;
+ return getCameraConnectionData(s);
+}
+
+ConnectionData getOpenLPConnectionData() {
+ QSettings s;
+ return getOpenLPConnectionData(s);
+}
+
+ConnectionData getOBSConnectionData() {
+ QSettings s;
+ return getOBSConnectionData(s);
+}
+
+
+void setViews(QSettings &settings, QVector const&views) {
+ settings.beginGroup("Views");
+ settings.beginWriteArray("Views");
+ for (auto i = 0; auto const&view : views) {
+ settings.setArrayIndex(i);
+ settings.setValue("Name", view.name);
+ settings.setValue("Slides", view.slides);
+ settings.setValue("ObsSlides", view.obsSlides);
+ settings.setValue("Preset", view.cameraPreset);
+ ++i;
+ }
+ settings.endArray();
+ settings.endGroup();
+}
+
+void setViews(QVector const&views) {
+ QSettings s;
+ return setViews(s, views);
+}
+
+QVector getViews(QSettings &settings) {
+ QVector out;
+ settings.beginGroup("Views");
+ const auto size = settings.beginReadArray("Views");
+ for (auto i = 0; i < size; ++i) {
+ settings.setArrayIndex(i);
+ out.emplace_back(View{
+ .name = settings.value("Name").toString(),
+ .slides = settings.value("Slides").toBool(),
+ .obsSlides = settings.value("ObsSlides").toBool(),
+ .cameraPreset = settings.value("Preset").toInt(),
+ });
+ }
+ settings.endArray();
+ settings.endGroup();
+ return out;
+}
+
+QVector getViews() {
+ QSettings s;
+ return getViews(s);
+}
diff --git a/src/settingsdata.hpp b/src/settingsdata.hpp
new file mode 100644
index 0000000..4a39622
--- /dev/null
+++ b/src/settingsdata.hpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 - 2023 gary@drinkingtea.net
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include
+#include
+
+struct ConnectionData {
+ QString host;
+ uint16_t port = 0;
+};
+
+void setCameraConnectionData(class QSettings &settings, ConnectionData const&cd);
+
+void setOpenLPConnectionData(class QSettings &settings, ConnectionData const&cd);
+
+void setOBSConnectionData(class QSettings &settings, ConnectionData const&cd);
+
+[[nodiscard]]
+ConnectionData getCameraConnectionData(class QSettings &settings);
+
+[[nodiscard]]
+ConnectionData getOpenLPConnectionData(class QSettings &settings);
+
+[[nodiscard]]
+ConnectionData getOBSConnectionData(class QSettings &settings);
+
+[[nodiscard]]
+ConnectionData getCameraConnectionData();
+
+[[nodiscard]]
+ConnectionData getOpenLPConnectionData();
+
+[[nodiscard]]
+ConnectionData getOBSConnectionData();
+
+
+struct View {
+ QString name;
+ bool slides = false;
+ bool obsSlides = false;
+ int cameraPreset = -1;
+};
+
+void setViews(class QSettings &settings, QVector const&views);
+
+void setViews(QVector const&views);
+
+[[nodiscard]]
+QVector getViews(class QSettings &settings);
+
+[[nodiscard]]
+QVector getViews();
\ No newline at end of file
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
new file mode 100644
index 0000000..ba76801
--- /dev/null
+++ b/src/settingsdialog.cpp
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2021 - 2023 gary@drinkingtea.net
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "settingsdialog.hpp"
+
+constexpr auto MaxPresets = 9;
+constexpr auto MaxViews = 9;
+
+enum ViewColumn {
+ Name = 0,
+ Slides,
+ ObsSlides,
+ CameraPreset,
+ Count
+};
+
+SettingsDialog::SettingsDialog(QWidget *parent): QDialog(parent) {
+ const auto lyt = new QVBoxLayout(this);
+ const auto tabs = new QTabWidget(this);
+ lyt->addWidget(tabs);
+ tabs->addTab(setupViewConfig(tabs), tr("&Views"));
+ tabs->addTab(setupNetworkInputs(tabs), tr("&Network"));
+ lyt->addWidget(setupButtons(this));
+ setFixedSize(440, 440);
+}
+
+QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) {
+ const auto root = new QWidget(parent);
+ const auto lyt = new QFormLayout(root);
+ const auto portValidator = new QIntValidator(1, 65536, this);
+ QSettings settings;
+ // camera settings
+ {
+ const auto c = getCameraConnectionData(settings);
+ m_cameraIpLe = new QLineEdit(root);
+ m_cameraPortLe = new QLineEdit(root);
+ m_cameraIpLe->setText(c.host);
+ m_cameraPortLe->setText(QString::number(c.port));
+ m_cameraPortLe->setValidator(portValidator);
+ lyt->addRow(tr("C&amera IP Address:"), m_cameraIpLe);
+ lyt->addRow(tr("Ca&mera Port:"), m_cameraPortLe);
+ }
+ // OpenLP settings
+ {
+ const auto c = getOpenLPConnectionData(settings);
+ m_openLpIpLe = new QLineEdit(root);
+ m_openLpPortLe = new QLineEdit(root);
+ m_openLpIpLe->setText(c.host);
+ m_openLpPortLe->setText(QString::number(c.port));
+ m_openLpPortLe->setValidator(portValidator);
+ lyt->addRow(tr("Op&enLP IP Address:"), m_openLpIpLe);
+ lyt->addRow(tr("Open&LP Port:"), m_openLpPortLe);
+ }
+ // OBS settings
+ {
+ const auto c = getOBSConnectionData(settings);
+ m_obsIpLe = new QLineEdit(root);
+ m_obsPortLe = new QLineEdit(root);
+ m_obsIpLe->setText(c.host);
+ m_obsPortLe->setText(QString::number(c.port));
+ m_obsPortLe->setValidator(portValidator);
+ lyt->addRow(tr("O&BS IP Address:"), m_obsIpLe);
+ lyt->addRow(tr("OB&S Port:"), m_obsPortLe);
+ }
+ return root;
+}
+
+QWidget *SettingsDialog::setupViewConfig(QWidget *parent) {
+ auto const root = new QWidget(parent);
+ auto const lyt = new QVBoxLayout(root);
+ // table
+ m_viewTable = new QTableWidget(parent);
+ {
+ lyt->addWidget(m_viewTable);
+ QStringList columns;
+ columns.resize(ViewColumn::Count);
+ columns[ViewColumn::Name] = tr("Name");
+ columns[ViewColumn::Slides] = tr("Slides");
+ columns[ViewColumn::ObsSlides] = tr("OBS Slides");
+ columns[ViewColumn::CameraPreset] = tr("Camera Preset");
+ m_viewTable->setColumnCount(static_cast(columns.size()));
+ m_viewTable->setHorizontalHeaderLabels(columns);
+ m_viewTable->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection);
+ m_viewTable->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
+ auto const hdr = m_viewTable->horizontalHeader();
+ m_viewTable->setColumnWidth(1, 70);
+ m_viewTable->setColumnWidth(2, 70);
+ m_viewTable->setColumnWidth(3, 70);
+ hdr->setStretchLastSection(true);
+ }
+ // add/removes buttons
+ {
+ auto const btnsRoot = new QWidget(root);
+ auto const btnsLyt = new QHBoxLayout(btnsRoot);
+ auto const addBtn = new QPushButton("+", btnsRoot);
+ auto const rmBtn = new QPushButton("-", btnsRoot);
+ addBtn->setFixedWidth(20);
+ rmBtn->setFixedWidth(20);
+ rmBtn->setDisabled(true);
+ lyt->addWidget(btnsRoot);
+ btnsLyt->addWidget(addBtn);
+ btnsLyt->addWidget(rmBtn);
+ btnsLyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
+ connect(addBtn, &QPushButton::clicked, this, [this, addBtn] {
+ auto const row = m_viewTable->rowCount();
+ m_viewTable->setRowCount(row + 1);
+ setupViewRow(row);
+ addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
+ });
+ connect(rmBtn, &QPushButton::clicked, this, [this, addBtn] {
+ auto const row = m_viewTable->currentRow();
+ m_viewTable->removeRow(row);
+ addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
+ });
+ connect(m_viewTable, &QTableWidget::currentCellChanged, rmBtn, [this, addBtn, rmBtn] (int row) {
+ rmBtn->setEnabled(row > -1 && row < m_viewTable->rowCount());
+ addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
+ });
+ const auto views = getViews();
+ m_viewTable->setRowCount(static_cast(views.size()));
+ for (auto row = 0; auto const&view : views) {
+ setupViewRow(row, view);
+ ++row;
+ }
+ }
+ return root;
+}
+
+QWidget *SettingsDialog::setupButtons(QWidget *parent) {
+ const auto root = new QWidget(parent);
+ const auto lyt = new QHBoxLayout(root);
+ m_errLbl = new QLabel(root);
+ const auto okBtn = new QPushButton(tr("&OK"), root);
+ const auto cancelBtn = new QPushButton(tr("&Cancel"), root);
+ lyt->addWidget(m_errLbl);
+ lyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
+ lyt->addWidget(okBtn);
+ lyt->addWidget(cancelBtn);
+ connect(okBtn, &QPushButton::clicked, this, &SettingsDialog::handleOK);
+ connect(cancelBtn, &QPushButton::clicked, this, &SettingsDialog::reject);
+ return root;
+}
+
+void SettingsDialog::handleOK() {
+ QSettings settings;
+ QVector views;
+ auto const viewsErr = collectViews(views);
+ if (viewsErr) {
+ return;
+ }
+ setViews(settings, views);
+ setCameraConnectionData(settings, {
+ .host = m_cameraIpLe->text(),
+ .port = m_cameraPortLe->text().toUShort(),
+ });
+ setOpenLPConnectionData(settings, {
+ .host = m_openLpIpLe->text(),
+ .port = m_openLpPortLe->text().toUShort(),
+ });
+ setOBSConnectionData(settings, {
+ .host = m_obsIpLe->text(),
+ .port = m_obsPortLe->text().toUShort(),
+ });
+ accept();
+}
+
+void SettingsDialog::setupViewRow(int row, View const&view) {
+ // name
+ const auto nameItem = new QTableWidgetItem(view.name);
+ m_viewTable->setItem(row, ViewColumn::Name, nameItem);
+ // slides
+ const auto slidesCb = new QCheckBox(m_viewTable);
+ slidesCb->setChecked(view.slides);
+ m_viewTable->setCellWidget(row, ViewColumn::Slides, slidesCb);
+ // obs slides
+ const auto obsSlidesCb = new QCheckBox(m_viewTable);
+ obsSlidesCb->setChecked(view.obsSlides);
+ m_viewTable->setCellWidget(row, ViewColumn::ObsSlides, obsSlidesCb);
+ // camera preset
+ const auto presetItem = new QTableWidgetItem(QString::number(view.cameraPreset));
+ m_viewTable->setItem(row, ViewColumn::CameraPreset, presetItem);
+}
+
+int SettingsDialog::collectViews(QVector &views) const {
+ for (auto row = 0; row < m_viewTable->rowCount(); ++row) {
+ auto const viewNo = row + 1;
+ bool ok = false;
+ auto const name = m_viewTable->item(row, ViewColumn::Name)->text();
+ if (name.trimmed() == "") {
+ m_errLbl->setText(tr("View %1 has no name.").arg(viewNo));
+ return 1;
+ }
+ const auto cameraPreset = m_viewTable->item(row, ViewColumn::CameraPreset)->text().toInt(&ok);
+ if (!ok || cameraPreset < 1 || cameraPreset > MaxPresets) {
+ m_errLbl->setText(tr("View %1 has invalid preset (1-%2)").arg(viewNo).arg(MaxPresets));
+ return 2;
+ }
+ views.emplace_back(View{
+ .name = name,
+ .slides = dynamic_cast(m_viewTable->cellWidget(row, ViewColumn::Slides))->isChecked(),
+ .obsSlides = dynamic_cast(m_viewTable->cellWidget(row, ViewColumn::ObsSlides))->isChecked(),
+ .cameraPreset = cameraPreset,
+ });
+ }
+ return 0;
+}
diff --git a/src/settingsdialog.hpp b/src/settingsdialog.hpp
new file mode 100644
index 0000000..633d517
--- /dev/null
+++ b/src/settingsdialog.hpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 - 2023 gary@drinkingtea.net
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#pragma once
+
+#include
+
+#include "settingsdata.hpp"
+
+class SettingsDialog: public QDialog {
+ Q_OBJECT
+ private:
+ class QLabel *m_errLbl = nullptr;
+ class QLineEdit *m_cameraIpLe = nullptr;
+ class QLineEdit *m_cameraPortLe = nullptr;
+ class QLineEdit *m_openLpIpLe = nullptr;
+ class QLineEdit *m_openLpPortLe = nullptr;
+ class QLineEdit *m_obsIpLe = nullptr;
+ class QLineEdit *m_obsPortLe = nullptr;
+ class QTableWidget *m_viewTable = nullptr;
+ public:
+ explicit SettingsDialog(QWidget *parent);
+ private:
+ QWidget *setupNetworkInputs(QWidget *parent);
+ QWidget *setupViewConfig(QWidget *parent);
+ QWidget *setupButtons(QWidget *parent);
+ void handleOK();
+ void setupViewRow(int row, View const&view = {});
+ /**
+ * Gets views from table.
+ * @return error code
+ */
+ [[nodiscard("Must check error code")]]
+ int collectViews(QVector &views) const;
+};
diff --git a/src/slideview.cpp b/src/slideview.cpp
index 5b920eb..152f995 100644
--- a/src/slideview.cpp
+++ b/src/slideview.cpp
@@ -6,7 +6,6 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-#include
#include
#include
#include
@@ -43,9 +42,9 @@ QString SlideView::getNextSong() const {
return "";
}
-void SlideView::pollUpdate(QString songName, int slide) {
+void SlideView::pollUpdate(QString const&songName, int slide) {
auto songItems = m_songSelector->findItems(songName, Qt::MatchFixedString);
- if (songItems.size() < 1) {
+ if (songItems.empty()) {
return;
}
auto songItem = songItems.first();
@@ -66,11 +65,11 @@ void SlideView::changeSong(int song) {
}
}
-void SlideView::slideListUpdate(QStringList tagList, QStringList slideList) {
+void SlideView::slideListUpdate(QStringList const&tagList, QStringList const&slideList) {
m_currentSlide = 0;
- m_slideTable->setRowCount(slideList.size());
+ m_slideTable->setRowCount(static_cast(slideList.size()));
for (int i = 0; i < slideList.size(); ++i) {
- auto txt = slideList[i];
+ const auto& txt = slideList[i];
auto item = new QTableWidgetItem(txt);
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
m_slideTable->setItem(i, 0, item);
@@ -86,7 +85,7 @@ void SlideView::reset() {
m_currentSlide = -1;
}
-void SlideView::songListUpdate(QStringList songList) {
+void SlideView::songListUpdate(QStringList const&songList) {
// Is this replacing an existing song list or is it the initial song list?
// We want to reset the song to 0 upon replacement,
// but leave it alone upon initialization.
diff --git a/src/slideview.hpp b/src/slideview.hpp
index fb20c32..ff8fdf1 100644
--- a/src/slideview.hpp
+++ b/src/slideview.hpp
@@ -24,11 +24,11 @@ class SlideView: public QWidget {
QString getNextSong() const;
public slots:
- void pollUpdate(QString songId, int slideNum);
+ void pollUpdate(const QString& songId, int slideNum);
- void songListUpdate(QStringList songList);
+ void songListUpdate(QStringList const&songList);
- void slideListUpdate(QStringList tagList, QStringList songList);
+ void slideListUpdate(QStringList const&tagList, QStringList const&songList);
void reset();