Compare commits

...

39 Commits

Author SHA1 Message Date
9f57ba69ac Remove camera reboot, bump version to 1.0.0 2025-04-11 22:49:58 -05:00
5afd22a7bf Update copyright year in About 2025-03-29 14:50:54 -05:00
af07f8853a Fix SettingsDialog to do validity check when OK is pressed 2025-03-29 14:45:32 -05:00
2e9b37f3b4 Fix default Show button 2025-03-27 20:32:53 -05:00
c4e792c9a7 Bump version to 1.0-beta6 2025-03-27 20:29:46 -05:00
01cd6b7ba8 Add icon 2025-03-27 20:26:25 -05:00
e42c3a7dc8 Fix 'make run' and 'make debug' 2024-10-30 00:40:31 -05:00
1d66bcc3bb Remove .idea 2024-10-29 23:51:26 -05:00
efab455a24 Cleanup 2024-10-29 23:45:58 -05:00
834a36c417 Fix make run for new buildcore 2024-10-29 23:45:31 -05:00
59aee596e9 Add icon 2024-10-29 23:44:54 -05:00
b96b75a735 [buildcore] Update buildcore 2024-10-29 23:44:45 -05:00
215f9b4d1d Cleanup 2024-10-29 00:46:30 -05:00
4957bec1c6 Cleanup 2024-10-29 00:45:16 -05:00
ef24f01566 Disable Fusion theme on Mac 2024-10-29 00:26:32 -05:00
d0eaad7dfe Remove debug code 2024-10-28 23:59:54 -05:00
fff098d80e Set app organization name 2024-10-28 20:29:42 -05:00
ec18a0c507 Switch Fusion dark theme 2024-10-28 19:46:06 -05:00
88da18a380 Up version 2024-10-27 14:49:16 -05:00
0db9bff0de Split slide tag across lines 2024-10-25 00:44:58 -05:00
a0a1cd8af1 Upgrade OpenLPClient to target OpenLP 3 2024-10-23 22:22:53 -05:00
f4e0b5ab9f Update year in copyright notices 2024-03-08 22:07:39 -06:00
bb825f947e Update version 2024-03-03 21:25:14 -06:00
b71a64ca33 Add Apply button to settings dialog 2024-03-03 21:21:00 -06:00
048b06db97 Fix Image Preview button to use the correct image settings 2024-03-03 21:14:14 -06:00
344cc4f819 Up version in about menu to 1.0-beta3 2023-10-15 17:09:04 -05:00
ae580a0d58 Fix Reboot dialog for Qt6 2023-10-15 16:47:18 -05:00
ac7bb9c585 Add camera reboot option 2023-10-15 16:02:24 -05:00
56f98eed60 Update buildcore 2023-08-07 21:55:55 -05:00
9171a080a7 Make project require Qt6 2023-08-05 18:07:43 -05:00
8a4989923c Add preview button to Image tab in settings 2023-07-28 23:46:21 -05:00
77df4257c5 Remove some commented out code 2023-07-24 23:36:56 -05:00
87997e8f18 Change mnemonic in Contrast from 'o' to 't' 2023-07-24 22:41:14 -05:00
8bdad3ed68 Remove qDebug 2023-07-24 22:32:58 -05:00
b7aba1180d Up version to 1.0-beta2 2023-07-24 21:53:39 -05:00
ed01f44b91 Make Image tab in setting the second tab 2023-07-24 21:50:25 -05:00
b119268396 Add video image settings per camera preset 2023-07-24 21:46:41 -05:00
bc342a290f Switch autos to east const 2023-07-21 01:19:07 -05:00
814cef4a2e Add README 2023-07-21 00:11:16 -05:00
35 changed files with 655 additions and 282 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@
.clangd .clangd
.conanbuild .conanbuild
.current_build .current_build
.idea
__pycache__ __pycache__
CMakeLists.txt.user CMakeLists.txt.user
Session.vim Session.vim

7
.idea/misc.xml generated
View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="CompDBWorkspace">
<contentRoot DIR="$PROJECT_DIR$" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

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

View File

@ -14,6 +14,10 @@ if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion")
endif() endif()
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../") set(CMAKE_INSTALL_RPATH "$ORIGIN" "$ORIGIN/../")
if(QTDIR) if(QTDIR)
set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib") set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_RPATH} "${QTDIR}/lib")

View File

