16 Commits

Author SHA1 Message Date
7bc42bf01e [sc9k] Fix signal/slot disconnect in SlideView::songListUpdate 2022-07-31 08:48:13 -05:00
9f29b58522 [sc9k] Fix to SlideView to use correct signal from QListWidget 2022-07-24 09:23:07 -05:00
564ed77c9a [sc9k] Swap song view and slide view, fix item lookup in song view 2022-07-24 08:56:17 -05:00
7ca3b810fc [sc9k] Make song selector a QListWidget instead of QComboBox 2022-07-24 00:35:11 -05:00
68d963ab69 [buildcore] Update buildcore 2022-07-24 00:34:52 -05:00
6f6f77f104 Get rid of "in Both" language 2022-01-30 21:45:03 -06:00
e57ba9e8f2 Eliminate now redundant tool tip for Show in Both button 2022-01-30 21:18:03 -06:00
382e09d4b4 Collapse slide controls into one line 2022-01-30 19:43:19 -06:00
181e1b8599 Combine Show in OpenLP and Hide in OBS buttons 2022-01-30 19:22:50 -06:00
3115083267 Fix show/hide button order and naming 2022-01-30 19:10:06 -06:00
2d5af03724 Update button layout and keyboard shortcut 2022-01-30 18:59:11 -06:00
c1cab3e3f3 [buildcore] Update buildcore 2022-01-30 18:56:46 -06:00
8d0b0fb4c5 Fix next song in status bar 2021-10-24 16:01:23 -05:00
f9122c2942 Fix typo n Next Song button 2021-10-24 16:01:23 -05:00
b0eeb81592 Add next song to status line 2021-10-24 16:01:23 -05:00
7999cc486f Fix OBS general GET reply handling 2021-10-24 16:01:23 -05:00
12 changed files with 217 additions and 98 deletions

View File

@ -11,7 +11,7 @@ set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/dist/${BUILDCORE_BUILD_CONFIG}")
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_CXX_STANDARD 17) 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
@ -26,9 +26,14 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
add_definitions(-DDEBUG) add_definitions(-DDEBUG)
else() else()
add_definitions(-DNDEBUG) add_definitions(-DNDEBUG)
if(APPLE)
set(CMAKE_OSX_ARCHITECTURES arm64;x86_64)
endif()
endif() endif()
if(NOT MSVC) if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:preprocessor")
else()
# forces colored output when using ninja # forces colored output when using ninja
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color")
# enable warnings # enable warnings
@ -39,7 +44,7 @@ if(NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-field-initializers") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-field-initializers")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnull-dereference") #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnull-dereference")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic")

View File

