Switch from QMake to CMake with buildcore

This commit is contained in:
2021-07-28 01:13:20 -05:00
parent 1ab03975f7
commit 91683b4dd7
23 changed files with 898 additions and 30 deletions

31
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,31 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Network Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED)
add_executable(
SlideController MACOSX_BUNDLE
main.cpp
mainwindow.cpp
obsclient.cpp
openlpclient.cpp
slideview.cpp
)
target_link_libraries(
SlideController
Qt${QT_VERSION_MAJOR}::Network
Qt${QT_VERSION_MAJOR}::Widgets
)
install(
TARGETS
SlideController
DESTINATION
bin
BUNDLE DESTINATION .
)

11
src/consts.hpp Normal file
View File

@ -0,0 +1,11 @@
/*
* Copyright 2021 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
constexpr auto SlideHost = "turbo";

18
src/main.cpp Normal file
View File

@ -0,0 +1,18 @@
/*
* Copyright 2021 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 <QApplication>
#include "mainwindow.hpp"
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

118
src/mainwindow.cpp Normal file
View File

@ -0,0 +1,118 @@
/*
* Copyright 2021 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 <QFormLayout>
#include <QHBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QStatusBar>
#include <QVBoxLayout>
#include "slideview.hpp"
#include "mainwindow.hpp"
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
move(0, 0);
setFixedSize(590, 555);
setWindowTitle(tr("Slide Controller 9000"));
const auto mainWidget = new QWidget(this);
const auto rootLyt = new QVBoxLayout;
const auto controlsLayout = new QGridLayout;
const auto slideView = new SlideView(this);
setCentralWidget(mainWidget);
mainWidget->setLayout(rootLyt);
rootLyt->addWidget(slideView);
rootLyt->addLayout(controlsLayout);
// setup slide controls
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 btnBlankSlides = new QPushButton(tr("Blank Slides (,)"), this);
const auto btnShowSlides = new QPushButton(tr("Show Slides (.)"), this);
controlsLayout->addWidget(btnPrevSlide, 0, 0);
controlsLayout->addWidget(btnNextSlide, 0, 1);
controlsLayout->addWidget(btnPrevSong, 1, 0);
controlsLayout->addWidget(btnNextSong, 1, 1);
controlsLayout->addWidget(btnBlankSlides, 2, 0);
controlsLayout->addWidget(btnShowSlides, 2, 1);
btnNextSong->setShortcut(Qt::Key_Right);
btnPrevSong->setShortcut(Qt::Key_Left);
btnNextSlide->setShortcut(Qt::Key_Down);
btnPrevSlide->setShortcut(Qt::Key_Up);
btnBlankSlides->setShortcut(Qt::Key_Comma);
btnShowSlides->setShortcut(Qt::Key_Period);
btnBlankSlides->setToolTip(tr("Also hides slides in OBS"));
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(btnBlankSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen);
connect(btnBlankSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, slideView, &SlideView::pollUpdate);
connect(&m_openlpClient, &OpenLPClient::songListUpdate, slideView, &SlideView::songListUpdate);
connect(&m_openlpClient, &OpenLPClient::slideListUpdate, slideView, &SlideView::slideListUpdate);
connect(&m_openlpClient, &OpenLPClient::pollFailed, slideView, &SlideView::reset);
connect(slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong);
connect(slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide);
// setup scene selector
const auto btnObsHideSlides = new QPushButton(tr("Hide Slides in OBS (;)"), mainWidget);
const auto btnObsShowSlides = new QPushButton(tr("Show Slides in OBS (')"), mainWidget);
controlsLayout->addWidget(btnObsHideSlides, 3, 0);
controlsLayout->addWidget(btnObsShowSlides, 3, 1);
btnObsHideSlides->setShortcut(Qt::Key_Semicolon);
btnObsShowSlides->setShortcut(Qt::Key_Apostrophe);
btnObsShowSlides->setToolTip(tr("Also shows slides in OpenLP"));
connect(btnObsHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
connect(btnObsShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
connect(btnObsShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
// setup status bar
setStatusBar(new QStatusBar(this));
connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
refreshStatusBar();
}
MainWindow::~MainWindow() {
}
void MainWindow::openLpConnectionInit() {
disconnect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
connect(&m_openlpClient, &OpenLPClient::pollFailed, this, &MainWindow::openLpConnectionLost);
m_openLpConnected = true;
refreshStatusBar();
}
void MainWindow::openLpConnectionLost() {
disconnect(&m_openlpClient, &OpenLPClient::pollFailed, this, &MainWindow::openLpConnectionLost);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
m_openLpConnected = false;
refreshStatusBar();
}
void MainWindow::obsConnectionInit() {
disconnect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
connect(&m_obsClient, &OBSClient::pollFailed, this, &MainWindow::obsConnectionLost);
m_obsConnected = true;
refreshStatusBar();
}
void MainWindow::obsConnectionLost() {
disconnect(&m_obsClient, &OBSClient::pollFailed, this, &MainWindow::obsConnectionLost);
connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
m_obsConnected = false;
refreshStatusBar();
}
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");
statusBar()->showMessage(openLpStatus + " | " + obsStatus);
}

42
src/mainwindow.hpp Normal file
View File

@ -0,0 +1,42 @@
/*
* Copyright 2021 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 <cstdint>
#include <QMainWindow>
#include "obsclient.hpp"
#include "openlpclient.hpp"
class MainWindow: public QMainWindow {
Q_OBJECT
private:
OBSClient m_obsClient;
OpenLPClient m_openlpClient;
bool m_openLpConnected = false;
bool m_obsConnected = false;
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void openLpConnectionInit();
void openLpConnectionLost();
void obsConnectionInit();
void obsConnectionLost();
void refreshStatusBar();
};

60
src/obsclient.cpp Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright 2021 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 <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include "obsclient.hpp"
OBSClient::OBSClient(QObject *parent): QObject(parent) {
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) {
get(QString("/Scene?name=%1").arg(scene));
}
void OBSClient::showSlides() {
setScene("MusicScene");
}
void OBSClient::hideSlides() {
setScene("SpeakerScene");
}
void OBSClient::setSlidesVisible(int state) {
if (state) {
setScene("MusicScene");
} else {
setScene("SpeakerScene");
}
}
void OBSClient::get(QString urlExt) {
QUrl url(QString(BaseUrl) + urlExt);
QNetworkRequest rqst(url);
m_nam->get(rqst);
}
void OBSClient::poll() {
QUrl url(QString(BaseUrl) + "/ping");
QNetworkRequest rqst(url);
m_pollingNam->get(rqst);
}
void OBSClient::handlePollResponse(QNetworkReply *reply) {
if (reply->error()) {
qDebug() << "OBSClient error response:" << reply->errorString();
emit pollFailed();
return;
}
emit pollUpdate();
}

50
src/obsclient.hpp Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright 2021 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 <QNetworkAccessManager>
#include <QObject>
#include <QTimer>
#include "consts.hpp"
class OBSClient: public QObject {
Q_OBJECT
private:
const QString BaseUrl = QString("http://") + SlideHost + ":9302";
QNetworkAccessManager *m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this);
QTimer m_pollTimer;
public:
explicit OBSClient(QObject *parent = nullptr);
public slots:
void setScene(QString scene);
void showSlides();
void hideSlides();
void setSlidesVisible(int state);
private:
void get(QString url);
void poll();
void handlePollResponse(QNetworkReply *reply);
signals:
void pollUpdate();
void pollFailed();
};

164
src/openlpclient.cpp Normal file
View File

@ -0,0 +1,164 @@
/*
* Copyright 2021 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 <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValueRef>
#include <QNetworkReply>
#include "openlpclient.hpp"
OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) {
poll();
m_pollTimer.start(250);
connect(&m_pollTimer, &QTimer::timeout, this, &OpenLPClient::poll);
connect(m_nam, &QNetworkAccessManager::finished, this, &OpenLPClient::handleGeneralResponse);
connect(m_songListNam, &QNetworkAccessManager::finished, this, &OpenLPClient::handleSongListResponse);
connect(m_slideListNam, &QNetworkAccessManager::finished, this, &OpenLPClient::handleSlideListResponse);
connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OpenLPClient::handlePollResponse);
}
void OpenLPClient::nextSlide() {
get("/api/controller/live/next");
}
void OpenLPClient::prevSlide() {
get("/api/controller/live/previous");
}
void OpenLPClient::nextSong() {
get("/api/service/next");
}
void OpenLPClient::prevSong() {
get("/api/service/previous");
}
void OpenLPClient::blankScreen() {
get("/api/display/hide");
}
void OpenLPClient::showSlides() {
get("/api/display/show");
}
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";
get(url);
}
void OpenLPClient::changeSlide(int slide) {
auto n = QString::number(slide);
auto url = R"(/api/controller/live/set?data={"request"%3A+{"id"%3A)" + n + "}}&_=1626628079579)";
get(url);
}
void OpenLPClient::get(QString urlExt) {
QUrl url(QString(BaseUrl) + urlExt);
QNetworkRequest rqst(url);
m_nam->get(rqst);
}
void OpenLPClient::requestSongList() {
QUrl url(QString(BaseUrl) + "/api/service/list?_=1626628079579");
QNetworkRequest rqst(url);
m_songListNam->get(rqst);
}
void OpenLPClient::requestSlideList() {
QUrl url(QString(BaseUrl) + "/api/controller/live/text?_=1626628079579");
QNetworkRequest rqst(url);
m_slideListNam->get(rqst);
}
void OpenLPClient::poll() {
QUrl url(QString(BaseUrl) + "/api/poll?_=1626628079579");
QNetworkRequest rqst(url);
m_pollingNam->get(rqst);
}
void OpenLPClient::handleGeneralResponse(QNetworkReply *reply) {
if (reply->error()) {
qDebug() << "OpenLPClient error response:" << reply->request().url() << ":" << reply->errorString();
}
}
void OpenLPClient::handlePollResponse(QNetworkReply *reply) {
if (reply->error()) {
qDebug() << "OpenLPClient error response:" << reply->errorString();
emit pollFailed();
m_currentServiceId = -1;
m_currentSongId = "";
m_songNameMap.clear();
return;
}
auto data = reply->readAll();
if (data.isEmpty()) {
return;
}
auto doc = QJsonDocument::fromJson(data);
auto obj = doc.object()["results"].toObject();
auto songId = obj["item"].toString();
auto slide = obj["slide"].toInt();
auto service = obj["service"].toInt();
if (service != m_currentServiceId) {
requestSongList();
m_currentServiceId = service;
}
if (m_currentSongId != songId) {
requestSlideList();
m_currentSongId = songId;
}
emit pollUpdate(m_songNameMap[songId], slide);
}
void OpenLPClient::handleSongListResponse(QNetworkReply *reply) {
if (reply->error()) {
qDebug() << "OpenLPClient error response:" << reply->errorString();
}
auto data = reply->readAll();
if (data.isEmpty()) {
return;
}
QStringList songList;
auto doc = QJsonDocument::fromJson(data);
auto items = doc.object()["results"].toObject()["items"].toArray();
m_songNameMap.clear();
for (const auto &item : items) {
auto song = item.toObject();
auto name = song["title"].toString();
auto id = song["id"].toString();
m_songNameMap[id] = name;
songList.push_back(name);
}
emit songListUpdate(songList);
}
void OpenLPClient::handleSlideListResponse(QNetworkReply *reply) {
if (reply->error()) {
qDebug() << "OpenLPClient error response:" << reply->errorString();
}
auto data = reply->readAll();
if (data.isEmpty()) {
return;
}
QStringList slideList;
QStringList tagList;
auto doc = QJsonDocument::fromJson(data);
auto items = doc.object()["results"].toObject()["slides"].toArray();
for (const auto &item : items) {
auto slide = item.toObject();
auto text = slide["text"].toString();
auto tag = slide["tag"].toString();
slideList.push_back(text);
tagList.push_back(tag);
}
emit slideListUpdate(tagList, slideList);
}

84
src/openlpclient.hpp Normal file
View File

@ -0,0 +1,84 @@
/*
* Copyright 2021 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 <QHash>
#include <QNetworkAccessManager>
#include <QObject>
#include <QTimer>
#include "consts.hpp"
class OpenLPClient: public QObject {
Q_OBJECT
private:
struct Song {
QString name;
QString id;
};
const QString BaseUrl = QString("http://") + SlideHost + ":4316";
QNetworkAccessManager *m_nam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_songListNam = new QNetworkAccessManager(this);
QNetworkAccessManager *m_slideListNam = new QNetworkAccessManager(this);
QTimer m_pollTimer;
QHash<QString, QString> m_songNameMap;
int m_currentServiceId = -1;
QString m_currentSongId;
public:
explicit OpenLPClient(QObject *parent = nullptr);
public slots:
void nextSlide();
void prevSlide();
void nextSong();
void prevSong();
void blankScreen();
void showSlides();
void changeSong(int it);
void changeSlide(int slide);
private:
void get(QString url);
void requestSongList();
void requestSlideList();
private slots:
void poll();
void handleGeneralResponse(QNetworkReply *reply);
void handlePollResponse(QNetworkReply *reply);
void handleSongListResponse(QNetworkReply *reply);
void handleSlideListResponse(QNetworkReply *reply);
signals:
void pollUpdate(QString songId, int slideNum);
void pollFailed();
void songListUpdate(QStringList);
void slideListUpdate(QStringList, QStringList);
};

85
src/slideview.cpp Normal file
View File

@ -0,0 +1,85 @@
/*
* Copyright 2021 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 <QComboBox>
#include <QDebug>
#include <QHeaderView>
#include <QTableWidget>
#include <QVBoxLayout>
#include "slideview.hpp"
SlideView::SlideView(QWidget *parent): QWidget(parent) {
auto lyt = new QVBoxLayout(this);
m_songSelector = new QComboBox(this);
m_slideTable = new QTableWidget(this);
auto header = m_slideTable->horizontalHeader();
header->setVisible(false);
header->setStretchLastSection(true);
m_slideTable->setSelectionBehavior(QTableWidget::SelectionBehavior::SelectRows);
m_slideTable->setSelectionMode(QTableWidget::SelectionMode::SingleSelection);
m_slideTable->setColumnCount(1);
m_slideTable->verticalHeader()->setDefaultSectionSize(300);
#ifndef _WIN32
m_slideTable->setAlternatingRowColors(true);
#endif
lyt->addWidget(m_songSelector);
lyt->addWidget(m_slideTable);
connect(m_slideTable, &QTableWidget::currentCellChanged, this, &SlideView::slideChanged);
}
void SlideView::pollUpdate(QString songName, int slide) {
if (songName != m_currentSong) {
m_currentSong = songName;
m_songSelector->setCurrentText(songName);
}
if (slide != m_currentSlide) {
m_currentSlide = slide;
m_slideTable->setCurrentCell(slide, 0);
}
}
void SlideView::changeSong(int song) {
if (m_songSelector->currentText() != m_currentSong) {
emit songChanged(song);
}
}
void SlideView::slideListUpdate(QStringList tagList, QStringList slideList) {
m_currentSlide = 0;
m_slideTable->setRowCount(slideList.size());
for (int i = 0; i < slideList.size(); ++i) {
auto txt = slideList[i];
auto item = new QTableWidgetItem(txt);
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
m_slideTable->setItem(i, 0, item);
}
m_slideTable->setVerticalHeaderLabels(tagList);
m_slideTable->resizeRowsToContents();
}
void SlideView::reset() {
m_songSelector->clear();
m_slideTable->setRowCount(0);
m_currentSong = "";
m_currentSlide = -1;
}
void SlideView::songListUpdate(QStringList 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.
auto isReplacement = m_songSelector->count() > 0;
disconnect(m_songSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSong(int)));
m_songSelector->clear();
m_songSelector->addItems(songList);
if (isReplacement) {
changeSong(0);
}
connect(m_songSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSong(int)));
}

39
src/slideview.hpp Normal file
View File

@ -0,0 +1,39 @@
/*
* Copyright 2021 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 <QWidget>
class SlideView: public QWidget {
Q_OBJECT
private:
class QTableWidget *m_slideTable = nullptr;
class QComboBox *m_songSelector = nullptr;
QString m_currentSong;
int m_currentSlide = -1;
public:
explicit SlideView(QWidget *parent = nullptr);
public slots:
void pollUpdate(QString songId, int slideNum);
void songListUpdate(QStringList songList);
void slideListUpdate(QStringList tagList, QStringList songList);
void reset();
private slots:
void changeSong(int song);
signals:
void songChanged(int);
void slideChanged(int);
};