@ -3,15 +3,15 @@ BUILDCORE_PATH=deps/buildcore
VCPKG_PKGS= VCPKG_PKGS=
include ${BUILDCORE_PATH}/base.mk include ${BUILDCORE_PATH}/base.mk
ifeq ($(OS),darwin) ifeq ($(BC_VAR_OS),darwin)
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/${PROJECT_NAME}.app/Contents/MacOS/SlideController PROJECT_EXECUTABLE=./build/${BC_VAR_CURRENT_BUILD}/bin/${PROJECT_NAME}.app/Contents/MacOS/SlideController
else else
PROJECT_EXECUTABLE=./dist/${CURRENT_BUILD}/bin/SlideController PROJECT_EXECUTABLE=./build/${BC_VAR_CURRENT_BUILD}/bin/${PROJECT_NAME}
endif endif
.PHONY: run .PHONY: run
run: install run: build
${ENV_RUN} ${PROJECT_EXECUTABLE} ${ENV_RUN} ${PROJECT_EXECUTABLE}
.PHONY: debug .PHONY: debug
debug: install debug: build
${DEBUGGER} ${PROJECT_EXECUTABLE} ${DEBUGGER} ${PROJECT_EXECUTABLE}

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

196
deps/buildcore/base.mk vendored
View File

