18 Commits

23 changed files with 383 additions and 130 deletions

View File

@ -2,7 +2,7 @@
source: source:
- . - .
copyright_notice: |- copyright_notice: |-
Copyright 2021 - 2023 gary@drinkingtea.net Copyright 2021 - 2024 gary@drinkingtea.net
This Source Code Form is subject to the terms of the Mozilla Public 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 License, v. 2.0. If a copy of the MPL was not distributed with this

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Slide Controller 9000
## Build Prerequisites
* Install GCC, Clang, or Visual Studio with C++20 support
* Install Python 3
* Install Ninja, Make, and CMake
* [Qt6](https://www.qt.io/download-qt-installer-oss) (Network and Widgets)
* Consider also installing ccache for faster subsequent build times
## Build
Build options: release, debug, asan
make purge configure-{release,debug,asan} install
## Run
make run

View File

@ -14,6 +14,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
# enable ccache # enable ccache
if(NOT DEFINED ENV{BUILDCORE_SUPPRESS_CCACHE}) if(NOT DEFINED ENV{BUILDCORE_SUPPRESS_CCACHE})
find_program(CCACHE_PROGRAM ccache) find_program(CCACHE_PROGRAM ccache)

View File

@ -6,6 +6,10 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# #
ifndef USE_CONAN
USE_CONAN=0
endif
ifeq (${OS},Windows_NT) ifeq (${OS},Windows_NT)
SHELL := powershell.exe SHELL := powershell.exe
.SHELLFLAGS := -NoProfile -Command .SHELLFLAGS := -NoProfile -Command
@ -78,6 +82,7 @@ purge:
${ENV_RUN} ${RM_RF} .current_build ${ENV_RUN} ${RM_RF} .current_build
${ENV_RUN} ${RM_RF} ${BUILD_PATH} ${ENV_RUN} ${RM_RF} ${BUILD_PATH}
${ENV_RUN} ${RM_RF} dist ${ENV_RUN} ${RM_RF} dist
${ENV_RUN} ${RM_RF} compile_commands.json
.PHONY: test .PHONY: test
test: build test: build
${ENV_RUN} mypy ${SCRIPTS} ${ENV_RUN} mypy ${SCRIPTS}
@ -129,6 +134,8 @@ else
${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.bat ${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.bat
endif endif
endif
.PHONY: vcpkg-install .PHONY: vcpkg-install
vcpkg-install: vcpkg-install:
ifneq (${OS},windows) ifneq (${OS},windows)
@ -137,39 +144,30 @@ else
${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS} ${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS}
endif endif
else ifdef USE_CONAN # USE_VCPKG ################################################ ifeq (${USE_CONAN},1) # USE_CONAN ################################################
.PHONY: conan-config
.PHONY: setup-conan
conan-config: conan-config:
${ENV_RUN} conan profile new ${PROJECT_NAME} --detect --force ${ENV_RUN} conan profile detect -f --name ${PROJECT_NAME}
ifeq ($(OS),linux)
${ENV_RUN} conan profile update settings.compiler.libcxx=libstdc++11 ${PROJECT_NAME}
else
${ENV_RUN} conan profile update settings.compiler.cppstd=20 ${PROJECT_NAME}
ifeq ($(OS),windows)
${ENV_RUN} conan profile update settings.compiler.runtime=static ${PROJECT_NAME}
endif
endif
.PHONY: conan .PHONY: conan
conan: conan:
${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME} ${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME}
endif # USE_VCPKG ############################################### endif # USE_CONAN ###############################################
ifeq (${OS},darwin) ifeq (${OS},darwin)
.PHONY: configure-xcode .PHONY: configure-xcode
configure-xcode: configure-xcode:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 --build_root=${BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 --build_root=${BUILD_PATH} --use_conan=${USE_CONAN}
endif endif
.PHONY: configure-release .PHONY: configure-release
configure-release: configure-release:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=release --build_root=${BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=release --build_root=${BUILD_PATH} --use_conan=${USE_CONAN}
.PHONY: configure-debug .PHONY: configure-debug
configure-debug: configure-debug:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BUILD_PATH} --use_conan=${USE_CONAN}
.PHONY: configure-asan .PHONY: configure-asan
configure-asan: configure-asan:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BUILD_PATH} ${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BUILD_PATH} --use_conan=${USE_CONAN}

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# #
# Copyright 2016 - 2021 gary@drinkingtea.net # Copyright 2016 - 2023 gary@drinkingtea.net
# #
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # License, v. 2.0. If a copy of the MPL was not distributed with this
@ -74,7 +74,7 @@ def conan() -> int:
return 1 return 1
if err != 0: if err != 0:
return err return err
args = ['conan', 'install', '../', '--build=missing', '-pr', project_name] args = ['conan', 'install', '../', '-of', '.', '--build=missing', '-pr', project_name]
os.chdir(conan_dir) os.chdir(conan_dir)
err = subprocess.run(args).returncode err = subprocess.run(args).returncode
if err != 0: if err != 0:

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# #
# Copyright 2016 - 2021 gary@drinkingtea.net # Copyright 2016 - 2023 gary@drinkingtea.net
# #
# This Source Code Form is subject to the terms of the Mozilla Public # 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 # License, v. 2.0. If a copy of the MPL was not distributed with this
@ -17,16 +17,18 @@ import sys
from pybb import mkdir, rm from pybb import mkdir, rm
os_name = os.uname().sysname.lower()
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--target', help='Platform target', parser.add_argument('--target', help='Platform target',
default='{:s}-{:s}'.format(sys.platform, platform.machine())) default='{:s}-{:s}'.format(os_name, platform.machine()))
parser.add_argument('--build_type', help='Build type (asan,debug,release)', default='release') parser.add_argument('--build_type', help='Build type (asan,debug,release)', default='release')
parser.add_argument('--build_tool', help='Build tool (default,xcode)', default='') parser.add_argument('--build_tool', help='Build tool (default,xcode)', default='')
parser.add_argument('--build_root', help='Path to the root of build directories (must be in project dir)', default='build') parser.add_argument('--build_root', help='Path to the root of build directories (must be in project dir)', default='build')
parser.add_argument('--toolchain', help='Path to CMake toolchain file', default='') parser.add_argument('--toolchain', help='Path to CMake toolchain file', default='')
parser.add_argument('--current_build', help='Indicates whether or not to make this the active build', default=1) parser.add_argument('--current_build', help='Indicates whether or not to make this the active build', default=1)
parser.add_argument('--use_conan', help='Indicates whether or not should use .conanbuild/conan_toolchain.cmake', default='0')
args = parser.parse_args() args = parser.parse_args()
if args.build_type == 'asan': if args.build_type == 'asan':
@ -75,6 +77,8 @@ def main() -> int:
'-DBUILDCORE_BUILD_CONFIG={:s}'.format(build_config), '-DBUILDCORE_BUILD_CONFIG={:s}'.format(build_config),
'-DBUILDCORE_TARGET={:s}'.format(args.target), '-DBUILDCORE_TARGET={:s}'.format(args.target),
] ]
if args.use_conan != '0':
cmake_cmd.append('-DCMAKE_TOOLCHAIN_FILE={:s}'.format('.conanbuild/conan_toolchain.cmake'))
if qt_path != '': if qt_path != '':
cmake_cmd.append(qt_path) cmake_cmd.append(qt_path)
if platform.system() == 'Windows': if platform.system() == 'Windows':

