mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 12:45:28 -05:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd69ca1b35 | ||
|
|
66ddf3b4ef | ||
|
|
c9b5bbfc14 | ||
|
|
3e1e8d01b6 | ||
|
|
c22f40e333 | ||
|
|
b83acaca79 | ||
|
|
53bef18d15 | ||
|
|
ced1c7605a | ||
|
|
f78e91eec4 | ||
|
|
7fdff72a92 | ||
|
|
d2d042bae3 | ||
|
|
f70ae803a3 | ||
|
|
cdefbf383c | ||
|
|
5f8641b18a | ||
|
|
d107f653de | ||
|
|
22f4c11ee3 | ||
|
|
7683957267 | ||
|
|
e0d4c7244d | ||
|
|
b2ccd30f68 | ||
|
|
1dea2e5f5d | ||
|
|
c95fb6c2ea | ||
|
|
d779ced27f | ||
|
|
c84c1638bf | ||
|
|
3ac4e22600 | ||
|
|
a59d15a77a | ||
|
|
c2edc7c612 | ||
|
|
77fe75f738 | ||
|
|
fdb70a32c6 | ||
|
|
487db7302e | ||
|
|
12a74caf62 | ||
|
|
742996fc83 | ||
|
|
f5a0b2e91a | ||
|
|
114ca7b545 | ||
|
|
a8953f23f4 | ||
|
|
106be642aa | ||
|
|
4c20ac605d | ||
|
|
75cdd3c80d | ||
|
|
b2585ae176 | ||
|
|
d1e912cb24 | ||
|
|
bd5ecb3b3b | ||
|
|
1a8a4584c1 | ||
|
|
6d755ad570 | ||
|
|
99ceb6a42a | ||
|
|
7ddfdcf5be | ||
|
|
a243e026c8 | ||
|
|
f40f98c702 | ||
|
|
d99618d2f6 | ||
|
|
a93305e16e | ||
|
|
fa3ae607b5 | ||
|
|
bddbcebece | ||
|
|
aeaa13955c | ||
|
|
f99eb6c3c8 | ||
|
|
c16b97e5cd | ||
|
|
0d29fb2ee1 | ||
|
|
0169f6c445 | ||
|
|
886639fe24 | ||
|
|
7304fa349e |
8
.github/actions/build-plugin/action.yml
vendored
8
.github/actions/build-plugin/action.yml
vendored
@@ -20,6 +20,10 @@ inputs:
|
||||
description: 'Visual Studio version (Windows only)'
|
||||
required: false
|
||||
default: 'Visual Studio 16 2019'
|
||||
portable:
|
||||
description: 'Set portable mode (Linux only)'
|
||||
required: false
|
||||
default: 'false'
|
||||
workingDirectory:
|
||||
description: 'Working directory for packaging'
|
||||
required: false
|
||||
@@ -56,6 +60,10 @@ runs:
|
||||
build_args+=(--debug)
|
||||
fi
|
||||
|
||||
if [[ '${{ inputs.portable }}' == 'true' ]]; then
|
||||
build_args+=(-p)
|
||||
fi
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/build-linux.sh "${build_args[@]}"
|
||||
|
||||
- name: Run Windows Build
|
||||
|
||||
8
.github/actions/package-plugin/action.yml
vendored
8
.github/actions/package-plugin/action.yml
vendored
@@ -36,6 +36,10 @@ inputs:
|
||||
description: 'Create InnoSetup installer (Windows only)'
|
||||
required: false
|
||||
default: 'false'
|
||||
portable:
|
||||
description: 'Create deb package / portable archive (Linux only)'
|
||||
required: false
|
||||
default: 'false'
|
||||
workingDirectory:
|
||||
description: 'Working directory for packaging'
|
||||
required: false
|
||||
@@ -75,6 +79,10 @@ runs:
|
||||
build_args+=(--debug)
|
||||
fi
|
||||
|
||||
if [[ '${{ inputs.portable }}' == 'true' ]]; then
|
||||
package_args+=(-z)
|
||||
fi
|
||||
|
||||
${{ inputs.workingDirectory }}/.github/scripts/package-linux.sh "${package_args[@]}"
|
||||
|
||||
- name: Run Windows packaging
|
||||
|
||||
8
.github/scripts/.build.zsh
vendored
8
.github/scripts/.build.zsh
vendored
@@ -76,6 +76,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
%B-t | --target%b Specify target - default: %B%F{green}${host_os}-${CPUTYPE}%f%b
|
||||
%B-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
|
||||
%B-s | --codesign%b Enable codesigning (macOS only)
|
||||
%B-p | --portable%b Enable portable mode (Linux only)
|
||||
%B--generator%b Specify build system to generate - default: %B%F{green}Ninja%f%b
|
||||
Available generators:
|
||||
- Ninja
|
||||
@@ -130,6 +131,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
shift 2
|
||||
;;
|
||||
-s|--codesign) CODESIGN=1; shift ;;
|
||||
-p|--portable) typeset -g PORTABLE=1; shift ;;
|
||||
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
|
||||
-v|--verbose) (( _verbosity += 1 )); shift ;;
|
||||
-h|--help) log_output ${_usage}; exit 0 ;;
|
||||
@@ -245,7 +247,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
|
||||
;;
|
||||
linux-*)
|
||||
if (( ${+CI} )) {
|
||||
if (( ${+PORTABLE} )) {
|
||||
cmake_args+=(
|
||||
-DLINUX_PORTABLE=ON
|
||||
)
|
||||
} else {
|
||||
cmake_args+=(
|
||||
-DCMAKE_INSTALL_PREFIX=/usr
|
||||
-DLINUX_PORTABLE=OFF
|
||||
|
||||
21
.github/scripts/.package.zsh
vendored
21
.github/scripts/.package.zsh
vendored
@@ -61,6 +61,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
%B-c | --config%b Build configuration - default: %B%F{green}RelWithDebInfo%f%b
|
||||
%B-s | --codesign%b Enable codesigning (macOS only)
|
||||
%B-n | --notarize%b Enable notarization (macOS only)
|
||||
%B-z | --zip%b Zip only (Linux only)
|
||||
|
||||
%F{yellow} Output options%f
|
||||
-----------------------------------------------------------------------------
|
||||
@@ -110,6 +111,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
;;
|
||||
-s|--codesign) typeset -g CODESIGN=1; shift ;;
|
||||
-n|--notarize) typeset -g NOTARIZE=1; typeset -g CODESIGN=1; shift ;;
|
||||
-z|--zip) typeset -g ZIP=1; shift ;;
|
||||
-q|--quiet) (( _verbosity -= 1 )) || true; shift ;;
|
||||
-v|--verbose) (( _verbosity += 1 )); shift ;;
|
||||
-h|--help) log_output ${_usage}; exit 0 ;;
|
||||
@@ -132,7 +134,7 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
if [[ ${host_os} == 'macos' ]] {
|
||||
autoload -Uz check_packages read_codesign read_codesign_installer read_codesign_pass
|
||||
|
||||
local output_name="${product_name}-${product_version}-${host_os}-${target##*-}.pkg"
|
||||
local output_name="${product_name}-${host_os}-${target##*-}.pkg"
|
||||
|
||||
if [[ ! -d ${project_root}/release/${product_name}.plugin ]] {
|
||||
log_error 'No release artifact found. Run the build script or the CMake install procedure first.'
|
||||
@@ -180,12 +182,19 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
|
||||
}
|
||||
popd
|
||||
} elif [[ ${host_os} == 'linux' ]] {
|
||||
local -a cmake_args=()
|
||||
if (( _loglevel > 1 )) cmake_args+=(--verbose)
|
||||
if (( ${+ZIP} )) {
|
||||
local output_name="${product_name}-${host_os}-${target##*-}.zip"
|
||||
pushd ${project_root}/release
|
||||
zip -r "${output_name}" *
|
||||
popd
|
||||
} else {
|
||||
local -a cmake_args=()
|
||||
if (( _loglevel > 1 )) cmake_args+=(--verbose)
|
||||
|
||||
pushd ${project_root}
|
||||
cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args}
|
||||
popd
|
||||
pushd ${project_root}
|
||||
cmake --build build_${target##*-} --config ${BUILD_CONFIG:-RelWithDebInfo} -t package ${cmake_args}
|
||||
popd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
.github/scripts/Package-Windows.ps1
vendored
3
.github/scripts/Package-Windows.ps1
vendored
@@ -38,9 +38,8 @@ function Package {
|
||||
|
||||
$BuildSpec = Get-Content -Path ${BuildSpecFile} -Raw | ConvertFrom-Json
|
||||
$ProductName = $BuildSpec.name
|
||||
$ProductVersion = $BuildSpec.version
|
||||
|
||||
$OutputName = "${ProductName}-${ProductVersion}-windows-${Target}"
|
||||
$OutputName = "${ProductName}-windows-${Target}"
|
||||
|
||||
Install-BuildDependencies -WingetFile "${ScriptHome}/.Wingetfile"
|
||||
|
||||
|
||||
69
.github/workflows/build-debian.yml
vendored
Normal file
69
.github/workflows/build-debian.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: debian-build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'data/locale/**'
|
||||
branches:
|
||||
- master
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'data/locale/**'
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: "recursive"
|
||||
- name: check_libobs_revision
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libobs-dev
|
||||
mkdir source
|
||||
cd source
|
||||
dpkg -l libobs-dev | tr -s " "| grep libobs | cut -d" " -f3 > libobs.rev
|
||||
- name: install_frontend_header
|
||||
run: |
|
||||
[ -e /usr/include/obs/obs-frontend-api.h ] && { echo "ERROR: obs-frontend-api.h already in system. Maybe the package libobs-dev is installing it."; exit 1; }
|
||||
cd source
|
||||
LIBOBSREV=$(cat libobs.rev)
|
||||
sudo apt update
|
||||
sudo apt install devscripts
|
||||
dget -u http://archive.ubuntu.com/ubuntu/pool/universe/o/obs-studio/obs-studio_$LIBOBSREV.dsc
|
||||
cd ..
|
||||
sudo find -name obs-frontend-api.h -exec cp {} /usr/include/obs/ \;
|
||||
egrep '#include <obs.h>' /usr/include/obs/obs-frontend-api.h || { echo "ERROR: check if the sed commands are needed from now."; exit 1; }
|
||||
sudo sed -i 's/#include <obs.h>/#include <obs\/obs.h>/' /usr/include/obs/obs-frontend-api.h
|
||||
sudo sed -i 's/#include <util\/darray.h>/#include <obs\/util\/darray.h>/' /usr/include/obs/obs-frontend-api.h
|
||||
- name: create_tarball
|
||||
run: |
|
||||
cd ..
|
||||
tar --exclude=.git -cvzf obs-scene-switcher_0.1+testonly.orig.tar.gz SceneSwitcher
|
||||
- name: create_debian_dir
|
||||
run: |
|
||||
cp -a CI/linux/debian .
|
||||
- name: install_dependencies
|
||||
run: |
|
||||
# devscripts and libobs-dev are needed but they were already installed
|
||||
# from check_libobs_revision and install_frontend_header sections.
|
||||
sudo apt update
|
||||
sudo apt install cmake debhelper libcurl4-openssl-dev libxss-dev libxtst-dev qtbase5-dev libopencv-dev libprocps-dev
|
||||
- name: build
|
||||
run: |
|
||||
debuild --no-lintian --no-sign
|
||||
mv ../*.deb .
|
||||
- name: Publish
|
||||
if: success()
|
||||
uses: actions/upload-artifact@v2.2.1
|
||||
with:
|
||||
name: "obs-scene-switcher.deb"
|
||||
path: ${{ github.workspace }}/*.deb
|
||||
25
.github/workflows/main.yml
vendored
25
.github/workflows/main.yml
vendored
@@ -169,7 +169,7 @@ jobs:
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}
|
||||
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}-*-macos-${{ matrix.arch }}.pkg
|
||||
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}*-macos-${{ matrix.arch }}.pkg
|
||||
|
||||
linux_build:
|
||||
name: 02 - Linux
|
||||
@@ -178,6 +178,7 @@ jobs:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch: [x86_64]
|
||||
portable: [true, false]
|
||||
if: always()
|
||||
needs: [clang_check]
|
||||
outputs:
|
||||
@@ -235,13 +236,7 @@ jobs:
|
||||
workingDirectory: ${{ github.workspace }}/plugin
|
||||
target: ${{ matrix.arch }}
|
||||
config: RelWithDebInfo
|
||||
|
||||
- name: Upload Build Artifact
|
||||
if: ${{ success() }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}
|
||||
path: ${{ github.workspace }}/plugin/release/*
|
||||
portable: ${{ matrix.portable }}
|
||||
|
||||
- name: Package Plugin
|
||||
uses: ./plugin/.github/actions/package-plugin
|
||||
@@ -249,13 +244,21 @@ jobs:
|
||||
workingDirectory: ${{ github.workspace }}/plugin
|
||||
target: ${{ matrix.arch }}
|
||||
config: RelWithDebInfo
|
||||
portable: ${{ matrix.portable }}
|
||||
|
||||
- name: Upload Package Artifact
|
||||
if: ${{ success() }}
|
||||
if: ${{ matrix.portable }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}-portable
|
||||
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}*-linux-${{ matrix.arch }}.*
|
||||
|
||||
- name: Upload Build Artifact
|
||||
if: ${{ ! matrix.portable }}
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}
|
||||
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}-*-linux-${{ matrix.arch }}.*
|
||||
path: ${{ github.workspace }}/plugin/release/${{ env.LIB_NAME }}*-linux-${{ matrix.arch }}.*
|
||||
|
||||
windows_build:
|
||||
name: 02 - Windows
|
||||
@@ -263,7 +266,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
arch: [x86, x64]
|
||||
arch: [x64]
|
||||
if: always()
|
||||
needs: [clang_check]
|
||||
outputs:
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -10,6 +10,3 @@
|
||||
[submodule "deps/openvr"]
|
||||
path = deps/openvr
|
||||
url = https://github.com/ValveSoftware/openvr.git
|
||||
[submodule "deps/obs-websocket"]
|
||||
path = deps/obs-websocket
|
||||
url = https://github.com/obsproject/obs-websocket.git
|
||||
|
||||
@@ -6,6 +6,8 @@ You have the option to ...
|
||||
|
||||
As both methods require you to have a working [OBS Studio development environment](https://obsproject.com/wiki/Building-OBS-Studio) and [CMake](https://cmake.org/download/) it is recommended to build the plugin in tree as it is easier to set up and will enable straightforward debugging.
|
||||
|
||||
The plugin can be compiled for OBS 25 and above, although using the latest version of OBS is recommended to support all features.
|
||||
|
||||
## Compiling in tree (recommended for development)
|
||||
Add the "SceneSwitcher" source directory to your obs-studio source directory under obs-studio/UI/frontend-plugins/:
|
||||
```
|
||||
@@ -25,6 +27,7 @@ Note that on Linux systems it might be necessary to additionally install the fol
|
||||
sudo apt-get install \
|
||||
libxtst-dev \
|
||||
libxss-dev \
|
||||
libprocps-dev \
|
||||
libopencv-dev
|
||||
```
|
||||
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
Note that you will have to use OBS version 27 or newer!
|
||||
|
||||
--- Windows ---
|
||||
Recommended: Run the provided installer. (You might have to click 'More info' and select 'Run anyway' if it is blocked by Windows)
|
||||
|
||||
Alternatively manually copy the 'obs-plugins' and 'data' folders in the respective OBS Studio installation directory.
|
||||
It is usually located at 'C:\Program Files (x86)\obs-studio\'.
|
||||
Remember to install the Visual C++ Redistributable for Visual Studio 2019, if you have not done so already. (See plugin overview page)
|
||||
|
||||
--- macOS ---
|
||||
Run the provided installer. (You might have to right-click or control-click and select 'open' if it is blocked)
|
||||
|
||||
--- Linux ---
|
||||
Note that the plugin has dependencies to:
|
||||
* libXss
|
||||
* libopencv-imgproc
|
||||
* libopencv-objdetect
|
||||
Optional:
|
||||
* libXtst
|
||||
* libcurl
|
||||
|
||||
If you are using snap you can install the following package for an OBS installation which comes bundled with the plugin:
|
||||
sudo snap install obs-studio
|
||||
|
||||
The plugin is also available via the Flatpak package manager for users who installed OBS via Flatpak:
|
||||
flatpak install com.obsproject.Studio.Plugin.SceneSwitcher
|
||||
|
||||
If you have installed OBS via other means, it is most likely necessary to install the plugin manually.
|
||||
To do so copy the advanced-scene-switcher.so file and into the OBS Studio plugin folder.
|
||||
The location of this folder can vary, so you might have to look around a bit.
|
||||
|
||||
Examples are ...
|
||||
/usr/lib/obs-plugins/
|
||||
/usr/lib/x86_64-linux-gnu/obs-plugins/
|
||||
/usr/share/obs/obs-plugins/
|
||||
~/.config/obs-studio/plugins/advanced-scene-switcher/bin/64bit/
|
||||
~/.local/share/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/lib/obs-plugins/
|
||||
/var/lib/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/lib/obs-plugins/
|
||||
|
||||
Unfortunately the expected location of the "locale" and "res" folder, which can be found in the data folder, can vary also.
|
||||
The data folder usually should be copied to "~/.config/obs-studio/plugins/advanced-scene-switcher/" and reflect the following structure:
|
||||
|
||||
/home/user/.config/obs-studio/plugins/
|
||||
└── advanced-scene-switcher
|
||||
├── bin
|
||||
│ └── 64bit
|
||||
│ └── advanced-scene-switcher.so
|
||||
└── data
|
||||
├── locale
|
||||
│ ├── de-DE.ini
|
||||
│ ├── en-US.ini
|
||||
│ ├── ru-RU.ini
|
||||
│ └── zh-CN.ini
|
||||
└── res
|
||||
├── cascadeClassifiers
|
||||
│ ├── haarcascade_eye_tree_eyeglasses.xml
|
||||
│ ├── haarcascade_eye.xml
|
||||
│ ├── haarcascade_frontalface_alt2.xml
|
||||
│ ├── haarcascade_frontalface_alt_tree.xml
|
||||
│ ├── haarcascade_frontalface_alt.xml
|
||||
│ ├── haarcascade_frontalface_default.xml
|
||||
│ ├── haarcascade_fullbody.xml
|
||||
│ ├── haarcascade_lefteye_2splits.xml
|
||||
│ ├── haarcascade_lowerbody.xml
|
||||
│ ├── haarcascade_profileface.xml
|
||||
│ ├── haarcascade_righteye_2splits.xml
|
||||
│ └── haarcascade_upperbody.xml
|
||||
└── time.svg
|
||||
|
||||
If this does not work you can try to copy the "locale" folder found inside the data folder to:
|
||||
/usr/share/obs/obs-plugins/advanced-scene-switcher/locale
|
||||
~/.local/share/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/share/obs/obs-plugins/advanced-scene-switcher/locale
|
||||
/var/lib/flatpak/app/com.obsproject.Studio/x86_64/stable/active/files/share/obs/obs-plugins/advanced-scene-switcher/locale
|
||||
|
||||
In doubt, please check where other "en-US.ini" files are located on your system.
|
||||
@@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
cmake_minimum_required(VERSION 3.16.3)
|
||||
|
||||
project(advanced-scene-switcher VERSION 1.0.0)
|
||||
set(LIB_NAME "${PROJECT_NAME}-lib")
|
||||
@@ -147,6 +147,8 @@ target_sources(
|
||||
src/macro-core/macro-action-timer.hpp
|
||||
src/macro-core/macro-action-transition.cpp
|
||||
src/macro-core/macro-action-transition.hpp
|
||||
src/macro-core/macro-action-variable.cpp
|
||||
src/macro-core/macro-action-variable.hpp
|
||||
src/macro-core/macro-action-virtual-cam.cpp
|
||||
src/macro-core/macro-action-virtual-cam.hpp
|
||||
src/macro-core/macro-action-wait.cpp
|
||||
@@ -205,6 +207,8 @@ target_sources(
|
||||
src/macro-core/macro-condition-timer.hpp
|
||||
src/macro-core/macro-condition-transition.cpp
|
||||
src/macro-core/macro-condition-transition.hpp
|
||||
src/macro-core/macro-condition-variable.cpp
|
||||
src/macro-core/macro-condition-variable.hpp
|
||||
src/macro-core/macro-condition-virtual-cam.cpp
|
||||
src/macro-core/macro-condition-virtual-cam.hpp
|
||||
src/macro-core/macro-condition-websocket.cpp
|
||||
@@ -238,12 +242,16 @@ target_sources(
|
||||
src/utils/curl-helper.hpp
|
||||
src/utils/duration-control.cpp
|
||||
src/utils/duration-control.hpp
|
||||
src/utils/item-selection-helpers.cpp
|
||||
src/utils/item-selection-helpers.hpp
|
||||
src/utils/file-selection.cpp
|
||||
src/utils/file-selection.hpp
|
||||
src/utils/macro-list.cpp
|
||||
src/utils/macro-list.hpp
|
||||
src/utils/name-dialog.cpp
|
||||
src/utils/name-dialog.hpp
|
||||
src/utils/regex-config.cpp
|
||||
src/utils/regex-config.hpp
|
||||
src/utils/resizing-text-edit.cpp
|
||||
src/utils/resizing-text-edit.hpp
|
||||
src/utils/scene-item-selection.cpp
|
||||
@@ -258,29 +266,27 @@ target_sources(
|
||||
src/utils/transition-selection.hpp
|
||||
src/utils/utility.cpp
|
||||
src/utils/utility.hpp
|
||||
src/utils/variable.cpp
|
||||
src/utils/variable.hpp
|
||||
src/utils/volume-control.cpp
|
||||
src/utils/volume-control.hpp
|
||||
src/utils/websocket-helpers.cpp
|
||||
src/utils/websocket-helpers.hpp
|
||||
src/utils/volume-control.hpp)
|
||||
src/utils/websocket-helpers.hpp)
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB_NAME})
|
||||
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
find_package(libobs REQUIRED)
|
||||
find_package(obs-frontend-api REQUIRED)
|
||||
include(cmake/ObsPluginHelpers.cmake)
|
||||
target_link_libraries(${LIB_NAME} PUBLIC OBS::libobs OBS::obs-frontend-api)
|
||||
else()
|
||||
target_link_libraries(${LIB_NAME} PUBLIC OBS::libobs OBS::frontend-api)
|
||||
endif()
|
||||
include(cmake/AdvSSHelpers.cmake)
|
||||
setup_obs_lib_dependency(${LIB_NAME})
|
||||
setup_obs_lib_dependency(${PROJECT_NAME})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB_NAME})
|
||||
|
||||
find_qt(COMPONENTS Widgets Core)
|
||||
target_link_libraries(${LIB_NAME} PUBLIC Qt::Core Qt::Widgets)
|
||||
|
||||
include(cmake/AdvSSHelpers.cmake)
|
||||
|
||||
# --- Platform-independent build settings ---
|
||||
|
||||
target_include_directories(
|
||||
@@ -357,9 +363,10 @@ elseif(OS_MACOS)
|
||||
|
||||
# --- Linux-specific build settings and tasks ---
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN")
|
||||
target_compile_options(${LIB_NAME} PRIVATE -Wall -Wextra)
|
||||
|
||||
set_target_properties(${LIB_NAME} PROPERTIES PREFIX "")
|
||||
set_target_properties(${LIB_NAME} PROPERTIES SOVERSION 1)
|
||||
|
||||
find_package(X11 REQUIRED COMPONENTS Xtst Xss)
|
||||
find_path(PROCPS_INCLUDE_DIR NAMES proc/procps.h)
|
||||
@@ -392,31 +399,26 @@ endif()
|
||||
# --- End of section ---
|
||||
|
||||
target_link_libraries(${LIB_NAME} PUBLIC ${${PROJECT_NAME}_PLATFORM_LIBS})
|
||||
setup_plugin_target(${PROJECT_NAME})
|
||||
install_advss_lib(${LIB_NAME})
|
||||
|
||||
# --- Helpers for debian package creation ---
|
||||
|
||||
if(DEB_INSTALL)
|
||||
# Additional commands to install the module in the correct place. Find all the
|
||||
# translation files so we can copy them to the correct place later on.
|
||||
file(GLOB ASS_TRANSLATION_FILES "data/locale/*.ini")
|
||||
|
||||
# Linux
|
||||
if(UNIX AND NOT APPLE)
|
||||
if(NOT LIB_OUT_DIR)
|
||||
set(LIB_OUT_DIR "/lib/obs-plugins")
|
||||
endif()
|
||||
if(NOT DATA_OUT_DIR)
|
||||
set(DATA_OUT_DIR "/share/obs/obs-plugins/${PROJECT_NAME}")
|
||||
endif()
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
|
||||
install(DIRECTORY data/locale
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
|
||||
install(DIRECTORY data/res
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
|
||||
if(NOT LIB_OUT_DIR)
|
||||
set(LIB_OUT_DIR "/lib/obs-plugins")
|
||||
endif()
|
||||
if(NOT DATA_OUT_DIR)
|
||||
set(DATA_OUT_DIR "/share/obs/obs-plugins/${PROJECT_NAME}")
|
||||
endif()
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
|
||||
install(TARGETS ${LIB_NAME}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR})
|
||||
install(DIRECTORY data/locale
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
|
||||
install(DIRECTORY data/res
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/${DATA_OUT_DIR})
|
||||
else()
|
||||
setup_plugin_target(${PROJECT_NAME})
|
||||
install_advss_lib(${LIB_NAME})
|
||||
endif()
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
@@ -26,6 +26,7 @@ If `apt` is supported on your system they can be installed using:
|
||||
sudo apt-get install \
|
||||
libxtst-dev \
|
||||
libxss-dev \
|
||||
libprocps-dev \
|
||||
libopencv-dev
|
||||
```
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"obs-studio": {
|
||||
"version": "28.0.0-beta1",
|
||||
"version": "28.0.1",
|
||||
"repository": "https://github.com/obsproject/obs-studio.git",
|
||||
"branch": "master",
|
||||
"hash": "43a49dca47344a5170159ef99b86b97f90d4e4ad"
|
||||
"hash": "e8dc70d0eef4503378d6ac300e680215eb5c9379"
|
||||
},
|
||||
"prebuilt": {
|
||||
"version": "2022-08-02",
|
||||
|
||||
@@ -67,18 +67,27 @@ if(OS_MACOS)
|
||||
else()
|
||||
# --- Windows / Linux section ---
|
||||
function(plugin_install_helper what where where_deb)
|
||||
install(
|
||||
TARGETS ${what}
|
||||
RUNTIME DESTINATION "${where}" COMPONENT ${what}_Runtime
|
||||
LIBRARY DESTINATION "${where}"
|
||||
COMPONENT ${what}_Runtime
|
||||
NAMELINK_COMPONENT ${what}_Development)
|
||||
install(
|
||||
FILES $<TARGET_FILE:${what}>
|
||||
DESTINATION $<CONFIG>/${where}
|
||||
COMPONENT ${what}_rundir
|
||||
EXCLUDE_FROM_ALL)
|
||||
if(DEB_INSTALL)
|
||||
if(NOT LIB_OUT_DIR)
|
||||
set(LIB_OUT_DIR "/lib/obs-plugins")
|
||||
endif()
|
||||
install(
|
||||
TARGETS ${what}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR}/${where_deb})
|
||||
else()
|
||||
install(
|
||||
TARGETS ${what}
|
||||
RUNTIME DESTINATION "${where}" COMPONENT ${what}_Runtime
|
||||
LIBRARY DESTINATION "${where}"
|
||||
COMPONENT ${what}_Runtime
|
||||
NAMELINK_COMPONENT ${what}_Development)
|
||||
|
||||
install(
|
||||
FILES $<TARGET_FILE:${what}>
|
||||
DESTINATION $<CONFIG>/${where}
|
||||
COMPONENT ${what}_rundir
|
||||
EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
if(OS_WINDOWS)
|
||||
install(
|
||||
FILES $<TARGET_PDB_FILE:${what}>
|
||||
@@ -93,7 +102,6 @@ else()
|
||||
COMPONENT ${what}_rundir
|
||||
OPTIONAL EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${what}
|
||||
POST_BUILD
|
||||
@@ -104,25 +112,23 @@ else()
|
||||
${CMAKE_CURRENT_BINARY_DIR}/cmake_install.cmake
|
||||
COMMENT "Installing ${what} to plugin rundir ${OBS_OUTPUT_DIR}/${where}\n"
|
||||
VERBATIM)
|
||||
|
||||
if(OS_POSIX AND DEB_INSTALL)
|
||||
if(NOT LIB_OUT_DIR)
|
||||
set(LIB_OUT_DIR "/lib/obs-plugins")
|
||||
endif()
|
||||
install(
|
||||
TARGETS ${what}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR}/${where_deb})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(install_advss_lib target)
|
||||
plugin_install_helper("${target}" "${OBS_PLUGIN_DESTINATION}" "")
|
||||
if(OS_POSIX)
|
||||
set_target_properties(${target} PROPERTIES INSTALL_RPATH "$ORIGIN")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(install_advss_plugin target)
|
||||
plugin_install_helper(
|
||||
"${target}" "${OBS_PLUGIN_DESTINATION}/${_PLUGIN_FOLDER}"
|
||||
"${_PLUGIN_FOLDER}")
|
||||
if(OS_POSIX)
|
||||
set_target_properties(${target} PROPERTIES INSTALL_RPATH
|
||||
"$ORIGIN:$ORIGIN/..")
|
||||
endif()
|
||||
message(STATUS "ADVSS: ENABLED PLUGIN ${target}")
|
||||
endfunction()
|
||||
|
||||
@@ -205,14 +211,50 @@ else()
|
||||
endif()
|
||||
|
||||
# --- End of section ---
|
||||
|
||||
function(setup_advss_plugin target)
|
||||
function(setup_obs_lib_dependency target)
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
target_link_libraries(${target} PUBLIC OBS::libobs OBS::obs-frontend-api)
|
||||
find_package(libobs)
|
||||
if(libobs_FOUND)
|
||||
target_link_libraries(${target} PUBLIC OBS::libobs)
|
||||
else()
|
||||
if(NOT LIBOBS_LIB)
|
||||
message(FATAL_ERROR "obs library not found - please set LIBOBS_LIB")
|
||||
endif()
|
||||
target_link_libraries(${target} PUBLIC ${LIBOBS_LIB})
|
||||
if(NOT LIBOBS_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR "obs.hpp header not found - please set LIBOBS_INCLUDE_DIR"
|
||||
)
|
||||
endif()
|
||||
target_include_directories(${target} PRIVATE ${LIBOBS_INCLUDE_DIR})
|
||||
endif()
|
||||
find_package(obs-frontend-api)
|
||||
if(obs-frontend-api_FOUND)
|
||||
target_link_libraries(${target} PUBLIC OBS::obs-frontend-api)
|
||||
else()
|
||||
if(NOT LIBOBS_FRONTEND_API_LIB)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
"libobs frontend-api library not found - please set LIBOBS_FRONTEND_API_LIB"
|
||||
)
|
||||
endif()
|
||||
target_link_libraries(${target} PUBLIC ${LIBOBS_FRONTEND_API_LIB})
|
||||
if(NOT LIBOBS_FRONTEND_INCLUDE_DIR)
|
||||
message(
|
||||
FATAL_ERROR
|
||||
" obs-frontend-api.h not found - please set LIBOBS_FRONTEND_INCLUDE_DIR"
|
||||
)
|
||||
endif()
|
||||
target_include_directories(${target}
|
||||
PRIVATE ${LIBOBS_FRONTEND_INCLUDE_DIR})
|
||||
endif()
|
||||
else()
|
||||
target_link_libraries(${target} PUBLIC OBS::libobs OBS::frontend-api)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(setup_advss_plugin target)
|
||||
setup_obs_lib_dependency(${target})
|
||||
find_qt(COMPONENTS Widgets Core)
|
||||
target_link_libraries(${target} PRIVATE Qt::Core Qt::Widgets)
|
||||
|
||||
|
||||
@@ -350,9 +350,15 @@ else()
|
||||
"${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-linux-x86_64")
|
||||
|
||||
set(CPACK_GENERATOR "DEB")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"obs-studio (>= 27.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)"
|
||||
)
|
||||
if(${QT_VERSION} STREQUAL "6")
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"obs-studio (>= 28.0.0), libqt6core6 (>= 6.2.0), libqt6gui6 (>= 6.1.2), libqt6widgets6 (>= 6.1.2)"
|
||||
)
|
||||
else()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS
|
||||
"obs-studio (>= 28.0.0), libqt5core5a (>= 5.9.0~beta), libqt5gui5 (>= 5.3.0), libqt5widgets5 (>= 5.7.0)"
|
||||
)
|
||||
endif()
|
||||
|
||||
set(CPACK_OUTPUT_FILE_PREFIX ${CMAKE_SOURCE_DIR}/release)
|
||||
|
||||
|
||||
@@ -83,7 +83,8 @@ AdvSceneSwitcher.macroTab.minimize="Minimize"
|
||||
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
|
||||
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
|
||||
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
|
||||
AdvSceneSwitcher.macroTab.disableHotkeys="Register hotkeys to control pause state of selected macro"
|
||||
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="Register hotkeys to control pause state of new macros"
|
||||
AdvSceneSwitcher.macroTab.currentDisableHotkeys="Register hotkeys to control pause state of selected macro"
|
||||
|
||||
; Macro List
|
||||
AdvSceneSwitcher.macroList.deleted="deleted"
|
||||
@@ -128,7 +129,10 @@ AdvSceneSwitcher.condition.window="Window"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} exist and ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... is {{fullscreen}} fullscreen {{maximized}} maximized {{focused}} focused {{windowFocusChanged}} foreground window changed"
|
||||
AdvSceneSwitcher.condition.file="File"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Content of {{fileType}} {{filePath}} matches:"
|
||||
AdvSceneSwitcher.condition.file.type.match="matches"
|
||||
AdvSceneSwitcher.condition.file.type.contentChange="content changed"
|
||||
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Content of{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Media"
|
||||
@@ -148,6 +152,8 @@ AdvSceneSwitcher.condition.video.condition.object="contains object"
|
||||
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
|
||||
AdvSceneSwitcher.condition.video.reduceLatency="Reduce matching latency"
|
||||
AdvSceneSwitcher.condition.video.reduceLatency.tooltip="Enabling will reduce the matching latency, but will slow down the condition checks of all macros overall."
|
||||
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Use pattern matching"
|
||||
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="This will allow you to control how much the image has to change for the condition to be true."
|
||||
AdvSceneSwitcher.condition.video.patternThreshold="Threshold: "
|
||||
@@ -169,7 +175,8 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
|
||||
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}} {{selectArea}}"
|
||||
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in area"
|
||||
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
|
||||
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
|
||||
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
|
||||
AdvSceneSwitcher.condition.video.selectArea="Select area"
|
||||
@@ -227,7 +234,6 @@ AdvSceneSwitcher.condition.source="Source"
|
||||
AdvSceneSwitcher.condition.source.type.active="Is active"
|
||||
AdvSceneSwitcher.condition.source.type.showing="Is showing"
|
||||
AdvSceneSwitcher.condition.source.type.settings="Settings match"
|
||||
AdvSceneSwitcher.condition.source.regex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
|
||||
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
|
||||
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
|
||||
@@ -240,7 +246,6 @@ AdvSceneSwitcher.condition.filter="Filter"
|
||||
AdvSceneSwitcher.condition.filter.type.active="Is enabled"
|
||||
AdvSceneSwitcher.condition.filter.type.showing="Is disabled"
|
||||
AdvSceneSwitcher.condition.filter.type.settings="Settings match"
|
||||
AdvSceneSwitcher.condition.filter.regex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
|
||||
AdvSceneSwitcher.condition.filter.entry.line1="On {{sources}} {{filters}} {{conditions}}"
|
||||
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
|
||||
@@ -286,7 +291,6 @@ AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
|
||||
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} On repeat update selected date to repeat date"
|
||||
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line1="On{{scenes}}{{sources}}matches transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
|
||||
@@ -335,12 +339,25 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
|
||||
AdvSceneSwitcher.condition.profile="Profile"
|
||||
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
|
||||
AdvSceneSwitcher.condition.websocket="Websocket"
|
||||
AdvSceneSwitcher.condition.websocket.type.request="Request"
|
||||
AdvSceneSwitcher.condition.websocket.type.event="Event"
|
||||
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.websocket.entry="Message was received:"
|
||||
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
|
||||
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
|
||||
AdvSceneSwitcher.condition.variable="Variable"
|
||||
AdvSceneSwitcher.condition.variable.type.compare="equals"
|
||||
AdvSceneSwitcher.condition.variable.type.empty="is empty"
|
||||
AdvSceneSwitcher.condition.variable.type.number="is a number"
|
||||
AdvSceneSwitcher.condition.variable.type.lessThan="is less than"
|
||||
AdvSceneSwitcher.condition.variable.type.greaterThan="is greater than"
|
||||
AdvSceneSwitcher.condition.variable.type.valueChanged="value changed"
|
||||
AdvSceneSwitcher.condition.variable.regex="Use regular expressions"
|
||||
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Switch scene"
|
||||
AdvSceneSwitcher.action.scene.entry="Switch to scene {{scenes}} using {{transitions}} with a duration of {{duration}} seconds"
|
||||
AdvSceneSwitcher.action.scene.entry="Switch to scene{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
|
||||
AdvSceneSwitcher.action.scene.entry.noDuration="Switch to scene{{scenes}}using{{transitions}}"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
|
||||
AdvSceneSwitcher.action.wait="Wait"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="fixed"
|
||||
@@ -481,8 +498,13 @@ AdvSceneSwitcher.action.random.entry="Randomly run any of the following macros (
|
||||
AdvSceneSwitcher.action.systray="System tray notification"
|
||||
AdvSceneSwitcher.action.systray.entry="Show notification: {{message}}"
|
||||
AdvSceneSwitcher.action.screenshot="Screenshot"
|
||||
AdvSceneSwitcher.action.screenshot.save.default="Default"
|
||||
AdvSceneSwitcher.action.screenshot.save.custom="Custom"
|
||||
AdvSceneSwitcher.action.screenshot.type.source="Source"
|
||||
AdvSceneSwitcher.action.screenshot.type.scene="Scene"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS's main output"
|
||||
AdvSceneSwitcher.action.screenshot.entry="Screenshot {{sources}}"
|
||||
AdvSceneSwitcher.action.screenshot.blackscreenNote="Sources or scenes, which are not always rendered, may result in some parts of screenshots to remain blank."
|
||||
AdvSceneSwitcher.action.screenshot.entry="Screenshot{{targetType}}{{sources}}{{scenes}}and save to{{saveType}}location"
|
||||
AdvSceneSwitcher.action.profile="Profile"
|
||||
AdvSceneSwitcher.action.profile.entry="Switch active profile to {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Scene collection"
|
||||
@@ -495,12 +517,22 @@ AdvSceneSwitcher.action.sequence.status.none="none"
|
||||
AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached"
|
||||
AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item"
|
||||
AdvSceneSwitcher.action.websocket="Websocket"
|
||||
AdvSceneSwitcher.action.websocket.entry="Send scene switcher message via {{connection}}"
|
||||
AdvSceneSwitcher.action.websocket.type.request="Request"
|
||||
AdvSceneSwitcher.action.websocket.type.event="Event"
|
||||
AdvSceneSwitcher.action.websocket.entry.request="Send scene switcher {{type}} via {{connection}}"
|
||||
AdvSceneSwitcher.action.websocket.entry.event="Send scene switcher {{type}} to connected clients"
|
||||
AdvSceneSwitcher.action.http="Http"
|
||||
AdvSceneSwitcher.action.http.type.get="GET"
|
||||
AdvSceneSwitcher.action.http.type.post="POST"
|
||||
AdvSceneSwitcher.action.http.entry.line1="Send {{method}} to {{url}}"
|
||||
AdvSceneSwitcher.action.http.entry.line2="Timeout: {{timeout}} seconds"
|
||||
AdvSceneSwitcher.action.variable="Variable"
|
||||
AdvSceneSwitcher.action.variable.type.set="Set"
|
||||
AdvSceneSwitcher.action.variable.type.append="Append"
|
||||
AdvSceneSwitcher.action.variable.type.appendVar="Append variable"
|
||||
AdvSceneSwitcher.action.variable.type.increment="Increment"
|
||||
AdvSceneSwitcher.action.variable.type.decrement="Decrement"
|
||||
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}"
|
||||
|
||||
|
||||
; Transition Tab
|
||||
@@ -757,15 +789,27 @@ AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
|
||||
AdvSceneSwitcher.close="Close"
|
||||
AdvSceneSwitcher.browse="Browse"
|
||||
|
||||
AdvSceneSwitcher.item.select="--select item--"
|
||||
AdvSceneSwitcher.item.add="Add new item"
|
||||
AdvSceneSwitcher.item.rename="Rename"
|
||||
AdvSceneSwitcher.item.remove="Remove"
|
||||
AdvSceneSwitcher.item.properties="Properties"
|
||||
AdvSceneSwitcher.item.newName="New name:"
|
||||
AdvSceneSwitcher.item.emptyName="Empty name not allowed!"
|
||||
AdvSceneSwitcher.item.nameNotAvailable="Name not available!"
|
||||
AdvSceneSwitcher.item.nameReserved="Name reserved!"
|
||||
|
||||
AdvSceneSwitcher.variable.select="--select variable--"
|
||||
AdvSceneSwitcher.variable.add="Add new variable"
|
||||
AdvSceneSwitcher.variable.name="Name:"
|
||||
AdvSceneSwitcher.variable.value="Current value:"
|
||||
AdvSceneSwitcher.variable.save="Save / load behavior"
|
||||
AdvSceneSwitcher.variable.save.dontSave="Don't save variable value"
|
||||
AdvSceneSwitcher.variable.save.save="Save variable value"
|
||||
AdvSceneSwitcher.variable.save.default="Set to value"
|
||||
|
||||
AdvSceneSwitcher.connection.select="--select connection--"
|
||||
AdvSceneSwitcher.connection.add="Add new connection"
|
||||
AdvSceneSwitcher.connection.rename="Rename"
|
||||
AdvSceneSwitcher.connection.remove="Remove"
|
||||
AdvSceneSwitcher.connection.properties="Properties"
|
||||
AdvSceneSwitcher.connection.newName="New name:"
|
||||
AdvSceneSwitcher.connection.emptyName="Empty name not allowed!"
|
||||
AdvSceneSwitcher.connection.nameNotAvailable="Name not available!"
|
||||
AdvSceneSwitcher.connection.nameReserved="Name reserved!"
|
||||
AdvSceneSwitcher.connection.name="Name:"
|
||||
AdvSceneSwitcher.connection.address="Address:"
|
||||
AdvSceneSwitcher.connection.port="Port:"
|
||||
@@ -779,9 +823,17 @@ AdvSceneSwitcher.connection.status.connecting="Connecting"
|
||||
AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated"
|
||||
AdvSceneSwitcher.connection.status.authenticated="Connected and authenticated"
|
||||
|
||||
AdvSceneSwitcher.regex.enable="Enable regular expressions"
|
||||
AdvSceneSwitcher.regex.partialMatch="Allow partial match"
|
||||
AdvSceneSwitcher.regex.caseInsensitive="Match case insensitive"
|
||||
AdvSceneSwitcher.regex.dotMatchNewline=". matches newlines"
|
||||
AdvSceneSwitcher.regex.multiLine="^ and $ match start/end of line"
|
||||
AdvSceneSwitcher.regex.extendedPattern="Enable Qt's ExtendedPatternSyntax"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--select scene--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
|
||||
AdvSceneSwitcher.selectCurrentScene="Current Scene"
|
||||
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
|
||||
AdvSceneSwitcher.selectAnyScene="Any Scene"
|
||||
AdvSceneSwitcher.currentTransition="Current Transition"
|
||||
AdvSceneSwitcher.anyTransition="Any Transition"
|
||||
@@ -790,7 +842,7 @@ AdvSceneSwitcher.selectWindow="--select window--"
|
||||
AdvSceneSwitcher.selectSource="--select source--"
|
||||
AdvSceneSwitcher.selectAudioSource="--select audio source--"
|
||||
AdvSceneSwitcher.selectVideoSource="--select video source--"
|
||||
AdvSceneSwitcher.OBSVideoOutput="OBS video output"
|
||||
AdvSceneSwitcher.OBSVideoOutput="OBS's main output"
|
||||
AdvSceneSwitcher.selectMediaSource="--select media source--"
|
||||
AdvSceneSwitcher.selectProcess="--select process--"
|
||||
AdvSceneSwitcher.selectFilter="--select filter--"
|
||||
|
||||
@@ -123,7 +123,7 @@ AdvSceneSwitcher.condition.window="Ventana"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} existen y ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... es {{fullscreen}} pantalla completa {{maximized}} maximizada {{focused}} enfocada {{windowFocusChanged}} ventana de primer plano cambiada"
|
||||
AdvSceneSwitcher.condition.file="Archivo"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Contenido de {{fileType}} {{filePath}} coincidencias:"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Contenido de{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Medios"
|
||||
@@ -164,6 +164,7 @@ AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePa
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="Datos del modelo (haar cascade classifier): {{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Mínimo de vecinos: {{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduzca la carga de la CPU realizando una comprobación solo cada {{throttleCount}} milisegundos"
|
||||
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Realizar comprobación solo en el área"
|
||||
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Realizar comprobación solo en el área {{checkArea}} {{selectArea}}"
|
||||
AdvSceneSwitcher.condition.video.minSize="Tamaño mínimo:"
|
||||
AdvSceneSwitcher.condition.video.maxSize="Tamaño máximo:"
|
||||
@@ -463,7 +464,6 @@ AdvSceneSwitcher.action.systray="Notificación de la bandeja del sistema"
|
||||
AdvSceneSwitcher.action.systray.entry="Mostrar notificación: {{message}}"
|
||||
AdvSceneSwitcher.action.screenshot="Captura de pantalla"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="Salida principal de OBS"
|
||||
AdvSceneSwitcher.action.screenshot.entry="Captura de pantalla {{sources}}"
|
||||
AdvSceneSwitcher.action.profile="Perfil"
|
||||
AdvSceneSwitcher.action.profile.entry="Cambiar perfil activo a {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Colección de escenas"
|
||||
|
||||
@@ -111,7 +111,7 @@ AdvSceneSwitcher.condition.window="Pencere"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} Varolan ve ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... {{fullscreen}} Tam ekran {{maximized}} büyütüldü {{focused}} odaklanıldı {{windowFocusChanged}} ön plan penceresi değişikliği"
|
||||
AdvSceneSwitcher.condition.file="Dosya"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik {{fileType}} {{filePath}} eşleştirmeler:"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik{{fileType}}{{filePath}}{{conditions}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Medya"
|
||||
@@ -383,7 +383,6 @@ AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
|
||||
AdvSceneSwitcher.action.systray.entry="Bildirimleri göster: {{message}}"
|
||||
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı"
|
||||
AdvSceneSwitcher.action.screenshot.entry="Ekran görüntüsü {{sources}}"
|
||||
AdvSceneSwitcher.action.profile="Profil"
|
||||
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"
|
||||
|
||||
@@ -116,7 +116,6 @@ AdvSceneSwitcher.condition.window="窗口"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改"
|
||||
AdvSceneSwitcher.condition.file="文件"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="内容 {{fileType}} {{filePath}} (本地是文本,网络是获取的网页)完全匹配:"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="媒体"
|
||||
@@ -424,7 +423,6 @@ AdvSceneSwitcher.action.systray="系统托盘通知"
|
||||
AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}"
|
||||
AdvSceneSwitcher.action.screenshot="截图"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS 预览画面"
|
||||
AdvSceneSwitcher.action.screenshot.entry="截图 {{sources}}"
|
||||
AdvSceneSwitcher.action.profile="配置文件"
|
||||
AdvSceneSwitcher.action.profile.entry="将活动配置文件切换到 {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="场景集合"
|
||||
|
||||
1
deps/obs-websocket
vendored
1
deps/obs-websocket
vendored
Submodule deps/obs-websocket deleted from a25427c7cc
216
deps/obs-websocket/lib/obs-websocket-api.h
vendored
Normal file
216
deps/obs-websocket/lib/obs-websocket-api.h
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2020-2022 Kyle Manning <tt2468@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef _OBS_WEBSOCKET_API_H
|
||||
#define _OBS_WEBSOCKET_API_H
|
||||
|
||||
#include <obs.h>
|
||||
|
||||
#define OBS_WEBSOCKET_API_VERSION 2
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef void *obs_websocket_vendor;
|
||||
typedef void (*obs_websocket_request_callback_function)(obs_data_t *, obs_data_t *, void *);
|
||||
|
||||
struct obs_websocket_request_response {
|
||||
unsigned int status_code;
|
||||
char *comment;
|
||||
char *response_data; // JSON string, because obs_data_t* only supports array<object>, so conversions would break API.
|
||||
};
|
||||
|
||||
/* ==================== INTERNAL DEFINITIONS ==================== */
|
||||
|
||||
struct obs_websocket_request_callback {
|
||||
obs_websocket_request_callback_function callback;
|
||||
void *priv_data;
|
||||
};
|
||||
|
||||
inline proc_handler_t *_ph;
|
||||
|
||||
/* ==================== INTERNAL API FUNCTIONS ==================== */
|
||||
|
||||
static inline proc_handler_t *obs_websocket_get_ph(void)
|
||||
{
|
||||
proc_handler_t *global_ph = obs_get_proc_handler();
|
||||
assert(global_ph != NULL);
|
||||
|
||||
calldata_t cd = {0};
|
||||
if (!proc_handler_call(global_ph, "obs_websocket_api_get_ph", &cd))
|
||||
blog(LOG_DEBUG, "Unable to fetch obs-websocket proc handler object. obs-websocket not installed?");
|
||||
proc_handler_t *ret = (proc_handler_t *)calldata_ptr(&cd, "ph");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_ensure_ph(void)
|
||||
{
|
||||
if (!_ph)
|
||||
_ph = obs_websocket_get_ph();
|
||||
return _ph != NULL;
|
||||
}
|
||||
|
||||
static inline bool obs_websocket_vendor_run_simple_proc(obs_websocket_vendor vendor, const char *proc_name, calldata_t *cd)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return false;
|
||||
|
||||
if (!vendor || !proc_name || !strlen(proc_name) || !cd)
|
||||
return false;
|
||||
|
||||
calldata_set_ptr(cd, "vendor", vendor);
|
||||
|
||||
proc_handler_call(_ph, proc_name, cd);
|
||||
return calldata_bool(cd, "success");
|
||||
}
|
||||
|
||||
/* ==================== GENERAL API FUNCTIONS ==================== */
|
||||
|
||||
// Gets the API version built with the obs-websocket plugin
|
||||
static inline unsigned int obs_websocket_get_api_version(void)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return 0;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
if (!proc_handler_call(_ph, "get_api_version", &cd))
|
||||
return 1; // API v1 does not include get_api_version
|
||||
|
||||
unsigned int ret = calldata_int(&cd, "version");
|
||||
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Calls an obs-websocket request. Free response with `obs_websocket_request_response_free()`
|
||||
static inline obs_websocket_request_response *obs_websocket_call_request(const char *request_type, obs_data_t *request_data = NULL)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return NULL;
|
||||
|
||||
const char *request_data_string = NULL;
|
||||
if (request_data)
|
||||
request_data_string = obs_data_get_json(request_data);
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "request_type", request_type);
|
||||
calldata_set_string(&cd, "request_data", request_data_string);
|
||||
|
||||
proc_handler_call(_ph, "call_request", &cd);
|
||||
|
||||
auto ret = (struct obs_websocket_request_response *)calldata_ptr(&cd, "response");
|
||||
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Free a request response object returned by `obs_websocket_call_request()`
|
||||
static inline void obs_websocket_request_response_free(struct obs_websocket_request_response *response)
|
||||
{
|
||||
if (!response)
|
||||
return;
|
||||
|
||||
if (response->comment)
|
||||
bfree(response->comment);
|
||||
if (response->response_data)
|
||||
bfree(response->response_data);
|
||||
bfree(response);
|
||||
}
|
||||
|
||||
/* ==================== VENDOR API FUNCTIONS ==================== */
|
||||
|
||||
// ALWAYS CALL ONLY VIA `obs_module_post_load()` CALLBACK!
|
||||
// Registers a new "vendor" (Example: obs-ndi)
|
||||
static inline obs_websocket_vendor obs_websocket_register_vendor(const char *vendor_name)
|
||||
{
|
||||
if (!obs_websocket_ensure_ph())
|
||||
return NULL;
|
||||
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "name", vendor_name);
|
||||
|
||||
proc_handler_call(_ph, "vendor_register", &cd);
|
||||
obs_websocket_vendor ret = calldata_ptr(&cd, "vendor");
|
||||
calldata_free(&cd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Registers a new request for a vendor
|
||||
static inline bool obs_websocket_vendor_register_request(obs_websocket_vendor vendor, const char *request_type,
|
||||
obs_websocket_request_callback_function request_callback, void *priv_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
struct obs_websocket_request_callback cb = {};
|
||||
cb.callback = request_callback;
|
||||
cb.priv_data = priv_data;
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
calldata_set_ptr(&cd, "callback", &cb);
|
||||
|
||||
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_register", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Unregisters an existing vendor request
|
||||
static inline bool obs_websocket_vendor_unregister_request(obs_websocket_vendor vendor, const char *request_type)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", request_type);
|
||||
|
||||
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_request_unregister", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
// Does not affect event_data refcount.
|
||||
// Emits an event under the vendor's name
|
||||
static inline bool obs_websocket_vendor_emit_event(obs_websocket_vendor vendor, const char *event_name, obs_data_t *event_data)
|
||||
{
|
||||
calldata_t cd = {0};
|
||||
|
||||
calldata_set_string(&cd, "type", event_name);
|
||||
calldata_set_ptr(&cd, "data", (void *)event_data);
|
||||
|
||||
bool success = obs_websocket_vendor_run_simple_proc(vendor, "vendor_event_emit", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
/* ==================== END API FUNCTIONS ==================== */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -642,7 +642,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -658,7 +658,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -713,17 +713,14 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string>upArrowIconSmall</string>
|
||||
<string notr="true">upArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -732,17 +729,14 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string>downArrowIconSmall</string>
|
||||
<string notr="true">downArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -819,7 +813,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
@@ -864,7 +858,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -880,7 +874,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -935,7 +929,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
@@ -954,7 +948,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -1006,7 +1000,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -1022,7 +1016,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -1077,7 +1071,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
@@ -1093,7 +1087,7 @@
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
|
||||
@@ -227,7 +227,7 @@ void SwitcherData::Thread()
|
||||
}
|
||||
}
|
||||
|
||||
websocketMessages.clear();
|
||||
ClearWebsocketMessages();
|
||||
|
||||
// After this point we will call frontend functions like
|
||||
// obs_frontend_set_current_scene() and
|
||||
@@ -629,9 +629,11 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
|
||||
resetLiveTime();
|
||||
break;
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
|
||||
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_SAVED:
|
||||
setReplayBufferSaved();
|
||||
break;
|
||||
#endif
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_STOPPED:
|
||||
setTranstionEnd();
|
||||
break;
|
||||
|
||||
@@ -95,6 +95,9 @@ signals:
|
||||
void ConnectionAdded(const QString &);
|
||||
void ConnectionRenamed(const QString &oldName, const QString &newName);
|
||||
void ConnectionRemoved(const QString &);
|
||||
void VariableAdded(const QString &);
|
||||
void VariableRenamed(const QString &oldName, const QString &newName);
|
||||
void VariableRemoved(const QString &);
|
||||
|
||||
public slots:
|
||||
void on_windowUp_clicked();
|
||||
|
||||
@@ -192,7 +192,9 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
|
||||
|
||||
bool isLegacyTab(const QString &name)
|
||||
{
|
||||
return name == obs_module_text("AdvSceneSwitcher.networkTab.title") ||
|
||||
return name == obs_module_text(
|
||||
"AdvSceneSwitcher.sceneGroupTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.networkTab.title") ||
|
||||
name == obs_module_text(
|
||||
"AdvSceneSwitcher.transitionTab.title") ||
|
||||
name == obs_module_text(
|
||||
@@ -221,13 +223,13 @@ void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
|
||||
|
||||
for (int idx = 0; idx < ui->tabWidget->count(); idx++) {
|
||||
if (isLegacyTab(ui->tabWidget->tabText(idx))) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
ui->tabWidget->setTabVisible(idx, !state);
|
||||
#else
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
// TODO: Switch to setTabVisible() once QT 5.15 is more wide spread
|
||||
ui->tabWidget->setTabEnabled(idx, !state);
|
||||
ui->tabWidget->setStyleSheet(
|
||||
"QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} ");
|
||||
#else
|
||||
ui->tabWidget->setTabVisible(idx, !state);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -503,6 +505,7 @@ void SwitcherData::loadSettings(obs_data_t *obj)
|
||||
// Needs to be loaded before any entries which might rely on scene group
|
||||
// selections to be available.
|
||||
loadSceneGroups(obj);
|
||||
loadVariables(obj);
|
||||
loadMacros(obj);
|
||||
loadConnections(obj);
|
||||
loadWindowTitleSwitches(obj);
|
||||
@@ -537,6 +540,7 @@ void SwitcherData::saveSettings(obs_data_t *obj)
|
||||
saveSceneGroups(obj);
|
||||
saveMacros(obj);
|
||||
saveConnections(obj);
|
||||
saveVariables(obj);
|
||||
saveWindowTitleSwitches(obj);
|
||||
saveScreenRegionSwitches(obj);
|
||||
savePauseSwitches(obj);
|
||||
@@ -687,6 +691,7 @@ void saveSplitterPos(QList<int> &sizes, obs_data_t *obj, const std::string name)
|
||||
|
||||
void loadSplitterPos(QList<int> &sizes, obs_data_t *obj, const std::string name)
|
||||
{
|
||||
sizes.clear();
|
||||
obs_data_array_t *array = obs_data_get_array(obj, name.c_str());
|
||||
size_t count = obs_data_array_count(array);
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
|
||||
@@ -185,18 +185,22 @@ void frontEndActionThread(sceneTriggerAction action, double delay)
|
||||
case sceneTriggerAction::STOP_STREAMING:
|
||||
obs_frontend_streaming_stop();
|
||||
break;
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
|
||||
case sceneTriggerAction::START_REPLAY_BUFFER:
|
||||
obs_frontend_replay_buffer_start();
|
||||
break;
|
||||
case sceneTriggerAction::STOP_REPLAY_BUFFER:
|
||||
obs_frontend_replay_buffer_stop();
|
||||
break;
|
||||
#endif
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
case sceneTriggerAction::START_VCAM:
|
||||
obs_frontend_start_virtualcam();
|
||||
break;
|
||||
case sceneTriggerAction::STOP_VCAM:
|
||||
obs_frontend_stop_virtualcam();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
|
||||
static_cast<int>(action));
|
||||
|
||||
@@ -43,9 +43,11 @@ bool MacroActionRecord::PerformAction()
|
||||
obs_frontend_recording_pause(false);
|
||||
}
|
||||
break;
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(28, 0, 0)
|
||||
case RecordAction::SPLIT:
|
||||
obs_frontend_recording_split_file();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -75,22 +75,22 @@ MacroActionRunEdit::MacroActionRunEdit(
|
||||
_filePath = new FileSelection();
|
||||
_argList = new QListWidget();
|
||||
_addArg = new QPushButton();
|
||||
_addArg->setMaximumSize(QSize(22, 22));
|
||||
_addArg->setMaximumWidth(22);
|
||||
_addArg->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("addIconSmall")));
|
||||
_addArg->setFlat(true);
|
||||
_removeArg = new QPushButton();
|
||||
_removeArg->setMaximumSize(QSize(22, 22));
|
||||
_removeArg->setMaximumWidth(22);
|
||||
_removeArg->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
_removeArg->setFlat(true);
|
||||
_argUp = new QPushButton();
|
||||
_argUp->setMaximumSize(QSize(22, 22));
|
||||
_argUp->setMaximumWidth(22);
|
||||
_argUp->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
_argUp->setFlat(true);
|
||||
_argDown = new QPushButton();
|
||||
_argDown->setMaximumSize(QSize(22, 22));
|
||||
_argDown->setMaximumWidth(22);
|
||||
_argDown->setProperty(
|
||||
"themeID", QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
_argDown->setFlat(true);
|
||||
|
||||
@@ -157,7 +157,7 @@ MacroActionSceneOrderEdit::MacroActionSceneOrderEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneOrder> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, true);
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_actions = new QComboBox();
|
||||
_position = new QSpinBox();
|
||||
|
||||
@@ -55,7 +55,7 @@ int getTransitionOverrideDuration(OBSWeakSource &scene)
|
||||
return duration;
|
||||
}
|
||||
|
||||
bool isUsingFixedLengthTransition(OBSWeakSource &transition)
|
||||
bool isUsingFixedLengthTransition(const OBSWeakSource &transition)
|
||||
{
|
||||
obs_source_t *source = obs_weak_source_get_source(transition);
|
||||
bool ret = obs_transition_fixed(source);
|
||||
@@ -123,15 +123,15 @@ void MacroActionSwitchScene::LogAction()
|
||||
auto t = _scene.GetType();
|
||||
auto sceneName = GetWeakSourceName(_scene.GetScene(false));
|
||||
switch (t) {
|
||||
case SceneSelectionType::SCENE:
|
||||
case SceneSelection::Type::SCENE:
|
||||
vblog(LOG_INFO, "switch to scene '%s'",
|
||||
_scene.ToString().c_str());
|
||||
break;
|
||||
case SceneSelectionType::GROUP:
|
||||
case SceneSelection::Type::GROUP:
|
||||
vblog(LOG_INFO, "switch to scene '%s' (scene group '%s')",
|
||||
sceneName.c_str(), _scene.ToString().c_str());
|
||||
break;
|
||||
case SceneSelectionType::PREVIOUS:
|
||||
case SceneSelection::Type::PREVIOUS:
|
||||
vblog(LOG_INFO, "switch to previous scene '%s'",
|
||||
sceneName.c_str());
|
||||
break;
|
||||
@@ -153,30 +153,6 @@ bool MacroActionSwitchScene::Save(obs_data_t *obj)
|
||||
|
||||
bool MacroActionSwitchScene::Load(obs_data_t *obj)
|
||||
{
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "targetType")) {
|
||||
auto sceneName = obs_data_get_string(obj, "target");
|
||||
obs_data_set_string(obj, "scene", sceneName);
|
||||
auto transitionName = obs_data_get_string(obj, "transition");
|
||||
bool usePreviousScene =
|
||||
strcmp(sceneName, previous_scene_name) == 0;
|
||||
bool useCurrentTransition =
|
||||
strcmp(transitionName, current_transition_name) == 0;
|
||||
obs_data_set_int(
|
||||
obj, "sceneType",
|
||||
(usePreviousScene)
|
||||
? static_cast<int>(SceneSelectionType::PREVIOUS)
|
||||
: obs_data_get_int(obj, "targetType"));
|
||||
obs_data_set_int(
|
||||
obj, "transitionType",
|
||||
(useCurrentTransition)
|
||||
? static_cast<int>(
|
||||
TransitionSelectionType::CURRENT)
|
||||
: static_cast<int>(
|
||||
TransitionSelectionType::TRANSITION));
|
||||
}
|
||||
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_transition.Load(obj);
|
||||
@@ -193,14 +169,14 @@ std::string MacroActionSwitchScene::GetShortDesc()
|
||||
|
||||
MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSwitchScene> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, true, true)),
|
||||
_transitions(new TransitionSelectionWidget(this)),
|
||||
_duration(new DurationSelection(parent, false)),
|
||||
_blockUntilTransitionDone(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"))),
|
||||
_entryLayout(new QHBoxLayout())
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), true, true);
|
||||
_transitions = new TransitionSelectionWidget(this);
|
||||
_duration = new DurationSelection(parent, false);
|
||||
_blockUntilTransitionDone = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.blockUntilTransitionDone"));
|
||||
|
||||
_duration->SpinBox()->setSpecialValueText("-");
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
@@ -214,7 +190,6 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
QWidget::connect(_blockUntilTransitionDone, SIGNAL(stateChanged(int)),
|
||||
this, SLOT(BlockUntilTransitionDoneChanged(int)));
|
||||
|
||||
QHBoxLayout *entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
@@ -222,10 +197,10 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
{"{{blockUntilTransitionDone}}", _blockUntilTransitionDone},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.scene.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
_entryLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addLayout(_entryLayout);
|
||||
mainLayout->addWidget(_blockUntilTransitionDone);
|
||||
setLayout(mainLayout);
|
||||
|
||||
@@ -235,6 +210,7 @@ MacroActionSwitchSceneEdit::MacroActionSwitchSceneEdit(
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_blockUntilTransitionDone->setChecked(
|
||||
_entryData->_blockUntilTransitionDone);
|
||||
SetDurationVisibility();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
@@ -258,6 +234,37 @@ void MacroActionSwitchSceneEdit::BlockUntilTransitionDoneChanged(int state)
|
||||
_entryData->_blockUntilTransitionDone = state;
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SetDurationVisibility()
|
||||
{
|
||||
if (_entryData->_transition.GetType() !=
|
||||
TransitionSelection::Type::TRANSITION) {
|
||||
_duration->show();
|
||||
}
|
||||
const bool fixedDuration = isUsingFixedLengthTransition(
|
||||
_entryData->_transition.GetTransition());
|
||||
_duration->setVisible(!fixedDuration);
|
||||
|
||||
_entryLayout->removeWidget(_scenes);
|
||||
_entryLayout->removeWidget(_transitions);
|
||||
_entryLayout->removeWidget(_duration);
|
||||
clearLayout(_entryLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{transitions}}", _transitions},
|
||||
{"{{duration}}", _duration},
|
||||
};
|
||||
if (fixedDuration) {
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.scene.entry.noDuration"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
} else {
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.scene.entry"),
|
||||
_entryLayout, widgetPlaceholders);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSwitchSceneEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -278,4 +285,5 @@ void MacroActionSwitchSceneEdit::TransitionChanged(const TransitionSelection &t)
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_transition = t;
|
||||
SetDurationVisibility();
|
||||
}
|
||||
|
||||
@@ -61,9 +61,11 @@ protected:
|
||||
TransitionSelectionWidget *_transitions;
|
||||
DurationSelection *_duration;
|
||||
QCheckBox *_blockUntilTransitionDone;
|
||||
QHBoxLayout *_entryLayout;
|
||||
|
||||
std::shared_ptr<MacroActionSwitchScene> _entryData;
|
||||
|
||||
private:
|
||||
void SetDurationVisibility();
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -107,7 +107,7 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneTransform> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, true);
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneTransform.getTransform"));
|
||||
|
||||
@@ -166,7 +166,7 @@ MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, true, true);
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
|
||||
_sourceTypes = new QComboBox();
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_sourceGroups = new QComboBox();
|
||||
|
||||
@@ -9,73 +9,155 @@ bool MacroActionScreenshot::_registered = MacroActionFactory::Register(
|
||||
{MacroActionScreenshot::Create, MacroActionScreenshotEdit::Create,
|
||||
"AdvSceneSwitcher.action.screenshot"});
|
||||
|
||||
bool MacroActionScreenshot::PerformAction()
|
||||
void MacroActionScreenshot::FrontendScreenshot(OBSWeakSource &source)
|
||||
{
|
||||
if (_source) {
|
||||
auto s = obs_weak_source_get_source(_source);
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(26, 0, 0)
|
||||
if (source) {
|
||||
auto s = obs_weak_source_get_source(source);
|
||||
obs_frontend_take_source_screenshot(s);
|
||||
obs_source_release(s);
|
||||
} else {
|
||||
obs_frontend_take_screenshot();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void MacroActionScreenshot::CustomScreenshot(OBSWeakSource &source)
|
||||
{
|
||||
if (!source && _targetType == TargetType::SCENE) {
|
||||
return;
|
||||
}
|
||||
auto s = obs_weak_source_get_source(source);
|
||||
_screenshot.~ScreenshotHelper();
|
||||
new (&_screenshot) ScreenshotHelper(s, false, 0, true, _path);
|
||||
obs_source_release(s);
|
||||
}
|
||||
|
||||
bool MacroActionScreenshot::PerformAction()
|
||||
{
|
||||
OBSWeakSource source = nullptr;
|
||||
switch (_targetType) {
|
||||
case MacroActionScreenshot::TargetType::SOURCE:
|
||||
source = _source;
|
||||
break;
|
||||
case MacroActionScreenshot::TargetType::SCENE:
|
||||
source = _scene.GetScene(false);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (_saveType) {
|
||||
case MacroActionScreenshot::SaveType::OBS_DEFAULT:
|
||||
FrontendScreenshot(source);
|
||||
break;
|
||||
case MacroActionScreenshot::SaveType::CUSTOM:
|
||||
CustomScreenshot(source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionScreenshot::LogAction()
|
||||
{
|
||||
vblog(LOG_INFO, "trigger screenshot for source \"%s\"",
|
||||
GetWeakSourceName(_source).c_str());
|
||||
vblog(LOG_INFO, "trigger screenshot for \"%s\"",
|
||||
_targetType == TargetType::SOURCE
|
||||
? GetWeakSourceName(_source).c_str()
|
||||
: _scene.ToString().c_str());
|
||||
}
|
||||
|
||||
bool MacroActionScreenshot::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
_scene.Save(obj);
|
||||
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
|
||||
obs_data_set_int(obj, "saveType", static_cast<int>(_saveType));
|
||||
obs_data_set_int(obj, "targetType", static_cast<int>(_targetType));
|
||||
obs_data_set_string(obj, "savePath", _path.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionScreenshot::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_scene.Load(obj);
|
||||
const char *sourceName = obs_data_get_string(obj, "source");
|
||||
_source = GetWeakSourceByName(sourceName);
|
||||
_saveType = static_cast<SaveType>(obs_data_get_int(obj, "saveType"));
|
||||
_targetType =
|
||||
static_cast<TargetType>(obs_data_get_int(obj, "targetType"));
|
||||
_path = obs_data_get_string(obj, "savePath");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionScreenshot::GetShortDesc()
|
||||
{
|
||||
if (_source) {
|
||||
if (_targetType == TargetType::SCENE) {
|
||||
return _scene.ToString();
|
||||
} else {
|
||||
return GetWeakSourceName(_source);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
void addOBSMainOutputEntry(QComboBox *cb)
|
||||
static void populateSaveTypeSelection(QComboBox *list)
|
||||
{
|
||||
cb->insertItem(
|
||||
0, obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.mainOutput"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.save.default"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.save.custom"));
|
||||
}
|
||||
|
||||
static void populateTargetTypeSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.type.source"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.type.scene"));
|
||||
}
|
||||
|
||||
MacroActionScreenshotEdit::MacroActionScreenshotEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionScreenshot> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(this, true, false, true, true,
|
||||
true)),
|
||||
_sources(new QComboBox()),
|
||||
_saveType(new QComboBox()),
|
||||
_targetType(new QComboBox()),
|
||||
_savePath(new FileSelection(FileSelection::Type::WRITE, this))
|
||||
{
|
||||
_sources = new QComboBox();
|
||||
populateVideoSelection(_sources, true, false);
|
||||
addOBSMainOutputEntry(_sources);
|
||||
setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.action.screenshot.blackscreenNote"));
|
||||
populateVideoSelection(_sources, true);
|
||||
populateSaveTypeSelection(_saveType);
|
||||
populateTargetTypeSelection(_targetType);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_sources, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(SourceChanged(const QString &)));
|
||||
QWidget::connect(_saveType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SaveTypeChanged(int)));
|
||||
QWidget::connect(_targetType, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(TargetTypeChanged(int)));
|
||||
QWidget::connect(_savePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(PathChanged(const QString &)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{sources}}", _sources},
|
||||
{"{{scenes}}", _scenes},
|
||||
{"{{saveType}}", _saveType},
|
||||
{"{{targetType}}", _targetType},
|
||||
};
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.screenshot.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
layout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(layout);
|
||||
mainLayout->addWidget(_savePath);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -93,8 +175,55 @@ void MacroActionScreenshotEdit::UpdateEntryData()
|
||||
_sources->setCurrentText(
|
||||
GetWeakSourceName(_entryData->_source).c_str());
|
||||
} else {
|
||||
_sources->setCurrentIndex(0);
|
||||
_sources->setCurrentIndex(1);
|
||||
}
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_saveType->setCurrentIndex(static_cast<int>(_entryData->_saveType));
|
||||
_targetType->setCurrentIndex(static_cast<int>(_entryData->_targetType));
|
||||
_savePath->SetPath(QString::fromStdString(_entryData->_path));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SceneChanged(const SceneSelection &s)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_scene = s;
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SaveTypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
}
|
||||
|
||||
_entryData->_saveType =
|
||||
static_cast<MacroActionScreenshot::SaveType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::TargetTypeChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
}
|
||||
|
||||
_entryData->_targetType =
|
||||
static_cast<MacroActionScreenshot::TargetType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::PathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_path = text.toUtf8().constData();
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SourceChanged(const QString &text)
|
||||
@@ -108,3 +237,18 @@ void MacroActionScreenshotEdit::SourceChanged(const QString &text)
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionScreenshotEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_savePath->setVisible(_entryData->_saveType ==
|
||||
MacroActionScreenshot::SaveType::CUSTOM);
|
||||
_sources->setVisible(_entryData->_targetType ==
|
||||
MacroActionScreenshot::TargetType::SOURCE);
|
||||
_scenes->setVisible(_entryData->_targetType ==
|
||||
MacroActionScreenshot::TargetType::SCENE);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "screenshot-helper.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
@@ -16,9 +19,25 @@ public:
|
||||
{
|
||||
return std::make_shared<MacroActionScreenshot>(m);
|
||||
}
|
||||
enum class SaveType {
|
||||
OBS_DEFAULT,
|
||||
CUSTOM,
|
||||
};
|
||||
SaveType _saveType = SaveType::OBS_DEFAULT;
|
||||
enum class TargetType {
|
||||
SOURCE,
|
||||
SCENE,
|
||||
};
|
||||
TargetType _targetType = TargetType::SOURCE;
|
||||
SceneSelection _scene;
|
||||
OBSWeakSource _source;
|
||||
std::string _path = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
|
||||
private:
|
||||
void FrontendScreenshot(OBSWeakSource &);
|
||||
void CustomScreenshot(OBSWeakSource &);
|
||||
|
||||
ScreenshotHelper _screenshot;
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
@@ -40,14 +59,24 @@ public:
|
||||
action));
|
||||
}
|
||||
private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SourceChanged(const QString &text);
|
||||
void SaveTypeChanged(int index);
|
||||
void TargetTypeChanged(int index);
|
||||
void PathChanged(const QString &text);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
QComboBox *_sources;
|
||||
QComboBox *_saveType;
|
||||
QComboBox *_targetType;
|
||||
FileSelection *_savePath;
|
||||
std::shared_ptr<MacroActionScreenshot> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -124,7 +124,7 @@ MacroActionSudioModeEdit::MacroActionSudioModeEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionSudioMode> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox()),
|
||||
_scenes(new SceneSelectionWidget(window(), true, true, true))
|
||||
_scenes(new SceneSelectionWidget(window(), true, true, true, true))
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
|
||||
@@ -49,8 +49,32 @@ void MacroActionTransition::SetTransitionOverride()
|
||||
obs_source_release(scene);
|
||||
}
|
||||
|
||||
#if (LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)) && \
|
||||
(LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(28, 0, 0))
|
||||
void obs_sceneitem_set_transition(obs_sceneitem_t *item, bool show,
|
||||
obs_source_t *transition)
|
||||
{
|
||||
if (show) {
|
||||
obs_sceneitem_set_show_transition(item, transition);
|
||||
} else {
|
||||
obs_sceneitem_set_hide_transition(item, transition);
|
||||
}
|
||||
}
|
||||
|
||||
void obs_sceneitem_set_transition_duration(obs_sceneitem_t *item, bool show,
|
||||
uint32_t duration_ms)
|
||||
{
|
||||
if (show) {
|
||||
obs_sceneitem_set_show_transition_duration(item, duration_ms);
|
||||
} else {
|
||||
obs_sceneitem_set_hide_transition_duration(item, duration_ms);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void MacroActionTransition::SetSourceTransition(bool show)
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
auto transition =
|
||||
obs_weak_source_get_source(_transition.GetTransition());
|
||||
obs_data_t *settings = obs_source_get_settings(transition);
|
||||
@@ -73,6 +97,7 @@ void MacroActionTransition::SetSourceTransition(bool show)
|
||||
}
|
||||
|
||||
obs_source_release(t);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MacroActionTransition::PerformAction()
|
||||
@@ -182,7 +207,7 @@ MacroActionTransitionEdit::MacroActionTransitionEdit(
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox),
|
||||
_sources(new SceneItemSelectionWidget(parent, false)),
|
||||
_scenes(new SceneSelectionWidget(this, false, false, true)),
|
||||
_scenes(new SceneSelectionWidget(this, true, false, false, true)),
|
||||
_setTransition(new QCheckBox),
|
||||
_setDuration(new QCheckBox),
|
||||
_transitions(new TransitionSelectionWidget(this, false)),
|
||||
|
||||
253
src/macro-core/macro-action-variable.cpp
Normal file
253
src/macro-core/macro-action-variable.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
#include "macro-action-variable.hpp"
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
const std::string MacroActionVariable::id = "variable";
|
||||
|
||||
bool MacroActionVariable::_registered = MacroActionFactory::Register(
|
||||
MacroActionVariable::id,
|
||||
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
|
||||
"AdvSceneSwitcher.action.variable"});
|
||||
|
||||
static std::map<MacroActionVariable::Type, std::string> waitTypes = {
|
||||
{MacroActionVariable::Type::SET,
|
||||
"AdvSceneSwitcher.action.variable.type.set"},
|
||||
{MacroActionVariable::Type::APPEND,
|
||||
"AdvSceneSwitcher.action.variable.type.append"},
|
||||
{MacroActionVariable::Type::APPEND_VAR,
|
||||
"AdvSceneSwitcher.action.variable.type.appendVar"},
|
||||
{MacroActionVariable::Type::INCREMENT,
|
||||
"AdvSceneSwitcher.action.variable.type.increment"},
|
||||
{MacroActionVariable::Type::DECREMENT,
|
||||
"AdvSceneSwitcher.action.variable.type.decrement"},
|
||||
};
|
||||
|
||||
static void apppend(Variable &var, const std::string &value)
|
||||
{
|
||||
auto currentValue = var.Value();
|
||||
var.SetValue(currentValue + value);
|
||||
}
|
||||
|
||||
static void modifyNumValue(Variable &var, double val, const bool increment)
|
||||
{
|
||||
double current;
|
||||
if (!var.DoubleValue(current)) {
|
||||
return;
|
||||
}
|
||||
if (increment) {
|
||||
var.SetValue(current + val);
|
||||
} else {
|
||||
var.SetValue(current - val);
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionVariable::PerformAction()
|
||||
{
|
||||
auto var = GetVariableByName(_variableName);
|
||||
if (!var) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (_type) {
|
||||
case MacroActionVariable::Type::SET:
|
||||
var->SetValue(_strValue);
|
||||
break;
|
||||
case MacroActionVariable::Type::APPEND:
|
||||
apppend(*var, _strValue);
|
||||
break;
|
||||
case MacroActionVariable::Type::APPEND_VAR: {
|
||||
auto var2 = GetVariableByName(_variable2Name);
|
||||
if (!var2) {
|
||||
return true;
|
||||
}
|
||||
apppend(*var, var2->Value());
|
||||
break;
|
||||
}
|
||||
case MacroActionVariable::Type::INCREMENT:
|
||||
modifyNumValue(*var, _numValue, true);
|
||||
break;
|
||||
case MacroActionVariable::Type::DECREMENT:
|
||||
modifyNumValue(*var, _numValue, false);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionVariable::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_string(obj, "variableName", _variableName.c_str());
|
||||
obs_data_set_string(obj, "variable2Name", _variable2Name.c_str());
|
||||
obs_data_set_string(obj, "strValue", _strValue.c_str());
|
||||
obs_data_set_double(obj, "numValue", _numValue);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_type));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroActionVariable::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_variableName = obs_data_get_string(obj, "variableName");
|
||||
_variable2Name = obs_data_get_string(obj, "variable2Name");
|
||||
_strValue = obs_data_get_string(obj, "strValue");
|
||||
_numValue = obs_data_get_double(obj, "numValue");
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroActionVariable::GetShortDesc()
|
||||
{
|
||||
return _variableName;
|
||||
}
|
||||
|
||||
static inline void populateTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : waitTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionVariableEdit::MacroActionVariableEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionVariable> entryData)
|
||||
: QWidget(parent),
|
||||
_variables(new VariableSelection(this)),
|
||||
_variables2(new VariableSelection(this)),
|
||||
_actions(new QComboBox()),
|
||||
_strValue(new QLineEdit()),
|
||||
_numValue(new QDoubleSpinBox())
|
||||
{
|
||||
_numValue->setMinimum(-9999999999);
|
||||
_numValue->setMaximum(9999999999);
|
||||
populateTypeSelection(_actions);
|
||||
|
||||
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
||||
this, SLOT(VariableChanged(const QString &)));
|
||||
QWidget::connect(_variables2, SIGNAL(SelectionChanged(const QString &)),
|
||||
this, SLOT(Variable2Changed(const QString &)));
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_strValue, SIGNAL(editingFinished()), this,
|
||||
SLOT(StrValueChanged()));
|
||||
QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(NumValueChanged(double)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{variables}}", _variables}, {"{{variables2}}", _variables2},
|
||||
{"{{actions}}", _actions}, {"{{strValue}}", _strValue},
|
||||
{"{{numValue}}", _numValue},
|
||||
};
|
||||
|
||||
auto mainLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_variables->SetVariable(_entryData->_variableName);
|
||||
_variables2->SetVariable(_entryData->_variable2Name);
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_strValue->setText(QString::fromStdString(_entryData->_strValue));
|
||||
_numValue->setValue(_entryData->_numValue);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::VariableChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_variableName = text.toStdString();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::Variable2Changed(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_variable2Name = text.toStdString();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::ActionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_type = static_cast<MacroActionVariable::Type>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::StrValueChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_strValue = _strValue->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::NumValueChanged(double val)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_numValue = val;
|
||||
}
|
||||
|
||||
void MacroActionVariableEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto type = _entryData->_type;
|
||||
switch (type) {
|
||||
case MacroActionVariable::Type::SET:
|
||||
_variables2->hide();
|
||||
_strValue->show();
|
||||
_numValue->hide();
|
||||
break;
|
||||
case MacroActionVariable::Type::APPEND:
|
||||
_variables2->hide();
|
||||
_strValue->show();
|
||||
_numValue->hide();
|
||||
break;
|
||||
case MacroActionVariable::Type::APPEND_VAR:
|
||||
_variables2->show();
|
||||
_strValue->hide();
|
||||
_numValue->hide();
|
||||
break;
|
||||
case MacroActionVariable::Type::INCREMENT:
|
||||
_variables2->hide();
|
||||
_strValue->hide();
|
||||
_numValue->show();
|
||||
break;
|
||||
case MacroActionVariable::Type::DECREMENT:
|
||||
_variables2->hide();
|
||||
_strValue->hide();
|
||||
_numValue->show();
|
||||
break;
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
76
src/macro-core/macro-action-variable.hpp
Normal file
76
src/macro-core/macro-action-variable.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
class MacroActionVariable : public MacroAction {
|
||||
public:
|
||||
MacroActionVariable(Macro *m) : MacroAction(m) {}
|
||||
bool PerformAction();
|
||||
bool Save(obs_data_t *obj);
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc();
|
||||
std::string GetId() { return id; };
|
||||
static std::shared_ptr<MacroAction> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroActionVariable>(m);
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
SET,
|
||||
APPEND,
|
||||
APPEND_VAR,
|
||||
INCREMENT,
|
||||
DECREMENT,
|
||||
//...
|
||||
};
|
||||
|
||||
Type _type = Type::SET;
|
||||
std::string _variableName = "";
|
||||
std::string _variable2Name = "";
|
||||
std::string _strValue = "";
|
||||
double _numValue = 0;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroActionVariableEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroActionVariableEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionVariable> entryData = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
{
|
||||
return new MacroActionVariableEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroActionVariable>(action));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void VariableChanged(const QString &);
|
||||
void Variable2Changed(const QString &);
|
||||
void ActionChanged(int);
|
||||
void StrValueChanged();
|
||||
void NumValueChanged(double);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
VariableSelection *_variables;
|
||||
VariableSelection *_variables2;
|
||||
QComboBox *_actions;
|
||||
QLineEdit *_strValue;
|
||||
QDoubleSpinBox *_numValue;
|
||||
std::shared_ptr<MacroActionVariable> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
@@ -16,6 +16,7 @@ const static std::map<VCamAction, std::string> actionTypes = {
|
||||
|
||||
bool MacroActionVCam::PerformAction()
|
||||
{
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
switch (_action) {
|
||||
case VCamAction::STOP:
|
||||
if (obs_frontend_virtualcam_active()) {
|
||||
@@ -30,6 +31,7 @@ bool MacroActionVCam::PerformAction()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,25 +9,58 @@ bool MacroActionWebsocket::_registered = MacroActionFactory::Register(
|
||||
{MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create,
|
||||
"AdvSceneSwitcher.action.websocket"});
|
||||
|
||||
bool MacroActionWebsocket::PerformAction()
|
||||
static std::map<MacroActionWebsocket::Type, std::string> actionTypes = {
|
||||
{MacroActionWebsocket::Type::REQUEST,
|
||||
"AdvSceneSwitcher.action.websocket.type.request"},
|
||||
{MacroActionWebsocket::Type::EVENT,
|
||||
"AdvSceneSwitcher.action.websocket.type.event"},
|
||||
};
|
||||
|
||||
void MacroActionWebsocket::SendRequest()
|
||||
{
|
||||
auto connection = GetConnectionByName(_connection);
|
||||
if (!connection) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
connection->SendMsg(_message);
|
||||
}
|
||||
|
||||
bool MacroActionWebsocket::PerformAction()
|
||||
{
|
||||
switch (_type) {
|
||||
case MacroActionWebsocket::Type::REQUEST:
|
||||
SendRequest();
|
||||
break;
|
||||
case MacroActionWebsocket::Type::EVENT:
|
||||
SendWebsocketEvent(_message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MacroActionWebsocket::LogAction()
|
||||
{
|
||||
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
|
||||
_connection.c_str());
|
||||
switch (_type) {
|
||||
case MacroActionWebsocket::Type::REQUEST:
|
||||
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
|
||||
_connection.c_str());
|
||||
break;
|
||||
case MacroActionWebsocket::Type::EVENT:
|
||||
vblog(LOG_INFO, "sent event \"%s\" to connected clients",
|
||||
_message.c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroActionWebsocket::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Save(obj);
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_string(obj, "message", _message.c_str());
|
||||
obs_data_set_string(obj, "connection", _connection.c_str());
|
||||
return true;
|
||||
@@ -36,6 +69,7 @@ bool MacroActionWebsocket::Save(obs_data_t *obj)
|
||||
bool MacroActionWebsocket::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroAction::Load(obj);
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||
_message = obs_data_get_string(obj, "message");
|
||||
_connection = obs_data_get_string(obj, "connection");
|
||||
return true;
|
||||
@@ -43,30 +77,39 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
|
||||
|
||||
std::string MacroActionWebsocket::GetShortDesc()
|
||||
{
|
||||
return _connection;
|
||||
if (_type == Type::REQUEST) {
|
||||
return _connection;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateActionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : actionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionWebsocketEdit::MacroActionWebsocketEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData)
|
||||
: QWidget(parent),
|
||||
_actions(new QComboBox(this)),
|
||||
_message(new ResizingPlainTextEdit(this)),
|
||||
_connection(new ConnectionSelection(this))
|
||||
_connection(new ConnectionSelection(this)),
|
||||
_editLayout(new QHBoxLayout())
|
||||
{
|
||||
populateActionSelection(_actions);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
QWidget::connect(_message, SIGNAL(textChanged()), this,
|
||||
SLOT(MessageChanged()));
|
||||
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
|
||||
this,
|
||||
SLOT(ConnectionSelectionChanged(const QString &)));
|
||||
|
||||
auto *connectionLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{connection}}", _connection},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.websocket.entry"),
|
||||
connectionLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(connectionLayout);
|
||||
mainLayout->addLayout(_editLayout);
|
||||
mainLayout->addWidget(_message);
|
||||
setLayout(mainLayout);
|
||||
|
||||
@@ -75,19 +118,73 @@ MacroActionWebsocketEdit::MacroActionWebsocketEdit(
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::SetupRequestEdit()
|
||||
{
|
||||
_editLayout->removeWidget(_actions);
|
||||
_editLayout->removeWidget(_connection);
|
||||
clearLayout(_editLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{type}}", _actions},
|
||||
{"{{connection}}", _connection},
|
||||
};
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.entry.request"),
|
||||
_editLayout, widgetPlaceholders);
|
||||
_connection->show();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::SetupEventEdit()
|
||||
{
|
||||
_editLayout->removeWidget(_actions);
|
||||
_editLayout->removeWidget(_connection);
|
||||
clearLayout(_editLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{type}}", _actions},
|
||||
{"{{connection}}", _connection},
|
||||
};
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.websocket.entry.event"),
|
||||
_editLayout, widgetPlaceholders);
|
||||
_connection->hide();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_message->setPlainText(QString::fromStdString(_entryData->_message));
|
||||
_connection->SetConnection(_entryData->_connection);
|
||||
|
||||
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
|
||||
SetupRequestEdit();
|
||||
} else {
|
||||
SetupEventEdit();
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::ActionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_type = static_cast<MacroActionWebsocket::Type>(index);
|
||||
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
|
||||
SetupRequestEdit();
|
||||
} else {
|
||||
SetupEventEdit();
|
||||
}
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroActionWebsocketEdit::MessageChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
|
||||
@@ -22,10 +22,18 @@ public:
|
||||
return std::make_shared<MacroActionWebsocket>(m);
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
REQUEST,
|
||||
EVENT,
|
||||
};
|
||||
|
||||
Type _type = Type::REQUEST;
|
||||
std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
std::string _connection;
|
||||
|
||||
private:
|
||||
void SendRequest();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
@@ -47,6 +55,7 @@ public:
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ActionChanged(int);
|
||||
void MessageChanged();
|
||||
void ConnectionSelectionChanged(const QString &);
|
||||
signals:
|
||||
@@ -56,7 +65,12 @@ protected:
|
||||
std::shared_ptr<MacroActionWebsocket> _entryData;
|
||||
|
||||
private:
|
||||
void SetupRequestEdit();
|
||||
void SetupEventEdit();
|
||||
|
||||
QComboBox *_actions;
|
||||
ResizingPlainTextEdit *_message;
|
||||
ConnectionSelection *_connection;
|
||||
QHBoxLayout *_editLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -120,7 +120,7 @@ DurationModifierEdit::DurationModifierEdit(QWidget *parent)
|
||||
_condition = new QComboBox(parent);
|
||||
_duration = new DurationSelection(parent);
|
||||
_toggle = new QPushButton(parent);
|
||||
_toggle->setMaximumSize(22, 22);
|
||||
_toggle->setMaximumWidth(22);
|
||||
_toggle->setIcon(
|
||||
QIcon(QString::fromStdString(getDataFilePath("res/time.svg"))));
|
||||
populateDurationModifierTypes(_condition);
|
||||
@@ -447,6 +447,9 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||
if (idx == 0 && macro->Conditions().size() > 0) {
|
||||
auto newRoot = macro->Conditions().at(0);
|
||||
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(0))
|
||||
->SetRootNode(true);
|
||||
}
|
||||
SetConditionData(*macro);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ static std::string getRemoteData(std::string &url)
|
||||
return readBuffer;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::matchFileContent(QString &filedata)
|
||||
bool MacroConditionFile::MatchFileContent(QString &filedata)
|
||||
{
|
||||
if (_onlyMatchIfChanged) {
|
||||
size_t newHash = strHash(filedata.toUtf8().constData());
|
||||
@@ -50,31 +50,30 @@ bool MacroConditionFile::matchFileContent(QString &filedata)
|
||||
_lastHash = newHash;
|
||||
}
|
||||
|
||||
if (_useRegex) {
|
||||
try {
|
||||
std::regex expr(_text);
|
||||
return std::regex_match(filedata.toStdString(), expr);
|
||||
} catch (const std::regex_error &) {
|
||||
if (_regex.Enabled()) {
|
||||
auto expr = _regex.GetRegularExpression(_text);
|
||||
if (!expr.isValid()) {
|
||||
return false;
|
||||
}
|
||||
auto match = expr.match(filedata);
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
QString text = QString::fromStdString(_text);
|
||||
return compareIgnoringLineEnding(text, filedata);
|
||||
}
|
||||
|
||||
bool MacroConditionFile::checkRemoteFileContent()
|
||||
bool MacroConditionFile::CheckRemoteFileContent()
|
||||
{
|
||||
std::string data = getRemoteData(_file);
|
||||
QString qdata = QString::fromStdString(data);
|
||||
return matchFileContent(qdata);
|
||||
return MatchFileContent(qdata);
|
||||
}
|
||||
|
||||
bool MacroConditionFile::checkLocalFileContent()
|
||||
bool MacroConditionFile::CheckLocalFileContent()
|
||||
{
|
||||
QString t = QString::fromStdString(_text);
|
||||
QFile file(QString::fromStdString(_file));
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -87,28 +86,78 @@ bool MacroConditionFile::checkLocalFileContent()
|
||||
}
|
||||
|
||||
QString filedata = QTextStream(&file).readAll();
|
||||
bool match = matchFileContent(filedata);
|
||||
bool match = MatchFileContent(filedata);
|
||||
|
||||
file.close();
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckCondition()
|
||||
bool MacroConditionFile::CheckChangeContent()
|
||||
{
|
||||
QString filedata;
|
||||
switch (_fileType) {
|
||||
case FileType::LOCAL: {
|
||||
QFile file(QString::fromStdString(_file));
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
return false;
|
||||
}
|
||||
filedata = QTextStream(&file).readAll();
|
||||
file.close();
|
||||
} break;
|
||||
case FileType::REMOTE: {
|
||||
std::string data = getRemoteData(_file);
|
||||
QString filedata = QString::fromStdString(data);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
size_t newHash = strHash(filedata.toUtf8().constData());
|
||||
const bool contentChanged = newHash != _lastHash;
|
||||
_lastHash = newHash;
|
||||
return contentChanged;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckChangeDate()
|
||||
{
|
||||
if (_fileType == FileType::REMOTE) {
|
||||
return checkRemoteFileContent();
|
||||
} else {
|
||||
return checkLocalFileContent();
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(QString::fromStdString(_file));
|
||||
QDateTime newLastMod = QFileInfo(file).lastModified();
|
||||
const bool dateChanged = _lastMod != newLastMod;
|
||||
_lastMod = newLastMod;
|
||||
return dateChanged;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::CheckCondition()
|
||||
{
|
||||
switch (_condition) {
|
||||
case MacroConditionFile::ConditionType::MATCH:
|
||||
if (_fileType == FileType::REMOTE) {
|
||||
return CheckRemoteFileContent();
|
||||
}
|
||||
return CheckLocalFileContent();
|
||||
case MacroConditionFile::ConditionType::CONTENT_CHANGE:
|
||||
return CheckChangeContent();
|
||||
case MacroConditionFile::ConditionType::DATE_CHANGE:
|
||||
return CheckChangeDate();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionFile::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
_regex.Save(obj);
|
||||
obs_data_set_string(obj, "file", _file.c_str());
|
||||
obs_data_set_string(obj, "text", _text.c_str());
|
||||
obs_data_set_int(obj, "fileType", static_cast<int>(_fileType));
|
||||
obs_data_set_bool(obj, "useRegex", _useRegex);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_bool(obj, "useTime", _useTime);
|
||||
obs_data_set_bool(obj, "onlyMatchIfChanged", _onlyMatchIfChanged);
|
||||
return true;
|
||||
@@ -117,10 +166,17 @@ bool MacroConditionFile::Save(obs_data_t *obj)
|
||||
bool MacroConditionFile::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_regex.Load(obj);
|
||||
// TODO: remove in future version
|
||||
if (obs_data_has_user_value(obj, "useRegex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "useRegex"));
|
||||
}
|
||||
_file = obs_data_get_string(obj, "file");
|
||||
_text = obs_data_get_string(obj, "text");
|
||||
_fileType = static_cast<FileType>(obs_data_get_int(obj, "fileType"));
|
||||
_useRegex = obs_data_get_bool(obj, "useRegex");
|
||||
_condition =
|
||||
static_cast<ConditionType>(obs_data_get_int(obj, "condition"));
|
||||
_useTime = obs_data_get_bool(obj, "useTime");
|
||||
_onlyMatchIfChanged = obs_data_get_bool(obj, "onlyMatchIfChanged");
|
||||
return true;
|
||||
@@ -131,41 +187,59 @@ std::string MacroConditionFile::GetShortDesc()
|
||||
return _file;
|
||||
}
|
||||
|
||||
static void populateFileTypes(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.fileTab.local"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.fileTab.remote"));
|
||||
}
|
||||
|
||||
static void populateConditions(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.condition.file.type.match"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.file.type.contentChange"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.file.type.dateChange"));
|
||||
}
|
||||
|
||||
MacroConditionFileEdit::MacroConditionFileEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionFile> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_fileTypes(new QComboBox()),
|
||||
_conditions(new QComboBox()),
|
||||
_filePath(new FileSelection()),
|
||||
_matchText(new ResizingPlainTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent)),
|
||||
_checkModificationDate(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.fileTab.checkfileContentTime"))),
|
||||
_checkFileContent(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent")))
|
||||
{
|
||||
_fileType = new QComboBox();
|
||||
_filePath = new FileSelection();
|
||||
_matchText = new ResizingPlainTextEdit(this);
|
||||
_useRegex = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.fileTab.useRegExp"));
|
||||
_checkModificationDate = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.fileTab.checkfileContentTime"));
|
||||
_checkFileContent = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.fileTab.checkfileContent"));
|
||||
populateFileTypes(_fileTypes);
|
||||
populateConditions(_conditions);
|
||||
|
||||
QWidget::connect(_fileType, SIGNAL(currentIndexChanged(int)), this,
|
||||
QWidget::connect(_fileTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(FileTypeChanged(int)));
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_filePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(PathChanged(const QString &)));
|
||||
QWidget::connect(_matchText, SIGNAL(textChanged()), this,
|
||||
SLOT(MatchTextChanged()));
|
||||
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseRegexChanged(int)));
|
||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||
SLOT(RegexChanged(RegexConfig)));
|
||||
QWidget::connect(_checkModificationDate, SIGNAL(stateChanged(int)),
|
||||
this, SLOT(CheckModificationDateChanged(int)));
|
||||
QWidget::connect(_checkFileContent, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(OnlyMatchIfChangedChanged(int)));
|
||||
|
||||
_fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.local"));
|
||||
_fileType->addItem(obs_module_text("AdvSceneSwitcher.fileTab.remote"));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{fileType}}", _fileType},
|
||||
{"{{fileType}}", _fileTypes},
|
||||
{"{{conditions}}", _conditions},
|
||||
{"{{filePath}}", _filePath},
|
||||
{"{{matchText}}", _matchText},
|
||||
{"{{useRegex}}", _useRegex},
|
||||
{"{{useRegex}}", _regex},
|
||||
{"{{checkModificationDate}}", _checkModificationDate},
|
||||
{"{{checkFileContent}}", _checkFileContent},
|
||||
};
|
||||
@@ -200,16 +274,23 @@ void MacroConditionFileEdit::UpdateEntryData()
|
||||
return;
|
||||
}
|
||||
|
||||
_fileType->setCurrentIndex(static_cast<int>(_entryData->_fileType));
|
||||
|
||||
_fileTypes->setCurrentIndex(static_cast<int>(_entryData->_fileType));
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_filePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_matchText->setPlainText(QString::fromStdString(_entryData->_text));
|
||||
_useRegex->setChecked(_entryData->_useRegex);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_checkModificationDate->setChecked(_entryData->_useTime);
|
||||
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
// TODO: Remove in future version
|
||||
if (!_entryData->_useTime) {
|
||||
_checkModificationDate->hide();
|
||||
}
|
||||
if (!_entryData->_onlyMatchIfChanged) {
|
||||
_checkFileContent->hide();
|
||||
}
|
||||
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::FileTypeChanged(int index)
|
||||
@@ -218,9 +299,10 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
|
||||
return;
|
||||
}
|
||||
|
||||
FileType type = static_cast<FileType>(index);
|
||||
MacroConditionFile::FileType type =
|
||||
static_cast<MacroConditionFile::FileType>(index);
|
||||
|
||||
if (type == FileType::LOCAL) {
|
||||
if (type == MacroConditionFile::FileType::LOCAL) {
|
||||
_filePath->Button()->setDisabled(false);
|
||||
_checkModificationDate->setDisabled(false);
|
||||
} else {
|
||||
@@ -232,6 +314,18 @@ void MacroConditionFileEdit::FileTypeChanged(int index)
|
||||
_entryData->_fileType = type;
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::ConditionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_condition =
|
||||
static_cast<MacroConditionFile::ConditionType>(index);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::PathChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -257,14 +351,16 @@ void MacroConditionFileEdit::MatchTextChanged()
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::UseRegexChanged(int state)
|
||||
void MacroConditionFileEdit::RegexChanged(RegexConfig conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_useRegex = state;
|
||||
_entryData->_regex = conf;
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::CheckModificationDateChanged(int state)
|
||||
@@ -286,3 +382,25 @@ void MacroConditionFileEdit::OnlyMatchIfChangedChanged(int state)
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_onlyMatchIfChanged = state;
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_matchText->setVisible(_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
_regex->setVisible(_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
_checkModificationDate->setVisible(
|
||||
_entryData->_useTime &&
|
||||
_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
_checkFileContent->setVisible(
|
||||
_entryData->_onlyMatchIfChanged &&
|
||||
_entryData->_condition ==
|
||||
MacroConditionFile::ConditionType::MATCH);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "macro.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
@@ -10,11 +11,6 @@
|
||||
#include <QPushButton>
|
||||
#include <QCheckBox>
|
||||
|
||||
enum class FileType {
|
||||
LOCAL,
|
||||
REMOTE,
|
||||
};
|
||||
|
||||
class MacroConditionFile : public MacroCondition {
|
||||
public:
|
||||
MacroConditionFile(Macro *m) : MacroCondition(m) {}
|
||||
@@ -28,17 +24,33 @@ public:
|
||||
return std::make_shared<MacroConditionFile>(m);
|
||||
}
|
||||
|
||||
enum class FileType {
|
||||
LOCAL,
|
||||
REMOTE,
|
||||
};
|
||||
|
||||
enum class ConditionType {
|
||||
MATCH,
|
||||
CONTENT_CHANGE,
|
||||
DATE_CHANGE,
|
||||
};
|
||||
|
||||
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
std::string _text = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
FileType _fileType = FileType::LOCAL;
|
||||
bool _useRegex = false;
|
||||
ConditionType _condition = ConditionType::MATCH;
|
||||
RegexConfig _regex;
|
||||
|
||||
// TODO: Remove in future version
|
||||
bool _useTime = false;
|
||||
bool _onlyMatchIfChanged = false;
|
||||
|
||||
private:
|
||||
bool matchFileContent(QString &filedata);
|
||||
bool checkRemoteFileContent();
|
||||
bool checkLocalFileContent();
|
||||
bool MatchFileContent(QString &filedata);
|
||||
bool CheckRemoteFileContent();
|
||||
bool CheckLocalFileContent();
|
||||
bool CheckChangeContent();
|
||||
bool CheckChangeDate();
|
||||
|
||||
QDateTime _lastMod;
|
||||
size_t _lastHash = 0;
|
||||
@@ -64,23 +76,27 @@ public:
|
||||
|
||||
private slots:
|
||||
void FileTypeChanged(int index);
|
||||
void ConditionChanged(int index);
|
||||
void PathChanged(const QString &text);
|
||||
void MatchTextChanged();
|
||||
void UseRegexChanged(int state);
|
||||
void RegexChanged(RegexConfig);
|
||||
void CheckModificationDateChanged(int state);
|
||||
void OnlyMatchIfChangedChanged(int state);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_fileType;
|
||||
QComboBox *_fileTypes;
|
||||
QComboBox *_conditions;
|
||||
FileSelection *_filePath;
|
||||
ResizingPlainTextEdit *_matchText;
|
||||
QCheckBox *_useRegex;
|
||||
RegexConfigWidget *_regex;
|
||||
QCheckBox *_checkModificationDate;
|
||||
QCheckBox *_checkFileContent;
|
||||
std::shared_ptr<MacroConditionFile> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -56,7 +56,7 @@ bool MacroConditionFilter::Save(obs_data_t *obj)
|
||||
obs_data_set_string(obj, "filter", GetWeakSourceName(_filter).c_str());
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "settings", _settings.c_str());
|
||||
obs_data_set_bool(obj, "regex", _regex);
|
||||
_regex.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,12 @@ bool MacroConditionFilter::Load(obs_data_t *obj)
|
||||
_condition = static_cast<FilterCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_settings = obs_data_get_string(obj, "settings");
|
||||
_regex = obs_data_get_bool(obj, "regex");
|
||||
_regex.Load(obj);
|
||||
// TOOD: remove in future version
|
||||
if (obs_data_has_user_value(obj, "regex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "regex"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -92,18 +97,16 @@ static inline void populateConditionSelection(QComboBox *list)
|
||||
|
||||
MacroConditionFilterEdit::MacroConditionFilterEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionFilter> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_sources(new QComboBox()),
|
||||
_filters(new QComboBox()),
|
||||
_conditions(new QComboBox()),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.getSettings"))),
|
||||
_settings(new ResizingPlainTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent))
|
||||
{
|
||||
_sources = new QComboBox();
|
||||
_filters = new QComboBox();
|
||||
_filters->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
||||
_conditions = new QComboBox();
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.getSettings"));
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_regex = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.condition.filter.regex"));
|
||||
|
||||
populateConditionSelection(_conditions);
|
||||
populateSourcesWithFilterSelection(_sources);
|
||||
|
||||
@@ -117,8 +120,8 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settings, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsChanged()));
|
||||
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RegexChanged(int)));
|
||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||
SLOT(RegexChanged(RegexConfig)));
|
||||
|
||||
QHBoxLayout *line1Layout = new QHBoxLayout;
|
||||
QHBoxLayout *line2Layout = new QHBoxLayout;
|
||||
@@ -194,7 +197,7 @@ void MacroConditionFilterEdit::GetSettingsClicked()
|
||||
}
|
||||
|
||||
QString json = formatJsonString(getSourceSettings(_entryData->_filter));
|
||||
if (_entryData->_regex) {
|
||||
if (_entryData->_regex.Enabled()) {
|
||||
json = escapeForRegex(json);
|
||||
}
|
||||
_settings->setPlainText(json);
|
||||
@@ -213,14 +216,17 @@ void MacroConditionFilterEdit::SettingsChanged()
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::RegexChanged(int state)
|
||||
void MacroConditionFilterEdit::RegexChanged(RegexConfig conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_regex = state;
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::SetSettingsSelectionVisible(bool visible)
|
||||
@@ -244,7 +250,7 @@ void MacroConditionFilterEdit::UpdateEntryData()
|
||||
GetWeakSourceName(_entryData->_filter).c_str());
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
||||
FilterCondition::SETTINGS);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
@@ -29,7 +30,7 @@ public:
|
||||
OBSWeakSource _filter;
|
||||
FilterCondition _condition = FilterCondition::ENABLED;
|
||||
std::string _settings = "";
|
||||
bool _regex = false;
|
||||
RegexConfig _regex;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
@@ -58,7 +59,7 @@ private slots:
|
||||
void ConditionChanged(int cond);
|
||||
void GetSettingsClicked();
|
||||
void SettingsChanged();
|
||||
void RegexChanged(int);
|
||||
void RegexChanged(RegexConfig);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
@@ -68,7 +69,7 @@ protected:
|
||||
QComboBox *_conditions;
|
||||
QPushButton *_getSettings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QCheckBox *_regex;
|
||||
RegexConfigWidget *_regex;
|
||||
|
||||
std::shared_ptr<MacroConditionFilter> _entryData;
|
||||
|
||||
|
||||
@@ -124,6 +124,7 @@ bool MacroConditionMacro::Save(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_counterCondition));
|
||||
obs_data_set_int(obj, "count", _count);
|
||||
obs_data_set_int(obj, "multiStateCount", _multiSateCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -136,6 +137,7 @@ bool MacroConditionMacro::Load(obs_data_t *obj)
|
||||
_counterCondition = static_cast<CounterCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_count = obs_data_get_int(obj, "count");
|
||||
_multiSateCount = obs_data_get_int(obj, "multiStateCount");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ bool MacroConditionSceneOrder::Save(obs_data_t *obj)
|
||||
MacroCondition::Save(obj);
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
_source2.Save(obj, "sceneItem2", "sceneItemTarget2", "sceneItemIdx2");
|
||||
_source2.Save(obj, "sceneItemSelection2");
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_int(obj, "position", _position);
|
||||
return true;
|
||||
@@ -160,7 +160,14 @@ bool MacroConditionSceneOrder::Load(obs_data_t *obj)
|
||||
MacroCondition::Load(obj);
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_source2.Load(obj, "sceneItem2", "sceneItemTarget2", "sceneItemIdx2");
|
||||
// Convert old data format
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "sceneItem2")) {
|
||||
_source2.Load(obj, "sceneItem2", "sceneItemTarget2",
|
||||
"sceneItemIdx2");
|
||||
} else {
|
||||
_source2.Load(obj, "sceneItemSelection2");
|
||||
}
|
||||
_condition = static_cast<SceneOrderCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_position = obs_data_get_int(obj, "position");
|
||||
@@ -191,7 +198,7 @@ MacroConditionSceneOrderEdit::MacroConditionSceneOrderEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionSceneOrder> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, true);
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_sources2 = new SceneItemSelectionWidget(parent);
|
||||
_conditions = new QComboBox();
|
||||
|
||||
@@ -33,7 +33,7 @@ bool MacroConditionSceneTransform::Save(obs_data_t *obj)
|
||||
_scene.Save(obj);
|
||||
_source.Save(obj);
|
||||
obs_data_set_string(obj, "settings", _settings.c_str());
|
||||
obs_data_set_bool(obj, "regex", _regex);
|
||||
_regex.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -50,7 +50,12 @@ bool MacroConditionSceneTransform::Load(obs_data_t *obj)
|
||||
_scene.Load(obj);
|
||||
_source.Load(obj);
|
||||
_settings = obs_data_get_string(obj, "settings");
|
||||
_regex = obs_data_get_bool(obj, "regex");
|
||||
_regex.Load(obj);
|
||||
// TOOD: remove in future version
|
||||
if (obs_data_has_user_value(obj, "regex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "regex"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -66,17 +71,16 @@ std::string MacroConditionSceneTransform::GetShortDesc()
|
||||
MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionSceneTransform> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
|
||||
_sources(new SceneItemSelectionWidget(
|
||||
parent, true,
|
||||
SceneItemSelectionWidget::AllSelectionType::ANY)),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.getTransform"))),
|
||||
_settings(new ResizingPlainTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent))
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, true);
|
||||
_sources = new SceneItemSelectionWidget(
|
||||
parent, true, SceneItemSelectionWidget::AllSelectionType::ANY);
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.getTransform"));
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_regex = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.regex"));
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
@@ -88,8 +92,8 @@ MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settings, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsChanged()));
|
||||
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RegexChanged(int)));
|
||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||
SLOT(RegexChanged(RegexConfig)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{scenes}}", _scenes}, {"{{sources}}", _sources},
|
||||
@@ -132,7 +136,7 @@ void MacroConditionSceneTransformEdit::UpdateEntryData()
|
||||
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
|
||||
|
||||
adjustSize();
|
||||
@@ -175,7 +179,7 @@ void MacroConditionSceneTransformEdit::GetSettingsClicked()
|
||||
}
|
||||
|
||||
auto settings = formatJsonString(getSceneItemTransform(items[0]));
|
||||
if (_entryData->_regex) {
|
||||
if (_entryData->_regex.Enabled()) {
|
||||
settings = escapeForRegex(settings);
|
||||
}
|
||||
_settings->setPlainText(settings);
|
||||
@@ -198,12 +202,15 @@ void MacroConditionSceneTransformEdit::SettingsChanged()
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::RegexChanged(int state)
|
||||
void MacroConditionSceneTransformEdit::RegexChanged(RegexConfig conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_regex = state;
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QCheckBox>
|
||||
@@ -22,7 +23,7 @@ public:
|
||||
|
||||
SceneSelection _scene;
|
||||
SceneItemSelection _source;
|
||||
bool _regex = false;
|
||||
RegexConfig _regex;
|
||||
std::string _settings = "";
|
||||
|
||||
private:
|
||||
@@ -53,7 +54,7 @@ private slots:
|
||||
void SourceChanged(const SceneItemSelection &);
|
||||
void GetSettingsClicked();
|
||||
void SettingsChanged();
|
||||
void RegexChanged(int);
|
||||
void RegexChanged(RegexConfig);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
@@ -62,7 +63,7 @@ protected:
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QPushButton *_getSettings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QCheckBox *_regex;
|
||||
RegexConfigWidget *_regex;
|
||||
|
||||
std::shared_ptr<MacroConditionSceneTransform> _entryData;
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ MacroConditionSceneVisibilityEdit::MacroConditionSceneVisibilityEdit(
|
||||
std::shared_ptr<MacroConditionSceneVisibility> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, true, true);
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_conditions = new QComboBox();
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ MacroConditionSceneEdit::MacroConditionSceneEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionScene> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, false);
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, false, false);
|
||||
_sceneType = new QComboBox();
|
||||
_useTransitionTargetScene = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour"));
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include "utility.hpp"
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
const std::string MacroConditionSource::id = "source";
|
||||
|
||||
bool MacroConditionSource::_registered = MacroConditionFactory::Register(
|
||||
@@ -55,7 +53,7 @@ bool MacroConditionSource::Save(obs_data_t *obj)
|
||||
obs_data_set_string(obj, "source", GetWeakSourceName(_source).c_str());
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "settings", _settings.c_str());
|
||||
obs_data_set_bool(obj, "regex", _regex);
|
||||
_regex.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -67,7 +65,12 @@ bool MacroConditionSource::Load(obs_data_t *obj)
|
||||
_condition = static_cast<SourceCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
_settings = obs_data_get_string(obj, "settings");
|
||||
_regex = obs_data_get_bool(obj, "regex");
|
||||
_regex.Load(obj);
|
||||
// TOOD: remove in future version
|
||||
if (obs_data_has_user_value(obj, "regex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "regex"));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -88,16 +91,14 @@ static inline void populateConditionSelection(QComboBox *list)
|
||||
|
||||
MacroConditionSourceEdit::MacroConditionSourceEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionSource> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_sources(new QComboBox()),
|
||||
_conditions(new QComboBox()),
|
||||
_getSettings(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.getSettings"))),
|
||||
_settings(new ResizingPlainTextEdit(this)),
|
||||
_regex(new RegexConfigWidget(parent))
|
||||
{
|
||||
_sources = new QComboBox();
|
||||
_conditions = new QComboBox();
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.source.getSettings"));
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_regex = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.condition.source.regex"));
|
||||
|
||||
populateConditionSelection(_conditions);
|
||||
populateSourceSelection(_sources);
|
||||
|
||||
@@ -109,8 +110,8 @@ MacroConditionSourceEdit::MacroConditionSourceEdit(
|
||||
SLOT(GetSettingsClicked()));
|
||||
QWidget::connect(_settings, SIGNAL(textChanged()), this,
|
||||
SLOT(SettingsChanged()));
|
||||
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RegexChanged(int)));
|
||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||
SLOT(RegexChanged(RegexConfig)));
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
QHBoxLayout *line1Layout = new QHBoxLayout;
|
||||
@@ -171,7 +172,7 @@ void MacroConditionSourceEdit::GetSettingsClicked()
|
||||
}
|
||||
|
||||
QString json = formatJsonString(getSourceSettings(_entryData->_source));
|
||||
if (_entryData->_regex) {
|
||||
if (_entryData->_regex.Enabled()) {
|
||||
json = escapeForRegex(json);
|
||||
}
|
||||
_settings->setPlainText(json);
|
||||
@@ -190,14 +191,17 @@ void MacroConditionSourceEdit::SettingsChanged()
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSourceEdit::RegexChanged(int state)
|
||||
void MacroConditionSourceEdit::RegexChanged(RegexConfig conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_regex = state;
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSourceEdit::SetSettingsSelectionVisible(bool visible)
|
||||
@@ -218,7 +222,7 @@ void MacroConditionSourceEdit::UpdateEntryData()
|
||||
GetWeakSourceName(_entryData->_source).c_str());
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
||||
SourceCondition::SETTINGS);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
@@ -28,7 +29,7 @@ public:
|
||||
OBSWeakSource _source = nullptr;
|
||||
SourceCondition _condition = SourceCondition::ACTIVE;
|
||||
std::string _settings = "";
|
||||
bool _regex = false;
|
||||
RegexConfig _regex;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
@@ -56,7 +57,7 @@ private slots:
|
||||
void ConditionChanged(int cond);
|
||||
void GetSettingsClicked();
|
||||
void SettingsChanged();
|
||||
void RegexChanged(int);
|
||||
void RegexChanged(RegexConfig);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
@@ -65,7 +66,7 @@ protected:
|
||||
QComboBox *_conditions;
|
||||
QPushButton *_getSettings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QCheckBox *_regex;
|
||||
RegexConfigWidget *_regex;
|
||||
|
||||
std::shared_ptr<MacroConditionSource> _entryData;
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ MacroConditionStudioModeEdit::MacroConditionStudioModeEdit(
|
||||
: QWidget(parent)
|
||||
{
|
||||
_condition = new QComboBox();
|
||||
_scenes = new SceneSelectionWidget(window());
|
||||
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
|
||||
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ bool MacroConditionTransition::CheckCondition()
|
||||
bool transitionStarted = false;
|
||||
bool transitionEnded = false;
|
||||
|
||||
if (_transition.GetType() == TransitionSelectionType::ANY) {
|
||||
if (_transition.GetType() == TransitionSelection::Type::ANY) {
|
||||
transitionStarted = anyTransitionStarted;
|
||||
transitionEnded = anyTransitionEnded;
|
||||
} else {
|
||||
@@ -183,7 +183,7 @@ MacroConditionTransitionEdit::MacroConditionTransitionEdit(
|
||||
{
|
||||
_conditions = new QComboBox();
|
||||
_transitions = new TransitionSelectionWidget(this, true, true);
|
||||
_scenes = new SceneSelectionWidget(this, false, true, true);
|
||||
_scenes = new SceneSelectionWidget(this, true, false, true, true);
|
||||
_duration = new DurationSelection(this, false);
|
||||
_durationSuffix = new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.transition.durationSuffix"));
|
||||
|
||||
283
src/macro-core/macro-condition-variable.cpp
Normal file
283
src/macro-core/macro-condition-variable.cpp
Normal file
@@ -0,0 +1,283 @@
|
||||
#include "macro-condition-edit.hpp"
|
||||
#include "macro-condition-variable.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
|
||||
#include <regex>
|
||||
|
||||
const std::string MacroConditionVariable::id = "variable";
|
||||
|
||||
bool MacroConditionVariable::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionVariable::id,
|
||||
{MacroConditionVariable::Create, MacroConditionVariableEdit::Create,
|
||||
"AdvSceneSwitcher.condition.variable"});
|
||||
|
||||
const static std::map<MacroConditionVariable::Type, std::string>
|
||||
conditionTypes = {
|
||||
{MacroConditionVariable::Type::EQUALS,
|
||||
"AdvSceneSwitcher.condition.variable.type.compare"},
|
||||
{MacroConditionVariable::Type::IS_EMPTY,
|
||||
"AdvSceneSwitcher.condition.variable.type.empty"},
|
||||
{MacroConditionVariable::Type::IS_NUMBER,
|
||||
"AdvSceneSwitcher.condition.variable.type.number"},
|
||||
{MacroConditionVariable::Type::LESS_THAN,
|
||||
"AdvSceneSwitcher.condition.variable.type.lessThan"},
|
||||
{MacroConditionVariable::Type::GREATER_THAN,
|
||||
"AdvSceneSwitcher.condition.variable.type.greaterThan"},
|
||||
{MacroConditionVariable::Type::VALUE_CHANGED,
|
||||
"AdvSceneSwitcher.condition.variable.type.valueChanged"},
|
||||
};
|
||||
|
||||
static bool isNumber(const Variable &var)
|
||||
{
|
||||
double _;
|
||||
return var.DoubleValue(_);
|
||||
}
|
||||
|
||||
static bool compareNumber(const Variable &var, double value, bool less)
|
||||
{
|
||||
double varValue;
|
||||
|
||||
if (!var.DoubleValue(varValue)) {
|
||||
return false;
|
||||
}
|
||||
if (less) {
|
||||
return varValue < value;
|
||||
}
|
||||
return varValue > value;
|
||||
}
|
||||
|
||||
bool MacroConditionVariable::Compare(const Variable &var) const
|
||||
{
|
||||
if (_regex) {
|
||||
try {
|
||||
std::regex expr(_strValue);
|
||||
return std::regex_match(var.Value(), expr);
|
||||
} catch (const std::regex_error &) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return _strValue == var.Value();
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroConditionVariable::ValueChanged(const Variable &var)
|
||||
{
|
||||
bool changed = var.Value() != _lastValue;
|
||||
if (changed) {
|
||||
_lastValue = var.Value();
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool MacroConditionVariable::CheckCondition()
|
||||
{
|
||||
auto var = GetVariableByName(_variableName);
|
||||
if (!var) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (_type) {
|
||||
case MacroConditionVariable::Type::EQUALS:
|
||||
return Compare(*var);
|
||||
case MacroConditionVariable::Type::IS_EMPTY:
|
||||
return var->Value().empty();
|
||||
case MacroConditionVariable::Type::IS_NUMBER:
|
||||
return isNumber(*var);
|
||||
case MacroConditionVariable::Type::LESS_THAN:
|
||||
return compareNumber(*var, _numValue, true);
|
||||
case MacroConditionVariable::Type::GREATER_THAN:
|
||||
return compareNumber(*var, _numValue, false);
|
||||
case MacroConditionVariable::Type::VALUE_CHANGED:
|
||||
return ValueChanged(*var);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionVariable::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_string(obj, "variableName", _variableName.c_str());
|
||||
obs_data_set_string(obj, "strValue", _strValue.c_str());
|
||||
obs_data_set_double(obj, "numValue", _numValue);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_type));
|
||||
obs_data_set_bool(obj, "regex", _regex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionVariable::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_variableName = obs_data_get_string(obj, "variableName");
|
||||
_strValue = obs_data_get_string(obj, "strValue");
|
||||
_numValue = obs_data_get_double(obj, "numValue");
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "condition"));
|
||||
_regex = obs_data_get_bool(obj, "regex");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionVariable::GetShortDesc()
|
||||
{
|
||||
return _variableName;
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : conditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionVariableEdit::MacroConditionVariableEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionVariable> entryData)
|
||||
: QWidget(parent),
|
||||
_variables(new VariableSelection(this)),
|
||||
_conditions(new QComboBox()),
|
||||
_strValue(new QLineEdit()),
|
||||
_numValue(new QDoubleSpinBox()),
|
||||
_regex(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.condition.variable.regex")))
|
||||
{
|
||||
_numValue->setMinimum(-9999999999);
|
||||
_numValue->setMaximum(9999999999);
|
||||
populateConditionSelection(_conditions);
|
||||
|
||||
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
|
||||
this, SLOT(VariableChanged(const QString &)));
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_strValue, SIGNAL(editingFinished()), this,
|
||||
SLOT(StrValueChanged()));
|
||||
QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(NumValueChanged(double)));
|
||||
QWidget::connect(_regex, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RegexChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{variables}}", _variables}, {"{{conditions}}", _conditions},
|
||||
{"{{strValue}}", _strValue}, {"{{numValue}}", _numValue},
|
||||
{"{{regex}}", _regex},
|
||||
};
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.variable.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(layout);
|
||||
mainLayout->addWidget(_regex);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_variables->SetVariable(_entryData->_variableName);
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_strValue->setText(QString::fromStdString(_entryData->_strValue));
|
||||
_numValue->setValue(_entryData->_numValue);
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::VariableChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_variableName = text.toStdString();
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::ConditionChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_type = static_cast<MacroConditionVariable::Type>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::StrValueChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_strValue = _strValue->text().toStdString();
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::NumValueChanged(double val)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_numValue = val;
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::RegexChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_regex = state;
|
||||
}
|
||||
|
||||
void MacroConditionVariableEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_entryData->_type) {
|
||||
case MacroConditionVariable::Type::EQUALS:
|
||||
_regex->show();
|
||||
_strValue->show();
|
||||
_numValue->hide();
|
||||
break;
|
||||
case MacroConditionVariable::Type::IS_EMPTY:
|
||||
_regex->hide();
|
||||
_strValue->hide();
|
||||
_numValue->hide();
|
||||
break;
|
||||
case MacroConditionVariable::Type::IS_NUMBER:
|
||||
_regex->hide();
|
||||
_strValue->hide();
|
||||
_numValue->hide();
|
||||
break;
|
||||
case MacroConditionVariable::Type::LESS_THAN:
|
||||
_regex->hide();
|
||||
_strValue->hide();
|
||||
_numValue->show();
|
||||
break;
|
||||
case MacroConditionVariable::Type::GREATER_THAN:
|
||||
_regex->hide();
|
||||
_strValue->hide();
|
||||
_numValue->show();
|
||||
break;
|
||||
case MacroConditionVariable::Type::VALUE_CHANGED:
|
||||
_regex->hide();
|
||||
_strValue->hide();
|
||||
_numValue->hide();
|
||||
break;
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
88
src/macro-core/macro-condition-variable.hpp
Normal file
88
src/macro-core/macro-condition-variable.hpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QLineEdit>
|
||||
#include <QCheckBox>
|
||||
|
||||
class MacroConditionVariable : public MacroCondition {
|
||||
public:
|
||||
MacroConditionVariable(Macro *m) : MacroCondition(m) {}
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj);
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc();
|
||||
std::string GetId() { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionVariable>(m);
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
EQUALS,
|
||||
IS_EMPTY,
|
||||
IS_NUMBER,
|
||||
LESS_THAN,
|
||||
GREATER_THAN,
|
||||
VALUE_CHANGED,
|
||||
//...
|
||||
};
|
||||
|
||||
Type _type = Type::EQUALS;
|
||||
std::string _variableName = "";
|
||||
std::string _strValue = "";
|
||||
double _numValue = 0;
|
||||
bool _regex = false;
|
||||
|
||||
private:
|
||||
bool Compare(const Variable &) const;
|
||||
bool ValueChanged(const Variable &);
|
||||
|
||||
std::string _lastValue = "";
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionVariableEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionVariableEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionVariable> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionVariableEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionVariable>(
|
||||
cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void VariableChanged(const QString &);
|
||||
void ConditionChanged(int);
|
||||
void StrValueChanged();
|
||||
void NumValueChanged(double);
|
||||
void RegexChanged(int state);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
VariableSelection *_variables;
|
||||
QComboBox *_conditions;
|
||||
QLineEdit *_strValue;
|
||||
QDoubleSpinBox *_numValue;
|
||||
QCheckBox *_regex;
|
||||
std::shared_ptr<MacroConditionVariable> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
@@ -20,6 +20,7 @@ static std::map<VCamState, std::string> VCamStates = {
|
||||
bool MacroConditionVCam::CheckCondition()
|
||||
{
|
||||
bool stateMatch = false;
|
||||
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(27, 0, 0)
|
||||
switch (_state) {
|
||||
case VCamState::STOP:
|
||||
stateMatch = !obs_frontend_virtualcam_active();
|
||||
@@ -30,6 +31,7 @@ bool MacroConditionVCam::CheckCondition()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
return stateMatch;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,21 +12,50 @@ bool MacroConditionWebsocket::_registered = MacroConditionFactory::Register(
|
||||
{MacroConditionWebsocket::Create, MacroConditionWebsocketEdit::Create,
|
||||
"AdvSceneSwitcher.condition.websocket"});
|
||||
|
||||
static bool matchRegex(const std::string &msg, const std::string &expr)
|
||||
static std::map<MacroConditionWebsocket::Type, std::string> conditionTypes = {
|
||||
{MacroConditionWebsocket::Type::REQUEST,
|
||||
"AdvSceneSwitcher.condition.websocket.type.request"},
|
||||
{MacroConditionWebsocket::Type::EVENT,
|
||||
"AdvSceneSwitcher.condition.websocket.type.event"},
|
||||
};
|
||||
|
||||
static bool matchRegex(const RegexConfig &conf, const std::string &msg,
|
||||
const std::string &expr)
|
||||
{
|
||||
try {
|
||||
std::regex regExpr(expr);
|
||||
return std::regex_match(msg, regExpr);
|
||||
} catch (const std::regex_error &) {
|
||||
auto regex = conf.GetRegularExpression(expr);
|
||||
if (!regex.isValid()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
auto match = regex.match(QString::fromStdString(msg));
|
||||
return match.hasMatch();
|
||||
}
|
||||
|
||||
bool MacroConditionWebsocket::CheckCondition()
|
||||
{
|
||||
for (const auto &msg : switcher->websocketMessages) {
|
||||
if (_useRegex) {
|
||||
if (matchRegex(msg, _message)) {
|
||||
const std::vector<std::string> *messages = nullptr;
|
||||
switch (_type) {
|
||||
case MacroConditionWebsocket::Type::REQUEST:
|
||||
messages = &switcher->websocketMessages;
|
||||
break;
|
||||
case MacroConditionWebsocket::Type::EVENT: {
|
||||
auto connection = GetConnectionByName(_connection);
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
messages = &connection->Events();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!messages) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto &msg : *messages) {
|
||||
if (_regex.Enabled()) {
|
||||
if (matchRegex(_regex, msg, _message)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
@@ -41,42 +70,72 @@ bool MacroConditionWebsocket::CheckCondition()
|
||||
bool MacroConditionWebsocket::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_string(obj, "message", _message.c_str());
|
||||
obs_data_set_bool(obj, "useRegex", _useRegex);
|
||||
_regex.Save(obj);
|
||||
obs_data_set_string(obj, "connection", _connection.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionWebsocket::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
|
||||
_message = obs_data_get_string(obj, "message");
|
||||
_useRegex = obs_data_get_bool(obj, "useRegex");
|
||||
_regex.Load(obj);
|
||||
// TOOD: remove in future version
|
||||
if (obs_data_has_user_value(obj, "useRegex")) {
|
||||
_regex.CreateBackwardsCompatibleRegex(
|
||||
obs_data_get_bool(obj, "useRegex"), false);
|
||||
}
|
||||
_connection = obs_data_get_string(obj, "connection");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionWebsocket::GetShortDesc()
|
||||
{
|
||||
return "";
|
||||
if (_type == Type::REQUEST) {
|
||||
return "";
|
||||
}
|
||||
return _connection;
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : conditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionWebsocket> entryData)
|
||||
: QWidget(parent),
|
||||
_conditions(new QComboBox(this)),
|
||||
_message(new ResizingPlainTextEdit(this)),
|
||||
_useRegex(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.websocket.useRegex")))
|
||||
_regex(new RegexConfigWidget(parent)),
|
||||
_connection(new ConnectionSelection(this)),
|
||||
_editLayout(new QHBoxLayout())
|
||||
{
|
||||
populateConditionSelection(_conditions);
|
||||
|
||||
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_message, SIGNAL(textChanged()), this,
|
||||
SLOT(MessageChanged()));
|
||||
QWidget::connect(_useRegex, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(UseRegexChanged(int)));
|
||||
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
|
||||
SLOT(RegexChanged(RegexConfig)));
|
||||
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
|
||||
this,
|
||||
SLOT(ConnectionSelectionChanged(const QString &)));
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
|
||||
mainLayout->addWidget(new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.condition.websocket.entry")));
|
||||
mainLayout->addLayout(_editLayout);
|
||||
mainLayout->addWidget(_message);
|
||||
mainLayout->addWidget(_useRegex);
|
||||
auto regexLayout = new QHBoxLayout;
|
||||
regexLayout->addWidget(_regex);
|
||||
regexLayout->addStretch();
|
||||
regexLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->addLayout(regexLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -84,19 +143,76 @@ MacroConditionWebsocketEdit::MacroConditionWebsocketEdit(
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::SetupRequestEdit()
|
||||
{
|
||||
_editLayout->removeWidget(_conditions);
|
||||
_editLayout->removeWidget(_connection);
|
||||
clearLayout(_editLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{type}}", _conditions},
|
||||
{"{{connection}}", _connection},
|
||||
};
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.websocket.entry.request"),
|
||||
_editLayout, widgetPlaceholders);
|
||||
_connection->hide();
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::SetupEventEdit()
|
||||
{
|
||||
_editLayout->removeWidget(_conditions);
|
||||
_editLayout->removeWidget(_connection);
|
||||
clearLayout(_editLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{type}}", _conditions},
|
||||
{"{{connection}}", _connection},
|
||||
};
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.websocket.entry.event"),
|
||||
_editLayout, widgetPlaceholders);
|
||||
_connection->show();
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_conditions->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_message->setPlainText(QString::fromStdString(_entryData->_message));
|
||||
_useRegex->setChecked(_entryData->_useRegex);
|
||||
_regex->SetRegexConfig(_entryData->_regex);
|
||||
_connection->SetConnection(_entryData->_connection);
|
||||
|
||||
if (_entryData->_type == MacroConditionWebsocket::Type::REQUEST) {
|
||||
SetupRequestEdit();
|
||||
} else {
|
||||
SetupEventEdit();
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::ConditionChanged(int index)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_type = static_cast<MacroConditionWebsocket::Type>(index);
|
||||
if (_entryData->_type == MacroConditionWebsocket::Type::REQUEST) {
|
||||
SetupRequestEdit();
|
||||
} else {
|
||||
SetupEventEdit();
|
||||
}
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::MessageChanged()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -110,12 +226,27 @@ void MacroConditionWebsocketEdit::MessageChanged()
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::UseRegexChanged(int state)
|
||||
void MacroConditionWebsocketEdit::ConnectionSelectionChanged(
|
||||
const QString &connection)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_useRegex = state;
|
||||
_entryData->_connection = connection.toStdString();
|
||||
emit(HeaderInfoChanged(connection));
|
||||
}
|
||||
|
||||
void MacroConditionWebsocketEdit::RegexChanged(RegexConfig conf)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_regex = conf;
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "connection-manager.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
@@ -17,8 +19,15 @@ public:
|
||||
return std::make_shared<MacroConditionWebsocket>(m);
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
REQUEST,
|
||||
EVENT,
|
||||
};
|
||||
|
||||
Type _type = Type::REQUEST;
|
||||
std::string _message = obs_module_text("AdvSceneSwitcher.enterText");
|
||||
bool _useRegex = false;
|
||||
RegexConfig _regex;
|
||||
std::string _connection;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
@@ -43,16 +52,24 @@ public:
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ConditionChanged(int);
|
||||
void MessageChanged();
|
||||
void UseRegexChanged(int state);
|
||||
void RegexChanged(RegexConfig);
|
||||
void ConnectionSelectionChanged(const QString &);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
ResizingPlainTextEdit *_message;
|
||||
QCheckBox *_useRegex;
|
||||
std::shared_ptr<MacroConditionWebsocket> _entryData;
|
||||
|
||||
private:
|
||||
void SetupRequestEdit();
|
||||
void SetupEventEdit();
|
||||
|
||||
QComboBox *_conditions;
|
||||
ResizingPlainTextEdit *_message;
|
||||
RegexConfigWidget *_regex;
|
||||
ConnectionSelection *_connection;
|
||||
QHBoxLayout *_editLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -10,6 +10,8 @@ void MacroProperties::Save(obs_data_t *obj)
|
||||
obs_data_set_bool(data, "highlightExecuted", _highlightExecuted);
|
||||
obs_data_set_bool(data, "highlightConditions", _highlightConditions);
|
||||
obs_data_set_bool(data, "highlightActions", _highlightActions);
|
||||
obs_data_set_bool(data, "newMacroRegisterHotkey",
|
||||
_newMacroRegisterHotkeys);
|
||||
obs_data_set_obj(obj, "macroProperties", data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
@@ -17,16 +19,11 @@ void MacroProperties::Save(obs_data_t *obj)
|
||||
void MacroProperties::Load(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, "macroProperties");
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "highlightExecutedMacros")) {
|
||||
_highlightExecuted =
|
||||
obs_data_get_bool(obj, "highlightExecutedMacros");
|
||||
} else {
|
||||
_highlightExecuted =
|
||||
obs_data_get_bool(data, "highlightExecuted");
|
||||
}
|
||||
_highlightExecuted = obs_data_get_bool(data, "highlightExecuted");
|
||||
_highlightConditions = obs_data_get_bool(data, "highlightConditions");
|
||||
_highlightActions = obs_data_get_bool(data, "highlightActions");
|
||||
_newMacroRegisterHotkeys =
|
||||
obs_data_get_bool(data, "newMacroRegisterHotkey");
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
@@ -40,8 +37,10 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
||||
"AdvSceneSwitcher.macroTab.highlightTrueConditions"))),
|
||||
_actions(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.highlightPerformedActions"))),
|
||||
_hotkeys(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.disableHotkeys")))
|
||||
_newMacroRegisterHotkeys(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.newMacroRegisterHotkey"))),
|
||||
_currentMacroRegisterHotkeys(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.currentDisableHotkeys")))
|
||||
{
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
@@ -52,17 +51,26 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
||||
_executed->setChecked(prop._highlightExecuted);
|
||||
_conditions->setChecked(prop._highlightConditions);
|
||||
_actions->setChecked(prop._highlightActions);
|
||||
_newMacroRegisterHotkeys->setChecked(prop._newMacroRegisterHotkeys);
|
||||
if (macro) {
|
||||
_hotkeys->setChecked(macro->PauseHotkeysEnabled());
|
||||
_currentMacroRegisterHotkeys->setChecked(
|
||||
macro->PauseHotkeysEnabled());
|
||||
} else {
|
||||
_hotkeys->hide();
|
||||
_currentMacroRegisterHotkeys->hide();
|
||||
}
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(_executed);
|
||||
layout->addWidget(_conditions);
|
||||
layout->addWidget(_actions);
|
||||
layout->addWidget(_hotkeys);
|
||||
layout->addWidget(_newMacroRegisterHotkeys);
|
||||
if (macro) {
|
||||
QFrame *line = new QFrame();
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
layout->addWidget(line);
|
||||
}
|
||||
layout->addWidget(_currentMacroRegisterHotkeys);
|
||||
setLayout(layout);
|
||||
|
||||
QDialogButtonBox *buttonbox = new QDialogButtonBox(
|
||||
@@ -85,8 +93,11 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
|
||||
userInput._highlightExecuted = dialog._executed->isChecked();
|
||||
userInput._highlightConditions = dialog._conditions->isChecked();
|
||||
userInput._highlightActions = dialog._actions->isChecked();
|
||||
userInput._newMacroRegisterHotkeys =
|
||||
dialog._newMacroRegisterHotkeys->isChecked();
|
||||
if (macro) {
|
||||
macro->EnablePauseHotkeys(dialog._hotkeys->isChecked());
|
||||
macro->EnablePauseHotkeys(
|
||||
dialog._currentMacroRegisterHotkeys->isChecked());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ public:
|
||||
bool _highlightExecuted = false;
|
||||
bool _highlightConditions = false;
|
||||
bool _highlightActions = false;
|
||||
bool _newMacroRegisterHotkeys = true;
|
||||
};
|
||||
|
||||
// Dialog for configuring global and macro specific settings
|
||||
@@ -31,5 +32,7 @@ private:
|
||||
QCheckBox *_executed;
|
||||
QCheckBox *_conditions;
|
||||
QCheckBox *_actions;
|
||||
QCheckBox *_hotkeys;
|
||||
QCheckBox *_newMacroRegisterHotkeys;
|
||||
// Current macro specific settings
|
||||
QCheckBox *_currentMacroRegisterHotkeys;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <QString>
|
||||
#include <obs-module.h>
|
||||
|
||||
|
||||
@@ -58,8 +58,9 @@ bool AdvSceneSwitcher::addNewMacro(std::string &name, std::string format)
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->macros.emplace_back(
|
||||
std::make_shared<Macro>(name, true));
|
||||
switcher->macros.emplace_back(std::make_shared<Macro>(
|
||||
name,
|
||||
switcher->macroProperties._newMacroRegisterHotkeys));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ Macro::Macro(const std::string &name, const bool addHotkey)
|
||||
if (addHotkey) {
|
||||
SetupHotkeys();
|
||||
}
|
||||
_registerHotkeys = addHotkey;
|
||||
}
|
||||
|
||||
Macro::~Macro()
|
||||
|
||||
@@ -82,6 +82,10 @@ bool MacroConditionVideo::CheckCondition()
|
||||
return _lastMatchResult;
|
||||
}
|
||||
|
||||
if (_blockUntilScreenshotDone) {
|
||||
GetScreenshot(true);
|
||||
}
|
||||
|
||||
if (_screenshotData.done) {
|
||||
match = Compare();
|
||||
_lastMatchResult = match;
|
||||
@@ -94,7 +98,7 @@ bool MacroConditionVideo::CheckCondition()
|
||||
match = _lastMatchResult;
|
||||
}
|
||||
|
||||
if (_getNextScreenshot) {
|
||||
if (!_blockUntilScreenshotDone && _getNextScreenshot) {
|
||||
GetScreenshot();
|
||||
}
|
||||
return match;
|
||||
@@ -106,6 +110,8 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
|
||||
_video.Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "filePath", _file.c_str());
|
||||
obs_data_set_bool(obj, "blockUntilScreenshotDone",
|
||||
_blockUntilScreenshotDone);
|
||||
obs_data_set_bool(obj, "usePatternForChangedCheck",
|
||||
_usePatternForChangedCheck);
|
||||
obs_data_set_double(obj, "threshold", _patternThreshold);
|
||||
@@ -143,6 +149,8 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
_condition =
|
||||
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
|
||||
_file = obs_data_get_string(obj, "filePath");
|
||||
_blockUntilScreenshotDone =
|
||||
obs_data_get_bool(obj, "blockUntilScreenshotDone");
|
||||
_usePatternForChangedCheck =
|
||||
obs_data_get_bool(obj, "usePatternForChangedCheck");
|
||||
_patternThreshold = obs_data_get_double(obj, "threshold");
|
||||
@@ -186,11 +194,12 @@ std::string MacroConditionVideo::GetShortDesc()
|
||||
return _video.ToString();
|
||||
}
|
||||
|
||||
void MacroConditionVideo::GetScreenshot()
|
||||
void MacroConditionVideo::GetScreenshot(bool blocking)
|
||||
{
|
||||
auto source = obs_weak_source_get_source(_video.GetVideo());
|
||||
_screenshotData.~ScreenshotHelper();
|
||||
new (&_screenshotData) ScreenshotHelper(source);
|
||||
new (&_screenshotData)
|
||||
ScreenshotHelper(source, blocking, GetSwitcher()->interval);
|
||||
obs_source_release(source);
|
||||
_getNextScreenshot = false;
|
||||
}
|
||||
@@ -200,6 +209,8 @@ bool MacroConditionVideo::LoadImageFromFile()
|
||||
if (!_matchImage.load(QString::fromStdString(_file))) {
|
||||
blog(LOG_WARNING, "Cannot load image data from file '%s'",
|
||||
_file.c_str());
|
||||
(&_matchImage)->~QImage();
|
||||
new (&_matchImage) QImage();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -285,6 +296,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
: QWidget(parent),
|
||||
_videoSelection(new VideoSelectionWidget(this)),
|
||||
_condition(new QComboBox()),
|
||||
_reduceLatency(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.reduceLatency"))),
|
||||
_usePatternForChangedCheck(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))),
|
||||
_imagePath(new FileSelection()),
|
||||
@@ -312,7 +325,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
_minSize(new SizeSelection(0, 1024)),
|
||||
_maxSize(new SizeSelection(0, 4096)),
|
||||
_checkAreaControlLayout(new QHBoxLayout),
|
||||
_checkAreaEnable(new QCheckBox()),
|
||||
_checkAreaEnable(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.checkAreaEnable"))),
|
||||
_checkArea(new AreaSelection(0, 99999)),
|
||||
_selectArea(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectArea"))),
|
||||
@@ -323,6 +337,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
"AdvSceneSwitcher.condition.video.showMatch"))),
|
||||
_previewDialog(this, entryData.get(), &GetSwitcher()->m)
|
||||
{
|
||||
_reduceLatency->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.reduceLatency.tooltip"));
|
||||
_imagePath->Button()->disconnect();
|
||||
_usePatternForChangedCheck->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
|
||||
@@ -338,6 +354,8 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
SLOT(VideoSelectionChanged(const VideoSelection &)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_reduceLatency, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(ReduceLatencyChanged(int)));
|
||||
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
SLOT(ImagePathChanged(const QString &)));
|
||||
QWidget::connect(_imagePath->Button(), SIGNAL(clicked()), this,
|
||||
@@ -380,6 +398,7 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{videoSources}}", _videoSelection},
|
||||
{"{{condition}}", _condition},
|
||||
{"{{reduceLatency}}", _reduceLatency},
|
||||
{"{{imagePath}}", _imagePath},
|
||||
{"{{minNeighbors}}", _minNeighbors},
|
||||
{"{{minSize}}", _minSize},
|
||||
@@ -437,9 +456,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
mainLayout->addLayout(_neighborsControlLayout);
|
||||
mainLayout->addWidget(_minNeighborsDescription);
|
||||
mainLayout->addLayout(_sizeLayout);
|
||||
mainLayout->addLayout(showMatchLayout);
|
||||
mainLayout->addLayout(_throttleControlLayout);
|
||||
mainLayout->addLayout(_checkAreaControlLayout);
|
||||
mainLayout->addWidget(_reduceLatency);
|
||||
mainLayout->addLayout(showMatchLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -632,6 +652,16 @@ void MacroConditionVideoEdit::PatternThresholdChanged(double value)
|
||||
_entryData->_patternThreshold = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::ReduceLatencyChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
|
||||
_entryData->_blockUntilScreenshotDone = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::UseAlphaAsMaskChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -693,6 +723,9 @@ void MacroConditionVideoEdit::CheckAreaEnableChanged(int value)
|
||||
_entryData->_checkAreaEnable = value;
|
||||
_checkArea->setEnabled(value);
|
||||
_selectArea->setEnabled(value);
|
||||
_checkArea->setVisible(value);
|
||||
_selectArea->setVisible(value);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value)
|
||||
@@ -825,6 +858,8 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
needsThrottleControls(_entryData->_condition));
|
||||
setLayoutVisible(_checkAreaControlLayout,
|
||||
needsAreaControls(_entryData->_condition));
|
||||
_checkArea->setVisible(_entryData->_checkAreaEnable);
|
||||
_selectArea->setVisible(_entryData->_checkAreaEnable);
|
||||
|
||||
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
|
||||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
|
||||
@@ -843,6 +878,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
|
||||
|
||||
_videoSelection->SetVideoSelection(_entryData->_video);
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_reduceLatency->setChecked(_entryData->_blockUntilScreenshotDone);
|
||||
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_usePatternForChangedCheck->setChecked(
|
||||
_entryData->_usePatternForChangedCheck);
|
||||
|
||||
@@ -40,7 +40,7 @@ public:
|
||||
{
|
||||
return std::make_shared<MacroConditionVideo>(m);
|
||||
}
|
||||
void GetScreenshot();
|
||||
void GetScreenshot(bool blocking = false);
|
||||
bool LoadImageFromFile();
|
||||
bool LoadModelData(std::string &path);
|
||||
std::string GetModelDataPath() { return _modelDataPath; }
|
||||
@@ -49,6 +49,13 @@ public:
|
||||
VideoSelection _video;
|
||||
VideoCondition _condition = VideoCondition::MATCH;
|
||||
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
// Enabling this will reduce matching latency, but slow down the
|
||||
// the condition checks of all macros overall.
|
||||
//
|
||||
// If not set the screenshot will be gathered in one interval and
|
||||
// checked in the next one.
|
||||
// If set both operations will happen in the same interval.
|
||||
bool _blockUntilScreenshotDone = false;
|
||||
bool _useAlphaAsMask = false;
|
||||
bool _usePatternForChangedCheck = false;
|
||||
PatternMatchData _patternData;
|
||||
@@ -107,8 +114,11 @@ public:
|
||||
private slots:
|
||||
void VideoSelectionChanged(const VideoSelection &);
|
||||
void ConditionChanged(int cond);
|
||||
void ReduceLatencyChanged(int value);
|
||||
|
||||
void ImagePathChanged(const QString &text);
|
||||
void ImageBrowseButtonClicked();
|
||||
|
||||
void UsePatternForChangedCheckChanged(int value);
|
||||
void PatternThresholdChanged(double);
|
||||
void UseAlphaAsMaskChanged(int value);
|
||||
@@ -134,8 +144,11 @@ protected:
|
||||
VideoSelectionWidget *_videoSelection;
|
||||
QComboBox *_condition;
|
||||
|
||||
QCheckBox *_reduceLatency;
|
||||
|
||||
QCheckBox *_usePatternForChangedCheck;
|
||||
FileSelection *_imagePath;
|
||||
|
||||
ThresholdSlider *_patternThreshold;
|
||||
QCheckBox *_useAlphaAsMask;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
PatternMatchData createPatternData(QImage &pattern)
|
||||
{
|
||||
PatternMatchData data;
|
||||
PatternMatchData data{};
|
||||
if (pattern.isNull()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,7 @@ typedef struct transitionData {
|
||||
|
||||
} transitionData;
|
||||
|
||||
class Item;
|
||||
class SwitcherThread;
|
||||
|
||||
/********************************************************************************
|
||||
@@ -138,9 +139,11 @@ struct SwitcherData {
|
||||
|
||||
Curlhelper curl;
|
||||
|
||||
std::deque<Connection> connections;
|
||||
std::deque<std::shared_ptr<Item>> connections;
|
||||
std::vector<std::string> websocketMessages;
|
||||
|
||||
std::deque<std::shared_ptr<Item>> variables;
|
||||
|
||||
std::deque<WindowSwitch> windowSwitches;
|
||||
std::vector<std::string> ignoreIdleWindows;
|
||||
std::string lastTitle;
|
||||
@@ -291,6 +294,7 @@ struct SwitcherData {
|
||||
void saveSettings(obs_data_t *obj);
|
||||
void saveMacros(obs_data_t *obj);
|
||||
void saveConnections(obs_data_t *obj);
|
||||
void saveVariables(obs_data_t *obj);
|
||||
void saveWindowTitleSwitches(obs_data_t *obj);
|
||||
void saveScreenRegionSwitches(obs_data_t *obj);
|
||||
void savePauseSwitches(obs_data_t *obj);
|
||||
@@ -315,6 +319,7 @@ struct SwitcherData {
|
||||
void loadSettings(obs_data_t *obj);
|
||||
void loadMacros(obs_data_t *obj);
|
||||
void loadConnections(obs_data_t *obj);
|
||||
void loadVariables(obs_data_t *obj);
|
||||
void loadWindowTitleSwitches(obs_data_t *obj);
|
||||
void loadScreenRegionSwitches(obs_data_t *obj);
|
||||
void loadPauseSwitches(obs_data_t *obj);
|
||||
|
||||
@@ -14,9 +14,9 @@ Q_DECLARE_METATYPE(Connection *);
|
||||
void SwitcherData::saveConnections(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *connectionArray = obs_data_array_create();
|
||||
for (Connection &c : connections) {
|
||||
for (const auto &c : connections) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
c.Save(array_obj);
|
||||
c->Save(array_obj);
|
||||
obs_data_array_push_back(connectionArray, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
@@ -34,8 +34,9 @@ void SwitcherData::loadConnections(obs_data_t *obj)
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(connectionArray, i);
|
||||
connections.emplace_back();
|
||||
connections.back().Load(array_obj);
|
||||
auto con = Connection::Create();
|
||||
connections.emplace_back(con);
|
||||
connections.back()->Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(connectionArray);
|
||||
@@ -44,7 +45,7 @@ void SwitcherData::loadConnections(obs_data_t *obj)
|
||||
Connection::Connection(std::string name, std::string address, uint64_t port,
|
||||
std::string pass, bool connectOnStart, bool reconnect,
|
||||
int reconnectDelay)
|
||||
: _name(name),
|
||||
: Item(name),
|
||||
_address(address),
|
||||
_port(port),
|
||||
_password(pass),
|
||||
@@ -54,7 +55,7 @@ Connection::Connection(std::string name, std::string address, uint64_t port,
|
||||
{
|
||||
}
|
||||
|
||||
Connection::Connection(const Connection &other)
|
||||
Connection::Connection(const Connection &other) : Item(other)
|
||||
{
|
||||
_name = other._name;
|
||||
_address = other._address;
|
||||
@@ -125,8 +126,8 @@ Connection *GetConnectionByName(const QString &name)
|
||||
Connection *GetConnectionByName(const std::string &name)
|
||||
{
|
||||
for (auto &con : switcher->connections) {
|
||||
if (con.GetName() == name) {
|
||||
return &con;
|
||||
if (con->Name() == name) {
|
||||
return dynamic_cast<Connection *>(con.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
@@ -142,58 +143,38 @@ bool ConnectionNameAvailable(const std::string &name)
|
||||
return ConnectionNameAvailable(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
ConnectionSelection::ConnectionSelection(QWidget *parent)
|
||||
: QWidget(parent), _selection(new QComboBox), _modify(new QPushButton)
|
||||
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
|
||||
{
|
||||
_modify->setMaximumSize(22, 22);
|
||||
QIcon icon;
|
||||
icon.addFile(
|
||||
QString::fromUtf8(":/settings/images/settings/general.svg"),
|
||||
QSize(), QIcon::Normal, QIcon::Off);
|
||||
_modify->setIcon(icon);
|
||||
_modify->setFlat(true);
|
||||
Connection &ConnectionSettings = dynamic_cast<Connection &>(settings);
|
||||
return ConnectionSettingsDialog::AskForSettings(parent,
|
||||
ConnectionSettings);
|
||||
}
|
||||
|
||||
ConnectionSelection::ConnectionSelection(QWidget *parent)
|
||||
: ItemSelection(switcher->connections, Connection::Create,
|
||||
AskForSettingsWrapper,
|
||||
"AdvSceneSwitcher.connection.select",
|
||||
"AdvSceneSwitcher.connection.add", parent)
|
||||
{
|
||||
// Connect to slots
|
||||
QWidget::connect(_selection,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(ChangeSelection(const QString &)));
|
||||
QWidget::connect(_modify, SIGNAL(clicked()), this,
|
||||
SLOT(ModifyButtonClicked()));
|
||||
QWidget::connect(
|
||||
window(),
|
||||
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
|
||||
this, SLOT(RenameConnection(const QString &, const QString &)));
|
||||
this, SLOT(RenameItem(const QString &, const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(ConnectionAdded(const QString &)),
|
||||
this, SLOT(AddConnection(const QString &)));
|
||||
this, SLOT(AddItem(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(ConnectionRemoved(const QString &)),
|
||||
this, SLOT(RemoveConnection(const QString &)));
|
||||
this, SLOT(RemoveItem(const QString &)));
|
||||
|
||||
// Forward signals
|
||||
QWidget::connect(
|
||||
this,
|
||||
SIGNAL(ConnectionRenamed(const QString &, const QString &)),
|
||||
this, SIGNAL(ItemRenamed(const QString &, const QString &)),
|
||||
window(),
|
||||
SIGNAL(ConnectionRenamed(const QString &, const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ConnectionAdded(const QString &)),
|
||||
window(), SIGNAL(ConnectionAdded(const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ConnectionRemoved(const QString &)),
|
||||
window(), SIGNAL(ConnectionRemoved(const QString &)));
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
layout->addWidget(_selection);
|
||||
layout->addWidget(_modify);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
|
||||
for (const auto &c : switcher->connections) {
|
||||
_selection->addItem(QString::fromStdString(c._name));
|
||||
}
|
||||
_selection->model()->sort(0);
|
||||
addSelectionEntry(
|
||||
_selection,
|
||||
obs_module_text("AdvSceneSwitcher.connection.select"));
|
||||
_selection->insertSeparator(_selection->count());
|
||||
_selection->addItem(obs_module_text("AdvSceneSwitcher.connection.add"));
|
||||
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
|
||||
SIGNAL(ConnectionAdded(const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
|
||||
SIGNAL(ConnectionRemoved(const QString &)));
|
||||
}
|
||||
|
||||
void ConnectionSelection::SetConnection(const std::string &con)
|
||||
@@ -206,191 +187,30 @@ void ConnectionSelection::SetConnection(const std::string &con)
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSelection::ChangeSelection(const QString &sel)
|
||||
{
|
||||
if (sel == obs_module_text("AdvSceneSwitcher.connection.add")) {
|
||||
Connection connection;
|
||||
bool accepted = ConnectionSettingsDialog::AskForSettings(
|
||||
this, connection);
|
||||
if (!accepted) {
|
||||
_selection->setCurrentIndex(0);
|
||||
return;
|
||||
}
|
||||
switcher->connections.emplace_back(connection);
|
||||
const QSignalBlocker b(_selection);
|
||||
const QString name = QString::fromStdString(connection._name);
|
||||
AddConnection(name);
|
||||
_selection->setCurrentText(name);
|
||||
emit ConnectionAdded(name);
|
||||
emit SelectionChanged(name);
|
||||
return;
|
||||
}
|
||||
auto connection = GetCurrentConnection();
|
||||
if (connection) {
|
||||
emit SelectionChanged(
|
||||
QString::fromStdString(connection->_name));
|
||||
} else {
|
||||
emit SelectionChanged("");
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSelection::ModifyButtonClicked()
|
||||
{
|
||||
auto connection = GetCurrentConnection();
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
auto properties = [&]() {
|
||||
const auto oldName = connection->_name;
|
||||
bool accepted = ConnectionSettingsDialog::AskForSettings(
|
||||
this, *connection);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
if (oldName != connection->_name) {
|
||||
emit ConnectionRenamed(
|
||||
QString::fromStdString(oldName),
|
||||
QString::fromStdString(connection->_name));
|
||||
}
|
||||
};
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
QAction *action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.connection.rename"), &menu);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(RenameConnection()));
|
||||
action->setProperty("connetion", QVariant::fromValue(connection));
|
||||
menu.addAction(action);
|
||||
|
||||
action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.connection.remove"), &menu);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(RemoveConnection()));
|
||||
menu.addAction(action);
|
||||
|
||||
action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.connection.properties"),
|
||||
&menu);
|
||||
connect(action, &QAction::triggered, properties);
|
||||
menu.addAction(action);
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void ConnectionSelection::RenameConnection()
|
||||
{
|
||||
QAction *action = reinterpret_cast<QAction *>(sender());
|
||||
QVariant variant = action->property("connetion");
|
||||
Connection *connection = variant.value<Connection *>();
|
||||
|
||||
std::string name;
|
||||
bool accepted = AdvSSNameDialog::AskForName(
|
||||
this, obs_module_text("AdvSceneSwitcher.windowTitle"),
|
||||
obs_module_text("AdvSceneSwitcher.connection.newName"), name,
|
||||
QString::fromStdString(name));
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
if (name.empty()) {
|
||||
DisplayMessage("AdvSceneSwitcher.connection.emptyName");
|
||||
return;
|
||||
}
|
||||
if (_selection->currentText().toStdString() != name &&
|
||||
!ConnectionNameAvailable(name)) {
|
||||
DisplayMessage("AdvSceneSwitcher.connection.nameNotAvailable");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto oldName = connection->_name;
|
||||
connection->_name = name;
|
||||
emit ConnectionRenamed(QString::fromStdString(oldName),
|
||||
QString::fromStdString(name));
|
||||
}
|
||||
|
||||
void ConnectionSelection::RenameConnection(const QString &oldName,
|
||||
const QString &name)
|
||||
{
|
||||
int idx = _selection->findText(oldName);
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
_selection->setItemText(idx, name);
|
||||
}
|
||||
|
||||
void ConnectionSelection::AddConnection(const QString &name)
|
||||
{
|
||||
if (_selection->findText(name) == -1) {
|
||||
_selection->insertItem(1, name);
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSelection::RemoveConnection()
|
||||
{
|
||||
auto connection = GetCurrentConnection();
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx =
|
||||
_selection->findText(QString::fromStdString(connection->_name));
|
||||
if (idx == -1 || idx == _selection->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit ConnectionRemoved(QString::fromStdString(connection->_name));
|
||||
|
||||
for (auto it = switcher->connections.begin();
|
||||
it != switcher->connections.end(); ++it) {
|
||||
if (it->_name == connection->_name) {
|
||||
switcher->connections.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSelection::RemoveConnection(const QString &name)
|
||||
{
|
||||
const int idx = _selection->findText(name);
|
||||
if (idx == _selection->currentIndex()) {
|
||||
_selection->setCurrentIndex(0);
|
||||
}
|
||||
_selection->removeItem(idx);
|
||||
}
|
||||
|
||||
Connection *ConnectionSelection::GetCurrentConnection()
|
||||
{
|
||||
return GetConnectionByName(_selection->currentText());
|
||||
}
|
||||
|
||||
ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||
const Connection &settings)
|
||||
: QDialog(parent),
|
||||
_name(new QLineEdit()),
|
||||
_nameHint(new QLabel),
|
||||
: ItemSettingsDialog(settings, switcher->connections,
|
||||
"AdvSceneSwitcher.connection.select",
|
||||
"AdvSceneSwitcher.connection.add", parent),
|
||||
_address(new QLineEdit()),
|
||||
_port(new QSpinBox()),
|
||||
_password(new QLineEdit()),
|
||||
_showPassword(new QPushButton()),
|
||||
_connectOnStart(new QCheckBox()),
|
||||
_reconnect(new QCheckBox()),
|
||||
_reconnectDelay(new QSpinBox()),
|
||||
_test(new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.connection.test"))),
|
||||
_status(new QLabel()),
|
||||
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel))
|
||||
_status(new QLabel())
|
||||
{
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setFixedWidth(555);
|
||||
setMinimumHeight(100);
|
||||
|
||||
_port->setMaximum(65535);
|
||||
_showPassword->setMaximumWidth(22);
|
||||
_showPassword->setFlat(true);
|
||||
_showPassword->setStyleSheet(
|
||||
"QPushButton { background-color: transparent; border: 0px }");
|
||||
_reconnectDelay->setMaximum(9999);
|
||||
_reconnectDelay->setSuffix("s");
|
||||
_buttonbox->setCenterButtons(true);
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
|
||||
|
||||
_name->setText(QString::fromStdString(settings._name));
|
||||
_address->setText(QString::fromStdString(settings._address));
|
||||
_port->setValue(settings._port);
|
||||
_password->setText(QString::fromStdString(settings._password));
|
||||
@@ -398,18 +218,15 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||
_reconnect->setChecked(settings._reconnect);
|
||||
_reconnectDelay->setValue(settings._reconnectDelay);
|
||||
|
||||
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
|
||||
SLOT(NameChanged(const QString &)));
|
||||
QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(ReconnectChanged(int)));
|
||||
QWidget::connect(_showPassword, SIGNAL(pressed()), this,
|
||||
SLOT(ShowPassword()));
|
||||
QWidget::connect(_showPassword, SIGNAL(released()), this,
|
||||
SLOT(HidePassword()));
|
||||
QWidget::connect(_test, SIGNAL(clicked()), this,
|
||||
SLOT(TestConnection()));
|
||||
|
||||
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
|
||||
&QDialog::accept);
|
||||
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
|
||||
&QDialog::reject);
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
int row = 0;
|
||||
layout->addWidget(
|
||||
@@ -433,7 +250,10 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||
layout->addWidget(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.password")),
|
||||
row, 0);
|
||||
layout->addWidget(_password, row, 1);
|
||||
QHBoxLayout *passLayout = new QHBoxLayout;
|
||||
passLayout->addWidget(_password);
|
||||
passLayout->addWidget(_showPassword);
|
||||
layout->addLayout(passLayout, row, 1);
|
||||
++row;
|
||||
layout->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
@@ -458,30 +278,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
|
||||
layout->addWidget(_buttonbox, row, 0, 1, -1);
|
||||
setLayout(layout);
|
||||
|
||||
NameChanged(_name->text());
|
||||
ReconnectChanged(_reconnect->isChecked());
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::NameChanged(const QString &text)
|
||||
{
|
||||
|
||||
if (text != _name->text() && !ConnectionNameAvailable(text)) {
|
||||
SetNameWarning(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.nameNotAvailable"));
|
||||
return;
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
SetNameWarning(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.emptyName"));
|
||||
return;
|
||||
}
|
||||
if (text == obs_module_text("AdvSceneSwitcher.connection.select") ||
|
||||
text == obs_module_text("AdvSceneSwitcher.connection.add")) {
|
||||
SetNameWarning(obs_module_text(
|
||||
"AdvSceneSwitcher.connection.nameReserved"));
|
||||
return;
|
||||
}
|
||||
SetNameWarning("");
|
||||
HidePassword();
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::ReconnectChanged(int state)
|
||||
@@ -513,6 +311,18 @@ void ConnectionSettingsDialog::SetStatus()
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::ShowPassword()
|
||||
{
|
||||
setButtonIcon(_showPassword, ":res/images/visible.svg");
|
||||
_password->setEchoMode(QLineEdit::Normal);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::HidePassword()
|
||||
{
|
||||
setButtonIcon(_showPassword, ":res/images/invisible.svg");
|
||||
_password->setEchoMode(QLineEdit::PasswordEchoOnEdit);
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::TestConnection()
|
||||
{
|
||||
_testConnection.Disconnect();
|
||||
@@ -545,21 +355,9 @@ bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConnectionSettingsDialog::SetNameWarning(const QString warn)
|
||||
{
|
||||
if (warn.isEmpty()) {
|
||||
_nameHint->hide();
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(false);
|
||||
return;
|
||||
}
|
||||
_nameHint->setText(warn);
|
||||
_nameHint->show();
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
|
||||
}
|
||||
|
||||
void Connection::Load(obs_data_t *obj)
|
||||
{
|
||||
_name = obs_data_get_string(obj, "name");
|
||||
Item::Load(obj);
|
||||
_address = obs_data_get_string(obj, "address");
|
||||
_port = obs_data_get_int(obj, "port");
|
||||
_password = obs_data_get_string(obj, "password");
|
||||
@@ -573,9 +371,9 @@ void Connection::Load(obs_data_t *obj)
|
||||
}
|
||||
}
|
||||
|
||||
void Connection::Save(obs_data_t *obj)
|
||||
void Connection::Save(obs_data_t *obj) const
|
||||
{
|
||||
obs_data_set_string(obj, "name", _name.c_str());
|
||||
Item::Save(obj);
|
||||
obs_data_set_string(obj, "address", _address.c_str());
|
||||
obs_data_set_int(obj, "port", _port);
|
||||
obs_data_set_string(obj, "password", _password.c_str());
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "item-selection-helpers.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
@@ -7,7 +8,6 @@
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QSpinBox>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
@@ -18,7 +18,7 @@
|
||||
class ConnectionSelection;
|
||||
class ConnectionSettingsDialog;
|
||||
|
||||
class Connection {
|
||||
class Connection : public Item {
|
||||
public:
|
||||
Connection(std::string name, std::string address, uint64_t port,
|
||||
std::string pass, bool connectOnStart, bool reconnect,
|
||||
@@ -27,15 +27,19 @@ public:
|
||||
Connection(const Connection &);
|
||||
Connection &operator=(const Connection &);
|
||||
~Connection();
|
||||
static std::shared_ptr<Item> Create()
|
||||
{
|
||||
return std::make_shared<Connection>();
|
||||
}
|
||||
|
||||
void Reconnect();
|
||||
void SendMsg(const std::string &msg);
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
std::string GetName() { return _name; }
|
||||
std::vector<std::string> &Events() { return _client.Events(); }
|
||||
|
||||
private:
|
||||
std::string _name = "";
|
||||
std::string _address = "localhost";
|
||||
uint64_t _port = 4455;
|
||||
std::string _password = "password";
|
||||
@@ -53,7 +57,7 @@ private:
|
||||
Connection *GetConnectionByName(const QString &);
|
||||
Connection *GetConnectionByName(const std::string &);
|
||||
|
||||
class ConnectionSettingsDialog : public QDialog {
|
||||
class ConnectionSettingsDialog : public ItemSettingsDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@@ -61,54 +65,31 @@ public:
|
||||
static bool AskForSettings(QWidget *parent, Connection &settings);
|
||||
|
||||
private slots:
|
||||
void NameChanged(const QString &);
|
||||
void ReconnectChanged(int);
|
||||
void ShowPassword();
|
||||
void HidePassword();
|
||||
void SetStatus();
|
||||
void TestConnection();
|
||||
|
||||
private:
|
||||
void SetNameWarning(const QString);
|
||||
|
||||
QLineEdit *_name;
|
||||
QLabel *_nameHint;
|
||||
QLineEdit *_address;
|
||||
QSpinBox *_port;
|
||||
QLineEdit *_password;
|
||||
QPushButton *_showPassword;
|
||||
QCheckBox *_connectOnStart;
|
||||
QCheckBox *_reconnect;
|
||||
QSpinBox *_reconnectDelay;
|
||||
QPushButton *_test;
|
||||
QLabel *_status;
|
||||
QDialogButtonBox *_buttonbox;
|
||||
|
||||
QTimer _statusTimer;
|
||||
WSConnection _testConnection;
|
||||
};
|
||||
|
||||
class ConnectionSelection : public QWidget {
|
||||
class ConnectionSelection : public ItemSelection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConnectionSelection(QWidget *parent = 0);
|
||||
void SetConnection(const std::string &);
|
||||
|
||||
private slots:
|
||||
void ModifyButtonClicked();
|
||||
void ChangeSelection(const QString &);
|
||||
void RenameConnection();
|
||||
void RenameConnection(const QString &oldName, const QString &name);
|
||||
void AddConnection(const QString &);
|
||||
void RemoveConnection();
|
||||
void RemoveConnection(const QString &);
|
||||
signals:
|
||||
void SelectionChanged(const QString &);
|
||||
void ConnectionAdded(const QString &);
|
||||
void ConnectionRemoved(const QString &);
|
||||
void ConnectionRenamed(const QString &oldName, const QString &name);
|
||||
|
||||
private:
|
||||
Connection *GetCurrentConnection();
|
||||
|
||||
QComboBox *_selection;
|
||||
QPushButton *_modify;
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include <obs-module.h>
|
||||
#include <QLayout>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include <filesystem>
|
||||
|
||||
FileSelection::FileSelection(FileSelection::Type type, QWidget *parent)
|
||||
: QWidget(parent), _type(type)
|
||||
@@ -29,12 +31,22 @@ void FileSelection::SetPath(const QString &path)
|
||||
|
||||
void FileSelection::BrowseButtonClicked()
|
||||
{
|
||||
QString defaultPath;
|
||||
if (std::filesystem::exists(
|
||||
std::filesystem::path(_filePath->text().toStdString()))) {
|
||||
defaultPath = _filePath->text();
|
||||
} else {
|
||||
defaultPath = QStandardPaths::writableLocation(
|
||||
QStandardPaths::DesktopLocation);
|
||||
}
|
||||
|
||||
QString path;
|
||||
if (_type == FileSelection::Type::WRITE) {
|
||||
path = QFileDialog::getSaveFileName(this);
|
||||
path = QFileDialog::getSaveFileName(this, "", defaultPath);
|
||||
} else {
|
||||
path = QFileDialog::getOpenFileName(this);
|
||||
path = QFileDialog::getOpenFileName(this, "", defaultPath);
|
||||
}
|
||||
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
317
src/utils/item-selection-helpers.cpp
Normal file
317
src/utils/item-selection-helpers.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
#include "item-selection-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
#include "name-dialog.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
#include <QLayout>
|
||||
#include <obs-module.h>
|
||||
|
||||
Q_DECLARE_METATYPE(Item *);
|
||||
|
||||
Item::Item(std::string name) : _name(name) {}
|
||||
|
||||
static Item *GetItemByName(const std::string &name,
|
||||
std::deque<std::shared_ptr<Item>> &items)
|
||||
{
|
||||
for (auto &item : items) {
|
||||
if (item->Name() == name) {
|
||||
return item.get();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Item *GetItemByName(const QString &name,
|
||||
std::deque<std::shared_ptr<Item>> &items)
|
||||
{
|
||||
return GetItemByName(name.toStdString(), items);
|
||||
}
|
||||
|
||||
bool ItemNameAvailable(const QString &name,
|
||||
std::deque<std::shared_ptr<Item>> &items)
|
||||
{
|
||||
return !GetItemByName(name, items);
|
||||
}
|
||||
|
||||
bool ItemNameAvailable(const std::string &name,
|
||||
std::deque<std::shared_ptr<Item>> &items)
|
||||
{
|
||||
return ItemNameAvailable(QString::fromStdString(name), items);
|
||||
}
|
||||
|
||||
ItemSelection::ItemSelection(std::deque<std::shared_ptr<Item>> &items,
|
||||
CreateItemFunc create, SettingsCallback callback,
|
||||
std::string_view select, std::string_view add,
|
||||
QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_selection(new QComboBox),
|
||||
_modify(new QPushButton),
|
||||
_create(create),
|
||||
_askForSettings(callback),
|
||||
_items(items),
|
||||
_selectStr(select),
|
||||
_addStr(add)
|
||||
{
|
||||
_modify->setMaximumWidth(22);
|
||||
setButtonIcon(_modify, ":/settings/images/settings/general.svg");
|
||||
_modify->setFlat(true);
|
||||
|
||||
// Connect to slots
|
||||
QWidget::connect(_selection,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(ChangeSelection(const QString &)));
|
||||
QWidget::connect(_modify, SIGNAL(clicked()), this,
|
||||
SLOT(ModifyButtonClicked()));
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
layout->addWidget(_selection);
|
||||
layout->addWidget(_modify);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(layout);
|
||||
|
||||
for (const auto &i : items) {
|
||||
_selection->addItem(QString::fromStdString(i->_name));
|
||||
}
|
||||
_selection->model()->sort(0);
|
||||
addSelectionEntry(_selection, obs_module_text(_selectStr.data()));
|
||||
_selection->insertSeparator(_selection->count());
|
||||
_selection->addItem(obs_module_text(_addStr.data()));
|
||||
}
|
||||
|
||||
void ItemSelection::SetItem(const std::string &item)
|
||||
{
|
||||
const QSignalBlocker blocker(_selection);
|
||||
if (!!GetItemByName(item, _items)) {
|
||||
_selection->setCurrentText(QString::fromStdString(item));
|
||||
} else {
|
||||
_selection->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSelection::ChangeSelection(const QString &sel)
|
||||
{
|
||||
if (sel == obs_module_text(_addStr.data())) {
|
||||
auto item = _create();
|
||||
bool accepted = _askForSettings(this, *item.get());
|
||||
if (!accepted) {
|
||||
_selection->setCurrentIndex(0);
|
||||
return;
|
||||
}
|
||||
_items.emplace_back(item);
|
||||
const QSignalBlocker b(_selection);
|
||||
const QString name = QString::fromStdString(item->_name);
|
||||
AddItem(name);
|
||||
_selection->setCurrentText(name);
|
||||
emit ItemAdded(name);
|
||||
emit SelectionChanged(name);
|
||||
return;
|
||||
}
|
||||
auto item = GetCurrentItem();
|
||||
if (item) {
|
||||
emit SelectionChanged(QString::fromStdString(item->_name));
|
||||
} else {
|
||||
emit SelectionChanged("");
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSelection::ModifyButtonClicked()
|
||||
{
|
||||
auto item = GetCurrentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto properties = [&]() {
|
||||
const auto oldName = item->_name;
|
||||
bool accepted = _askForSettings(this, *item);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
if (oldName != item->_name) {
|
||||
emit ItemRenamed(QString::fromStdString(oldName),
|
||||
QString::fromStdString(item->_name));
|
||||
}
|
||||
};
|
||||
|
||||
QMenu menu(this);
|
||||
|
||||
QAction *action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.item.rename"), &menu);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(RenameItem()));
|
||||
action->setProperty("connetion", QVariant::fromValue(item));
|
||||
menu.addAction(action);
|
||||
|
||||
action = new QAction(obs_module_text("AdvSceneSwitcher.item.remove"),
|
||||
&menu);
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(RemoveItem()));
|
||||
menu.addAction(action);
|
||||
|
||||
action = new QAction(
|
||||
obs_module_text("AdvSceneSwitcher.item.properties"), &menu);
|
||||
connect(action, &QAction::triggered, properties);
|
||||
menu.addAction(action);
|
||||
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void ItemSelection::RenameItem()
|
||||
{
|
||||
QAction *action = reinterpret_cast<QAction *>(sender());
|
||||
QVariant variant = action->property("connetion");
|
||||
Item *item = variant.value<Item *>();
|
||||
|
||||
std::string name;
|
||||
bool accepted = AdvSSNameDialog::AskForName(
|
||||
this, obs_module_text("AdvSceneSwitcher.windowTitle"),
|
||||
obs_module_text("AdvSceneSwitcher.item.newName"), name,
|
||||
QString::fromStdString(name));
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
if (name.empty()) {
|
||||
DisplayMessage("AdvSceneSwitcher.item.emptyName");
|
||||
return;
|
||||
}
|
||||
if (_selection->currentText().toStdString() != name &&
|
||||
!ItemNameAvailable(name, _items)) {
|
||||
DisplayMessage("AdvSceneSwitcher.item.nameNotAvailable");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto oldName = item->_name;
|
||||
item->_name = name;
|
||||
emit ItemRenamed(QString::fromStdString(oldName),
|
||||
QString::fromStdString(name));
|
||||
}
|
||||
|
||||
void ItemSelection::RenameItem(const QString &oldName, const QString &name)
|
||||
{
|
||||
int idx = _selection->findText(oldName);
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
_selection->setItemText(idx, name);
|
||||
}
|
||||
|
||||
void ItemSelection::AddItem(const QString &name)
|
||||
{
|
||||
if (_selection->findText(name) == -1) {
|
||||
_selection->insertItem(1, name);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemSelection::RemoveItem()
|
||||
{
|
||||
auto item = GetCurrentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
int idx = _selection->findText(QString::fromStdString(item->_name));
|
||||
if (idx == -1 || idx == _selection->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto name = item->_name;
|
||||
for (auto it = _items.begin(); it != _items.end(); ++it) {
|
||||
if (it->get()->_name == item->_name) {
|
||||
_items.erase(it);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit ItemRemoved(QString::fromStdString(name));
|
||||
}
|
||||
|
||||
void ItemSelection::RemoveItem(const QString &name)
|
||||
{
|
||||
const int idx = _selection->findText(name);
|
||||
if (idx == _selection->currentIndex()) {
|
||||
_selection->setCurrentIndex(0);
|
||||
}
|
||||
_selection->removeItem(idx);
|
||||
}
|
||||
|
||||
Item *ItemSelection::GetCurrentItem()
|
||||
{
|
||||
return GetItemByName(_selection->currentText(), _items);
|
||||
}
|
||||
|
||||
ItemSettingsDialog::ItemSettingsDialog(const Item &settings,
|
||||
std::deque<std::shared_ptr<Item>> &items,
|
||||
std::string_view select,
|
||||
std::string_view add, QWidget *parent)
|
||||
: QDialog(parent),
|
||||
_name(new QLineEdit()),
|
||||
_nameHint(new QLabel),
|
||||
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel)),
|
||||
_items(items),
|
||||
_selectStr(select),
|
||||
_addStr(add)
|
||||
{
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setFixedWidth(555);
|
||||
setMinimumHeight(100);
|
||||
|
||||
_buttonbox->setCenterButtons(true);
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
|
||||
|
||||
_name->setText(QString::fromStdString(settings._name));
|
||||
|
||||
QWidget::connect(_name, SIGNAL(textEdited(const QString &)), this,
|
||||
SLOT(NameChanged(const QString &)));
|
||||
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
|
||||
&QDialog::accept);
|
||||
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
|
||||
&QDialog::reject);
|
||||
|
||||
NameChanged(_name->text());
|
||||
}
|
||||
|
||||
void ItemSettingsDialog::NameChanged(const QString &text)
|
||||
{
|
||||
|
||||
if (text != _name->text() && !ItemNameAvailable(text, _items)) {
|
||||
SetNameWarning(obs_module_text(
|
||||
"AdvSceneSwitcher.item.nameNotAvailable"));
|
||||
return;
|
||||
}
|
||||
if (text.isEmpty()) {
|
||||
SetNameWarning(
|
||||
obs_module_text("AdvSceneSwitcher.item.emptyName"));
|
||||
return;
|
||||
}
|
||||
if (text == obs_module_text(_selectStr.data()) ||
|
||||
text == obs_module_text(_addStr.data())) {
|
||||
SetNameWarning(
|
||||
obs_module_text("AdvSceneSwitcher.item.nameReserved"));
|
||||
return;
|
||||
}
|
||||
SetNameWarning("");
|
||||
}
|
||||
|
||||
void ItemSettingsDialog::SetNameWarning(const QString warn)
|
||||
{
|
||||
if (warn.isEmpty()) {
|
||||
_nameHint->hide();
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(false);
|
||||
return;
|
||||
}
|
||||
_nameHint->setText(warn);
|
||||
_nameHint->show();
|
||||
_buttonbox->button(QDialogButtonBox::Ok)->setDisabled(true);
|
||||
}
|
||||
|
||||
void Item::Load(obs_data_t *obj)
|
||||
{
|
||||
_name = obs_data_get_string(obj, "name");
|
||||
}
|
||||
|
||||
void Item::Save(obs_data_t *obj) const
|
||||
{
|
||||
obs_data_set_string(obj, "name", _name.c_str());
|
||||
}
|
||||
101
src/utils/item-selection-helpers.hpp
Normal file
101
src/utils/item-selection-helpers.hpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QDialog>
|
||||
#include <QLineEdit>
|
||||
#include <QCheckBox>
|
||||
#include <QSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QSpinBox>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
#include <deque>
|
||||
#include <obs.hpp>
|
||||
#include <websocket-helpers.hpp>
|
||||
|
||||
class ItemSelection;
|
||||
class ItemSettingsDialog;
|
||||
|
||||
class Item {
|
||||
public:
|
||||
Item(std::string name);
|
||||
Item() = default;
|
||||
virtual ~Item() = default;
|
||||
|
||||
virtual void Load(obs_data_t *obj);
|
||||
virtual void Save(obs_data_t *obj) const;
|
||||
std::string Name() const { return _name; }
|
||||
|
||||
protected:
|
||||
std::string _name = "";
|
||||
|
||||
friend ItemSelection;
|
||||
friend ItemSettingsDialog;
|
||||
};
|
||||
|
||||
class ItemSettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemSettingsDialog(const Item &, std::deque<std::shared_ptr<Item>> &,
|
||||
std::string_view = "AdvSceneSwitcher.item.select",
|
||||
std::string_view = "AdvSceneSwitcher.item.select",
|
||||
QWidget *parent = 0);
|
||||
virtual ~ItemSettingsDialog() = default;
|
||||
|
||||
private slots:
|
||||
void NameChanged(const QString &);
|
||||
|
||||
protected:
|
||||
void SetNameWarning(const QString);
|
||||
|
||||
QLineEdit *_name;
|
||||
QLabel *_nameHint;
|
||||
QDialogButtonBox *_buttonbox;
|
||||
std::deque<std::shared_ptr<Item>> &_items;
|
||||
std::string_view _selectStr;
|
||||
std::string_view _addStr;
|
||||
};
|
||||
|
||||
typedef bool (*SettingsCallback)(QWidget *, Item &);
|
||||
typedef std::shared_ptr<Item> (*CreateItemFunc)();
|
||||
|
||||
class ItemSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ItemSelection(std::deque<std::shared_ptr<Item>> &, CreateItemFunc,
|
||||
SettingsCallback,
|
||||
std::string_view = "AdvSceneSwitcher.item.select",
|
||||
std::string_view = "AdvSceneSwitcher.item.select",
|
||||
QWidget *parent = 0);
|
||||
virtual ~ItemSelection() = default;
|
||||
void SetItem(const std::string &);
|
||||
|
||||
private slots:
|
||||
void ModifyButtonClicked();
|
||||
void ChangeSelection(const QString &);
|
||||
void RenameItem();
|
||||
void RenameItem(const QString &oldName, const QString &name);
|
||||
void AddItem(const QString &);
|
||||
void RemoveItem();
|
||||
void RemoveItem(const QString &);
|
||||
signals:
|
||||
void SelectionChanged(const QString &);
|
||||
void ItemAdded(const QString &);
|
||||
void ItemRemoved(const QString &);
|
||||
void ItemRenamed(const QString &oldName, const QString &name);
|
||||
|
||||
protected:
|
||||
Item *GetCurrentItem();
|
||||
|
||||
QComboBox *_selection;
|
||||
QPushButton *_modify;
|
||||
CreateItemFunc _create;
|
||||
SettingsCallback _askForSettings;
|
||||
std::deque<std::shared_ptr<Item>> &_items;
|
||||
std::string_view _selectStr;
|
||||
std::string_view _addStr;
|
||||
};
|
||||
@@ -17,19 +17,19 @@ MacroList::MacroList(QWidget *parent, bool allowDuplicates, bool reorder)
|
||||
_up->setVisible(reorder);
|
||||
_down->setVisible(reorder);
|
||||
|
||||
_add->setMaximumSize(QSize(22, 22));
|
||||
_add->setMaximumWidth(22);
|
||||
_add->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("addIconSmall")));
|
||||
_add->setFlat(true);
|
||||
_remove->setMaximumSize(QSize(22, 22));
|
||||
_remove->setMaximumWidth(22);
|
||||
_remove->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
_remove->setFlat(true);
|
||||
_up->setMaximumSize(QSize(22, 22));
|
||||
_up->setMaximumWidth(22);
|
||||
_up->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
_up->setFlat(true);
|
||||
_down->setMaximumSize(QSize(22, 22));
|
||||
_down->setMaximumWidth(22);
|
||||
_down->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
_down->setFlat(true);
|
||||
|
||||
175
src/utils/regex-config.cpp
Normal file
175
src/utils/regex-config.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
#include "regex-config.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QLayout>
|
||||
#include <obs-module.h>
|
||||
|
||||
void RegexConfig::Save(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_bool(data, "enable", _enable);
|
||||
obs_data_set_bool(data, "partial", _partialMatch);
|
||||
obs_data_set_int(data, "options", _options);
|
||||
obs_data_set_obj(obj, "regexConfig", data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void RegexConfig::Load(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, "regexConfig");
|
||||
_enable = obs_data_get_bool(data, "enable");
|
||||
_partialMatch = obs_data_get_bool(data, "partial");
|
||||
_options = static_cast<QRegularExpression::PatternOption>(
|
||||
obs_data_get_int(data, "options"));
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void RegexConfig::CreateBackwardsCompatibleRegex(bool enable, bool setOptions)
|
||||
{
|
||||
_enable = enable;
|
||||
if (setOptions) {
|
||||
_options |= QRegularExpression::DotMatchesEverythingOption;
|
||||
}
|
||||
}
|
||||
|
||||
QRegularExpression RegexConfig::GetRegularExpression(const QString &expr) const
|
||||
{
|
||||
if (_partialMatch) {
|
||||
return QRegularExpression(expr, _options);
|
||||
}
|
||||
return QRegularExpression(QRegularExpression::anchoredPattern(expr),
|
||||
_options);
|
||||
}
|
||||
|
||||
QRegularExpression
|
||||
RegexConfig::GetRegularExpression(const std::string &expr) const
|
||||
{
|
||||
return GetRegularExpression(QString::fromStdString(expr));
|
||||
}
|
||||
|
||||
RegexConfigWidget::RegexConfigWidget(QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_openSettings(new QPushButton()),
|
||||
_enable(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.regex.enable")))
|
||||
{
|
||||
_openSettings->setMaximumWidth(22);
|
||||
setButtonIcon(_openSettings, ":/settings/images/settings/general.svg");
|
||||
_openSettings->setFlat(true);
|
||||
|
||||
QWidget::connect(_enable, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(EnableChanged(int)));
|
||||
QWidget::connect(_openSettings, SIGNAL(clicked()), this,
|
||||
SLOT(OpenSettingsClicked()));
|
||||
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_enable);
|
||||
layout->addWidget(_openSettings);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void RegexConfigWidget::SetRegexConfig(const RegexConfig &config)
|
||||
{
|
||||
_config = config;
|
||||
_enable->setChecked(config._enable);
|
||||
SetVisibility();
|
||||
}
|
||||
|
||||
void RegexConfigWidget::OpenSettingsClicked()
|
||||
{
|
||||
bool accepted = RegexConfigDialog::AskForSettings(this, _config);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
emit RegexConfigChanged(_config);
|
||||
}
|
||||
|
||||
void RegexConfigWidget::SetVisibility()
|
||||
{
|
||||
_openSettings->setVisible(_config._enable);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void RegexConfigWidget::EnableChanged(int value)
|
||||
{
|
||||
_config._enable = value;
|
||||
SetVisibility();
|
||||
emit RegexConfigChanged(_config);
|
||||
}
|
||||
|
||||
RegexConfigDialog::RegexConfigDialog(QWidget *parent, const RegexConfig &config)
|
||||
: QDialog(parent),
|
||||
_partialMatch(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.regex.partialMatch"))),
|
||||
_caseInsensitive(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.regex.caseInsensitive"))),
|
||||
_dotMatch(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.regex.dotMatchNewline"))),
|
||||
_multiLine(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.regex.multiLine"))),
|
||||
_extendedPattern(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.regex.extendedPattern"))),
|
||||
_buttonbox(new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel))
|
||||
{
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
_partialMatch->setChecked(config._partialMatch);
|
||||
_caseInsensitive->setChecked(config._options &
|
||||
QRegularExpression::CaseInsensitiveOption);
|
||||
_dotMatch->setChecked(config._options &
|
||||
QRegularExpression::DotMatchesEverythingOption);
|
||||
_multiLine->setChecked(config._options &
|
||||
QRegularExpression::MultilineOption);
|
||||
_extendedPattern->setChecked(
|
||||
config._options &
|
||||
QRegularExpression::ExtendedPatternSyntaxOption);
|
||||
|
||||
QWidget::connect(_buttonbox, &QDialogButtonBox::accepted, this,
|
||||
&QDialog::accept);
|
||||
QWidget::connect(_buttonbox, &QDialogButtonBox::rejected, this,
|
||||
&QDialog::reject);
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->addWidget(_partialMatch);
|
||||
layout->addWidget(_caseInsensitive);
|
||||
layout->addWidget(_dotMatch);
|
||||
layout->addWidget(_multiLine);
|
||||
layout->addWidget(_extendedPattern);
|
||||
layout->addWidget(_buttonbox, Qt::AlignHCenter);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void RegexConfigDialog::SetOption(RegexConfig &conf,
|
||||
QRegularExpression::PatternOptions what,
|
||||
QCheckBox *cb)
|
||||
{
|
||||
if (cb->isChecked()) {
|
||||
conf._options |= what;
|
||||
} else {
|
||||
conf._options &= ~what;
|
||||
}
|
||||
}
|
||||
|
||||
bool RegexConfigDialog::AskForSettings(QWidget *parent, RegexConfig &settings)
|
||||
{
|
||||
RegexConfigDialog dialog(parent, settings);
|
||||
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
if (dialog.exec() != DialogCode::Accepted) {
|
||||
return false;
|
||||
}
|
||||
settings._partialMatch = dialog._partialMatch->isChecked();
|
||||
SetOption(settings, QRegularExpression::CaseInsensitiveOption,
|
||||
dialog._caseInsensitive);
|
||||
SetOption(settings, QRegularExpression::DotMatchesEverythingOption,
|
||||
dialog._dotMatch);
|
||||
SetOption(settings, QRegularExpression::MultilineOption,
|
||||
dialog._multiLine);
|
||||
SetOption(settings, QRegularExpression::ExtendedPatternSyntaxOption,
|
||||
dialog._extendedPattern);
|
||||
return true;
|
||||
}
|
||||
74
src/utils/regex-config.hpp
Normal file
74
src/utils/regex-config.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
#include "obs-data.h"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QCheckBox>
|
||||
#include <QPushButton>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QRegularExpression>
|
||||
|
||||
class RegexConfigWidget;
|
||||
class RegexConfigDialog;
|
||||
|
||||
class RegexConfig {
|
||||
public:
|
||||
void Save(obs_data_t *obj);
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
bool Enabled() const { return _enable; }
|
||||
void CreateBackwardsCompatibleRegex(bool, bool = true);
|
||||
QRegularExpression::PatternOptions GetPatternOptions() const
|
||||
{
|
||||
return _options;
|
||||
};
|
||||
QRegularExpression GetRegularExpression(const QString &) const;
|
||||
QRegularExpression GetRegularExpression(const std::string &) const;
|
||||
|
||||
private:
|
||||
bool _enable = false;
|
||||
bool _partialMatch = false;
|
||||
QRegularExpression::PatternOptions _options =
|
||||
QRegularExpression::NoPatternOption;
|
||||
friend RegexConfigWidget;
|
||||
friend RegexConfigDialog;
|
||||
};
|
||||
|
||||
class RegexConfigWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RegexConfigWidget(QWidget *parent = nullptr);
|
||||
void SetRegexConfig(const RegexConfig &);
|
||||
|
||||
private slots:
|
||||
void EnableChanged(int);
|
||||
void OpenSettingsClicked();
|
||||
signals:
|
||||
void RegexConfigChanged(RegexConfig);
|
||||
|
||||
private:
|
||||
void SetVisibility();
|
||||
|
||||
QPushButton *_openSettings;
|
||||
QCheckBox *_enable;
|
||||
RegexConfig _config;
|
||||
};
|
||||
|
||||
class RegexConfigDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RegexConfigDialog(QWidget *parent, const RegexConfig &);
|
||||
static bool AskForSettings(QWidget *parent, RegexConfig &settings);
|
||||
|
||||
private:
|
||||
static void SetOption(RegexConfig &, QRegularExpression::PatternOptions,
|
||||
QCheckBox *);
|
||||
|
||||
QCheckBox *_partialMatch;
|
||||
QCheckBox *_caseInsensitive;
|
||||
QCheckBox *_dotMatch;
|
||||
QCheckBox *_multiLine;
|
||||
QCheckBox *_extendedPattern;
|
||||
QDialogButtonBox *_buttonbox;
|
||||
};
|
||||
@@ -2,28 +2,105 @@
|
||||
|
||||
#include <obs-module.h>
|
||||
|
||||
void SceneItemSelection::Save(obs_data_t *obj, const char *name,
|
||||
const char *targetName, const char *idxName)
|
||||
constexpr std::string_view typeSaveName = "type";
|
||||
constexpr std::string_view itemSaveName = "item";
|
||||
constexpr std::string_view idxSaveName = "idx";
|
||||
constexpr std::string_view idxTypeSaveName = "idxType";
|
||||
|
||||
void SceneItemSelection::Save(obs_data_t *obj, const char *name) const
|
||||
{
|
||||
obs_data_set_int(obj, targetName, static_cast<int>(_target));
|
||||
if (_target == SceneItemSelection::Target::INDIVIDUAL) {
|
||||
obs_data_set_int(obj, idxName, _idx);
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
|
||||
obs_data_set_int(data, idxTypeSaveName.data(),
|
||||
static_cast<int>(_idxType));
|
||||
if (_idxType != IdxType::INDIVIDUAL) {
|
||||
obs_data_set_int(data, idxSaveName.data(), _idx);
|
||||
} else {
|
||||
obs_data_set_int(obj, idxName, 0);
|
||||
obs_data_set_int(data, idxSaveName.data(), 0);
|
||||
}
|
||||
obs_data_set_string(obj, name, GetWeakSourceName(_sceneItem).c_str());
|
||||
if (_type == SceneItemSelection::Type::SOURCE) {
|
||||
obs_data_set_string(data, itemSaveName.data(),
|
||||
GetWeakSourceName(_sceneItem).c_str());
|
||||
} else {
|
||||
auto var = _variable.lock();
|
||||
if (var) {
|
||||
obs_data_set_string(data, itemSaveName.data(),
|
||||
var->Name().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
obs_data_set_obj(obj, name, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
// TODO: Remove in future version
|
||||
void SceneItemSelection::Load(obs_data_t *obj, const char *name,
|
||||
const char *targetName, const char *idxName)
|
||||
const char *typeName, const char *idxName)
|
||||
{
|
||||
_target = static_cast<SceneItemSelection::Target>(
|
||||
obs_data_get_int(obj, targetName));
|
||||
_type = Type::SOURCE;
|
||||
_idxType = static_cast<IdxType>(obs_data_get_int(obj, typeName));
|
||||
_idx = obs_data_get_int(obj, idxName);
|
||||
auto sceneItemName = obs_data_get_string(obj, name);
|
||||
_sceneItem = GetWeakSourceByName(sceneItemName);
|
||||
}
|
||||
|
||||
void SceneItemSelection::Load(obs_data_t *obj, const char *name)
|
||||
{
|
||||
// TODO: Remove in future version
|
||||
if (!obs_data_has_user_value(obj, name)) {
|
||||
Load(obj, "sceneItem", "sceneItemTarget", "sceneItemIdx");
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = obs_data_get_obj(obj, name);
|
||||
_type = static_cast<Type>(obs_data_get_int(data, typeSaveName.data()));
|
||||
_idxType = static_cast<IdxType>(
|
||||
obs_data_get_int(data, idxTypeSaveName.data()));
|
||||
_idx = obs_data_get_int(data, idxSaveName.data());
|
||||
const auto itemName = obs_data_get_string(data, itemSaveName.data());
|
||||
switch (_type) {
|
||||
case SceneItemSelection::Type::SOURCE:
|
||||
_sceneItem = GetWeakSourceByName(itemName);
|
||||
break;
|
||||
case SceneItemSelection::Type::VARIABLE:
|
||||
_variable = GetWeakVariableByName(itemName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
struct ItemInfo {
|
||||
std::string name;
|
||||
std::vector<obs_sceneitem_t *> items = {};
|
||||
};
|
||||
|
||||
static bool getSceneItems(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
{
|
||||
ItemInfo *moveInfo = reinterpret_cast<ItemInfo *>(ptr);
|
||||
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
|
||||
if (moveInfo->name == sourceName) {
|
||||
obs_sceneitem_addref(item);
|
||||
moveInfo->items.push_back(item);
|
||||
}
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, getSceneItems, ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<obs_scene_item *> getSceneItemsWithName(obs_scene_t *scene,
|
||||
std::string &name)
|
||||
{
|
||||
ItemInfo itemInfo = {name};
|
||||
obs_scene_enum_items(scene, getSceneItems, &itemInfo);
|
||||
return itemInfo.items;
|
||||
}
|
||||
|
||||
struct ItemCountData {
|
||||
std::string name;
|
||||
int count = 0;
|
||||
@@ -44,12 +121,12 @@ static bool countSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
return true;
|
||||
}
|
||||
|
||||
int getCountOfSceneItemOccurance(SceneSelection &s, std::string &name,
|
||||
int getCountOfSceneItemOccurance(const SceneSelection &s,
|
||||
const std::string &name,
|
||||
bool enumAllScenes = true)
|
||||
{
|
||||
ItemCountData data{name};
|
||||
if (enumAllScenes && (s.GetType() == SceneSelectionType::CURRENT ||
|
||||
s.GetType() == SceneSelectionType::PREVIOUS)) {
|
||||
if (enumAllScenes && (s.GetType() != SceneSelection::Type::SCENE)) {
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
if (!source) {
|
||||
return true;
|
||||
@@ -72,17 +149,25 @@ int getCountOfSceneItemOccurance(SceneSelection &s, std::string &name,
|
||||
std::vector<obs_scene_item *>
|
||||
SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
|
||||
{
|
||||
std::vector<obs_scene_item *> ret;
|
||||
auto s = obs_weak_source_get_source(sceneSelection.GetScene(false));
|
||||
auto scene = obs_scene_from_source(s);
|
||||
auto name = GetWeakSourceName(_sceneItem);
|
||||
std::string name;
|
||||
if (_type == Type::VARIABLE) {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
return ret;
|
||||
}
|
||||
name = var->Value();
|
||||
} else {
|
||||
auto name = GetWeakSourceName(_sceneItem);
|
||||
}
|
||||
int count = getCountOfSceneItemOccurance(sceneSelection, name, false);
|
||||
auto items = getSceneItemsWithName(scene, name);
|
||||
obs_source_release(s);
|
||||
|
||||
std::vector<obs_scene_item *> ret;
|
||||
|
||||
if (_target == SceneItemSelection::Target::ALL ||
|
||||
_target == SceneItemSelection::Target::ANY) {
|
||||
if (_idxType == SceneItemSelection::IdxType::ALL ||
|
||||
_idxType == SceneItemSelection::IdxType::ANY) {
|
||||
ret = items;
|
||||
} else {
|
||||
// Index order starts at the bottom and increases to the top
|
||||
@@ -103,13 +188,96 @@ SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
|
||||
|
||||
std::string SceneItemSelection::ToString()
|
||||
{
|
||||
if (_type == Type::VARIABLE) {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
return "";
|
||||
}
|
||||
return var->Name();
|
||||
}
|
||||
return GetWeakSourceName(_sceneItem);
|
||||
}
|
||||
|
||||
static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
{
|
||||
QStringList *names = reinterpret_cast<QStringList *>(ptr);
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, enumSceneItem, ptr);
|
||||
}
|
||||
auto name = obs_source_get_name(obs_sceneitem_get_source(item));
|
||||
names->append(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void populateSceneItemSelection(QComboBox *list)
|
||||
{
|
||||
QStringList names;
|
||||
obs_scene_enum_items(nullptr, enumSceneItem, &names);
|
||||
names.removeDuplicates();
|
||||
names.sort();
|
||||
list->addItems(names);
|
||||
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
|
||||
list->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
QStringList GetSceneItemsList(SceneSelection &s)
|
||||
{
|
||||
QStringList names;
|
||||
if (s.GetType() != SceneSelection::Type::SCENE) {
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
if (!source) {
|
||||
return true;
|
||||
}
|
||||
QStringList *names =
|
||||
reinterpret_cast<QStringList *>(param);
|
||||
auto scene = obs_scene_from_source(source);
|
||||
obs_scene_enum_items(scene, enumSceneItem, names);
|
||||
return true;
|
||||
};
|
||||
|
||||
obs_enum_scenes(enumScenes, &names);
|
||||
} else {
|
||||
auto source = obs_weak_source_get_source(s.GetScene(false));
|
||||
auto scene = obs_scene_from_source(source);
|
||||
obs_scene_enum_items(scene, enumSceneItem, &names);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
names.removeDuplicates();
|
||||
names.sort();
|
||||
return names;
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::Reset()
|
||||
{
|
||||
auto previousSel = _currentSelection;
|
||||
PopulateItemSelection();
|
||||
SetSceneItem(previousSel);
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::PopulateItemSelection()
|
||||
{
|
||||
_sceneItems->clear();
|
||||
addSelectionEntry(_sceneItems,
|
||||
obs_module_text("AdvSceneSwitcher.selectItem"));
|
||||
_sceneItems->insertSeparator(_sceneItems->count());
|
||||
|
||||
const QStringList variables = GetVariablesNameList();
|
||||
addSelectionGroup(_sceneItems, variables);
|
||||
_variablesEndIdx = _sceneItems->count();
|
||||
|
||||
const QStringList sceneItmes = GetSceneItemsList(_scene);
|
||||
addSelectionGroup(_sceneItems, sceneItmes, false);
|
||||
_itemsEndIdx = _sceneItems->count();
|
||||
_sceneItems->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
|
||||
bool showAll,
|
||||
AllSelectionType type)
|
||||
: QWidget(parent), _showAll(showAll), _allType(type)
|
||||
: QWidget(parent), _hasAllEntry(showAll), _allType(type)
|
||||
{
|
||||
_sceneItems = new QComboBox();
|
||||
_idx = new QComboBox();
|
||||
@@ -124,6 +292,16 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
|
||||
SLOT(SelectionChanged(const QString &)));
|
||||
QWidget::connect(_idx, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(IdxChanged(int)));
|
||||
// Variables
|
||||
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
|
||||
SLOT(ItemAdd(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
|
||||
this, SLOT(ItemRemove(const QString &)));
|
||||
QWidget::connect(
|
||||
window(),
|
||||
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
|
||||
SLOT(ItemRename(const QString &, const QString &)));
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_idx);
|
||||
@@ -134,21 +312,48 @@ SceneItemSelectionWidget::SceneItemSelectionWidget(QWidget *parent,
|
||||
|
||||
void SceneItemSelectionWidget::SetSceneItem(const SceneItemSelection &item)
|
||||
{
|
||||
_sceneItems->setCurrentText(
|
||||
QString::fromStdString(GetWeakSourceName(item._sceneItem)));
|
||||
if (item._target == SceneItemSelection::Target::ALL) {
|
||||
_allType = AllSelectionType::ALL;
|
||||
_idx->setCurrentIndex(0);
|
||||
} else if (item._target == SceneItemSelection::Target::ANY) {
|
||||
_allType = AllSelectionType::ANY;
|
||||
_idx->setCurrentIndex(0);
|
||||
} else {
|
||||
int itemIdx = 0;
|
||||
switch (item._type) {
|
||||
case SceneItemSelection::Type::SOURCE: {
|
||||
int idx = item._idx;
|
||||
if (_showAll) {
|
||||
if (_hasAllEntry) {
|
||||
idx += 1;
|
||||
}
|
||||
_idx->setCurrentIndex(idx);
|
||||
itemIdx = findIdxInRagne(_sceneItems, _variablesEndIdx,
|
||||
_itemsEndIdx,
|
||||
GetWeakSourceName(item._sceneItem));
|
||||
_sceneItems->setCurrentIndex(itemIdx);
|
||||
break;
|
||||
}
|
||||
case SceneItemSelection::Type::VARIABLE: {
|
||||
|
||||
auto var = item._variable.lock();
|
||||
if (!var) {
|
||||
break;
|
||||
}
|
||||
itemIdx = findIdxInRagne(_sceneItems, _selectIdx,
|
||||
_variablesEndIdx, var->Name());
|
||||
_sceneItems->setCurrentIndex(itemIdx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (item._idxType) {
|
||||
case SceneItemSelection::IdxType::ALL:
|
||||
case SceneItemSelection::IdxType::ANY:
|
||||
_allType = AllSelectionType::ALL;
|
||||
_idx->setCurrentIndex(0);
|
||||
break;
|
||||
case SceneItemSelection::IdxType::INDIVIDUAL:
|
||||
int idx = item._idx;
|
||||
if (_hasAllEntry) {
|
||||
idx += 1;
|
||||
}
|
||||
_idx->setCurrentIndex(idx);
|
||||
break;
|
||||
}
|
||||
_currentSelection = item;
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
|
||||
@@ -156,12 +361,12 @@ void SceneItemSelectionWidget::SetScene(const SceneSelection &s)
|
||||
_scene = s;
|
||||
_sceneItems->clear();
|
||||
_idx->hide();
|
||||
populateSceneItemSelection(_sceneItems, _scene);
|
||||
PopulateItemSelection();
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::SetShowAll(bool value)
|
||||
{
|
||||
_showAll = value;
|
||||
_hasAllEntry = value;
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::SetShowAllSelectionType(AllSelectionType t)
|
||||
@@ -179,21 +384,36 @@ void SceneItemSelectionWidget::SceneChanged(const SceneSelection &s)
|
||||
void SceneItemSelectionWidget::SelectionChanged(const QString &name)
|
||||
{
|
||||
SceneItemSelection s;
|
||||
_sceneItem = GetWeakSourceByQString(name);
|
||||
s._sceneItem = _sceneItem;
|
||||
if (_allType == AllSelectionType::ALL) {
|
||||
s._target = SceneItemSelection::Target::ALL;
|
||||
} else {
|
||||
s._target = SceneItemSelection::Target::ANY;
|
||||
}
|
||||
auto stdName = name.toStdString();
|
||||
int sceneItemCount = getCountOfSceneItemOccurance(_scene, stdName);
|
||||
int sceneItemCount =
|
||||
getCountOfSceneItemOccurance(_scene, name.toStdString());
|
||||
if (sceneItemCount > 1) {
|
||||
_idx->show();
|
||||
SetupIdxSelection(sceneItemCount);
|
||||
} else {
|
||||
_idx->hide();
|
||||
}
|
||||
|
||||
if (_hasAllEntry) {
|
||||
switch (_allType) {
|
||||
case SceneItemSelectionWidget::AllSelectionType::ALL:
|
||||
s._idxType = SceneItemSelection::IdxType::ALL;
|
||||
break;
|
||||
case SceneItemSelectionWidget::AllSelectionType::ANY:
|
||||
s._idxType = SceneItemSelection::IdxType::ANY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const int idx = _sceneItems->currentIndex();
|
||||
if (idx < _variablesEndIdx) {
|
||||
s._type = SceneItemSelection::Type::VARIABLE;
|
||||
s._variable = GetWeakVariableByQString(name);
|
||||
} else if (idx < _itemsEndIdx) {
|
||||
s._type = SceneItemSelection::Type::SOURCE;
|
||||
s._sceneItem = GetWeakSourceByQString(name);
|
||||
}
|
||||
|
||||
_currentSelection = s;
|
||||
emit SceneItemChanged(s);
|
||||
}
|
||||
|
||||
@@ -203,29 +423,50 @@ void SceneItemSelectionWidget::IdxChanged(int idx)
|
||||
return;
|
||||
}
|
||||
|
||||
SceneItemSelection s;
|
||||
s._sceneItem = _sceneItem;
|
||||
if (_showAll && idx == 0) {
|
||||
if (_allType == AllSelectionType::ALL) {
|
||||
s._target = SceneItemSelection::Target::ALL;
|
||||
} else {
|
||||
s._target = SceneItemSelection::Target::ANY;
|
||||
_currentSelection._idx = idx;
|
||||
if (_hasAllEntry && idx == 0) {
|
||||
switch (_allType) {
|
||||
case SceneItemSelectionWidget::AllSelectionType::ALL:
|
||||
_currentSelection._idxType =
|
||||
SceneItemSelection::IdxType::ALL;
|
||||
break;
|
||||
case SceneItemSelectionWidget::AllSelectionType::ANY:
|
||||
_currentSelection._idxType =
|
||||
SceneItemSelection::IdxType::ANY;
|
||||
break;
|
||||
}
|
||||
s._idx = 0;
|
||||
} else {
|
||||
s._target = SceneItemSelection::Target::INDIVIDUAL;
|
||||
if (_showAll) {
|
||||
idx -= 1;
|
||||
}
|
||||
s._idx = idx;
|
||||
}
|
||||
emit SceneItemChanged(s);
|
||||
if (_hasAllEntry && idx > 0) {
|
||||
_currentSelection._idx -= 1;
|
||||
_currentSelection._idxType =
|
||||
SceneItemSelection::IdxType::INDIVIDUAL;
|
||||
}
|
||||
emit SceneItemChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::ItemAdd(const QString &)
|
||||
{
|
||||
blockSignals(true);
|
||||
Reset();
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::ItemRemove(const QString &)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::ItemRename(const QString &, const QString &)
|
||||
{
|
||||
blockSignals(true);
|
||||
Reset();
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
void SceneItemSelectionWidget::SetupIdxSelection(int sceneItemCount)
|
||||
{
|
||||
_idx->clear();
|
||||
if (_showAll) {
|
||||
if (_hasAllEntry) {
|
||||
if (_allType == AllSelectionType::ALL) {
|
||||
_idx->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneItemSelection.all"));
|
||||
|
||||
@@ -1,28 +1,41 @@
|
||||
#pragma once
|
||||
#include "scene-selection.hpp"
|
||||
#include "variable.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <obs-data.h>
|
||||
|
||||
#include "scene-selection.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
class SceneItemSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name = "sceneItem",
|
||||
const char *targetName = "sceneItemTarget",
|
||||
const char *idxName = "sceneItemIdx");
|
||||
void Load(obs_data_t *obj, const char *name = "sceneItem",
|
||||
const char *targetName = "sceneItemTarget",
|
||||
const char *idxName = "sceneItemIdx");
|
||||
void Save(obs_data_t *obj,
|
||||
const char *name = "sceneItemSelection") const;
|
||||
void Load(obs_data_t *obj, const char *name = "sceneItemSelection");
|
||||
// TODO: Remove in future version
|
||||
void Load(obs_data_t *obj, const char *name, const char *targetName,
|
||||
const char *idxName);
|
||||
|
||||
enum class Target { ALL, ANY, INDIVIDUAL };
|
||||
Target GetType() { return _target; }
|
||||
enum class Type {
|
||||
SOURCE,
|
||||
VARIABLE,
|
||||
};
|
||||
enum class IdxType {
|
||||
ALL,
|
||||
ANY,
|
||||
INDIVIDUAL,
|
||||
};
|
||||
|
||||
Type GetType() { return _type; }
|
||||
IdxType GetIndexType() { return _idxType; }
|
||||
std::vector<obs_scene_item *> GetSceneItems(SceneSelection &s);
|
||||
std::string ToString();
|
||||
|
||||
private:
|
||||
OBSWeakSource _sceneItem;
|
||||
Target _target = Target::ALL;
|
||||
int _idx = 0;
|
||||
std::weak_ptr<Variable> _variable;
|
||||
Type _type = Type::SOURCE;
|
||||
IdxType _idxType = IdxType::ALL;
|
||||
int _idx = 0; // Multiple items with the same name can exist
|
||||
friend class SceneItemSelectionWidget;
|
||||
};
|
||||
|
||||
@@ -30,9 +43,10 @@ class SceneItemSelectionWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
// Influences what is being displaye for the "all" entry
|
||||
enum class AllSelectionType { ALL, ANY };
|
||||
SceneItemSelectionWidget(
|
||||
QWidget *parent, bool allSelection = true,
|
||||
QWidget *parent, bool addAllSelection = true,
|
||||
AllSelectionType allType = AllSelectionType::ALL);
|
||||
void SetSceneItem(const SceneItemSelection &);
|
||||
void SetScene(const SceneSelection &);
|
||||
@@ -45,15 +59,28 @@ private slots:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
void SelectionChanged(const QString &name);
|
||||
void IdxChanged(int);
|
||||
void ItemAdd(const QString &name);
|
||||
void ItemRemove(const QString &name);
|
||||
void ItemRename(const QString &oldName, const QString &newName);
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
void PopulateItemSelection();
|
||||
void SetupIdxSelection(int);
|
||||
|
||||
QComboBox *_sceneItems;
|
||||
QComboBox *_idx;
|
||||
|
||||
SceneSelection _scene;
|
||||
OBSWeakSource _sceneItem;
|
||||
bool _showAll = false;
|
||||
SceneItemSelection _currentSelection;
|
||||
bool _hasAllEntry = false;
|
||||
AllSelectionType _allType = AllSelectionType::ALL;
|
||||
|
||||
// Order of entries
|
||||
// 1. "select entry" entry
|
||||
// 2. Variables
|
||||
// 3. Scene items
|
||||
const int _selectIdx = 0;
|
||||
int _variablesEndIdx = -1;
|
||||
int _itemsEndIdx = -1;
|
||||
};
|
||||
|
||||
@@ -1,52 +1,95 @@
|
||||
#include "scene-selection.hpp"
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
void SceneSelection::Save(obs_data_t *obj, const char *name,
|
||||
const char *typeName)
|
||||
constexpr std::string_view typeSaveName = "type";
|
||||
constexpr std::string_view nameSaveName = "name";
|
||||
constexpr std::string_view selectionSaveName = "sceneSelection";
|
||||
|
||||
void SceneSelection::Save(obs_data_t *obj) const
|
||||
{
|
||||
obs_data_set_int(obj, typeName, static_cast<int>(_type));
|
||||
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
|
||||
switch (_type) {
|
||||
case SceneSelectionType::SCENE:
|
||||
obs_data_set_string(obj, name,
|
||||
case Type::SCENE:
|
||||
obs_data_set_string(data, nameSaveName.data(),
|
||||
GetWeakSourceName(_scene).c_str());
|
||||
break;
|
||||
case SceneSelectionType::GROUP:
|
||||
obs_data_set_string(obj, name, _group->name.c_str());
|
||||
case Type::GROUP:
|
||||
obs_data_set_string(data, nameSaveName.data(),
|
||||
_group->name.c_str());
|
||||
break;
|
||||
case Type::VARIABLE: {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
break;
|
||||
}
|
||||
obs_data_set_string(data, nameSaveName.data(),
|
||||
var->Name().c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_data_set_obj(obj, selectionSaveName.data(), data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void SceneSelection::Load(obs_data_t *obj, const char *name,
|
||||
const char *typeName)
|
||||
{
|
||||
_type = static_cast<SceneSelectionType>(
|
||||
obs_data_get_int(obj, typeName));
|
||||
auto target = obs_data_get_string(obj, name);
|
||||
// TODO: Remove in future version
|
||||
if (!obs_data_has_user_value(obj, selectionSaveName.data())) {
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, typeName));
|
||||
auto targetName = obs_data_get_string(obj, name);
|
||||
switch (_type) {
|
||||
case Type::SCENE:
|
||||
_scene = GetWeakSourceByName(targetName);
|
||||
break;
|
||||
case Type::GROUP:
|
||||
_group = GetSceneGroupByName(targetName);
|
||||
break;
|
||||
case Type::PREVIOUS:
|
||||
break;
|
||||
case Type::CURRENT:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = obs_data_get_obj(obj, selectionSaveName.data());
|
||||
_type = static_cast<Type>(obs_data_get_int(data, typeSaveName.data()));
|
||||
auto targetName = obs_data_get_string(data, nameSaveName.data());
|
||||
switch (_type) {
|
||||
case SceneSelectionType::SCENE:
|
||||
_scene = GetWeakSourceByName(target);
|
||||
case Type::SCENE:
|
||||
_scene = GetWeakSourceByName(targetName);
|
||||
break;
|
||||
case SceneSelectionType::GROUP:
|
||||
_group = GetSceneGroupByName(target);
|
||||
case Type::GROUP:
|
||||
_group = GetSceneGroupByName(targetName);
|
||||
break;
|
||||
case SceneSelectionType::PREVIOUS:
|
||||
case Type::PREVIOUS:
|
||||
break;
|
||||
case SceneSelectionType::CURRENT:
|
||||
case Type::CURRENT:
|
||||
break;
|
||||
case Type::PREVIEW:
|
||||
break;
|
||||
case Type::VARIABLE:
|
||||
_variable = GetWeakVariableByName(targetName);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
OBSWeakSource SceneSelection::GetScene(bool advance)
|
||||
OBSWeakSource SceneSelection::GetScene(bool advance) const
|
||||
{
|
||||
switch (_type) {
|
||||
case SceneSelectionType::SCENE:
|
||||
case Type::SCENE:
|
||||
return _scene;
|
||||
case SceneSelectionType::GROUP:
|
||||
case Type::GROUP:
|
||||
if (!_group) {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -54,199 +97,339 @@ OBSWeakSource SceneSelection::GetScene(bool advance)
|
||||
return _group->getNextScene();
|
||||
}
|
||||
return _group->getCurrentScene();
|
||||
case SceneSelectionType::PREVIOUS:
|
||||
case Type::PREVIOUS:
|
||||
return switcher->previousScene;
|
||||
case SceneSelectionType::CURRENT:
|
||||
case Type::CURRENT:
|
||||
return switcher->currentScene;
|
||||
case Type::PREVIEW: {
|
||||
auto s = obs_frontend_get_current_preview_scene();
|
||||
auto scene = obs_source_get_weak_source(s);
|
||||
obs_weak_source_release(scene);
|
||||
obs_source_release(s);
|
||||
return scene;
|
||||
}
|
||||
case Type::VARIABLE: {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
return nullptr;
|
||||
}
|
||||
return GetWeakSourceByName(var->Value().c_str());
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string SceneSelection::ToString()
|
||||
std::string SceneSelection::ToString() const
|
||||
{
|
||||
switch (_type) {
|
||||
case SceneSelectionType::SCENE:
|
||||
case Type::SCENE:
|
||||
return GetWeakSourceName(_scene);
|
||||
case SceneSelectionType::GROUP:
|
||||
case Type::GROUP:
|
||||
if (_group) {
|
||||
return _group->name;
|
||||
}
|
||||
break;
|
||||
case SceneSelectionType::PREVIOUS:
|
||||
case Type::PREVIOUS:
|
||||
return obs_module_text("AdvSceneSwitcher.selectPreviousScene");
|
||||
case SceneSelectionType::CURRENT:
|
||||
case Type::CURRENT:
|
||||
return obs_module_text("AdvSceneSwitcher.selectCurrentScene");
|
||||
case Type::PREVIEW:
|
||||
return obs_module_text("AdvSceneSwitcher.selectPreviewScene");
|
||||
case Type::VARIABLE: {
|
||||
auto var = _variable.lock();
|
||||
if (!var) {
|
||||
return "";
|
||||
}
|
||||
return var->Name();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool sceneGroups,
|
||||
bool previous, bool current)
|
||||
: QComboBox(parent)
|
||||
SceneSelection SceneSelectionWidget::CurrentSelection()
|
||||
{
|
||||
SceneSelection s;
|
||||
const int idx = currentIndex();
|
||||
const auto name = currentText();
|
||||
|
||||
if (idx < _placeholderEndIdx) {
|
||||
if (IsCurrentSceneSelected(name)) {
|
||||
s._type = SceneSelection::Type::CURRENT;
|
||||
}
|
||||
if (IsPreviousSceneSelected(name)) {
|
||||
s._type = SceneSelection::Type::PREVIOUS;
|
||||
}
|
||||
if (IsPreviewSceneSelected(name)) {
|
||||
s._type = SceneSelection::Type::PREVIEW;
|
||||
}
|
||||
} else if (idx < _variablesEndIdx) {
|
||||
s._type = SceneSelection::Type::VARIABLE;
|
||||
s._variable = GetWeakVariableByQString(name);
|
||||
} else if (idx < _groupsEndIdx) {
|
||||
s._type = SceneSelection::Type::GROUP;
|
||||
s._group = GetSceneGroupByQString(name);
|
||||
} else if (idx < _scenesEndIdx) {
|
||||
s._type = SceneSelection::Type::SCENE;
|
||||
s._scene = GetWeakSourceByQString(name);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static QStringList getOrderList(bool current, bool previous, bool preview)
|
||||
{
|
||||
QStringList list;
|
||||
if (current) {
|
||||
list << obs_module_text("AdvSceneSwitcher.selectCurrentScene");
|
||||
}
|
||||
if (previous) {
|
||||
list << obs_module_text("AdvSceneSwitcher.selectPreviousScene");
|
||||
}
|
||||
if (preview) {
|
||||
list << obs_module_text("AdvSceneSwitcher.selectPreviewScene");
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
static QStringList getScenesList()
|
||||
{
|
||||
QStringList list;
|
||||
char **scenes = obs_frontend_get_scene_names();
|
||||
char **temp = scenes;
|
||||
while (*temp) {
|
||||
const char *name = *temp;
|
||||
list << name;
|
||||
temp++;
|
||||
}
|
||||
bfree(scenes);
|
||||
return list;
|
||||
}
|
||||
|
||||
static QStringList getSceneGroupsList()
|
||||
{
|
||||
QStringList list;
|
||||
for (const auto &sg : switcher->sceneGroups) {
|
||||
list << QString::fromStdString(sg.name);
|
||||
}
|
||||
list.sort();
|
||||
return list;
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::Reset()
|
||||
{
|
||||
auto previousSel = _currentSelection;
|
||||
PopulateSelection();
|
||||
SetScene(previousSel);
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::PopulateSelection()
|
||||
{
|
||||
clear();
|
||||
addSelectionEntry(this,
|
||||
obs_module_text("AdvSceneSwitcher.selectScene"));
|
||||
insertSeparator(count());
|
||||
|
||||
if (_current || _previous) {
|
||||
const QStringList order =
|
||||
getOrderList(_current, _previous, _preview);
|
||||
addSelectionGroup(this, order);
|
||||
}
|
||||
_placeholderEndIdx = count();
|
||||
|
||||
if (_variables) {
|
||||
const QStringList variables = GetVariablesNameList();
|
||||
addSelectionGroup(this, variables);
|
||||
}
|
||||
_variablesEndIdx = count();
|
||||
|
||||
if (_sceneGroups) {
|
||||
const QStringList sceneGroups = getSceneGroupsList();
|
||||
addSelectionGroup(this, sceneGroups);
|
||||
}
|
||||
_groupsEndIdx = count();
|
||||
|
||||
const QStringList scenes = getScenesList();
|
||||
addSelectionGroup(this, scenes);
|
||||
_scenesEndIdx = count();
|
||||
|
||||
// Remove last separator
|
||||
removeItem(count() - 1);
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
|
||||
SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
|
||||
bool sceneGroups, bool previous,
|
||||
bool current, bool preview)
|
||||
: QComboBox(parent),
|
||||
_current(current),
|
||||
_previous(previous),
|
||||
_preview(preview),
|
||||
_variables(variables),
|
||||
_sceneGroups(sceneGroups)
|
||||
{
|
||||
// For the rare occasion of a name conflict with current / previous
|
||||
setDuplicatesEnabled(true);
|
||||
populateSceneSelection(this, previous, current, false, sceneGroups,
|
||||
&switcher->sceneGroups);
|
||||
PopulateSelection();
|
||||
|
||||
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(SelectionChanged(const QString &)));
|
||||
QWidget::connect(parent, SIGNAL(SceneGroupAdded(const QString &)), this,
|
||||
SLOT(SceneGroupAdd(const QString &)));
|
||||
QWidget::connect(parent, SIGNAL(SceneGroupRemoved(const QString &)),
|
||||
this, SLOT(SceneGroupRemove(const QString &)));
|
||||
|
||||
// Scene groups
|
||||
QWidget::connect(window(), SIGNAL(SceneGroupAdded(const QString &)),
|
||||
this, SLOT(ItemAdd(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(SceneGroupRemoved(const QString &)),
|
||||
this, SLOT(ItemRemove(const QString &)));
|
||||
QWidget::connect(
|
||||
parent,
|
||||
window(),
|
||||
SIGNAL(SceneGroupRenamed(const QString &, const QString &)),
|
||||
this, SLOT(SceneGroupRename(const QString &, const QString &)));
|
||||
this, SLOT(ItemRename(const QString &, const QString &)));
|
||||
|
||||
// Variables
|
||||
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
|
||||
SLOT(ItemAdd(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
|
||||
this, SLOT(ItemRemove(const QString &)));
|
||||
QWidget::connect(
|
||||
window(),
|
||||
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
|
||||
SLOT(ItemRename(const QString &, const QString &)));
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::SetScene(SceneSelection &s)
|
||||
void SceneSelectionWidget::SetScene(const SceneSelection &s)
|
||||
{
|
||||
// Order of entries
|
||||
// 1. Any Scene (current not used)
|
||||
// 2. Current Scene
|
||||
// 3. Previous Scene
|
||||
// 4. Scenes / Scene Groups
|
||||
|
||||
int idx;
|
||||
int idx = 0;
|
||||
|
||||
switch (s.GetType()) {
|
||||
case SceneSelectionType::SCENE:
|
||||
case SceneSelectionType::GROUP:
|
||||
setCurrentText(QString::fromStdString(s.ToString()));
|
||||
break;
|
||||
case SceneSelectionType::PREVIOUS:
|
||||
idx = findText(QString::fromStdString(obs_module_text(
|
||||
"AdvSceneSwitcher.selectPreviousScene")));
|
||||
if (idx != -1) {
|
||||
setCurrentIndex(idx);
|
||||
case SceneSelection::Type::SCENE: {
|
||||
if (_scenesEndIdx == -1) {
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
idx = findIdxInRagne(this, _groupsEndIdx, _scenesEndIdx,
|
||||
s.ToString());
|
||||
break;
|
||||
case SceneSelectionType::CURRENT:
|
||||
idx = findText(QString::fromStdString(obs_module_text(
|
||||
"AdvSceneSwitcher.selectCurrentScene")));
|
||||
if (idx != -1) {
|
||||
setCurrentIndex(idx);
|
||||
}
|
||||
case SceneSelection::Type::GROUP: {
|
||||
if (_groupsEndIdx == -1) {
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
idx = findIdxInRagne(this, _variablesEndIdx, _groupsEndIdx,
|
||||
s.ToString());
|
||||
break;
|
||||
}
|
||||
case SceneSelection::Type::PREVIOUS: {
|
||||
if (_placeholderEndIdx == -1) {
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
idx = findIdxInRagne(
|
||||
this, _selectIdx, _placeholderEndIdx,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.selectPreviousScene"));
|
||||
break;
|
||||
}
|
||||
case SceneSelection::Type::CURRENT: {
|
||||
if (_placeholderEndIdx == -1) {
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
idx = findIdxInRagne(
|
||||
this, _selectIdx, _placeholderEndIdx,
|
||||
obs_module_text("AdvSceneSwitcher.selectCurrentScene"));
|
||||
break;
|
||||
}
|
||||
case SceneSelection::Type::PREVIEW: {
|
||||
if (_placeholderEndIdx == -1) {
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
idx = findIdxInRagne(
|
||||
this, _selectIdx, _placeholderEndIdx,
|
||||
obs_module_text("AdvSceneSwitcher.selectPreviewScene"));
|
||||
break;
|
||||
}
|
||||
case SceneSelection::Type::VARIABLE: {
|
||||
if (_variablesEndIdx == -1) {
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
idx = findIdxInRagne(this, _placeholderEndIdx, _variablesEndIdx,
|
||||
s.ToString());
|
||||
break;
|
||||
default:
|
||||
setCurrentIndex(0);
|
||||
idx = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isFirstEntry(QComboBox *l, QString name, int idx)
|
||||
{
|
||||
for (auto i = l->count() - 1; i >= 0; i--) {
|
||||
if (l->itemText(i) == name) {
|
||||
return idx == i;
|
||||
}
|
||||
}
|
||||
|
||||
// If entry cannot be found we dont want the selection to be empty
|
||||
return false;
|
||||
setCurrentIndex(idx);
|
||||
_currentSelection = s;
|
||||
}
|
||||
|
||||
bool SceneSelectionWidget::IsCurrentSceneSelected(const QString &name)
|
||||
{
|
||||
if (name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.selectCurrentScene")))) {
|
||||
return isFirstEntry(this, name, currentIndex());
|
||||
}
|
||||
return false;
|
||||
return name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.selectCurrentScene")));
|
||||
}
|
||||
|
||||
bool SceneSelectionWidget::IsPreviousSceneSelected(const QString &name)
|
||||
{
|
||||
if (name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.selectPreviousScene")))) {
|
||||
return isFirstEntry(this, name, currentIndex());
|
||||
return name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.selectPreviousScene")));
|
||||
}
|
||||
|
||||
bool SceneSelectionWidget::IsPreviewSceneSelected(const QString &name)
|
||||
{
|
||||
return name == QString::fromStdString((obs_module_text(
|
||||
"AdvSceneSwitcher.selectPreviewScene")));
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::SelectionChanged(const QString &)
|
||||
{
|
||||
_currentSelection = CurrentSelection();
|
||||
emit SceneChanged(_currentSelection);
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::ItemAdd(const QString &)
|
||||
{
|
||||
blockSignals(true);
|
||||
Reset();
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
bool SceneSelectionWidget::NameUsed(const QString &name)
|
||||
{
|
||||
if (_currentSelection._type == SceneSelection::Type::GROUP &&
|
||||
_currentSelection._group &&
|
||||
_currentSelection._group->name == name.toStdString()) {
|
||||
return true;
|
||||
}
|
||||
if (_currentSelection._type == SceneSelection::Type::VARIABLE) {
|
||||
auto var = _currentSelection._variable.lock();
|
||||
if (var && var->Name() == name.toStdString()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::SelectionChanged(const QString &name)
|
||||
void SceneSelectionWidget::ItemRemove(const QString &name)
|
||||
{
|
||||
SceneSelection s;
|
||||
auto scene = GetWeakSourceByQString(name);
|
||||
if (scene) {
|
||||
s._type = SceneSelectionType::SCENE;
|
||||
s._scene = scene;
|
||||
if (!NameUsed(name)) {
|
||||
blockSignals(true);
|
||||
}
|
||||
|
||||
auto group = GetSceneGroupByQString(name);
|
||||
if (group) {
|
||||
s._type = SceneSelectionType::GROUP;
|
||||
s._scene = nullptr;
|
||||
s._group = group;
|
||||
}
|
||||
|
||||
if (!scene && !group) {
|
||||
if (IsCurrentSceneSelected(name)) {
|
||||
s._type = SceneSelectionType::CURRENT;
|
||||
}
|
||||
if (IsPreviousSceneSelected(name)) {
|
||||
s._type = SceneSelectionType::PREVIOUS;
|
||||
}
|
||||
}
|
||||
|
||||
emit SceneChanged(s);
|
||||
Reset();
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::SceneGroupAdd(const QString &name)
|
||||
void SceneSelectionWidget::ItemRename(const QString &, const QString &)
|
||||
{
|
||||
addItem(name);
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::SceneGroupRemove(const QString &name)
|
||||
{
|
||||
int idx = findText(name);
|
||||
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int curIdx = currentIndex();
|
||||
removeItem(idx);
|
||||
|
||||
if (curIdx == idx) {
|
||||
SceneSelection s;
|
||||
emit SceneChanged(s);
|
||||
}
|
||||
|
||||
setCurrentIndex(0);
|
||||
}
|
||||
|
||||
static int findLastOf(QComboBox *l, QString name)
|
||||
{
|
||||
int idx = 0;
|
||||
for (auto i = l->count() - 1; i >= 0; i--) {
|
||||
if (l->itemText(i) == name) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void SceneSelectionWidget::SceneGroupRename(const QString &oldName,
|
||||
const QString &newName)
|
||||
{
|
||||
bool renameSelected = currentText() == oldName;
|
||||
int idx = findText(oldName);
|
||||
|
||||
if (idx == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeItem(idx);
|
||||
insertItem(idx, newName);
|
||||
|
||||
if (renameSelected) {
|
||||
setCurrentIndex(findLastOf(this, newName));
|
||||
}
|
||||
blockSignals(true);
|
||||
Reset();
|
||||
blockSignals(false);
|
||||
}
|
||||
|
||||
@@ -1,31 +1,34 @@
|
||||
#pragma once
|
||||
#include "scene-group.hpp"
|
||||
#include "variable.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
enum class SceneSelectionType {
|
||||
SCENE,
|
||||
GROUP,
|
||||
PREVIOUS,
|
||||
CURRENT,
|
||||
};
|
||||
|
||||
class SceneSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name = "scene",
|
||||
const char *typeName = "sceneType");
|
||||
void Save(obs_data_t *obj) const;
|
||||
void Load(obs_data_t *obj, const char *name = "scene",
|
||||
const char *typeName = "sceneType");
|
||||
|
||||
SceneSelectionType GetType() { return _type; }
|
||||
OBSWeakSource GetScene(bool advance = true);
|
||||
std::string ToString();
|
||||
enum class Type {
|
||||
SCENE,
|
||||
GROUP,
|
||||
PREVIOUS,
|
||||
CURRENT,
|
||||
PREVIEW,
|
||||
VARIABLE,
|
||||
};
|
||||
|
||||
Type GetType() const { return _type; }
|
||||
OBSWeakSource GetScene(bool advance = true) const;
|
||||
std::string ToString() const;
|
||||
|
||||
private:
|
||||
OBSWeakSource _scene;
|
||||
SceneGroup *_group = nullptr;
|
||||
SceneSelectionType _type = SceneSelectionType::SCENE;
|
||||
std::weak_ptr<Variable> _variable;
|
||||
Type _type = Type::SCENE;
|
||||
friend class SceneSelectionWidget;
|
||||
};
|
||||
|
||||
@@ -33,19 +36,45 @@ class SceneSelectionWidget : public QComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SceneSelectionWidget(QWidget *parent, bool sceneGroups = false,
|
||||
bool previous = false, bool current = false);
|
||||
void SetScene(SceneSelection &);
|
||||
SceneSelectionWidget(QWidget *parent, bool variables = false,
|
||||
bool sceneGroups = false, bool previous = false,
|
||||
bool current = false, bool preview = false);
|
||||
void SetScene(const SceneSelection &);
|
||||
signals:
|
||||
void SceneChanged(const SceneSelection &);
|
||||
|
||||
private slots:
|
||||
void SelectionChanged(const QString &name);
|
||||
void SceneGroupAdd(const QString &name);
|
||||
void SceneGroupRemove(const QString &name);
|
||||
void SceneGroupRename(const QString &oldName, const QString &newName);
|
||||
void ItemAdd(const QString &name);
|
||||
void ItemRemove(const QString &name);
|
||||
void ItemRename(const QString &oldName, const QString &newName);
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
SceneSelection CurrentSelection();
|
||||
void PopulateSelection();
|
||||
bool IsCurrentSceneSelected(const QString &name);
|
||||
bool IsPreviousSceneSelected(const QString &name);
|
||||
bool IsPreviewSceneSelected(const QString &name);
|
||||
bool NameUsed(const QString &name);
|
||||
|
||||
bool _current;
|
||||
bool _previous;
|
||||
bool _preview;
|
||||
bool _variables;
|
||||
bool _sceneGroups;
|
||||
|
||||
SceneSelection _currentSelection;
|
||||
|
||||
// Order of entries
|
||||
// 1. "select entry" entry
|
||||
// 2. Current / previous / preview scene
|
||||
// 3. Variables
|
||||
// 4. Scene groups
|
||||
// 5. Regular scenes
|
||||
const int _selectIdx = 0;
|
||||
int _placeholderEndIdx = -1;
|
||||
int _variablesEndIdx = -1;
|
||||
int _groupsEndIdx = -1;
|
||||
int _scenesEndIdx = -1;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,35 @@
|
||||
#include "screenshot-helper.hpp"
|
||||
#include "advanced-scene-switcher.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
static void ScreenshotTick(void *param, float);
|
||||
|
||||
ScreenshotHelper::ScreenshotHelper(obs_source_t *source)
|
||||
: weakSource(OBSGetWeakRef(source))
|
||||
ScreenshotHelper::ScreenshotHelper(obs_source_t *source, bool blocking,
|
||||
int timeout, bool saveToFile,
|
||||
std::string path)
|
||||
: weakSource(OBSGetWeakRef(source)),
|
||||
_blocking(blocking),
|
||||
_saveToFile(saveToFile),
|
||||
_path(path)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_initDone = true;
|
||||
obs_add_tick_callback(ScreenshotTick, this);
|
||||
if (_blocking) {
|
||||
auto res =
|
||||
_cv.wait_for(lock, std::chrono::milliseconds(timeout));
|
||||
if (res == std::cv_status::timeout) {
|
||||
if (source) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to get screenshot in time for source %s",
|
||||
obs_source_get_name(source));
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to get screenshot in time");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScreenshotHelper::~ScreenshotHelper()
|
||||
@@ -20,6 +42,9 @@ ScreenshotHelper::~ScreenshotHelper()
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
if (_saveThread.joinable()) {
|
||||
_saveThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenshotHelper::Screenshot()
|
||||
@@ -98,6 +123,26 @@ void ScreenshotHelper::MarkDone()
|
||||
{
|
||||
time = std::chrono::high_resolution_clock::now();
|
||||
done = true;
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_cv.notify_all();
|
||||
}
|
||||
|
||||
void ScreenshotHelper::WriteToFile()
|
||||
{
|
||||
if (!_saveToFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
_saveThread = std::thread([this]() {
|
||||
if (image.save(QString::fromStdString(_path))) {
|
||||
vblog(LOG_INFO, "Wrote screenshot to \"%s\"",
|
||||
_path.c_str());
|
||||
} else {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to save screenshot to \"%s\"!\nMaybe unknown format?",
|
||||
_path.c_str());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#define STAGE_SCREENSHOT 0
|
||||
@@ -124,6 +169,7 @@ static void ScreenshotTick(void *param, float)
|
||||
break;
|
||||
case STAGE_COPY_AND_SAVE:
|
||||
data->Copy();
|
||||
data->WriteToFile();
|
||||
data->MarkDone();
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, data);
|
||||
|
||||
@@ -3,11 +3,16 @@
|
||||
#include <string>
|
||||
#include <QImage>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
|
||||
class ScreenshotHelper {
|
||||
public:
|
||||
ScreenshotHelper() = default;
|
||||
ScreenshotHelper(obs_source_t *source);
|
||||
ScreenshotHelper(obs_source_t *source, bool blocking = false,
|
||||
int timeout = 1000, bool saveToFile = false,
|
||||
std::string path = "");
|
||||
ScreenshotHelper &operator=(const ScreenshotHelper &) = delete;
|
||||
ScreenshotHelper(const ScreenshotHelper &) = delete;
|
||||
~ScreenshotHelper();
|
||||
@@ -16,6 +21,7 @@ public:
|
||||
void Download();
|
||||
void Copy();
|
||||
void MarkDone();
|
||||
void WriteToFile();
|
||||
|
||||
gs_texrender_t *texrender = nullptr;
|
||||
gs_stagesurf_t *stagesurf = nullptr;
|
||||
@@ -31,4 +37,10 @@ public:
|
||||
|
||||
private:
|
||||
bool _initDone = false;
|
||||
bool _blocking = false;
|
||||
std::thread _saveThread;
|
||||
bool _saveToFile = false;
|
||||
std::string _path = "";
|
||||
std::mutex _mutex;
|
||||
std::condition_variable _cv;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ void TransitionSelection::Save(obs_data_t *obj, const char *name,
|
||||
obs_data_set_int(obj, typeName, static_cast<int>(_type));
|
||||
|
||||
switch (_type) {
|
||||
case TransitionSelectionType::TRANSITION:
|
||||
case Type::TRANSITION:
|
||||
obs_data_set_string(obj, name,
|
||||
GetWeakSourceName(_transition).c_str());
|
||||
break;
|
||||
@@ -19,11 +19,10 @@ void TransitionSelection::Save(obs_data_t *obj, const char *name,
|
||||
void TransitionSelection::Load(obs_data_t *obj, const char *name,
|
||||
const char *typeName)
|
||||
{
|
||||
_type = static_cast<TransitionSelectionType>(
|
||||
obs_data_get_int(obj, typeName));
|
||||
_type = static_cast<Type>(obs_data_get_int(obj, typeName));
|
||||
auto target = obs_data_get_string(obj, name);
|
||||
switch (_type) {
|
||||
case TransitionSelectionType::TRANSITION:
|
||||
case Type::TRANSITION:
|
||||
_transition = GetWeakTransitionByName(target);
|
||||
break;
|
||||
default:
|
||||
@@ -34,9 +33,9 @@ void TransitionSelection::Load(obs_data_t *obj, const char *name,
|
||||
OBSWeakSource TransitionSelection::GetTransition()
|
||||
{
|
||||
switch (_type) {
|
||||
case TransitionSelectionType::TRANSITION:
|
||||
case Type::TRANSITION:
|
||||
return _transition;
|
||||
case TransitionSelectionType::CURRENT: {
|
||||
case Type::CURRENT: {
|
||||
auto source = obs_frontend_get_current_transition();
|
||||
auto weakSource = obs_source_get_weak_source(source);
|
||||
obs_weak_source_release(weakSource);
|
||||
@@ -52,11 +51,11 @@ OBSWeakSource TransitionSelection::GetTransition()
|
||||
std::string TransitionSelection::ToString()
|
||||
{
|
||||
switch (_type) {
|
||||
case TransitionSelectionType::TRANSITION:
|
||||
case Type::TRANSITION:
|
||||
return GetWeakSourceName(_transition);
|
||||
case TransitionSelectionType::CURRENT:
|
||||
case Type::CURRENT:
|
||||
return obs_module_text("AdvSceneSwitcher.currentTransition");
|
||||
case TransitionSelectionType::ANY:
|
||||
case Type::ANY:
|
||||
return obs_module_text("AdvSceneSwitcher.anyTransition");
|
||||
default:
|
||||
break;
|
||||
@@ -85,17 +84,17 @@ void TransitionSelectionWidget::SetTransition(TransitionSelection &t)
|
||||
int idx;
|
||||
|
||||
switch (t.GetType()) {
|
||||
case TransitionSelectionType::TRANSITION:
|
||||
case TransitionSelection::Type::TRANSITION:
|
||||
setCurrentText(QString::fromStdString(t.ToString()));
|
||||
break;
|
||||
case TransitionSelectionType::CURRENT:
|
||||
case TransitionSelection::Type::CURRENT:
|
||||
idx = findText(QString::fromStdString(
|
||||
obs_module_text("AdvSceneSwitcher.currentTransition")));
|
||||
if (idx != -1) {
|
||||
setCurrentIndex(idx);
|
||||
}
|
||||
break;
|
||||
case TransitionSelectionType::ANY:
|
||||
case TransitionSelection::Type::ANY:
|
||||
idx = findText(QString::fromStdString(
|
||||
obs_module_text("AdvSceneSwitcher.anyTransition")));
|
||||
if (idx != -1) {
|
||||
@@ -155,16 +154,16 @@ void TransitionSelectionWidget::SelectionChanged(const QString &name)
|
||||
TransitionSelection t;
|
||||
auto transition = GetWeakTransitionByQString(name);
|
||||
if (transition) {
|
||||
t._type = TransitionSelectionType::TRANSITION;
|
||||
t._type = TransitionSelection::Type::TRANSITION;
|
||||
t._transition = transition;
|
||||
}
|
||||
|
||||
if (!transition) {
|
||||
if (IsCurrentTransitionSelected(name)) {
|
||||
t._type = TransitionSelectionType::CURRENT;
|
||||
t._type = TransitionSelection::Type::CURRENT;
|
||||
}
|
||||
if (IsAnyTransitionSelected(name)) {
|
||||
t._type = TransitionSelectionType::ANY;
|
||||
t._type = TransitionSelection::Type::ANY;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,12 +3,6 @@
|
||||
|
||||
#include <QComboBox>
|
||||
|
||||
enum class TransitionSelectionType {
|
||||
TRANSITION,
|
||||
CURRENT,
|
||||
ANY,
|
||||
};
|
||||
|
||||
class TransitionSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name = "transition",
|
||||
@@ -16,13 +10,19 @@ public:
|
||||
void Load(obs_data_t *obj, const char *name = "transition",
|
||||
const char *typeName = "transitionType");
|
||||
|
||||
TransitionSelectionType GetType() { return _type; }
|
||||
enum class Type {
|
||||
TRANSITION,
|
||||
CURRENT,
|
||||
ANY,
|
||||
};
|
||||
|
||||
Type GetType() { return _type; }
|
||||
OBSWeakSource GetTransition();
|
||||
std::string ToString();
|
||||
|
||||
private:
|
||||
OBSWeakSource _transition;
|
||||
TransitionSelectionType _type = TransitionSelectionType::TRANSITION;
|
||||
Type _type = Type::TRANSITION;
|
||||
friend class TransitionSelectionWidget;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "utility.hpp"
|
||||
#include "platform-funcs.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "regex-config.hpp"
|
||||
|
||||
#include <QTextStream>
|
||||
#include <QLabel>
|
||||
@@ -301,41 +302,10 @@ void setSourceSettings(obs_source_t *s, const std::string &settings)
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
struct ItemInfo {
|
||||
std::string name;
|
||||
std::vector<obs_sceneitem_t *> items = {};
|
||||
};
|
||||
|
||||
static bool getSceneItems(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
{
|
||||
ItemInfo *moveInfo = reinterpret_cast<ItemInfo *>(ptr);
|
||||
auto sourceName = obs_source_get_name(obs_sceneitem_get_source(item));
|
||||
if (moveInfo->name == sourceName) {
|
||||
obs_sceneitem_addref(item);
|
||||
moveInfo->items.push_back(item);
|
||||
}
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, getSceneItems, ptr);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<obs_scene_item *> getSceneItemsWithName(obs_scene_t *scene,
|
||||
std::string &name)
|
||||
{
|
||||
ItemInfo itemInfo = {name};
|
||||
obs_scene_enum_items(scene, getSceneItems, &itemInfo);
|
||||
return itemInfo.items;
|
||||
}
|
||||
|
||||
// Match json1 with pattern json2
|
||||
bool matchJson(const std::string &json1, const std::string &json2,
|
||||
bool useRegex)
|
||||
const RegexConfig ®ex)
|
||||
{
|
||||
bool ret = false;
|
||||
auto j1 = formatJsonString(json1).toStdString();
|
||||
auto j2 = formatJsonString(json2).toStdString();
|
||||
|
||||
@@ -346,24 +316,23 @@ bool matchJson(const std::string &json1, const std::string &json2,
|
||||
j2 = json2;
|
||||
}
|
||||
|
||||
if (useRegex) {
|
||||
try {
|
||||
std::regex expr(j2);
|
||||
ret = std::regex_match(
|
||||
j1, expr, std::regex_constants::match_default);
|
||||
} catch (const std::regex_error &) {
|
||||
if (regex.Enabled()) {
|
||||
auto expr = regex.GetRegularExpression(j2);
|
||||
if (!expr.isValid()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
ret = j1 == j2;
|
||||
auto match = expr.match(QString::fromStdString(j1));
|
||||
return match.hasMatch();
|
||||
}
|
||||
return ret;
|
||||
return j1 == j2;
|
||||
}
|
||||
|
||||
bool compareSourceSettings(const OBSWeakSource &source,
|
||||
const std::string &settings, bool useRegex)
|
||||
const std::string &settings,
|
||||
const RegexConfig ®ex)
|
||||
{
|
||||
std::string currentSettings = getSourceSettings(source);
|
||||
return matchJson(currentSettings, settings, useRegex);
|
||||
return matchJson(currentSettings, settings, regex);
|
||||
}
|
||||
|
||||
std::string getDataFilePath(const std::string &file)
|
||||
@@ -652,7 +621,8 @@ void populateMediaSelection(QComboBox *sel, bool addSelect)
|
||||
reinterpret_cast<std::vector<std::string> *>(data);
|
||||
std::string sourceId = obs_source_get_id(source);
|
||||
if (sourceId.compare("ffmpeg_source") == 0 ||
|
||||
sourceId.compare("vlc_source") == 0) {
|
||||
sourceId.compare("vlc_source") == 0 ||
|
||||
sourceId.compare("slideshow") == 0) {
|
||||
list->push_back(obs_source_get_name(source));
|
||||
}
|
||||
return true;
|
||||
@@ -793,68 +763,6 @@ void populateFilterSelection(QComboBox *list, OBSWeakSource weakSource)
|
||||
list->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
static bool enumSceneItem(obs_scene_t *, obs_sceneitem_t *item, void *ptr)
|
||||
{
|
||||
std::set<QString> *names = reinterpret_cast<std::set<QString> *>(ptr);
|
||||
|
||||
if (obs_sceneitem_is_group(item)) {
|
||||
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
|
||||
obs_scene_enum_items(scene, enumSceneItem, ptr);
|
||||
}
|
||||
auto name = obs_source_get_name(obs_sceneitem_get_source(item));
|
||||
names->emplace(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
void populateSceneItemSelection(QComboBox *list, OBSWeakSource sceneWeakSource)
|
||||
{
|
||||
std::set<QString> names;
|
||||
auto s = obs_weak_source_get_source(sceneWeakSource);
|
||||
auto scene = obs_scene_from_source(s);
|
||||
obs_scene_enum_items(scene, enumSceneItem, &names);
|
||||
obs_source_release(s);
|
||||
|
||||
for (auto &name : names) {
|
||||
list->addItem(name);
|
||||
}
|
||||
list->model()->sort(0);
|
||||
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
|
||||
list->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void populateSceneItemSelection(QComboBox *list, SceneSelection &s)
|
||||
{
|
||||
std::set<QString> names;
|
||||
|
||||
if (s.GetType() == SceneSelectionType::CURRENT ||
|
||||
s.GetType() == SceneSelectionType::PREVIOUS) {
|
||||
auto enumScenes = [](void *param, obs_source_t *source) {
|
||||
if (!source) {
|
||||
return true;
|
||||
}
|
||||
std::set<QString> *names =
|
||||
reinterpret_cast<std::set<QString> *>(param);
|
||||
auto scene = obs_scene_from_source(source);
|
||||
obs_scene_enum_items(scene, enumSceneItem, names);
|
||||
return true;
|
||||
};
|
||||
|
||||
obs_enum_scenes(enumScenes, &names);
|
||||
} else {
|
||||
auto source = obs_weak_source_get_source(s.GetScene(false));
|
||||
auto scene = obs_scene_from_source(source);
|
||||
obs_scene_enum_items(scene, enumSceneItem, &names);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
for (auto &name : names) {
|
||||
list->addItem(name);
|
||||
}
|
||||
list->model()->sort(0);
|
||||
addSelectionEntry(list, obs_module_text("AdvSceneSwitcher.selectItem"));
|
||||
list->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void populateSourceGroupSelection(QComboBox *list)
|
||||
{
|
||||
std::set<QString> sourceTypeNames;
|
||||
@@ -1020,3 +928,41 @@ bool doubleEquals(double left, double right, double epsilon)
|
||||
{
|
||||
return (fabs(left - right) < epsilon);
|
||||
}
|
||||
|
||||
void setButtonIcon(QPushButton *button, const char *path)
|
||||
{
|
||||
QIcon icon;
|
||||
icon.addFile(QString::fromUtf8(path), QSize(), QIcon::Normal,
|
||||
QIcon::Off);
|
||||
button->setIcon(icon);
|
||||
}
|
||||
|
||||
void addSelectionGroup(QComboBox *selection, const QStringList &group,
|
||||
bool addSeparator)
|
||||
{
|
||||
selection->addItems(group);
|
||||
if (addSeparator) {
|
||||
selection->insertSeparator(selection->count());
|
||||
}
|
||||
}
|
||||
|
||||
int findIdxInRagne(QComboBox *list, int start, int stop,
|
||||
const std::string &value)
|
||||
{
|
||||
if (value.empty()) {
|
||||
return 0;
|
||||
}
|
||||
auto model = list->model();
|
||||
auto startIdx = model->index(start, 0);
|
||||
auto match = model->match(startIdx, Qt::DisplayRole,
|
||||
QString::fromStdString(value), 1,
|
||||
Qt::MatchExactly | Qt::MatchCaseSensitive);
|
||||
if (match.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int foundIdx = match.first().row();
|
||||
if (foundIdx > stop) {
|
||||
return 0;
|
||||
}
|
||||
return foundIdx;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "scene-group.hpp"
|
||||
|
||||
class SceneSelection;
|
||||
class RegexConfig;
|
||||
|
||||
bool WeakSourceValid(obs_weak_source_t *ws);
|
||||
std::string GetWeakSourceName(obs_weak_source_t *weak_source);
|
||||
@@ -26,12 +27,13 @@ bool compareIgnoringLineEnding(QString &s1, QString &s2);
|
||||
std::string getSourceSettings(OBSWeakSource ws);
|
||||
void setSourceSettings(obs_source_t *s, const std::string &settings);
|
||||
bool compareSourceSettings(const OBSWeakSource &source,
|
||||
const std::string &settings, bool regex);
|
||||
const std::string &settings,
|
||||
const RegexConfig ®ex);
|
||||
std::vector<obs_scene_item *> getSceneItemsWithName(obs_scene_t *scene,
|
||||
std::string &name);
|
||||
std::string getDataFilePath(const std::string &file);
|
||||
bool matchJson(const std::string &json1, const std::string &json2,
|
||||
bool useRegex);
|
||||
const RegexConfig ®ex);
|
||||
QString formatJsonString(std::string);
|
||||
QString formatJsonString(QString);
|
||||
QString escapeForRegex(QString &s);
|
||||
@@ -77,10 +79,12 @@ void populateSceneSelection(QComboBox *sel, bool addPrevious = false,
|
||||
void populateSourcesWithFilterSelection(QComboBox *list);
|
||||
void populateFilterSelection(QComboBox *list,
|
||||
OBSWeakSource weakSource = nullptr);
|
||||
void populateSceneItemSelection(QComboBox *list,
|
||||
OBSWeakSource sceneWeakSource = nullptr);
|
||||
void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
|
||||
void populateSourceGroupSelection(QComboBox *list);
|
||||
void populateProfileSelection(QComboBox *list);
|
||||
bool windowPosValid(QPoint pos);
|
||||
bool doubleEquals(double left, double right, double epsilon);
|
||||
void setButtonIcon(QPushButton *button, const char *path);
|
||||
void addSelectionGroup(QComboBox *selection, const QStringList &group,
|
||||
bool addSeparator = true);
|
||||
int findIdxInRagne(QComboBox *list, int start, int stop,
|
||||
const std::string &value);
|
||||
|
||||
235
src/utils/variable.cpp
Normal file
235
src/utils/variable.cpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#include "variable.hpp"
|
||||
|
||||
#include <switcher-data-structs.hpp>
|
||||
|
||||
Variable::Variable() : Item() {}
|
||||
|
||||
void Variable::Load(obs_data_t *obj)
|
||||
{
|
||||
Item::Load(obj);
|
||||
_saveAction =
|
||||
static_cast<SaveAction>(obs_data_get_int(obj, "saveAction"));
|
||||
_defaultValue = obs_data_get_string(obj, "defaultValue");
|
||||
if (_saveAction == SaveAction::SAVE) {
|
||||
_value = obs_data_get_string(obj, "value");
|
||||
} else if (_saveAction == SaveAction::SET_DEFAULT) {
|
||||
_value = _defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
void Variable::Save(obs_data_t *obj) const
|
||||
{
|
||||
Item::Save(obj);
|
||||
obs_data_set_int(obj, "saveAction", static_cast<int>(_saveAction));
|
||||
if (_saveAction == SaveAction::SAVE) {
|
||||
obs_data_set_string(obj, "value", _value.c_str());
|
||||
}
|
||||
obs_data_set_string(obj, "defaultValue", _defaultValue.c_str());
|
||||
}
|
||||
|
||||
bool Variable::DoubleValue(double &value) const
|
||||
{
|
||||
char *end = nullptr;
|
||||
value = strtod(_value.c_str(), &end);
|
||||
return end != _value.c_str() && *end == '\0' && value != HUGE_VAL;
|
||||
}
|
||||
|
||||
void Variable::SetValue(double value)
|
||||
{
|
||||
_value = std::to_string(value);
|
||||
}
|
||||
|
||||
Variable *GetVariableByName(const std::string &name)
|
||||
{
|
||||
for (const auto &v : switcher->variables) {
|
||||
if (v->Name() == name) {
|
||||
return dynamic_cast<Variable *>(v.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Variable *GetVariableByQString(const QString &name)
|
||||
{
|
||||
return GetVariableByName(name.toStdString());
|
||||
}
|
||||
|
||||
std::weak_ptr<Variable> GetWeakVariableByName(const std::string &name)
|
||||
{
|
||||
for (const auto &v : switcher->variables) {
|
||||
if (v->Name() == name) {
|
||||
std::weak_ptr<Variable> wp =
|
||||
std::dynamic_pointer_cast<Variable>(v);
|
||||
return wp;
|
||||
}
|
||||
}
|
||||
return std::weak_ptr<Variable>();
|
||||
}
|
||||
|
||||
std::weak_ptr<Variable> GetWeakVariableByQString(const QString &name)
|
||||
{
|
||||
return GetWeakVariableByName(name.toStdString());
|
||||
}
|
||||
|
||||
QStringList GetVariablesNameList()
|
||||
{
|
||||
QStringList list;
|
||||
for (const auto &var : switcher->variables) {
|
||||
list << QString::fromStdString(var->Name());
|
||||
}
|
||||
list.sort();
|
||||
return list;
|
||||
}
|
||||
|
||||
void SwitcherData::saveVariables(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *variablesArray = obs_data_array_create();
|
||||
for (const auto &v : variables) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
v->Save(array_obj);
|
||||
obs_data_array_push_back(variablesArray, array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, "variables", variablesArray);
|
||||
obs_data_array_release(variablesArray);
|
||||
}
|
||||
|
||||
void SwitcherData::loadVariables(obs_data_t *obj)
|
||||
{
|
||||
variables.clear();
|
||||
|
||||
obs_data_array_t *variablesArray = obs_data_get_array(obj, "variables");
|
||||
size_t count = obs_data_array_count(variablesArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(variablesArray, i);
|
||||
auto var = Variable::Create();
|
||||
variables.emplace_back(var);
|
||||
variables.back()->Load(array_obj);
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(variablesArray);
|
||||
}
|
||||
|
||||
static void populateSaveActionSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.variable.save.dontSave"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.variable.save.save"));
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.variable.save.default"));
|
||||
}
|
||||
|
||||
VariableSettingsDialog::VariableSettingsDialog(QWidget *parent,
|
||||
const Variable &settings)
|
||||
: ItemSettingsDialog(settings, switcher->variables,
|
||||
"AdvSceneSwitcher.variable.select",
|
||||
"AdvSceneSwitcher.variable.add", parent),
|
||||
_value(new QLineEdit()),
|
||||
_defaultValue(new QLineEdit()),
|
||||
_save(new QComboBox())
|
||||
{
|
||||
QWidget::connect(_save, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(SaveActionChanged(int)));
|
||||
|
||||
_value->setText(QString::fromStdString(settings._value));
|
||||
_defaultValue->setText(QString::fromStdString(settings._defaultValue));
|
||||
populateSaveActionSelection(_save);
|
||||
_save->setCurrentIndex(static_cast<int>(settings._saveAction));
|
||||
|
||||
QGridLayout *layout = new QGridLayout;
|
||||
int row = 0;
|
||||
layout->addWidget(
|
||||
new QLabel(obs_module_text("AdvSceneSwitcher.variable.name")),
|
||||
row, 0);
|
||||
QHBoxLayout *nameLayout = new QHBoxLayout;
|
||||
nameLayout->addWidget(_name);
|
||||
nameLayout->addWidget(_nameHint);
|
||||
layout->addLayout(nameLayout, row, 1);
|
||||
++row;
|
||||
layout->addWidget(
|
||||
new QLabel(obs_module_text("AdvSceneSwitcher.variable.value")),
|
||||
row, 0);
|
||||
layout->addWidget(_value, row, 1);
|
||||
++row;
|
||||
layout->addWidget(
|
||||
new QLabel(obs_module_text("AdvSceneSwitcher.variable.save")),
|
||||
row, 0);
|
||||
auto saveLayout = new QVBoxLayout;
|
||||
saveLayout->addWidget(_save);
|
||||
saveLayout->addWidget(_defaultValue);
|
||||
saveLayout->addStretch();
|
||||
layout->addLayout(saveLayout, row, 1);
|
||||
++row;
|
||||
layout->addWidget(_buttonbox, row, 0, 1, -1);
|
||||
layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void VariableSettingsDialog::SaveActionChanged(int idx)
|
||||
{
|
||||
const Variable::SaveAction action =
|
||||
static_cast<Variable::SaveAction>(idx);
|
||||
_defaultValue->setVisible(action == Variable::SaveAction::SET_DEFAULT);
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
bool VariableSettingsDialog::AskForSettings(QWidget *parent, Variable &settings)
|
||||
{
|
||||
VariableSettingsDialog dialog(parent, settings);
|
||||
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
if (dialog.exec() != DialogCode::Accepted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
settings._name = dialog._name->text().toStdString();
|
||||
settings._value = dialog._value->text().toStdString();
|
||||
settings._defaultValue = dialog._defaultValue->text().toStdString();
|
||||
settings._saveAction =
|
||||
static_cast<Variable::SaveAction>(dialog._save->currentIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool AskForSettingsWrapper(QWidget *parent, Item &settings)
|
||||
{
|
||||
Variable &VariableSettings = dynamic_cast<Variable &>(settings);
|
||||
return VariableSettingsDialog::AskForSettings(parent, VariableSettings);
|
||||
}
|
||||
|
||||
VariableSelection::VariableSelection(QWidget *parent)
|
||||
: ItemSelection(switcher->variables, Variable::Create,
|
||||
AskForSettingsWrapper,
|
||||
"AdvSceneSwitcher.variable.select",
|
||||
"AdvSceneSwitcher.variable.add", parent)
|
||||
{
|
||||
// Connect to slots
|
||||
QWidget::connect(
|
||||
window(),
|
||||
SIGNAL(VariableRenamed(const QString &, const QString &)), this,
|
||||
SLOT(RenameItem(const QString &, const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(VariableAdded(const QString &)), this,
|
||||
SLOT(AddItem(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(VariableRemoved(const QString &)),
|
||||
this, SLOT(RemoveItem(const QString &)));
|
||||
|
||||
// Forward signals
|
||||
QWidget::connect(
|
||||
this, SIGNAL(ItemRenamed(const QString &, const QString &)),
|
||||
window(),
|
||||
SIGNAL(VariableRenamed(const QString &, const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ItemAdded(const QString &)), window(),
|
||||
SIGNAL(VariableAdded(const QString &)));
|
||||
QWidget::connect(this, SIGNAL(ItemRemoved(const QString &)), window(),
|
||||
SIGNAL(VariableRemoved(const QString &)));
|
||||
}
|
||||
|
||||
void VariableSelection::SetVariable(const std::string &variable)
|
||||
{
|
||||
const QSignalBlocker blocker(_selection);
|
||||
if (!!GetVariableByName(variable)) {
|
||||
_selection->setCurrentText(QString::fromStdString(variable));
|
||||
} else {
|
||||
_selection->setCurrentIndex(0);
|
||||
}
|
||||
}
|
||||
68
src/utils/variable.hpp
Normal file
68
src/utils/variable.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include "item-selection-helpers.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <QStringList>
|
||||
#include <obs.hpp>
|
||||
|
||||
class VariableSelection;
|
||||
class VariableSettingsDialog;
|
||||
|
||||
class Variable : public Item {
|
||||
public:
|
||||
Variable();
|
||||
void Load(obs_data_t *obj);
|
||||
void Save(obs_data_t *obj) const;
|
||||
std::string Value() const { return _value; }
|
||||
bool DoubleValue(double &) const;
|
||||
void SetValue(const std::string &val) { _value = val; }
|
||||
void SetValue(double);
|
||||
static std::shared_ptr<Item> Create()
|
||||
{
|
||||
return std::make_shared<Variable>();
|
||||
}
|
||||
|
||||
enum class SaveAction {
|
||||
DONT_SAVE,
|
||||
SAVE,
|
||||
SET_DEFAULT,
|
||||
};
|
||||
|
||||
private:
|
||||
SaveAction _saveAction = SaveAction::DONT_SAVE;
|
||||
std::string _value = "";
|
||||
std::string _defaultValue = "";
|
||||
|
||||
friend VariableSelection;
|
||||
friend VariableSettingsDialog;
|
||||
};
|
||||
|
||||
Variable *GetVariableByName(const std::string &name);
|
||||
Variable *GetVariableByQString(const QString &name);
|
||||
std::weak_ptr<Variable> GetWeakVariableByName(const std::string &name);
|
||||
std::weak_ptr<Variable> GetWeakVariableByQString(const QString &name);
|
||||
QStringList GetVariablesNameList();
|
||||
|
||||
class VariableSettingsDialog : public ItemSettingsDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VariableSettingsDialog(QWidget *parent, const Variable &);
|
||||
static bool AskForSettings(QWidget *parent, Variable &settings);
|
||||
|
||||
private slots:
|
||||
void SaveActionChanged(int);
|
||||
|
||||
private:
|
||||
QLineEdit *_value;
|
||||
QLineEdit *_defaultValue;
|
||||
QComboBox *_save;
|
||||
};
|
||||
|
||||
class VariableSelection : public ItemSelection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VariableSelection(QWidget *parent = 0);
|
||||
void SetVariable(const std::string &);
|
||||
};
|
||||
@@ -5,23 +5,24 @@
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QStyle>
|
||||
#include <QStyleFactory>
|
||||
#include <QSlider>
|
||||
|
||||
#include <util/platform.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x)))
|
||||
#define FADER_PRECISION 4096.0
|
||||
#define FADER_PRECISION 100
|
||||
|
||||
// Size of the audio indicator in pixels
|
||||
#define INDICATOR_THICKNESS 3
|
||||
|
||||
// Padding on top and bottom of vertical meters
|
||||
#define METER_PADDING 1
|
||||
|
||||
QWeakPointer<VolumeMeterTimer> VolumeMeter::updateTimer;
|
||||
|
||||
void VolControl::OBSVolumeChanged(void *data, float db)
|
||||
{
|
||||
Q_UNUSED(db);
|
||||
Q_UNUSED(data);
|
||||
}
|
||||
void VolControl::OBSVolumeChanged(void *, float) {}
|
||||
|
||||
void VolControl::OBSVolumeLevel(void *data,
|
||||
const float magnitude[MAX_AUDIO_CHANNELS],
|
||||
@@ -33,9 +34,19 @@ void VolControl::OBSVolumeLevel(void *data,
|
||||
volControl->volMeter->setLevels(magnitude, peak, inputPeak);
|
||||
}
|
||||
|
||||
void VolControl::SliderChanged(int vol)
|
||||
void VolControl::VolumeChanged()
|
||||
{
|
||||
Q_UNUSED(vol);
|
||||
slider->blockSignals(true);
|
||||
slider->setValue(
|
||||
(int)(obs_fader_get_deflection(obs_fader) * FADER_PRECISION));
|
||||
slider->blockSignals(false);
|
||||
|
||||
updateText();
|
||||
}
|
||||
|
||||
void VolControl::SliderChanged(int)
|
||||
{
|
||||
float prev = obs_source_get_volume(source);
|
||||
updateText();
|
||||
}
|
||||
|
||||
@@ -61,6 +72,12 @@ void VolControl::SetName(const QString &newName)
|
||||
{
|
||||
nameLabel->setText(newName);
|
||||
}
|
||||
|
||||
void VolControl::EmitConfigClicked()
|
||||
{
|
||||
emit ConfigClicked();
|
||||
}
|
||||
|
||||
void VolControl::SetMeterDecayRate(qreal q)
|
||||
{
|
||||
volMeter->setPeakDecayRate(q);
|
||||
@@ -71,23 +88,46 @@ void VolControl::setPeakMeterType(enum obs_peak_meter_type peakMeterType)
|
||||
volMeter->setPeakMeterType(peakMeterType);
|
||||
}
|
||||
|
||||
VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
VolumeSlider::VolumeSlider(obs_fader_t *fader, QWidget *parent)
|
||||
: QSlider(parent)
|
||||
{
|
||||
fad = fader;
|
||||
}
|
||||
|
||||
VolumeSlider::VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation,
|
||||
QWidget *parent)
|
||||
: QSlider(orientation, parent)
|
||||
{
|
||||
fad = fader;
|
||||
}
|
||||
|
||||
VolControl::VolControl(OBSSource source_, bool showConfig, bool vertical)
|
||||
: source(std::move(source_)),
|
||||
levelTotal(0.0f),
|
||||
levelCount(0.0f),
|
||||
obs_fader(obs_fader_create(OBS_FADER_LOG)),
|
||||
obs_volmeter(obs_volmeter_create(OBS_FADER_LOG)),
|
||||
vertical(vertical)
|
||||
vertical(vertical),
|
||||
contextMenu(nullptr)
|
||||
{
|
||||
nameLabel = new QLabel();
|
||||
volLabel = new QLabel();
|
||||
|
||||
QString sourceName = "";
|
||||
if (source) {
|
||||
obs_source_get_name(source);
|
||||
}
|
||||
QString sourceName = obs_source_get_name(source);
|
||||
setObjectName(sourceName);
|
||||
|
||||
if (showConfig) {
|
||||
config = new QPushButton(this);
|
||||
config->setProperty("themeID", "menuIconSmall");
|
||||
config->setSizePolicy(QSizePolicy::Maximum,
|
||||
QSizePolicy::Maximum);
|
||||
config->setMaximumSize(22, 22);
|
||||
config->setAutoDefault(false);
|
||||
|
||||
connect(config, &QAbstractButton::clicked, this,
|
||||
&VolControl::EmitConfigClicked);
|
||||
}
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->setContentsMargins(4, 4, 4, 4);
|
||||
mainLayout->setSpacing(2);
|
||||
@@ -99,7 +139,7 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
QHBoxLayout *meterLayout = new QHBoxLayout;
|
||||
|
||||
volMeter = new VolumeMeter(nullptr, obs_volmeter, true);
|
||||
slider = new QSlider(Qt::Vertical);
|
||||
slider = new VolumeSlider(obs_fader, Qt::Vertical);
|
||||
|
||||
nameLayout->setAlignment(Qt::AlignCenter);
|
||||
meterLayout->setAlignment(Qt::AlignCenter);
|
||||
@@ -113,7 +153,11 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
controlLayout->setContentsMargins(0, 0, 0, 0);
|
||||
controlLayout->setSpacing(0);
|
||||
|
||||
if (showConfig)
|
||||
controlLayout->addWidget(config);
|
||||
|
||||
controlLayout->addItem(new QSpacerItem(3, 0));
|
||||
// Add Headphone (audio monitoring) widget here
|
||||
|
||||
meterLayout->setContentsMargins(0, 0, 0, 0);
|
||||
meterLayout->setSpacing(0);
|
||||
@@ -131,6 +175,12 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
|
||||
volMeter->setFocusProxy(slider);
|
||||
|
||||
// Default size can cause clipping of long names in vertical layout.
|
||||
QFont font = nameLabel->font();
|
||||
QFontInfo info(font);
|
||||
font.setPointSizeF(0.8 * info.pointSizeF());
|
||||
nameLabel->setFont(font);
|
||||
|
||||
setMaximumWidth(110);
|
||||
} else {
|
||||
QHBoxLayout *volLayout = new QHBoxLayout;
|
||||
@@ -138,7 +188,7 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
QHBoxLayout *botLayout = new QHBoxLayout;
|
||||
|
||||
volMeter = new VolumeMeter(nullptr, obs_volmeter, false);
|
||||
slider = new QSlider(Qt::Horizontal);
|
||||
slider = new VolumeSlider(obs_fader, Qt::Horizontal);
|
||||
|
||||
textLayout->setContentsMargins(0, 0, 0, 0);
|
||||
textLayout->addWidget(nameLabel);
|
||||
@@ -153,6 +203,9 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
botLayout->setSpacing(0);
|
||||
botLayout->addLayout(volLayout);
|
||||
|
||||
if (showConfig)
|
||||
botLayout->addWidget(config);
|
||||
|
||||
mainLayout->addItem(textLayout);
|
||||
mainLayout->addWidget(volMeter);
|
||||
mainLayout->addItem(botLayout);
|
||||
@@ -162,15 +215,10 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
|
||||
setLayout(mainLayout);
|
||||
|
||||
QFont font = nameLabel->font();
|
||||
font.setPointSize(font.pointSize() - 1);
|
||||
|
||||
nameLabel->setText(sourceName);
|
||||
nameLabel->setFont(font);
|
||||
volLabel->setFont(font);
|
||||
|
||||
slider->setMinimum(0);
|
||||
slider->setMaximum(100);
|
||||
slider->setMaximum(int(FADER_PRECISION));
|
||||
|
||||
obs_fader_add_callback(obs_fader, OBSVolumeChanged, this);
|
||||
obs_volmeter_add_callback(obs_volmeter, OBSVolumeLevel, this);
|
||||
@@ -180,6 +228,9 @@ VolControl::VolControl(OBSSource source_, bool vertical)
|
||||
|
||||
obs_fader_attach_source(obs_fader, source);
|
||||
obs_volmeter_attach_source(obs_volmeter, source);
|
||||
|
||||
/* Call volume changed once to init the slider position and label */
|
||||
VolumeChanged();
|
||||
}
|
||||
|
||||
void VolControl::EnableSlider(bool enable)
|
||||
@@ -187,11 +238,6 @@ void VolControl::EnableSlider(bool enable)
|
||||
slider->setEnabled(enable);
|
||||
}
|
||||
|
||||
QSlider *VolControl::GetSlider() const
|
||||
{
|
||||
return slider;
|
||||
}
|
||||
|
||||
VolControl::~VolControl()
|
||||
{
|
||||
obs_fader_remove_callback(obs_fader, OBSVolumeChanged, this);
|
||||
@@ -199,66 +245,143 @@ VolControl::~VolControl()
|
||||
|
||||
obs_fader_destroy(obs_fader);
|
||||
obs_volmeter_destroy(obs_volmeter);
|
||||
if (contextMenu)
|
||||
contextMenu->close();
|
||||
}
|
||||
|
||||
static inline QColor color_from_int(long long val)
|
||||
{
|
||||
QColor color(val & 0xff, (val >> 8) & 0xff, (val >> 16) & 0xff,
|
||||
(val >> 24) & 0xff);
|
||||
color.setAlpha(255);
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getBackgroundNominalColor() const
|
||||
{
|
||||
return backgroundNominalColor;
|
||||
return p_backgroundNominalColor;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getBackgroundNominalColorDisabled() const
|
||||
{
|
||||
return backgroundNominalColorDisabled;
|
||||
}
|
||||
|
||||
void VolumeMeter::setBackgroundNominalColor(QColor c)
|
||||
{
|
||||
backgroundNominalColor = std::move(c);
|
||||
p_backgroundNominalColor = std::move(c);
|
||||
backgroundNominalColor = p_backgroundNominalColor;
|
||||
}
|
||||
|
||||
void VolumeMeter::setBackgroundNominalColorDisabled(QColor c)
|
||||
{
|
||||
backgroundNominalColorDisabled = std::move(c);
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getBackgroundWarningColor() const
|
||||
{
|
||||
return backgroundWarningColor;
|
||||
return p_backgroundWarningColor;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getBackgroundWarningColorDisabled() const
|
||||
{
|
||||
return backgroundWarningColorDisabled;
|
||||
}
|
||||
|
||||
void VolumeMeter::setBackgroundWarningColor(QColor c)
|
||||
{
|
||||
backgroundWarningColor = std::move(c);
|
||||
p_backgroundWarningColor = std::move(c);
|
||||
backgroundWarningColor = p_backgroundWarningColor;
|
||||
}
|
||||
|
||||
void VolumeMeter::setBackgroundWarningColorDisabled(QColor c)
|
||||
{
|
||||
backgroundWarningColorDisabled = std::move(c);
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getBackgroundErrorColor() const
|
||||
{
|
||||
return backgroundErrorColor;
|
||||
return p_backgroundErrorColor;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getBackgroundErrorColorDisabled() const
|
||||
{
|
||||
return backgroundErrorColorDisabled;
|
||||
}
|
||||
|
||||
void VolumeMeter::setBackgroundErrorColor(QColor c)
|
||||
{
|
||||
backgroundErrorColor = std::move(c);
|
||||
p_backgroundErrorColor = std::move(c);
|
||||
backgroundErrorColor = p_backgroundErrorColor;
|
||||
}
|
||||
|
||||
void VolumeMeter::setBackgroundErrorColorDisabled(QColor c)
|
||||
{
|
||||
backgroundErrorColorDisabled = std::move(c);
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getForegroundNominalColor() const
|
||||
{
|
||||
return foregroundNominalColor;
|
||||
return p_foregroundNominalColor;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getForegroundNominalColorDisabled() const
|
||||
{
|
||||
return foregroundNominalColorDisabled;
|
||||
}
|
||||
|
||||
void VolumeMeter::setForegroundNominalColor(QColor c)
|
||||
{
|
||||
foregroundNominalColor = std::move(c);
|
||||
p_foregroundNominalColor = std::move(c);
|
||||
foregroundNominalColor = p_foregroundNominalColor;
|
||||
}
|
||||
|
||||
void VolumeMeter::setForegroundNominalColorDisabled(QColor c)
|
||||
{
|
||||
foregroundNominalColorDisabled = std::move(c);
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getForegroundWarningColor() const
|
||||
{
|
||||
return foregroundWarningColor;
|
||||
return p_foregroundWarningColor;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getForegroundWarningColorDisabled() const
|
||||
{
|
||||
return foregroundWarningColorDisabled;
|
||||
}
|
||||
|
||||
void VolumeMeter::setForegroundWarningColor(QColor c)
|
||||
{
|
||||
foregroundWarningColor = std::move(c);
|
||||
p_foregroundWarningColor = std::move(c);
|
||||
foregroundWarningColor = p_foregroundWarningColor;
|
||||
}
|
||||
|
||||
void VolumeMeter::setForegroundWarningColorDisabled(QColor c)
|
||||
{
|
||||
foregroundWarningColorDisabled = std::move(c);
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getForegroundErrorColor() const
|
||||
{
|
||||
return foregroundErrorColor;
|
||||
return p_foregroundErrorColor;
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getForegroundErrorColorDisabled() const
|
||||
{
|
||||
return foregroundErrorColorDisabled;
|
||||
}
|
||||
|
||||
void VolumeMeter::setForegroundErrorColor(QColor c)
|
||||
{
|
||||
foregroundErrorColor = std::move(c);
|
||||
p_foregroundErrorColor = std::move(c);
|
||||
foregroundErrorColor = p_foregroundErrorColor;
|
||||
}
|
||||
|
||||
void VolumeMeter::setForegroundErrorColorDisabled(QColor c)
|
||||
{
|
||||
foregroundErrorColorDisabled = std::move(c);
|
||||
}
|
||||
|
||||
QColor VolumeMeter::getClipColor() const
|
||||
@@ -301,6 +424,42 @@ void VolumeMeter::setMinorTickColor(QColor c)
|
||||
minorTickColor = std::move(c);
|
||||
}
|
||||
|
||||
int VolumeMeter::getMeterThickness() const
|
||||
{
|
||||
return meterThickness;
|
||||
}
|
||||
|
||||
void VolumeMeter::setMeterThickness(int v)
|
||||
{
|
||||
meterThickness = v;
|
||||
recalculateLayout = true;
|
||||
}
|
||||
|
||||
qreal VolumeMeter::getMeterFontScaling() const
|
||||
{
|
||||
return meterFontScaling;
|
||||
}
|
||||
|
||||
void VolumeMeter::setMeterFontScaling(qreal v)
|
||||
{
|
||||
meterFontScaling = v;
|
||||
recalculateLayout = true;
|
||||
}
|
||||
|
||||
void VolControl::refreshColors()
|
||||
{
|
||||
volMeter->setBackgroundNominalColor(
|
||||
volMeter->getBackgroundNominalColor());
|
||||
volMeter->setBackgroundWarningColor(
|
||||
volMeter->getBackgroundWarningColor());
|
||||
volMeter->setBackgroundErrorColor(volMeter->getBackgroundErrorColor());
|
||||
volMeter->setForegroundNominalColor(
|
||||
volMeter->getForegroundNominalColor());
|
||||
volMeter->setForegroundWarningColor(
|
||||
volMeter->getForegroundWarningColor());
|
||||
volMeter->setForegroundErrorColor(volMeter->getForegroundErrorColor());
|
||||
}
|
||||
|
||||
qreal VolumeMeter::getMinimumLevel() const
|
||||
{
|
||||
return minimumLevel;
|
||||
@@ -434,10 +593,9 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
|
||||
bool vertical)
|
||||
: QWidget(parent), obs_volmeter(obs_volmeter), vertical(vertical)
|
||||
{
|
||||
// Use a font that can be rendered small.
|
||||
tickFont = QFont("Arial");
|
||||
tickFont.setPixelSize(7);
|
||||
// Default meter color settings, they only show if
|
||||
setAttribute(Qt::WA_OpaquePaintEvent, true);
|
||||
|
||||
// Default meter settings, they only show if
|
||||
// there is no stylesheet, do not remove.
|
||||
backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green
|
||||
backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow
|
||||
@@ -445,27 +603,38 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
|
||||
foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green
|
||||
foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow
|
||||
foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red
|
||||
clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
|
||||
magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
|
||||
majorTickColor.setRgb(0xff, 0xff, 0xff); // Black
|
||||
minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black
|
||||
minimumLevel = -60.0; // -60 dB
|
||||
warningLevel = -20.0; // -20 dB
|
||||
errorLevel = -9.0; // -9 dB
|
||||
clipLevel = -0.5; // -0.5 dB
|
||||
minimumInputLevel = -50.0; // -50 dB
|
||||
peakDecayRate = 11.76; // 20 dB / 1.7 sec
|
||||
magnitudeIntegrationTime = 0.3; // 99% in 300 ms
|
||||
peakHoldDuration = 20.0; // 20 seconds
|
||||
inputPeakHoldDuration = 1.0; // 1 second
|
||||
|
||||
backgroundNominalColorDisabled.setRgb(90, 90, 90);
|
||||
backgroundWarningColorDisabled.setRgb(117, 117, 117);
|
||||
backgroundErrorColorDisabled.setRgb(65, 65, 65);
|
||||
foregroundNominalColorDisabled.setRgb(163, 163, 163);
|
||||
foregroundWarningColorDisabled.setRgb(217, 217, 217);
|
||||
foregroundErrorColorDisabled.setRgb(113, 113, 113);
|
||||
|
||||
clipColor.setRgb(0xff, 0xff, 0xff); // Bright white
|
||||
magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black
|
||||
majorTickColor.setRgb(0xff, 0xff, 0xff); // Black
|
||||
minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black
|
||||
minimumLevel = -60.0; // -60 dB
|
||||
warningLevel = -20.0; // -20 dB
|
||||
errorLevel = -9.0; // -9 dB
|
||||
clipLevel = -0.5; // -0.5 dB
|
||||
minimumInputLevel = -50.0; // -50 dB
|
||||
peakDecayRate = 11.76; // 20 dB / 1.7 sec
|
||||
magnitudeIntegrationTime = 0.3; // 99% in 300 ms
|
||||
peakHoldDuration = 20.0; // 20 seconds
|
||||
inputPeakHoldDuration = 1.0; // 1 second
|
||||
meterThickness = 3; // Bar thickness in pixels
|
||||
meterFontScaling =
|
||||
0.7; // Font size for numbers is 70% of Widget's font size
|
||||
channels = (int)audio_output_get_channels(obs_get_audio());
|
||||
|
||||
handleChannelCofigurationChange();
|
||||
doLayout();
|
||||
updateTimerRef = updateTimer.toStrongRef();
|
||||
if (!updateTimerRef) {
|
||||
updateTimerRef = QSharedPointer<VolumeMeterTimer>::create();
|
||||
updateTimerRef->start(34);
|
||||
updateTimerRef->setTimerType(Qt::PreciseTimer);
|
||||
updateTimerRef->start(16);
|
||||
updateTimer = updateTimerRef;
|
||||
}
|
||||
|
||||
@@ -475,7 +644,6 @@ VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter,
|
||||
VolumeMeter::~VolumeMeter()
|
||||
{
|
||||
updateTimerRef->RemoveVolControl(this);
|
||||
delete tickPaintCache;
|
||||
}
|
||||
|
||||
void VolumeMeter::setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
|
||||
@@ -515,23 +683,58 @@ inline void VolumeMeter::resetLevels()
|
||||
}
|
||||
}
|
||||
|
||||
inline void VolumeMeter::handleChannelCofigurationChange()
|
||||
bool VolumeMeter::needLayoutChange()
|
||||
{
|
||||
int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
|
||||
|
||||
if (!currentNrAudioChannels) {
|
||||
struct obs_audio_info oai;
|
||||
obs_get_audio_info(&oai);
|
||||
currentNrAudioChannels = (oai.speakers == SPEAKERS_MONO) ? 1
|
||||
: 2;
|
||||
}
|
||||
|
||||
if (displayNrAudioChannels != currentNrAudioChannels) {
|
||||
displayNrAudioChannels = currentNrAudioChannels;
|
||||
recalculateLayout = true;
|
||||
}
|
||||
|
||||
return recalculateLayout;
|
||||
}
|
||||
|
||||
// When this is called from the constructor, obs_volmeter_get_nr_channels has not
|
||||
// yet been called and Q_PROPERTY settings have not yet been read from the
|
||||
// stylesheet.
|
||||
inline void VolumeMeter::doLayout()
|
||||
{
|
||||
QMutexLocker locker(&dataMutex);
|
||||
|
||||
int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter);
|
||||
if (displayNrAudioChannels != currentNrAudioChannels) {
|
||||
displayNrAudioChannels = currentNrAudioChannels;
|
||||
recalculateLayout = false;
|
||||
|
||||
// Make room for 3 pixels meter, with one pixel between each.
|
||||
// Then 9/13 pixels for ticks and numbers.
|
||||
if (vertical)
|
||||
setMinimumSize(displayNrAudioChannels * 4 + 14, 130);
|
||||
else
|
||||
setMinimumSize(130, displayNrAudioChannels * 4 + 8);
|
||||
|
||||
resetLevels();
|
||||
tickFont = font();
|
||||
QFontInfo info(tickFont);
|
||||
tickFont.setPointSizeF(info.pointSizeF() * meterFontScaling);
|
||||
QFontMetrics metrics(tickFont);
|
||||
if (vertical) {
|
||||
// Each meter channel is meterThickness pixels wide, plus one pixel
|
||||
// between channels, but not after the last.
|
||||
// Add 4 pixels for ticks, space to hold our longest label in this font,
|
||||
// and a few pixels before the fader.
|
||||
QRect scaleBounds = metrics.boundingRect("-88");
|
||||
setMinimumSize(displayNrAudioChannels * (meterThickness + 1) -
|
||||
1 + 4 + scaleBounds.width() + 2,
|
||||
130);
|
||||
} else {
|
||||
// Each meter channel is meterThickness pixels high, plus one pixel
|
||||
// between channels, but not after the last.
|
||||
// Add 4 pixels for ticks, and space high enough to hold our label in
|
||||
// this font, presuming that digits don't have descenders.
|
||||
setMinimumSize(130,
|
||||
displayNrAudioChannels * (meterThickness + 1) -
|
||||
1 + 4 + metrics.capHeight());
|
||||
}
|
||||
|
||||
resetLevels();
|
||||
}
|
||||
|
||||
inline bool VolumeMeter::detectIdle(uint64_t ts)
|
||||
@@ -649,12 +852,12 @@ void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, int width,
|
||||
painter.fillRect(x, y, width, height, color);
|
||||
}
|
||||
|
||||
void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width,
|
||||
int height)
|
||||
void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width)
|
||||
{
|
||||
qreal scale = width / minimumLevel;
|
||||
|
||||
painter.setFont(tickFont);
|
||||
QFontMetrics metrics(tickFont);
|
||||
painter.setPen(majorTickColor);
|
||||
|
||||
// Draw major tick lines and numeric indicators.
|
||||
@@ -662,10 +865,18 @@ void VolumeMeter::paintHTicks(QPainter &painter, int x, int y, int width,
|
||||
int position = int(x + width - (i * scale) - 1);
|
||||
QString str = QString::number(i);
|
||||
|
||||
if (i == 0 || i == -5)
|
||||
painter.drawText(position - 3, height, str);
|
||||
else
|
||||
painter.drawText(position - 5, height, str);
|
||||
// Center the number on the tick, but don't overflow
|
||||
QRect textBounds = metrics.boundingRect(str);
|
||||
int pos;
|
||||
if (i == 0) {
|
||||
pos = position - textBounds.width();
|
||||
} else {
|
||||
pos = position - (textBounds.width() / 2);
|
||||
if (pos < 0)
|
||||
pos = 0;
|
||||
}
|
||||
painter.drawText(pos, y + 4 + metrics.capHeight(), str);
|
||||
|
||||
painter.drawLine(position, y, position, y + 2);
|
||||
}
|
||||
|
||||
@@ -683,26 +894,31 @@ void VolumeMeter::paintVTicks(QPainter &painter, int x, int y, int height)
|
||||
qreal scale = height / minimumLevel;
|
||||
|
||||
painter.setFont(tickFont);
|
||||
QFontMetrics metrics(tickFont);
|
||||
painter.setPen(majorTickColor);
|
||||
|
||||
// Draw major tick lines and numeric indicators.
|
||||
for (int i = 0; i >= minimumLevel; i -= 5) {
|
||||
int position = y + int((i * scale) - 1);
|
||||
int position = y + int(i * scale) + METER_PADDING;
|
||||
QString str = QString::number(i);
|
||||
|
||||
if (i == 0)
|
||||
painter.drawText(x + 5, position + 4, str);
|
||||
else if (i == -60)
|
||||
painter.drawText(x + 4, position, str);
|
||||
else
|
||||
painter.drawText(x + 4, position + 2, str);
|
||||
// Center the number on the tick, but don't overflow
|
||||
if (i == 0) {
|
||||
painter.drawText(x + 6, position + metrics.capHeight(),
|
||||
str);
|
||||
} else {
|
||||
painter.drawText(x + 4,
|
||||
position + (metrics.capHeight() / 2),
|
||||
str);
|
||||
}
|
||||
|
||||
painter.drawLine(x, position, x + 2, position);
|
||||
}
|
||||
|
||||
// Draw minor tick lines.
|
||||
painter.setPen(minorTickColor);
|
||||
for (int i = 0; i >= minimumLevel; i--) {
|
||||
int position = y + int((i * scale) - 1);
|
||||
int position = y + int(i * scale) + METER_PADDING;
|
||||
if (i % 5 != 0)
|
||||
painter.drawLine(x, position, x + 1, position);
|
||||
}
|
||||
@@ -777,7 +993,7 @@ void VolumeMeter::paintHMeter(QPainter &painter, int x, int y, int width,
|
||||
painter.fillRect(peakPosition, y,
|
||||
maximumPosition - peakPosition, height,
|
||||
backgroundErrorColor);
|
||||
} else {
|
||||
} else if (int(magnitude) != 0) {
|
||||
if (!clipping) {
|
||||
QTimer::singleShot(CLIP_FLASH_DURATION_MS, this,
|
||||
SLOT(ClipEnding()));
|
||||
@@ -879,7 +1095,7 @@ void VolumeMeter::paintVMeter(QPainter &painter, int x, int y, int width,
|
||||
|
||||
int end = errorLength + warningLength + nominalLength;
|
||||
painter.fillRect(x, minimumPosition, width, end,
|
||||
QBrush(foregroundErrorColor));
|
||||
foregroundErrorColor);
|
||||
}
|
||||
|
||||
if (peakHoldPosition - 3 < minimumPosition)
|
||||
@@ -903,53 +1119,48 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
|
||||
{
|
||||
uint64_t ts = os_gettime_ns();
|
||||
qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001;
|
||||
|
||||
const QRect rect = event->region().boundingRect();
|
||||
int width = rect.width();
|
||||
int height = rect.height();
|
||||
|
||||
handleChannelCofigurationChange();
|
||||
calculateBallistics(ts, timeSinceLastRedraw);
|
||||
bool idle = detectIdle(ts);
|
||||
|
||||
// Draw the ticks in a off-screen buffer when the widget changes size.
|
||||
QSize tickPaintCacheSize;
|
||||
QRect widgetRect = rect();
|
||||
int width = widgetRect.width();
|
||||
int height = widgetRect.height();
|
||||
|
||||
QPainter painter(this);
|
||||
|
||||
if (vertical)
|
||||
tickPaintCacheSize = QSize(14, height);
|
||||
else
|
||||
tickPaintCacheSize = QSize(width, 9);
|
||||
if (tickPaintCache == nullptr ||
|
||||
tickPaintCache->size() != tickPaintCacheSize) {
|
||||
delete tickPaintCache;
|
||||
tickPaintCache = new QPixmap(tickPaintCacheSize);
|
||||
height -= METER_PADDING * 2;
|
||||
|
||||
QColor clearColor(0, 0, 0, 0);
|
||||
tickPaintCache->fill(clearColor);
|
||||
// timerEvent requests update of the bar(s) only, so we can avoid the
|
||||
// overhead of repainting the scale and labels.
|
||||
if (event->region().boundingRect() != getBarRect()) {
|
||||
if (needLayoutChange())
|
||||
doLayout();
|
||||
|
||||
// Paint window background color (as widget is opaque)
|
||||
QColor background =
|
||||
palette().color(QPalette::ColorRole::Window);
|
||||
painter.fillRect(widgetRect, background);
|
||||
|
||||
QPainter tickPainter(tickPaintCache);
|
||||
if (vertical) {
|
||||
tickPainter.translate(0, height);
|
||||
tickPainter.scale(1, -1);
|
||||
paintVTicks(tickPainter, 0, 11,
|
||||
tickPaintCacheSize.height() - 11);
|
||||
paintVTicks(painter,
|
||||
displayNrAudioChannels *
|
||||
(meterThickness + 1) -
|
||||
1,
|
||||
0, height - (INDICATOR_THICKNESS + 3));
|
||||
} else {
|
||||
paintHTicks(tickPainter, 6, 0,
|
||||
tickPaintCacheSize.width() - 6,
|
||||
tickPaintCacheSize.height());
|
||||
paintHTicks(painter, INDICATOR_THICKNESS + 3,
|
||||
displayNrAudioChannels *
|
||||
(meterThickness + 1) -
|
||||
1,
|
||||
width - (INDICATOR_THICKNESS + 3));
|
||||
}
|
||||
tickPainter.end();
|
||||
}
|
||||
|
||||
// Actual painting of the widget starts here.
|
||||
QPainter painter(this);
|
||||
if (vertical) {
|
||||
// Invert the Y axis to ease the math
|
||||
painter.translate(0, height);
|
||||
painter.translate(0, height + METER_PADDING);
|
||||
painter.scale(1, -1);
|
||||
painter.drawPixmap(displayNrAudioChannels * 4 - 1, 7,
|
||||
*tickPaintCache);
|
||||
} else {
|
||||
painter.drawPixmap(0, height - 9, *tickPaintCache);
|
||||
}
|
||||
|
||||
for (int channelNr = 0; channelNr < displayNrAudioChannels;
|
||||
@@ -961,12 +1172,17 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
|
||||
: channelNr;
|
||||
|
||||
if (vertical)
|
||||
paintVMeter(painter, channelNr * 4, 8, 3, height - 10,
|
||||
paintVMeter(painter, channelNr * (meterThickness + 1),
|
||||
INDICATOR_THICKNESS + 2, meterThickness,
|
||||
height - (INDICATOR_THICKNESS + 2),
|
||||
displayMagnitude[channelNrFixed],
|
||||
displayPeak[channelNrFixed],
|
||||
displayPeakHold[channelNrFixed]);
|
||||
else
|
||||
paintHMeter(painter, 5, channelNr * 4, width - 5, 3,
|
||||
paintHMeter(painter, INDICATOR_THICKNESS + 2,
|
||||
channelNr * (meterThickness + 1),
|
||||
width - (INDICATOR_THICKNESS + 2),
|
||||
meterThickness,
|
||||
displayMagnitude[channelNrFixed],
|
||||
displayPeak[channelNrFixed],
|
||||
displayPeakHold[channelNrFixed]);
|
||||
@@ -978,16 +1194,40 @@ void VolumeMeter::paintEvent(QPaintEvent *event)
|
||||
// see that the audio stream has been stopped, without
|
||||
// having too much visual impact.
|
||||
if (vertical)
|
||||
paintInputMeter(painter, channelNr * 4, 3, 3, 3,
|
||||
paintInputMeter(painter,
|
||||
channelNr * (meterThickness + 1), 0,
|
||||
meterThickness, INDICATOR_THICKNESS,
|
||||
displayInputPeakHold[channelNrFixed]);
|
||||
else
|
||||
paintInputMeter(painter, 0, channelNr * 4, 3, 3,
|
||||
paintInputMeter(painter, 0,
|
||||
channelNr * (meterThickness + 1),
|
||||
INDICATOR_THICKNESS, meterThickness,
|
||||
displayInputPeakHold[channelNrFixed]);
|
||||
}
|
||||
|
||||
lastRedrawTime = ts;
|
||||
}
|
||||
|
||||
QRect VolumeMeter::getBarRect() const
|
||||
{
|
||||
QRect rec = rect();
|
||||
if (vertical)
|
||||
rec.setWidth(displayNrAudioChannels * (meterThickness + 1) - 1);
|
||||
else
|
||||
rec.setHeight(displayNrAudioChannels * (meterThickness + 1) -
|
||||
1);
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
void VolumeMeter::changeEvent(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::StyleChange)
|
||||
recalculateLayout = true;
|
||||
|
||||
QWidget::changeEvent(e);
|
||||
}
|
||||
|
||||
void VolumeMeterTimer::AddVolControl(VolumeMeter *meter)
|
||||
{
|
||||
volumeMeters.push_back(meter);
|
||||
@@ -1000,6 +1240,13 @@ void VolumeMeterTimer::RemoveVolControl(VolumeMeter *meter)
|
||||
|
||||
void VolumeMeterTimer::timerEvent(QTimerEvent *)
|
||||
{
|
||||
for (VolumeMeter *meter : volumeMeters)
|
||||
meter->update();
|
||||
for (VolumeMeter *meter : volumeMeters) {
|
||||
if (meter->needLayoutChange()) {
|
||||
// Tell paintEvent to update layout and paint everything
|
||||
meter->update();
|
||||
} else {
|
||||
// Tell paintEvent to paint only the bars
|
||||
meter->update(meter->getBarRect());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <QTimer>
|
||||
#include <QMutex>
|
||||
#include <QList>
|
||||
#include <QSlider>
|
||||
#include <QMenu>
|
||||
|
||||
class QPushButton;
|
||||
class VolumeMeterTimer;
|
||||
@@ -25,6 +27,32 @@ class VolumeMeter : public QWidget {
|
||||
WRITE setForegroundWarningColor DESIGNABLE true)
|
||||
Q_PROPERTY(QColor foregroundErrorColor READ getForegroundErrorColor
|
||||
WRITE setForegroundErrorColor DESIGNABLE true)
|
||||
|
||||
Q_PROPERTY(QColor backgroundNominalColorDisabled READ
|
||||
getBackgroundNominalColorDisabled WRITE
|
||||
setBackgroundNominalColorDisabled
|
||||
DESIGNABLE true)
|
||||
Q_PROPERTY(QColor backgroundWarningColorDisabled READ
|
||||
getBackgroundWarningColorDisabled WRITE
|
||||
setBackgroundWarningColorDisabled
|
||||
DESIGNABLE true)
|
||||
Q_PROPERTY(
|
||||
QColor backgroundErrorColorDisabled READ
|
||||
getBackgroundErrorColorDisabled WRITE
|
||||
setBackgroundErrorColorDisabled DESIGNABLE true)
|
||||
Q_PROPERTY(QColor foregroundNominalColorDisabled READ
|
||||
getForegroundNominalColorDisabled WRITE
|
||||
setForegroundNominalColorDisabled
|
||||
DESIGNABLE true)
|
||||
Q_PROPERTY(QColor foregroundWarningColorDisabled READ
|
||||
getForegroundWarningColorDisabled WRITE
|
||||
setForegroundWarningColorDisabled
|
||||
DESIGNABLE true)
|
||||
Q_PROPERTY(
|
||||
QColor foregroundErrorColorDisabled READ
|
||||
getForegroundErrorColorDisabled WRITE
|
||||
setForegroundErrorColorDisabled DESIGNABLE true)
|
||||
|
||||
Q_PROPERTY(QColor clipColor READ getClipColor WRITE setClipColor
|
||||
DESIGNABLE true)
|
||||
Q_PROPERTY(QColor magnitudeColor READ getMagnitudeColor WRITE
|
||||
@@ -33,6 +61,10 @@ class VolumeMeter : public QWidget {
|
||||
setMajorTickColor DESIGNABLE true)
|
||||
Q_PROPERTY(QColor minorTickColor READ getMinorTickColor WRITE
|
||||
setMinorTickColor DESIGNABLE true)
|
||||
Q_PROPERTY(int meterThickness READ getMeterThickness WRITE
|
||||
setMeterThickness DESIGNABLE true)
|
||||
Q_PROPERTY(qreal meterFontScaling READ getMeterFontScaling WRITE
|
||||
setMeterFontScaling DESIGNABLE true)
|
||||
|
||||
// Levels are denoted in dBFS.
|
||||
Q_PROPERTY(qreal minimumLevel READ getMinimumLevel WRITE setMinimumLevel
|
||||
@@ -61,6 +93,8 @@ class VolumeMeter : public QWidget {
|
||||
Q_PROPERTY(qreal inputPeakHoldDuration READ getInputPeakHoldDuration
|
||||
WRITE setInputPeakHoldDuration DESIGNABLE true)
|
||||
|
||||
friend class VolControl;
|
||||
|
||||
private slots:
|
||||
void ClipEnding();
|
||||
|
||||
@@ -70,7 +104,7 @@ private:
|
||||
QSharedPointer<VolumeMeterTimer> updateTimerRef;
|
||||
|
||||
inline void resetLevels();
|
||||
inline void handleChannelCofigurationChange();
|
||||
inline void doLayout();
|
||||
inline bool detectIdle(uint64_t ts);
|
||||
inline void calculateBallistics(uint64_t ts,
|
||||
qreal timeSinceLastRedraw = 0.0);
|
||||
@@ -81,20 +115,19 @@ private:
|
||||
int height, float peakHold);
|
||||
void paintHMeter(QPainter &painter, int x, int y, int width, int height,
|
||||
float magnitude, float peak, float peakHold);
|
||||
void paintHTicks(QPainter &painter, int x, int y, int width,
|
||||
int height);
|
||||
void paintHTicks(QPainter &painter, int x, int y, int width);
|
||||
void paintVMeter(QPainter &painter, int x, int y, int width, int height,
|
||||
float magnitude, float peak, float peakHold);
|
||||
void paintVTicks(QPainter &painter, int x, int y, int height);
|
||||
|
||||
QMutex dataMutex;
|
||||
|
||||
bool recalculateLayout = true;
|
||||
uint64_t currentLastUpdateTime = 0;
|
||||
float currentMagnitude[MAX_AUDIO_CHANNELS];
|
||||
float currentPeak[MAX_AUDIO_CHANNELS];
|
||||
float currentInputPeak[MAX_AUDIO_CHANNELS];
|
||||
|
||||
QPixmap *tickPaintCache = nullptr;
|
||||
int displayNrAudioChannels = 0;
|
||||
float displayMagnitude[MAX_AUDIO_CHANNELS];
|
||||
float displayPeak[MAX_AUDIO_CHANNELS];
|
||||
@@ -110,10 +143,22 @@ private:
|
||||
QColor foregroundNominalColor;
|
||||
QColor foregroundWarningColor;
|
||||
QColor foregroundErrorColor;
|
||||
|
||||
QColor backgroundNominalColorDisabled;
|
||||
QColor backgroundWarningColorDisabled;
|
||||
QColor backgroundErrorColorDisabled;
|
||||
QColor foregroundNominalColorDisabled;
|
||||
QColor foregroundWarningColorDisabled;
|
||||
QColor foregroundErrorColorDisabled;
|
||||
|
||||
QColor clipColor;
|
||||
QColor magnitudeColor;
|
||||
QColor majorTickColor;
|
||||
QColor minorTickColor;
|
||||
|
||||
int meterThickness;
|
||||
qreal meterFontScaling;
|
||||
|
||||
qreal minimumLevel;
|
||||
qreal warningLevel;
|
||||
qreal errorLevel;
|
||||
@@ -124,6 +169,13 @@ private:
|
||||
qreal peakHoldDuration;
|
||||
qreal inputPeakHoldDuration;
|
||||
|
||||
QColor p_backgroundNominalColor;
|
||||
QColor p_backgroundWarningColor;
|
||||
QColor p_backgroundErrorColor;
|
||||
QColor p_foregroundNominalColor;
|
||||
QColor p_foregroundWarningColor;
|
||||
QColor p_foregroundErrorColor;
|
||||
|
||||
uint64_t lastRedrawTime = 0;
|
||||
int channels = 0;
|
||||
bool clipping = false;
|
||||
@@ -138,6 +190,8 @@ public:
|
||||
void setLevels(const float magnitude[MAX_AUDIO_CHANNELS],
|
||||
const float peak[MAX_AUDIO_CHANNELS],
|
||||
const float inputPeak[MAX_AUDIO_CHANNELS]);
|
||||
QRect getBarRect() const;
|
||||
bool needLayoutChange();
|
||||
|
||||
QColor getBackgroundNominalColor() const;
|
||||
void setBackgroundNominalColor(QColor c);
|
||||
@@ -151,6 +205,20 @@ public:
|
||||
void setForegroundWarningColor(QColor c);
|
||||
QColor getForegroundErrorColor() const;
|
||||
void setForegroundErrorColor(QColor c);
|
||||
|
||||
QColor getBackgroundNominalColorDisabled() const;
|
||||
void setBackgroundNominalColorDisabled(QColor c);
|
||||
QColor getBackgroundWarningColorDisabled() const;
|
||||
void setBackgroundWarningColorDisabled(QColor c);
|
||||
QColor getBackgroundErrorColorDisabled() const;
|
||||
void setBackgroundErrorColorDisabled(QColor c);
|
||||
QColor getForegroundNominalColorDisabled() const;
|
||||
void setForegroundNominalColorDisabled(QColor c);
|
||||
QColor getForegroundWarningColorDisabled() const;
|
||||
void setForegroundWarningColorDisabled(QColor c);
|
||||
QColor getForegroundErrorColorDisabled() const;
|
||||
void setForegroundErrorColorDisabled(QColor c);
|
||||
|
||||
QColor getClipColor() const;
|
||||
void setClipColor(QColor c);
|
||||
QColor getMagnitudeColor() const;
|
||||
@@ -159,6 +227,10 @@ public:
|
||||
void setMajorTickColor(QColor c);
|
||||
QColor getMinorTickColor() const;
|
||||
void setMinorTickColor(QColor c);
|
||||
int getMeterThickness() const;
|
||||
void setMeterThickness(int v);
|
||||
qreal getMeterFontScaling() const;
|
||||
void setMeterFontScaling(qreal v);
|
||||
qreal getMinimumLevel() const;
|
||||
void setMinimumLevel(qreal v);
|
||||
qreal getWarningLevel() const;
|
||||
@@ -182,6 +254,7 @@ public:
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void changeEvent(QEvent *e) override;
|
||||
};
|
||||
|
||||
class VolumeMeterTimer : public QTimer {
|
||||
@@ -200,7 +273,6 @@ protected:
|
||||
|
||||
class QLabel;
|
||||
class QSlider;
|
||||
class MuteCheckBox;
|
||||
|
||||
class VolControl : public QWidget {
|
||||
Q_OBJECT
|
||||
@@ -211,11 +283,13 @@ private:
|
||||
QLabel *volLabel;
|
||||
VolumeMeter *volMeter;
|
||||
QSlider *slider;
|
||||
QPushButton *config = nullptr;
|
||||
float levelTotal;
|
||||
float levelCount;
|
||||
obs_fader_t *obs_fader;
|
||||
obs_volmeter_t *obs_volmeter;
|
||||
bool vertical;
|
||||
QMenu *contextMenu;
|
||||
|
||||
static void OBSVolumeChanged(void *param, float db);
|
||||
static void OBSVolumeLevel(void *data,
|
||||
@@ -223,12 +297,20 @@ private:
|
||||
const float peak[MAX_AUDIO_CHANNELS],
|
||||
const float inputPeak[MAX_AUDIO_CHANNELS]);
|
||||
|
||||
void EmitConfigClicked();
|
||||
|
||||
private slots:
|
||||
void VolumeChanged();
|
||||
|
||||
void SliderChanged(int vol);
|
||||
void updateText();
|
||||
|
||||
signals:
|
||||
void ConfigClicked();
|
||||
|
||||
public:
|
||||
explicit VolControl(OBSSource source, bool vertical = false);
|
||||
explicit VolControl(OBSSource source, bool showConfig = false,
|
||||
bool vertical = false);
|
||||
~VolControl();
|
||||
|
||||
inline obs_source_t *GetSource() const { return source; }
|
||||
@@ -240,6 +322,19 @@ public:
|
||||
void setPeakMeterType(enum obs_peak_meter_type peakMeterType);
|
||||
|
||||
void EnableSlider(bool enable);
|
||||
inline void SetContextMenu(QMenu *cm) { contextMenu = cm; }
|
||||
|
||||
QSlider *GetSlider() const;
|
||||
void refreshColors();
|
||||
QSlider *GetSlider() const { return slider; }
|
||||
};
|
||||
|
||||
class VolumeSlider : public QSlider {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
obs_fader_t *fad;
|
||||
|
||||
VolumeSlider(obs_fader_t *fader, QWidget *parent = nullptr);
|
||||
VolumeSlider(obs_fader_t *fader, Qt::Orientation orientation,
|
||||
QWidget *parent = nullptr);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,25 @@ using websocketpp::lib::bind;
|
||||
|
||||
obs_websocket_vendor vendor;
|
||||
|
||||
void ClearWebsocketMessages()
|
||||
{
|
||||
switcher->websocketMessages.clear();
|
||||
for (auto &connection : switcher->connections) {
|
||||
Connection *c = dynamic_cast<Connection *>(connection.get());
|
||||
if (c) {
|
||||
c->Events().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SendWebsocketEvent(const std::string &eventMsg)
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_string(data, "message", eventMsg.c_str());
|
||||
obs_websocket_vendor_emit_event(vendor, VendorEvent, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void ReceiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, void *)
|
||||
{
|
||||
if (!obs_data_has_user_value(request_data, "message")) {
|
||||
@@ -24,7 +43,7 @@ void ReceiveWebsocketMessage(obs_data_t *request_data, obs_data_t *, void *)
|
||||
auto msg = obs_data_get_string(request_data, "message");
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->websocketMessages.emplace_back(msg);
|
||||
vblog(LOG_INFO, "received messsage: %s", msg);
|
||||
vblog(LOG_INFO, "received message: %s", msg);
|
||||
}
|
||||
|
||||
extern "C" void RegisterWebsocketVendor()
|
||||
@@ -232,6 +251,31 @@ void WSConnection::HandleHello(obs_data_t *helloMsg)
|
||||
Send(response);
|
||||
}
|
||||
|
||||
void WSConnection::HandleEvent(obs_data_t *msg)
|
||||
{
|
||||
auto d = obs_data_get_obj(msg, "d");
|
||||
auto eventData = obs_data_get_obj(d, "eventData");
|
||||
if (strcmp(obs_data_get_string(eventData, "vendorName"), VendorName) !=
|
||||
0) {
|
||||
vblog(LOG_INFO, "ignoring vendor event from \"%s\"",
|
||||
obs_data_get_string(eventData, "vendorName"));
|
||||
return;
|
||||
}
|
||||
if (strcmp(obs_data_get_string(eventData, "eventType"), VendorEvent) !=
|
||||
0) {
|
||||
vblog(LOG_INFO, "ignoring event type\"%s\"",
|
||||
obs_data_get_string(eventData, "eventType"));
|
||||
return;
|
||||
}
|
||||
auto eventDataNested = obs_data_get_obj(eventData, "eventData");
|
||||
_messages.emplace_back(obs_data_get_string(eventDataNested, "message"));
|
||||
vblog(LOG_INFO, "received event msg \"%s\"",
|
||||
obs_data_get_string(eventDataNested, "message"));
|
||||
obs_data_release(eventDataNested);
|
||||
obs_data_release(eventData);
|
||||
obs_data_release(d);
|
||||
}
|
||||
|
||||
void WSConnection::HandleResponse(obs_data_t *response)
|
||||
{
|
||||
auto data = obs_data_get_obj(response, "d");
|
||||
@@ -278,11 +322,14 @@ void WSConnection::OnMessage(connection_hdl, client::message_ptr message)
|
||||
case 2: // Identified
|
||||
_status = Status::AUTHENTICATED;
|
||||
break;
|
||||
case 5: // Event (Vendor)
|
||||
HandleEvent(json);
|
||||
break;
|
||||
case 7: // RequestResponse
|
||||
HandleResponse(json);
|
||||
break;
|
||||
default:
|
||||
vblog(LOG_INFO, "ignoring unkown opcode %d", opcode);
|
||||
vblog(LOG_INFO, "ignoring unknown opcode %d", opcode);
|
||||
break;
|
||||
}
|
||||
obs_data_release(json);
|
||||
|
||||
@@ -24,6 +24,10 @@ typedef websocketpp::client<websocketpp::config::asio_client> client;
|
||||
|
||||
constexpr char VendorName[] = "AdvancedSceneSwitcher";
|
||||
constexpr char VendorRequest[] = "AdvancedSceneSwitcherMessage";
|
||||
constexpr char VendorEvent[] = "AdvancedSceneSwitcherEvent";
|
||||
|
||||
void ClearWebsocketMessages();
|
||||
void SendWebsocketEvent(const std::string &);
|
||||
|
||||
class WSConnection : public QObject {
|
||||
public:
|
||||
@@ -34,6 +38,7 @@ public:
|
||||
bool _reconnect, int reconnectDelay = 10);
|
||||
void Disconnect();
|
||||
void SendRequest(const std::string &msg);
|
||||
std::vector<std::string> &Events() { return _messages; }
|
||||
std::string GetFail() { return _failMsg; }
|
||||
|
||||
enum class Status {
|
||||
@@ -51,6 +56,7 @@ private:
|
||||
void Send(const std::string &);
|
||||
void ConnectThread();
|
||||
void HandleHello(obs_data_t *helloMsg);
|
||||
void HandleEvent(obs_data_t *event);
|
||||
void HandleResponse(obs_data_t *response);
|
||||
|
||||
client _client;
|
||||
@@ -66,4 +72,6 @@ private:
|
||||
std::string _failMsg = "";
|
||||
std::atomic<Status> _status = {Status::DISCONNECTED};
|
||||
std::atomic_bool _disconnect{false};
|
||||
|
||||
std::vector<std::string> _messages;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user