@ -16,16 +16,27 @@ else
HOST_ENV=${OS}-$(shell uname -m) HOST_ENV=${OS}-$(shell uname -m)
endif endif
ifeq ($(shell python -c 'import sys; print(sys.version_info[0])'),3) DEVENV=devenv$(shell pwd | sed 's/\//-/g')
PYTHON3=python DEVENV_IMAGE=${PROJECT_NAME}-devenv
else ifneq ($(shell which docker 2> /dev/null),)
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
endif
endif
ifneq ($(shell which python3 2> /dev/null),)
PYTHON3=python3 PYTHON3=python3
else
ifeq ($(shell ${ENV_RUN} python -c 'import sys; print(sys.version_info[0])'),3)
PYTHON3=python
endif
endif endif
SCRIPTS=${BUILDCORE_PATH}/scripts SCRIPTS=${BUILDCORE_PATH}/scripts
SETUP_BUILD=${PYTHON3} ${SCRIPTS}/setup-build.py SETUP_BUILD=${PYTHON3} ${SCRIPTS}/setup-build.py
PYBB=${PYTHON3} ${SCRIPTS}/pybb.py PYBB=${PYTHON3} ${SCRIPTS}/pybb.py
CMAKE_BUILD=${PYBB} cmake-build CMAKE_BUILD=${PYBB} cmake-build
CTEST=${PYBB} ctest-all
RM_RF=${PYBB} rm RM_RF=${PYBB} rm
ifdef USE_VCPKG ifdef USE_VCPKG
ifndef VCPKG_DIR_BASE ifndef VCPKG_DIR_BASE
@ -37,19 +48,12 @@ ifdef USE_VCPKG
VCPKG_TOOLCHAIN=--toolchain=${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake VCPKG_TOOLCHAIN=--toolchain=${VCPKG_DIR}/scripts/buildsystems/vcpkg.cmake
endif endif
ifeq ($(OS),darwin) ifeq ($(OS),darwin)
DEBUGGER=lldb DEBUGGER=lldb --
else else
DEBUGGER=gdb --args DEBUGGER=gdb --args
endif endif
VCPKG_DIR=$(VCPKG_DIR_BASE)/$(VCPKG_VERSION)-$(HOST_ENV) VCPKG_DIR=$(VCPKG_DIR_BASE)/$(VCPKG_VERSION)-$(HOST_ENV)
DEVENV=devenv$(shell pwd | sed 's/\//-/g')
DEVENV_IMAGE=${PROJECT_NAME}-devenv
ifneq ($(shell which docker 2> /dev/null),)
ifeq ($(shell docker inspect --format="{{.State.Status}}" ${DEVENV} 2>&1),running)
ENV_RUN=docker exec -i -t --user $(shell id -u ${USER}) ${DEVENV}
endif
endif
CURRENT_BUILD=$(HOST_ENV)-$(shell ${PYBB} cat .current_build) CURRENT_BUILD=$(HOST_ENV)-$(shell ${PYBB} cat .current_build)
.PHONY: build .PHONY: build
@ -69,6 +73,12 @@ purge:
.PHONY: test .PHONY: test
test: build test: build
${ENV_RUN} ${CMAKE_BUILD} build test ${ENV_RUN} ${CMAKE_BUILD} build test
.PHONY: test-verbose
test-verbose: build
${ENV_RUN} ${CTEST} build --output-on-failure
.PHONY: test-rerun-verbose
test-rerun-verbose: build
${ENV_RUN} ${CTEST} build --rerun-failed --output-on-failure
.PHONY: devenv-image .PHONY: devenv-image
devenv-image: devenv-image:
@ -89,11 +99,14 @@ devenv-create:
.PHONY: devenv-destroy .PHONY: devenv-destroy
devenv-destroy: devenv-destroy:
docker rm -f ${DEVENV} docker rm -f ${DEVENV}
ifdef ENV_RUN
.PHONY: devenv-shell .PHONY: devenv-shell
devenv-shell: devenv-shell:
${ENV_RUN} bash ${ENV_RUN} bash
endif
ifdef USE_VCPKG ifdef USE_VCPKG
.PHONY: vcpkg .PHONY: vcpkg
vcpkg: ${VCPKG_DIR} vcpkg-install vcpkg: ${VCPKG_DIR} vcpkg-install
@ -114,18 +127,24 @@ ifneq (${OS},windows)
else else
${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS} ${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS}
endif endif
else # USE_VCPKG
else ifdef USE_CONAN # USE_VCPKG ################################################
.PHONY: setup-conan .PHONY: setup-conan
conan-config: conan-config:
conan profile new nostalgia --detect --force ${ENV_RUN} conan profile new ${PROJECT_NAME} --detect --force
ifeq ($(OS),linux) ifeq ($(OS),linux)
conan profile update settings.compiler.libcxx=libstdc++11 ${PROJECT_NAME} ${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 endif
.PHONY: conan .PHONY: conan
conan: conan:
@mkdir -p .conanbuild && cd .conanbuild && conan install ../ --build=missing -pr=${PROJECT_NAME} ${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME}
endif # USE_VCPKG endif # USE_VCPKG ###############################################
.PHONY: configure-xcode .PHONY: configure-xcode
configure-xcode: configure-xcode:

View File

@ -8,7 +8,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
# #
# "Python Busy Box" - adds cross platform equivalents to Unix commands that # "Python Busy Box" - adds cross-platform equivalents to Unix commands that
# don't translate well to that other operating system # don't translate well to that other operating system
import os import os
@ -17,31 +17,56 @@ import subprocess
import sys import sys
def cat(path): def cat(paths: [str]) -> int:
for path in paths:
try: try:
with open(path) as f: with open(path) as f:
data = f.read() data = f.read()
print(data) sys.stdout.write(data)
return 0
except FileNotFoundError: except FileNotFoundError:
sys.stderr.write('cat: {}: no such file or directory\n'.format(path)) sys.stderr.write('cat: {}: no such file or directory\n'.format(path))
return 1 return 1
sys.stdout.write('\n')
return 0
def mkdir(path): def mkdir(path: str) -> int:
if not os.path.exists(path) and os.path.isdir(path): if not os.path.exists(path):
try:
os.mkdir(path) os.mkdir(path)
except:
return 1
return 0
if os.path.isdir(path):
return 0
return 1
# this exists because Windows is utterly incapable of providing a proper rm -rf # this exists because Windows is utterly incapable of providing a proper rm -rf
def rm(path): def rm(path: str) -> int:
if (os.path.exists(path) or os.path.islink(path)) and not os.path.isdir(path): if (os.path.exists(path) or os.path.islink(path)) and not os.path.isdir(path):
os.remove(path) os.remove(path)
elif os.path.isdir(path): elif os.path.isdir(path):
shutil.rmtree(path) shutil.rmtree(path)
return 0
def cmake_build(base_path, target): def ctest_all() -> int:
base_path = sys.argv[2]
if not os.path.isdir(base_path):
# no generated projects
return 0
args = ['ctest'] + sys.argv[3:]
orig_dir = os.getcwd()
for d in os.listdir(base_path):
os.chdir(os.path.join(orig_dir, base_path, d))
err = subprocess.run(args).returncode
if err != 0:
return err
return 0
def cmake_build(base_path: str, target: str) -> int:
if not os.path.isdir(base_path): if not os.path.isdir(base_path):
# nothing to build # nothing to build
return 0 return 0
@ -52,24 +77,47 @@ def cmake_build(base_path, target):
err = subprocess.run(args).returncode err = subprocess.run(args).returncode
if err != 0: if err != 0:
return err return err
return 0
def conan() -> int:
project_name = sys.argv[2]
conan_dir = '.conanbuild'
err = mkdir(conan_dir)
if err != 0:
return err
args = ['conan', 'install', '../', '--build=missing', '-pr', project_name]
os.chdir(conan_dir)
err = subprocess.run(args).returncode
if err != 0:
return err
return 0
def main(): def main():
err = 0
if sys.argv[1] == 'mkdir': if sys.argv[1] == 'mkdir':
mkdir(sys.argv[2]) err = mkdir(sys.argv[2])
elif sys.argv[1] == 'rm': elif sys.argv[1] == 'rm':
for i in range(2, len(sys.argv)): for i in range(2, len(sys.argv)):
rm(sys.argv[i]) rm(sys.argv[i])
elif sys.argv[1] == 'conan-install':
err = conan()
elif sys.argv[1] == '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], sys.argv[3] if len(sys.argv) > 3 else None)
sys.exit(err)
elif sys.argv[1] == 'cat': elif sys.argv[1] == 'cat':
err = cat(sys.argv[2]) err = cat(sys.argv[2:])
sys.exit(err) else:
sys.stderr.write('Command not found\n')
err = 1
return err
if __name__ == '__main__': if __name__ == '__main__':
try: try:
main() err = main()
sys.exit(err)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(1) sys.exit(1)

View File

@ -66,15 +66,21 @@ def main():
build_dir = '{:s}/build/{:s}'.format(project_dir, build_config) build_dir = '{:s}/build/{:s}'.format(project_dir, build_config)
rm(build_dir) rm(build_dir)
mkdir(build_dir) mkdir(build_dir)
subprocess.run(['cmake', '-S', project_dir, '-B', build_dir, build_tool, cmake_cmd = [
'cmake', '-S', project_dir, '-B', build_dir, build_tool,
'-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),
'-DUSE_ASAN={:s}'.format(sanitizer_status), '-DUSE_ASAN={:s}'.format(sanitizer_status),
'-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),
qt_path, ]
]) if qt_path != '':
cmake_cmd.append(qt_path)
if platform.system() == 'Windows':
cmake_cmd.append('-A x64')
subprocess.run(cmake_cmd)
mkdir('dist') mkdir('dist')
if int(args.current_build) != 0: if int(args.current_build) != 0:

View File

@ -3,6 +3,7 @@ from urllib.parse import urlparse, parse_qs
import threading import threading
import obspython as obs import obspython as obs
def set_current_scene(scene_name): def set_current_scene(scene_name):
scenes = obs.obs_frontend_get_scenes() scenes = obs.obs_frontend_get_scenes()
for scene in scenes: for scene in scenes:
@ -12,6 +13,7 @@ def set_current_scene(scene_name):
return 0 return 0
return 1 return 1
class RqstHandler(BaseHTTPRequestHandler): class RqstHandler(BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
@ -25,12 +27,15 @@ class RqstHandler(BaseHTTPRequestHandler):
self.send_response(200) self.send_response(200)
self.end_headers() self.end_headers()
def log_message(self, format, *args): def log_message(self, format, *args):
pass pass
def run(name): def run(name):
httpd = HTTPServer(('127.0.0.1', 9302), RqstHandler) httpd = HTTPServer(('127.0.0.1', 9302), RqstHandler)
httpd.serve_forever() httpd.serve_forever()
t = threading.Thread(target=run, args=(1,), daemon=True) t = threading.Thread(target=run, args=(1,), daemon=True)
t.start() t.start()

View File

@ -6,9 +6,7 @@
* 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 <QFormLayout>
#include <QHBoxLayout> #include <QHBoxLayout>
#include <QLineEdit>
#include <QPushButton> #include <QPushButton>
#include <QStatusBar> #include <QStatusBar>
@ -22,57 +20,56 @@ MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) {
const auto mainWidget = new QWidget(this); const auto mainWidget = new QWidget(this);
const auto rootLyt = new QVBoxLayout; const auto rootLyt = new QVBoxLayout;
const auto controlsLayout = new QGridLayout; const auto controlsLayout = new QGridLayout;
const auto slideView = new SlideView(this); m_slideView = new SlideView(this);
setCentralWidget(mainWidget); setCentralWidget(mainWidget);
mainWidget->setLayout(rootLyt); mainWidget->setLayout(rootLyt);
rootLyt->addWidget(slideView); rootLyt->addWidget(m_slideView);
rootLyt->addLayout(controlsLayout); rootLyt->addLayout(controlsLayout);
// setup slide controls // setup slide controls
const auto showHideLyt = new QHBoxLayout;
rootLyt->addLayout(showHideLyt);
const auto btnPrevSong = new QPushButton(tr("Previous Song (Left)"), this); const auto btnPrevSong = new QPushButton(tr("Previous Song (Left)"), this);
const auto btnPrevSlide = new QPushButton(tr("Previous Slide (Up)"), this); const auto btnPrevSlide = new QPushButton(tr("Previous Slide (Up)"), this);
const auto btnNextSlide = new QPushButton(tr("Next Slide (Down)"), this); const auto btnNextSlide = new QPushButton(tr("Next Slide (Down)"), this);
const auto btnNextSong = new QPushButton(tr("Next Song (Right))"), this); const auto btnNextSong = new QPushButton(tr("Next Song (Right)"), this);
const auto btnBlankSlides = new QPushButton(tr("Blank Slides (,)"), this); const auto btnHideSlides = new QPushButton(tr("Hide (1)"), this);
const auto btnShowSlides = new QPushButton(tr("Show Slides (.)"), this); const auto btnOpenLpShowSlides = new QPushButton(tr("Show in OpenLP Only (2)"), this);
controlsLayout->addWidget(btnPrevSlide, 0, 0); const auto btnShowSlides = new QPushButton(tr("Show (3)"), mainWidget);
controlsLayout->addWidget(btnNextSlide, 0, 1); controlsLayout->addWidget(btnPrevSlide, 0, 1);
controlsLayout->addWidget(btnPrevSong, 1, 0); controlsLayout->addWidget(btnNextSlide, 0, 2);
controlsLayout->addWidget(btnNextSong, 1, 1); controlsLayout->addWidget(btnPrevSong, 0, 0);
controlsLayout->addWidget(btnBlankSlides, 2, 0); controlsLayout->addWidget(btnNextSong, 0, 3);
controlsLayout->addWidget(btnShowSlides, 2, 1); showHideLyt->addWidget(btnHideSlides);
showHideLyt->addWidget(btnOpenLpShowSlides);
showHideLyt->addWidget(btnShowSlides);
btnNextSong->setShortcut(Qt::Key_Right); btnNextSong->setShortcut(Qt::Key_Right);
btnPrevSong->setShortcut(Qt::Key_Left); btnPrevSong->setShortcut(Qt::Key_Left);
btnNextSlide->setShortcut(Qt::Key_Down); btnNextSlide->setShortcut(Qt::Key_Down);
btnPrevSlide->setShortcut(Qt::Key_Up); btnPrevSlide->setShortcut(Qt::Key_Up);
btnBlankSlides->setShortcut(Qt::Key_Comma); btnHideSlides->setShortcut(Qt::Key_1);
btnShowSlides->setShortcut(Qt::Key_Period); btnOpenLpShowSlides->setShortcut(Qt::Key_2);
btnBlankSlides->setToolTip(tr("Also hides slides in OBS")); btnHideSlides->setToolTip(tr("Also hides slides in OBS"));
btnShowSlides->setShortcut(Qt::Key_3);
connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide); connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide);
connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide); connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide);
connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong); connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong);
connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong); connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong);
connect(btnBlankSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen); connect(btnHideSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen);
connect(btnBlankSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides); connect(btnHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
connect(btnShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides); connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, slideView, &SlideView::pollUpdate); connect(&m_openlpClient, &OpenLPClient::pollUpdate, m_slideView, &SlideView::pollUpdate);
connect(&m_openlpClient, &OpenLPClient::songListUpdate, slideView, &SlideView::songListUpdate); connect(&m_openlpClient, &OpenLPClient::songListUpdate, m_slideView, &SlideView::songListUpdate);
connect(&m_openlpClient, &OpenLPClient::slideListUpdate, slideView, &SlideView::slideListUpdate); connect(&m_openlpClient, &OpenLPClient::slideListUpdate, m_slideView, &SlideView::slideListUpdate);
connect(&m_openlpClient, &OpenLPClient::pollFailed, slideView, &SlideView::reset); connect(&m_openlpClient, &OpenLPClient::pollFailed, m_slideView, &SlideView::reset);
connect(slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong); connect(m_slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong);
connect(slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide); connect(m_slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide);
// setup scene selector // setup scene selector
const auto btnObsHideSlides = new QPushButton(tr("Hide Slides in OBS (;)"), mainWidget); connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
const auto btnObsShowSlides = new QPushButton(tr("Show Slides in OBS (')"), mainWidget);
controlsLayout->addWidget(btnObsHideSlides, 3, 0);
controlsLayout->addWidget(btnObsShowSlides, 3, 1);
btnObsHideSlides->setShortcut(Qt::Key_Semicolon);
btnObsShowSlides->setShortcut(Qt::Key_Apostrophe);
btnObsShowSlides->setToolTip(tr("Also shows slides in OpenLP"));
connect(btnObsHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides);
connect(btnObsShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides);
connect(btnObsShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides);
// setup status bar // setup status bar
setStatusBar(new QStatusBar(this)); setStatusBar(new QStatusBar(this));
connect(&m_openlpClient, &OpenLPClient::songChanged, this, &MainWindow::refreshStatusBar);
connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit); connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit);
connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit); connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit);
refreshStatusBar(); refreshStatusBar();
@ -109,5 +106,7 @@ void MainWindow::obsConnectionLost() {
void MainWindow::refreshStatusBar() { void MainWindow::refreshStatusBar() {
const auto openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected"); const auto openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected");
const auto obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected"); const auto obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected");
statusBar()->showMessage(openLpStatus + " | " + obsStatus); const auto nextSong = m_openlpClient.getNextSong();
const auto nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : "";
statusBar()->showMessage(openLpStatus + " | " + obsStatus + nextSongTxt);
} }