View File

@ -3,7 +3,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON) set(CMAKE_AUTOUIC ON)
find_package(QT NAMES Qt6 Qt5 COMPONENTS Network Widgets REQUIRED) find_package(QT NAMES Qt6 COMPONENTS Network Widgets REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED) find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED)
add_executable( add_executable(

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,6 +8,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QSettings> #include <QSettings>
#include <string_view>
#include "settingsdata.hpp" #include "settingsdata.hpp"
#include "cameraclient.hpp" #include "cameraclient.hpp"
@ -19,21 +20,80 @@ CameraClient::CameraClient(QObject *parent): QObject(parent) {
connect(m_pollingNam, &QNetworkAccessManager::finished, this, &CameraClient::handlePollResponse); connect(m_pollingNam, &QNetworkAccessManager::finished, this, &CameraClient::handlePollResponse);
} }
void CameraClient::setPreset(int preset) { void CameraClient::setPresetVC(int preset, VideoConfig const&vc) {
if (preset > -1) { if (preset > 0 && preset < MaxCameraPresets) {
get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset)); get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset));
setBrightness(vc.brightness);
setSaturation(vc.saturation);
setContrast(vc.contrast);
setSharpness(vc.sharpness);
setHue(vc.hue);
} }
} }
void CameraClient::setPreset(int preset) {
if (preset > 0 && preset < MaxCameraPresets) {
get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset));
auto const vc = getVideoConfig()[preset - 1];
setBrightness(vc.brightness);
setSaturation(vc.saturation);
setContrast(vc.contrast);
setSharpness(vc.sharpness);
setHue(vc.hue);
}
}
void CameraClient::setBrightness(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&bright&%1").arg(val));
}
}
void CameraClient::setSaturation(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&saturation&%1").arg(val));
}
}
void CameraClient::setContrast(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&contrast&%1").arg(val));
}
}
void CameraClient::setSharpness(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&sharpness&%1").arg(val));
}
}
void CameraClient::setHue(int val) {
if (val > -1) {
get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&hue&%1").arg(val));
}
}
void CameraClient::reboot() {
post("/cgi-bin/param.cgi?post_reboot");
emit pollFailed();
}
void CameraClient::setBaseUrl() { void CameraClient::setBaseUrl() {
const auto [host, port] = getCameraConnectionData(); auto const [host, port] = getCameraConnectionData();
m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port)); m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
} }
void CameraClient::get(QString const&urlExt) { void CameraClient::get(QString const&urlExt) {
QUrl url(QString(m_baseUrl) + urlExt); QUrl url(QString(m_baseUrl) + urlExt);
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
auto reply = m_nam->get(rqst); auto const reply = m_nam->get(rqst);
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
}
void CameraClient::post(QString const&urlExt) {
QUrl url(QString(m_baseUrl) + urlExt);
QNetworkRequest rqst(url);
auto const reply = m_nam->post(rqst, QByteArray{});
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater); connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -25,14 +25,30 @@ class CameraClient: public QObject {
public: public:
explicit CameraClient(QObject *parent = nullptr); explicit CameraClient(QObject *parent = nullptr);
void setPresetVC(int preset, struct VideoConfig const&vc);
void setPreset(int preset); void setPreset(int preset);
void setBrightness(int val);
void setSaturation(int val);
void setContrast(int val);
void setSharpness(int val);
void setHue(int val);
void reboot();
public slots: public slots:
void setBaseUrl(); void setBaseUrl();
private: private:
void get(QString const&url); void get(QString const&url);
void post(QString const&url);
void poll(); void poll();
void handlePollResponse(QNetworkReply *reply); void handlePollResponse(QNetworkReply *reply);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -10,4 +10,5 @@
constexpr auto MaxCameraPresets = 9; constexpr auto MaxCameraPresets = 9;
constexpr auto MaxViews = 9; constexpr auto MaxViews = 9;
constexpr auto Version = "1.0-beta4";

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -22,19 +22,19 @@ MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
setFixedSize(610, 555); setFixedSize(610, 555);
setWindowTitle(tr("Slide Controller 9000")); setWindowTitle(tr("Slide Controller 9000"));
setupMenu(); setupMenu();
const auto mainWidget = new QWidget(this); auto const mainWidget = new QWidget(this);
m_rootLyt = new QVBoxLayout; m_rootLyt = new QVBoxLayout;
const auto controlsLayout = new QGridLayout; auto const controlsLayout = new QGridLayout;
m_slideView = new SlideView(this); m_slideView = new SlideView(this);
setCentralWidget(mainWidget); setCentralWidget(mainWidget);
mainWidget->setLayout(m_rootLyt); mainWidget->setLayout(m_rootLyt);
m_rootLyt->addWidget(m_slideView); m_rootLyt->addWidget(m_slideView);
m_rootLyt->addLayout(controlsLayout); m_rootLyt->addLayout(controlsLayout);
// setup slide controls // setup slide controls
const auto btnPrevSong = new QPushButton(tr("Previous Song"), this); auto const btnPrevSong = new QPushButton(tr("Previous Song"), this);
const auto btnPrevSlide = new QPushButton(tr("Previous Slide"), this); auto const btnPrevSlide = new QPushButton(tr("Previous Slide"), this);
const auto btnNextSlide = new QPushButton(tr("Next Slide"), this); auto const btnNextSlide = new QPushButton(tr("Next Slide"), this);
const auto btnNextSong = new QPushButton(tr("Next Song"), this); auto const btnNextSong = new QPushButton(tr("Next Song"), this);
btnPrevSong->setToolTip(tr("Change to previous song (left arrow key)")); btnPrevSong->setToolTip(tr("Change to previous song (left arrow key)"));
btnPrevSlide->setToolTip(tr("Change to previous slide (up arrow key)")); btnPrevSlide->setToolTip(tr("Change to previous slide (up arrow key)"));
btnNextSong->setToolTip(tr("Change to next song (right arrow key)")); btnNextSong->setToolTip(tr("Change to next song (right arrow key)"));
@ -112,8 +112,8 @@ void MainWindow::setupMenu() {
} }
// camera preset menu // camera preset menu
{ {
auto const menu = menuBar()->addMenu(tr("&Camera Preset")); auto const menu = menuBar()->addMenu(tr("&Camera"));
for (auto i = 0; i < MaxCameraPresets; ++i) { for (auto i = 0; i < std::min(9, MaxCameraPresets); ++i) {
auto const cameraPresetAct = new QAction(tr("Camera Preset &%1").arg(i + 1), this); auto const cameraPresetAct = new QAction(tr("Camera Preset &%1").arg(i + 1), this);
cameraPresetAct->setShortcut(Qt::ALT | static_cast<Qt::Key>(Qt::Key_1 + i)); cameraPresetAct->setShortcut(Qt::ALT | static_cast<Qt::Key>(Qt::Key_1 + i));
connect(cameraPresetAct, &QAction::triggered, &m_cameraClient, [this, i] { connect(cameraPresetAct, &QAction::triggered, &m_cameraClient, [this, i] {
@ -121,6 +121,18 @@ void MainWindow::setupMenu() {
}); });
menu->addAction(cameraPresetAct); menu->addAction(cameraPresetAct);
} }
menu->addSeparator();
auto const rebootAct = new QAction(tr("&Reboot"), this);
connect(rebootAct, &QAction::triggered, &m_cameraClient, [this] {
QMessageBox confirm(this);
confirm.setText(tr("Are you sure you want to reboot the camera? This will take about 20 seconds."));
confirm.addButton(tr("&No"), QMessageBox::ButtonRole::NoRole);
confirm.addButton(tr("&Yes"), QMessageBox::ButtonRole::YesRole);
if (confirm.exec()) {
m_cameraClient.reboot();
}
});
menu->addAction(rebootAct);
} }
// help menu // help menu
{ {
@ -129,39 +141,19 @@ void MainWindow::setupMenu() {
connect(aboutAct, &QAction::triggered, &m_cameraClient, [this] { connect(aboutAct, &QAction::triggered, &m_cameraClient, [this] {
QMessageBox about(this); QMessageBox about(this);
about.setText(tr( about.setText(tr(
R"(Slide Controller 9000 - 1.0-beta1 R"(Slide Controller 9000 - %1
Build date: %1 Build date: %2
Copyright 2021 - 2023 Gary Talent (gary@drinkingtea.net) Copyright 2021 - 2024 Gary Talent (gary@drinkingtea.net)
Slide Controller 9000 is released under the MPL 2.0 Slide Controller 9000 is released under the MPL 2.0
Built on Qt library under LGPL 2.0)").arg(__DATE__)); Built on Qt library under LGPL 2.0)").arg(Version, __DATE__));
about.exec(); about.exec();
}); });
menu->addAction(aboutAct); menu->addAction(aboutAct);
} }
} }
void MainWindow::setupDefaultViewControls(QGridLayout *viewCtlLyt) { void MainWindow::setupViewControlButtons(QVector<View> const&views, QGridLayout *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, 0, 0);
viewCtlLyt->addWidget(btnOpenLpShowSlides, 0, 1);
viewCtlLyt->addWidget(btnShowSlides, 0, 2);
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<View> const&views, QGridLayout *viewCtlLyt) {
constexpr auto columns = 3; constexpr auto columns = 3;
auto const parent = viewCtlLyt->parentWidget(); auto const parent = viewCtlLyt->parentWidget();
for (auto i = 0; auto const&view : views) { for (auto i = 0; auto const&view : views) {
@ -203,8 +195,8 @@ void MainWindow::setupViewControls(QVBoxLayout *rootLyt) {
.obsSlides = false, .obsSlides = false,
}); });
views.emplace_back(View{ views.emplace_back(View{
.name = tr("Hide in OBS"), .name = tr("Show in OpenLP Only"),
.slides = false, .slides = true,
.obsSlides = false, .obsSlides = false,
}); });
views.emplace_back(View{ views.emplace_back(View{
@ -213,15 +205,12 @@ void MainWindow::setupViewControls(QVBoxLayout *rootLyt) {
.obsSlides = false, .obsSlides = false,
}); });
} }
if (views.empty()) { setupViewControlButtons(views, viewCtlLyt);
setupDefaultViewControls(viewCtlLyt);
} else {
setupCustomViewControls(views, viewCtlLyt);
}
} }
void MainWindow::openSettings() { void MainWindow::openSettings() {
SettingsDialog d(this); SettingsDialog d(this);
connect(&d, &SettingsDialog::previewPreset, &m_cameraClient, &CameraClient::setPresetVC);
auto const result = d.exec(); auto const result = d.exec();
if (result == QDialog::Accepted) { if (result == QDialog::Accepted) {
m_cameraClient.setBaseUrl(); m_cameraClient.setBaseUrl();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -40,9 +40,7 @@ class MainWindow: public QMainWindow {
private: private:
void setupMenu(); void setupMenu();
void setupDefaultViewControls(class QGridLayout *rootLyt); void setupViewControlButtons(QVector<View> const&views, class QGridLayout *rootLyt);
void setupCustomViewControls(QVector<View> const&views, class QGridLayout *rootLyt);
void setupViewControls(class QVBoxLayout *rootLyt); void setupViewControls(class QVBoxLayout *rootLyt);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -42,7 +42,7 @@ void OBSClient::setSlidesVisible(bool state) {
} }
void OBSClient::setBaseUrl() { void OBSClient::setBaseUrl() {
const auto [host, port] = getOBSConnectionData(); auto const [host, port] = getOBSConnectionData();
m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port)); m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -28,8 +28,8 @@ OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) {
} }
QString OpenLPClient::getNextSong() { QString OpenLPClient::getNextSong() {
const auto currentSong = m_songNameMap[m_currentSongId]; auto const currentSong = m_songNameMap[m_currentSongId];
const auto songIdx = m_songList.indexOf(currentSong) + 1; auto const songIdx = m_songList.indexOf(currentSong) + 1;
if (songIdx < m_songList.size()) { if (songIdx < m_songList.size()) {
return m_songList[songIdx]; return m_songList[songIdx];
} }
@ -81,7 +81,7 @@ void OpenLPClient::changeSlide(int slide) {
} }
void OpenLPClient::setBaseUrl() { void OpenLPClient::setBaseUrl() {
const auto [host, port] = getOpenLPConnectionData(); auto const [host, port] = getOpenLPConnectionData();
m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port)); m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port));
} }
@ -160,7 +160,7 @@ void OpenLPClient::handleSongListResponse(QNetworkReply *reply) {
auto items = doc.object()["results"].toObject()["items"].toArray(); auto items = doc.object()["results"].toObject()["items"].toArray();
m_songNameMap.clear(); m_songNameMap.clear();
m_songList.clear(); m_songList.clear();
for (const auto &item : items) { for (auto const &item : items) {
auto song = item.toObject(); auto song = item.toObject();
auto name = song["title"].toString(); auto name = song["title"].toString();
auto id = song["id"].toString(); auto id = song["id"].toString();
@ -183,7 +183,7 @@ void OpenLPClient::handleSlideListResponse(QNetworkReply *reply) {
QStringList tagList; QStringList tagList;
auto doc = QJsonDocument::fromJson(data); auto doc = QJsonDocument::fromJson(data);
auto items = doc.object()["results"].toObject()["slides"].toArray(); auto items = doc.object()["results"].toObject()["slides"].toArray();
for (const auto &item : items) { for (auto const &item : items) {
auto slide = item.toObject(); auto slide = item.toObject();
auto text = slide["text"].toString(); auto text = slide["text"].toString();
auto tag = slide["tag"].toString(); auto tag = slide["tag"].toString();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,8 +8,54 @@
#include <QSettings> #include <QSettings>
#include "consts.hpp"
#include "settingsdata.hpp" #include "settingsdata.hpp"
void setVideoConfig(QSettings &settings, QVector<VideoConfig> const&vcList) {
settings.beginGroup("Camera");
settings.beginWriteArray("VideoImageConfig");
for (auto i = 0; auto const&vc : vcList) {
settings.setArrayIndex(i);
settings.setValue("brightness", vc.brightness);
settings.setValue("saturation", vc.saturation);
settings.setValue("contrast", vc.contrast);
settings.setValue("sharpness", vc.sharpness);
settings.setValue("hue", vc.hue);
++i;
}
settings.endArray();
settings.endGroup();
}
void setVideoConfig(QVector<VideoConfig> const&vcList) {
QSettings s;
setVideoConfig(s, vcList);
}
QVector<VideoConfig> getVideoConfig(QSettings &settings) {
QVector<VideoConfig> vc(MaxCameraPresets);
settings.beginGroup("Camera");
auto const size = std::min(settings.beginReadArray("VideoImageConfig"), MaxCameraPresets);
for (auto i = 0; i < size; ++i) {
settings.setArrayIndex(i);
vc[i] = {
.brightness = settings.value("brightness").toInt(),
.saturation = settings.value("saturation").toInt(),
.contrast = settings.value("contrast").toInt(),
.sharpness = settings.value("sharpness").toInt(),
.hue = settings.value("hue").toInt(),
};
}
settings.endArray();
settings.endGroup();
return vc;
}
QVector<VideoConfig> getVideoConfig() {
QSettings s;
return getVideoConfig(s);
}
void setCameraConnectionData(QSettings &settings, ConnectionData const&cd) { void setCameraConnectionData(QSettings &settings, ConnectionData const&cd) {
settings.beginGroup("CameraClient"); settings.beginGroup("CameraClient");
settings.setValue("Host", cd.host); settings.setValue("Host", cd.host);
@ -121,7 +167,7 @@ void setViews(QVector<View> const&views) {
QVector<View> getViews(QSettings &settings) { QVector<View> getViews(QSettings &settings) {
QVector<View> out; QVector<View> out;
settings.beginGroup("Views"); settings.beginGroup("Views");
const auto size = settings.beginReadArray("Views"); auto const size = settings.beginReadArray("Views");
for (auto i = 0; i < size; ++i) { for (auto i = 0; i < size; ++i) {
settings.setArrayIndex(i); settings.setArrayIndex(i);
out.emplace_back(View{ out.emplace_back(View{

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -8,9 +8,26 @@
#pragma once #pragma once
#include <cstdint>
#include <QString> #include <QString>
#include <QVector> #include <QVector>
struct VideoConfig {
int brightness = 6;
int saturation = 4;
int contrast = 8;
int sharpness = 3;
int hue = 7;
};
void setVideoConfig(class QSettings &settings, QVector<VideoConfig> const&vc);
void setVideoConfig(QVector<VideoConfig> const&vc);
QVector<VideoConfig> getVideoConfig(class QSettings &settings);
QVector<VideoConfig> getVideoConfig();
struct ConnectionData { struct ConnectionData {
QString host; QString host;
uint16_t port = 0; uint16_t port = 0;

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -7,6 +7,7 @@
*/ */
#include <QCheckBox> #include <QCheckBox>
#include <QComboBox>
#include <QFormLayout> #include <QFormLayout>
#include <QHeaderView> #include <QHeaderView>
#include <QIntValidator> #include <QIntValidator>
@ -15,6 +16,7 @@
#include <QPushButton> #include <QPushButton>
#include <QSettings> #include <QSettings>
#include <QSpacerItem> #include <QSpacerItem>
#include <QSpinBox>
#include <QTabWidget> #include <QTabWidget>
#include <QTableWidget> #include <QTableWidget>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -31,34 +33,35 @@ enum ViewColumn {
}; };
SettingsDialog::SettingsDialog(QWidget *parent): QDialog(parent) { SettingsDialog::SettingsDialog(QWidget *parent): QDialog(parent) {
const auto lyt = new QVBoxLayout(this); auto const lyt = new QVBoxLayout(this);
const auto tabs = new QTabWidget(this); auto const tabs = new QTabWidget(this);
lyt->addWidget(tabs); lyt->addWidget(tabs);
tabs->addTab(setupViewConfig(tabs), tr("&Views")); tabs->addTab(setupViewConfig(tabs), tr("&Views"));
tabs->addTab(setupImageConfig(tabs), tr("&Image"));
tabs->addTab(setupNetworkInputs(tabs), tr("&Network")); tabs->addTab(setupNetworkInputs(tabs), tr("&Network"));
lyt->addWidget(setupButtons(this)); lyt->addWidget(setupButtons(this));
setFixedSize(440, 440); setFixedSize(440, 440);
} }
QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) { QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) {
const auto root = new QWidget(parent); auto const root = new QWidget(parent);
const auto lyt = new QFormLayout(root); auto const lyt = new QFormLayout(root);
const auto portValidator = new QIntValidator(1, 65536, this); auto const portValidator = new QIntValidator(1, 65536, this);
QSettings settings; QSettings settings;
// camera settings // camera settings
{ {
const auto c = getCameraConnectionData(settings); auto const c = getCameraConnectionData(settings);
m_cameraHostLe = new QLineEdit(root); m_cameraHostLe = new QLineEdit(root);
m_cameraPortLe = new QLineEdit(root); m_cameraPortLe = new QLineEdit(root);
m_cameraHostLe->setText(c.host); m_cameraHostLe->setText(c.host);
m_cameraPortLe->setText(QString::number(c.port)); m_cameraPortLe->setText(QString::number(c.port));
m_cameraPortLe->setValidator(portValidator); m_cameraPortLe->setValidator(portValidator);
lyt->addRow(tr("C&amera Host:"), m_cameraHostLe); lyt->addRow(tr("Camera &Host:"), m_cameraHostLe);
lyt->addRow(tr("Ca&mera Port:"), m_cameraPortLe); lyt->addRow(tr("Ca&mera Port:"), m_cameraPortLe);
} }
// OpenLP settings // OpenLP settings
{ {
const auto c = getOpenLPConnectionData(settings); auto const c = getOpenLPConnectionData(settings);
m_openLpHostLe = new QLineEdit(root); m_openLpHostLe = new QLineEdit(root);
m_openLpPortLe = new QLineEdit(root); m_openLpPortLe = new QLineEdit(root);
m_openLpHostLe->setText(c.host); m_openLpHostLe->setText(c.host);
@ -69,7 +72,7 @@ QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) {
} }
// OBS settings // OBS settings
{ {
const auto c = getOBSConnectionData(settings); auto const c = getOBSConnectionData(settings);
m_obsHostLe = new QLineEdit(root); m_obsHostLe = new QLineEdit(root);
m_obsPortLe = new QLineEdit(root); m_obsPortLe = new QLineEdit(root);
m_obsHostLe->setText(c.host); m_obsHostLe->setText(c.host);
@ -81,13 +84,58 @@ QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) {
return root; return root;
} }
QWidget *SettingsDialog::setupImageConfig(QWidget *parent) {
auto const root = new QWidget(parent);
auto const lyt = new QVBoxLayout(root);
{
auto const formRoot = new QWidget(parent);
auto const formLyt = new QFormLayout(formRoot);
lyt->addWidget(formRoot);
m_videoConfig = getVideoConfig();
auto const mkSb = [parent, formLyt](QString const&lbl) {
auto const s = new QSpinBox(parent);
s->setAlignment(Qt::AlignRight);
s->setRange(0, 14);
formLyt->addRow(lbl, s);
return s;
};
auto const presetNo = new QComboBox(parent);
connect(presetNo, &QComboBox::currentIndexChanged, this, &SettingsDialog::updateVidConfigPreset);
for (auto i = 0; i < MaxCameraPresets; ++i) {
presetNo->addItem(tr("Camera Preset %1").arg(i + 1));
}
formLyt->addRow(presetNo);
m_vidBrightness = mkSb(tr("&Brightness:"));
m_vidSaturation = mkSb(tr("&Saturation:"));
m_vidContrast = mkSb(tr("Con&trast:"));
m_vidSharpness = mkSb(tr("Sharpn&ess:"));
m_vidHue = mkSb(tr("&Hue:"));
updateVidConfigPreset(0);
}
{
auto const btnRoot = new QWidget(parent);
auto const btnLyt = new QHBoxLayout(btnRoot);
lyt->addWidget(btnRoot);
btnLyt->setAlignment(Qt::AlignRight);
auto const previewBtn = new QPushButton(tr("&Preview"), btnRoot);
btnLyt->addWidget(previewBtn);
connect(previewBtn, &QPushButton::clicked, this, [this] {
this->collectVideoConfig();
auto const &vc = m_videoConfig[m_vidCurrentPreset];
emit previewPreset(m_vidCurrentPreset + 1, vc);
});
}
return root;
}
QWidget *SettingsDialog::setupViewConfig(QWidget *parent) { QWidget *SettingsDialog::setupViewConfig(QWidget *parent) {
auto const root = new QWidget(parent); auto const root = new QWidget(parent);
auto const lyt = new QVBoxLayout(root); auto const lyt = new QVBoxLayout(root);
// table auto const btnsRoot = new QWidget(root);
m_viewTable = new QTableWidget(parent); m_viewTable = new QTableWidget(root);
{ lyt->addWidget(btnsRoot);
lyt->addWidget(m_viewTable); lyt->addWidget(m_viewTable);
{ // table
QStringList columns; QStringList columns;
columns.resize(ViewColumn::Count); columns.resize(ViewColumn::Count);
columns[ViewColumn::Name] = tr("Name"); columns[ViewColumn::Name] = tr("Name");
@ -104,19 +152,16 @@ QWidget *SettingsDialog::setupViewConfig(QWidget *parent) {
m_viewTable->setColumnWidth(3, 70); m_viewTable->setColumnWidth(3, 70);
hdr->setStretchLastSection(true); hdr->setStretchLastSection(true);
} }
// add/removes buttons { // add/removes buttons
{
auto const btnsRoot = new QWidget(root);
auto const btnsLyt = new QHBoxLayout(btnsRoot); auto const btnsLyt = new QHBoxLayout(btnsRoot);
auto const addBtn = new QPushButton("+", btnsRoot); auto const addBtn = new QPushButton("A&dd", btnsRoot);
auto const rmBtn = new QPushButton("-", btnsRoot); auto const rmBtn = new QPushButton("&Remove", btnsRoot);
addBtn->setFixedWidth(20); addBtn->setFixedWidth(70);
rmBtn->setFixedWidth(20); rmBtn->setFixedWidth(70);
rmBtn->setDisabled(true); rmBtn->setDisabled(true);
lyt->addWidget(btnsRoot);
btnsLyt->addWidget(addBtn); btnsLyt->addWidget(addBtn);
btnsLyt->addWidget(rmBtn); btnsLyt->addWidget(rmBtn);
btnsLyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); btnsLyt->setAlignment(Qt::AlignLeft);
connect(addBtn, &QPushButton::clicked, this, [this, addBtn] { connect(addBtn, &QPushButton::clicked, this, [this, addBtn] {
auto const row = m_viewTable->rowCount(); auto const row = m_viewTable->rowCount();
m_viewTable->setRowCount(row + 1); m_viewTable->setRowCount(row + 1);
@ -132,7 +177,7 @@ QWidget *SettingsDialog::setupViewConfig(QWidget *parent) {
rmBtn->setEnabled(row > -1 && row < m_viewTable->rowCount()); rmBtn->setEnabled(row > -1 && row < m_viewTable->rowCount());
addBtn->setEnabled(m_viewTable->rowCount() < MaxViews); addBtn->setEnabled(m_viewTable->rowCount() < MaxViews);
}); });
const auto views = getViews(); auto const views = getViews();
m_viewTable->setRowCount(static_cast<int>(views.size())); m_viewTable->setRowCount(static_cast<int>(views.size()));
for (auto row = 0; auto const&view : views) { for (auto row = 0; auto const&view : views) {
setupViewRow(row, view); setupViewRow(row, view);
@ -143,21 +188,24 @@ QWidget *SettingsDialog::setupViewConfig(QWidget *parent) {
} }
QWidget *SettingsDialog::setupButtons(QWidget *parent) { QWidget *SettingsDialog::setupButtons(QWidget *parent) {
const auto root = new QWidget(parent); auto const root = new QWidget(parent);
const auto lyt = new QHBoxLayout(root); auto const lyt = new QHBoxLayout(root);
m_errLbl = new QLabel(root); m_errLbl = new QLabel(root);
const auto okBtn = new QPushButton(tr("&OK"), root); auto const okBtn = new QPushButton(tr("&OK"), root);
const auto cancelBtn = new QPushButton(tr("&Cancel"), root); auto const applyBtn = new QPushButton(tr("&Apply"), root);
auto const cancelBtn = new QPushButton(tr("&Cancel"), root);
lyt->addWidget(m_errLbl); lyt->addWidget(m_errLbl);
lyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); lyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored));
lyt->addWidget(okBtn); lyt->addWidget(okBtn);
lyt->addWidget(applyBtn);
lyt->addWidget(cancelBtn); lyt->addWidget(cancelBtn);
connect(okBtn, &QPushButton::clicked, this, &SettingsDialog::handleOK); connect(okBtn, &QPushButton::clicked, this, &SettingsDialog::handleOK);
connect(applyBtn, &QPushButton::clicked, this, &SettingsDialog::handleApply);
connect(cancelBtn, &QPushButton::clicked, this, &SettingsDialog::reject); connect(cancelBtn, &QPushButton::clicked, this, &SettingsDialog::reject);
return root; return root;
} }
void SettingsDialog::handleOK() { void SettingsDialog::handleApply() {
QSettings settings; QSettings settings;
QVector<View> views; QVector<View> views;
auto const viewsErr = collectViews(views); auto const viewsErr = collectViews(views);
@ -177,23 +225,29 @@ void SettingsDialog::handleOK() {
.host = m_obsHostLe->text(), .host = m_obsHostLe->text(),
.port = m_obsPortLe->text().toUShort(), .port = m_obsPortLe->text().toUShort(),
}); });
collectVideoConfig();
setVideoConfig(settings, m_videoConfig);
}
void SettingsDialog::handleOK() {
handleApply();
accept(); accept();
} }
void SettingsDialog::setupViewRow(int row, View const&view) { void SettingsDialog::setupViewRow(int row, View const&view) {
// name // name
const auto nameItem = new QTableWidgetItem(view.name); auto const nameItem = new QTableWidgetItem(view.name);
m_viewTable->setItem(row, ViewColumn::Name, nameItem); m_viewTable->setItem(row, ViewColumn::Name, nameItem);
// slides // slides
const auto slidesCb = new QCheckBox(m_viewTable); auto const slidesCb = new QCheckBox(m_viewTable);
slidesCb->setChecked(view.slides); slidesCb->setChecked(view.slides);
m_viewTable->setCellWidget(row, ViewColumn::Slides, slidesCb); m_viewTable->setCellWidget(row, ViewColumn::Slides, slidesCb);
// obs slides // obs slides
const auto obsSlidesCb = new QCheckBox(m_viewTable); auto const obsSlidesCb = new QCheckBox(m_viewTable);
obsSlidesCb->setChecked(view.obsSlides); obsSlidesCb->setChecked(view.obsSlides);
m_viewTable->setCellWidget(row, ViewColumn::ObsSlides, obsSlidesCb); m_viewTable->setCellWidget(row, ViewColumn::ObsSlides, obsSlidesCb);
// camera preset // camera preset
const auto presetItem = new QTableWidgetItem(QString::number(view.cameraPreset)); auto const presetItem = new QTableWidgetItem(QString::number(view.cameraPreset));
m_viewTable->setItem(row, ViewColumn::CameraPreset, presetItem); m_viewTable->setItem(row, ViewColumn::CameraPreset, presetItem);
} }
@ -206,7 +260,7 @@ int SettingsDialog::collectViews(QVector<View> &views) const {
m_errLbl->setText(tr("View %1 has no name.").arg(viewNo)); m_errLbl->setText(tr("View %1 has no name.").arg(viewNo));
return 1; return 1;
} }
const auto cameraPreset = m_viewTable->item(row, ViewColumn::CameraPreset)->text().toInt(&ok); auto const cameraPreset = m_viewTable->item(row, ViewColumn::CameraPreset)->text().toInt(&ok);
if (!ok || cameraPreset < 1 || cameraPreset > MaxCameraPresets) { if (!ok || cameraPreset < 1 || cameraPreset > MaxCameraPresets) {
m_errLbl->setText(tr("View %1 has invalid preset (1-%2)").arg(viewNo).arg(MaxCameraPresets)); m_errLbl->setText(tr("View %1 has invalid preset (1-%2)").arg(viewNo).arg(MaxCameraPresets));
return 2; return 2;
@ -220,3 +274,38 @@ int SettingsDialog::collectViews(QVector<View> &views) const {
} }
return 0; return 0;
} }
void SettingsDialog::collectVideoConfig() {
auto &vc = m_videoConfig[m_vidCurrentPreset];
auto constexpr getVal = [](int &val, QSpinBox *src) {
if (src) {
val = src->value();
}
};
getVal(vc.brightness, m_vidBrightness);
getVal(vc.saturation, m_vidSaturation);
getVal(vc.contrast, m_vidContrast);
getVal(vc.sharpness, m_vidSharpness);
getVal(vc.hue, m_vidHue);
}
void SettingsDialog::updateVidConfigPreset(int preset) {
// update to new value
auto constexpr setVal = [](int val, QSpinBox *dst) {
if (dst) {
dst->setValue(val);
}
};
auto const&vc = m_videoConfig[preset];
setVal(vc.brightness, m_vidBrightness);
setVal(vc.saturation, m_vidSaturation);
setVal(vc.contrast, m_vidContrast);
setVal(vc.sharpness, m_vidSharpness);
setVal(vc.hue, m_vidHue);
m_vidCurrentPreset = preset;
}
void SettingsDialog::updateVidConfigPresetCollect(int preset) {
collectVideoConfig();
updateVidConfigPreset(preset);
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -10,11 +10,13 @@
#include <QDialog> #include <QDialog>
#include "consts.hpp"
#include "settingsdata.hpp" #include "settingsdata.hpp"
class SettingsDialog: public QDialog { class SettingsDialog: public QDialog {
Q_OBJECT Q_OBJECT
private: private:
QVector<VideoConfig> m_videoConfig = QVector<VideoConfig>(MaxCameraPresets);
class QLabel *m_errLbl = nullptr; class QLabel *m_errLbl = nullptr;
class QLineEdit *m_cameraHostLe = nullptr; class QLineEdit *m_cameraHostLe = nullptr;
class QLineEdit *m_cameraPortLe = nullptr; class QLineEdit *m_cameraPortLe = nullptr;
@ -22,13 +24,21 @@ class SettingsDialog: public QDialog {
class QLineEdit *m_openLpPortLe = nullptr; class QLineEdit *m_openLpPortLe = nullptr;
class QLineEdit *m_obsHostLe = nullptr; class QLineEdit *m_obsHostLe = nullptr;
class QLineEdit *m_obsPortLe = nullptr; class QLineEdit *m_obsPortLe = nullptr;
class QSpinBox *m_vidBrightness = nullptr;
class QSpinBox *m_vidSaturation = nullptr;
class QSpinBox *m_vidContrast = nullptr;
class QSpinBox *m_vidSharpness = nullptr;
class QSpinBox *m_vidHue = nullptr;
int m_vidCurrentPreset = 0;
class QTableWidget *m_viewTable = nullptr; class QTableWidget *m_viewTable = nullptr;
public: public:
explicit SettingsDialog(QWidget *parent); explicit SettingsDialog(QWidget *parent);
private: private:
QWidget *setupNetworkInputs(QWidget *parent); QWidget *setupNetworkInputs(QWidget *parent);
QWidget *setupViewConfig(QWidget *parent); QWidget *setupViewConfig(QWidget *parent);
QWidget *setupImageConfig(QWidget *parent);
QWidget *setupButtons(QWidget *parent); QWidget *setupButtons(QWidget *parent);
void handleApply();
void handleOK(); void handleOK();
void setupViewRow(int row, View const&view = {}); void setupViewRow(int row, View const&view = {});
/** /**
@ -37,4 +47,9 @@ class SettingsDialog: public QDialog {
*/ */
[[nodiscard("Must check error code")]] [[nodiscard("Must check error code")]]
int collectViews(QVector<View> &views) const; int collectViews(QVector<View> &views) const;
void collectVideoConfig();
void updateVidConfigPreset(int preset);
void updateVidConfigPresetCollect(int preset);
signals:
void previewPreset(int, VideoConfig const&);
}; };

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@ -34,8 +34,8 @@ SlideView::SlideView(QWidget *parent): QWidget(parent) {
} }
QString SlideView::getNextSong() const { QString SlideView::getNextSong() const {
const auto cnt = m_songSelector->count(); auto const cnt = m_songSelector->count();
const auto idx = m_songSelector->currentRow() + 1; auto const idx = m_songSelector->currentRow() + 1;
if (idx < cnt) { if (idx < cnt) {
return m_songSelector->currentItem()->text(); return m_songSelector->currentItem()->text();
} }
@ -72,7 +72,7 @@ void SlideView::slideListUpdate(QStringList const&tagList, QStringList const&sli
m_currentSlide = 0; m_currentSlide = 0;
m_slideTable->setRowCount(static_cast<int>(slideList.size())); m_slideTable->setRowCount(static_cast<int>(slideList.size()));
for (int i = 0; i < slideList.size(); ++i) { for (int i = 0; i < slideList.size(); ++i) {
const auto& txt = slideList[i]; auto const& txt = slideList[i];
auto item = new QTableWidgetItem(txt); auto item = new QTableWidgetItem(txt);
item->setFlags(item->flags() & ~Qt::ItemIsEditable); item->setFlags(item->flags() & ~Qt::ItemIsEditable);
m_slideTable->setItem(i, 0, item); m_slideTable->setItem(i, 0, item);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2021 - 2023 gary@drinkingtea.net * Copyright 2021 - 2024 gary@drinkingtea.net
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this