@ -9,89 +9,103 @@
ifeq (${OS},Windows_NT) ifeq (${OS},Windows_NT)
SHELL := powershell.exe SHELL := powershell.exe
.SHELLFLAGS := -NoProfile -Command .SHELLFLAGS := -NoProfile -Command
OS=windows BC_VAR_OS=windows
HOST_ENV=${OS} BC_CMD_HOST_PY3=python
else else
OS=$(shell uname | tr [:upper:] [:lower:]) BC_VAR_OS=$(shell uname | tr [:upper:] [:lower:])
HOST_ENV=${OS}-$(shell uname -m) ifneq ($(shell which python3 2> /dev/null),)
endif BC_CMD_HOST_PY3=python3
else
DEVENV=devenv$(shell pwd | sed 's/\//-/g') ifeq ($(shell python -c 'import sys; print(sys.version_info[0])'),3)
DEVENV_IMAGE=${PROJECT_NAME}-devenv BC_CMD_HOST_PY3=python
ifneq ($(shell which docker 2> /dev/null),) else
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running) echo 'Please install Python3 on host'
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV} exit 1
endif
endif endif
endif endif
ifneq ($(shell ${ENV_RUN} which python3 2> /dev/null),)
PYTHON3=python3 ifdef BC_VAR_USE_DOCKER_DEVENV
else ifneq ($(shell which docker 2> /dev/null),)
ifeq ($(shell ${ENV_RUN} python -c 'import sys; print(sys.version_info[0])'),3) BC_VAR_DEVENV=devenv$(shell pwd | sed 's/\//-/g')
PYTHON3=python BC_VAR_DEVENV_IMAGE=${BC_VAR_PROJECT_NAME}-devenv
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${BC_VAR_DEVENV} 2>&1),running)
BC_CMD_ENVRUN=docker exec -i -t --user $(shell id -u ${USER}) ${BC_VAR_DEVENV}
endif
endif endif
ifneq ($(shell ${BC_CMD_ENVRUN} which python3 2> /dev/null),)
BC_CMD_PY3=${BC_CMD_ENVRUN} python3
else
ifeq ($(shell ${BC_CMD_ENVRUN} python -c 'import sys; print(sys.version_info[0])'),3)
BC_CMD_PY3=${BC_CMD_ENVRUN} python
else
echo 'Please install Python3 in devenv'
exit 1
endif
endif
ifndef BC_VAR_DEVENV_ROOT
BC_VAR_DEVENV_ROOT="."
endif
else
BC_CMD_PY3=${BC_CMD_HOST_PY3}
endif endif
SCRIPTS=${BUILDCORE_PATH}/scripts BC_VAR_SCRIPTS=${BUILDCORE_PATH}/scripts
SETUP_BUILD=${PYTHON3} ${SCRIPTS}/setup-build.py BC_CMD_SETUP_BUILD=${BC_CMD_PY3} ${BC_VAR_SCRIPTS}/setup-build.py
PYBB=${PYTHON3} ${SCRIPTS}/pybb.py BC_CMD_PYBB=${BC_CMD_PY3} ${BC_VAR_SCRIPTS}/pybb.py
CMAKE_BUILD=${PYBB} cmake-build BC_CMD_HOST_PYBB=${BC_CMD_HOST_PY3} ${BC_VAR_SCRIPTS}/pybb.py
GET_ENV=${PYBB} getenv BC_CMD_CMAKE_BUILD=${BC_CMD_PYBB} cmake-build
CTEST=${PYBB} ctest-all BC_CMD_GETENV=${BC_CMD_PYBB} getenv
RM_RF=${PYBB} rm BC_CMD_CTEST=${BC_CMD_PYBB} ctest-all
HOST=$(shell ${PYBB} hostname) BC_CMD_RM_RF=${BC_CMD_PYBB} rm
BUILDCORE_HOST_SPECIFIC_BUILDPATH=$(shell ${GET_ENV} BUILDCORE_HOST_SPECIFIC_BUILDPATH) BC_CMD_MKDIR_P=${BC_CMD_PYBB} mkdir
ifneq (${BUILDCORE_HOST_SPECIFIC_BUILDPATH},) BC_CMD_CAT=${BC_CMD_PYBB} cat
BUILD_PATH=build/${HOST} BC_CMD_DEBUGGER=${BC_CMD_PYBB} debug
else BC_CMD_HOST_DEBUGGER=${BC_CMD_HOST_PYBB} debug
BUILD_PATH=build BC_VAR_HOSTENV=$(shell ${BC_CMD_ENVRUN} ${BC_CMD_PYBB} hostenv)
endif BC_VAR_BUILD_PATH=build
ifdef USE_VCPKG BC_VAR_CURRENT_BUILD=$(BC_VAR_HOSTENV)-$(shell ${BC_CMD_ENVRUN} ${BC_CMD_CAT} .current_build)
ifndef VCPKG_DIR_BASE
VCPKG_DIR_BASE=.vcpkg
endif
ifndef VCPKG_VERSION
VCPKG_VERSION=2020.06
endif
VCPKG_TOOLCHAIN=--toolchain=${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
endif
ifeq ($(OS),darwin)
DEBUGGER=lldb --
else
DEBUGGER=gdb --args
endif
VCPKG_DIR=$(VCPKG_DIR_BASE)/$(VCPKG_VERSION)-$(HOST_ENV) ifdef BC_VAR_USE_VCPKG
CURRENT_BUILD=$(HOST_ENV)-$(shell ${ENV_RUN} ${PYBB} cat .current_build) ifndef BC_VAR_VCPKG_DIR_BASE
BC_VAR_VCPKG_DIR_BASE=.vcpkg
endif
ifndef BC_VAR_VCPKG_VERSION
BC_VAR_VCPKG_VERSION=2023.08.09
endif
endif
.PHONY: build .PHONY: build
build: build:
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH}
.PHONY: install .PHONY: install
install: install:
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} install ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} install
.PHONY: clean .PHONY: clean
clean: clean:
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} clean ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} clean
.PHONY: purge .PHONY: purge
purge: purge:
${ENV_RUN} ${RM_RF} .current_build ${BC_CMD_RM_RF} .current_build
${ENV_RUN} ${RM_RF} ${BUILD_PATH} ${BC_CMD_RM_RF} ${BC_VAR_BUILD_PATH}
${ENV_RUN} ${RM_RF} dist ${BC_CMD_RM_RF} dist
${BC_CMD_RM_RF} compile_commands.json
.PHONY: test .PHONY: test
test: build test: build
${ENV_RUN} mypy ${SCRIPTS} ${BC_CMD_ENVRUN} mypy ${BC_VAR_SCRIPTS}
${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} test ${BC_CMD_CMAKE_BUILD} ${BC_VAR_BUILD_PATH} test
.PHONY: test-verbose .PHONY: test-verbose
test-verbose: build test-verbose: build
${ENV_RUN} ${CTEST} ${BUILD_PATH} --output-on-failure ${BC_CMD_CTEST} ${BC_VAR_BUILD_PATH} --output-on-failure
.PHONY: test-rerun-verbose .PHONY: test-rerun-verbose
test-rerun-verbose: build test-rerun-verbose: build
${ENV_RUN} ${CTEST} ${BUILD_PATH} --rerun-failed --output-on-failure ${BC_CMD_CTEST} ${BC_VAR_BUILD_PATH} --rerun-failed --output-on-failure
ifdef BC_VAR_USE_DOCKER_DEVENV
.PHONY: devenv-image .PHONY: devenv-image
devenv-image: devenv-image:
docker build . -t ${DEVENV_IMAGE} docker build ${BC_VAR_DEVENV_ROOT} -t ${BC_VAR_DEVENV_IMAGE}
.PHONY: devenv-create .PHONY: devenv-create
devenv-create: devenv-create:
docker run -d \ docker run -d \
@ -103,73 +117,77 @@ devenv-create:
-v $(shell pwd):/usr/src/project \ -v $(shell pwd):/usr/src/project \
-v /dev/shm:/dev/shm \ -v /dev/shm:/dev/shm \
--restart=always \ --restart=always \
--name ${DEVENV} \ --name ${BC_VAR_DEVENV} \
-t ${DEVENV_IMAGE} bash -t ${BC_VAR_DEVENV_IMAGE} bash
.PHONY: devenv-destroy .PHONY: devenv-destroy
devenv-destroy: devenv-destroy:
docker rm -f ${DEVENV} docker rm -f ${BC_VAR_DEVENV}
ifdef ENV_RUN ifdef BC_CMD_ENVRUN
.PHONY: devenv-shell .PHONY: devenv-shell
devenv-shell: devenv-shell:
${ENV_RUN} bash ${BC_CMD_ENVRUN} bash
endif
endif endif
ifdef USE_VCPKG ifdef BC_VAR_USE_VCPKG
BC_VAR_VCPKG_TOOLCHAIN=--toolchain=${BC_VAR_VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
BC_VAR_VCPKG_DIR=$(BC_VAR_VCPKG_DIR_BASE)/$(BC_VAR_VCPKG_VERSION)-$(BC_VAR_HOSTENV)
.PHONY: vcpkg .PHONY: vcpkg
vcpkg: ${VCPKG_DIR} vcpkg-install vcpkg: ${BC_VAR_VCPKG_DIR} vcpkg-install
${VCPKG_DIR}: ${BC_VAR_VCPKG_DIR}:
${ENV_RUN} ${RM_RF} ${VCPKG_DIR} ${BC_CMD_RM_RF} ${BC_VAR_VCPKG_DIR}
${ENV_RUN} mkdir -p ${VCPKG_DIR_BASE} ${BC_CMD_PYBB} mkdir ${BC_VAR_VCPKG_DIR_BASE}
${ENV_RUN} git clone -b release --depth 1 --branch ${VCPKG_VERSION} https://github.com/microsoft/vcpkg.git ${VCPKG_DIR} ${BC_CMD_ENVRUN} git clone -b release --depth 1 --branch ${BC_VAR_VCPKG_VERSION} https://github.com/microsoft/vcpkg.git ${BC_VAR_VCPKG_DIR}
ifneq (${OS},windows) ifneq (${BC_VAR_OS},windows)
${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.sh ${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/bootstrap-vcpkg.sh
else else
${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.bat ${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/bootstrap-vcpkg.bat
endif endif
.PHONY: vcpkg-install .PHONY: vcpkg-install
vcpkg-install: vcpkg-install:
ifneq (${OS},windows) ifneq (${BC_VAR_OS},windows)
${VCPKG_DIR}/vcpkg install ${VCPKG_PKGS} ${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/vcpkg install ${BC_VAR_VCPKG_PKGS}
else else
${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS} ${BC_CMD_ENVRUN} ${BC_VAR_VCPKG_DIR}/vcpkg install --triplet x64-windows ${BC_VAR_VCPKG_PKGS}
endif endif
else ifdef USE_CONAN # USE_VCPKG ################################################ else ifdef USE_CONAN # USE_VCPKG / USE_CONAN ####################################
.PHONY: setup-conan .PHONY: setup-conan
conan-config: conan-config:
${ENV_RUN} conan profile new ${PROJECT_NAME} --detect --force ${BC_CMD_ENVRUN} conan profile new ${BC_VAR_PROJECT_NAME} --detect --force
ifeq ($(OS),linux) ifeq ($(BC_VAR_OS),linux)
${ENV_RUN} conan profile update settings.compiler.libcxx=libstdc++11 ${PROJECT_NAME} ${BC_CMD_ENVRUN} conan profile update settings.compiler.libcxx=libstdc++11 ${BC_VAR_PROJECT_NAME}
else else
${ENV_RUN} conan profile update settings.compiler.cppstd=20 ${PROJECT_NAME} ${BC_CMD_ENVRUN} conan profile update settings.compiler.cppstd=20 ${BC_VAR_PROJECT_NAME}
ifeq ($(OS),windows) ifeq ($(BC_VAR_OS),windows)
${ENV_RUN} conan profile update settings.compiler.runtime=static ${PROJECT_NAME} ${BC_CMD_ENVRUN} conan profile update settings.compiler.runtime=static ${BC_VAR_PROJECT_NAME}
endif endif
endif endif
.PHONY: conan .PHONY: conan
conan: conan:
${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME} ${BC_CMD_PYBB} conan-install ${BC_VAR_PROJECT_NAME}
endif # USE_VCPKG ############################################### endif # USE_CONAN ###############################################
ifeq (${OS},darwin) ifeq (${BC_VAR_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} ${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 --build_root=${BC_VAR_BUILD_PATH}
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} ${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_type=release --build_root=${BC_VAR_BUILD_PATH}
.PHONY: configure-debug .PHONY: configure-debug
configure-debug: configure-debug:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BUILD_PATH} ${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BC_VAR_BUILD_PATH}
.PHONY: configure-asan .PHONY: configure-asan
configure-asan: configure-asan:
${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BUILD_PATH} ${BC_CMD_SETUP_BUILD} ${BC_VAR_VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BC_VAR_BUILD_PATH}

28
deps/buildcore/scripts/file_to_c.py vendored Normal file
View File

@ -0,0 +1,28 @@
#! /usr/bin/env python3
#
# Copyright 2016 - 2022 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/.
#
import argparse
import sys
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--out-cpp', help='path to output cpp file')
parser.add_argument('--out-hpp', help='path to output hpp file')
args = parser.parse_args()
return 0
if __name__ == '__main__':
try:
err = main()
sys.exit(err)
except KeyboardInterrupt:
sys.exit(1)

View File

@ -18,18 +18,20 @@ import subprocess
import sys import sys
from typing import List, Optional from typing import List, Optional
import util
def mkdir(path: str):
if not os.path.exists(path):
os.mkdir(path)
# this exists because Windows is utterly incapable of providing a proper rm -rf def mkdir(path: str) -> int:
def rm(path: str): try:
if (os.path.exists(path) or os.path.islink(path)) and not os.path.isdir(path): util.mkdir_p(path)
os.remove(path) except Exception:
elif os.path.isdir(path): return 1
shutil.rmtree(path) return 0
def rm_multi(paths: List[str]):
for path in paths:
util.rm(path)
def ctest_all() -> int: def ctest_all() -> int:
@ -70,16 +72,13 @@ def conan() -> int:
err = 0 err = 0
try: try:
mkdir(conan_dir) mkdir(conan_dir)
except: except Exception:
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', '../', '--build=missing', '-pr', project_name]
os.chdir(conan_dir) os.chdir(conan_dir)
err = subprocess.run(args).returncode return subprocess.run(args).returncode
if err != 0:
return err
return 0
def cat(paths: List[str]) -> int: def cat(paths: List[str]) -> int:
@ -87,48 +86,70 @@ def cat(paths: List[str]) -> int:
try: try:
with open(path) as f: with open(path) as f:
data = f.read() data = f.read()
sys.stdout.write(data) print(data)
except FileNotFoundError: except FileNotFoundError:
sys.stderr.write('cat: {}: no such file or directory\n'.format(path)) sys.stderr.write(f'cat: {path}: no such file or directory\n')
return 1 return 1
sys.stdout.write('\n')
return 0 return 0
def debug(paths: List[str]) -> int:
if shutil.which('gdb') is not None:
args = ['gdb', '--args']
elif shutil.which('lldb') is not None:
args = ['lldb', '--']
else:
sys.stderr.write('debug: could not find a supported debugger\n')
return 1
args.extend(paths)
return subprocess.run(args).returncode
def get_env(var_name: str) -> int: def get_env(var_name: str) -> int:
if var_name not in os.environ: if var_name not in os.environ:
return 1 return 1
sys.stdout.write(os.environ[var_name]) print(os.environ[var_name])
return 0 return 0
def hostname() -> int: def hostname() -> int:
sys.stdout.write(platform.node()) print(platform.node())
return 0 return 0
def host_env() -> int:
os_name = platform.system().lower()
arch = util.get_arch()
print(f'{os_name}-{arch}')
return 0
def clarg(idx: int) -> Optional[str]:
return sys.argv[idx] if len(sys.argv) > idx else None
def main() -> int: def main() -> int:
err = 0 err = 0
if sys.argv[1] == 'mkdir': if sys.argv[1] == 'mkdir':
try: err = mkdir(sys.argv[2])
mkdir(sys.argv[2])
except:
err = 1
elif sys.argv[1] == 'rm': elif sys.argv[1] == 'rm':
for i in range(2, len(sys.argv)): rm_multi(sys.argv[2:])
rm(sys.argv[i])
elif sys.argv[1] == 'conan-install': elif sys.argv[1] == 'conan-install':
err = conan() err = conan()
elif sys.argv[1] == 'ctest-all': elif sys.argv[1] == 'ctest-all':
err = ctest_all() err = ctest_all()
elif sys.argv[1] == 'cmake-build': elif sys.argv[1] == 'cmake-build':
err = cmake_build(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None) err = cmake_build(sys.argv[2], clarg(3))
elif sys.argv[1] == 'cat': elif sys.argv[1] == 'cat':
err = cat(sys.argv[2:]) err = cat(sys.argv[2:])
elif sys.argv[1] == 'debug':
err = debug(sys.argv[2:])
elif sys.argv[1] == 'getenv': elif sys.argv[1] == 'getenv':
err = get_env(sys.argv[2]) err = get_env(sys.argv[2])
elif sys.argv[1] == 'hostname': elif sys.argv[1] == 'hostname':
err = hostname() err = hostname()
elif sys.argv[1] == 'hostenv':
err = host_env()
else: else:
sys.stderr.write('Command not found\n') sys.stderr.write('Command not found\n')
err = 1 err = 1

View File

@ -15,18 +15,35 @@ import shutil
import subprocess import subprocess
import sys import sys
from pybb import mkdir, rm import util
def main() -> int: def main() -> int:
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('--target', help='Platform target', parser.add_argument(
default='{:s}-{:s}'.format(sys.platform, platform.machine())) '--target',
parser.add_argument('--build_type', help='Build type (asan,debug,release)', default='release') help='Platform target',
parser.add_argument('--build_tool', help='Build tool (default,xcode)', default='') default=f'{util.get_os()}-{util.get_arch()}')
parser.add_argument('--build_root', help='Path to the root of build directories (must be in project dir)', default='build') parser.add_argument(
parser.add_argument('--toolchain', help='Path to CMake toolchain file', default='') '--build_type',
parser.add_argument('--current_build', help='Indicates whether or not to make this the active build', default=1) help='Build type (asan,debug,release)',
default='release')
parser.add_argument(
'--build_tool',
help='Build tool (default,xcode)',
default='')
parser.add_argument(
'--build_root',
help='Path to the root build directory (must be in project dir)',
default='build')
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)
args = parser.parse_args() args = parser.parse_args()
if args.build_type == 'asan': if args.build_type == 'asan':
@ -64,10 +81,10 @@ def main() -> int:
return 1 return 1
project_dir = os.getcwd() project_dir = os.getcwd()
build_dir = '{:s}/{:s}/{:s}'.format(project_dir, args.build_root, build_config) build_dir = f'{project_dir}/{args.build_root}/{build_config}'
rm(build_dir) util.rm(build_dir)
cmake_cmd = [ cmake_cmd = [
'cmake', '-S', project_dir, '-B', build_dir, build_tool, 'cmake', '-S', project_dir, '-B', build_dir,
'-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON',
'-DCMAKE_TOOLCHAIN_FILE={:s}'.format(args.toolchain), '-DCMAKE_TOOLCHAIN_FILE={:s}'.format(args.toolchain),
'-DCMAKE_BUILD_TYPE={:s}'.format(build_type_arg), '-DCMAKE_BUILD_TYPE={:s}'.format(build_type_arg),
@ -75,22 +92,27 @@ 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 build_tool != '':
cmake_cmd.append(build_tool)
if qt_path != '': if qt_path != '':
cmake_cmd.append(qt_path) cmake_cmd.append(qt_path)
if platform.system() == 'Windows': if platform.system() == 'Windows' and platform.system() == 'AMD64':
cmake_cmd.append('-A x64') cmake_cmd.append('-A x64')
subprocess.run(cmake_cmd) cmake_err = subprocess.run(cmake_cmd).returncode
if cmake_err != 0:
return cmake_err
mkdir('dist') util.mkdir_p('dist')
if int(args.current_build) != 0: if int(args.current_build) != 0:
cb = open('.current_build', 'w') cb = open('.current_build', 'w')
cb.write(args.build_type) cb.write(args.build_type)
cb.close() cb.close()
rm('compile_commands.json') util.rm('compile_commands.json')
if platform.system() != 'Windows': if platform.system() != 'Windows':
os.symlink('{:s}/compile_commands.json'.format(build_dir), 'compile_commands.json') os.symlink(f'{build_dir}/compile_commands.json',
'compile_commands.json')
return 0 return 0

38
deps/buildcore/scripts/util.py vendored Normal file
View File

@ -0,0 +1,38 @@
#
# Copyright 2016 - 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/.
#
import os
import platform
import shutil
def mkdir_p(path: str):
if not os.path.exists(path):
os.mkdir(path)
# this exists because Windows is utterly incapable of providing a proper rm -rf
def rm(path: str):
file_exists = os.path.exists(path)
is_link = os.path.islink(path)
is_dir = os.path.isdir(path)
if (file_exists or is_link) and not is_dir:
os.remove(path)
elif os.path.isdir(path):
shutil.rmtree(path)
def get_os() -> str:
return platform.system().lower()
def get_arch() -> str:
arch = platform.machine().lower()
if arch == 'amd64':
arch = 'x86_64'
return arch

BIN
iconsrc/icon-16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
iconsrc/icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
iconsrc/icon-32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

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(
@ -16,6 +16,7 @@ add_executable(
settingsdata.cpp settingsdata.cpp
settingsdialog.cpp settingsdialog.cpp
slideview.cpp slideview.cpp
sc9k.rc
) )
target_link_libraries( target_link_libraries(

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
@ -9,6 +9,7 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QSettings> #include <QSettings>
#include "consts.hpp"
#include "settingsdata.hpp" #include "settingsdata.hpp"
#include "cameraclient.hpp" #include "cameraclient.hpp"
@ -19,27 +20,85 @@ 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::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::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::get(QString const&urlExt) { void CameraClient::get(QString const&urlExt) {
QUrl url(QString(m_baseUrl) + urlExt); QUrl const 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) {
QNetworkRequest const rqst{QUrl{QString{m_baseUrl} + urlExt}};
auto const reply = m_nam->post(rqst, QByteArray{});
connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater); connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater);
} }
void CameraClient::poll() { void CameraClient::poll() {
QUrl url(QString(m_baseUrl) + "/cgi-bin/param.cgi?get_device_conf"); QUrl const url{QString{m_baseUrl} + "/cgi-bin/param.cgi?get_device_conf"};
QNetworkRequest rqst(url); QNetworkRequest const rqst{url};
m_pollingNam->get(rqst); m_pollingNam->get(rqst);
} }

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
@ -12,8 +12,6 @@
#include <QObject> #include <QObject>
#include <QTimer> #include <QTimer>
#include "consts.hpp"
class CameraClient: public QObject { class CameraClient: public QObject {
Q_OBJECT Q_OBJECT
private: private:
@ -25,14 +23,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 reboot();
public slots: public slots:
void setBaseUrl(); void setBaseUrl();
private: private:
void setBrightness(int val);
void setSaturation(int val);
void setContrast(int val);
void setSharpness(int val);
void setHue(int val);
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.0";

BIN
src/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

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
@ -13,8 +13,12 @@
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
QSettings::setDefaultFormat(QSettings::Format::IniFormat); QSettings::setDefaultFormat(QSettings::Format::IniFormat);
#ifndef __APPLE__
QApplication::setStyle("Fusion");
#endif
QApplication a(argc, argv); QApplication a(argc, argv);
QApplication::setApplicationName(QObject::tr("Slide Controller 9000")); QApplication::setOrganizationName("DrinkingTea");
QApplication::setApplicationName("Slide Controller 9000");
MainWindow w; MainWindow w;
w.show(); w.show();
return QApplication::exec(); return QApplication::exec();

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] {
@ -129,39 +129,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 - 2025 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,25 +183,22 @@ 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{
.name = tr("Show"), .name = tr("Show"),
.slides = false, .slides = true,
.obsSlides = false, .obsSlides = true,
}); });
} }
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
@ -8,8 +8,6 @@
#pragma once #pragma once
#include <cstdint>
#include <QMainWindow> #include <QMainWindow>
#include "cameraclient.hpp" #include "cameraclient.hpp"
@ -40,9 +38,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,11 +1,12 @@
/* /*
* 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
#include <QHttpPart>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
@ -28,8 +29,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];
} }
@ -37,19 +38,19 @@ QString OpenLPClient::getNextSong() {
} }
void OpenLPClient::nextSlide() { void OpenLPClient::nextSlide() {
get("/api/controller/live/next"); post("/api/v2/controller/progress", R"({"action":"next"})");
} }
void OpenLPClient::prevSlide() { void OpenLPClient::prevSlide() {
get("/api/controller/live/previous"); post("/api/v2/controller/progress", R"({"action":"previous"})");
} }
void OpenLPClient::nextSong() { void OpenLPClient::nextSong() {
get("/api/service/next"); post("/api/v2/service/progress", R"({"action":"next"})");
} }
void OpenLPClient::prevSong() { void OpenLPClient::prevSong() {
get("/api/service/previous"); post("/api/v2/service/progress", R"({"action":"previous"})");
} }
void OpenLPClient::blankScreen() { void OpenLPClient::blankScreen() {
@ -81,7 +82,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));
} }
@ -91,6 +92,12 @@ void OpenLPClient::get(QString const&urlExt) {
m_nam->get(rqst); m_nam->get(rqst);
} }
void OpenLPClient::post(QString const&url, QString const&data) {
QNetworkRequest rqst(QUrl(m_baseUrl + url));
rqst.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
m_nam->post(rqst, data.toUtf8());
}
void OpenLPClient::requestSongList() { void OpenLPClient::requestSongList() {
QUrl url(m_baseUrl + "/api/service/list?_=1626628079579"); QUrl url(m_baseUrl + "/api/service/list?_=1626628079579");
QNetworkRequest rqst(url); QNetworkRequest rqst(url);
@ -160,7 +167,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,12 +190,12 @@ 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 const slide = item.toObject();
auto text = slide["text"].toString(); auto text = slide["text"].toString();
auto tag = slide["tag"].toString(); auto tag = slide["tag"].toString();
slideList.push_back(text); slideList.push_back(std::move(text));
tagList.push_back(tag); tagList.push_back(std::move(tag));
} }
emit slideListUpdate(tagList, slideList); emit slideListUpdate(tagList, slideList);
} }

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
@ -60,6 +60,8 @@ class OpenLPClient: public QObject {
private: private:
void get(QString const&url); void get(QString const&url);
void post(QString const&url, QString const&data);
void requestSongList(); void requestSongList();
void requestSlideList(); void requestSlideList();

BIN
src/sc9k.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

1
src/sc9k.rc Normal file
View File

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "sc9k.ico"

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,26 +188,29 @@ 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() { int SettingsDialog::handleApply() {
QSettings settings; QSettings settings;
QVector<View> views; QVector<View> views;
auto const viewsErr = collectViews(views); auto const viewsErr = collectViews(views);
if (viewsErr) { if (viewsErr) {
return; return -1;
} }
setViews(settings, views); setViews(settings, views);
setCameraConnectionData(settings, { setCameraConnectionData(settings, {
@ -177,23 +225,31 @@ void SettingsDialog::handleOK() {
.host = m_obsHostLe->text(), .host = m_obsHostLe->text(),
.port = m_obsPortLe->text().toUShort(), .port = m_obsPortLe->text().toUShort(),
}); });
accept(); collectVideoConfig();
setVideoConfig(settings, m_videoConfig);
return 0;
}
void SettingsDialog::handleOK() {
if (handleApply() == 0) {
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 +262,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 +276,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,22 @@ 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);
[[nodiscard]]
int handleApply();
void handleOK(); void handleOK();
void setupViewRow(int row, View const&view = {}); void setupViewRow(int row, View const&view = {});
/** /**
@ -37,4 +48,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();
} }
@ -43,7 +43,7 @@ QString SlideView::getNextSong() const {
} }
void SlideView::pollUpdate(QString const&songName, int slide) { void SlideView::pollUpdate(QString const&songName, int slide) {
auto songItems = m_songSelector->findItems(songName, Qt::MatchFixedString); auto const songItems = m_songSelector->findItems(songName, Qt::MatchFixedString);
if (songItems.empty()) { if (songItems.empty()) {
return; return;
} }
@ -68,11 +68,14 @@ void SlideView::changeSong(int song) {
} }
} }
void SlideView::slideListUpdate(QStringList const&tagList, QStringList const&slideList) { void SlideView::slideListUpdate(QStringList tagList, QStringList const&slideList) {
for (auto &tag : tagList) {
tag = tag.split("").join("\n");
}
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
@ -28,7 +28,7 @@ class SlideView: public QWidget {
void songListUpdate(QStringList const&songList); void songListUpdate(QStringList const&songList);
void slideListUpdate(QStringList const&tagList, QStringList const&songList); void slideListUpdate(QStringList tagList, QStringList const&songList);
void reset(); void reset();