View File

@ -21,6 +21,7 @@ class MainWindow: public QMainWindow {
private: private:
OBSClient m_obsClient; OBSClient m_obsClient;
OpenLPClient m_openlpClient; OpenLPClient m_openlpClient;
class SlideView *m_slideView = nullptr;
bool m_openLpConnected = false; bool m_openLpConnected = false;
bool m_obsConnected = false; bool m_obsConnected = false;

View File

@ -5,6 +5,7 @@
* 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 <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QUrl> #include <QUrl>

View File

@ -24,6 +24,15 @@ OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) {
connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OpenLPClient::handlePollResponse); connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OpenLPClient::handlePollResponse);
} }
QString OpenLPClient::getNextSong() {
const auto currentSong = m_songNameMap[m_currentSongId];
const auto songIdx = m_songList.indexOf(currentSong) + 1;
if (songIdx < m_songList.size()) {
return m_songList[songIdx];
}
return "";
}
void OpenLPClient::nextSlide() { void OpenLPClient::nextSlide() {
get("/api/controller/live/next"); get("/api/controller/live/next");
} }
@ -117,6 +126,7 @@ void OpenLPClient::handlePollResponse(QNetworkReply *reply) {
if (m_currentSongId != songId) { if (m_currentSongId != songId) {
requestSlideList(); requestSlideList();
m_currentSongId = songId; m_currentSongId = songId;
emit songChanged(songId);
} }
emit pollUpdate(m_songNameMap[songId], slide); emit pollUpdate(m_songNameMap[songId], slide);
} }
@ -130,18 +140,18 @@ void OpenLPClient::handleSongListResponse(QNetworkReply *reply) {
if (data.isEmpty()) { if (data.isEmpty()) {
return; return;
} }
QStringList songList;
auto doc = QJsonDocument::fromJson(data); auto doc = QJsonDocument::fromJson(data);
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();
for (const auto &item : items) { for (const auto &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();
m_songNameMap[id] = name; m_songNameMap[id] = name;
songList.push_back(name); m_songList.push_back(name);
} }
emit songListUpdate(songList); emit songListUpdate(m_songList);
} }
void OpenLPClient::handleSlideListResponse(QNetworkReply *reply) { void OpenLPClient::handleSlideListResponse(QNetworkReply *reply) {

View File

@ -30,12 +30,16 @@ class OpenLPClient: public QObject {
QNetworkAccessManager *m_slideListNam = new QNetworkAccessManager(this); QNetworkAccessManager *m_slideListNam = new QNetworkAccessManager(this);
QTimer m_pollTimer; QTimer m_pollTimer;
QHash<QString, QString> m_songNameMap; QHash<QString, QString> m_songNameMap;
QStringList m_songList;
int m_currentServiceId = -1; int m_currentServiceId = -1;
QString m_currentSongId; QString m_currentSongId;
public: public:
explicit OpenLPClient(QObject *parent = nullptr); explicit OpenLPClient(QObject *parent = nullptr);
[[nodiscard]]
QString getNextSong();
public slots: public slots:
void nextSlide(); void nextSlide();
@ -80,5 +84,6 @@ class OpenLPClient: public QObject {
void slideListUpdate(QStringList, QStringList); void slideListUpdate(QStringList, QStringList);
void songChanged(QString);
}; };

View File

@ -8,15 +8,16 @@
#include <QComboBox> #include <QComboBox>
#include <QDebug> #include <QDebug>
#include <QHBoxLayout>
#include <QHeaderView> #include <QHeaderView>
#include <QListWidget>
#include <QTableWidget> #include <QTableWidget>
#include <QVBoxLayout>
#include "slideview.hpp" #include "slideview.hpp"
SlideView::SlideView(QWidget *parent): QWidget(parent) { SlideView::SlideView(QWidget *parent): QWidget(parent) {
auto lyt = new QVBoxLayout(this); auto lyt = new QHBoxLayout(this);
m_songSelector = new QComboBox(this); m_songSelector = new QListWidget(this);
m_slideTable = new QTableWidget(this); m_slideTable = new QTableWidget(this);
auto header = m_slideTable->horizontalHeader(); auto header = m_slideTable->horizontalHeader();
header->setVisible(false); header->setVisible(false);
@ -28,15 +29,29 @@ SlideView::SlideView(QWidget *parent): QWidget(parent) {
#ifndef _WIN32 #ifndef _WIN32
m_slideTable->setAlternatingRowColors(true); m_slideTable->setAlternatingRowColors(true);
#endif #endif
lyt->addWidget(m_songSelector);
lyt->addWidget(m_slideTable); lyt->addWidget(m_slideTable);
lyt->addWidget(m_songSelector);
connect(m_slideTable, &QTableWidget::currentCellChanged, this, &SlideView::slideChanged); connect(m_slideTable, &QTableWidget::currentCellChanged, this, &SlideView::slideChanged);
} }
QString SlideView::getNextSong() const {
const auto cnt = m_songSelector->count();
const auto idx = m_songSelector->currentRow() + 1;
if (idx < cnt) {
return m_songSelector->currentItem()->text();
}
return "";
}
void SlideView::pollUpdate(QString songName, int slide) { void SlideView::pollUpdate(QString songName, int slide) {
if (songName != m_currentSong) { auto songItems = m_songSelector->findItems(songName, Qt::MatchFixedString);
if (songItems.size() < 1) {
return;
}
auto songItem = songItems.first();
if (songItem != m_songSelector->currentItem()) {
m_currentSong = songName; m_currentSong = songName;
m_songSelector->setCurrentText(songName); m_songSelector->setCurrentItem(songItem);
} }
if (slide != m_currentSlide) { if (slide != m_currentSlide) {
m_currentSlide = slide; m_currentSlide = slide;
@ -45,7 +60,8 @@ void SlideView::pollUpdate(QString songName, int slide) {
} }
void SlideView::changeSong(int song) { void SlideView::changeSong(int song) {
if (m_songSelector->currentText() != m_currentSong) { auto songItem = m_songSelector->item(song);
if (songItem->text() != m_currentSong) {
emit songChanged(song); emit songChanged(song);
} }
} }
@ -75,11 +91,11 @@ void SlideView::songListUpdate(QStringList songList) {
// We want to reset the song to 0 upon replacement, // We want to reset the song to 0 upon replacement,
// but leave it alone upon initialization. // but leave it alone upon initialization.
auto isReplacement = m_songSelector->count() > 0; auto isReplacement = m_songSelector->count() > 0;
disconnect(m_songSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSong(int))); disconnect(m_songSelector, &QListWidget::currentRowChanged, this, &SlideView::changeSong);
m_songSelector->clear(); m_songSelector->clear();
m_songSelector->addItems(songList); m_songSelector->addItems(songList);
if (isReplacement) { if (isReplacement) {
changeSong(0); changeSong(0);
} }
connect(m_songSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(changeSong(int))); connect(m_songSelector, &QListWidget::currentRowChanged, this, &SlideView::changeSong);
} }

View File

@ -13,12 +13,16 @@ class SlideView: public QWidget {
Q_OBJECT Q_OBJECT
private: private:
class QTableWidget *m_slideTable = nullptr; class QTableWidget *m_slideTable = nullptr;
class QComboBox *m_songSelector = nullptr; class QListWidget *m_songSelector = nullptr;
QString m_currentSong; QString m_currentSong;
int m_currentSlide = -1; int m_currentSlide = -1;
public: public:
explicit SlideView(QWidget *parent = nullptr); explicit SlideView(QWidget *parent = nullptr);
[[nodiscard]]
QString getNextSong() const;
public slots: public slots:
void pollUpdate(QString songId, int slideNum); void pollUpdate(QString songId, int slideNum);