Compare commits

...

81 Commits

Author SHA1 Message Date
WarmUpTill
3f2eb40c06 Improve "Studio mode" action 2025-10-28 19:21:29 +01:00
WarmUpTill
6932de866d Refactor Twitch event server migration and reconnect handling
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
This should avoid any events being lost due to server migration.
2025-10-28 19:20:52 +01:00
WarmUpTill
00db0cf7c4 CI: Refactor OpenSSL handling on MacOS to support MQTT SSL 2025-10-28 19:20:52 +01:00
WarmUpTill
e9baf27ca2 Add helper to find recent versions of OpenSSL on Windows 2025-10-28 19:20:52 +01:00
WarmUpTill
b1a5db0c9c Fix crash when switching macros after deleting one containing temp refs 2025-10-28 19:20:52 +01:00
WarmUpTill
8f3b868fd9 Limit projector action to main canvas and improve layout 2025-10-28 19:20:52 +01:00
WarmUpTill
b3bf89840b Add GetPath() 2025-10-28 19:20:52 +01:00
WarmUpTill
84f7d0d214 Add SSL support to MQTT connections
Also fixes crash on startup if SSL was used while there was no support
for encrypted connections yet
2025-10-28 19:20:52 +01:00
WarmUpTill
e1164c4fa3 Refactor help icon usage 2025-10-28 19:20:52 +01:00
WarmUpTill
4534b23bad Enable Windows and MacOS build with OBS versions older than 31.1.1 2025-10-28 19:20:52 +01:00
WarmUpTill
5d6a693f36 Refactor macro signal handling
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
Also drop support for scene group signals in macro, which hopefully
aren't used much anymore
2025-10-19 18:05:16 +02:00
WarmUpTill
a82662c8f4 Refactor "no match scene" handling 2025-10-19 18:05:16 +02:00
WarmUpTill
602675b3b3 Add option to automatically start the plugin on a given scene
This functionality was lost with the removal of the "Scene trigger" tab.
2025-10-19 18:05:16 +02:00
WarmUpTill
9551519cb9 Cleanup 2025-10-19 18:05:16 +02:00
WarmUpTill
badce063eb Retain settings of currently unknown macro segments 2025-10-19 18:05:16 +02:00
WarmUpTill
10d45c67d6 Workaround for FilterComboBox cutting off entries 2025-10-19 18:05:16 +02:00
WarmUpTill
0b77ef5081 Adapt to support canvas API 2025-10-19 18:05:16 +02:00
WarmUpTill
8f54e71e61 Add canvas and position variable support to scene condition 2025-10-19 18:05:16 +02:00
WarmUpTill
16136b8741 Rework scene switch action
* Support "next scene" and "previous scene"
* Support "scene at index"
* Add canvas support
* Rework layout
2025-10-19 18:05:16 +02:00
WarmUpTill
02d5051974 Rework scene switch helpers to support vertical canvas 2025-10-19 18:05:16 +02:00
WarmUpTill
078d62fa18 Add canvas support to scene selection 2025-10-19 18:05:16 +02:00
WarmUpTill
8aa6a7df2c CI: Bump dependency cache version 2025-10-19 18:05:16 +02:00
WarmUpTill
1494b1db6c Update based on obs-plugintemplate to enable switch to OBS 31.1.1 2025-10-19 18:05:16 +02:00
WarmUpTill
b4b3dc5a2b Update json to v3.12.0
Enable compatability with cmake versions > 3.5.
This is required for OBS 31.1.1.
2025-10-19 18:05:16 +02:00
WarmUpTill
ee41a8d58b CI: Switch to MacOS 15 to enable OBS 31.1.1 support 2025-10-19 18:05:16 +02:00
WarmUpTill
5f2237ac2a buildspec: Update OBS Studio dependency to 31.1.1
This enabled obs_canvas API support
2025-10-19 18:05:16 +02:00
WarmUpTill
6fb76e7e07 Refactor macro dock handling
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
* Move dock related settings into its own class
* Add support for grouping macro docks into a single dock window
2025-10-16 18:03:06 +02:00
WarmUpTill
ba094372a9 Add macro dock windows
They can be used to consolidate multiple macro docks into a single dock
window
2025-10-16 18:03:06 +02:00
WarmUpTill
0c886c8679 Refactor save / load handling 2025-10-16 18:03:06 +02:00
WarmUpTill
d9f05d3f7b Enable user to select install directory in Windows installer 2025-10-16 18:03:06 +02:00
WarmUpTill
c99b5f1feb Cleanup 2025-10-16 18:03:06 +02:00
WarmUpTill
a3b3cf9818 Improve disconnect reason logging
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-10-14 23:28:11 +02:00
WarmUpTill
ca3bbf5660 Fix Twitch event sub connection breaking after reconnect message
This will have to be revisited to avoid events being lost during the
reconnect
2025-10-14 23:28:11 +02:00
WarmUpTill
18524761a6 Add option to check if a macro's actions were performed
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
2025-10-14 19:21:29 +02:00
WarmUpTill
85222aed33 Enable left click to open links 2025-10-03 20:44:50 +02:00
WarmUpTill
84132f7c37 Ignore autoselect deprecations for now 2025-10-03 20:44:50 +02:00
WarmUpTill
ead664763f CI: Fix OBS 32 build 2025-10-03 20:44:50 +02:00
WarmUpTill
a2fa16f2d7 Fix typo 2025-10-03 20:44:50 +02:00
Tiefsee5037008
75edcffac9 l10n(zh-CN): correct reversed duration condition strings 2025-10-03 13:17:10 +02:00
WarmUpTill
dd5bcf425a Fix freeze on shutdown
Recording / streaming start event might be stuck in event queue if
advanced scene switcher or OBS takes a while to shut down.
This could result in the plugin starting back up while OBS is attempting
to shut down resulting in a potential deadlock during shutdown.
2025-10-03 11:56:40 +02:00
WarmUpTill
de32e1b18e Allow macro property selection from parent macro 2025-10-03 11:56:40 +02:00
WarmUpTill
1d412a818d Fix constness 2025-10-03 11:56:40 +02:00
WarmUpTill
c8e5b561ef Fix MacOS build 2025-10-03 11:56:40 +02:00
WarmUpTill
a1702dc798 Fix nested macro property selections not being saved 2025-10-03 11:56:40 +02:00
WarmUpTill
ae571583fc Fix queued nested macros duplicating segments 2025-10-03 11:56:40 +02:00
WarmUpTill
c4f70657d9 Fix nested macro PostLoad being called too early
Would lead to macro dependent settings potentially being lost
2025-10-03 11:56:40 +02:00
WarmUpTill
7901a988af Add option to enable or disable OBS preview 2025-10-03 11:56:40 +02:00
WarmUpTill
d2b70bbc6b Add option to change stream language 2025-10-03 11:56:40 +02:00
WarmUpTill
4d22a539f0 Add option to set content classification of Twitch stream 2025-10-03 11:56:40 +02:00
WarmUpTill
4e561320f7 Add option to disable shutdown confirmation dialog 2025-10-03 11:56:40 +02:00
WarmUpTill
0dfa4fe2c0 Add support for temp var selection highlighting in light theme 2025-10-03 11:56:40 +02:00
WarmUpTill
4cac4584f3 Fix invalid channel selections leading to event sub disconnect
Repeated invalid POSTs to /helix/eventsub/subscriptions will lead to a
disconnect of the event sub websocket connection.
Thus one single invalid channel selection could tear down any existing
subscriptions with it.
2025-10-03 11:56:40 +02:00
WarmUpTill
d20a975c4f Add option to enable / disable / toggle macro highlighting 2025-10-03 11:56:40 +02:00
WarmUpTill
756d7bbd3c Add support for multi-action stream deck buttons
It has to be noted however:

* multi-action buttons don't have any coordinates
* multi-action buttons send the "up" and "down" event at the same time.
  (When the multi-action button is released)
  Thus they might arrive in the wrong order due to network latency.
2025-10-03 11:56:40 +02:00
WarmUpTill
395a18fa4c Fix typo 2025-10-03 11:56:40 +02:00
WarmUpTill
2ea00d94c5 Add support for commercial start event 2025-10-03 11:56:40 +02:00
WarmUpTill
058e941a46 Add option to set Twitch stream tags 2025-10-03 11:56:40 +02:00
WarmUpTill
ea4a951554 Add warning if inline script file is invalid
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-09-23 22:59:42 +02:00
WarmUpTill
3c355ac6fe Improve file condition
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
* Add support for existence check
* Add support for "is file" check
* Add support for "is folder" check
* Add more temp vars
2025-09-13 08:41:40 +02:00
WarmUpTill
37606e274c CI: demote optional dependencies in deb package
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-09-07 19:11:22 +02:00
WarmUpTill
3e8d6e103a Add temp var to Media condition indicating which source matched
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-08-11 18:02:45 +02:00
WarmUpTill
4cae420ade Add support for "any" selection in secene item selection 2025-08-11 18:02:45 +02:00
WarmUpTill
b27a11931a Restore current selection when re-populating 2025-08-11 18:02:45 +02:00
WarmUpTill
92616bed6b Ignore warning C5287 for libusb build 2025-08-11 18:02:45 +02:00
WarmUpTill
b3f38851b6 Update libusb to v1.0.29
Resolves few warnings which were treated as errors with the recent MSVC
udpate
2025-08-11 18:02:45 +02:00
WarmUpTill
bf7fe71ae3 Fix system tray warning not being translated
Some checks failed
debian-build / build (push) Has been cancelled
Check locale / ubuntu64 (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-07-17 17:49:44 +02:00
炭酸コーラ
12a6b26d9a Updated Japanese translation to the latest version 2025-07-17 17:45:44 +02:00
WarmUpTill
373e74c080 Switch to different port for aquiring Twitch tokens
Some checks are pending
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
2025-07-16 21:15:36 +02:00
WarmUpTill
f3e7eaf212 Rework to support frequently resizing widgets
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
2025-07-13 18:17:46 +02:00
WarmUpTill
b036736547 Refactor MacroSegmentList rework to support nested macros 2025-07-13 18:17:46 +02:00
WarmUpTill
817de13e9d Add nested macro support 2025-07-13 18:17:46 +02:00
WarmUpTill
0b774c171d Cleanup
* Use GetSettingsWindow() instead of window() to avoid connecting to the
  wrong widget
* Fix typos
2025-07-13 18:17:46 +02:00
WarmUpTill
be8f7bd70f Add ResizableWidget
When manual resizing is enabled, the vertical widget size can be changed
by dragging the widget in the lower right corner.
2025-07-13 18:17:46 +02:00
WarmUpTill
5f963b5b7d Refactor macro edit area into separate class
This will enable support for nested macros
2025-07-13 18:17:46 +02:00
WarmUpTill
c6155c9fea Add default settings support 2025-07-13 18:17:46 +02:00
WarmUpTill
be809dbfab Fix macro dock being being destroyed when opening macro settings
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
2025-07-09 15:22:04 +02:00
WarmUpTill
7478c149b3 Add option to toggle mute state of audio source
Some checks failed
debian-build / build (push) Waiting to run
Push to master / Check Formatting 🔍 (push) Waiting to run
Push to master / Build Project 🧱 (push) Waiting to run
Push to master / Create Release 🛫 (push) Blocked by required conditions
Check locale / ubuntu64 (push) Has been cancelled
2025-07-08 17:51:21 +02:00
WarmUpTill
f4eaa9785d Fix crash on macro load 2025-07-08 17:49:33 +02:00
WarmUpTill
49c0de3f1b Add context menu option to expand / collapse all macro groups 2025-07-08 17:49:33 +02:00
WarmUpTill
101ef4e973 Fix crash when adding new macro to group 2025-07-08 17:49:33 +02:00
WarmUpTill
82568b23e8 Fix crash when initially setting up tab ordering
Some checks failed
debian-build / build (push) Has been cancelled
Push to master / Check Formatting 🔍 (push) Has been cancelled
Push to master / Build Project 🧱 (push) Has been cancelled
Push to master / Create Release 🛫 (push) Has been cancelled
The tabOrder vector was initialized with the size of tabNames.
Then new tabs were registered with AddSetupTabCallback() causing
tabNames to grow but tabOrder to remain the same.

Usually this is not a problem as LoadTabOrder() will be called which
ensures that their sizes are in sync.

However, when using a fresh OBS install or one with a corrupted scene
configuration file, the on_save() callbacks are called before any
loading callbacks.
With the sizes of tabNames and tabOrder not being in sync this would
lead to a crash.

Fixed by adding a tabWidgetOrderValid() check within SaveTabOrder()
before iterating over both vectors.
The tabWidgetOrderValid() was expanded to check the sizes of both
vectors.
Additionally the tabOrder initialization was moved from global static
initialization to function local static initialization to ensure that
potential preceeding calls to AddSetupTabCallback() will be taken into
account when setting up the tabOrder vector.
2025-07-06 10:19:20 +02:00
176 changed files with 9934 additions and 4357 deletions

View File

@@ -22,7 +22,7 @@ runs:
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.13
with:
cmake-version: '3.24.x'
cmake-version: '3.x.x'
- name: Restore cached dependencies
id: restore-cache

View File

@@ -394,8 +394,11 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
popd
pushd ${advss_dep_path}
log_info "Prepare openssl ..."
rm -rf ${advss_dep_path}/openssl ${advss_dep_path}/openssl_build
mkdir ${advss_dep_path}/openssl_build
pushd ${advss_dep_path}/openssl_build
rm -rf openssl
git clone https://github.com/openssl/openssl.git --branch openssl-3.1.2 --depth 1
mv openssl openssl_x86
@@ -403,25 +406,27 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
log_info "Building openssl x86 ..."
export MACOSX_DEPLOYMENT_TARGET=10.9
cd openssl_x86
./Configure darwin64-x86_64-cc shared
make
pushd openssl_x86
./Configure darwin64-x86_64-cc no-shared no-module no-zlib --prefix=${advss_dep_path}
make -j$(nproc)
popd
log_info "Building openssl arm ..."
export MACOSX_DEPLOYMENT_TARGET=10.15
cd ../openssl_arm
./Configure enable-rc5 zlib darwin64-arm64-cc no-asm
make
pushd openssl_arm
./Configure enable-rc5 darwin64-arm64-cc no-shared no-module no-asm no-zlib --prefix=${advss_dep_path}
make -j$(nproc)
log_info "Install openssl ..."
make install
popd
log_info "Combine arm and x86 openssl binaries ..."
cd ..
mkdir openssl-combined
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output openssl-combined/libcrypto.a
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output openssl-combined/libssl.a
lipo -create openssl_x86/libcrypto.a openssl_arm/libcrypto.a -output ${advss_dep_path}/lib/libcrypto.a
lipo -create openssl_x86/libssl.a openssl_arm/libssl.a -output ${advss_dep_path}/lib/libssl.a
log_info "Clean up openssl dir ..."
mv openssl_x86 openssl
rm -rf openssl_arm
rm -rf openssl_x86 openssl_arm
popd
pushd ${project_root}/deps/libusb
@@ -439,14 +444,16 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
mkdir ${project_root}/deps/libusb/out_x86
./autogen.sh
./configure --host=x86_64-apple-darwin --prefix=${advss_dep_path}
make && make install
make -j$(nproc)
make install
log_info "Configure libusb arm ..."
make clean
rm -r ${project_root}/deps/libusb/out_x86
mkdir ${project_root}/deps/libusb/out_x86
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_x86
make && make install
make -j$(nproc)
make install
log_info "Building libusb arm ..."
make clean
@@ -459,7 +466,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
export MACOSX_DEPLOYMENT_TARGET=10.15
mkdir ${project_root}/deps/libusb/out_arm
./configure --host=aarch64-apple-darwin --prefix=${project_root}/deps/libusb/out_arm
make && make install
make -j$(nproc)
make install
log_info "Combine arm and x86 libusb binaries ..."
lipo -create ${project_root}/deps/libusb/out_x86/lib/libusb-1.0.0.dylib \
@@ -491,8 +499,8 @@ Usage: %B${functrace[1]%:*}%b <option> [<options>]
-DPAHO_BUILD_SHARED=OFF
-DPAHO_BUILD_STATIC=ON
-DPAHO_WITH_MQTT_C=ON
-DOPENSSL_ROOT_DIR="${advss_dep_path}"
-DPAHO_WITH_SSL=OFF # TODO: figure out linking issues with openssl
-DPAHO_WITH_SSL=ON
-DOPENSSL_USE_STATIC_LIBS=ON
)
pushd ${mqtt_dir}

View File

@@ -253,12 +253,8 @@ ${_usage_host:-}"
macos-*)
if (( ${+CI} )) typeset -gx NSUnbufferedIO=YES
local openssl_lib_dir="${advss_deps_path}/openssl-combined/"
local openssl_include_dir="${advss_deps_path}/openssl/include"
cmake_args+=(
-DOPENSSL_INCLUDE_DIR="${openssl_include_dir}"
-DOPENSSL_LIBRARIES="${openssl_lib_dir}/libcrypto.a;${openssl_lib_dir}/libssl.a"
-DCMAKE_PREFIX_PATH="${advss_deps_path}"
--preset ${_preset}
)

View File

@@ -258,6 +258,9 @@ ${_usage_host:-}"
pushd ${project_root}
cmake --build build_${target##*-} --config ${config} -t package ${cmake_args}
# Mark certain deps as optional
build-aux/CI/linux/demote-deps.sh ${project_root}/release/*.deb Recommends '(mqtt)|(opencv)|(tesseract)|(usb)|(x11)'
if [ ! -e ${project_root}/release/${output_name}.deb ]; then
mv ${project_root}/release/*.deb ${project_root}/release/${output_name}.deb
mv ${project_root}/release/*.ddeb ${project_root}/release/${output_name}.ddeb

View File

@@ -209,7 +209,9 @@ function Build {
$msbuildExe = vswhere -latest -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | select-object -first 1
if ($msbuildExe) {
$env:CL="/wd5287"
Invoke-External $msbuildExe "${LibusbPath}/msvc/libusb.sln" /property:Configuration=Release /property:Platform=x64
Remove-Item Env:CL
$libusbBuildResultDirectory = "${LibusbPath}/build/v143/x64/Release"
if (-not (Test-Path -Path $libusbBuildResultDirectory)) {
@@ -232,8 +234,24 @@ function Build {
"-DCMAKE_PREFIX_PATH:PATH=${OBSDepPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${ADVSSDepPath}"
"-DPAHO_WITH_MQTT_C=ON"
"-DPAHO_WITH_SSL=ON"
)
# Try to find OpenSSL installed via winget
$pf64 = Join-Path $Env:ProgramFiles "OpenSSL-Win64"
$pf = Join-Path $Env:ProgramFiles "OpenSSL"
$possibleDirs = @($pf64, $pf)
$opensslDir = $possibleDirs | Where-Object { Test-Path (Join-Path $_ "include\openssl\ssl.h") } | Select-Object -First 1
if ($opensslDir) {
Write-Host "Detected OpenSSL at: $opensslDir"
$MqttCmakeArgs += "-DOPENSSL_ROOT_DIR=$opensslDir"
$MqttCmakeArgs += "-DOPENSSL_CRYPTO_LIBRARY=$opensslDir\lib\VC\x64\MD\libcrypto.lib"
$MqttCmakeArgs += "-DOPENSSL_SSL_LIBRARY=$opensslDir\lib\VC\x64\MD\libssl.lib"
} else {
Write-Warning "OpenSSL not found - maybe cmake will find it ..."
}
Log-Information "Configuring paho.mqtt.cpp..."
Invoke-External cmake -S ${MqttPath} -B ${MqttBuildPath} @MqttCmakeArgs

View File

@@ -18,5 +18,7 @@ if (( ! ${+commands[brew]} )) {
}
brew bundle --file ${SCRIPT_HOME}/.Brewfile
rehash
# Workaround to make sure locally built openssl is picked up by cmake
brew uninstall --ignore-dependencies openssl@3 || true
rehash || true
log_group

View File

@@ -10,7 +10,6 @@ if (( ${+commands[ccache]} )) {
typeset -gx CCACHE_CONFIGPATH="${project_root}/.ccache.conf"
ccache --set-config=run_second_cpp=true
ccache --set-config=direct_mode=true
ccache --set-config=inode_cache=true
ccache --set-config=compiler_check=content

View File

@@ -41,6 +41,7 @@ if (( ! (${skips[(Ie)all]} + ${skips[(Ie)deps]}) )) {
sudo apt-get install ${apt_args} \
build-essential \
libgles2-mesa-dev \
libsimde-dev \
obs-studio
local -a _qt_packages=()

View File

@@ -6,7 +6,7 @@ on:
description: "Project name detected by parsing build spec file"
value: ${{ jobs.check-event.outputs.pluginName }}
env:
DEP_DIR: .deps/advss-build-dependencies
DEP_DIR: .deps/advss-build-dependencies-3
jobs:
check-event:
name: Check GitHub Event Data 🔎
@@ -75,7 +75,7 @@ jobs:
macos-build:
name: Build for macOS 🍏
runs-on: macos-14
runs-on: macos-15
needs: check-event
defaults:
run:
@@ -107,8 +107,8 @@ jobs:
print "pluginName=${product_name}" >> $GITHUB_OUTPUT
print "pluginVersion=${git_tag}" >> $GITHUB_OUTPUT
print '::group::Enable Xcode 15.2'
sudo xcode-select --switch /Applications/Xcode_15.2.app/Contents/Developer
print '::group::Enable Xcode 16.1'
sudo xcode-select --switch /Applications/Xcode_16.1.0.app/Contents/Developer
print '::endgroup::'
- uses: actions/cache@v4
@@ -213,7 +213,7 @@ jobs:
- name: Set up CMake 🏗️
uses: jwlawson/actions-setup-cmake@v1.13
with:
cmake-version: '3.24.x'
cmake-version: '3.x.x'
- name: Set up Homebrew 🍺
uses: Homebrew/actions/setup-homebrew@master

View File

@@ -34,6 +34,11 @@ include(cmake/common/get_git_revision_description.cmake)
get_git_head_revision(GIT_REFSPEC GIT_SHA1)
git_describe(GIT_TAG)
# Helper for OpenSSL
if(OS_WINDOWS)
include(cmake/windows/wingetssl.cmake)
endif()
if(${GIT_TAG} STREQUAL "GIT-NOTFOUND")
set(GIT_TAG ${PROJECT_VERSION})
endif()
@@ -123,6 +128,12 @@ target_sources(
lib/macro/macro-condition.hpp
lib/macro/macro-dock.cpp
lib/macro/macro-dock.hpp
lib/macro/macro-dock-settings.hpp
lib/macro/macro-dock-settings.cpp
lib/macro/macro-dock-window.cpp
lib/macro/macro-dock-window.hpp
lib/macro/macro-edit.cpp
lib/macro/macro-edit.hpp
lib/macro/macro-export-import-dialog.cpp
lib/macro/macro-export-import-dialog.hpp
lib/macro/macro-helpers.cpp
@@ -141,12 +152,15 @@ target_sources(
lib/macro/macro-segment-list.hpp
lib/macro/macro-segment-selection.cpp
lib/macro/macro-segment-selection.hpp
lib/macro/macro-segment-unknown.hpp
lib/macro/macro-segment.cpp
lib/macro/macro-segment.hpp
lib/macro/macro-selection.cpp
lib/macro/macro-selection.hpp
lib/macro/macro-settings.cpp
lib/macro/macro-settings.hpp
lib/macro/macro-signals.cpp
lib/macro/macro-signals.hpp
lib/macro/macro-tab.cpp
lib/macro/macro-tree.cpp
lib/macro/macro-tree.hpp
@@ -164,6 +178,8 @@ target_sources(
lib/utils/auto-update-tooltip-label.hpp
lib/utils/backup.cpp
lib/utils/backup.hpp
lib/utils/canvas-helpers.cpp
lib/utils/canvas-helpers.hpp
lib/utils/condition-logic.cpp
lib/utils/condition-logic.hpp
lib/utils/curl-helper.cpp
@@ -217,6 +233,8 @@ target_sources(
lib/utils/priority-helper.hpp
lib/utils/regex-config.cpp
lib/utils/regex-config.hpp
lib/utils/resizable-widget.cpp
lib/utils/resizable-widget.hpp
lib/utils/resizing-text-edit.cpp
lib/utils/resizing-text-edit.hpp
lib/utils/resource-table.cpp

126
build-aux/CI/linux/demote-deps.sh Executable file
View File

@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# Usage: demote_deps.sh <in.deb> [Recommends|Suggests] [regex]
# Example: demote_deps.sh build/advanced-scene-switcher_1.31.0_amd64.deb Recommends 'opencv'
set -euo pipefail
if [[ $# -lt 1 ]]; then
echo "Usage: $0 <in.deb> [Recommends|Suggests] [regex]" >&2
exit 1
fi
IN_DEB="$1"
DEST_FIELD="${2:-Recommends}" # Recommends or Suggests
MATCH_REGEX="${3:-opencv}" # regex to match packages to demote
echo "Demoting dependencies matching '${MATCH_REGEX}' to ${DEST_FIELD}"
TMPDIR="$(mktemp -d)"
trap 'rm -rf "$TMPDIR"' EXIT
dpkg-deb -R "$IN_DEB" "$TMPDIR"
CONTROL="$TMPDIR/DEBIAN/control"
# Read a field (single line value), handling continuation lines starting with a space.
get_field() {
local key="$1"
awk -v key="$key" '
BEGIN { val=""; collecting=0 }
$0 ~ "^" key ":" {
collecting=1
sub("^" key ":[[:space:]]*", "", $0)
val=$0
next
}
collecting==1 {
if ($0 ~ "^[[:space:]]") {
sub("^[[:space:]]+", "", $0)
val = val " " $0
next
} else {
collecting=0
}
}
END { print val }
' "$CONTROL"
}
# Split a comma-separated list into lines, trimming whitespace
split_csv() {
tr ',' '\n' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//' | sed '/^$/d'
}
# Join with ", "
join_csv() {
awk 'BEGIN{first=1}{ if(!first) printf(", "); printf("%s",$0); first=0 } END{print ""}'
}
# Deduplicate while preserving order
dedup() {
awk '!seen[$0]++'
}
DEPENDS_VAL="$(get_field Depends)"
EXIST_DEST_VAL="$(get_field "$DEST_FIELD")"
# Separate opencv-matching vs the rest (preserve tokens exactly)
mapfile -t DEPENDS_ARR < <(printf "%s\n" "$DEPENDS_VAL" | split_csv)
declare -a MOVED=()
declare -a KEPT=()
for item in "${DEPENDS_ARR[@]}"; do
# Skip empty just in case
[[ -z "$item" ]] && continue
if [[ "$item" =~ $MATCH_REGEX ]]; then
MOVED+=("$item")
else
KEPT+=("$item")
fi
done
# Merge with existing dest field
if [[ -n "$EXIST_DEST_VAL" ]]; then
mapfile -t EXIST_DEST_ARR < <(printf "%s\n" "$EXIST_DEST_VAL" | split_csv)
MOVED+=("${EXIST_DEST_ARR[@]}")
fi
# De-duplicate
mapfile -t MOVED < <(printf "%s\n" "${MOVED[@]:-}" | sed '/^$/d' | dedup)
mapfile -t KEPT < <(printf "%s\n" "${KEPT[@]:-}" | sed '/^$/d' | dedup)
# Rebuild values
NEW_DEPENDS="$(printf "%s\n" "${KEPT[@]:-}" | join_csv || true)"
NEW_DEST="$(printf "%s\n" "${MOVED[@]:-}" | join_csv || true)"
# Write a cleaned control (remove existing Depends/Recommends/Suggests incl. continuations)
awk '
BEGIN { skip=0 }
{
if ($0 ~ "^(Depends|Recommends|Suggests):") { skip=1; next }
if (skip==1) {
if ($0 ~ "^[[:space:]]") { next } else { skip=0 }
}
print
}
' "$CONTROL" > "$CONTROL.clean"
# Append the rebuilt fields
{
cat "$CONTROL.clean"
if [[ -n "$NEW_DEPENDS" ]]; then
echo "Depends: $NEW_DEPENDS"
fi
if [[ -n "$NEW_DEST" ]]; then
echo "$DEST_FIELD: $NEW_DEST"
fi
} > "$CONTROL.new"
# Clean up empty lines
sed -i '/^[[:space:]]*$/d' "$CONTROL.new"
mv "$CONTROL.new" "$CONTROL"
rm -f "$CONTROL.clean"
# Repack
rm -f "${IN_DEB}"
OUT_DEB="${IN_DEB}"
dpkg-deb -b "$TMPDIR" "$OUT_DEB" >/dev/null
echo "$OUT_DEB"

View File

@@ -1,33 +1,33 @@
{
"dependencies": {
"obs-studio": {
"version": "30.1.2",
"version": "31.1.1",
"baseUrl": "https://github.com/obsproject/obs-studio/archive/refs/tags",
"label": "OBS sources",
"hashes": {
"macos": "490bae1c392b3b344b0270afd8cb887da4bc50bd92c0c426e96713c1ccb9701a",
"windows-x64": "c2dd03fa7fd01fad5beafce8f7156da11f9ed9a588373fd40b44a06f4c03b867"
"macos": "39751f067bacc13d44b116c5138491b5f1391f91516d3d590d874edd21292291",
"windows-x64": "2c8427c10b55ac6d68008df2e9a3e82f4647aaad18f105e30d4713c2de678ccf"
}
},
"prebuilt": {
"version": "2024-03-19",
"version": "2025-07-11",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built obs-deps",
"hashes": {
"macos": "2e9bfb55a5e0e4c1086fa1fda4cf268debfead473089df2aaea80e1c7a3ca7ff",
"windows-x64": "6e86068371526a967e805f6f9903f9407adb683c21820db5f07da8f30d11e998"
"macos": "495687e63383d1a287684b6e2e9bfe246bb8f156fe265926afb1a325af1edd2a",
"windows-x64": "c8c642c1070dc31ce9a0f1e4cef5bb992f4bff4882255788b5da12129e85caa7"
}
},
"qt6": {
"version": "2024-03-19",
"version": "2025-07-11",
"baseUrl": "https://github.com/obsproject/obs-deps/releases/download",
"label": "Pre-Built Qt6",
"hashes": {
"macos": "694f1e639c017e3b1f456f735330dc5afae287cbea85757101af1368de3142c8",
"windows-x64": "72d1df34a0ef7413a681d5fcc88cae81da60adc03dcd23ef17862ab170bcc0dd"
"macos": "d3f5f04b6ea486e032530bdf0187cbda9a54e0a49621a4c8ba984c5023998867",
"windows-x64": "0e76bf0555dd5382838850b748d3dcfab44a1e1058441309ab54e1a65b156d0a"
},
"debugSymbols": {
"windows-x64": "fbddd1f659c360f2291911ac5709b67b6f8182e6bca519d24712e4f6fd3cc865"
"windows-x64": "11b7be92cf66a273299b8f3515c07a5cfb61614b59a4e67f7fc5ecba5e2bdf21"
}
}
},

View File

@@ -65,17 +65,16 @@ function(_setup_obs_studio)
if(OS_WINDOWS)
set(_cmake_generator "${CMAKE_GENERATOR}")
set(_cmake_arch "-A ${arch}")
set(_cmake_arch
"-A ${arch},version=${CMAKE_VS_WINDOWS_TARGET_PLATFORM_VERSION}")
set(_cmake_extra
"-DCMAKE_SYSTEM_VERSION=${CMAKE_SYSTEM_VERSION} -DCMAKE_ENABLE_SCRIPTING=OFF"
)
set(_cmake_version "2.0.0")
elseif(OS_MACOS)
set(_cmake_generator "Xcode")
set(_cmake_arch "-DCMAKE_OSX_ARCHITECTURES:STRING='arm64;x86_64'")
set(_cmake_extra
"-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
set(_cmake_version "3.0.0")
endif()
message(STATUS "Configure ${label} (${arch})")
@@ -83,32 +82,42 @@ function(_setup_obs_studio)
COMMAND
"${CMAKE_COMMAND}" -S "${dependencies_dir}/${_obs_destination}" -B
"${dependencies_dir}/${_obs_destination}/build_${arch}" -G
${_cmake_generator} "${_cmake_arch}"
-DOBS_CMAKE_VERSION:STRING=${_cmake_version} -DENABLE_PLUGINS:BOOL=OFF
-DENABLE_UI:BOOL=OFF -DOBS_VERSION_OVERRIDE:STRING=${_obs_version}
${_cmake_generator} "${_cmake_arch}" -DOBS_CMAKE_VERSION:STRING=3.0.0
-DENABLE_PLUGINS:BOOL=OFF -DENABLE_FRONTEND:BOOL=OFF
-DOBS_VERSION_OVERRIDE:STRING=${_obs_version}
"-DCMAKE_PREFIX_PATH='${CMAKE_PREFIX_PATH}'" ${_is_fresh} ${_cmake_extra}
RESULT_VARIABLE _process_result COMMAND_ERROR_IS_FATAL ANY
OUTPUT_QUIET)
message(STATUS "Configure ${label} (${arch}) - done")
message(STATUS "Build ${label} (${arch})")
message(STATUS "Build ${label} (Debug - ${arch})")
execute_process(
COMMAND "${CMAKE_COMMAND}" --build build_${arch} --target obs-frontend-api
--config Debug --parallel
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
RESULT_VARIABLE _process_result COMMAND_ERROR_IS_FATAL ANY
OUTPUT_QUIET)
message(STATUS "Build ${label} (${arch}) - done")
message(STATUS "Build ${label} (Debug - ${arch}) - done")
message(STATUS "Build ${label} (Release - ${arch})")
execute_process(
COMMAND "${CMAKE_COMMAND}" --build build_${arch} --target obs-frontend-api
--config Release --parallel
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
RESULT_VARIABLE _process_result COMMAND_ERROR_IS_FATAL ANY
OUTPUT_QUIET)
message(STATUS "Build ${label} (Reelase - ${arch}) - done")
message(STATUS "Install ${label} (${arch})")
if(OS_WINDOWS)
set(_cmake_extra "--component obs_libraries")
else()
set(_cmake_extra "")
endif()
execute_process(
COMMAND "${CMAKE_COMMAND}" --install build_${arch} --component Development
--config Debug --prefix "${dependencies_dir}" ${_cmake_extra}
--config Debug --prefix "${dependencies_dir}"
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
RESULT_VARIABLE _process_result COMMAND_ERROR_IS_FATAL ANY
OUTPUT_QUIET)
execute_process(
COMMAND "${CMAKE_COMMAND}" --install build_${arch} --component Development
--config Release --prefix "${dependencies_dir}"
WORKING_DIRECTORY "${dependencies_dir}/${_obs_destination}"
RESULT_VARIABLE _process_result COMMAND_ERROR_IS_FATAL ANY
OUTPUT_QUIET)

View File

@@ -22,6 +22,7 @@ SolidCompression=yes
DirExistsWarning=no
SetupIconFile=installer.ico
UninstallDisplayIcon={app}\data\obs-plugins\advanced-scene-switcher\res\images\logo.ico
DisableDirPage=no
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"

View File

@@ -0,0 +1,78 @@
# ---------------------------------------------------------------------------
# Detects OpenSSL installed via winget or other common Windows locations,
# without requiring the user to manually set OPENSSL_ROOT_DIR.
# ---------------------------------------------------------------------------
if(WIN32 AND (NOT OpenSSL_FOUND))
set(_openssl_roots
"$ENV{ProgramFiles}/OpenSSL-Win64" "$ENV{ProgramFiles}/OpenSSL"
"$ENV{ProgramW6432}/OpenSSL-Win64")
set(_openssl_lib_suffixes "lib/VC/x64/MD" "lib/VC/x64/MDd" "lib/VC/x64/MT"
"lib/VC/x64/MTd" "lib")
# Determine which configuration we're building
if(CMAKE_BUILD_TYPE MATCHES "Debug")
set(_is_debug TRUE)
else()
set(_is_debug FALSE)
endif()
# Determine which runtime we use Default to /MD (shared CRT)
set(_crt_kind "MD")
if(MSVC)
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "MultiThreaded")
if(CMAKE_MSVC_RUNTIME_LIBRARY MATCHES "Debug")
set(_crt_kind "MTd")
else()
set(_crt_kind "MT")
endif()
else()
if(_is_debug)
set(_crt_kind "MDd")
else()
set(_crt_kind "MD")
endif()
endif()
endif()
message(STATUS "Looking for OpenSSL built with CRT variant: ${_crt_kind}")
# Try to find the root and corresponding lib path
foreach(_root ${_openssl_roots})
if(EXISTS "${_root}/include/openssl/ssl.h")
foreach(_suffix ${_openssl_lib_suffixes})
if(_suffix MATCHES "${_crt_kind}$"
AND EXISTS "${_root}/${_suffix}/libcrypto.lib")
set(OPENSSL_ROOT_DIR
"${_root}"
CACHE PATH "Path to OpenSSL root")
set(OPENSSL_CRYPTO_LIBRARY
"${_root}/${_suffix}/libcrypto.lib"
CACHE FILEPATH "OpenSSL crypto lib")
set(OPENSSL_SSL_LIBRARY
"${_root}/${_suffix}/libssl.lib"
CACHE FILEPATH "OpenSSL ssl lib")
set(OPENSSL_INCLUDE_DIR
"${_root}/include"
CACHE PATH "OpenSSL include dir")
set(OpenSSL_FOUND
TRUE
CACHE BOOL "Whether OpenSSL was found")
message(STATUS "Found OpenSSL at: ${_root}/${_suffix}")
return()
endif()
endforeach()
endif()
if(OpenSSL_FOUND)
break()
endif()
endforeach()
if(NOT OpenSSL_FOUND)
message(WARNING "Could not auto-detect OpenSSL under Program Files. "
"Might have to set OPENSSL_ROOT_DIR manually.")
endif()
endif()

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Aktiviere den Szenenwe
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Aktiviere den Szenenwechsler nicht"
AdvSceneSwitcher.generalTab.status.start="Start"
AdvSceneSwitcher.generalTab.status.stop="Stop"
AdvSceneSwitcher.generalTab.status.autoStart="Starte auotmatischen den Szenenwechsler beim:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Starte auotmatischen den Szenenwechsler beim:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Niemals"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Aufnehmen"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Streamen"
@@ -383,8 +383,6 @@ AdvSceneSwitcher.condition.variable.type.greaterThanVariable="ist größer als d
; Macro Actions
AdvSceneSwitcher.action.scene="Szene wechseln"
AdvSceneSwitcher.action.scene.entry="Wechsle{{sceneTypes}}Szene zu{{scenes}}mittels{{transitions}}mit einer Dauer von{{duration}}Sekunden"
AdvSceneSwitcher.action.scene.entry.noDuration="Wechsle{{sceneTypes}}Szene zu{{scenes}}mittels{{transitions}}"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Warten, bis der Übergang zur Zielszene abgeschlossen ist"
AdvSceneSwitcher.action.wait="Warten"
AdvSceneSwitcher.action.wait.type.fixed="fixe"
@@ -466,7 +464,7 @@ AdvSceneSwitcher.action.pluginState.type.stop="Erweiterten Automatischen Szenenw
AdvSceneSwitcher.action.pluginState.type.noMatch="Ändern des Nichtübereinstimmungsverhaltens:"
AdvSceneSwitcher.action.pluginState.type.import="Einstellungen importieren aus"
AdvSceneSwitcher.action.pluginState.importWarning="Hinweis: Die Aktion wird ignoriert, solange das Einstellungsfenster geöffnet ist."
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}"
AdvSceneSwitcher.action.virtualCamera="Virtuelle Kamera"
AdvSceneSwitcher.action.virtualCamera.type.stop="Virtuelle Kamera stoppen"
AdvSceneSwitcher.action.virtualCamera.type.start="Virtuelle Kamera starten"
@@ -497,7 +495,7 @@ AdvSceneSwitcher.action.file.type.write="Schreiben"
AdvSceneSwitcher.action.file.type.append="Anhängen"
AdvSceneSwitcher.action.file.entry="{{actions}} in {{filePath}}:"
AdvSceneSwitcher.action.studioMode="Studio-Modus"
AdvSceneSwitcher.action.studioMode.type.swap="Vorschau- und Programm-Szene tauschen"
AdvSceneSwitcher.action.studioMode.type.transitionWithSwap="Vorschau- und Programm-Szene tauschen"
AdvSceneSwitcher.action.studioMode.type.setScene="Vorschau-Szene einstellen auf"
AdvSceneSwitcher.action.studioMode.type.enable="Studio-Modus aktivieren"
AdvSceneSwitcher.action.studioMode.type.disable="Studio-Modus deaktivieren"

View File

@@ -4,7 +4,7 @@ AdvSceneSwitcher.windowTitle="Advanced Scene Switcher"
# General Tab
AdvSceneSwitcher.generalTab.title="General"
AdvSceneSwitcher.generalTab.status="Status"
AdvSceneSwitcher.generalTab.status.hotkeytips="Hotkeys can be defined in the OBS settings"
AdvSceneSwitcher.generalTab.status.hotkeytips="Start/stop hotkeys can be defined in the OBS settings"
AdvSceneSwitcher.generalTab.status.currentStatus="Advanced Scene Switcher is:"
AdvSceneSwitcher.generalTab.status.onStartup="On startup of OBS:"
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="Start the scene switcher if it was running"
@@ -12,11 +12,12 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Always start the scene
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Do not start the scene switcher"
AdvSceneSwitcher.generalTab.status.start="Start"
AdvSceneSwitcher.generalTab.status.stop="Stop"
AdvSceneSwitcher.generalTab.status.autoStart="Automatically start the scene switcher when:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Automatically start the scene switcher when:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Never"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Recording"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Streaming"
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Recording or Streaming"
AdvSceneSwitcher.generalTab.status.autoStart.scene="Automatically start the scene switcher on scene:"
AdvSceneSwitcher.generalTab.status.checkInterval="Check conditions every"
AdvSceneSwitcher.generalTab.status.checkIntervalTooLow="⚠️ Conflict with macro \"%1\"!"
AdvSceneSwitcher.generalTab.status.checkIntervalTooLow.tooltip="Macro \"%1\" won't be able to check its condition at the desired interval of %2.\nEither change the condition check value on the General tab or in the settings of macro \"%3\"."
@@ -150,9 +151,9 @@ AdvSceneSwitcher.macroTab.title="Macro"
AdvSceneSwitcher.macroTab.macros="Macros"
AdvSceneSwitcher.macroTab.priorityWarning="Note: It is recommended to configure macros to be the highest priority functionality.\nThis setting can be changed on the General tab."
AdvSceneSwitcher.macroTab.help="Macros allow you to execute a string of actions depending on multiple conditions.\n\nClick on the highlighted plus symbol to add a new Macro."
AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define Macro conditions.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new condition."
AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define Macro actions.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.editElseActionHelp="This section allows you to define Macro actions, which are executed if the conditions are *not* met.\n\nSelect an existing or add a new Macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.editConditionHelp="This section allows you to define macro conditions.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new condition."
AdvSceneSwitcher.macroTab.editActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.editElseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nSelect an existing or add a new macro on the left.\nThen click the plus button below to add a new action."
AdvSceneSwitcher.macroTab.edit="Edit macro"
AdvSceneSwitcher.macroTab.edit.logic="Logic type:"
AdvSceneSwitcher.macroTab.edit.condition="Condition type:"
@@ -177,6 +178,8 @@ AdvSceneSwitcher.macroTab.contextMenuAdd="Add"
AdvSceneSwitcher.macroTab.copy="Duplicate Macro"
AdvSceneSwitcher.macroTab.group="Group Selected Macros"
AdvSceneSwitcher.macroTab.ungroup="Ungroup Selected Groups"
AdvSceneSwitcher.macroTab.expandAllGroups="Expand all Groups"
AdvSceneSwitcher.macroTab.collapseAllGroups="Collapse all Groups"
AdvSceneSwitcher.macroTab.rename="Rename"
AdvSceneSwitcher.macroTab.remove="Remove"
AdvSceneSwitcher.macroTab.export="Export"
@@ -229,6 +232,8 @@ AdvSceneSwitcher.macroTab.pauseStateSaveBehavior.persist="The state the macro wa
AdvSceneSwitcher.macroTab.pauseStateSaveBehavior.pause="Paused"
AdvSceneSwitcher.macroTab.pauseStateSaveBehavior.unpause="Unpaused"
AdvSceneSwitcher.macroTab.currentRegisterDock="Register dock widget to control the pause state of selected macro or run it manually"
AdvSceneSwitcher.macroTab.currentIsStandaloneDock="Is standalone dock"
AdvSceneSwitcher.macroTab.currentDockWindowName="Add to macro dock with name:"
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Add button to run the macro"
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="Add button to pause or unpause the macro"
AdvSceneSwitcher.macroTab.currentDockAddStatusLabel="Add status label"
@@ -321,6 +326,7 @@ AdvSceneSwitcher.condition.scene.type.previousPattern="Previous scene matches"
AdvSceneSwitcher.condition.scene.type.previewPattern="Preview scene matches"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="During transition check for transition target scene"
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="During transition check for transition source scene"
AdvSceneSwitcher.condition.scene.canvasNotSupported="This check is not supported for the currently selected canvas \"%1\""
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}{{regex}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="Window"
@@ -336,6 +342,9 @@ AdvSceneSwitcher.condition.file="File"
AdvSceneSwitcher.condition.file.type.match="matches"
AdvSceneSwitcher.condition.file.type.contentChange="content changed"
AdvSceneSwitcher.condition.file.type.dateChange="modification date changed"
AdvSceneSwitcher.condition.file.type.exists="exists"
AdvSceneSwitcher.condition.file.type.isFile="is a file"
AdvSceneSwitcher.condition.file.type.isFolder="is a folder"
AdvSceneSwitcher.condition.file.remote="Remote file"
AdvSceneSwitcher.condition.file.local="Local file"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}{{useRegex}}"
@@ -485,6 +494,7 @@ AdvSceneSwitcher.condition.macro.type.multiState="Multiple macro condition state
AdvSceneSwitcher.condition.macro.type.actionDisabled="Macro action disabled"
AdvSceneSwitcher.condition.macro.type.actionEnabled="Macro action enabled"
AdvSceneSwitcher.condition.macro.type.paused="Macro is paused"
AdvSceneSwitcher.condition.macro.type.actionsPerformed="Macro actions were executed"
AdvSceneSwitcher.condition.macro.type.selection="{{types}}"
AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than"
@@ -501,6 +511,7 @@ AdvSceneSwitcher.condition.macro.count.entry.line2="Current count:{{currentCount
AdvSceneSwitcher.condition.macro.actionState.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled"
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
AdvSceneSwitcher.condition.macro.paused.entry="Macro{{macros}}is paused"
AdvSceneSwitcher.condition.macro.actionsPerformed.entry="The actions of macro{{macros}}were performed"
AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing"
@@ -516,6 +527,7 @@ AdvSceneSwitcher.condition.source.sizeCompare.equal="Equals"
AdvSceneSwitcher.condition.source.sizeCompare.more="Bigger than"
AdvSceneSwitcher.condition.source.refresh="Refresh"
AdvSceneSwitcher.condition.source.refresh.tooltip="Repopulate the source settings selection with the settings of the source which's name matches the variable value."
AdvSceneSwitcher.condition.source.includeDefaults="Include default settings values"
AdvSceneSwitcher.condition.source.sceneVisibilityHint="Scene specific visibility can be checked using the \"Scene item visibility\" condition"
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}}{{conditions}}{{settingSelection}}{{refresh}}{{sizeCompareMethods}}{{size}}"
@@ -535,6 +547,7 @@ AdvSceneSwitcher.condition.filter.type.individualSettingListSelectionMatches="Se
AdvSceneSwitcher.condition.filter.type.individualSettingChanged="Settings value changed"
AdvSceneSwitcher.condition.filter.refresh="Refresh"
AdvSceneSwitcher.condition.filter.refresh.tooltip="Repopulate the filter settings selection with the settings of the filter which's name matches the variable value."
AdvSceneSwitcher.condition.filter.includeDefaults="Include default settings values"
AdvSceneSwitcher.condition.filter.getSettings="Get current settings"
AdvSceneSwitcher.condition.filter.entry.line1="On{{sources}}{{filters}}{{conditions}}{{settingSelection}}{{refresh}}"
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
@@ -695,6 +708,7 @@ AdvSceneSwitcher.condition.twitch.type.event.channel.raid.outbound="Raided someo
AdvSceneSwitcher.condition.twitch.type.event.channel.raid.inbound="Raid to your channel occured"
AdvSceneSwitcher.condition.twitch.type.event.channel.shoutout.outbound="Shoutout to someone's channel given"
AdvSceneSwitcher.condition.twitch.type.event.channel.shoutout.inbound="Shoutout to your channel received"
AdvSceneSwitcher.condition.twitch.type.event.channel.commercial.start="Commercial break started"
AdvSceneSwitcher.condition.twitch.type.event.channel.poll.start="Poll started"
AdvSceneSwitcher.condition.twitch.type.event.channel.poll.progress="Poll votes changed (progress)"
AdvSceneSwitcher.condition.twitch.type.event.channel.poll.end="Poll ended"
@@ -810,12 +824,21 @@ AdvSceneSwitcher.condition.script="Script"
# Macro Actions
AdvSceneSwitcher.action.unknown="Unknown action"
AdvSceneSwitcher.action.scene="Switch scene"
AdvSceneSwitcher.action.scene.action.fixed="Scene with name"
AdvSceneSwitcher.action.scene.action.sceneListNext="Next scene in scene list"
AdvSceneSwitcher.action.scene.action.sceneListPrevious="Previous scene in scene list"
AdvSceneSwitcher.action.scene.action.sceneListIndex="Scene in scene list at position"
AdvSceneSwitcher.action.scene.type.program="Program"
AdvSceneSwitcher.action.scene.type.preview="Preview"
AdvSceneSwitcher.action.scene.entry="Switch{{sceneTypes}}scene to{{scenes}}using{{transitions}}with a duration of{{duration}}seconds"
AdvSceneSwitcher.action.scene.entry.noDuration="Switch{{sceneTypes}}scene to{{scenes}}using{{transitions}}"
AdvSceneSwitcher.action.scene.entry.preview="Switch{{sceneTypes}}scene to{{scenes}}"
AdvSceneSwitcher.action.scene.layout.action.fixed="Switch{{sceneTypes}}to{{actions}}{{scenes}}"
AdvSceneSwitcher.action.scene.layout.action.list="Switch{{sceneTypes}}to{{actions}}on{{canvas}}"
AdvSceneSwitcher.action.scene.layout.action.list.noCanvasSelection="Switch{{sceneTypes}}to{{actions}}"
AdvSceneSwitcher.action.scene.layout.action.listIndex="Switch{{sceneTypes}}to{{actions}}{{index}}on{{canvas}}{{sceneNameAtIndex}}"
AdvSceneSwitcher.action.scene.layout.action.listIndex.noCanvasSelection="Switch{{sceneTypes}}to{{actions}}{{index}}{{sceneNameAtIndex}}"
AdvSceneSwitcher.action.scene.layout.transition="Use transition{{transitions}}"
AdvSceneSwitcher.action.scene.layout.transitionWithDuration="Use transition{{transitions}}with a duration of{{duration}}seconds"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Wait until transition to target scene is complete"
AdvSceneSwitcher.action.scene.canvasNotSupported="Not supported by the currently selected canvas \"%1\""
AdvSceneSwitcher.action.wait="Wait"
AdvSceneSwitcher.action.wait.type.fixed="fixed"
AdvSceneSwitcher.action.wait.type.random="random"
@@ -824,6 +847,7 @@ AdvSceneSwitcher.action.wait.entry.random="Wait for{{waitType}}duration from{{du
AdvSceneSwitcher.action.audio="Audio"
AdvSceneSwitcher.action.audio.type.mute="Mute"
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
AdvSceneSwitcher.action.audio.type.toggleMute="Toggle mute"
AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume"
AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume"
AdvSceneSwitcher.action.audio.type.syncOffset="Set sync offset"
@@ -959,6 +983,10 @@ AdvSceneSwitcher.action.macro.type.stop="Stop actions"
AdvSceneSwitcher.action.macro.type.disableAction="Disable action"
AdvSceneSwitcher.action.macro.type.enableAction="Enable action"
AdvSceneSwitcher.action.macro.type.toggleAction="Toggle action"
AdvSceneSwitcher.action.macro.type.nestedMacro="Nested macro"
AdvSceneSwitcher.action.macro.type.nestedMacro.conditionHelp="This section allows you to define macro conditions.\n\nClick the plus button below to add a new condition."
AdvSceneSwitcher.action.macro.type.nestedMacro.actionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are met.\n\nClick the plus button below to add a new action."
AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp="This section allows you to define macro actions.\nThe actions in this section will be performed when the conditions are *not* met.\n\nClick the plus button below to add a new action."
AdvSceneSwitcher.action.macro.entry.run="{{actions}}{{actionTypes}}of{{macros}}"
AdvSceneSwitcher.action.macro.entry.run.condition="{{conditionBehaviors}}of{{conditionMacros}}"
AdvSceneSwitcher.action.macro.entry.other="{{actions}}{{actionIndex}}{{macros}}"
@@ -969,7 +997,11 @@ AdvSceneSwitcher.action.pluginState.type.import="Import settings from"
AdvSceneSwitcher.action.pluginState.importWarning="Note: Action will be ignored while settings window is opened."
AdvSceneSwitcher.action.pluginState.type.terminate="Shut down OBS"
AdvSceneSwitcher.action.pluginState.terminateConfirm="OBS will be shut down automatically in 10 seconds!\nDo you want to continue shutting down OBS?"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.pluginState.type.enableMacroHighlighting="Enable macro highlighting"
AdvSceneSwitcher.action.pluginState.type.disableMacroHighlighting="Disable macro highlighting"
AdvSceneSwitcher.action.pluginState.type.toggleMacroHighlighting="Toggle macro highlighting"
AdvSceneSwitcher.action.pluginState.confirmShutdown="Ask for shutdown confirmation"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}"
AdvSceneSwitcher.action.virtualCamera="Virtual camera"
AdvSceneSwitcher.action.virtualCamera.type.stop="Stop virtual camera"
AdvSceneSwitcher.action.virtualCamera.type.start="Start virtual camera"
@@ -1023,7 +1055,17 @@ AdvSceneSwitcher.action.file.type.write="Write"
AdvSceneSwitcher.action.file.type.append="Append"
AdvSceneSwitcher.action.file.entry="{{actions}}to{{filePath}}:"
AdvSceneSwitcher.action.studioMode="Studio mode"
AdvSceneSwitcher.action.studioMode.type.swap="Swap preview and program scene"
AdvSceneSwitcher.action.studioMode.type.transition="Transition"
AdvSceneSwitcher.action.studioMode.type.transitionWithSwap="Swap preview and program scene"
AdvSceneSwitcher.action.studioMode.type.transitionSwap.enable="Enable preview/program scene swapping after transition"
AdvSceneSwitcher.action.studioMode.type.transitionSwap.disable="Disable preview/program scene swapping after transition"
AdvSceneSwitcher.action.studioMode.type.transitionSwap.toggle="Toggle preview/program scene swapping after transition"
AdvSceneSwitcher.action.studioMode.type.duplicateScene.enable="Enable scene duplication"
AdvSceneSwitcher.action.studioMode.type.duplicateScene.disable="Disable scene duplication"
AdvSceneSwitcher.action.studioMode.type.duplicateScene.toggle="Toggle scene duplication"
AdvSceneSwitcher.action.studioMode.type.duplicateSource.enable="Enable source duplication"
AdvSceneSwitcher.action.studioMode.type.duplicateSource.disable="Disable source duplication"
AdvSceneSwitcher.action.studioMode.type.duplicateSource.toggle="Toggle source duplication"
AdvSceneSwitcher.action.studioMode.type.setScene="Set preview scene to"
AdvSceneSwitcher.action.studioMode.type.enable="Enable studio mode"
AdvSceneSwitcher.action.studioMode.type.disable="Disable studio mode"
@@ -1080,7 +1122,7 @@ AdvSceneSwitcher.action.websocket.api.genericWebsocket="Generic websocket messag
AdvSceneSwitcher.action.websocket.type.request="Request"
AdvSceneSwitcher.action.websocket.type.event="Event"
AdvSceneSwitcher.action.websocket.settingsConflictGeneric="Possible settings conflict: Generic websocket message selection with connection which follows the OBS protocol!"
AdvSceneSwitcher.action.websocket.settingsConflictOBS="Possible settings conflict: Connection selected which only supports generic websockte messages while message type requires OBS protocol!"
AdvSceneSwitcher.action.websocket.settingsConflictOBS="Possible settings conflict: Connection selected which only supports generic websocket messages while message type requires OBS protocol!"
AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.request="Send{{api}}of type{{type}}via{{connection}}"
AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event="Send{{api}}of type{{type}}to connected clients"
AdvSceneSwitcher.action.websocket.entry.generic="Send{{api}}via{{connection}}"
@@ -1189,6 +1231,8 @@ AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.twitch="Twitch"
AdvSceneSwitcher.action.twitch.type.channel.info.title.set="Set stream title"
AdvSceneSwitcher.action.twitch.type.channel.info.category.set="Set stream category"
AdvSceneSwitcher.action.twitch.type.channel.info.tags.set="Set stream tags"
AdvSceneSwitcher.action.twitch.type.channel.info.language.set="Set stream language"
AdvSceneSwitcher.action.twitch.type.raid.start="Start raid"
AdvSceneSwitcher.action.twitch.type.commercial.start="Start commercial"
AdvSceneSwitcher.action.twitch.type.marker.create="Create stream marker"
@@ -1216,6 +1260,58 @@ AdvSceneSwitcher.action.twitch.announcement.orange="Orange"
AdvSceneSwitcher.action.twitch.announcement.purple="Purple"
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.id="User Id"
AdvSceneSwitcher.action.twitch.user.getInfo.queryType.login="User login"
AdvSceneSwitcher.action.twitch.tags.add="Add Channel Tag"
AdvSceneSwitcher.action.twitch.tags.getCurrent="Get current channel tags"
AdvSceneSwitcher.action.twitch.tags.invalid="Invalid Tag"
AdvSceneSwitcher.action.twitch.tags.invalid.info="Tags must be 1%1 characters long, contain only letters or numbers, and have no spaces or special characters."
AdvSceneSwitcher.action.twitch.tags.duplicate="Duplicate Tag"
AdvSceneSwitcher.action.twitch.tags.duplicate.info="This tag is already in the list."
AdvSceneSwitcher.action.twitch.tags.limit="Tag Limit Reached"
AdvSceneSwitcher.action.twitch.tags.limit.info="You can only have up to %1 tags."
AdvSceneSwitcher.action.twitch.type.channel.info.contentClassification.set="Set content classification labels"
AdvSceneSwitcher.action.twitch.contentClassification.debatedSocialIssuesAndPolitics="Discussions or debates about politics or sensitive social issues such as elections, civic integrity, military conflict, and civil rights."
AdvSceneSwitcher.action.twitch.contentClassification.drugsIntoxication="Excessive tobacco glorification or promotion, any marijuana consumption/use, legal drug and alcohol induced intoxication, discussions of illegal drugs."
AdvSceneSwitcher.action.twitch.contentClassification.sexualThemes="Content that focuses on sexualized activities, sexual topics, or experiences."
AdvSceneSwitcher.action.twitch.contentClassification.violentGraphic="Simulations and/or depictions of realistic violence, gore, extreme injury, or death."
AdvSceneSwitcher.action.twitch.contentClassification.gambling="Participating in or promoting online or in-person gambling, poker, or fantasy sports that involve the exchange of real money."
AdvSceneSwitcher.action.twitch.contentClassification.profanityVulgarity="Prolonged and repeated use of obscenities, profanities, and vulgarities, especially as a regular part of speech."
AdvSceneSwitcher.action.twitch.contentClassification.getCurrent="Get current content classification labels"
AdvSceneSwitcher.action.twitch.language.english="English"
AdvSceneSwitcher.action.twitch.language.indonesian="Bahasa Indonesia"
AdvSceneSwitcher.action.twitch.language.catalan="Català"
AdvSceneSwitcher.action.twitch.language.danish="Dansk"
AdvSceneSwitcher.action.twitch.language.german="Deutsch"
AdvSceneSwitcher.action.twitch.language.spanish="Español"
AdvSceneSwitcher.action.twitch.language.french="Français"
AdvSceneSwitcher.action.twitch.language.italian="Italiano"
AdvSceneSwitcher.action.twitch.language.hungarian="Magyar"
AdvSceneSwitcher.action.twitch.language.dutch="Nederlands"
AdvSceneSwitcher.action.twitch.language.norwegian="Norsk"
AdvSceneSwitcher.action.twitch.language.polish="Polski"
AdvSceneSwitcher.action.twitch.language.portuguese="Português"
AdvSceneSwitcher.action.twitch.language.romanian="Română"
AdvSceneSwitcher.action.twitch.language.slovak="Slovenčina"
AdvSceneSwitcher.action.twitch.language.finnish="Suomi"
AdvSceneSwitcher.action.twitch.language.swedish="Svenska"
AdvSceneSwitcher.action.twitch.language.tagalog="Tagalog"
AdvSceneSwitcher.action.twitch.language.vietnamese="Tiếng Việt"
AdvSceneSwitcher.action.twitch.language.turkish="Türkçe"
AdvSceneSwitcher.action.twitch.language.czech="Čeština"
AdvSceneSwitcher.action.twitch.language.greek="Ελληνικά"
AdvSceneSwitcher.action.twitch.language.bulgarian="Български"
AdvSceneSwitcher.action.twitch.language.russian="Русский"
AdvSceneSwitcher.action.twitch.language.ukrainian="Українська"
AdvSceneSwitcher.action.twitch.language.arabic="العربية"
AdvSceneSwitcher.action.twitch.language.malay="بهاس ملايو"
AdvSceneSwitcher.action.twitch.language.hindi="मानक हिन्दी"
AdvSceneSwitcher.action.twitch.language.thai="ภาษาไทย"
AdvSceneSwitcher.action.twitch.language.chinese="中文"
AdvSceneSwitcher.action.twitch.language.japanese="日本語"
AdvSceneSwitcher.action.twitch.language.cantonese="粵語"
AdvSceneSwitcher.action.twitch.language.korean="한국어"
AdvSceneSwitcher.action.twitch.language.asl="American Sign Language"
AdvSceneSwitcher.action.twitch.language.other="Other"
AdvSceneSwitcher.action.twitch.language.getCurrent="Get current language"
AdvSceneSwitcher.action.clipboard="Clipboard"
AdvSceneSwitcher.action.clipboard.type.copy.text="Copy text"
AdvSceneSwitcher.action.clipboard.type.copy.image="Copy image"
@@ -1255,6 +1351,9 @@ AdvSceneSwitcher.action.obsSetting.action.setBaseCanvasX="Set base resolution X
AdvSceneSwitcher.action.obsSetting.action.setBaseCanvasY="Set base resolution Y value"
AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasX="Set output resolution X value"
AdvSceneSwitcher.action.obsSetting.action.setOutputCanvasY="Set output resolution Y value"
AdvSceneSwitcher.action.obsSetting.action.enablePreview="Enable preview"
AdvSceneSwitcher.action.obsSetting.action.disablePreview="Disable preview"
AdvSceneSwitcher.action.obsSetting.action.togglePreview="Toggle preview enable"
AdvSceneSwitcher.action.obsSetting.getCurrentValue="Get current value"
AdvSceneSwitcher.action.obsSettings.layout="{{actions}}{{fpsType}}{{fpsIntValue}}{{fpsStringValue}}{{canvasSizeValue}}{{getCurrentValue}}"
AdvSceneSwitcher.action.script="Script"
@@ -1425,6 +1524,13 @@ AdvSceneSwitcher.mqttConnection.name="Name:"
AdvSceneSwitcher.mqttConnection.address="Address:"
AdvSceneSwitcher.mqttConnection.username="Username:"
AdvSceneSwitcher.mqttConnection.password="Password:"
AdvSceneSwitcher.mqttConnection.trustStore="Trust store:"
AdvSceneSwitcher.mqttConnection.trustStore.help="The file in PEM format containing the public digital certificates trusted by the client."
AdvSceneSwitcher.mqttConnection.keyStore="Key store:"
AdvSceneSwitcher.mqttConnection.keyStore.help="The file in PEM format containing the public certificate chain of the client.\nIt may also include the client's private key."
AdvSceneSwitcher.mqttConnection.privateKey="Private key:"
AdvSceneSwitcher.mqttConnection.privateKey.help="If not included in the key store, this is the file in PEM format containing the client's private key."
AdvSceneSwitcher.mqttConnection.verifyServerCert="Verify server certificate"
AdvSceneSwitcher.mqttConnection.reconnect="Reconnect automatically:"
AdvSceneSwitcher.mqttConnection.reconnectDelay="Automatically reconnect after:"
AdvSceneSwitcher.mqttConnection.connectOnStart="Connect on startup:"
@@ -1470,6 +1576,7 @@ AdvSceneSwitcher.twitchToken.bits.read="Read channel's Bits information."
AdvSceneSwitcher.twitchToken.channel.raids.manage="Manage channel's raids."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.read="Read channel's shoutouts."
AdvSceneSwitcher.twitchToken.moderator.shoutouts.manage="Manage channel's shoutouts."
AdvSceneSwitcher.twitchToken.channel.ads.read="Read the ads schedule and details on your channel."
AdvSceneSwitcher.twitchToken.channel.polls.read="Read channel's polls' state."
AdvSceneSwitcher.twitchToken.channel.polls.manage="Manage channel's polls."
AdvSceneSwitcher.twitchToken.channel.predictions.read="Read channel's predictions' state."
@@ -1531,6 +1638,9 @@ AdvSceneSwitcher.script.language.select="--select language--"
AdvSceneSwitcher.script.language.layout="Script language:{{language}}"
AdvSceneSwitcher.script.file.open="Open"
AdvSceneSwitcher.script.file.open.failed="Could not open script file!"
AdvSceneSwitcher.script.file.warning.notFound="Warning: The given file path was not found!"
AdvSceneSwitcher.script.file.warning.openFail="Warning: Could not open the script file!"
AdvSceneSwitcher.script.file.warning.loadMissing="Warning: The script is missing a 'script_load' function!"
AdvSceneSwitcher.script.file.layout="Script file:{{path}}{{open}}"
AdvSceneSwitcher.tempVar.select="--select value--"
@@ -1982,6 +2092,18 @@ AdvSceneSwitcher.tempVar.twitch.offline_image_url.user.getInfo="Offline image UR
AdvSceneSwitcher.tempVar.twitch.created_at.user.getInfo="Account creation date"
AdvSceneSwitcher.tempVar.twitch.created_at.user.getInfo.description="The UTC date and time that the user's account was created. The timestamp is in RFC3339 format."
AdvSceneSwitcher.tempVar.twitch.duration_seconds.commercial="Duration"
AdvSceneSwitcher.tempVar.twitch.duration_seconds.commercial.description="Length in seconds of the mid-roll ad break requested."
AdvSceneSwitcher.tempVar.twitch.started_at.commercial="Started at"
AdvSceneSwitcher.tempVar.twitch.started_at.commercial.description="The UTC timestamp of when the ad break began, in RFC3339 format. Note that there is potential delay between this event, when the streamer requested the ad break, and when the viewers will see ads."
AdvSceneSwitcher.tempVar.twitch.is_automatic.commercial="Was automatic"
AdvSceneSwitcher.tempVar.twitch.is_automatic.commercial.description="Indicates if the ad was automatically scheduled via Ads Manager"
AdvSceneSwitcher.tempVar.twitch.requester_user_id.commercial="Requester user ID"
AdvSceneSwitcher.tempVar.twitch.requester_user_id.commercial.description="The ID of the user that requested the ad. For automatic ads, this will be the ID of the broadcaster."
AdvSceneSwitcher.tempVar.twitch.requester_user_login.commercial="Requester user login"
AdvSceneSwitcher.tempVar.twitch.requester_user_login.commercial.description="The login of the user that requested the ad."
AdvSceneSwitcher.tempVar.twitch.requester_user_name.commercial="Requester user name"
AdvSceneSwitcher.tempVar.twitch.requester_user_name.commercial.description="The display name of the user that requested the ad."
AdvSceneSwitcher.tempVar.audio.output_volume="Output volume"
AdvSceneSwitcher.tempVar.audio.output_volume.description="The volume the audio source is outputting."
@@ -2000,6 +2122,7 @@ AdvSceneSwitcher.tempVar.clipboard.mimeType.all.description="All MIME types of t
AdvSceneSwitcher.tempVar.scene.current="Current scene"
AdvSceneSwitcher.tempVar.scene.previous="Previous scene"
AdvSceneSwitcher.tempVar.scene.preview="Preview scene"
AdvSceneSwitcher.tempVar.scene.position="Position in scenes list"
AdvSceneSwitcher.tempVar.window.window="Window title"
AdvSceneSwitcher.tempVar.window.window.description="The window title of the matching window."
@@ -2083,6 +2206,19 @@ AdvSceneSwitcher.tempVar.clipboard.text.description="The text contained in the c
AdvSceneSwitcher.tempVar.file.content="File content"
AdvSceneSwitcher.tempVar.file.date="File modification date"
AdvSceneSwitcher.tempVar.file.basename="File basename"
AdvSceneSwitcher.tempVar.file.basename.description="Returns the base name of the file without the path.\nThe base name consists of all characters in the file up to (but not including) the first '.' character."
AdvSceneSwitcher.tempVar.file.basenameComplete="File basename (complete)"
AdvSceneSwitcher.tempVar.file.basenameComplete.description="Returns the complete base name of the file without the path.\nThe complete base name consists of all characters in the file up to (but not including) the last '.' character."
AdvSceneSwitcher.tempVar.file.suffix="File suffix"
AdvSceneSwitcher.tempVar.file.suffix.description="Returns the suffix (extension) of the file.\nThe suffix consists of all characters in the file after (but not including) the last '.'."
AdvSceneSwitcher.tempVar.file.suffixComplete="File suffix (complete)"
AdvSceneSwitcher.tempVar.file.suffixComplete.description="Returns the complete suffix (extension) of the file.\nThe complete suffix consists of all characters in the file after (but not including) the first '.'."
AdvSceneSwitcher.tempVar.file.filename="File name"
AdvSceneSwitcher.tempVar.file.absoluteFilePath="Absolute file path"
AdvSceneSwitcher.tempVar.file.absolutePath="Absolute path"
AdvSceneSwitcher.tempVar.file.isAbsolutePath="Path is absolute"
AdvSceneSwitcher.tempVar.folder.newFiles="New files"
AdvSceneSwitcher.tempVar.folder.changedFiles="Changed files"
@@ -2116,6 +2252,8 @@ AdvSceneSwitcher.tempVar.usb.serialNumber="Serial Number"
AdvSceneSwitcher.tempVar.transform.setting="Setting value"
AdvSceneSwitcher.tempVar.transform.settings="Settings string"
AdvSceneSwitcher.tempVar.media.source="Source"
AdvSceneSwitcher.tempVar.media.source.description="The name of the source which matched.\nIf multiple sources matched the last one is returned."
AdvSceneSwitcher.tempVar.media.state="State"
AdvSceneSwitcher.tempVar.media.state.description="The current media state represented as an integer value:\n0 being 'None'\n1 being 'Playing'\n2 being 'Opening'\n3 being 'Buffering'\n4 being 'Paused'\n5 being 'Stopped'\n6 being 'Ended'\n7 being 'Error'"
AdvSceneSwitcher.tempVar.media.time="Current time"
@@ -2181,6 +2319,7 @@ AdvSceneSwitcher.tempVar.cursor.x="Cursor position (X)"
AdvSceneSwitcher.tempVar.cursor.y="Cursor position (Y)"
AdvSceneSwitcher.selectScene="--select scene--"
AdvSceneSwitcher.selectCanvas="--select canvas--"
AdvSceneSwitcher.selectPreviousScene="Previous Scene"
AdvSceneSwitcher.selectCurrentScene="Current Scene"
AdvSceneSwitcher.selectPreviewScene="Preview Scene"
@@ -2241,21 +2380,23 @@ AdvSceneSwitcher.setting.transform.width="Width"
AdvSceneSwitcher.sceneItemSelection.configure="Configure scene item selection type"
AdvSceneSwitcher.sceneItemSelection.type.sourceName="Source name"
AdvSceneSwitcher.sceneItemSelection.type.sourceName.entry="{{nameConflictIndex}}{{sourceName}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceName.layout="{{nameConflictIndex}}{{sourceName}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable="Variable name"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable.entry="{{nameConflictIndex}}{{variable}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable.layout="{{nameConflictIndex}}{{variable}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern="Source name matches pattern"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.entry="{{nameConflictIndex}}matching{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.layout="{{nameConflictIndex}}matching{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup="Sources in source group"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup.entry="Sources in{{sourceGroups}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup.layout="{{nameConflictIndex}}Sources in{{sourceGroups}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType="Sources of type"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.entry="Source type{{nameConflictIndex}}{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.layout="Source type{{nameConflictIndex}}{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.index="Source with index"
AdvSceneSwitcher.sceneItemSelection.type.index.entry="{{index}}source"
AdvSceneSwitcher.sceneItemSelection.type.index.layout="{{index}}source"
AdvSceneSwitcher.sceneItemSelection.type.indexRange="Sources in index range"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.entry="Sources from{{index}}to{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.layout="Sources from{{index}}to{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.all="All sources"
AdvSceneSwitcher.sceneItemSelection.type.all.entry="All sources"
AdvSceneSwitcher.sceneItemSelection.type.all.layout="All sources"
AdvSceneSwitcher.sceneItemSelection.type.any="Any source"
AdvSceneSwitcher.sceneItemSelection.type.any.layout="Any source"
AdvSceneSwitcher.sceneItemSelection.all="All"
AdvSceneSwitcher.sceneItemSelection.any="Any"

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Siempre iniciar el sel
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="No iniciar el selector de escenas"
AdvSceneSwitcher.generalTab.status.start="Iniciar"
AdvSceneSwitcher.generalTab.status.stop="Detener"
AdvSceneSwitcher.generalTab.status.autoStart="Iniciar automáticamente el selector de escenas cuando:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Iniciar automáticamente el selector de escenas cuando:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Nunca"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Grabando"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Emitiendo"
@@ -311,7 +311,6 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} esta {{condition}} {{value}}"
; Macro Actions
AdvSceneSwitcher.action.scene="Cambiar escena"
AdvSceneSwitcher.action.scene.entry="Cambiar a la{{sceneTypes}}escena{{scenes}}usando{{transitions}}con una duración de{{duration}} segundos"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Espere hasta que se complete la transición a la escena de destino"
AdvSceneSwitcher.action.wait="Esperar"
AdvSceneSwitcher.action.wait.type.fixed="fijo"
@@ -383,7 +382,7 @@ AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced S
AdvSceneSwitcher.action.pluginState.type.noMatch="Cambiar el comportamiento de no coincidencia:"
AdvSceneSwitcher.action.pluginState.type.import="Importar configuración desde"
AdvSceneSwitcher.action.pluginState.importWarning="Nota: la acción se ignorará mientras se abra la ventana de configuración."
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}"
AdvSceneSwitcher.action.virtualCamera="Cámara virtual"
AdvSceneSwitcher.action.virtualCamera.type.stop="Detener cámara virtual"
AdvSceneSwitcher.action.virtualCamera.type.start="Iniciar cámara virtual"
@@ -414,7 +413,7 @@ AdvSceneSwitcher.action.file.type.write="Escribir"
AdvSceneSwitcher.action.file.type.append="Agregar"
AdvSceneSwitcher.action.file.entry="{{actions}} a {{filePath}}:"
AdvSceneSwitcher.action.studioMode="Modo estudio"
AdvSceneSwitcher.action.studioMode.type.swap="Intercambiar vista previa y escena del programa"
AdvSceneSwitcher.action.studioMode.type.transitionWithSwap="Intercambiar vista previa y escena del programa"
AdvSceneSwitcher.action.studioMode.type.setScene="Establecer escena de vista previa en"
AdvSceneSwitcher.action.studioMode.type.enable="Activar modo estudio"
AdvSceneSwitcher.action.studioMode.type.disable="Deshabilitar el modo de estudio"

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Toujours démarrer le
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Ne pas démarrer le sélecteur de scène"
AdvSceneSwitcher.generalTab.status.start="Démarrer"
AdvSceneSwitcher.generalTab.status.stop="Arrêter"
AdvSceneSwitcher.generalTab.status.autoStart="Démarrer automatiquement le sélecteur de scène lorsque :"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Démarrer automatiquement le sélecteur de scène lorsque :"
AdvSceneSwitcher.generalTab.status.autoStart.never="Jamais"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Enregistrement"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Diffusion en continu"
@@ -459,8 +459,6 @@ AdvSceneSwitcher.condition.slideshow.updateInterval.tooltip="Les informations su
AdvSceneSwitcher.action.scene="Changer de scène"
AdvSceneSwitcher.action.scene.type.program="Programme"
AdvSceneSwitcher.action.scene.type.preview="Aperçu"
AdvSceneSwitcher.action.scene.entry="Changer la scène{{sceneTypes}}{{scenes}}en utilisant{{transitions}}avec une durée de{{duration}}secondes"
AdvSceneSwitcher.action.scene.entry.noDuration="Changer la scène{{sceneTypes}}{{scenes}}en utilisant{{transitions}}"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Attendre que la transition vers la scène cible soit terminée"
AdvSceneSwitcher.action.wait="Attendre"
AdvSceneSwitcher.action.wait.type.fixed="fixe"
@@ -615,7 +613,7 @@ AdvSceneSwitcher.action.file.type.write="Écrire"
AdvSceneSwitcher.action.file.type.append="Ajouter"
AdvSceneSwitcher.action.file.entry="{{actions}}vers{{filePath}}:"
AdvSceneSwitcher.action.studioMode="Mode studio"
AdvSceneSwitcher.action.studioMode.type.swap="Permuter la scène de prévisualisation et la scène du programme"
AdvSceneSwitcher.action.studioMode.type.transitionWithSwap="Permuter la scène de prévisualisation et la scène du programme"
AdvSceneSwitcher.action.studioMode.type.setScene="Définir la scène de prévisualisation sur"
AdvSceneSwitcher.action.studioMode.type.enable="Activer le mode studio"
AdvSceneSwitcher.action.studioMode.type.disable="Désactiver le mode studio"
@@ -1107,15 +1105,15 @@ AdvSceneSwitcher.sceneItemSelection.configure="Configurer le type de sélection
AdvSceneSwitcher.sceneItemSelection.type.sourceName="Nom de la source"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable="Nom de la variable"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern="Le nom de la source correspond au motif"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.entry="{{nameConflictIndex}}correspondant à{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.layout="{{nameConflictIndex}}correspondant à{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType="Sources de type"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.entry="{{nameConflictIndex}}Type de source{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.layout="{{nameConflictIndex}}Type de source{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.index="Source avec index"
AdvSceneSwitcher.sceneItemSelection.type.index.entry="{{index}}source"
AdvSceneSwitcher.sceneItemSelection.type.index.layout="{{index}}source"
AdvSceneSwitcher.sceneItemSelection.type.indexRange="Sources dans la plage d'index"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.entry="Sources de{{index}}à{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.layout="Sources de{{index}}à{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.all="Toutes les sources"
AdvSceneSwitcher.sceneItemSelection.type.all.entry="Toutes les sources"
AdvSceneSwitcher.sceneItemSelection.type.all.layout="Toutes les sources"
AdvSceneSwitcher.sceneItemSelection.all="Tout"
AdvSceneSwitcher.sceneItemSelection.any="N'importe lequel"

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Sempre iniciar o switc
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Não iniciar o switcher de cenas"
AdvSceneSwitcher.generalTab.status.start="Iniciar"
AdvSceneSwitcher.generalTab.status.stop="Parar"
AdvSceneSwitcher.generalTab.status.autoStart="Iniciar automaticamente o switcher de cenas quando:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Iniciar automaticamente o switcher de cenas quando:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Nunca"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Gravando"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Transmitindo"
@@ -720,9 +720,6 @@ AdvSceneSwitcher.condition.noDevicesFoundWarning="Nenhum dispositivo USB detecta
AdvSceneSwitcher.action.scene="Trocar cena"
AdvSceneSwitcher.action.scene.type.program="Programa"
AdvSceneSwitcher.action.scene.type.preview="Pré-visualização"
AdvSceneSwitcher.action.scene.entry="Trocar{{sceneTypes}}cena para{{scenes}}usando{{transitions}}com duração de{{duration}}segundos"
AdvSceneSwitcher.action.scene.entry.noDuration="Trocar{{sceneTypes}}cena para{{scenes}}usando{{transitions}}"
AdvSceneSwitcher.action.scene.entry.preview="Trocar{{sceneTypes}}cena para{{scenes}}"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Aguardar até que a transição para a cena de destino seja concluída"
AdvSceneSwitcher.action.wait="Aguardar"
AdvSceneSwitcher.action.wait.type.fixed="fixo"
@@ -874,7 +871,7 @@ AdvSceneSwitcher.action.pluginState.type.import="Importar configurações de"
AdvSceneSwitcher.action.pluginState.importWarning="Nota: Ação será ignorada enquanto a janela de configurações estiver aberta."
AdvSceneSwitcher.action.pluginState.type.terminate="Desligar OBS"
AdvSceneSwitcher.action.pluginState.terminateConfirm="O OBS será desligado automaticamente em 10 segundos!\nDeseja continuar com o desligamento do OBS?"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}"
AdvSceneSwitcher.action.virtualCamera="Câmera virtual"
AdvSceneSwitcher.action.virtualCamera.type.stop="Parar câmera virtual"
AdvSceneSwitcher.action.virtualCamera.type.start="Iniciar câmera virtual"
@@ -926,7 +923,7 @@ AdvSceneSwitcher.action.file.type.write="Escrever"
AdvSceneSwitcher.action.file.type.append="Anexar"
AdvSceneSwitcher.action.file.entry="{{actions}}para{{filePath}}:"
AdvSceneSwitcher.action.studioMode="Modo estúdio"
AdvSceneSwitcher.action.studioMode.type.swap="Trocar cena de visualização e programa"
AdvSceneSwitcher.action.studioMode.type.transitionWithSwap="Trocar cena de visualização e programa"
AdvSceneSwitcher.action.studioMode.type.setScene="Definir cena de visualização para"
AdvSceneSwitcher.action.studioMode.type.enable="Habilitar modo estúdio"
AdvSceneSwitcher.action.studioMode.type.disable="Desabilitar modo estúdio"
@@ -1851,19 +1848,19 @@ AdvSceneSwitcher.setting.transform.width="Largura"
AdvSceneSwitcher.sceneItemSelection.configure="Configurar tipo de seleção de item de cena"
AdvSceneSwitcher.sceneItemSelection.type.sourceName="Nome da fonte"
AdvSceneSwitcher.sceneItemSelection.type.sourceName.entry="{{nameConflictIndex}}{{sourceName}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceName.layout="{{nameConflictIndex}}{{sourceName}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable="Nome da variável"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable.entry="{{nameConflictIndex}}{{variable}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable.layout="{{nameConflictIndex}}{{variable}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern="Nome da fonte corresponde ao padrão"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.entry="{{nameConflictIndex}}correspondendo{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.layout="{{nameConflictIndex}}correspondendo{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType="Fontes do tipo"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.entry="Tipo de fonte{{nameConflictIndex}}{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.layout="Tipo de fonte{{nameConflictIndex}}{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.index="Fonte com índice"
AdvSceneSwitcher.sceneItemSelection.type.index.entry="{{index}}fonte"
AdvSceneSwitcher.sceneItemSelection.type.index.layout="{{index}}fonte"
AdvSceneSwitcher.sceneItemSelection.type.indexRange="Fontes no intervalo de índices"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.entry="Fontes de{{index}}para{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.layout="Fontes de{{index}}para{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.all="Todas as fontes"
AdvSceneSwitcher.sceneItemSelection.type.all.entry="Todas as fontes"
AdvSceneSwitcher.sceneItemSelection.type.all.layout="Todas as fontes"
AdvSceneSwitcher.sceneItemSelection.all="Todas"
AdvSceneSwitcher.sceneItemSelection.any="Qualquer"

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Всегда запу
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Не запускать переключатель сцен"
AdvSceneSwitcher.generalTab.status.start="Старт"
AdvSceneSwitcher.generalTab.status.stop="Стоп"
AdvSceneSwitcher.generalTab.status.autoStart="Автоматически запускать переключатель сцен, когда:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Автоматически запускать переключатель сцен, когда:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Никогда"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Запись"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Вещание"
@@ -114,7 +114,6 @@ AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
; Macro Actions
AdvSceneSwitcher.action.scene="Переключить сцену"
AdvSceneSwitcher.action.scene.entry="Перейти к сцене{{sceneTypes}}{{scenes}}используя{{transitions}}с продолжительностью{{duration}}секунд"
AdvSceneSwitcher.action.wait="Подождать"
AdvSceneSwitcher.action.wait.type.fixed="фиксированный"
AdvSceneSwitcher.action.wait.type.random="случайный"

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Her zaman sahne deği
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="Sahne değiştiriciyi başlatma"
AdvSceneSwitcher.generalTab.status.start="Başlat"
AdvSceneSwitcher.generalTab.status.stop="Durdur"
AdvSceneSwitcher.generalTab.status.autoStart="Aşağıdaki durumlarda sahne değiştiriciyi otomatik olarak başlatın:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="Aşağıdaki durumlarda sahne değiştiriciyi otomatik olarak başlatın:"
AdvSceneSwitcher.generalTab.status.autoStart.never="Asla"
AdvSceneSwitcher.generalTab.status.autoStart.recording="Kayıt"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Yayın"
@@ -249,7 +249,6 @@ AdvSceneSwitcher.condition.openvr.entry.line3="HMD mevcut {{xPos}} x {{yPos}} x
; Macro Actions
AdvSceneSwitcher.action.scene="Sahne Degistirici"
AdvSceneSwitcher.action.scene.entry="Sahneyi{{sceneTypes}}{{scenes}}kullanarak{{transitions}}süresi olan{{duration}}saniye"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="Hedef sahneye geçiş tamamlanana kadar bekleyin"
AdvSceneSwitcher.action.wait="Bekle"
AdvSceneSwitcher.action.wait.type.fixed="sabit"
@@ -312,7 +311,7 @@ AdvSceneSwitcher.action.pluginState.type.stop="Advanced Scene Switcher eklentisi
AdvSceneSwitcher.action.pluginState.type.noMatch="Eşleşmeme davranışını değiştirin:"
AdvSceneSwitcher.action.pluginState.type.import="Ayarları şuradan içe aktar:"
AdvSceneSwitcher.action.pluginState.importWarning="Not: Ayarlar penceresi açılırken eylem göz ardı edilecektir."
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}"
AdvSceneSwitcher.action.virtualCamera="Sanal Kamera"
AdvSceneSwitcher.action.virtualCamera.type.stop="Sanal Kamerayı Durdur"
AdvSceneSwitcher.action.virtualCamera.type.start="Sanal Kamerayı Başlat"

View File

@@ -12,7 +12,7 @@ AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="总是自动启动"
AdvSceneSwitcher.generalTab.status.onStartup.doNotStart="不要启动"
AdvSceneSwitcher.generalTab.status.start="启动"
AdvSceneSwitcher.generalTab.status.stop="停止"
AdvSceneSwitcher.generalTab.status.autoStart="在以下情况下自动启动场景切换器:"
AdvSceneSwitcher.generalTab.status.autoStart.startup="在以下情况下自动启动场景切换器:"
AdvSceneSwitcher.generalTab.status.autoStart.never="从不"
AdvSceneSwitcher.generalTab.status.autoStart.recording="录制"
AdvSceneSwitcher.generalTab.status.autoStart.streaming="直播"
@@ -698,7 +698,7 @@ AdvSceneSwitcher.condition.twitch.type.event.channel.user.moderator.deletion="
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online.live="开始直播"
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online.playlist="开始播放列表"
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online.watchParty="开始观看派对"
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online.premiere="开始首播
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online.premiere="开始首播"
AdvSceneSwitcher.condition.twitch.type.event.channel.stream.online.rerun="开始重播"
AdvSceneSwitcher.condition.twitch.type.polling.channel.live="当前正在直播"
AdvSceneSwitcher.condition.twitch.type.polling.channel.title="当前为头衔赛"
@@ -779,9 +779,6 @@ AdvSceneSwitcher.action.unknown="未知操作"
AdvSceneSwitcher.action.scene="切换场景"
AdvSceneSwitcher.action.scene.type.program="程序"
AdvSceneSwitcher.action.scene.type.preview="预览"
AdvSceneSwitcher.action.scene.entry="切换到场景{{sceneTypes}}{{scenes}}使用{{transitions}}时长{{duration}}秒"
AdvSceneSwitcher.action.scene.entry.noDuration="切换到场景{{sceneTypes}}{{scenes}}使用{{transitions}}"
AdvSceneSwitcher.action.scene.entry.preview="将{{sceneTypes}}场景切换到{{scenes}}"
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="等待转场到目标场景的动画完成"
AdvSceneSwitcher.action.wait="等待"
AdvSceneSwitcher.action.wait.type.fixed="固定"
@@ -933,7 +930,7 @@ AdvSceneSwitcher.action.pluginState.type.import="导入设置"
AdvSceneSwitcher.action.pluginState.importWarning="注意:当设置窗口打开时,操作将被忽略."
AdvSceneSwitcher.action.pluginState.type.terminate="关闭OBS"
AdvSceneSwitcher.action.pluginState.terminateConfirm="OBS将在10秒后自动关闭\n你想继续关闭OBS吗"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}"
AdvSceneSwitcher.action.virtualCamera="虚拟摄像机"
AdvSceneSwitcher.action.virtualCamera.type.stop="停止虚拟摄像机"
AdvSceneSwitcher.action.virtualCamera.type.start="启动虚拟摄像机"
@@ -987,7 +984,7 @@ AdvSceneSwitcher.action.file.type.write="覆盖写入"
AdvSceneSwitcher.action.file.type.append="追加写入"
AdvSceneSwitcher.action.file.entry="{{actions}} 到 {{filePath}}:"
AdvSceneSwitcher.action.studioMode="工作室模式"
AdvSceneSwitcher.action.studioMode.type.swap="切换预览和程序场景"
AdvSceneSwitcher.action.studioMode.type.transitionWithSwap="切换预览和程序场景"
AdvSceneSwitcher.action.studioMode.type.setScene="将预览场景设置为"
AdvSceneSwitcher.action.studioMode.type.enable="启用工作室模式"
AdvSceneSwitcher.action.studioMode.type.disable="停用工作室模式"
@@ -2095,21 +2092,19 @@ AdvSceneSwitcher.setting.transform.width="宽度"
AdvSceneSwitcher.sceneItemSelection.configure="配置场景项目选择类型"
AdvSceneSwitcher.sceneItemSelection.type.sourceName="源名称"
AdvSceneSwitcher.sceneItemSelection.type.sourceName.entry="{{nameConflictIndex}}{{sourceName}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable="变量名称"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable.entry="{{nameConflictIndex}}{{variable}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern="源名称与匹配相符"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.entry="{{nameConflictIndex}}匹配{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.layout="{{nameConflictIndex}}匹配{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup="源分组中的来源"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup.entry="{{sourceGroups}}中的源"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup.layout="{{nameConflictIndex}}{{sourceGroups}}中的源"
AdvSceneSwitcher.sceneItemSelection.type.sourceType="源类型"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.entry="源类型{{nameConflictIndex}}{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceType.layout="源类型{{nameConflictIndex}}{{sourceTypes}}"
AdvSceneSwitcher.sceneItemSelection.type.index="附有索引的源"
AdvSceneSwitcher.sceneItemSelection.type.index.entry="{{index}}源"
AdvSceneSwitcher.sceneItemSelection.type.index.layout="{{index}}源"
AdvSceneSwitcher.sceneItemSelection.type.indexRange="索引范围内的源"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.entry="从{{index}}到{{indexEnd}}的源"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.layout="从{{index}}到{{indexEnd}}的源"
AdvSceneSwitcher.sceneItemSelection.type.all="所有源"
AdvSceneSwitcher.sceneItemSelection.type.all.entry="所有源"
AdvSceneSwitcher.sceneItemSelection.type.all.layout="所有源"
AdvSceneSwitcher.sceneItemSelection.all="全部"
AdvSceneSwitcher.sceneItemSelection.any="任何"
@@ -2133,9 +2128,9 @@ AdvSceneSwitcher.unit.weeks="周"
AdvSceneSwitcher.unit.months="月"
AdvSceneSwitcher.unit.years="年"
AdvSceneSwitcher.duration.condition.none="没有时间限制"
AdvSceneSwitcher.duration.condition.more="时间于"
AdvSceneSwitcher.duration.condition.more="时间于"
AdvSceneSwitcher.duration.condition.equal="时间等于"
AdvSceneSwitcher.duration.condition.less="时间于"
AdvSceneSwitcher.duration.condition.less="时间于"
AdvSceneSwitcher.duration.condition.within="最后"
AdvSceneSwitcher.time.relative="%1 %2 之前"

2
deps/json vendored

Submodule deps/json updated: bc889afb4c...55f93686c0

2
deps/libusb vendored

View File

@@ -87,6 +87,40 @@
<item row="4" column="1">
<widget class="QComboBox" name="autoStartEvent"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart.startup</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_33">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.checkInterval</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="checkIntervalTooLowWarning">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="startupBehavior"/>
</item>
<item row="1" column="1">
<widget class="QWidget" name="placeholder3" native="true"/>
</item>
<item row="1" column="0">
<widget class="QWidget" name="placeholder" native="true"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_64">
<property name="text">
@@ -94,16 +128,13 @@
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="startupBehavior"/>
<item row="2" column="1">
<widget class="QWidget" name="placeholder2" native="true"/>
</item>
<item row="1" column="0">
<widget class="QWidget" name="placeholder" native="true"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_10">
<item row="6" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.autoStart</string>
<string>AdvSceneSwitcher.generalTab.status.hotkeytips</string>
</property>
</widget>
</item>
@@ -130,36 +161,25 @@
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_13">
<widget class="QCheckBox" name="autoStartSceneEnable">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.hotkeytips</string>
<string>AdvSceneSwitcher.generalTab.status.autoStart.scene</string>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_33">
<item row="5" column="1">
<layout class="QHBoxLayout" name="autoStartSceneLayout">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>AdvSceneSwitcher.generalTab.status.checkInterval</string>
</property>
</widget>
<widget class="advss::SceneSelectionWidget" name="autoStartScene" native="true"/>
</item>
<item>
<widget class="QLabel" name="checkIntervalTooLowWarning">
<property name="text">
<string/>
</property>
</widget>
<widget class="advss::VariableLineEdit" name="autoStartSceneName"/>
</item>
<item>
<widget class="advss::RegexConfigWidget" name="autoStartSceneNameRegex" native="true"/>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QWidget" name="placeholder2" native="true"/>
</item>
<item row="1" column="1">
<widget class="QWidget" name="placeholder3" native="true"/>
</item>
</layout>
</item>
</layout>
@@ -469,16 +489,7 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="noMatchSwitchScene">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<widget class="advss::SceneSelectionWidget" name="noMatchSwitchScene">
</widget>
</item>
</layout>
@@ -790,7 +801,7 @@
</item>
</layout>
</widget>
<widget class="QGroupBox" name="macroEdit">
<widget class="QGroupBox" name="macroEditGroup">
<property name="title">
<string>AdvSceneSwitcher.macroTab.edit</string>
</property>
@@ -896,339 +907,7 @@
</layout>
</item>
<item>
<widget class="QSplitter" name="macroActionConditionSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="macroConditions" native="true">
<layout class="QVBoxLayout" name="macroConditionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="advss::MacroSegmentList" name="conditionsList">
<property name="minimumSize">
<size>
<width>0</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="conditionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="conditionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionTopButton</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionUpButton</string>
</property>
<property name="text">
<string/>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionBottomButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QSplitter" name="macroElseActionSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="macroActions" native="true">
<layout class="QVBoxLayout" name="macroActionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="advss::MacroSegmentList" name="actionsList">
<property name="minimumSize">
<size>
<width>0</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="actionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="actionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionTopButton</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionUpButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionBottomButton</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_14">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="toggleElseActions">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.toggleShowElseSection</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="macroElseActions" native="true">
<layout class="QVBoxLayout" name="macroElseActionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="advss::MacroSegmentList" name="elseActionsList">
<property name="minimumSize">
<size>
<width>0</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="elseActionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="elseActionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionTopButton</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionUpButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionBottomButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</widget>
<widget class="advss::MacroEdit" name="macroEdit" native="true"/>
</item>
</layout>
</widget>
@@ -4440,12 +4119,6 @@
<extends>QListView</extends>
<header>macro-tree.hpp</header>
</customwidget>
<customwidget>
<class>advss::MacroSegmentList</class>
<extends>QScrollArea</extends>
<header>macro-segment-list.hpp</header>
<container>1</container>
</customwidget>
<customwidget>
<class>advss::MacroRunButton</class>
<extends>QPushButton</extends>
@@ -4456,6 +4129,29 @@
<extends>QWidget</extends>
<header>duration-control.hpp</header>
</customwidget>
<customwidget>
<class>advss::MacroEdit</class>
<extends>QWidget</extends>
<header>macro-edit.hpp</header>
<container>1</container>
</customwidget>
<customwidget>
<class>advss::SceneSelectionWidget</class>
<extends>QWidget</extends>
<header>scene-selection.hpp</header>
<container>1</container>
</customwidget>
<customwidget>
<class>advss::VariableLineEdit</class>
<extends>QLineEdit</extends>
<header>variable-line-edit.hpp</header>
</customwidget>
<customwidget>
<class>advss::RegexConfigWidget</class>
<extends>QWidget</extends>
<header>regex-config.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>tabWidget</tabstop>
@@ -4463,6 +4159,10 @@
<tabstop>checkInterval</tabstop>
<tabstop>startupBehavior</tabstop>
<tabstop>autoStartEvent</tabstop>
<tabstop>autoStartSceneEnable</tabstop>
<tabstop>autoStartScene</tabstop>
<tabstop>autoStartSceneName</tabstop>
<tabstop>autoStartSceneNameRegex</tabstop>
<tabstop>logLevel</tabstop>
<tabstop>saveWindowGeo</tabstop>
<tabstop>showTrayNotifications</tabstop>
@@ -4492,28 +4192,7 @@
<tabstop>runMacroInParallel</tabstop>
<tabstop>runMacroOnChange</tabstop>
<tabstop>macroSettings</tabstop>
<tabstop>conditionsList</tabstop>
<tabstop>conditionAdd</tabstop>
<tabstop>conditionRemove</tabstop>
<tabstop>conditionTop</tabstop>
<tabstop>conditionUp</tabstop>
<tabstop>conditionDown</tabstop>
<tabstop>conditionBottom</tabstop>
<tabstop>actionsList</tabstop>
<tabstop>actionAdd</tabstop>
<tabstop>actionRemove</tabstop>
<tabstop>actionTop</tabstop>
<tabstop>actionUp</tabstop>
<tabstop>actionDown</tabstop>
<tabstop>actionBottom</tabstop>
<tabstop>toggleElseActions</tabstop>
<tabstop>elseActionsList</tabstop>
<tabstop>elseActionAdd</tabstop>
<tabstop>elseActionRemove</tabstop>
<tabstop>elseActionTop</tabstop>
<tabstop>elseActionUp</tabstop>
<tabstop>elseActionDown</tabstop>
<tabstop>elseActionBottom</tabstop>
<tabstop>macroEdit</tabstop>
<tabstop>sceneGroups</tabstop>
<tabstop>sceneGroupAdd</tabstop>
<tabstop>sceneGroupRemove</tabstop>

388
forms/macro-edit.ui Normal file
View File

@@ -0,0 +1,388 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MacroEdit</class>
<widget class="QWidget" name="MacroEdit">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QSplitter" name="macroActionConditionSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="macroConditions" native="true">
<layout class="QVBoxLayout" name="macroConditionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="advss::MacroSegmentList" name="conditionsList">
<property name="minimumSize">
<size>
<width>0</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="conditionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="conditionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionTopButton</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionUpButton</string>
</property>
<property name="text">
<string/>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="conditionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.conditionBottomButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QSplitter" name="macroElseActionSplitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QWidget" name="macroActions" native="true">
<layout class="QVBoxLayout" name="macroActionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="advss::MacroSegmentList" name="actionsList">
<property name="minimumSize">
<size>
<width>0</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="actionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="actionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionTopButton</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionUpButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="actionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.actionBottomButton</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_14">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="toggleElseActions">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.toggleShowElseSection</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="macroElseActions" native="true">
<layout class="QVBoxLayout" name="macroElseActionsLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="advss::MacroSegmentList" name="elseActionsList">
<property name="minimumSize">
<size>
<width>0</width>
<height>1</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="elseActionControlsLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item>
<widget class="QToolButton" name="elseActionAdd">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionAddButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">addIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-plus</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionRemove">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionRemoveButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">removeIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-trash</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionTop">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionTopButton</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionUp">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionUpButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">upArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-up</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionDown">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionDownButton</string>
</property>
<property name="themeID" stdset="0">
<string notr="true">downArrowIconSmall</string>
</property>
<property name="class" stdset="0">
<string notr="true">icon-down</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="elseActionBottom">
<property name="toolTip">
<string>AdvSceneSwitcher.macroTab.tooltip.elseActionBottomButton</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>advss::MacroSegmentList</class>
<extends>QScrollArea</extends>
<header>macro-segment-list.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>conditionsList</tabstop>
<tabstop>conditionAdd</tabstop>
<tabstop>conditionRemove</tabstop>
<tabstop>conditionTop</tabstop>
<tabstop>conditionUp</tabstop>
<tabstop>conditionDown</tabstop>
<tabstop>conditionBottom</tabstop>
<tabstop>actionsList</tabstop>
<tabstop>actionAdd</tabstop>
<tabstop>actionRemove</tabstop>
<tabstop>actionTop</tabstop>
<tabstop>actionUp</tabstop>
<tabstop>actionDown</tabstop>
<tabstop>actionBottom</tabstop>
<tabstop>toggleElseActions</tabstop>
<tabstop>elseActionsList</tabstop>
<tabstop>elseActionAdd</tabstop>
<tabstop>elseActionRemove</tabstop>
<tabstop>elseActionTop</tabstop>
<tabstop>elseActionUp</tabstop>
<tabstop>elseActionDown</tabstop>
<tabstop>elseActionBottom</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -146,16 +146,7 @@ void AdvSceneSwitcher::LoadUI()
bool AdvSceneSwitcher::eventFilter(QObject *obj, QEvent *event)
{
auto eventType = event->type();
if (obj == ui->macroElseActions && eventType == QEvent::Resize) {
QResizeEvent *resizeEvent = static_cast<QResizeEvent *>(event);
if (resizeEvent->size().height() == 0) {
SetElseActionsStateToHidden();
return QDialog::eventFilter(obj, event);
}
SetElseActionsStateToVisible();
} else if (eventType == QEvent::KeyPress) {
if (eventType == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
auto pressedKey = keyEvent->key();
@@ -547,6 +538,7 @@ static void handleSceneChange()
}
switcher->checkDefaultSceneTransitions();
switcher->CheckAutoStart();
}
static void setLiveTime()
@@ -561,18 +553,28 @@ static void resetLiveTime()
static void checkAutoStartRecording()
{
if (switcher->obsIsShuttingDown) {
return;
}
if (switcher->autoStartEvent == SwitcherData::AutoStart::RECORDING ||
switcher->autoStartEvent ==
SwitcherData::AutoStart::RECORINDG_OR_STREAMING)
SwitcherData::AutoStart::RECORINDG_OR_STREAMING) {
switcher->Start();
}
}
static void checkAutoStartStreaming()
{
if (switcher->obsIsShuttingDown) {
return;
}
if (switcher->autoStartEvent == SwitcherData::AutoStart::STREAMING ||
switcher->autoStartEvent ==
SwitcherData::AutoStart::RECORINDG_OR_STREAMING)
SwitcherData::AutoStart::RECORINDG_OR_STREAMING) {
switcher->Start();
}
}
static void handleTransitionEnd()
@@ -608,7 +610,7 @@ static void handleSceneCollectionChanging()
AdvSceneSwitcher::window->close();
}
if (!switcher->stop) {
switcher->sceneColletionStop = true;
switcher->sceneCollectionStop = true;
switcher->Stop();
}
}
@@ -744,6 +746,21 @@ void AdvSceneSwitcher::HighlightMacroSettingsButton(bool enable)
highlight = HighlightWidget(ui->macroSettings, Qt::green);
}
void AdvSceneSwitcher::HighlightAction(int idx, QColor color) const
{
ui->macroEdit->HighlightAction(idx, color);
}
void AdvSceneSwitcher::HighlightElseAction(int idx, QColor color) const
{
ui->macroEdit->HighlightElseAction(idx, color);
}
void AdvSceneSwitcher::HighlightCondition(int idx, QColor color) const
{
ui->macroEdit->HighlightCondition(idx, color);
}
void HighlightMacroSettingsButton(bool enable)
{
auto window = GetSettingsWindow();

View File

@@ -45,7 +45,6 @@ protected:
/* --- Begin of general tab section --- */
public:
void SetupGeneralTab();
void UpdateNonMatchingScene(const QString &name);
void SetDeprecationWarnings();
public slots:
@@ -58,7 +57,6 @@ public slots:
void on_startupBehavior_currentIndexChanged(int index);
void on_logLevel_currentIndexChanged(int index);
void on_autoStartEvent_currentIndexChanged(int index);
void on_noMatchSwitchScene_currentTextChanged(const QString &text);
void on_checkInterval_valueChanged(int value);
void on_tabWidget_currentChanged(int index);
void on_exportSettings_clicked();
@@ -79,6 +77,7 @@ public slots:
/* --- Begin of macro tab section --- */
public:
void SetupMacroTab();
bool MacroTabIsInFocus();
bool AddNewMacro(std::shared_ptr<Macro> &res, std::string &name,
std::string format = "");
void RemoveMacro(std::shared_ptr<Macro> &);
@@ -86,7 +85,6 @@ public:
void RenameMacro(std::shared_ptr<Macro> &, const QString &name);
std::shared_ptr<Macro> GetSelectedMacro() const;
std::vector<std::shared_ptr<Macro>> GetSelectedMacros() const;
void SetEditMacro(Macro &m);
void SetMacroEditAreaDisabled(bool) const;
void HighlightAction(int idx, QColor color = QColor(Qt::green)) const;
void HighlightElseAction(int idx,
@@ -111,106 +109,17 @@ public slots:
void on_macroName_editingFinished();
void on_runMacroInParallel_stateChanged(int value) const;
void on_runMacroOnChange_stateChanged(int value) const;
void on_conditionAdd_clicked();
void on_conditionRemove_clicked();
void on_conditionTop_clicked();
void on_conditionUp_clicked();
void on_conditionDown_clicked();
void on_conditionBottom_clicked();
void on_actionAdd_clicked();
void on_actionRemove_clicked();
void on_actionTop_clicked();
void on_actionUp_clicked();
void on_actionDown_clicked();
void on_actionBottom_clicked();
void on_toggleElseActions_clicked() const;
void on_elseActionAdd_clicked();
void on_elseActionRemove_clicked();
void on_elseActionTop_clicked();
void on_elseActionUp_clicked();
void on_elseActionDown_clicked();
void on_elseActionBottom_clicked();
void MacroSelectionAboutToChange() const;
void MacroSelectionChanged();
void UpMacroSegementHotkey();
void DownMacroSegementHotkey();
void DeleteMacroSegementHotkey();
void ShowMacroContextMenu(const QPoint &);
void ShowMacroActionsContextMenu(const QPoint &);
void ShowMacroElseActionsContextMenu(const QPoint &);
void ShowMacroConditionsContextMenu(const QPoint &);
void CopyMacro();
void RenameSelectedMacro();
void ExportMacros() const;
void ImportMacros();
void ExpandAllActions() const;
void ExpandAllElseActions() const;
void ExpandAllConditions() const;
void CollapseAllActions() const;
void CollapseAllElseActions() const;
void CollapseAllConditions() const;
void MinimizeActions() const;
void MaximizeActions() const;
void MinimizeElseActions() const;
void MaximizeElseActions() const;
void MinimizeConditions() const;
void MaximizeConditions() const;
void SetElseActionsStateToHidden() const;
void SetElseActionsStateToVisible() const;
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(Macro *macro, int idx, const std::string &id,
obs_data_t *data);
void AddMacroAction(int idx);
void RemoveMacroAction(int idx);
void MoveMacroActionUp(int idx);
void MoveMacroActionDown(int idx);
void MacroElseActionSelectionChanged(int idx);
void MacroElseActionReorder(int to, int target);
void AddMacroElseAction(Macro *macro, int idx, const std::string &id,
obs_data_t *data);
void AddMacroElseAction(int idx);
void RemoveMacroElseAction(int idx);
void SwapElseActions(Macro *m, int pos1, int pos2);
void MoveMacroElseActionUp(int idx);
void MoveMacroElseActionDown(int idx);
void MacroConditionSelectionChanged(int idx);
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void AddMacroCondition(Macro *macro, int idx, const std::string &id,
obs_data_t *data, Logic::Type logic);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);
void MoveMacroConditionDown(int idx);
void HighlightControls() const;
void HighlightOnChange() const;
void on_macroSettings_clicked();
void CopyMacroSegment();
void PasteMacroSegment();
signals:
void MacroAdded(const QString &name);
void MacroRemoved(const QString &name);
void MacroRenamed(const QString &oldName, const QString &newName);
void MacroSegmentOrderChanged();
void SegmentTempVarsChanged(MacroSegment *);
void HighlightMacrosChanged(bool value);
void ConnectionAdded(const QString &);
void ConnectionRenamed(const QString &oldName, const QString &newName);
void ConnectionRemoved(const QString &);
private:
enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS };
void SetupMacroSegmentSelection(MacroSection type, int idx);
bool ResolveMacroImportNameConflict(std::shared_ptr<Macro> &);
bool MacroTabIsInFocus();
MacroSection lastInteracted = MacroSection::CONDITIONS;
int currentConditionIdx = -1;
int currentActionIdx = -1;
int currentElseActionIdx = -1;
/* --- End of macro tab section --- */

View File

@@ -3,6 +3,7 @@
#include "filter-combo-box.hpp"
#include "layout-helpers.hpp"
#include "macro.hpp"
#include "macro-settings.hpp"
#include "path-helpers.hpp"
#include "selection-helpers.hpp"
#include "source-helpers.hpp"
@@ -25,15 +26,6 @@ void AdvSceneSwitcher::reject()
close();
}
void AdvSceneSwitcher::UpdateNonMatchingScene(const QString &name)
{
OBSSourceAutoRelease scene =
obs_get_source_by_name(name.toUtf8().constData());
OBSWeakSourceAutoRelease ws = obs_source_get_weak_source(scene);
switcher->nonMatchingScene = ws;
}
void AdvSceneSwitcher::on_noMatchDontSwitch_clicked()
{
if (loading) {
@@ -55,7 +47,6 @@ void AdvSceneSwitcher::on_noMatchSwitch_clicked()
std::lock_guard<std::mutex> lock(switcher->m);
switcher->switchIfNotMatching = NoMatchBehavior::SWITCH;
ui->noMatchSwitchScene->setEnabled(true);
UpdateNonMatchingScene(ui->noMatchSwitchScene->currentText());
ui->randomDisabledWarning->setVisible(true);
}
@@ -131,17 +122,6 @@ void AdvSceneSwitcher::on_autoStartEvent_currentIndexChanged(int index)
switcher->autoStartEvent = static_cast<SwitcherData::AutoStart>(index);
}
void AdvSceneSwitcher::on_noMatchSwitchScene_currentTextChanged(
const QString &text)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
UpdateNonMatchingScene(text);
}
void AdvSceneSwitcher::on_checkInterval_valueChanged(int value)
{
if (loading) {
@@ -163,7 +143,7 @@ void AdvSceneSwitcher::closeEvent(QCloseEvent *)
switcher->windowSize = this->size();
switcher->macroListMacroEditSplitterPosition =
ui->macroListMacroEditSplitter->sizes();
MacroSelectionAboutToChange(); // Trigger saving of splitter states
ui->macroEdit->SetMacro(nullptr); // Trigger saving of splitter states
obs_frontend_save();
}
@@ -451,16 +431,14 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
}
// New post load steps to be declared during load
postLoadSteps.clear();
ClearPostLoadSteps();
// Needs to be loaded before any entries which might rely on scene group
// selections to be available.
loadSceneGroups(obj);
LoadVariables(obj);
for (const auto &func : loadSteps) {
func(obj);
}
RunLoadSteps(obj);
LoadMacros(obj);
LoadGlobalMacroSettings(obj);
@@ -516,18 +494,16 @@ void SwitcherData::SaveSettings(obs_data_t *obj)
SaveUISettings(obj);
SaveVersion(obj, g_GIT_SHA1);
for (const auto &func : saveSteps) {
func(obj);
}
RunSaveSteps(obj);
}
void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
{
obs_data_set_int(obj, "interval", interval);
std::string nonMatchingSceneName = GetWeakSourceName(nonMatchingScene);
obs_data_set_string(obj, "non_matching_scene",
nonMatchingSceneName.c_str());
OBSDataAutoRelease noMatchScene = obs_data_create();
nonMatchingScene.Save(noMatchScene);
obs_data_set_obj(obj, "noMatchScene", noMatchScene);
obs_data_set_int(obj, "switch_if_not_matching",
static_cast<int>(switchIfNotMatching));
noMatchDelay.Save(obj, "noMatchDelay");
@@ -535,13 +511,18 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
cooldown.Save(obj, "cooldown");
obs_data_set_bool(obj, "enableCooldown", enableCooldown);
obs_data_set_bool(obj, "active", sceneColletionStop ? true : !stop);
sceneColletionStop = false;
obs_data_set_bool(obj, "active", sceneCollectionStop ? true : !stop);
sceneCollectionStop = false;
obs_data_set_int(obj, "startup_behavior",
static_cast<int>(startupBehavior));
obs_data_set_int(obj, "autoStartEvent",
static_cast<int>(autoStartEvent));
OBSDataAutoRelease autoStart = obs_data_create();
obs_data_set_int(autoStart, "event", static_cast<int>(autoStartEvent));
obs_data_set_bool(autoStart, "useAutoStartScene", useAutoStartScene);
autoStartScene.Save(autoStart);
autoStartSceneName.Save(autoStart, "name");
autoStartSceneRegex.Save(autoStart);
obs_data_set_obj(obj, "autoStart", autoStart);
SaveLogLevel(obj);
@@ -576,9 +557,14 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
static_cast<int>(NoMatchBehavior::NO_SWITCH));
switchIfNotMatching = static_cast<NoMatchBehavior>(
obs_data_get_int(obj, "switch_if_not_matching"));
std::string nonMatchingSceneName =
obs_data_get_string(obj, "non_matching_scene");
nonMatchingScene = GetWeakSourceByName(nonMatchingSceneName.c_str());
if (obs_data_has_user_value(obj, "noMatchScene")) {
OBSDataAutoRelease noMatchScene =
obs_data_get_obj(obj, "noMatchScene");
nonMatchingScene.Load(noMatchScene);
} else {
nonMatchingScene.Load(obj, "non_matching_scene");
}
noMatchDelay.Load(obj, "noMatchDelay");
cooldown.Load(obj, "cooldown");
@@ -599,8 +585,15 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
stop = true;
}
autoStartEvent =
static_cast<AutoStart>(obs_data_get_int(obj, "autoStartEvent"));
OBSDataAutoRelease autoStart = obs_data_get_obj(obj, "autoStart");
autoStartEvent = static_cast<AutoStart>(
obs_data_has_user_value(obj, "autoStart")
? obs_data_get_int(autoStart, "event")
: obs_data_get_int(obj, "autoStartEvent"));
useAutoStartScene = obs_data_get_bool(autoStart, "useAutoStartScene");
autoStartScene.Load(autoStart);
autoStartSceneName.Load(autoStart, "name");
autoStartSceneRegex.Load(autoStart);
LoadLogLevel(obj);
@@ -680,10 +673,10 @@ void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
return;
}
if (switchIfNotMatching == NoMatchBehavior::SWITCH &&
nonMatchingScene) {
auto noMatchScene = nonMatchingScene.GetScene(false);
if (switchIfNotMatching == NoMatchBehavior::SWITCH && noMatchScene) {
match = true;
scene = nonMatchingScene;
scene = noMatchScene;
transition = nullptr;
}
if (switchIfNotMatching == NoMatchBehavior::RANDOM_SWITCH) {
@@ -691,6 +684,27 @@ void SwitcherData::CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
}
}
void SwitcherData::CheckAutoStart()
{
if (!useAutoStartScene) {
return;
}
bool shouldStartPlugin = false;
if (autoStartSceneRegex.Enabled()) {
const auto currentSceneName = GetWeakSourceName(currentScene);
shouldStartPlugin = autoStartSceneRegex.Matches(
currentSceneName, autoStartSceneName);
} else {
shouldStartPlugin = autoStartScene.GetScene(false) ==
currentScene;
}
if (shouldStartPlugin) {
Start();
}
}
void SwitcherData::checkSwitchCooldown(bool &match)
{
if (!match || !enableCooldown) {
@@ -833,7 +847,7 @@ static void setupGeneralTabInactiveWarning(QTabWidget *tabs)
inactiveTimer->start();
}
void advss::AdvSceneSwitcher::SetCheckIntervalTooLowVisibility() const
void AdvSceneSwitcher::SetCheckIntervalTooLowVisibility() const
{
auto macro = GetMacroWithInvalidConditionInterval();
if (!macro) {
@@ -857,8 +871,6 @@ void advss::AdvSceneSwitcher::SetCheckIntervalTooLowVisibility() const
void AdvSceneSwitcher::SetupGeneralTab()
{
PopulateSceneSelection(ui->noMatchSwitchScene, false);
if (switcher->switchIfNotMatching == NoMatchBehavior::SWITCH) {
ui->noMatchSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(true);
@@ -870,8 +882,17 @@ void AdvSceneSwitcher::SetupGeneralTab()
ui->noMatchRandomSwitch->setChecked(true);
ui->noMatchSwitchScene->setEnabled(false);
}
ui->noMatchSwitchScene->setCurrentText(
GetWeakSourceName(switcher->nonMatchingScene).c_str());
ui->noMatchSwitchScene->SetScene(switcher->nonMatchingScene);
ui->noMatchSwitchScene->LockToMainCanvas();
connect(ui->noMatchSwitchScene, &SceneSelectionWidget::SceneChanged,
this, [this](const SceneSelection &scene) {
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->nonMatchingScene = scene;
});
DurationSelection *noMatchDelay = new DurationSelection();
noMatchDelay->SetDuration(switcher->noMatchDelay);
@@ -922,6 +943,75 @@ void AdvSceneSwitcher::SetupGeneralTab()
populateAutoStartEventSelection(ui->autoStartEvent);
ui->autoStartEvent->setCurrentIndex(
static_cast<int>(switcher->autoStartEvent));
ui->autoStartSceneEnable->setChecked(switcher->useAutoStartScene);
ui->autoStartScene->SetScene(switcher->autoStartScene);
ui->autoStartScene->LockToMainCanvas();
ui->autoStartSceneName->setText(switcher->autoStartSceneName);
ui->autoStartSceneNameRegex->SetRegexConfig(
switcher->autoStartSceneRegex);
const auto setupAutoStartSceneLayoutVisibility = [this](bool useRegex) {
ui->autoStartSceneName->setVisible(useRegex);
ui->autoStartScene->setVisible(!useRegex);
if (useRegex) {
RemoveStretchIfPresent(ui->autoStartSceneLayout);
} else {
AddStretchIfNecessary(ui->autoStartSceneLayout);
}
};
setupAutoStartSceneLayoutVisibility(
switcher->autoStartSceneRegex.Enabled());
const auto setupAutoStartSceneWidgetState =
[this](bool useAutoStartScene) {
ui->autoStartScene->setEnabled(useAutoStartScene);
ui->autoStartSceneName->setEnabled(useAutoStartScene);
ui->autoStartSceneNameRegex->setEnabled(
useAutoStartScene);
};
setupAutoStartSceneWidgetState(switcher->useAutoStartScene);
connect(ui->autoStartSceneEnable, &QCheckBox::stateChanged, this,
[this, setupAutoStartSceneWidgetState](int enabled) {
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->useAutoStartScene = enabled;
setupAutoStartSceneWidgetState(enabled);
});
connect(ui->autoStartScene, &SceneSelectionWidget::SceneChanged, this,
[this](const SceneSelection &scene) {
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->autoStartScene = scene;
switcher->CheckAutoStart();
});
connect(ui->autoStartSceneName, &VariableLineEdit::editingFinished,
this, [this]() {
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->autoStartSceneName =
ui->autoStartSceneName->text().toStdString();
switcher->CheckAutoStart();
});
connect(ui->autoStartSceneNameRegex,
&RegexConfigWidget::RegexConfigChanged, this,
[this, setupAutoStartSceneLayoutVisibility](
const RegexConfig &regex) {
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->autoStartSceneRegex = regex;
setupAutoStartSceneLayoutVisibility(regex.Enabled());
switcher->CheckAutoStart();
});
// Set up status control
auto statusControl = new StatusControl(this, true);

View File

@@ -158,473 +158,4 @@ std::shared_ptr<MacroSegment> MacroActionEdit::Data() const
return *_entryData;
}
void AdvSceneSwitcher::AddMacroAction(Macro *macro, int idx,
const std::string &id, obs_data_t *data)
{
if (idx < 0 || idx > (int)macro->Actions().size()) {
assert(false);
return;
}
{
auto lock = LockContext();
macro->Actions().emplace(macro->Actions().begin() + idx,
MacroActionFactory::Create(id, macro));
if (data) {
macro->Actions().at(idx)->Load(data);
}
macro->Actions().at(idx)->PostLoad();
RunPostLoadSteps();
macro->UpdateActionIndices();
ui->actionsList->Insert(
idx,
new MacroActionEdit(this, &macro->Actions()[idx], id));
SetActionData(*macro);
}
HighlightAction(idx);
ui->actionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::AddMacroAction(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->Actions().size()) {
assert(false);
return;
}
std::string id;
if (idx - 1 >= 0) {
id = macro->Actions().at(idx - 1)->GetId();
} else {
id = MacroAction::GetDefaultID();
}
OBSDataAutoRelease data;
if (idx - 1 >= 0) {
data = obs_data_create();
macro->Actions().at(idx - 1)->Save(data);
}
AddMacroAction(macro.get(), idx, id, data);
}
void AdvSceneSwitcher::on_actionAdd_clicked()
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (currentActionIdx == -1) {
AddMacroAction((int)macro->Actions().size());
} else {
AddMacroAction(currentActionIdx + 1);
}
if (currentActionIdx != -1) {
MacroActionSelectionChanged(currentActionIdx + 1);
}
}
void AdvSceneSwitcher::RemoveMacroAction(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx >= (int)macro->Actions().size()) {
return;
}
{
auto lock = LockContext();
ui->actionsList->Remove(idx);
macro->Actions().erase(macro->Actions().begin() + idx);
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
macro->UpdateActionIndices();
SetActionData(*macro);
}
MacroActionSelectionChanged(-1);
lastInteracted = MacroSection::ACTIONS;
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::on_actionRemove_clicked()
{
if (currentActionIdx == -1) {
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
RemoveMacroAction((int)macro->Actions().size() - 1);
} else {
RemoveMacroAction(currentActionIdx);
}
MacroActionSelectionChanged(-1);
}
void AdvSceneSwitcher::on_actionTop_clicked()
{
if (currentActionIdx == -1) {
return;
}
MacroActionReorder(0, currentActionIdx);
MacroActionSelectionChanged(0);
}
void AdvSceneSwitcher::on_actionUp_clicked()
{
if (currentActionIdx == -1 || currentActionIdx == 0) {
return;
}
MoveMacroActionUp(currentActionIdx);
MacroActionSelectionChanged(currentActionIdx - 1);
}
void AdvSceneSwitcher::on_actionDown_clicked()
{
if (currentActionIdx == -1 ||
currentActionIdx == ui->actionsList->ContentLayout()->count() - 1) {
return;
}
MoveMacroActionDown(currentActionIdx);
MacroActionSelectionChanged(currentActionIdx + 1);
}
void AdvSceneSwitcher::on_actionBottom_clicked()
{
if (currentActionIdx == -1) {
return;
}
const int newIdx = ui->actionsList->ContentLayout()->count() - 1;
MacroActionReorder(newIdx, currentActionIdx);
MacroActionSelectionChanged(newIdx);
}
void AdvSceneSwitcher::on_elseActionAdd_clicked()
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (currentElseActionIdx == -1) {
AddMacroElseAction((int)macro->ElseActions().size());
} else {
AddMacroElseAction(currentElseActionIdx + 1);
}
if (currentElseActionIdx != -1) {
MacroElseActionSelectionChanged(currentElseActionIdx + 1);
}
}
void AdvSceneSwitcher::on_elseActionRemove_clicked()
{
if (currentElseActionIdx == -1) {
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
RemoveMacroElseAction((int)macro->ElseActions().size() - 1);
} else {
RemoveMacroElseAction(currentElseActionIdx);
}
MacroElseActionSelectionChanged(-1);
}
void AdvSceneSwitcher::on_elseActionTop_clicked()
{
if (currentElseActionIdx == -1) {
return;
}
MacroElseActionReorder(0, currentElseActionIdx);
MacroElseActionSelectionChanged(0);
}
void AdvSceneSwitcher::on_elseActionUp_clicked()
{
if (currentElseActionIdx == -1 || currentElseActionIdx == 0) {
return;
}
MoveMacroElseActionUp(currentElseActionIdx);
MacroElseActionSelectionChanged(currentElseActionIdx - 1);
}
void AdvSceneSwitcher::on_elseActionDown_clicked()
{
if (currentElseActionIdx == -1 ||
currentElseActionIdx ==
ui->elseActionsList->ContentLayout()->count() - 1) {
return;
}
MoveMacroElseActionDown(currentElseActionIdx);
MacroElseActionSelectionChanged(currentElseActionIdx + 1);
}
void AdvSceneSwitcher::on_elseActionBottom_clicked()
{
if (currentElseActionIdx == -1) {
return;
}
const int newIdx = ui->elseActionsList->ContentLayout()->count() - 1;
MacroElseActionReorder(newIdx, currentElseActionIdx);
MacroElseActionSelectionChanged(newIdx);
}
void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
{
if (pos1 == pos2) {
return;
}
if (pos1 > pos2) {
std::swap(pos1, pos2);
}
auto lock = LockContext();
iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
m->UpdateActionIndices();
auto widget1 = static_cast<MacroActionEdit *>(
ui->actionsList->ContentLayout()->takeAt(pos1)->widget());
auto widget2 = static_cast<MacroActionEdit *>(
ui->actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
ui->actionsList->Insert(pos1, widget2);
ui->actionsList->Insert(pos2, widget1);
SetActionData(*m);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::MoveMacroActionUp(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 1 || idx >= (int)macro->Actions().size()) {
return;
}
SwapActions(macro.get(), idx, idx - 1);
HighlightAction(idx - 1);
}
void AdvSceneSwitcher::MoveMacroActionDown(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx >= (int)macro->Actions().size() - 1) {
return;
}
SwapActions(macro.get(), idx, idx + 1);
HighlightAction(idx + 1);
}
void AdvSceneSwitcher::MacroElseActionSelectionChanged(int idx)
{
SetupMacroSegmentSelection(MacroSection::ELSE_ACTIONS, idx);
}
void AdvSceneSwitcher::MacroElseActionReorder(int to, int from)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (to == from || from < 0 || from > (int)macro->ElseActions().size() ||
to < 0 || to > (int)macro->ElseActions().size()) {
return;
}
{
auto lock = LockContext();
auto action = macro->ElseActions().at(from);
macro->ElseActions().erase(macro->ElseActions().begin() + from);
macro->ElseActions().insert(macro->ElseActions().begin() + to,
action);
macro->UpdateElseActionIndices();
ui->elseActionsList->ContentLayout()->insertItem(
to, ui->elseActionsList->ContentLayout()->takeAt(from));
SetElseActionData(*macro);
}
HighlightElseAction(to);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::AddMacroElseAction(Macro *macro, int idx,
const std::string &id,
obs_data_t *data)
{
if (idx < 0 || idx > (int)macro->ElseActions().size()) {
assert(false);
return;
}
{
auto lock = LockContext();
macro->ElseActions().emplace(macro->ElseActions().begin() + idx,
MacroActionFactory::Create(id,
macro));
if (data) {
macro->ElseActions().at(idx)->Load(data);
}
macro->ElseActions().at(idx)->PostLoad();
RunPostLoadSteps();
macro->UpdateElseActionIndices();
ui->elseActionsList->Insert(
idx, new MacroActionEdit(
this, &macro->ElseActions()[idx], id));
SetElseActionData(*macro);
}
HighlightElseAction(idx);
ui->elseActionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::AddMacroElseAction(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->ElseActions().size()) {
assert(false);
return;
}
std::string id;
if (idx - 1 >= 0) {
id = macro->ElseActions().at(idx - 1)->GetId();
} else {
id = MacroAction::GetDefaultID();
}
OBSDataAutoRelease data;
if (idx - 1 >= 0) {
data = obs_data_create();
macro->ElseActions().at(idx - 1)->Save(data);
}
AddMacroElseAction(macro.get(), idx, id, data);
}
void AdvSceneSwitcher::RemoveMacroElseAction(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx >= (int)macro->ElseActions().size()) {
return;
}
{
auto lock = LockContext();
ui->elseActionsList->Remove(idx);
macro->ElseActions().erase(macro->ElseActions().begin() + idx);
SetMacroAbortWait(true);
GetMacroWaitCV().notify_all();
macro->UpdateElseActionIndices();
SetElseActionData(*macro);
}
MacroElseActionSelectionChanged(-1);
lastInteracted = MacroSection::ELSE_ACTIONS;
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::SwapElseActions(Macro *m, int pos1, int pos2)
{
if (pos1 == pos2) {
return;
}
if (pos1 > pos2) {
std::swap(pos1, pos2);
}
auto lock = LockContext();
iter_swap(m->ElseActions().begin() + pos1,
m->ElseActions().begin() + pos2);
m->UpdateElseActionIndices();
auto widget1 = static_cast<MacroActionEdit *>(
ui->elseActionsList->ContentLayout()->takeAt(pos1)->widget());
auto widget2 = static_cast<MacroActionEdit *>(
ui->elseActionsList->ContentLayout()
->takeAt(pos2 - 1)
->widget());
ui->elseActionsList->Insert(pos1, widget2);
ui->elseActionsList->Insert(pos2, widget1);
SetElseActionData(*m);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::MoveMacroElseActionUp(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 1 || idx >= (int)macro->ElseActions().size()) {
return;
}
SwapElseActions(macro.get(), idx, idx - 1);
HighlightElseAction(idx - 1);
}
void AdvSceneSwitcher::MoveMacroElseActionDown(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx >= (int)macro->ElseActions().size() - 1) {
return;
}
SwapElseActions(macro.get(), idx, idx + 1);
HighlightElseAction(idx + 1);
}
void AdvSceneSwitcher::MacroActionSelectionChanged(int idx)
{
SetupMacroSegmentSelection(MacroSection::ACTIONS, idx);
}
void AdvSceneSwitcher::MacroActionReorder(int to, int from)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (to == from || from < 0 || from > (int)macro->Actions().size() ||
to < 0 || to > (int)macro->Actions().size()) {
return;
}
{
auto lock = LockContext();
auto action = macro->Actions().at(from);
macro->Actions().erase(macro->Actions().begin() + from);
macro->Actions().insert(macro->Actions().begin() + to, action);
macro->UpdateActionIndices();
ui->actionsList->ContentLayout()->insertItem(
to, ui->actionsList->ContentLayout()->takeAt(from));
SetActionData(*macro);
}
HighlightAction(to);
emit(MacroSegmentOrderChanged());
}
} // namespace advss

View File

@@ -1,25 +1,11 @@
#include "macro-action-factory.hpp"
#include "macro-segment-unknown.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroActionUnknown : public MacroAction {
public:
MacroActionUnknown(Macro *m) : MacroAction(m) {}
std::shared_ptr<MacroAction> Copy() const
{
return std::make_shared<MacroActionUnknown>(GetMacro());
}
bool PerformAction() { return true; };
bool Save(obs_data_t *obj) const { return MacroAction::Save(obj); };
bool Load(obs_data_t *obj) { return MacroAction::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
using MacroActionUnknown = MacroSegmentUnknown<MacroAction>;
static std::recursive_mutex mutex;
@@ -49,9 +35,10 @@ bool MacroActionFactory::Deregister(const std::string &id)
return true;
}
static std::shared_ptr<MacroAction> createUnknownAction(Macro *m)
static std::shared_ptr<MacroAction> createUnknownAction(Macro *m,
const std::string &id)
{
return std::make_shared<MacroActionUnknown>(m);
return std::make_shared<MacroActionUnknown>(m, id);
}
std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
@@ -62,12 +49,7 @@ std::shared_ptr<MacroAction> MacroActionFactory::Create(const std::string &id,
return it->second._create(m);
}
return createUnknownAction(m);
}
static QWidget *createUnknownActionWidget()
{
return new QLabel(obs_module_text("AdvSceneSwitcher.action.unknown"));
return createUnknownAction(m, id);
}
QWidget *MacroActionFactory::CreateWidget(const std::string &id,
@@ -79,7 +61,7 @@ QWidget *MacroActionFactory::CreateWidget(const std::string &id,
return it->second._createWidget(parent, action);
}
return createUnknownActionWidget();
return CreateUnknownSegmentWidget(true);
}
std::string MacroActionFactory::GetActionName(const std::string &id)

View File

@@ -14,6 +14,11 @@ bool MacroActionMacro::_registered = MacroActionFactory::Register(
bool MacroActionMacro::PerformAction()
{
if (_action == Action::NESTED_MACRO) {
const bool conditionsMatched = _nestedMacro->CheckConditions();
return _nestedMacro->PerformActions(conditionsMatched);
}
auto macro = _macro.GetMacro();
if (!macro) {
return true;
@@ -32,7 +37,7 @@ bool MacroActionMacro::PerformAction()
case Action::RESET_COUNTER:
macro->ResetRunCount();
break;
case Action::RUN:
case Action::RUN_ACTIONS:
RunActions(macro.get());
break;
case Action::STOP:
@@ -80,7 +85,7 @@ void MacroActionMacro::LogAction() const
ablog(LOG_INFO, "reset counter for \"%s\"",
macro->Name().c_str());
break;
case Action::RUN:
case Action::RUN_ACTIONS:
ablog(LOG_INFO, "run nested macro \"%s\"",
macro->Name().c_str());
break;
@@ -99,6 +104,9 @@ void MacroActionMacro::LogAction() const
ablog(LOG_INFO, "toggled action %d of macro \"%s\"",
_actionIndex.GetValue(), macro->Name().c_str());
break;
case Action::NESTED_MACRO:
ablog(LOG_INFO, "run nested macro");
break;
default:
break;
}
@@ -111,6 +119,10 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
_actionIndex.Save(obj, "actionIndex");
obs_data_set_int(obj, "action", static_cast<int>(_action));
_runOptions.Save(obj);
OBSDataAutoRelease nestedMacroData = obs_data_create();
_nestedMacro->Save(nestedMacroData);
obs_data_set_obj(obj, "nestedMacro", nestedMacroData);
obs_data_set_int(obj, "customWidgetHeight", _customWidgetHeight);
return true;
}
@@ -122,6 +134,15 @@ bool MacroActionMacro::Load(obs_data_t *obj)
_action = static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action"));
_runOptions.Load(obj);
if (obs_data_has_user_value(obj, "nestedMacro")) {
OBSDataAutoRelease nestedMacroData =
obs_data_get_obj(obj, "nestedMacro");
_nestedMacro->Load(nestedMacroData);
}
_customWidgetHeight = obs_data_get_int(obj, "customWidgetHeight");
return true;
}
@@ -130,6 +151,7 @@ bool MacroActionMacro::PostLoad()
MacroRefAction::PostLoad();
MacroAction::PostLoad();
_runOptions.macro.PostLoad();
_nestedMacro->PostLoad();
return true;
}
@@ -145,7 +167,17 @@ std::shared_ptr<MacroAction> MacroActionMacro::Create(Macro *m)
std::shared_ptr<MacroAction> MacroActionMacro::Copy() const
{
return std::make_shared<MacroActionMacro>(*this);
auto copy = std::make_shared<MacroActionMacro>(*this);
// Create a new nested macro
OBSDataAutoRelease data = obs_data_create();
_nestedMacro->Save(data);
copy->_nestedMacro = std::make_shared<Macro>();
copy->_nestedMacro->Load(data);
copy->_nestedMacro->PostLoad();
return copy;
}
void MacroActionMacro::ResolveVariablesToFixedValues()
@@ -204,7 +236,9 @@ static void populateActionSelection(QComboBox *list)
"AdvSceneSwitcher.action.macro.type.togglePause"},
{MacroActionMacro::Action::RESET_COUNTER,
"AdvSceneSwitcher.action.macro.type.resetCounter"},
{MacroActionMacro::Action::RUN,
{MacroActionMacro::Action::NESTED_MACRO,
"AdvSceneSwitcher.action.macro.type.nestedMacro"},
{MacroActionMacro::Action::RUN_ACTIONS,
"AdvSceneSwitcher.action.macro.type.run"},
{MacroActionMacro::Action::STOP,
"AdvSceneSwitcher.action.macro.type.stop"},
@@ -242,7 +276,7 @@ static void populateActionTypeSelection(QComboBox *list)
MacroActionMacroEdit::MacroActionMacroEdit(
QWidget *parent, std::shared_ptr<MacroActionMacro> entryData)
: QWidget(parent),
: ResizableWidget(parent),
_macros(new MacroSelection(parent)),
_actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION)),
@@ -261,7 +295,13 @@ MacroActionMacroEdit::MacroActionMacroEdit(
_entryLayout(new QHBoxLayout()),
_conditionLayout(new QHBoxLayout()),
_reevaluateConditionStateLayout(new QHBoxLayout()),
_setInputsLayout(new QHBoxLayout())
_setInputsLayout(new QHBoxLayout()),
_nestedMacro(new MacroEdit(
this,
QStringList()
<< "AdvSceneSwitcher.action.macro.type.nestedMacro.conditionHelp"
<< "AdvSceneSwitcher.action.macro.type.nestedMacro.actionHelp"
<< "AdvSceneSwitcher.action.macro.type.nestedMacro.elseActionHelp"))
{
populateActionSelection(_actions);
populateConditionBehaviorSelection(_conditionBehaviors);
@@ -310,6 +350,7 @@ MacroActionMacroEdit::MacroActionMacroEdit(
layout->addLayout(_setInputsLayout);
layout->addWidget(_inputs);
layout->addWidget(_skipWhenPaused);
layout->addWidget(_nestedMacro);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
@@ -321,6 +362,12 @@ void HighlightMacroSettingsButton(bool enable);
MacroActionMacroEdit::~MacroActionMacroEdit()
{
HighlightMacroSettingsButton(false);
if (!_entryData) {
return;
}
_entryData->_customWidgetHeight = GetCustomHeight();
_nestedMacro->SetMacro({}); // Save splitter states
}
void MacroActionMacroEdit::UpdateEntryData()
@@ -343,9 +390,20 @@ void MacroActionMacroEdit::UpdateEntryData()
_skipWhenPaused->setChecked(_entryData->_runOptions.skipWhenPaused);
_setInputs->setChecked(_entryData->_runOptions.setInputs);
SetupMacroInput(_entryData->_macro.GetMacro().get());
const auto &macro = _entryData->_nestedMacro;
_nestedMacro->SetMacro(macro);
SetWidgetVisibility();
}
QWidget *MacroActionMacroEdit::Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionMacroEdit(
parent, std::dynamic_pointer_cast<MacroActionMacro>(action));
}
void MacroActionMacroEdit::MacroChanged(const QString &text)
{
GUARD_LOADING_AND_LOCK();
@@ -444,7 +502,8 @@ void MacroActionMacroEdit::SetWidgetVisibility()
PlaceWidgets(
obs_module_text(
_entryData->_action == MacroActionMacro::Action::RUN
_entryData->_action ==
MacroActionMacro::Action::RUN_ACTIONS
? "AdvSceneSwitcher.action.macro.entry.run"
: "AdvSceneSwitcher.action.macro.entry.other"),
_entryLayout, placeholders);
@@ -460,7 +519,7 @@ void MacroActionMacroEdit::SetWidgetVisibility()
_conditionLayout, placeholders);
}
if (_entryData->_action == MacroActionMacro::Action::RUN ||
if (_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS ||
_entryData->_action == MacroActionMacro::Action::STOP) {
_macros->HideSelectedMacro();
} else {
@@ -476,27 +535,43 @@ void MacroActionMacroEdit::SetWidgetVisibility()
_actionIndex->setVisible(isModifyingActionState);
SetLayoutVisible(_conditionLayout,
_entryData->_action == MacroActionMacro::Action::RUN);
_entryData->_action ==
MacroActionMacro::Action::RUN_ACTIONS);
const bool needsAdditionalConditionWidgets =
_entryData->_action == MacroActionMacro::Action::RUN &&
_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS &&
_entryData->_runOptions.logic !=
MacroActionMacro::RunOptions::Logic::IGNORE_CONDITIONS;
_conditionMacros->setVisible(needsAdditionalConditionWidgets);
SetLayoutVisible(_reevaluateConditionStateLayout,
needsAdditionalConditionWidgets);
SetLayoutVisible(_setInputsLayout,
_entryData->_action == MacroActionMacro::Action::RUN);
_entryData->_action ==
MacroActionMacro::Action::RUN_ACTIONS);
_inputs->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN &&
MacroActionMacro::Action::RUN_ACTIONS &&
_entryData->_runOptions.setInputs);
HighlightMacroSettingsButton(_entryData->_action ==
MacroActionMacro::Action::RUN &&
_entryData->_runOptions.setInputs &&
!_inputs->HasInputsToSet());
HighlightMacroSettingsButton(
_entryData->_action == MacroActionMacro::Action::RUN_ACTIONS &&
_entryData->_runOptions.setInputs &&
!_inputs->HasInputsToSet());
_actionTypes->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN);
MacroActionMacro::Action::RUN_ACTIONS);
_skipWhenPaused->setVisible(_entryData->_action ==
MacroActionMacro::Action::RUN);
MacroActionMacro::Action::RUN_ACTIONS);
_nestedMacro->setVisible(_entryData->_action ==
MacroActionMacro::Action::NESTED_MACRO);
_macros->setVisible(_entryData->_action !=
MacroActionMacro::Action::NESTED_MACRO);
SetResizingEnabled(_entryData->_action ==
MacroActionMacro::Action::NESTED_MACRO);
if (_nestedMacro->IsEmpty()) {
_nestedMacro->ShowAllMacroSections();
// TODO: find a better solution than setting a fixed height
_entryData->_customWidgetHeight = 600;
}
SetCustomHeight(_entryData->_customWidgetHeight);
adjustSize();
updateGeometry();

View File

@@ -1,8 +1,11 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro.hpp"
#include "macro-edit.hpp"
#include "macro-input.hpp"
#include "macro-selection.hpp"
#include "macro-segment-selection.hpp"
#include "resizable-widget.hpp"
#include <QCheckBox>
#include <QHBoxLayout>
@@ -44,16 +47,19 @@ public:
PAUSE,
UNPAUSE,
RESET_COUNTER,
RUN,
RUN_ACTIONS,
STOP,
DISABLE_ACTION,
ENABLE_ACTION,
TOGGLE_ACTION,
TOGGLE_PAUSE,
NESTED_MACRO,
};
Action _action = Action::RUN;
Action _action = Action::NESTED_MACRO;
IntVariable _actionIndex = 1;
RunOptions _runOptions = {};
std::shared_ptr<Macro> _nestedMacro = std::make_shared<Macro>();
int _customWidgetHeight = 0;
private:
void RunActions(Macro *actionMacro) const;
@@ -62,7 +68,7 @@ private:
static const std::string id;
};
class MacroActionMacroEdit final : public QWidget {
class MacroActionMacroEdit final : public ResizableWidget {
Q_OBJECT
public:
@@ -71,13 +77,7 @@ public:
std::shared_ptr<MacroActionMacro> entryData = nullptr);
~MacroActionMacroEdit();
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionMacroEdit(
parent,
std::dynamic_pointer_cast<MacroActionMacro>(action));
}
static QWidget *Create(QWidget *, std::shared_ptr<MacroAction>);
private slots:
void MacroChanged(const QString &text);
@@ -112,6 +112,7 @@ private:
QHBoxLayout *_conditionLayout;
QHBoxLayout *_reevaluateConditionStateLayout;
QHBoxLayout *_setInputsLayout;
MacroEdit *_nestedMacro;
std::shared_ptr<MacroActionMacro> _entryData;
bool _loading = true;

View File

@@ -1,9 +1,10 @@
#include "macro-action-variable.hpp"
#include "advanced-scene-switcher.hpp"
#include "help-icon.hpp"
#include "json-helpers.hpp"
#include "layout-helpers.hpp"
#include "math-helpers.hpp"
#include "macro-condition-edit.hpp"
#include "macro-signals.hpp"
#include "math-helpers.hpp"
#include "macro.hpp"
#include "non-modal-dialog.hpp"
#include "source-helpers.hpp"
@@ -480,7 +481,7 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
_inputPlaceholder.Save(obj, "inputPlaceholder");
_envVariableName.Save(obj, "environmentVariableName");
_scene.Save(obj);
_tempVar.Save(obj);
_tempVar.Save(obj, GetMacro());
_sceneItemIndex.Save(obj, "sceneItemIndex");
obs_data_set_int(obj, "direction", static_cast<int>(_direction));
_stringLength.Save(obj, "stringLength");
@@ -828,7 +829,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
this)),
_randomLayout(new QVBoxLayout()),
_jsonQuery(new VariableLineEdit(this)),
_jsonQueryHelp(new QLabel(this)),
_jsonQueryHelp(new HelpIcon(
obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.info"),
this)),
_jsonIndex(new VariableSpinBox(this)),
_entryLayout(new QHBoxLayout())
{
@@ -859,14 +863,6 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_randomNumberStart->setMaximum(9999999999);
_randomNumberEnd->setMinimum(-9999999999);
_randomNumberEnd->setMaximum(9999999999);
const QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
const QIcon icon(path);
const QPixmap pixmap = icon.pixmap(QSize(16, 16));
_jsonQueryHelp->setPixmap(pixmap);
_jsonQueryHelp->setToolTip(obs_module_text(
"AdvSceneSwitcher.action.variable.type.queryJson.info"));
_jsonIndex->setMaximum(999);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
@@ -884,7 +880,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
QWidget::connect(_segmentIdx,
SIGNAL(SelectionChanged(const IntVariable &)), this,
SLOT(SegmentIndexChanged(const IntVariable &)));
QWidget::connect(window(), SIGNAL(MacroSegmentOrderChanged()), this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(SegmentOrderChanged()), this,
SLOT(MacroSegmentOrderChanged()));
QWidget::connect(
_subStringStart,

View File

@@ -275,289 +275,4 @@ std::shared_ptr<MacroSegment> MacroConditionEdit::Data() const
return *_entryData;
}
void AdvSceneSwitcher::AddMacroCondition(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx > (int)macro->Conditions().size()) {
assert(false);
return;
}
std::string id;
Logic::Type logic;
if (idx >= 1) {
id = macro->Conditions().at(idx - 1)->GetId();
if (idx == 1) {
logic = Logic::Type::OR;
} else {
logic = macro->Conditions().at(idx - 1)->GetLogicType();
}
} else {
id = MacroCondition::GetDefaultID();
logic = Logic::Type::ROOT_NONE;
}
OBSDataAutoRelease data;
if (idx - 1 >= 0) {
data = obs_data_create();
macro->Conditions().at(idx - 1)->Save(data);
}
AddMacroCondition(macro.get(), idx, id, data.Get(), logic);
}
void AdvSceneSwitcher::AddMacroCondition(Macro *macro, int idx,
const std::string &id,
obs_data_t *data, Logic::Type logic)
{
if (idx < 0 || idx > (int)macro->Conditions().size()) {
assert(false);
return;
}
{
auto lock = LockContext();
auto cond = macro->Conditions().emplace(
macro->Conditions().begin() + idx,
MacroConditionFactory::Create(id, macro));
if (data) {
macro->Conditions().at(idx)->Load(data);
}
macro->Conditions().at(idx)->PostLoad();
RunPostLoadSteps();
(*cond)->SetLogicType(logic);
macro->UpdateConditionIndices();
ui->conditionsList->Insert(
idx,
new MacroConditionEdit(this, &macro->Conditions()[idx],
id, idx == 0));
SetConditionData(*macro);
}
HighlightCondition(idx);
ui->conditionsList->SetHelpMsgVisible(false);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::on_conditionAdd_clicked()
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (currentConditionIdx == -1) {
AddMacroCondition((int)macro->Conditions().size());
} else {
AddMacroCondition(currentConditionIdx + 1);
}
if (currentConditionIdx != -1) {
MacroConditionSelectionChanged(currentConditionIdx + 1);
}
}
void AdvSceneSwitcher::RemoveMacroCondition(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx >= (int)macro->Conditions().size()) {
return;
}
{
auto lock = LockContext();
ui->conditionsList->Remove(idx);
macro->Conditions().erase(macro->Conditions().begin() + idx);
macro->UpdateConditionIndices();
if (idx == 0 && macro->Conditions().size() > 0) {
auto newRoot = macro->Conditions().at(0);
newRoot->SetLogicType(Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(true);
}
SetConditionData(*macro);
}
MacroConditionSelectionChanged(-1);
lastInteracted = MacroSection::CONDITIONS;
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::on_conditionRemove_clicked()
{
if (currentConditionIdx == -1) {
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
RemoveMacroCondition((int)macro->Conditions().size() - 1);
} else {
RemoveMacroCondition(currentConditionIdx);
}
MacroConditionSelectionChanged(-1);
}
void AdvSceneSwitcher::on_conditionTop_clicked()
{
if (currentConditionIdx == -1) {
return;
}
MacroConditionReorder(0, currentConditionIdx);
MacroConditionSelectionChanged(0);
}
void AdvSceneSwitcher::on_conditionUp_clicked()
{
if (currentConditionIdx == -1 || currentConditionIdx == 0) {
return;
}
MoveMacroConditionUp(currentConditionIdx);
MacroConditionSelectionChanged(currentConditionIdx - 1);
}
void AdvSceneSwitcher::on_conditionDown_clicked()
{
if (currentConditionIdx == -1 ||
currentConditionIdx ==
ui->conditionsList->ContentLayout()->count() - 1) {
return;
}
MoveMacroConditionDown(currentConditionIdx);
MacroConditionSelectionChanged(currentConditionIdx + 1);
}
void AdvSceneSwitcher::on_conditionBottom_clicked()
{
if (currentConditionIdx == -1) {
return;
}
const int newIdx = ui->conditionsList->ContentLayout()->count() - 1;
MacroConditionReorder(newIdx, currentConditionIdx);
MacroConditionSelectionChanged(newIdx);
}
void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
{
if (pos1 == pos2) {
return;
}
if (pos1 > pos2) {
std::swap(pos1, pos2);
}
bool root = pos1 == 0;
auto lock = LockContext();
iter_swap(m->Conditions().begin() + pos1,
m->Conditions().begin() + pos2);
m->UpdateConditionIndices();
auto c1 = m->Conditions().begin() + pos1;
auto c2 = m->Conditions().begin() + pos2;
if (root) {
auto logic1 = (*c1)->GetLogicType();
auto logic2 = (*c2)->GetLogicType();
(*c1)->SetLogicType(logic2);
(*c2)->SetLogicType(logic1);
}
auto widget1 = static_cast<MacroConditionEdit *>(
ui->conditionsList->ContentLayout()->takeAt(pos1)->widget());
auto widget2 = static_cast<MacroConditionEdit *>(
ui->conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
ui->conditionsList->Insert(pos1, widget2);
ui->conditionsList->Insert(pos2, widget1);
SetConditionData(*m);
widget2->SetRootNode(root);
widget1->SetRootNode(false);
emit(MacroSegmentOrderChanged());
}
void AdvSceneSwitcher::MoveMacroConditionUp(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 1 || idx >= (int)macro->Conditions().size()) {
return;
}
SwapConditions(macro.get(), idx, idx - 1);
HighlightCondition(idx - 1);
}
void AdvSceneSwitcher::MoveMacroConditionDown(int idx)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (idx < 0 || idx >= (int)macro->Conditions().size() - 1) {
return;
}
SwapConditions(macro.get(), idx, idx + 1);
HighlightCondition(idx + 1);
}
void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
{
SetupMacroSegmentSelection(MacroSection::CONDITIONS, idx);
}
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
{
auto macro = GetSelectedMacro();
if (!macro) {
return;
}
if (to == from || from < 0 || from > (int)macro->Conditions().size() ||
to < 0 || to > (int)macro->Conditions().size()) {
return;
}
{
auto lock = LockContext();
auto condition = macro->Conditions().at(from);
if (to == 0) {
condition->SetLogicType(Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(from))
->SetRootNode(true);
macro->Conditions().at(0)->SetLogicType(
Logic::Type::AND);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(0))
->SetRootNode(false);
}
if (from == 0) {
condition->SetLogicType(Logic::Type::AND);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(from))
->SetRootNode(false);
macro->Conditions().at(1)->SetLogicType(
Logic::Type::ROOT_NONE);
static_cast<MacroConditionEdit *>(
ui->conditionsList->WidgetAt(1))
->SetRootNode(true);
}
macro->Conditions().erase(macro->Conditions().begin() + from);
macro->Conditions().insert(macro->Conditions().begin() + to,
condition);
macro->UpdateConditionIndices();
ui->conditionsList->ContentLayout()->insertItem(
to, ui->conditionsList->ContentLayout()->takeAt(from));
SetConditionData(*macro);
}
HighlightCondition(to);
emit(MacroSegmentOrderChanged());
}
} // namespace advss

View File

@@ -1,21 +1,11 @@
#include "macro-condition-factory.hpp"
#include "macro-segment-unknown.hpp"
#include <mutex>
namespace advss {
namespace {
class MacroConditionUnknown : public MacroCondition {
public:
MacroConditionUnknown(Macro *m) : MacroCondition(m) {}
bool CheckCondition() { return false; }
bool Save(obs_data_t *obj) const { return MacroCondition::Save(obj); };
bool Load(obs_data_t *obj) { return MacroCondition::Load(obj); };
std::string GetId() const { return "unknown"; }
};
} // namespace
using MacroConditionUnknown = MacroSegmentUnknown<MacroCondition>;
static std::recursive_mutex mutex;
@@ -46,9 +36,10 @@ bool MacroConditionFactory::Deregister(const std::string &id)
return true;
}
static std::shared_ptr<MacroCondition> createUnknownCondition(Macro *m)
static std::shared_ptr<MacroCondition>
createUnknownCondition(Macro *m, const std::string &id)
{
return std::make_shared<MacroConditionUnknown>(m);
return std::make_shared<MacroConditionUnknown>(m, id);
}
std::shared_ptr<MacroCondition>
@@ -58,13 +49,7 @@ MacroConditionFactory::Create(const std::string &id, Macro *m)
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._create(m);
}
return createUnknownCondition(m);
}
static QWidget *createUnknownConditionWidget()
{
return new QLabel(
obs_module_text("AdvSceneSwitcher.condition.unknown"));
return createUnknownCondition(m, id);
}
QWidget *
@@ -75,7 +60,7 @@ MacroConditionFactory::CreateWidget(const std::string &id, QWidget *parent,
if (auto it = GetMap().find(id); it != GetMap().end()) {
return it->second._createWidget(parent, cond);
}
return createUnknownConditionWidget();
return CreateUnknownSegmentWidget(false);
}
std::string MacroConditionFactory::GetConditionName(const std::string &id)

View File

@@ -1,6 +1,7 @@
#include "macro-condition-macro.hpp"
#include "layout-helpers.hpp"
#include "macro-action-edit.hpp"
#include "macro-signals.hpp"
#include "macro.hpp"
namespace advss {
@@ -26,6 +27,8 @@ const static std::map<MacroConditionMacro::Type, std::string>
"AdvSceneSwitcher.condition.macro.type.actionEnabled"},
{MacroConditionMacro::Type::PAUSED,
"AdvSceneSwitcher.condition.macro.type.paused"},
{MacroConditionMacro::Type::ACTIONS_PERFORMED,
"AdvSceneSwitcher.condition.macro.type.actionsPerformed"},
};
const static std::map<MacroConditionMacro::CounterCondition, std::string>
@@ -121,6 +124,16 @@ bool MacroConditionMacro::CheckPauseState()
return macro->Paused();
}
bool MacroConditionMacro::CheckActionsPerformed()
{
auto macro = _macro.GetMacro();
if (!macro) {
return false;
}
return macro->WasExecutedSince(macro->LastConditionCheckTime());
}
bool MacroConditionMacro::CheckCountCondition()
{
auto macro = _macro.GetMacro();
@@ -158,6 +171,8 @@ bool MacroConditionMacro::CheckCondition()
return CheckActionStateCondition();
case Type::PAUSED:
return CheckPauseState();
case Type::ACTIONS_PERFORMED:
return CheckActionsPerformed();
default:
break;
}
@@ -307,7 +322,8 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &)));
QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(Remove(const QString &)), this,
SLOT(MacroRemove(const QString &)));
QWidget::connect(_types, SIGNAL(currentIndexChanged(int)), this,
SLOT(TypeChanged(int)));
@@ -414,6 +430,9 @@ void MacroConditionMacroEdit::SetupWidgets()
case MacroConditionMacro::Type::PAUSED:
SetupPauseWidgets();
break;
case MacroConditionMacro::Type::ACTIONS_PERFORMED:
SetupActionsPerformedWidgets();
break;
default:
break;
}
@@ -472,6 +491,14 @@ void MacroConditionMacroEdit::SetupPauseWidgets()
_settingsLine1, {{"{{macros}}", _macros}});
}
void MacroConditionMacroEdit::SetupActionsPerformedWidgets()
{
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.condition.macro.actionsPerformed.entry"),
_settingsLine1, {{"{{macros}}", _macros}});
}
void MacroConditionMacroEdit::SetWidgetVisibility()
{
_macros->setVisible(
@@ -481,7 +508,9 @@ void MacroConditionMacroEdit::SetWidgetVisibility()
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTION_ENABLED ||
_entryData->GetType() == MacroConditionMacro::Type::PAUSED);
_entryData->GetType() == MacroConditionMacro::Type::PAUSED ||
_entryData->GetType() ==
MacroConditionMacro::Type::ACTIONS_PERFORMED);
_counterConditions->setVisible(_entryData->GetType() ==
MacroConditionMacro::Type::COUNT);
_count->setVisible(_entryData->GetType() ==

View File

@@ -39,6 +39,7 @@ public:
ACTION_DISABLED,
ACTION_ENABLED,
PAUSED,
ACTIONS_PERFORMED,
};
void SetType(Type);
Type GetType() const { return _type; }
@@ -66,6 +67,7 @@ private:
bool CheckMultiStateCondition();
bool CheckActionStateCondition();
bool CheckPauseState();
bool CheckActionsPerformed();
void SetupTempVars();
@@ -134,6 +136,7 @@ private:
void SetupCountWidgets();
void SetupActionStateWidgets(bool enable);
void SetupPauseWidgets();
void SetupActionsPerformedWidgets();
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -154,7 +154,7 @@ bool MacroConditionTempVar::CheckCondition()
bool MacroConditionTempVar::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
_tempVar.Save(obj);
_tempVar.Save(obj, GetMacro());
obs_data_set_string(obj, "variableName",
GetWeakVariableName(_variable2).c_str());
_strValue.Save(obj, "strValue");

View File

@@ -0,0 +1,394 @@
#include "macro-dock-settings.hpp"
#include "macro-dock.hpp"
#include "macro-dock-window.hpp"
#include "macro.hpp"
#include "plugin-state-helpers.hpp"
#include <obs-frontend-api.h>
#include <util/platform.h>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0)
#include <QDockWidget>
namespace {
struct DockMapEntry {
QAction *action = nullptr;
QWidget *dock = nullptr;
};
} // namespace
static std::unordered_map<const char *, DockMapEntry> dockIds;
static std::mutex dockMutex;
static bool obs_frontend_add_dock_by_id(const char *id, const char *title,
QWidget *widget)
{
std::lock_guard<std::mutex> lock(dockMutex);
if (dockIds.count(id) > 0) {
return false;
}
widget->setObjectName(id);
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setFloating(true);
dock->setVisible(false);
dock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
auto action = static_cast<QAction *>(obs_frontend_add_dock(dock));
if (!action) {
return false;
}
dockIds[id] = {action, dock};
return true;
}
static void obs_frontend_remove_dock(const char *id)
{
std::lock_guard<std::mutex> lock(dockMutex);
auto it = dockIds.find(id);
if (it == dockIds.end()) {
return;
}
it->second.action->deleteLater();
it->second.dock->deleteLater();
dockIds.erase(it);
}
#endif
namespace advss {
MacroDockSettings::MacroDockSettings(Macro *macro) : _macro(macro) {}
MacroDockSettings::~MacroDockSettings()
{
// Keep the dock widgets in case of shutdown so they can be restored by
// OBS on startup
if (!OBSIsShuttingDown()) {
RemoveDock();
}
}
void MacroDockSettings::Save(obs_data_t *obj, bool saveForCopy) const
{
OBSDataAutoRelease dockSettings = obs_data_create();
obs_data_set_bool(dockSettings, "register", _registerDock);
obs_data_set_bool(dockSettings, "standaloneDock", _standaloneDock);
obs_data_set_string(dockSettings, "dockWindow", _dockWindow.c_str());
obs_data_set_bool(dockSettings, "hasRunButton", _hasRunButton);
obs_data_set_bool(dockSettings, "hasPauseButton", _hasPauseButton);
obs_data_set_bool(dockSettings, "hasStatusLabel", _hasStatusLabel);
obs_data_set_bool(dockSettings, "highlightIfConditionsTrue",
_highlight);
_runButtonText.Save(dockSettings, "runButtonText");
_pauseButtonText.Save(dockSettings, "pauseButtonText");
_unpauseButtonText.Save(dockSettings, "unpauseButtonText");
_conditionsTrueStatusText.Save(dockSettings,
"conditionsTrueStatusText");
_conditionsFalseStatusText.Save(dockSettings,
"conditionsFalseStatusText");
if (saveForCopy) {
auto uuid = GenerateId();
obs_data_set_string(dockSettings, "dockId", uuid.c_str());
} else {
obs_data_set_string(dockSettings, "dockId", _id.c_str());
}
obs_data_set_int(dockSettings, "version", 1);
obs_data_set_obj(obj, "dockSettings", dockSettings);
}
void MacroDockSettings::Load(obs_data_t *obj)
{
OBSDataAutoRelease dockSettings = obs_data_get_obj(obj, "dockSettings");
if (!dockSettings) {
// TODO: Remove this fallback
_hasRunButton = obs_data_get_bool(obj, "dockHasRunButton");
_hasPauseButton = obs_data_get_bool(obj, "dockHasPauseButton");
_registerDock = obs_data_get_bool(obj, "registerDock");
ResetDockIfEnabled();
return;
}
_macroName = _macro->Name();
if (!obs_data_has_user_value(dockSettings, "version")) {
assert(_macro);
_id = std::string("ADVSS-") + _macroName;
} else {
_id = obs_data_get_string(dockSettings, "dockId");
}
_registerDock = obs_data_get_bool(dockSettings, "register");
// TODO: remove these default settings in a future version
obs_data_set_default_bool(dockSettings, "standaloneDock", true);
obs_data_set_default_string(dockSettings, "dockWindow", "Dock");
obs_data_set_default_string(
dockSettings, "runButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.run"));
obs_data_set_default_string(
dockSettings, "pauseButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.pause"));
obs_data_set_default_string(
dockSettings, "unpauseButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.unpause"));
_standaloneDock = obs_data_get_bool(dockSettings, "standaloneDock");
_dockWindow = obs_data_get_string(dockSettings, "dockWindow");
_runButtonText.Load(dockSettings, "runButtonText");
_pauseButtonText.Load(dockSettings, "pauseButtonText");
_unpauseButtonText.Load(dockSettings, "unpauseButtonText");
_conditionsTrueStatusText.Load(dockSettings,
"conditionsTrueStatusText");
_conditionsFalseStatusText.Load(dockSettings,
"conditionsFalseStatusText");
if (_registerDock) {
_hasRunButton = obs_data_get_bool(dockSettings, "hasRunButton");
_hasPauseButton =
obs_data_get_bool(dockSettings, "hasPauseButton");
_hasStatusLabel =
obs_data_get_bool(dockSettings, "hasStatusLabel");
_highlight = obs_data_get_bool(dockSettings,
"highlightIfConditionsTrue");
}
ResetDockIfEnabled();
}
void MacroDockSettings::EnableDock(bool enable)
{
// Only apply "on change" to avoid recreation of the dock widget
if (_registerDock == enable) {
return;
}
RemoveDock();
if (!enable) {
_registerDock = enable;
return;
}
assert(_macro);
_macroName = _macro->Name();
_dock = new MacroDock(GetWeakMacroByName(_macroName.c_str()),
_runButtonText, _pauseButtonText,
_unpauseButtonText, _conditionsTrueStatusText,
_conditionsFalseStatusText, _highlight);
if (!_standaloneDock) {
auto window = GetDockWindowByName(_dockWindow);
if (!window) {
return;
}
window->AddMacroDock(_dock, QString::fromStdString(_macroName));
_registerDock = enable;
return;
}
if (!obs_frontend_add_dock_by_id(_id.c_str(), _macroName.c_str(),
_dock)) {
blog(LOG_INFO, "failed to add macro dock for macro %s",
_macroName.c_str());
_dock->deleteLater();
_dock = nullptr;
_registerDock = false;
return;
}
_registerDock = enable;
}
void MacroDockSettings::SetIsStandaloneDock(bool value)
{
if (_standaloneDock == value) {
return;
}
RemoveDock();
_standaloneDock = value;
ResetDockIfEnabled();
}
void MacroDockSettings::SetDockWindowName(const std::string &name)
{
if (_dockWindow == name) {
return;
}
RemoveDock();
_dockWindow = name;
ResetDockIfEnabled();
}
void MacroDockSettings::SetHasRunButton(bool value)
{
_hasRunButton = value;
if (!_dock) {
return;
}
_dock->ShowRunButton(value);
}
void MacroDockSettings::SetHasPauseButton(bool value)
{
_hasPauseButton = value;
if (!_dock) {
return;
}
_dock->ShowPauseButton(value);
}
void MacroDockSettings::SetHasStatusLabel(bool value)
{
_hasStatusLabel = value;
if (!_dock) {
return;
}
_dock->ShowStatusLabel(value);
}
void MacroDockSettings::SetHighlightEnable(bool value)
{
_highlight = value;
if (!_dock) {
return;
}
_dock->EnableHighlight(value);
}
void MacroDockSettings::SetRunButtonText(const std::string &text)
{
_runButtonText = text;
if (!_dock) {
return;
}
_dock->SetRunButtonText(text);
}
void MacroDockSettings::SetPauseButtonText(const std::string &text)
{
_pauseButtonText = text;
if (!_dock) {
return;
}
_dock->SetPauseButtonText(text);
}
void MacroDockSettings::SetUnpauseButtonText(const std::string &text)
{
_unpauseButtonText = text;
if (!_dock) {
return;
}
_dock->SetUnpauseButtonText(text);
}
void MacroDockSettings::SetConditionsTrueStatusText(const std::string &text)
{
_conditionsTrueStatusText = text;
if (!_dock) {
return;
}
_dock->SetConditionsTrueText(text);
}
StringVariable MacroDockSettings::ConditionsTrueStatusText() const
{
return _conditionsTrueStatusText;
}
void MacroDockSettings::SetConditionsFalseStatusText(const std::string &text)
{
_conditionsFalseStatusText = text;
if (!_dock) {
return;
}
_dock->SetConditionsFalseText(text);
}
StringVariable MacroDockSettings::ConditionsFalseStatusText() const
{
return _conditionsFalseStatusText;
}
void MacroDockSettings::HandleMacroNameChange()
{
const auto newName = _macro->Name();
if (!_standaloneDock) {
auto window = GetDockWindowByName(_dockWindow);
if (!window) {
return;
}
window->RenameMacro(_macroName, newName);
_macroName = newName;
return;
}
if (_macroName != newName) {
RemoveDock();
_id = GenerateId();
_macroName = newName;
}
ResetDockIfEnabled();
}
void MacroDockSettings::ResetDockIfEnabled()
{
if (_registerDock) {
_registerDock = false;
EnableDock(true);
}
}
void MacroDockSettings::RemoveDock()
{
if (_standaloneDock) {
obs_frontend_remove_dock(_id.c_str());
_dock = nullptr;
return;
}
auto window = GetDockWindowByName(_dockWindow);
if (window) {
window->RemoveMacroDock(_dock);
}
if (_dock) {
_dock = nullptr;
}
}
std::string MacroDockSettings::GenerateId()
{
#if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(30, 0, 0)
auto uuid = os_generate_uuid();
auto id = std::string("advss-macro-dock-") + std::string(uuid);
bfree(uuid);
return id;
#else
static std::atomic_int16_t idCounter = 0;
return std::to_string(++idCounter);
#endif
}
} // namespace advss

View File

@@ -0,0 +1,76 @@
#pragma once
#include "obs-module-helper.hpp"
#include "variable-string.hpp"
namespace advss {
class Macro;
class MacroDock;
class MacroDockSettings {
public:
MacroDockSettings(Macro *macro);
~MacroDockSettings();
void Save(obs_data_t *obj, bool saveForCopy) const;
void Load(obs_data_t *obj);
void EnableDock(bool);
bool DockEnabled() const { return _registerDock; }
bool IsStandaloneDock() const { return _standaloneDock; }
void SetIsStandaloneDock(bool value);
std::string DockWindowName() const { return _dockWindow; }
void SetDockWindowName(const std::string &name);
void SetHasRunButton(bool value);
bool HasRunButton() const { return _hasRunButton; }
void SetHasPauseButton(bool value);
bool HasPauseButton() const { return _hasPauseButton; }
void SetHasStatusLabel(bool value);
bool HasStatusLabel() const { return _hasStatusLabel; }
void SetHighlightEnable(bool value);
bool HighlightEnabled() const { return _highlight; }
StringVariable RunButtonText() const { return _runButtonText; }
void SetRunButtonText(const std::string &text);
StringVariable PauseButtonText() const { return _pauseButtonText; }
void SetPauseButtonText(const std::string &text);
StringVariable UnpauseButtonText() const { return _unpauseButtonText; }
void SetUnpauseButtonText(const std::string &text);
void SetConditionsTrueStatusText(const std::string &text);
StringVariable ConditionsTrueStatusText() const;
void SetConditionsFalseStatusText(const std::string &text);
StringVariable ConditionsFalseStatusText() const;
void HandleMacroNameChange();
private:
void ResetDockIfEnabled();
void RemoveDock();
static std::string GenerateId();
bool _registerDock = false;
bool _standaloneDock = true;
std::string _dockWindow = "Dock";
bool _hasRunButton = true;
bool _hasPauseButton = true;
bool _hasStatusLabel = false;
bool _highlight = false;
StringVariable _runButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.run");
StringVariable _pauseButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.pause");
StringVariable _unpauseButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.unpause");
StringVariable _conditionsTrueStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.true");
StringVariable _conditionsFalseStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.false");
std::string _id = GenerateId();
std::string _macroName = "";
Macro *_macro = nullptr;
MacroDock *_dock = nullptr;
};
} // namespace advss

View File

@@ -0,0 +1,170 @@
#include "macro-dock-window.hpp"
#include "log-helper.hpp"
#include "plugin-state-helpers.hpp"
#include <obs-frontend-api.h>
#include <QDockWidget>
#include <QLayout>
#include <mutex>
namespace advss {
static std::unordered_map<std::string, MacroDockWindow *> windows;
static std::unordered_map<std::string, QByteArray> windowGeometries;
static std::mutex mutex;
static void saveDocks(obs_data_t *obj)
{
std::lock_guard<std::mutex> lock(mutex);
OBSDataAutoRelease data = obs_data_create();
OBSDataArrayAutoRelease array = obs_data_array_create();
for (const auto &[name, window] : windows) {
OBSDataAutoRelease dockData = obs_data_create();
obs_data_set_string(dockData, "name", name.c_str());
obs_data_set_string(dockData, "geometry",
window->GetWindow()
->saveState()
.toBase64()
.toStdString()
.c_str());
obs_data_array_push_back(array, dockData);
}
obs_data_set_array(data, "docks", array);
obs_data_set_obj(obj, "dockWindows", data);
}
static void restoreDockGeometry()
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &[name, dock] : windows) {
const auto it = windowGeometries.find(name);
if (it == windowGeometries.end()) {
continue;
}
dock->GetWindow()->restoreState(it->second);
}
}
static void loadDocks(obs_data_t *obj)
{
std::lock_guard<std::mutex> lock(mutex);
windowGeometries.clear();
OBSDataAutoRelease data = obs_data_get_obj(obj, "dockWindows");
OBSDataArrayAutoRelease array = obs_data_get_array(data, "docks");
auto size = obs_data_array_count(array);
for (size_t i = 0; i < size; ++i) {
OBSDataAutoRelease dockData = obs_data_array_item(array, i);
const auto name = obs_data_get_string(dockData, "name");
const auto geometry = QByteArray::fromBase64(
obs_data_get_string(dockData, "geometry"));
windowGeometries[name] = geometry;
}
AddPostLoadStep(restoreDockGeometry);
}
[[maybe_unused]] static bool _ = []() {
AddPluginInitStep([]() {
AddSaveStep(saveDocks);
AddLoadStep(loadDocks);
});
return true;
}();
MacroDockWindow::MacroDockWindow(const std::string &name)
: QFrame(),
_name(name),
_window(new QMainWindow())
{
setFrameShape(QFrame::StyledPanel);
setFrameShadow(QFrame::Sunken);
_window->setDockNestingEnabled(true);
auto layout = new QVBoxLayout;
layout->addWidget(_window);
setLayout(layout);
}
QWidget *MacroDockWindow::AddMacroDock(QWidget *widget, const QString &title)
{
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setVisible(true);
dock->setFeatures(QDockWidget::DockWidgetMovable);
dock->setObjectName(title);
_window->addDockWidget(Qt::RightDockWidgetArea, dock);
return dock;
}
void MacroDockWindow::RenameMacro(const std::string &oldName,
const std::string &newName)
{
auto docks = _window->findChildren<QDockWidget *>();
for (const auto dock : docks) {
if (dock->windowTitle() == QString::fromStdString(oldName)) {
dock->setWindowTitle(QString::fromStdString(newName));
break;
}
}
}
void MacroDockWindow::RemoveMacroDock(QWidget *widget)
{
bool removedDock = false;
auto docks = _window->findChildren<QDockWidget *>();
for (const auto dock : docks) {
if (dock->widget() == widget) {
_window->removeDockWidget(dock);
dock->deleteLater();
removedDock = true;
break;
}
}
if (OBSIsShuttingDown()) {
return;
}
const bool shouldRemoveDockWindow = docks.isEmpty() ||
(removedDock && docks.count() == 1);
if (!shouldRemoveDockWindow) {
return;
}
std::lock_guard<std::mutex> lock(mutex);
auto it = windows.find(_name);
if (it != windows.end()) {
windows.erase(it);
}
const auto id = "advss-dock-window-" + _name;
obs_frontend_remove_dock(id.c_str());
}
QMainWindow *MacroDockWindow::GetWindow() const
{
return _window;
}
MacroDockWindow *GetDockWindowByName(const std::string &name)
{
std::lock_guard<std::mutex> lock(mutex);
auto it = windows.find(name);
if (it != windows.end()) {
return it->second;
}
auto window = new MacroDockWindow(name);
const auto id = "advss-dock-window-" + name;
if (!obs_frontend_add_dock_by_id(id.c_str(), name.c_str(), window)) {
blog(LOG_INFO, "failed to add macro dock window '%s'",
id.c_str());
return nullptr;
}
windows[name] = window;
return window;
}
} // namespace advss

View File

@@ -0,0 +1,29 @@
#pragma once
#include <QFrame>
#include <QMainWindow>
#include <QWidget>
#include <string>
#include <vector>
namespace advss {
class MacroDockWindow : public QFrame {
Q_OBJECT
public:
MacroDockWindow(const std::string &name);
QWidget *AddMacroDock(QWidget *, const QString &title);
void RenameMacro(const std::string &oldName,
const std::string &newName);
void RemoveMacroDock(QWidget *);
QMainWindow *GetWindow() const;
private:
std::string _name;
QMainWindow *_window;
};
MacroDockWindow *GetDockWindowByName(const std::string &name);
} // namespace advss

View File

@@ -30,9 +30,10 @@ MacroDock::MacroDock(std::weak_ptr<Macro> m,
auto macro = _macro.lock();
if (macro) {
_run->setVisible(macro->DockHasRunButton());
_pauseToggle->setVisible(macro->DockHasPauseButton());
_statusText->setVisible(macro->DockHasStatusLabel());
const auto &settings = macro->GetDockSettings();
_run->setVisible(settings.HasRunButton());
_pauseToggle->setVisible(settings.HasPauseButton());
_statusText->setVisible(settings.HasStatusLabel());
}
QWidget::connect(_run, SIGNAL(clicked()), this, SLOT(RunClicked()));

1842
lib/macro/macro-edit.cpp Normal file

File diff suppressed because it is too large Load Diff

144
lib/macro/macro-edit.hpp Normal file
View File

@@ -0,0 +1,144 @@
#pragma once
#include "condition-logic.hpp"
#include <ui_macro-edit.h>
namespace advss {
class Macro;
class MacroSegment;
/*******************************************************************************
* Advanced Scene Switcher window
*******************************************************************************/
class MacroEdit : public QWidget {
Q_OBJECT
public:
MacroEdit(QWidget *parent, QStringList helpMsg = {});
void SetMacro(const std::shared_ptr<Macro> &);
std::shared_ptr<Macro> GetMacro() const;
void ClearSegmentWidgetCacheFor(Macro *) const;
void SetControlsDisabled(bool disable) const;
void HighlightAction(int idx, QColor color = QColor(Qt::green)) const;
void HighlightElseAction(int idx,
QColor color = QColor(Qt::green)) const;
void HighlightCondition(int idx,
QColor color = QColor(Qt::green)) const;
void ResetConditionHighlights();
void ResetActionHighlights();
void SetActionData(Macro &m) const;
void SetElseActionData(Macro &m) const;
void SetConditionData(Macro &m) const;
void SwapActions(Macro *m, int pos1, int pos2);
void SwapElseActions(Macro *m, int pos1, int pos2);
void SwapConditions(Macro *m, int pos1, int pos2);
void CopyMacroSegment();
void PasteMacroSegment();
bool IsEmpty() const;
void ShowAllMacroSections();
private slots:
void on_conditionAdd_clicked();
void on_conditionRemove_clicked();
void on_conditionTop_clicked();
void on_conditionUp_clicked();
void on_conditionDown_clicked();
void on_conditionBottom_clicked();
void on_actionAdd_clicked();
void on_actionRemove_clicked();
void on_actionTop_clicked();
void on_actionUp_clicked();
void on_actionDown_clicked();
void on_actionBottom_clicked();
void on_toggleElseActions_clicked() const;
void on_elseActionAdd_clicked();
void on_elseActionRemove_clicked();
void on_elseActionTop_clicked();
void on_elseActionUp_clicked();
void on_elseActionDown_clicked();
void on_elseActionBottom_clicked();
void UpMacroSegmentHotkey();
void DownMacroSegmentHotkey();
void DeleteMacroSegmentHotkey();
void ShowMacroActionsContextMenu(const QPoint &);
void ShowMacroElseActionsContextMenu(const QPoint &);
void ShowMacroConditionsContextMenu(const QPoint &);
void ExpandAllActions() const;
void ExpandAllElseActions() const;
void ExpandAllConditions() const;
void CollapseAllActions() const;
void CollapseAllElseActions() const;
void CollapseAllConditions() const;
void MinimizeActions() const;
void MaximizeActions() const;
void MinimizeElseActions() const;
void MaximizeElseActions() const;
void MinimizeConditions() const;
void MaximizeConditions() const;
void SetElseActionsStateToHidden() const;
void SetElseActionsStateToVisible() const;
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(Macro *macro, int idx, const std::string &id,
obs_data_t *data);
void AddMacroAction(int idx);
void RemoveMacroAction(int idx);
void MoveMacroActionUp(int idx);
void MoveMacroActionDown(int idx);
void MacroElseActionSelectionChanged(int idx);
void MacroElseActionReorder(int to, int target);
void AddMacroElseAction(Macro *macro, int idx, const std::string &id,
obs_data_t *data);
void AddMacroElseAction(int idx);
void RemoveMacroElseAction(int idx);
void MoveMacroElseActionUp(int idx);
void MoveMacroElseActionDown(int idx);
void MacroConditionSelectionChanged(int idx);
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void AddMacroCondition(Macro *macro, int idx, const std::string &id,
obs_data_t *data, Logic::Type logic);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);
void MoveMacroConditionDown(int idx);
void HighlightControls() const;
signals:
void MacroAdded(const QString &name);
void MacroRemoved(const QString &name);
void MacroRenamed(const QString &oldName, const QString &newName);
void MacroSegmentOrderChanged();
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
enum class MacroSection { CONDITIONS, ACTIONS, ELSE_ACTIONS };
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroElseActions(Macro &m, uint32_t afterIdx = 0);
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
void SetupMacroSegmentSelection(MacroSection type, int idx);
void
SetupContextMenu(const QPoint &pos,
const std::function<void(MacroEdit *, int)> &remove,
const std::function<void(MacroEdit *)> &expand,
const std::function<void(MacroEdit *)> &collapse,
const std::function<void(MacroEdit *)> &maximize,
const std::function<void(MacroEdit *)> &minimize,
MacroSegmentList *list);
void RunSegmentHighlightChecks();
bool ElseSectionIsVisible() const;
MacroSection lastInteracted = MacroSection::CONDITIONS;
int currentConditionIdx = -1;
int currentActionIdx = -1;
int currentElseActionIdx = -1;
std::shared_ptr<Macro> _currentMacro;
std::unique_ptr<Ui_MacroEdit> ui;
};
} // namespace advss

View File

@@ -1,6 +1,7 @@
#include "macro-list.hpp"
#include "macro-helpers.hpp"
#include "macro-selection.hpp"
#include "macro-signals.hpp"
#include "obs-module-helper.hpp"
#include "ui-helpers.hpp"
@@ -10,11 +11,11 @@ MacroList::MacroList(QWidget *parent, bool allowDuplicates, bool reorder)
: ListEditor(parent, reorder),
_allowDuplicates(allowDuplicates)
{
QWidget::connect(window(),
SIGNAL(MacroRenamed(const QString &, const QString &)),
this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(MacroRename(const QString &, const QString &)));
QWidget::connect(window(), SIGNAL(MacroRemoved(const QString &)), this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(Remove(const QString &)), this,
SLOT(MacroRemove(const QString &)));
UpdateListSize();
}

View File

@@ -11,10 +11,14 @@ namespace advss {
MacroRunButton::MacroRunButton(QWidget *parent) : QPushButton(parent)
{
installEventFilter(this);
auto parentWindow = window();
if (parentWindow) {
parentWindow->installEventFilter(this);
}
// GetSettingsWindow() might still return null while this constructor is called
QTimer::singleShot(0, this, [this]() {
auto parentWindow = GetSettingsWindow();
if (parentWindow) {
parentWindow->installEventFilter(this);
}
});
setToolTip(obs_module_text("AdvSceneSwitcher.macroTab.run.tooltip"));

View File

@@ -13,7 +13,7 @@ struct MacroSegmentCopyInfo {
};
static MacroSegmentCopyInfo copyInfo;
void AdvSceneSwitcher::CopyMacroSegment()
void MacroEdit::CopyMacroSegment()
{
copyInfo.segment.reset();
copyInfo.type = MacroSegmentCopyInfo::Type::NONE;
@@ -23,7 +23,7 @@ void AdvSceneSwitcher::CopyMacroSegment()
return;
}
auto macro = GetSelectedMacro();
auto macro = _currentMacro;
if (!macro) {
return;
}
@@ -43,13 +43,13 @@ void AdvSceneSwitcher::CopyMacroSegment()
}
}
void AdvSceneSwitcher::PasteMacroSegment()
void MacroEdit::PasteMacroSegment()
{
if (copyInfo.type == MacroSegmentCopyInfo::Type::NONE) {
return;
}
auto macro = GetSelectedMacro();
auto macro = _currentMacro;
if (!macro || !copyInfo.segment) {
return;
}
@@ -113,14 +113,14 @@ void SetCopySegmentTargetActionType(bool setToElseAction)
}
}
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window)
void SetupSegmentCopyPasteShortcutHandlers(MacroEdit *edit)
{
auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), window);
QWidget::connect(copyShortcut, &QShortcut::activated, window,
&AdvSceneSwitcher::CopyMacroSegment);
auto pasteShortcut = new QShortcut(QKeySequence("Ctrl+V"), window);
QWidget::connect(pasteShortcut, &QShortcut::activated, window,
&AdvSceneSwitcher::PasteMacroSegment);
auto copyShortcut = new QShortcut(QKeySequence("Ctrl+C"), edit);
QWidget::connect(copyShortcut, &QShortcut::activated, edit,
&MacroEdit::CopyMacroSegment);
auto pasteShortcut = new QShortcut(QKeySequence("Ctrl+V"), edit);
QWidget::connect(pasteShortcut, &QShortcut::activated, edit,
&MacroEdit::PasteMacroSegment);
}
} // namespace advss

View File

@@ -1,10 +1,10 @@
namespace advss {
class AdvSceneSwitcher;
class MacroEdit;
bool MacroSegmentIsInClipboard();
bool MacroActionIsInClipboard();
void SetCopySegmentTargetActionType(bool setToElseAction);
void SetupSegmentCopyPasteShortcutHandlers(AdvSceneSwitcher *window);
void SetupSegmentCopyPasteShortcutHandlers(MacroEdit *edit);
} // namespace advss

View File

@@ -17,27 +17,39 @@ bool MacroSegmentList::_useCache = true;
MacroSegmentList::MacroSegmentList(QWidget *parent)
: QScrollArea(parent),
_stackedWidget(new QStackedWidget(this)),
_layout(new QVBoxLayout),
_contentLayout(new QVBoxLayout),
_helpMsg(new QLabel)
_helpMsg(new QLabel(this))
{
_helpMsg->setWordWrap(true);
_helpMsg->setAlignment(Qt::AlignCenter);
auto helpWidget = new QWidget(this);
auto helpLayout = new QVBoxLayout(helpWidget);
helpLayout->addWidget(_helpMsg);
helpLayout->setAlignment(Qt::AlignCenter);
auto contentWidget = new QWidget(this);
contentWidget->setLayout(_contentLayout);
_contentLayout->setSpacing(0);
auto helperLayout = new QGridLayout();
helperLayout->addWidget(_helpMsg, 0, 0,
Qt::AlignHCenter | Qt::AlignVCenter);
helperLayout->addLayout(_contentLayout, 0, 0);
helperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
_layout->addLayout(helperLayout, 10);
_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
QSizePolicy::Expanding));
auto wrapper = new QWidget;
wrapper->setLayout(_layout);
setWidget(wrapper);
auto contentWrapper = new QWidget(this);
auto wrapperLayout = new QVBoxLayout(contentWrapper);
wrapperLayout->setContentsMargins(0, 0, 0, 0);
wrapperLayout->addWidget(contentWidget);
// Move macro segments to the top of the list
wrapperLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
QSizePolicy::Expanding));
_stackedWidget->addWidget(helpWidget);
_stackedWidget->addWidget(contentWrapper);
setWidget(_stackedWidget);
setWidgetResizable(true);
setAcceptDrops(true);
SetHelpMsgVisible(true);
connect(verticalScrollBar(), &QScrollBar::valueChanged, this,
[this]() { SetupVisibleMacroSegmentWidgets(); });
}
@@ -107,9 +119,11 @@ void MacroSegmentList::SetHelpMsg(const QString &msg) const
_helpMsg->setText(msg);
}
void MacroSegmentList::SetHelpMsgVisible(bool visible) const
void MacroSegmentList::SetHelpMsgVisible(bool visible)
{
_helpMsg->setVisible(visible);
_stackedWidget->setCurrentIndex(visible ? 0 : 1);
adjustSize();
updateGeometry();
}
void MacroSegmentList::Insert(int idx, QWidget *widget)
@@ -117,23 +131,33 @@ void MacroSegmentList::Insert(int idx, QWidget *widget)
widget->installEventFilter(this);
_contentLayout->insertWidget(idx, widget);
SetupVisibleMacroSegmentWidgets();
SetHelpMsgVisible(false);
adjustSize();
updateGeometry();
}
void MacroSegmentList::Add(QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->addWidget(widget);
SetupVisibleMacroSegmentWidgets();
Insert(_contentLayout->count(), widget);
}
void MacroSegmentList::Remove(int idx) const
void MacroSegmentList::Remove(int idx)
{
DeleteLayoutItemWidget(_contentLayout->takeAt(idx));
adjustSize();
updateGeometry();
if (IsEmpty()) {
SetHelpMsgVisible(true);
}
}
void MacroSegmentList::Clear(int idx) const
void MacroSegmentList::Clear(int idx)
{
ClearLayout(_contentLayout, idx);
adjustSize();
updateGeometry();
SetHelpMsgVisible(true);
}
void MacroSegmentList::SetCachingEnabled(bool enable)
@@ -178,6 +202,8 @@ bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
widget->show();
}
adjustSize();
updateGeometry();
return true;
}
@@ -411,6 +437,24 @@ void MacroSegmentList::SetVisibilityCheckEnable(bool enable)
}
}
bool MacroSegmentList::IsEmpty() const
{
return _contentLayout->count() == 0;
}
QSize MacroSegmentList::minimumSizeHint() const
{
return _stackedWidget->currentWidget()->minimumSizeHint();
}
QSize MacroSegmentList::sizeHint() const
{
const auto contentSize = _stackedWidget->currentWidget()->sizeHint();
const auto hint =
QSize(width(), contentSize.height() + 2 * frameWidth());
return hint;
}
void MacroSegmentList::SetupVisibleMacroSegmentWidgets()
{
if (!_checkVisibility) {
@@ -428,6 +472,7 @@ void MacroSegmentList::SetupVisibleMacroSegmentWidgets()
segment->SetupWidgets();
}
updateGeometry();
}
int MacroSegmentList::GetSegmentIndexFromPos(const QPoint &pos) const

View File

@@ -1,12 +1,14 @@
#pragma once
#include "macro-segment.hpp"
#include <QWidget>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QLabel>
#include <thread>
#include <QLabel>
#include <QScrollArea>
#include <QStackedWidget>
#include <QVBoxLayout>
#include <QWidget>
namespace advss {
class Macro;
@@ -17,51 +19,63 @@ class MacroSegmentList : public QScrollArea {
public:
MacroSegmentList(QWidget *parent = nullptr);
~MacroSegmentList();
void SetHelpMsg(const QString &msg) const;
void SetHelpMsgVisible(bool visible) const;
void SetHelpMsgVisible(bool visible);
MacroSegmentEdit *WidgetAt(int idx) const;
MacroSegmentEdit *WidgetAt(const QPoint &) const;
int IndexAt(const QPoint &) const;
void Insert(int idx, QWidget *widget);
void Add(QWidget *widget);
void Remove(int idx) const;
void Clear(int idx = 0) const; // Clear all elements >= idx
void Remove(int idx);
void Clear(int idx = 0); // Clear all elements >= idx
bool IsEmpty() const;
static void SetCachingEnabled(bool enable);
void CacheCurrentWidgetsFor(const Macro *);
bool PopulateWidgetsFromCache(const Macro *);
void ClearWidgetsFromCacheFor(const Macro *);
void SetVisibilityCheckEnable(bool enable);
void Highlight(int idx, QColor color = QColor(Qt::green));
void SetCollapsed(bool) const;
void SetSelection(int idx) const;
QVBoxLayout *ContentLayout() const { return _contentLayout; }
void SetVisibilityCheckEnable(bool enable);
QSize minimumSizeHint() const override;
QSize sizeHint() const override;
signals:
void SelectionChanged(int idx);
void Reorder(int source, int target);
protected:
bool eventFilter(QObject *object, QEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void dragLeaveEvent(QDragLeaveEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dragMoveEvent(QDragMoveEvent *event);
void dropEvent(QDropEvent *event);
void resizeEvent(QResizeEvent *event);
bool eventFilter(QObject *object, QEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void dragLeaveEvent(QDragLeaveEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
void SetupVisibleMacroSegmentWidgets();
void ClearWidgetCache();
int GetSegmentIndexFromPos(const QPoint &) const;
bool IsInListArea(const QPoint &) const;
QRect GetContentItemRectWithPadding(int idx) const;
int GetDragIndex(const QPoint &) const;
int GetDropIndex(const QPoint &) const;
void CheckScroll() const;
void CheckDropLine(const QPoint &);
bool IsInListArea(const QPoint &) const;
QRect GetContentItemRectWithPadding(int idx) const;
void HideLastDropLine();
void ClearWidgetCache();
int _dragPosition = -1;
int _dropLineIdx = -1;
@@ -69,6 +83,7 @@ private:
std::thread _autoScrollThread;
std::atomic_bool _autoScroll{false};
QStackedWidget *_stackedWidget;
QVBoxLayout *_layout;
QVBoxLayout *_contentLayout;
QLabel *_helpMsg;

View File

@@ -4,6 +4,7 @@
#include "macro-condition-factory.hpp"
#include "macro-helpers.hpp"
#include "macro-segment.hpp"
#include "macro-signals.hpp"
#include "obs-module-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
@@ -31,7 +32,8 @@ MacroSegmentSelection::MacroSegmentSelection(QWidget *parent, Type type,
_index,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(IndexChanged(const NumberVariable<int> &)));
QWidget::connect(window(), SIGNAL(MacroSegmentOrderChanged()), this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(SegmentOrderChanged()), this,
SLOT(MacroSegmentOrderChanged()));
auto layout = new QHBoxLayout;

View File

@@ -0,0 +1,74 @@
#pragma once
#include "macro-action.hpp"
#include "macro-condition.hpp"
#include <obs.hpp>
#include <QLabel>
namespace advss {
// Retains the settings and ID of the macro segment even if they can't be
// interpreted correctly right now.
//
// As soon as a version of the plugin supporting the original ID is running it
// will take over again
template<typename T> class MacroSegmentUnknown : public T {
public:
MacroSegmentUnknown(Macro *macro, const std::string &originalId);
bool CheckCondition() { return false; }
std::shared_ptr<MacroAction> Copy() const;
bool PerformAction() { return true; };
bool Save(obs_data_t *data) const;
bool Load(obs_data_t *data);
std::string GetId() const { return "unknown"; }
private:
std::string _id;
OBSData _settings;
};
template<typename T>
inline MacroSegmentUnknown<T>::MacroSegmentUnknown(
Macro *macro, const std::string &originalId)
: T(macro),
_id(originalId),
_settings(obs_data_create())
{
obs_data_release(_settings);
}
template<typename T>
inline std::shared_ptr<MacroAction> MacroSegmentUnknown<T>::Copy() const
{
if constexpr (std::is_same_v<T, MacroAction>) {
return std::make_shared<MacroSegmentUnknown<T>>(
MacroAction::GetMacro(), _id);
}
return {};
}
template<typename T>
inline bool MacroSegmentUnknown<T>::Save(obs_data_t *data) const
{
T::Save(data);
obs_data_apply(data, _settings);
obs_data_set_string(_settings, "id", _id.c_str());
return true;
};
template<typename T> inline bool MacroSegmentUnknown<T>::Load(obs_data_t *data)
{
T::Load(data);
obs_data_apply(_settings, data);
return true;
};
inline QWidget *CreateUnknownSegmentWidget(bool isAction)
{
return new QLabel(obs_module_text(
isAction ? "AdvSceneSwitcher.action.unknown"
: "AdvSceneSwitcher.condition.unknown"));
}
} // namespace advss

View File

@@ -256,23 +256,6 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent)
QWidget::connect(_section, &Section::Collapsed, this,
&MacroSegmentEdit::Collapsed);
// Macro signals
QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this,
SIGNAL(MacroAdded(const QString &)));
QWidget::connect(parent, SIGNAL(MacroRemoved(const QString &)), this,
SIGNAL(MacroRemoved(const QString &)));
QWidget::connect(
parent, SIGNAL(MacroRenamed(const QString &, const QString &)),
this, SIGNAL(MacroRenamed(const QString &, const QString &)));
// Scene group signals
QWidget::connect(parent, SIGNAL(SceneGroupAdded(const QString &)), this,
SIGNAL(SceneGroupAdded(const QString &)));
QWidget::connect(parent, SIGNAL(SceneGroupRemoved(const QString &)),
this, SIGNAL(SceneGroupRemoved(const QString &)));
QWidget::connect(
parent,
SIGNAL(SceneGroupRenamed(const QString &, const QString)), this,
SIGNAL(SceneGroupRenamed(const QString &, const QString)));
auto frameLayout = new QGridLayout;
frameLayout->setContentsMargins(0, 0, 0, 0);

View File

@@ -113,13 +113,6 @@ public slots:
protected slots:
void Collapsed(bool) const;
signals:
void MacroAdded(const QString &name);
void MacroRemoved(const QString &name);
void MacroRenamed(const QString &oldName, const QString &newName);
void SceneGroupAdded(const QString &name);
void SceneGroupRemoved(const QString &name);
void SceneGroupRenamed(const QString &oldName, const QString newName);
protected:
void SetDisableEffect(bool);

View File

@@ -2,6 +2,7 @@
#include "advanced-scene-switcher.hpp"
#include "layout-helpers.hpp"
#include "macro.hpp"
#include "macro-signals.hpp"
#include "ui-helpers.hpp"
#include <QDialogButtonBox>
@@ -20,15 +21,14 @@ MacroSelection::MacroSelection(QWidget *parent)
addItem(QString::fromStdString(m->Name()));
}
QWidget::connect(GetSettingsWindow(),
SIGNAL(MacroAdded(const QString &)), this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(Add(const QString &)), this,
SLOT(MacroAdd(const QString &)));
QWidget::connect(GetSettingsWindow(),
SIGNAL(MacroRemoved(const QString &)), this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(Remove(const QString &)), this,
SLOT(MacroRemove(const QString &)));
QWidget::connect(GetSettingsWindow(),
SIGNAL(MacroRenamed(const QString &, const QString &)),
this,
QWidget::connect(MacroSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(MacroRename(const QString &, const QString &)));
}
@@ -49,7 +49,7 @@ void MacroSelection::SetCurrentMacro(const MacroRef &macro)
void MacroSelection::HideSelectedMacro()
{
auto ssWindow = static_cast<AdvSceneSwitcher *>(window());
auto ssWindow = static_cast<AdvSceneSwitcher *>(GetSettingsWindow());
if (!ssWindow) {
return;
}

View File

@@ -94,6 +94,9 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
_currentInputs(new MacroInputSelection()),
_currentMacroRegisterDock(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentRegisterDock"))),
_currentMacroIsStandaloneDock(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentIsStandaloneDock"))),
_currentMacroDockWindowName(new QLineEdit(this)),
_currentMacroDockAddRunButton(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockAddRunButton"))),
_currentMacroDockAddPauseButton(new QCheckBox(obs_module_text(
@@ -198,6 +201,15 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
int row = 0;
_dockLayout->addWidget(_currentMacroRegisterDock, row, 1, 1, 2);
row++;
_dockLayout->addWidget(_currentMacroIsStandaloneDock, row, 1, 1, 2);
row++;
_dockLayout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockWindowName")),
row, 1);
_dockLayout->addWidget(_currentMacroDockWindowName, row, 2);
_dockWindowNameRow = row;
row++;
_dockLayout->addWidget(_currentMacroDockAddRunButton, row, 1, 1, 2);
row++;
_dockLayout->addWidget(
@@ -252,6 +264,8 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
connect(_currentMacroRegisterDock, &QCheckBox::stateChanged, this,
&MacroSettingsDialog::DockEnableChanged);
connect(_currentMacroIsStandaloneDock, &QCheckBox::stateChanged, this,
&MacroSettingsDialog::IsStandaloneDockChanged);
connect(_currentMacroDockAddRunButton, &QCheckBox::stateChanged, this,
&MacroSettingsDialog::RunButtonEnableChanged);
connect(_currentMacroDockAddPauseButton, &QCheckBox::stateChanged, this,
@@ -329,55 +343,37 @@ MacroSettingsDialog::MacroSettingsDialog(QWidget *parent,
_currentSkipOnStartup->setChecked(macro->SkipExecOnStart());
_currentStopActionsIfNotDone->setChecked(macro->StopActionsIfNotDone());
_currentInputs->SetInputs(macro->GetInputVariables());
const bool dockEnabled = macro->DockEnabled();
const auto &dockSettings = macro->GetDockSettings();
const bool dockEnabled = dockSettings.DockEnabled();
_currentMacroRegisterDock->setChecked(dockEnabled);
_currentMacroDockAddRunButton->setChecked(macro->DockHasRunButton());
_currentMacroIsStandaloneDock->setChecked(
dockSettings.IsStandaloneDock());
_currentMacroDockWindowName->setText(
QString::fromStdString(dockSettings.DockWindowName()));
_currentMacroDockAddRunButton->setChecked(dockSettings.HasRunButton());
_currentMacroDockAddPauseButton->setChecked(
macro->DockHasPauseButton());
dockSettings.HasPauseButton());
_currentMacroDockAddStatusLabel->setChecked(
macro->DockHasStatusLabel());
dockSettings.HasStatusLabel());
_currentMacroDockHighlightIfConditionsTrue->setChecked(
macro->DockHighlightEnabled());
_runButtonText->setText(macro->RunButtonText());
_pauseButtonText->setText(macro->PauseButtonText());
_unpauseButtonText->setText(macro->UnpauseButtonText());
_conditionsTrueStatusText->setText(macro->ConditionsTrueStatusText());
_conditionsFalseStatusText->setText(macro->ConditionsFalseStatusText());
dockSettings.HighlightEnabled());
_runButtonText->setText(dockSettings.RunButtonText());
_pauseButtonText->setText(dockSettings.PauseButtonText());
_unpauseButtonText->setText(dockSettings.UnpauseButtonText());
_conditionsTrueStatusText->setText(
dockSettings.ConditionsTrueStatusText());
_conditionsFalseStatusText->setText(
dockSettings.ConditionsFalseStatusText());
_currentMacroDockAddRunButton->setVisible(dockEnabled);
_currentMacroDockAddPauseButton->setVisible(dockEnabled);
_currentMacroDockAddStatusLabel->setVisible(dockEnabled);
_currentMacroDockHighlightIfConditionsTrue->setVisible(dockEnabled);
SetGridLayoutRowVisible(_dockLayout, _runButtonTextRow,
dockEnabled && macro->DockHasRunButton());
SetGridLayoutRowVisible(_dockLayout, _pauseButtonTextRow,
dockEnabled && macro->DockHasPauseButton());
SetGridLayoutRowVisible(_dockLayout, _unpauseButtonTextRow,
dockEnabled && macro->DockHasPauseButton());
SetGridLayoutRowVisible(_dockLayout, _conditionsTrueTextRow,
dockEnabled && macro->DockHasStatusLabel());
SetGridLayoutRowVisible(_dockLayout, _conditionsFalseTextRow,
dockEnabled && macro->DockHasStatusLabel());
DockEnableChanged(dockEnabled);
MinimizeSizeOfColumn(_dockLayout, 0);
Resize();
// Try to set sensible initial size for the dialog window
QSize contentSize = contentWidget->sizeHint();
resize(contentSize.width() + layout->contentsMargins().left() +
layout->contentsMargins().right() +
dialogLayout->contentsMargins().left() +
dialogLayout->contentsMargins().right() +
scrollArea->verticalScrollBar()->sizeHint().width() + 20,
contentSize.height() + dialogLayout->spacing() +
buttonbox->sizeHint().height() +
dialogLayout->contentsMargins().top() +
dialogLayout->contentsMargins().bottom() +
scrollArea->horizontalScrollBar()->sizeHint().height() +
20);
Resize();
}
void MacroSettingsDialog::DockEnableChanged(int enabled)
{
_currentMacroIsStandaloneDock->setVisible(enabled);
_currentMacroDockAddRunButton->setVisible(enabled);
_currentMacroDockAddPauseButton->setVisible(enabled);
_currentMacroDockAddStatusLabel->setVisible(enabled);
@@ -401,6 +397,12 @@ void MacroSettingsDialog::DockEnableChanged(int enabled)
Resize();
}
void MacroSettingsDialog::IsStandaloneDockChanged(int enabled)
{
SetGridLayoutRowVisible(_dockLayout, _dockWindowNameRow, !enabled);
Resize();
}
void MacroSettingsDialog::RunButtonEnableChanged(int enabled)
{
SetGridLayoutRowVisible(_dockLayout, _runButtonTextRow, enabled);
@@ -478,23 +480,30 @@ bool MacroSettingsDialog::AskForSettings(QWidget *parent,
macro->SetSkipExecOnStart(dialog._currentSkipOnStartup->isChecked());
macro->SetStopActionsIfNotDone(
dialog._currentStopActionsIfNotDone->isChecked());
macro->EnableDock(dialog._currentMacroRegisterDock->isChecked());
macro->SetDockHasRunButton(
auto &dockSettings = macro->GetDockSettings();
dockSettings.EnableDock(dialog._currentMacroRegisterDock->isChecked());
dockSettings.SetIsStandaloneDock(
dialog._currentMacroIsStandaloneDock->isChecked());
dockSettings.SetDockWindowName(
dialog._currentMacroDockWindowName->text().toStdString());
dockSettings.SetHasRunButton(
dialog._currentMacroDockAddRunButton->isChecked());
macro->SetDockHasPauseButton(
dockSettings.SetHasPauseButton(
dialog._currentMacroDockAddPauseButton->isChecked());
macro->SetDockHasStatusLabel(
dockSettings.SetHasStatusLabel(
dialog._currentMacroDockAddStatusLabel->isChecked());
macro->SetHighlightEnable(
dockSettings.SetHighlightEnable(
dialog._currentMacroDockHighlightIfConditionsTrue->isChecked());
macro->SetRunButtonText(dialog._runButtonText->text().toStdString());
macro->SetPauseButtonText(
dockSettings.SetRunButtonText(
dialog._runButtonText->text().toStdString());
dockSettings.SetPauseButtonText(
dialog._pauseButtonText->text().toStdString());
macro->SetUnpauseButtonText(
dockSettings.SetUnpauseButtonText(
dialog._unpauseButtonText->text().toStdString());
macro->SetConditionsTrueStatusText(
dockSettings.SetConditionsTrueStatusText(
dialog._conditionsTrueStatusText->text().toStdString());
macro->SetConditionsFalseStatusText(
dockSettings.SetConditionsFalseStatusText(
dialog._conditionsFalseStatusText->text().toStdString());
macro->SetInputVariables(dialog._currentInputs->GetInputs());
return true;

View File

@@ -41,6 +41,7 @@ public:
Macro *macro);
private slots:
void DockEnableChanged(int);
void IsStandaloneDockChanged(int);
void RunButtonEnableChanged(int);
void PauseButtonEnableChanged(int);
void StatusLabelEnableChanged(int);
@@ -69,6 +70,8 @@ private:
QCheckBox *_currentStopActionsIfNotDone;
MacroInputSelection *_currentInputs;
QCheckBox *_currentMacroRegisterDock;
QCheckBox *_currentMacroIsStandaloneDock;
QLineEdit *_currentMacroDockWindowName;
QCheckBox *_currentMacroDockAddRunButton;
QCheckBox *_currentMacroDockAddPauseButton;
QCheckBox *_currentMacroDockAddStatusLabel;
@@ -81,6 +84,7 @@ private:
QGroupBox *_dockOptions;
QGridLayout *_dockLayout;
int _dockWindowNameRow = -1;
int _runButtonTextRow = -1;
int _pauseButtonTextRow = -1;
int _unpauseButtonTextRow = -1;

View File

@@ -0,0 +1,13 @@
#include "macro-signals.hpp"
namespace advss {
MacroSignalManager::MacroSignalManager(QObject *parent) : QObject(parent) {}
MacroSignalManager *MacroSignalManager::Instance()
{
static MacroSignalManager manager;
return &manager;
}
} // namespace advss

View File

@@ -0,0 +1,26 @@
#pragma once
#include "export-symbol-helper.hpp"
#include <QObject>
#include <QString>
namespace advss {
class ADVSS_EXPORT MacroSignalManager : public QObject {
Q_OBJECT
public:
static MacroSignalManager *Instance();
signals:
void Rename(const QString &, const QString &);
void Add(const QString &);
void Remove(const QString &);
void SegmentOrderChanged();
void HighlightChanged(bool value);
private:
MacroSignalManager(QObject *parent = nullptr);
};
} // namespace advss

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
#include "macro-tree.hpp"
#include "macro.hpp"
#include "macro-signals.hpp"
#include "path-helpers.hpp"
#include "sync-helpers.hpp"
#include "ui-helpers.hpp"
@@ -79,10 +80,10 @@ MacroTreeItem::MacroTreeItem(MacroTree *tree, std::shared_ptr<Macro> macroItem,
_macro->SetPaused(!val);
};
connect(_running, &QAbstractButton::clicked, setRunning);
connect(_tree->window(), SIGNAL(HighlightMacrosChanged(bool)), this,
SLOT(EnableHighlight(bool)));
connect(_tree->window(),
SIGNAL(MacroRenamed(const QString &, const QString &)), this,
connect(MacroSignalManager::Instance(), SIGNAL(HighlightChanged(bool)),
this, SLOT(EnableHighlight(bool)));
connect(MacroSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(MacroRenamed(const QString &, const QString &)));
connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightIfExecuted()));
connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
@@ -414,6 +415,20 @@ void MacroTreeModel::Add(std::shared_ptr<Macro> item)
QItemSelectionModel::Select);
}
void MacroTreeModel::MoveToBeginningOfGroup(std::shared_ptr<Macro> item,
std::shared_ptr<Macro> group)
{
auto it = std::find(_macros.begin(), _macros.end(), item);
assert(it != _macros.end());
std::shared_ptr<Macro> tmp = *it;
_macros.erase(it);
it = std::find(_macros.begin(), _macros.end(), group);
assert(it != _macros.end());
_macros.insert(std::next(it), tmp);
Reset(_macros);
}
void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
{
auto lock = LockContext();
@@ -789,6 +804,13 @@ void MacroTree::Add(std::shared_ptr<Macro> item,
assert(GetModel()->IsInValidState());
}
void MacroTree::AddToGroup(std::shared_ptr<Macro> item,
std::shared_ptr<Macro> group) const
{
GetModel()->Add(item);
GetModel()->MoveToBeginningOfGroup(item, group);
}
std::shared_ptr<Macro> MacroTree::GetCurrentMacro() const
{
return GetModel()->GetCurrentMacro();
@@ -1160,6 +1182,28 @@ bool MacroTree::SelectionEmpty() const
return selectedIndexes().empty();
}
bool MacroTree::GroupsExist() const
{
for (const auto &macro : GetMacros()) {
if (macro->IsGroup()) {
return true;
}
}
return false;
}
void MacroTree::ExpandGroup(std::shared_ptr<Macro> item) const
{
auto *mtm = GetModel();
mtm->ExpandGroup(item);
}
void MacroTree::CollapseGroup(std::shared_ptr<Macro> item) const
{
auto *mtm = GetModel();
mtm->CollapseGroup(item);
}
void MacroTree::MoveItemBefore(const std::shared_ptr<Macro> &item,
const std::shared_ptr<Macro> &after) const
{
@@ -1260,6 +1304,26 @@ void MacroTree::UngroupSelectedGroups()
assert(GetModel()->IsInValidState());
}
void MacroTree::ExpandAll()
{
for (const auto &macro : GetMacros()) {
if (!macro->IsGroup()) {
continue;
}
ExpandGroup(macro);
}
}
void MacroTree::CollapseAll()
{
for (const auto &macro : GetMacros()) {
if (!macro->IsGroup()) {
continue;
}
CollapseGroup(macro);
}
}
void MacroTree::SelectionChangedHelper(const QItemSelection &,
const QItemSelection &)
{

View File

@@ -91,6 +91,8 @@ private:
void MoveItemAfter(const std::shared_ptr<Macro> &item,
const std::shared_ptr<Macro> &after);
void Add(std::shared_ptr<Macro> item);
void MoveToBeginningOfGroup(std::shared_ptr<Macro> item,
std::shared_ptr<Macro>);
void Remove(std::shared_ptr<Macro> item);
std::shared_ptr<Macro> Neighbor(const std::shared_ptr<Macro> &m,
bool above) const;
@@ -126,6 +128,8 @@ public:
void Reset(std::deque<std::shared_ptr<Macro>> &, bool highlight);
void Add(std::shared_ptr<Macro> item,
std::shared_ptr<Macro> after = {}) const;
void AddToGroup(std::shared_ptr<Macro> item,
std::shared_ptr<Macro> group) const;
void Remove(std::shared_ptr<Macro> item) const;
void Up(std::shared_ptr<Macro> item) const;
void Down(std::shared_ptr<Macro> item) const;
@@ -135,10 +139,15 @@ public:
bool GroupedItemsSelected() const;
bool SingleItemSelected() const;
bool SelectionEmpty() const;
bool GroupsExist() const;
void ExpandGroup(std::shared_ptr<Macro> item) const;
void CollapseGroup(std::shared_ptr<Macro> item) const;
public slots:
void GroupSelectedItems();
void UngroupSelectedGroups();
void ExpandAll();
void CollapseAll();
void SelectionChangedHelper(const QItemSelection &,
const QItemSelection &);

View File

@@ -1,7 +1,6 @@
#include "macro.hpp"
#include "macro-action-factory.hpp"
#include "macro-condition-factory.hpp"
#include "macro-dock.hpp"
#include "macro-helpers.hpp"
#include "macro-settings.hpp"
#include "plugin-state-helpers.hpp"
@@ -15,69 +14,12 @@
#include <QAction>
#include <QMainWindow>
#include <unordered_map>
#include <util/platform.h>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(30, 0, 0)
#include <QDockWidget>
namespace {
struct DockMapEntry {
QAction *action = nullptr;
QWidget *dock = nullptr;
};
} // namespace
static std::unordered_map<const char *, DockMapEntry> dockIds;
static std::mutex dockMutex;
static bool obs_frontend_add_dock_by_id(const char *id, const char *title,
QWidget *widget)
{
std::lock_guard<std::mutex> lock(dockMutex);
if (dockIds.count(id) > 0) {
return false;
}
widget->setObjectName(id);
auto dock = new QDockWidget();
dock->setWindowTitle(title);
dock->setWidget(widget);
dock->setFloating(true);
dock->setVisible(false);
dock->setFeatures(QDockWidget::DockWidgetClosable |
QDockWidget::DockWidgetMovable |
QDockWidget::DockWidgetFloatable);
auto action = static_cast<QAction *>(obs_frontend_add_dock(dock));
if (!action) {
return false;
}
dockIds[id] = {action, dock};
return true;
}
static void obs_frontend_remove_dock(const char *id)
{
std::lock_guard<std::mutex> lock(dockMutex);
auto it = dockIds.find(id);
if (it == dockIds.end()) {
return;
}
it->second.action->deleteLater();
it->second.dock->deleteLater();
dockIds.erase(it);
}
#endif
namespace advss {
static std::deque<std::shared_ptr<Macro>> macros;
Macro::Macro(const std::string &name)
Macro::Macro(const std::string &name) : _dockSettings(this)
{
SetName(name);
}
@@ -99,12 +41,6 @@ Macro::~Macro()
_die = true;
Stop();
ClearHotkeys();
// Keep the dock widgets in case of shutdown so they can be restored by
// OBS on startup
if (!OBSIsShuttingDown()) {
RemoveDock();
}
}
std::shared_ptr<Macro>
@@ -412,15 +348,9 @@ bool Macro::ShouldRunActions() const
void Macro::SetName(const std::string &name)
{
const bool nameChanged = _name == name;
_name = name;
SetHotkeysDesc();
if (nameChanged) {
_dockId = GenerateDockId();
}
EnableDock(_registerDock);
_dockSettings.HandleMacroNameChange();
}
void Macro::ResetTimers()
@@ -605,7 +535,7 @@ void Macro::SetInputVariables(const MacroInputVariables &inputVariables)
_inputVariables = inputVariables;
}
std::vector<TempVariable> Macro::GetTempVars(MacroSegment *filter) const
std::vector<TempVariable> Macro::GetTempVars(const MacroSegment *filter) const
{
std::vector<TempVariable> res;
@@ -633,7 +563,7 @@ std::vector<TempVariable> Macro::GetTempVars(MacroSegment *filter) const
return ptr.get() == segment;
}) != _conditions.end();
};
auto isAction = [this](MacroSegment *segment) -> bool {
auto isAction = [this](const MacroSegment *segment) -> bool {
return std::find_if(_actions.begin(), _actions.end(),
[segment](
const std::shared_ptr<MacroSegment>
@@ -641,7 +571,7 @@ std::vector<TempVariable> Macro::GetTempVars(MacroSegment *filter) const
return ptr.get() == segment;
}) != _actions.end();
};
auto isElseAction = [this](MacroSegment *segment) -> bool {
auto isElseAction = [this](const MacroSegment *segment) -> bool {
return std::find_if(_elseActions.begin(), _elseActions.end(),
[segment](
const std::shared_ptr<MacroSegment>
@@ -819,7 +749,7 @@ bool Macro::Save(obs_data_t *obj, bool saveForCopy) const
_useCustomConditionCheckInterval);
_customConditionCheckInterval.Save(obj, "customConditionCheckInterval");
SaveDockSettings(obj, saveForCopy);
_dockSettings.Save(obj, saveForCopy);
SaveSplitterPos(_actionConditionSplitterPosition, obj,
"macroActionConditionSplitterPosition");
@@ -905,7 +835,7 @@ bool Macro::Load(obs_data_t *obj)
obs_data_get_bool(obj, "useCustomConditionCheckInterval");
_customConditionCheckInterval.Load(obj, "customConditionCheckInterval");
LoadDockSettings(obj);
_dockSettings.Load(obj);
LoadSplitterPos(_actionConditionSplitterPosition, obj,
"macroActionConditionSplitterPosition");
@@ -984,7 +914,7 @@ bool Macro::Load(obs_data_t *obj)
auto newEntry = MacroActionFactory::Create(id, this);
if (newEntry) {
_elseActions.emplace_back(newEntry);
auto action = _actions.back().get();
auto action = _elseActions.back().get();
action->WithLock([action, &arrayObj]() {
action->Load(arrayObj);
});
@@ -1095,224 +1025,6 @@ bool Macro::PauseHotkeysEnabled() const
return _registerHotkeys;
}
void Macro::SaveDockSettings(obs_data_t *obj, bool saveForCopy) const
{
auto dockSettings = obs_data_create();
obs_data_set_bool(dockSettings, "register", _registerDock);
obs_data_set_bool(dockSettings, "hasRunButton", _dockHasRunButton);
obs_data_set_bool(dockSettings, "hasPauseButton", _dockHasPauseButton);
obs_data_set_bool(dockSettings, "hasStatusLabel", _dockHasStatusLabel);
obs_data_set_bool(dockSettings, "highlightIfConditionsTrue",
_dockHighlight);
_runButtonText.Save(dockSettings, "runButtonText");
_pauseButtonText.Save(dockSettings, "pauseButtonText");
_unpauseButtonText.Save(dockSettings, "unpauseButtonText");
_conditionsTrueStatusText.Save(dockSettings,
"conditionsTrueStatusText");
_conditionsFalseStatusText.Save(dockSettings,
"conditionsFalseStatusText");
if (saveForCopy) {
auto uuid = GenerateDockId();
obs_data_set_string(dockSettings, "dockId", uuid.c_str());
} else {
obs_data_set_string(dockSettings, "dockId", _dockId.c_str());
}
obs_data_set_int(dockSettings, "version", 1);
obs_data_set_obj(obj, "dockSettings", dockSettings);
obs_data_release(dockSettings);
}
void Macro::LoadDockSettings(obs_data_t *obj)
{
auto dockSettings = obs_data_get_obj(obj, "dockSettings");
if (!dockSettings) {
// TODO: Remove this fallback
_dockHasRunButton = obs_data_get_bool(obj, "dockHasRunButton");
_dockHasPauseButton =
obs_data_get_bool(obj, "dockHasPauseButton");
EnableDock(obs_data_get_bool(obj, "registerDock"));
return;
}
if (!obs_data_has_user_value(dockSettings, "version")) {
_dockId = std::string("ADVSS-") + _name;
} else {
_dockId = obs_data_get_string(dockSettings, "dockId");
}
const bool dockEnabled = obs_data_get_bool(dockSettings, "register");
// TODO: remove these default settings in a future version
obs_data_set_default_string(
dockSettings, "runButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.run"));
obs_data_set_default_string(
dockSettings, "pauseButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.pause"));
obs_data_set_default_string(
dockSettings, "unpauseButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.unpause"));
_runButtonText.Load(dockSettings, "runButtonText");
_pauseButtonText.Load(dockSettings, "pauseButtonText");
_unpauseButtonText.Load(dockSettings, "unpauseButtonText");
_conditionsTrueStatusText.Load(dockSettings,
"conditionsTrueStatusText");
_conditionsFalseStatusText.Load(dockSettings,
"conditionsFalseStatusText");
if (dockEnabled) {
_dockHasRunButton =
obs_data_get_bool(dockSettings, "hasRunButton");
_dockHasPauseButton =
obs_data_get_bool(dockSettings, "hasPauseButton");
_dockHasStatusLabel =
obs_data_get_bool(dockSettings, "hasStatusLabel");
_dockHighlight = obs_data_get_bool(dockSettings,
"highlightIfConditionsTrue");
}
EnableDock(dockEnabled);
obs_data_release(dockSettings);
}
void Macro::EnableDock(bool value)
{
// Reset dock regardless
RemoveDock();
if (!value) {
_registerDock = value;
return;
}
_dock = new MacroDock(GetWeakMacroByName(_name.c_str()), _runButtonText,
_pauseButtonText, _unpauseButtonText,
_conditionsTrueStatusText,
_conditionsFalseStatusText, _dockHighlight);
if (!obs_frontend_add_dock_by_id(_dockId.c_str(), _name.c_str(),
_dock)) {
blog(LOG_INFO, "failed to add macro dock for macro %s",
_name.c_str());
_dock->deleteLater();
_dock = nullptr;
_registerDock = false;
return;
}
_registerDock = value;
}
void Macro::SetDockHasRunButton(bool value)
{
_dockHasRunButton = value;
if (!_dock) {
return;
}
_dock->ShowRunButton(value);
}
void Macro::SetDockHasPauseButton(bool value)
{
_dockHasPauseButton = value;
if (!_dock) {
return;
}
_dock->ShowPauseButton(value);
}
void Macro::SetDockHasStatusLabel(bool value)
{
_dockHasStatusLabel = value;
if (!_dock) {
return;
}
_dock->ShowStatusLabel(value);
}
void Macro::SetHighlightEnable(bool value)
{
_dockHighlight = value;
if (!_dock) {
return;
}
_dock->EnableHighlight(value);
}
void Macro::SetRunButtonText(const std::string &text)
{
_runButtonText = text;
if (!_dock) {
return;
}
_dock->SetRunButtonText(text);
}
void Macro::SetPauseButtonText(const std::string &text)
{
_pauseButtonText = text;
if (!_dock) {
return;
}
_dock->SetPauseButtonText(text);
}
void Macro::SetUnpauseButtonText(const std::string &text)
{
_unpauseButtonText = text;
if (!_dock) {
return;
}
_dock->SetUnpauseButtonText(text);
}
void Macro::SetConditionsTrueStatusText(const std::string &text)
{
_conditionsTrueStatusText = text;
if (!_dock) {
return;
}
_dock->SetConditionsTrueText(text);
}
StringVariable Macro::ConditionsTrueStatusText() const
{
return _conditionsTrueStatusText;
}
void Macro::SetConditionsFalseStatusText(const std::string &text)
{
_conditionsFalseStatusText = text;
if (!_dock) {
return;
}
_dock->SetConditionsFalseText(text);
}
StringVariable Macro::ConditionsFalseStatusText() const
{
return _conditionsFalseStatusText;
}
void Macro::RemoveDock()
{
obs_frontend_remove_dock(_dockId.c_str());
_dock = nullptr;
}
std::string Macro::GenerateDockId()
{
#if LIBOBS_API_VER > MAKE_SEMANTIC_VERSION(30, 0, 0)
auto uuid = os_generate_uuid();
auto id = std::string("advss-macro-dock-") + std::string(uuid);
bfree(uuid);
return id;
#else
static std::atomic_int16_t idCounter = 0;
return std::to_string(++idCounter);
#endif
}
static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
{
if (pressed) {

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro-action.hpp"
#include "macro-condition.hpp"
#include "macro-dock-settings.hpp"
#include "macro-helpers.hpp"
#include "macro-input.hpp"
#include "macro-ref.hpp"
@@ -20,7 +21,6 @@
namespace advss {
class MacroDock;
class GlobalMacroSettings;
class Macro {
@@ -88,7 +88,7 @@ public:
void SetInputVariables(const MacroInputVariables &);
// Temporary variable helpers
std::vector<TempVariable> GetTempVars(MacroSegment *filter) const;
std::vector<TempVariable> GetTempVars(const MacroSegment *filter) const;
std::optional<const TempVariable>
GetTempVar(const MacroSegment *, const std::string &id) const;
void InvalidateTempVarValues() const;
@@ -147,26 +147,7 @@ public:
bool PauseHotkeysEnabled() const;
// Docks
void EnableDock(bool);
bool DockEnabled() const { return _registerDock; }
void SetDockHasRunButton(bool value);
bool DockHasRunButton() const { return _dockHasRunButton; }
void SetDockHasPauseButton(bool value);
bool DockHasPauseButton() const { return _dockHasPauseButton; }
void SetDockHasStatusLabel(bool value);
bool DockHasStatusLabel() const { return _dockHasStatusLabel; }
void SetHighlightEnable(bool value);
bool DockHighlightEnabled() const { return _dockHighlight; }
StringVariable RunButtonText() const { return _runButtonText; }
void SetRunButtonText(const std::string &text);
StringVariable PauseButtonText() const { return _pauseButtonText; }
void SetPauseButtonText(const std::string &text);
StringVariable UnpauseButtonText() const { return _unpauseButtonText; }
void SetUnpauseButtonText(const std::string &text);
void SetConditionsTrueStatusText(const std::string &text);
StringVariable ConditionsTrueStatusText() const;
void SetConditionsFalseStatusText(const std::string &text);
StringVariable ConditionsFalseStatusText() const;
MacroDockSettings &GetDockSettings() { return _dockSettings; }
private:
void SetupHotkeys();
@@ -182,11 +163,6 @@ private:
bool RunActions(bool ignorePause);
bool RunElseActions(bool ignorePause);
void SaveDockSettings(obs_data_t *obj, bool saveForCopy) const;
void LoadDockSettings(obs_data_t *obj);
void RemoveDock();
static std::string GenerateDockId();
std::string _name = "";
bool _die = false;
bool _stop = false;
@@ -236,23 +212,7 @@ private:
QList<int> _actionConditionSplitterPosition;
QList<int> _elseActionSplitterPosition;
bool _registerDock = false;
bool _dockHasRunButton = true;
bool _dockHasPauseButton = true;
bool _dockHasStatusLabel = false;
bool _dockHighlight = false;
StringVariable _runButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.run");
StringVariable _pauseButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.pause");
StringVariable _unpauseButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.unpause");
StringVariable _conditionsTrueStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.true");
StringVariable _conditionsFalseStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.false");
MacroDock *_dock = nullptr;
std::string _dockId = GenerateDockId();
MacroDockSettings _dockSettings;
};
void LoadMacros(obs_data_t *obj);

View File

@@ -44,11 +44,6 @@ void SwitcherData::Prune()
}
}
if (nonMatchingScene && !WeakSourceValid(nonMatchingScene)) {
switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
nonMatchingScene = nullptr;
}
for (size_t i = 0; i < randomSwitches.size(); i++) {
RandomSwitch &s = randomSwitches[i];
if (!s.valid()) {
@@ -168,31 +163,6 @@ void SwitcherData::SaveVersion(obs_data_t *obj,
obs_data_set_string(obj, "version", currentVersion.c_str());
}
void SwitcherData::RunPostLoadSteps()
{
for (const auto &func : postLoadSteps) {
func();
}
postLoadSteps.clear();
}
void SwitcherData::AddSaveStep(std::function<void(obs_data_t *)> function)
{
std::lock_guard<std::mutex> lock(switcher->m);
saveSteps.emplace_back(function);
}
void SwitcherData::AddLoadStep(std::function<void(obs_data_t *)> function)
{
std::lock_guard<std::mutex> lock(switcher->m);
loadSteps.emplace_back(function);
}
void SwitcherData::AddPostLoadStep(std::function<void()> function)
{
postLoadSteps.emplace_back(function);
}
static void startHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *, bool pressed)
{
if (pressed) {

View File

@@ -14,10 +14,12 @@
#include "switch-sequence.hpp"
#include "switch-video.hpp"
#include "macro-settings.hpp"
#include "duration-control.hpp"
#include "priority-helper.hpp"
#include "plugin-state-helpers.hpp"
#include "priority-helper.hpp"
#include "regex-config.hpp"
#include "scene-selection.hpp"
#include "variable-string.hpp"
#include <condition_variable>
#include <vector>
@@ -57,15 +59,12 @@ public:
bool AnySceneTransitionStarted();
void SetPreconditions();
void AddSaveStep(std::function<void(obs_data_t *)>);
void AddLoadStep(std::function<void(obs_data_t *)>);
void AddPostLoadStep(std::function<void()>);
void RunPostLoadSteps();
bool CheckForMatch(OBSWeakSource &scene, OBSWeakSource &transition,
int &linger, bool &setPreviousSceneAsMatch,
bool &macroMatch);
void CheckNoMatchSwitch(bool &match, OBSWeakSource &scene,
OBSWeakSource &transition, int &sleep);
void CheckAutoStart();
/* --- Start of saving / loading section --- */
@@ -95,13 +94,9 @@ public:
bool stop = false;
std::condition_variable cv;
std::vector<std::function<void(obs_data_t *)>> saveSteps;
std::vector<std::function<void(obs_data_t *)>> loadSteps;
std::vector<std::function<void()>> postLoadSteps;
bool firstBoot = true;
bool transitionActive = false;
bool sceneColletionStop = false;
bool sceneCollectionStop = false;
bool obsIsShuttingDown = false;
bool firstInterval = true;
bool firstIntervalAfterStop = true;
@@ -114,7 +109,7 @@ public:
/* --- Start of General tab section --- */
int interval = default_interval;
OBSWeakSource nonMatchingScene;
SceneSelection nonMatchingScene;
NoMatchBehavior switchIfNotMatching = NoMatchBehavior::NO_SWITCH;
Duration noMatchDelay;
enum class StartupBehavior { PERSIST = 0, START = 1, STOP = 2 };
@@ -126,6 +121,10 @@ public:
RECORINDG_OR_STREAMING
};
AutoStart autoStartEvent = AutoStart::NEVER;
bool useAutoStartScene = false;
SceneSelection autoStartScene;
StringVariable autoStartSceneName;
RegexConfig autoStartSceneRegex;
bool enableCooldown = false;
Duration cooldown;
bool showSystemTrayNotifications = false;

View File

@@ -2,31 +2,31 @@
namespace advss {
AutoUpdateTooltipLabel::AutoUpdateTooltipLabel(
AutoUpdateHelpIcon::AutoUpdateHelpIcon(
QWidget *parent, const std::function<QString()> &updateTooltipCallback,
int updateIntervalMs)
: QLabel(parent),
: HelpIcon("", parent),
_callback(updateTooltipCallback),
_timer(new QTimer(this)),
_updateIntervalMs(updateIntervalMs)
{
connect(_timer, &QTimer::timeout, this,
&AutoUpdateTooltipLabel::UpdateTooltip);
&AutoUpdateHelpIcon::UpdateTooltip);
}
void AutoUpdateTooltipLabel::enterEvent(QEnterEvent *event)
void AutoUpdateHelpIcon::enterEvent(QEnterEvent *event)
{
_timer->start(_updateIntervalMs);
QLabel::enterEvent(event);
}
void AutoUpdateTooltipLabel::leaveEvent(QEvent *event)
void AutoUpdateHelpIcon::leaveEvent(QEvent *event)
{
_timer->stop();
QLabel::leaveEvent(event);
}
void AutoUpdateTooltipLabel::UpdateTooltip()
void AutoUpdateHelpIcon::UpdateTooltip()
{
setToolTip(_callback());
}

View File

@@ -1,20 +1,19 @@
#pragma once
#include "export-symbol-helper.hpp"
#include "help-icon.hpp"
#include <functional>
#include <QLabel>
#include <QTimer>
namespace advss {
class ADVSS_EXPORT AutoUpdateTooltipLabel : public QLabel {
class ADVSS_EXPORT AutoUpdateHelpIcon : public HelpIcon {
Q_OBJECT
public:
AutoUpdateTooltipLabel(
QWidget *parent,
const std::function<QString()> &updateTooltipCallback,
int updateIntervalMs = 300);
AutoUpdateHelpIcon(QWidget *parent,
const std::function<QString()> &updateTooltipCallback,
int updateIntervalMs = 300);
protected:
void enterEvent(QEnterEvent *event) override;

View File

@@ -0,0 +1,296 @@
#include "canvas-helpers.hpp"
#include "obs-module-helper.hpp"
#include "scene-group.hpp"
#include "scene-switch-helpers.hpp"
#include "selection-helpers.hpp"
#include "source-helpers.hpp"
#include "ui-helpers.hpp"
#include "utility.hpp"
#include "variable.hpp"
#include <obs-frontend-api.h>
#include <QLayout>
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
Q_DECLARE_METATYPE(advss::OBSWeakCanvas);
#else
Q_DECLARE_METATYPE(OBSWeakCanvas);
#endif
namespace advss {
void CanvasSelection::Populate()
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 1, 0)
static const auto enumCanvases = [](void *listPtr,
obs_canvas_t *canvas) -> bool {
auto list = static_cast<FilterComboBox *>(listPtr);
OBSWeakCanvas weakCanvas = obs_canvas_get_weak_canvas(canvas);
obs_weak_canvas_release(weakCanvas);
QVariant variant;
variant.setValue(weakCanvas);
list->addItem(obs_canvas_get_name(canvas), variant);
return true;
};
obs_enum_canvases(enumCanvases, this);
#endif
}
CanvasSelection::CanvasSelection(QWidget *parent)
: FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectScene"))
{
setSizeAdjustPolicy(QComboBox::AdjustToContents);
Populate();
connect(this, &FilterComboBox::currentIndexChanged, this,
[this]() { emit CanvasChanged(GetCanvas()); });
}
void CanvasSelection::SetCanvas(const OBSWeakCanvas &canvas)
{
QVariant variant;
variant.setValue(canvas);
setCurrentIndex(findData(variant));
}
OBSWeakCanvas CanvasSelection::GetCanvas()
{
return itemData(currentIndex()).value<OBSWeakCanvas>();
}
int GetCanvasCount()
{
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
return 1;
#else
static const auto enumCanvases = [](void *countPtr,
obs_canvas_t *canvas) -> bool {
auto count = static_cast<int *>(countPtr);
(*count)++;
return true;
};
int count = 0;
obs_enum_canvases(enumCanvases, &count);
return count;
#endif
}
OBSWeakCanvas GetWeakCanvasByName(const char *name)
{
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
return OBSWeakCanvas();
#else
struct Data {
OBSWeakCanvas canvas;
const std::string name;
};
static const auto enumCanvases = [](void *dataPtr,
obs_canvas_t *canvas) -> bool {
auto data = static_cast<Data *>(dataPtr);
if (data->name != obs_canvas_get_name(canvas)) {
return true;
}
data->canvas = obs_canvas_get_weak_canvas(canvas);
obs_weak_canvas_release(data->canvas);
return false;
};
Data data{nullptr, name};
obs_enum_canvases(enumCanvases, &data);
return data.canvas;
#endif
}
std::string GetWeakCanvasName(const OBSWeakCanvas &weakCanvas)
{
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
return "Main";
#else
OBSCanvasAutoRelease canvas = obs_weak_canvas_get_canvas(weakCanvas);
return canvas ? obs_canvas_get_name(canvas) : "";
#endif
}
bool IsMainCanvas(obs_weak_canvas_t *weakCanvas)
{
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
return true;
#else
if (!weakCanvas) {
return false;
}
OBSCanvasAutoRelease canvas = obs_weak_canvas_get_canvas(weakCanvas);
OBSCanvasAutoRelease main = obs_get_main_canvas();
return canvas == main;
#endif
}
OBSWeakSource GetActiveCanvasScene(const OBSWeakCanvas &weakCanvas)
{
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
OBSCanvasAutoRelease canvas;
#else
if (!weakCanvas) {
return nullptr;
}
OBSCanvasAutoRelease canvas = obs_weak_canvas_get_canvas(weakCanvas);
if (!canvas) {
return nullptr;
}
#endif
static const auto enumCanvasScenes = [](void *scenePtr,
obs_source_t *source) -> bool {
auto scene = static_cast<OBSWeakSource *>(scenePtr);
if (!obs_source_active(source)) {
return true;
}
*scene = obs_source_get_weak_source(source);
obs_weak_source_release(*scene);
return false;
};
OBSWeakSource scene;
obs_canvas_enum_scenes(canvas, enumCanvasScenes, &scene);
return scene;
}
OBSWeakSource GetSceneAtIndex(const OBSWeakCanvas &weakCanvas, int idx)
{
if (idx < 0) {
return nullptr;
}
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
OBSCanvas canvas;
#else
if (!weakCanvas) {
return nullptr;
}
OBSCanvas canvas = OBSGetStrongRef(weakCanvas);
if (!canvas) {
return nullptr;
}
#endif
struct Data {
const int idx;
int currentIdx;
OBSSource scene;
};
static const auto enumCanvasScenes = [](void *dataPtr,
obs_source_t *source) -> bool {
auto data = static_cast<Data *>(dataPtr);
if (data->idx != data->currentIdx) {
data->currentIdx++;
return true;
}
data->scene = source;
return false;
};
Data data{idx, 0, nullptr};
obs_canvas_enum_scenes(canvas, enumCanvasScenes, &data);
return OBSGetWeakRef(data.scene);
}
int GetIndexOfScene(const OBSWeakCanvas &weakCanvas, const OBSWeakSource &scene)
{
if (!scene) {
return 0;
}
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
OBSCanvas canvas;
#else
if (!weakCanvas) {
return 0;
}
OBSCanvas canvas = OBSGetStrongRef(weakCanvas);
if (!canvas) {
return 0;
}
#endif
struct Data {
int idx;
OBSSource scene;
};
static const auto enumCanvasScenes = [](void *dataPtr,
obs_source_t *source) -> bool {
auto data = static_cast<Data *>(dataPtr);
if (source == data->scene) {
return false;
}
data->idx++;
return true;
};
Data data{0, OBSGetStrongRef(scene)};
obs_canvas_enum_scenes(canvas, enumCanvasScenes, &data);
return data.idx;
}
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *)
{
return nullptr;
}
void obs_canvas_release(obs_canvas_t *) {}
void obs_weak_canvas_addref(obs_weak_canvas_t *) {}
void obs_weak_canvas_release(obs_weak_canvas_t *) {}
OBSCanvas OBSGetStrongRef(obs_weak_object_t *weak)
{
return {};
}
obs_weak_canvas_t *obs_canvas_get_weak_canvas(obs_canvas_t *)
{
return nullptr;
}
obs_canvas_t *obs_weak_canvas_get_canvas(obs_weak_canvas_t *)
{
return nullptr;
}
void obs_canvas_enum_scenes(obs_canvas_t *,
bool (*enum_proc)(void *, obs_source_t *),
void *param)
{
obs_enum_scenes(enum_proc, param);
}
OBSCanvas OBSGetStrongRef(obs_weak_canvas_t *)
{
return OBSCanvas();
}
obs_canvas_t *obs_get_main_canvas()
{
return nullptr;
}
#endif
} // namespace advss

View File

@@ -0,0 +1,61 @@
#pragma once
#include "filter-combo-box.hpp"
#include <obs.hpp>
namespace advss {
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
class EXPORT obs_canvas_t {};
class EXPORT obs_weak_canvas_t {};
EXPORT obs_canvas_t *obs_canvas_get_ref(obs_canvas_t *);
EXPORT void obs_canvas_release(obs_canvas_t *);
EXPORT void obs_weak_canvas_addref(obs_weak_canvas_t *);
EXPORT void obs_weak_canvas_release(obs_weak_canvas_t *);
using OBSCanvas =
OBSSafeRef<obs_canvas_t *, obs_canvas_get_ref, obs_canvas_release>;
using OBSWeakCanvas = OBSRef<obs_weak_canvas_t *, obs_weak_canvas_addref,
obs_weak_canvas_release>;
using OBSCanvasAutoRelease =
OBSRefAutoRelease<obs_canvas_t *, obs_canvas_release>;
using OBSWeakCanvasAutoRelease =
OBSRefAutoRelease<obs_weak_canvas_t *, obs_weak_canvas_release>;
EXPORT obs_weak_canvas_t *obs_canvas_get_weak_canvas(obs_canvas_t *);
EXPORT obs_canvas_t *obs_weak_canvas_get_canvas(obs_weak_canvas_t *);
EXPORT void obs_canvas_enum_scenes(obs_canvas_t *,
bool (*enum_proc)(void *, obs_source_t *),
void *param);
EXPORT OBSCanvas OBSGetStrongRef(obs_weak_canvas_t *);
EXPORT OBSWeakCanvas OBSGetWeakRef(obs_canvas_t *);
EXPORT obs_canvas_t *obs_get_main_canvas();
#endif
EXPORT int GetCanvasCount();
EXPORT OBSWeakCanvas GetWeakCanvasByName(const char *name);
EXPORT std::string GetWeakCanvasName(const OBSWeakCanvas &canvas);
EXPORT bool IsMainCanvas(obs_weak_canvas_t *canvas);
EXPORT OBSWeakSource GetActiveCanvasScene(const OBSWeakCanvas &canvas);
EXPORT OBSWeakSource GetSceneAtIndex(const OBSWeakCanvas &weakCanvas, int idx);
EXPORT int GetIndexOfScene(const OBSWeakCanvas &weakCanvas,
const OBSWeakSource &scene);
class CanvasSelection : public FilterComboBox {
Q_OBJECT
public:
EXPORT CanvasSelection(QWidget *parent);
EXPORT void SetCanvas(const OBSWeakCanvas &);
EXPORT OBSWeakCanvas GetCanvas();
signals:
void CanvasChanged(const OBSWeakCanvas &);
private:
void Populate();
};
} // namespace advss

View File

@@ -35,6 +35,11 @@ void FileSelection::SetPath(const QString &path)
_filePath->setText(path);
}
QString FileSelection::GetPath() const
{
return _filePath->text();
}
QString FileSelection::ValidPathOrDesktop(const QString &path)
{
QFileInfo fileInfo(path);

View File

@@ -22,6 +22,7 @@ public:
QWidget *parent = 0);
EXPORT void SetPath(const StringVariable &);
EXPORT void SetPath(const QString &);
EXPORT QString GetPath() const;
EXPORT QPushButton *Button() { return _browseButton; }
EXPORT static QString ValidPathOrDesktop(const QString &path);

View File

@@ -84,6 +84,21 @@ void FilterComboBox::setItemText(int index, const QString &text)
}
}
QSize FilterComboBox::sizeHint() const
{
QSize size = QComboBox::sizeHint();
if (!_filteringEnabled) {
return QComboBox::sizeHint();
}
QFontMetrics fm(font());
int extra = fm.horizontalAdvance("X") * 2; // Add padding
size.setWidth(size.width() + extra);
return size;
}
void FilterComboBox::focusOutEvent(QFocusEvent *event)
{
// Reset on invalid selection
@@ -96,7 +111,7 @@ void FilterComboBox::focusOutEvent(QFocusEvent *event)
_lastCompleterHighlightRow = -1;
}
static int findXthOccurance(QComboBox *list, int count, const QString &value)
static int findXthOccurrence(QComboBox *list, int count, const QString &value)
{
if (value.isEmpty() || count < 1) {
return -1;
@@ -121,7 +136,7 @@ void FilterComboBox::CompleterHighlightChanged(const QModelIndex &index)
{
_lastCompleterHighlightRow = index.row();
const auto text = currentText();
int idx = findXthOccurance(this, _lastCompleterHighlightRow, text);
int idx = findXthOccurrence(this, _lastCompleterHighlightRow, text);
if (idx == -1) {
return;
}
@@ -133,7 +148,7 @@ void FilterComboBox::TextChanged(const QString &text)
auto c = completer();
const bool completerActive = c->completionCount() > 0;
int count = completerActive ? _lastCompleterHighlightRow + 1 : 1;
int idx = findXthOccurance(this, count, text);
int idx = findXthOccurrence(this, count, text);
if (idx == -1) {
return;
}

View File

@@ -18,6 +18,8 @@ public:
void setCurrentText(const QString &text);
void setItemText(int index, const QString &text);
QSize sizeHint() const override;
protected:
void focusOutEvent(QFocusEvent *event) override;

View File

@@ -12,6 +12,7 @@ class ADVSS_EXPORT ListEditor : public QWidget {
public:
ListEditor(QWidget *parent = nullptr, bool reorder = true);
int count() const { return _list->count(); };
protected:
void showEvent(QShowEvent *);

View File

@@ -45,7 +45,10 @@ NonModalMessageDialog::NonModalMessageDialog(const QString &message, Type type,
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
auto layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(message, this));
auto label = new QLabel(message, this);
label->setTextInteractionFlags(Qt::TextBrowserInteraction);
label->setOpenExternalLinks(true);
layout->addWidget(label);
switch (type) {
case Type::INFO: {

View File

@@ -1,8 +1,14 @@
#include "plugin-state-helpers.hpp"
#include "macro-settings.hpp"
#include "macro-signals.hpp"
#include "switcher-data.hpp"
namespace advss {
static std::mutex initMutex;
static std::mutex postLoadMutex;
static std::mutex mutex;
static std::vector<std::function<void()>> &getPluginInitSteps()
{
static std::vector<std::function<void()>> steps;
@@ -39,7 +45,23 @@ static std::vector<std::function<void()>> &getStopSteps()
return steps;
}
static std::mutex mutex;
static std::vector<std::function<void(obs_data_t *)>> &getSaveSteps()
{
static std::vector<std::function<void(obs_data_t *)>> steps;
return steps;
}
static std::vector<std::function<void(obs_data_t *)>> &getLoadSteps()
{
static std::vector<std::function<void(obs_data_t *)>> steps;
return steps;
}
static std::vector<std::function<void()>> &getPostLoadSteps()
{
static std::vector<std::function<void()>> steps;
return steps;
}
void SavePluginSettings(obs_data_t *obj)
{
@@ -53,17 +75,20 @@ void LoadPluginSettings(obs_data_t *obj)
void AddSaveStep(std::function<void(obs_data_t *)> step)
{
GetSwitcher()->AddSaveStep(step);
std::lock_guard<std::mutex> lock(mutex);
getSaveSteps().emplace_back(step);
}
void AddLoadStep(std::function<void(obs_data_t *)> step)
{
GetSwitcher()->AddLoadStep(step);
std::lock_guard<std::mutex> lock(mutex);
getLoadSteps().emplace_back(step);
}
void AddPostLoadStep(std::function<void()> step)
{
GetSwitcher()->AddPostLoadStep(step);
std::lock_guard<std::mutex> lock(postLoadMutex);
getPostLoadSteps().emplace_back(step);
}
void AddIntervalResetStep(std::function<void()> step)
@@ -72,20 +97,45 @@ void AddIntervalResetStep(std::function<void()> step)
getResetIntervalSteps().emplace_back(step);
}
void RunSaveSteps(obs_data_t *obj)
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &func : getSaveSteps()) {
func(obj);
}
}
void RunLoadSteps(obs_data_t *obj)
{
std::lock_guard<std::mutex> lock(mutex);
for (const auto &func : getLoadSteps()) {
func(obj);
}
}
void RunPostLoadSteps()
{
GetSwitcher()->RunPostLoadSteps();
std::lock_guard<std::mutex> lock(postLoadMutex);
for (const auto &func : getPostLoadSteps()) {
func();
}
}
void ClearPostLoadSteps()
{
std::lock_guard<std::mutex> lock(postLoadMutex);
getPostLoadSteps().clear();
}
void AddPluginInitStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard<std::mutex> lock(initMutex);
getPluginInitSteps().emplace_back(step);
}
void AddPluginPostLoadStep(std::function<void()> step)
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard<std::mutex> lock(initMutex);
getPluginPostLoadSteps().emplace_back(step);
}
@@ -97,7 +147,7 @@ void AddPluginCleanupStep(std::function<void()> step)
void RunPluginInitSteps()
{
std::lock_guard<std::mutex> lock(mutex);
std::lock_guard<std::mutex> lock(initMutex);
for (const auto &step : getPluginInitSteps()) {
step();
}
@@ -188,7 +238,7 @@ NoMatchBehavior GetPluginNoMatchBehavior()
void SetNoMatchScene(const OBSWeakSource &scene)
{
GetSwitcher()->nonMatchingScene = scene;
GetSwitcher()->nonMatchingScene.SetScene(scene);
}
std::string ForegroundWindowTitle()
@@ -231,4 +281,30 @@ bool IsFirstIntervalAfterStop()
return GetSwitcher()->firstIntervalAfterStop;
}
void SetMacroHighlightingEnabled(bool enable)
{
auto &settings = GetGlobalMacroSettings();
settings._highlightActions = enable;
settings._highlightConditions = enable;
settings._highlightExecuted = enable;
obs_queue_task(
OBS_TASK_UI,
[](void *) {
if (!SettingsWindowIsOpened()) {
return;
}
MacroSignalManager::Instance()->HighlightChanged(
GetGlobalMacroSettings()._highlightExecuted);
},
nullptr, false);
}
bool IsMacroHighlightingEnabled()
{
const auto &settings = GetGlobalMacroSettings();
return settings._highlightActions || settings._highlightConditions ||
settings._highlightExecuted;
}
} // namespace advss

View File

@@ -12,7 +12,10 @@ EXPORT void AddSaveStep(std::function<void(obs_data_t *)>);
EXPORT void AddLoadStep(std::function<void(obs_data_t *)>);
EXPORT void AddPostLoadStep(std::function<void()>);
EXPORT void AddIntervalResetStep(std::function<void()>);
void RunSaveSteps(obs_data_t *);
void RunLoadSteps(obs_data_t *);
EXPORT void RunPostLoadSteps();
void ClearPostLoadSteps();
EXPORT void AddPluginInitStep(std::function<void()>);
EXPORT void AddPluginPostLoadStep(std::function<void()>);
@@ -47,4 +50,7 @@ EXPORT bool InitialLoadIsComplete();
EXPORT bool IsFirstInterval();
EXPORT bool IsFirstIntervalAfterStop();
EXPORT void SetMacroHighlightingEnabled(bool enable);
EXPORT bool IsMacroHighlightingEnabled();
} // namespace advss

View File

@@ -111,6 +111,10 @@ std::vector<ThreadPrio> GetThreadPrioMapping()
bool SwitcherData::PrioFuncsValid()
{
if (functionNamesByPriority.empty()) {
return false;
}
auto fNBPCopy = functionNamesByPriority;
std::sort(fNBPCopy.begin(), fNBPCopy.end());

View File

@@ -0,0 +1,106 @@
#include "resizable-widget.hpp"
namespace advss {
ResizableWidget::ResizableWidget(QWidget *parent) : QWidget(parent) {}
void ResizableWidget::SetResizingEnabled(bool enable)
{
setMouseTracking(enable);
_customHeight = enable ? sizeHint().height() : 0;
auto pol = sizePolicy();
pol.setVerticalPolicy(enable ? QSizePolicy::Fixed
: QSizePolicy::Preferred);
_resizingEnabled = enable;
}
void ResizableWidget::mousePressEvent(QMouseEvent *event)
{
if (!_resizingEnabled) {
QWidget::mousePressEvent(event);
return;
}
if (IsInResizeArea(event->pos())) {
_resizing = true;
_lastMousePos = event->globalPosition().toPoint();
event->accept();
} else {
QWidget::mousePressEvent(event);
}
}
void ResizableWidget::mouseMoveEvent(QMouseEvent *event)
{
if (!_resizingEnabled) {
QWidget::mouseMoveEvent(event);
return;
}
if (_resizing) {
const int dy = event->globalPosition().toPoint().y() -
_lastMousePos.y();
const int baseHeight = _customHeight == 0 ? height()
: _customHeight;
const int newHeight = baseHeight + dy;
_customHeight =
std::clamp(newHeight, minimumHeight(), maximumHeight());
updateGeometry();
_lastMousePos = event->globalPosition().toPoint();
event->accept();
} else {
setCursor(IsInResizeArea(event->pos()) ? Qt::SizeVerCursor
: Qt::ArrowCursor);
QWidget::mouseMoveEvent(event);
}
}
void ResizableWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (_resizing) {
event->accept();
_resizing = false;
} else {
QWidget::mouseReleaseEvent(event);
}
}
void ResizableWidget::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (!_resizingEnabled) {
return;
}
// Draw a triangle in bottom-right as resize indicator
QPainter painter(this);
QPolygon triangle;
triangle << QPoint(width() - 1, height() - _gripSize)
<< QPoint(width() - _gripSize, height() - 1)
<< QPoint(width() - 1, height() - 1);
painter.setBrush(Qt::gray);
painter.drawPolygon(triangle);
}
QSize ResizableWidget::sizeHint() const
{
if (!_resizingEnabled) {
return QWidget::sizeHint();
}
if (_customHeight == 0) {
return QWidget::sizeHint();
}
return QSize(width(), _customHeight);
}
bool ResizableWidget::IsInResizeArea(const QPoint &pos) const
{
return QRect(width() - _gripSize, height() - _gripSize, _gripSize,
_gripSize)
.contains(pos);
}
} // namespace advss

View File

@@ -0,0 +1,34 @@
#pragma once
#include <QMouseEvent>
#include <QPainter>
#include <QWidget>
namespace advss {
class ResizableWidget : public QWidget {
Q_OBJECT
public:
ResizableWidget(QWidget *parent = nullptr);
protected:
void SetResizingEnabled(bool enable);
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void paintEvent(QPaintEvent *event) override;
QSize sizeHint() const override;
int GetCustomHeight() const { return _customHeight; }
void SetCustomHeight(int value) { _customHeight = value; }
private:
bool IsInResizeArea(const QPoint &pos) const;
bool _resizingEnabled = false;
bool _resizing = false;
QPoint _lastMousePos;
int _customHeight = 0;
const int _gripSize = 15;
};
} // namespace advss

View File

@@ -10,15 +10,18 @@
#include <obs-frontend-api.h>
#include <QLayout>
namespace advss {
constexpr std::string_view typeSaveName = "type";
constexpr std::string_view nameSaveName = "name";
constexpr std::string_view selectionSaveName = "sceneSelection";
constexpr std::string_view canvasSaveName = "canvasSelection";
void SceneSelection::Save(obs_data_t *obj) const
{
auto data = obs_data_create();
OBSDataAutoRelease data = obs_data_create();
obs_data_set_int(data, typeSaveName.data(), static_cast<int>(_type));
switch (_type) {
case Type::SCENE:
@@ -41,8 +44,31 @@ void SceneSelection::Save(obs_data_t *obj) const
default:
break;
}
obs_data_set_string(data, canvasSaveName.data(),
GetWeakCanvasName(_canvas).c_str());
obs_data_set_obj(obj, selectionSaveName.data(), data);
obs_data_release(data);
}
static OBSWeakSource getWeakSceneSourceByName(const OBSWeakCanvas &weakCanvas,
const char *name)
{
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
return GetWeakSourceByName(name);
#else
if (!weakCanvas) {
return nullptr;
}
OBSCanvasAutoRelease canvas = obs_weak_canvas_get_canvas(weakCanvas);
if (!canvas) {
return nullptr;
}
OBSSourceAutoRelease source =
obs_canvas_get_source_by_name(canvas, name);
OBSWeakSource scene = OBSGetWeakRef(source);
return scene;
#endif
}
void SceneSelection::Load(obs_data_t *obj, const char *name,
@@ -51,10 +77,15 @@ void SceneSelection::Load(obs_data_t *obj, const char *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));
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
_canvas = obs_canvas_get_weak_canvas(mainCanvas);
obs_weak_canvas_release(_canvas);
auto targetName = obs_data_get_string(obj, name);
switch (_type) {
case Type::SCENE:
_scene = GetWeakSourceByName(targetName);
_scene = getWeakSceneSourceByName(_canvas, targetName);
break;
case Type::GROUP:
_group = GetSceneGroupByName(targetName);
@@ -69,12 +100,23 @@ void SceneSelection::Load(obs_data_t *obj, const char *name,
return;
}
auto data = obs_data_get_obj(obj, selectionSaveName.data());
OBSDataAutoRelease data =
obs_data_get_obj(obj, selectionSaveName.data());
if (obs_data_has_user_value(data, canvasSaveName.data())) {
_canvas = GetWeakCanvasByName(
obs_data_get_string(data, canvasSaveName.data()));
} else {
OBSCanvasAutoRelease mainCanvas = obs_get_main_canvas();
_canvas = obs_canvas_get_weak_canvas(mainCanvas);
obs_weak_canvas_release(_canvas);
}
_type = static_cast<Type>(obs_data_get_int(data, typeSaveName.data()));
auto targetName = obs_data_get_string(data, nameSaveName.data());
switch (_type) {
case Type::SCENE:
_scene = GetWeakSourceByName(targetName);
_scene = getWeakSceneSourceByName(_canvas, targetName);
break;
case Type::GROUP:
_group = GetSceneGroupByName(targetName);
@@ -91,15 +133,18 @@ void SceneSelection::Load(obs_data_t *obj, const char *name,
default:
break;
}
obs_data_release(data);
}
static bool IsScene(const OBSWeakSource &source)
static bool isScene(const OBSWeakSource &source)
{
auto s = obs_weak_source_get_source(source);
bool ret = !!obs_scene_from_source(s);
obs_source_release(s);
return ret;
OBSSourceAutoRelease s = obs_weak_source_get_source(source);
return !!obs_scene_from_source(s);
}
void SceneSelection::SetScene(const OBSWeakSource &scene)
{
_type = Type::SCENE;
_scene = scene;
}
OBSWeakSource SceneSelection::GetScene(bool advance) const
@@ -116,23 +161,30 @@ OBSWeakSource SceneSelection::GetScene(bool advance) const
}
return _group->getCurrentScene();
case Type::PREVIOUS:
if (!IsMainCanvas(_canvas)) {
return nullptr;
}
return GetPreviousScene();
case Type::CURRENT:
return GetCurrentScene();
if (IsMainCanvas(_canvas)) {
return GetCurrentScene();
} else {
return GetActiveCanvasScene(_canvas);
}
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;
OBSSourceAutoRelease s =
obs_frontend_get_current_preview_scene();
OBSWeakSourceAutoRelease scene = obs_source_get_weak_source(s);
return scene.Get();
}
case Type::VARIABLE: {
auto var = _variable.lock();
if (!var) {
return nullptr;
}
auto scene = GetWeakSourceByName(var->Value().c_str());
if (IsScene(scene)) {
auto scene =
getWeakSceneSourceByName(_canvas, var->Value().c_str());
if (isScene(scene)) {
return scene;
}
return nullptr;
@@ -190,8 +242,23 @@ void SceneSelection::ResolveVariables()
SceneSelection SceneSelectionWidget::CurrentSelection()
{
SceneSelection s;
const int idx = currentIndex();
const auto name = currentText();
static auto mainCanvas = obs_get_main_canvas();
static auto mainCanvasWeak = obs_canvas_get_weak_canvas(mainCanvas);
[[maybe_unused]] static const bool _ = []() {
// Let's just hope we don't have to deal with selecting scenes
// when the OBS main canvas gets deleted and release the
// references here already to avoid reporting leaks on shutdown
obs_canvas_release(mainCanvas);
obs_weak_canvas_release(mainCanvasWeak);
return true;
}();
s._canvas = _forceMainCanvas ? OBSWeakCanvas(mainCanvasWeak)
: _canvas->GetCanvas();
const int idx = _scenes->currentIndex();
const auto name = _scenes->currentText();
if (idx == -1 || name.isEmpty()) {
return s;
}
@@ -214,7 +281,8 @@ SceneSelection SceneSelectionWidget::CurrentSelection()
s._group = GetSceneGroupByQString(name);
} else if (idx < _scenesEndIdx) {
s._type = SceneSelection::Type::SCENE;
s._scene = GetWeakSourceByQString(name);
s._scene = getWeakSceneSourceByName(s._canvas,
name.toStdString().c_str());
}
return s;
}
@@ -234,20 +302,6 @@ static QStringList getOrderList(bool current, bool previous, bool preview)
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;
@@ -258,70 +312,92 @@ static QStringList getSceneGroupsList()
return list;
}
static QStringList getCanvasScenesList(obs_weak_canvas_t *weakCanvas)
{
#if LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(31, 1, 0)
if (!weakCanvas) {
return {};
}
#endif
OBSCanvasAutoRelease canvas = obs_weak_canvas_get_canvas(weakCanvas);
static const auto enumCanvasScenes = [](void *listPtr,
obs_source_t *source) -> bool {
auto list = static_cast<QStringList *>(listPtr);
*list << obs_source_get_name(source);
return true;
};
QStringList list;
obs_canvas_enum_scenes(canvas, enumCanvasScenes, &list);
return list;
}
void SceneSelectionWidget::Reset()
{
auto previousSel = _currentSelection;
PopulateSelection();
PopulateSceneSelection(_currentSelection._canvas);
SetScene(previousSel);
}
void SceneSelectionWidget::PopulateSelection()
void SceneSelectionWidget::PopulateSceneSelection(obs_weak_canvas_t *canvas)
{
clear();
if (_current || _previous) {
const QStringList order =
getOrderList(_current, _previous, _preview);
AddSelectionGroup(this, order);
_scenes->clear();
if ((_current || _previous)) {
const bool isMain = IsMainCanvas(canvas);
const QStringList order = getOrderList(
_current, _previous && isMain, _preview && isMain);
AddSelectionGroup(_scenes, order);
}
_placeholderEndIdx = count();
_placeholderEndIdx = _scenes->count();
if (_variables) {
const QStringList variables = GetVariablesNameList();
AddSelectionGroup(this, variables);
AddSelectionGroup(_scenes, variables);
}
_variablesEndIdx = count();
_variablesEndIdx = _scenes->count();
if (_sceneGroups) {
const QStringList sceneGroups = getSceneGroupsList();
AddSelectionGroup(this, sceneGroups);
AddSelectionGroup(_scenes, sceneGroups);
}
_groupsEndIdx = count();
_groupsEndIdx = _scenes->count();
const QStringList scenes = getScenesList();
AddSelectionGroup(this, scenes);
_scenesEndIdx = count();
const QStringList scenes = getCanvasScenesList(canvas);
AddSelectionGroup(_scenes, scenes);
_scenesEndIdx = _scenes->count();
// Remove last separator
removeItem(count() - 1);
setCurrentIndex(-1);
_scenes->removeItem(_scenes->count() - 1);
_scenes->setCurrentIndex(-1);
_isPopulated = true;
Resize();
}
SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
bool sceneGroups, bool previous,
bool current, bool preview)
: FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectScene")),
: QWidget(parent),
_scenes(new FilterComboBox(
this, obs_module_text("AdvSceneSwitcher.selectScene"))),
_canvas(new CanvasSelection(this)),
_current(current),
_previous(previous),
_preview(preview),
_variables(variables),
_sceneGroups(sceneGroups)
{
setDuplicatesEnabled(true);
PopulateSelection();
_scenes->setSizeAdjustPolicy(QComboBox::AdjustToContents);
_scenes->setDuplicatesEnabled(true);
QWidget::connect(this, SIGNAL(currentIndexChanged(int)), this,
QWidget::connect(_scenes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SelectionChanged(int)));
// 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(
window(),
SIGNAL(SceneGroupRenamed(const QString &, const QString &)),
this, SLOT(ItemRename(const QString &, const QString &)));
QWidget::connect(_canvas, SIGNAL(CanvasChanged(const OBSWeakCanvas &)),
this, SLOT(CanvasChangedSlot(const OBSWeakCanvas &)));
QWidget::connect(_canvas, SIGNAL(CanvasChanged(const OBSWeakCanvas &)),
this, SIGNAL(CanvasChanged(const OBSWeakCanvas &)));
// Variables
QWidget::connect(VariableSignalManager::Instance(),
@@ -333,19 +409,37 @@ SceneSelectionWidget::SceneSelectionWidget(QWidget *parent, bool variables,
QWidget::connect(VariableSignalManager::Instance(),
SIGNAL(Rename(const QString &, const QString &)), this,
SLOT(ItemRename(const QString &, const QString &)));
auto layout = new QHBoxLayout();
layout->addWidget(_canvas);
layout->addWidget(_scenes);
setLayout(layout);
setContentsMargins(0, 0, 0, 0);
layout->setContentsMargins(0, 0, 0, 0);
if (GetCanvasCount() <= 1) {
_canvas->hide();
}
Resize();
}
void SceneSelectionWidget::SetScene(const SceneSelection &s)
{
int idx = -1;
_canvas->SetCanvas(s._canvas);
if (!_isPopulated) {
PopulateSceneSelection(s._canvas);
}
switch (s.GetType()) {
case SceneSelection::Type::SCENE: {
if (_scenesEndIdx == -1) {
idx = -1;
break;
}
idx = FindIdxInRagne(this, _groupsEndIdx, _scenesEndIdx,
idx = FindIdxInRagne(_scenes, _groupsEndIdx, _scenesEndIdx,
s.ToString());
break;
}
@@ -354,7 +448,7 @@ void SceneSelectionWidget::SetScene(const SceneSelection &s)
idx = -1;
break;
}
idx = FindIdxInRagne(this, _variablesEndIdx, _groupsEndIdx,
idx = FindIdxInRagne(_scenes, _variablesEndIdx, _groupsEndIdx,
s.ToString());
break;
}
@@ -365,7 +459,7 @@ void SceneSelectionWidget::SetScene(const SceneSelection &s)
}
idx = FindIdxInRagne(
this, _selectIdx, _placeholderEndIdx,
_scenes, _selectIdx, _placeholderEndIdx,
obs_module_text(
"AdvSceneSwitcher.selectPreviousScene"));
break;
@@ -377,7 +471,7 @@ void SceneSelectionWidget::SetScene(const SceneSelection &s)
}
idx = FindIdxInRagne(
this, _selectIdx, _placeholderEndIdx,
_scenes, _selectIdx, _placeholderEndIdx,
obs_module_text("AdvSceneSwitcher.selectCurrentScene"));
break;
}
@@ -388,7 +482,7 @@ void SceneSelectionWidget::SetScene(const SceneSelection &s)
}
idx = FindIdxInRagne(
this, _selectIdx, _placeholderEndIdx,
_scenes, _selectIdx, _placeholderEndIdx,
obs_module_text("AdvSceneSwitcher.selectPreviewScene"));
break;
}
@@ -397,22 +491,31 @@ void SceneSelectionWidget::SetScene(const SceneSelection &s)
idx = -1;
break;
}
idx = FindIdxInRagne(this, _placeholderEndIdx, _variablesEndIdx,
s.ToString());
idx = FindIdxInRagne(_scenes, _placeholderEndIdx,
_variablesEndIdx, s.ToString());
break;
default:
idx = -1;
break;
}
}
setCurrentIndex(idx);
_scenes->setCurrentIndex(idx);
_currentSelection = s;
Resize();
}
void SceneSelectionWidget::LockToMainCanvas()
{
_forceMainCanvas = true;
_canvas->hide();
}
void SceneSelectionWidget::showEvent(QShowEvent *event)
{
FilterComboBox::showEvent(event);
const QSignalBlocker b(this);
QWidget::showEvent(event);
const QSignalBlocker b1(_scenes);
const QSignalBlocker b2(_canvas);
Reset();
}
@@ -440,9 +543,16 @@ void SceneSelectionWidget::SelectionChanged(int)
emit SceneChanged(_currentSelection);
}
void SceneSelectionWidget::CanvasChangedSlot(const OBSWeakCanvas &)
{
_currentSelection = CurrentSelection();
Reset();
}
void SceneSelectionWidget::ItemAdd(const QString &)
{
const QSignalBlocker b(this);
const QSignalBlocker b1(_scenes);
const QSignalBlocker b2(_canvas);
Reset();
}
@@ -454,7 +564,7 @@ bool SceneSelectionWidget::NameUsed(const QString &name)
return true;
}
return _currentSelection._type == SceneSelection::Type::VARIABLE &&
currentText() == name;
_scenes->currentText() == name;
}
void SceneSelectionWidget::ItemRemove(const QString &name)
@@ -468,8 +578,19 @@ void SceneSelectionWidget::ItemRemove(const QString &name)
void SceneSelectionWidget::ItemRename(const QString &, const QString &)
{
const QSignalBlocker b(this);
const QSignalBlocker b1(_scenes);
const QSignalBlocker b2(_canvas);
Reset();
}
void SceneSelectionWidget::Resize()
{
_scenes->adjustSize();
_scenes->updateGeometry();
_canvas->adjustSize();
_canvas->updateGeometry();
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -1,7 +1,5 @@
#pragma once
#include "filter-combo-box.hpp"
#include <obs.hpp>
#include "canvas-helpers.hpp"
namespace advss {
@@ -24,19 +22,22 @@ public:
};
EXPORT Type GetType() const { return _type; }
EXPORT void SetScene(const OBSWeakSource &scene);
EXPORT OBSWeakSource GetScene(bool advance = true) const;
EXPORT OBSWeakCanvas GetCanvas() const { return _canvas; };
EXPORT std::string ToString(bool resolve = false) const;
EXPORT void ResolveVariables();
private:
OBSWeakSource _scene;
OBSWeakCanvas _canvas;
SceneGroup *_group = nullptr;
std::weak_ptr<Variable> _variable;
Type _type = Type::SCENE;
friend class SceneSelectionWidget;
};
class SceneSelectionWidget : public FilterComboBox {
class SceneSelectionWidget : public QWidget {
Q_OBJECT
public:
@@ -45,15 +46,18 @@ public:
bool previous = false, bool current = false,
bool preview = false);
EXPORT void SetScene(const SceneSelection &);
EXPORT void LockToMainCanvas();
protected:
void showEvent(QShowEvent *event) override;
signals:
void SceneChanged(const SceneSelection &);
void CanvasChanged(const OBSWeakCanvas &);
private slots:
EXPORT void SelectionChanged(int);
EXPORT void CanvasChangedSlot(const OBSWeakCanvas &);
EXPORT void ItemAdd(const QString &name);
EXPORT void ItemRemove(const QString &name);
EXPORT void ItemRename(const QString &oldName, const QString &newName);
@@ -61,11 +65,15 @@ private slots:
private:
void Reset();
SceneSelection CurrentSelection();
void PopulateSelection();
void PopulateSceneSelection(obs_weak_canvas_t *canvas);
bool IsCurrentSceneSelected(const QString &name);
bool IsPreviousSceneSelected(const QString &name);
bool IsPreviewSceneSelected(const QString &name);
bool NameUsed(const QString &name);
void Resize();
FilterComboBox *_scenes;
CanvasSelection *_canvas;
bool _current;
bool _previous;
@@ -73,7 +81,10 @@ private:
bool _variables;
bool _sceneGroups;
bool _forceMainCanvas = false;
SceneSelection _currentSelection;
bool _isPopulated = false;
// Order of entries
// 1. "select entry" entry

View File

@@ -1,9 +1,13 @@
#include "scene-switch-helpers.hpp"
#include "log-helper.hpp"
#include "canvas-helpers.hpp"
#include "source-helpers.hpp"
#include "switcher-data.hpp"
#include <obs-frontend-api.h>
#include <obs-websocket-api.h>
#undef blog
#include "log-helper.hpp"
namespace advss {
@@ -123,7 +127,13 @@ void RestoreTransitionOverride(obs_source_t *scene, const TransitionData &td)
obs_data_set_int(data, "transition_duration", td.duration);
}
void SwitchScene(const SceneSwitchInfo &sceneSwitch, bool force)
static bool modifiesTransitionSettings(const SceneSwitchInfo &ssi)
{
return ssi.duration != 0 || ssi.transition;
}
void SwitchScene(const SceneSwitchInfo &sceneSwitch, bool force,
obs_weak_canvas_t *canvas)
{
if (!sceneSwitch.scene) {
vblog(LOG_INFO, "nothing to switch to");
@@ -134,17 +144,40 @@ void SwitchScene(const SceneSwitchInfo &sceneSwitch, bool force)
obs_weak_source_get_source(sceneSwitch.scene);
OBSSourceAutoRelease currentSource = obs_frontend_get_current_scene();
if (source && (source != currentSource || force)) {
TransitionData currentTransitionData;
SetNextTransition(sceneSwitch, currentSource,
currentTransitionData);
if (!source || (source == currentSource && !force)) {
return;
}
TransitionData currentTransitionData;
const bool modifiesTransition = modifiesTransitionSettings(sceneSwitch);
if (!canvas || IsMainCanvas(canvas)) { // Assume main canvas by default
if (modifiesTransition) {
SetNextTransition(sceneSwitch, currentSource,
currentTransitionData);
}
obs_frontend_set_current_scene(source);
if (ShouldModifyTransitionOverrides()) {
if (modifiesTransition && ShouldModifyTransitionOverrides()) {
RestoreTransitionOverride(source,
currentTransitionData);
}
vblog(LOG_INFO, "switched main canvas scene");
return;
}
vblog(LOG_INFO, "switched scene");
if (GetWeakCanvasName(canvas) == "Aitum Vertical") {
// Can't set transitions so we ignore these options
OBSDataAutoRelease data = obs_data_create();
obs_data_set_string(data, "vendorName",
"aitum-vertical-canvas");
obs_data_set_string(data, "requestType", "switch_scene");
OBSDataAutoRelease requestData = obs_data_create();
obs_data_set_string(requestData, "scene",
obs_source_get_name(source));
obs_data_set_obj(data, "requestData", requestData);
obs_websocket_request_response_free(
obs_websocket_call_request("CallVendorRequest", data));
vblog(LOG_INFO, "switched Aitum Vertical canvas scene");
}
}

View File

@@ -7,6 +7,10 @@
namespace advss {
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
class obs_weak_canvas_t;
#endif
struct SceneSwitchInfo {
OBSWeakSource scene;
OBSWeakSource transition;
@@ -18,7 +22,8 @@ struct TransitionData {
int duration = 0;
};
EXPORT void SwitchScene(const SceneSwitchInfo &, bool force = false);
EXPORT void SwitchScene(const SceneSwitchInfo &, bool force = false,
obs_weak_canvas_t *canvas = nullptr);
void SwitchPreviewScene(const OBSWeakSource &scene);
EXPORT std::chrono::high_resolution_clock::time_point

View File

@@ -44,6 +44,11 @@ Section::Section(const int animationDuration, QWidget *parent)
void Section::Collapse(bool collapse)
{
if (_contentHeight != _content->sizeHint().height()) {
_contentHeight = _content->sizeHint().height();
}
SetupAnimations();
_toggleButton->setChecked(collapse);
_toggleButton->setArrowType(collapse ? Qt::ArrowType::RightArrow
: Qt::ArrowType::DownArrow);
@@ -72,7 +77,7 @@ void Section::SetContent(QWidget *w, bool collapsed)
delete _contentArea;
// Setup contentArea
_contentArea = new QScrollArea(this);
_contentArea = new QWidget(this);
_contentArea->setObjectName("macroSegmentContent");
_contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
_contentArea->setStyleSheet(
@@ -93,12 +98,11 @@ void Section::SetContent(QWidget *w, bool collapsed)
SetupAnimations();
setMinimumHeight(_headerHeight);
if (collapsed) {
this->setMinimumHeight(_headerHeight);
_contentArea->setMaximumHeight(0);
} else {
this->setMinimumHeight(_headerHeight + _contentHeight);
_contentArea->setMaximumHeight(_contentHeight);
_contentArea->setMaximumHeight(QWIDGETSIZE_MAX);
}
const QSignalBlocker b(_toggleButton);
_toggleButton->setChecked(collapsed);
@@ -120,23 +124,6 @@ void Section::SetCollapsed(bool collapsed)
Collapse(collapsed);
}
bool Section::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) {
if (_contentHeight != _content->sizeHint().height()) {
_contentHeight = _content->sizeHint().height();
setMaximumHeight(_headerHeight + _contentHeight);
setMinimumHeight(_headerHeight + _contentHeight);
_contentArea->setMaximumHeight(_contentHeight);
// Note: Calling this too frequently inside this event
// filter will cause a segfault for some reason
SetupAnimations();
}
setMinimumWidth(_content->sizeHint().width());
}
return QObject::eventFilter(obj, event);
}
void Section::SetupAnimations()
{
if (_toggleAnimation) {
@@ -160,7 +147,7 @@ void Section::SetupAnimations()
SectionAnimation->setEndValue(_headerHeight + _contentHeight);
}
QPropertyAnimation *contentAnimation =
auto contentAnimation =
static_cast<QPropertyAnimation *>(_toggleAnimation->animationAt(
_toggleAnimation->animationCount() - 1));
contentAnimation->setDuration(_animationDuration);
@@ -185,6 +172,14 @@ void Section::CleanUpPreviousContent()
void Section::AnimationFinish()
{
_transitioning = false;
// Allow resizing if we are not currently transitioning or collapsed
if (!_collapsed) {
setMinimumHeight(0);
setMaximumHeight(QWIDGETSIZE_MAX);
_contentArea->setMaximumHeight(QWIDGETSIZE_MAX);
}
emit AnimationFinished();
}

View File

@@ -3,7 +3,6 @@
#include <QFrame>
#include <QGridLayout>
#include <QParallelAnimationGroup>
#include <QScrollArea>
#include <QToolButton>
#include <QWidget>
@@ -21,9 +20,6 @@ public:
void AddHeaderWidget(QWidget *);
void SetCollapsed(bool);
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
private slots:
void AnimationFinish();
void Collapse(bool collapse);
@@ -41,7 +37,7 @@ private:
QFrame *_headerLine;
QParallelAnimationGroup *_toggleAnimation = nullptr;
QParallelAnimationGroup *_contentAnimation = nullptr;
QScrollArea *_contentArea = nullptr;
QWidget *_contentArea = nullptr;
QWidget *_content = nullptr;
int _animationDuration;
std::atomic_bool _transitioning = {false};

View File

@@ -4,6 +4,10 @@
#include "source-helpers.hpp"
#include "scene-group.hpp"
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
#include "canvas-helpers.hpp"
#endif
#include <obs-frontend-api.h>
#include <QStandardItemModel>
@@ -38,7 +42,8 @@ static void hasFilterEnum(obs_source_t *, obs_source_t *filter, void *ptr)
QStringList GetSourcesWithFilterNames()
{
auto enumSourcesWithFilters = [](void *param, obs_source_t *source) {
static auto enumSourcesWithFilters = [](void *param,
obs_source_t *source) {
if (!source) {
return true;
}
@@ -53,7 +58,17 @@ QStringList GetSourcesWithFilterNames()
QStringList list;
obs_enum_sources(enumSourcesWithFilters, &list);
#if LIBOBS_API_VER < MAKE_SEMANTIC_VERSION(31, 1, 0)
obs_enum_scenes(enumSourcesWithFilters, &list);
#else
static const auto enumCanvases = [](void *listPtr,
obs_canvas_t *canvas) -> bool {
obs_canvas_enum_scenes(canvas, enumSourcesWithFilters, listPtr);
return true;
};
obs_enum_canvases(enumCanvases, &list);
#endif
return list;
}

View File

@@ -30,4 +30,36 @@ void LoadSplitterPos(QList<int> &sizes, obs_data_t *obj,
obs_data_array_release(array);
}
void CenterSplitterPosition(QSplitter *splitter)
{
SetSplitterPositionByFraction(splitter, 0.5);
}
void SetSplitterPositionByFraction(QSplitter *splitter, double fraction)
{
int value1 = (double)QWIDGETSIZE_MAX * fraction;
int value2 = (double)QWIDGETSIZE_MAX * (1.0 - fraction);
splitter->setSizes(QList<int>() << value1 << value2);
}
void MaximizeFirstSplitterEntry(QSplitter *splitter)
{
QList<int> newSizes;
newSizes << QWIDGETSIZE_MAX;
for (int i = 0; i < splitter->sizes().size() - 1; i++) {
newSizes << 0;
}
splitter->setSizes(newSizes);
}
void ReduceSizeOfSplitterIdx(QSplitter *splitter, int idx)
{
auto sizes = splitter->sizes();
int sum = sizes[0] + sizes[1];
int reducedSize = sum / 10;
sizes[idx] = reducedSize;
sizes[(idx + 1) % 2] = sum - reducedSize;
splitter->setSizes(sizes);
}
} // namespace advss

View File

@@ -2,7 +2,10 @@
#include "export-symbol-helper.hpp"
#include <obs-data.h>
#include <QList>
#include <QSplitter>
#include <string>
namespace advss {
@@ -12,4 +15,9 @@ void SaveSplitterPos(const QList<int> &sizes, obs_data_t *obj,
void LoadSplitterPos(QList<int> &sizes, obs_data_t *obj,
const std::string &name);
void CenterSplitterPosition(QSplitter *splitter);
void SetSplitterPositionByFraction(QSplitter *splitter, double fraction);
void MaximizeFirstSplitterEntry(QSplitter *splitter);
void ReduceSizeOfSplitterIdx(QSplitter *splitter, int idx);
} // namespace advss

View File

@@ -46,14 +46,17 @@ void StringList::ResolveVariables()
}
}
StringListEdit::StringListEdit(QWidget *parent, const QString &addString,
const QString &addStringDescription,
int maxStringSize, bool allowEmtpy)
StringListEdit::StringListEdit(
QWidget *parent, const QString &addString,
const QString &addStringDescription, int maxStringSize,
const std::function<bool(const std::string &)> &filter,
const std::function<void(std::string &input)> &preprocess)
: ListEditor(parent),
_addString(addString),
_addStringDescription(addStringDescription),
_maxStringSize(maxStringSize),
_allowEmpty(allowEmtpy)
_filterCallback(filter),
_preprocessCallback(preprocess)
{
}
@@ -77,15 +80,19 @@ void StringListEdit::SetMaxStringSize(int size)
void StringListEdit::Add()
{
std::string name;
std::string entry;
bool accepted = NameDialog::AskForName(this, _addString,
_addStringDescription, name, "",
_addStringDescription, entry, "",
_maxStringSize, false);
if (!accepted || (!_allowEmpty && name.empty())) {
if (!accepted) {
return;
}
StringVariable string = name;
_preprocessCallback(entry);
if (_filterCallback(entry)) {
return;
}
StringVariable string = entry;
QVariant v = QVariant::fromValue(string);
QListWidgetItem *item = new QListWidgetItem(
QString::fromStdString(string.UnresolvedValue()), _list);
@@ -145,17 +152,21 @@ void StringListEdit::Down()
void StringListEdit::Clicked(QListWidgetItem *item)
{
std::string name;
std::string entry;
bool accepted = NameDialog::AskForName(this, _addString,
_addStringDescription, name,
_addStringDescription, entry,
item->text(), _maxStringSize,
false);
if (!accepted || (!_allowEmpty && name.empty())) {
if (!accepted) {
return;
}
StringVariable string = name;
_preprocessCallback(entry);
if (_filterCallback(entry)) {
return;
}
StringVariable string = entry;
QVariant v = QVariant::fromValue(string);
item->setText(QString::fromStdString(string.UnresolvedValue()));
item->setData(Qt::UserRole, string);

View File

@@ -19,14 +19,20 @@ public:
friend class StringListEdit;
};
class ADVSS_EXPORT StringListEdit final : public ListEditor {
class ADVSS_EXPORT StringListEdit : public ListEditor {
Q_OBJECT
public:
StringListEdit(QWidget *parent, const QString &addString = "",
const QString &addStringDescription = "",
int maxStringSize = 170, bool allowEmtpy = false);
StringListEdit(
QWidget *parent, const QString &addString = "",
const QString &addStringDescription = "",
int maxStringSize = 170,
const std::function<bool(const std::string &)> &filter =
[](const std::string &) { return false; },
const std::function<void(std::string &input)> &preprocess =
[](std::string &) {});
void SetStringList(const StringList &);
StringList GetStringList() const { return _stringList; }
void SetMaxStringSize(int);
private slots:
@@ -44,7 +50,8 @@ private:
QString _addString;
QString _addStringDescription;
int _maxStringSize = 170;
bool _allowEmpty = false;
std::function<bool(const std::string &)> _filterCallback;
std::function<void(std::string &)> _preprocessCallback;
};
} // namespace advss

View File

@@ -17,7 +17,11 @@ static std::vector<std::string> tabNames = {
"videoTab", "sceneGroupTab", "transitionsTab", "pauseTab",
};
static std::vector<int> tabOrder = std::vector<int>(tabNames.size());
static std::vector<int> &getTabOrderVector()
{
static std::vector<int> tabOrder = std::vector<int>(tabNames.size());
return tabOrder;
}
namespace {
struct TabCallbacks {
@@ -65,19 +69,14 @@ void ResetLastOpenedTab()
lastOpenedTab = -1;
}
void SaveTabOrder(obs_data_t *obj)
{
OBSDataArrayAutoRelease tabWidgetOrder = obs_data_array_create();
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry = obs_data_create();
obs_data_set_int(entry, tabNames[i].c_str(), tabOrder[i]);
obs_data_array_push_back(tabWidgetOrder, entry);
}
obs_data_set_array(obj, "tabWidgetOrder", tabWidgetOrder);
}
static bool tabWidgetOrderValid()
{
auto &tabOrder = getTabOrderVector();
if (tabNames.size() != tabOrder.size()) {
return false;
}
auto tmp = std::vector<int>(tabNames.size());
std::iota(tmp.begin(), tmp.end(), 0);
@@ -92,10 +91,29 @@ static bool tabWidgetOrderValid()
static void resetTabWidgetOrder()
{
auto &tabOrder = getTabOrderVector();
tabOrder = std::vector<int>(tabNames.size());
std::iota(tabOrder.begin(), tabOrder.end(), 0);
}
void SaveTabOrder(obs_data_t *obj)
{
auto &tabOrder = getTabOrderVector();
// Can happen when corrupting settings files
if (!tabWidgetOrderValid()) {
resetTabWidgetOrder();
}
OBSDataArrayAutoRelease tabWidgetOrder = obs_data_array_create();
for (size_t i = 0; i < tabNames.size(); i++) {
OBSDataAutoRelease entry = obs_data_create();
obs_data_set_int(entry, tabNames[i].c_str(), tabOrder[i]);
obs_data_array_push_back(tabWidgetOrder, entry);
}
obs_data_set_array(obj, "tabWidgetOrder", tabWidgetOrder);
}
void LoadTabOrder(obs_data_t *obj)
{
OBSDataArrayAutoRelease defaultTabWidgetOrder = obs_data_array_create();
@@ -107,6 +125,7 @@ void LoadTabOrder(obs_data_t *obj)
obs_data_set_default_array(obj, "tabWidgetOrder",
defaultTabWidgetOrder);
auto &tabOrder = getTabOrderVector();
tabOrder.clear();
OBSDataArrayAutoRelease tabWidgetOrder =
obs_data_get_array(obj, "tabWidgetOrder");
@@ -144,6 +163,8 @@ void SetTabOrder(QTabWidget *tabWidget)
resetTabWidgetOrder();
}
auto &tabOrder = getTabOrderVector();
auto bar = tabWidget->tabBar();
for (int i = 0; i < bar->count(); ++i) {
int curPos = findTabIndex(tabWidget, tabOrder[i]);
@@ -153,9 +174,10 @@ void SetTabOrder(QTabWidget *tabWidget)
}
}
QWidget::connect(bar, &QTabBar::tabMoved, [](int from, int to) {
std::swap(tabOrder[from], tabOrder[to]);
});
QWidget::connect(bar, &QTabBar::tabMoved,
[&tabOrder](int from, int to) {
std::swap(tabOrder[from], tabOrder[to]);
});
}
void SetCurrentTab(QTabWidget *tabWidget)

View File

@@ -18,5 +18,6 @@ void SetCurrentTab(QTabWidget *tabWidget);
void SetupOtherTabs(QTabWidget *tabWidget);
void SaveTabOrder(obs_data_t *obj);
void LoadTabOrder(obs_data_t *obj);
bool MacroTabIsInFocus();
} // namespace advss

View File

@@ -1,7 +1,8 @@
#include "temp-variable.hpp"
#include "advanced-scene-switcher.hpp"
#include "obs-module-helper.hpp"
#include "macro.hpp"
#include "macro-action-macro.hpp"
#include "macro-edit.hpp"
#include "macro-segment.hpp"
#include "plugin-state-helpers.hpp"
#include "ui-helpers.hpp"
@@ -154,20 +155,161 @@ int TempVariableRef::GetIdx() const
return segment->GetIndex();
}
void TempVariableRef::Save(obs_data_t *obj, const char *name) const
template<typename T>
static bool
segmentIsPartOfSegmentList(const MacroSegment *segment,
const std::deque<std::shared_ptr<T>> &segmentList)
{
for (const auto &segmentFromList : segmentList) {
if (segment == segmentFromList.get()) {
return true;
}
}
return false;
}
static bool segmentIsPartOfMacro(const MacroSegment *segment,
const Macro *macro)
{
if (!macro) {
return false;
}
return segmentIsPartOfSegmentList(segment, macro->Conditions()) ||
segmentIsPartOfSegmentList(segment, macro->Actions()) ||
segmentIsPartOfSegmentList(segment, macro->ElseActions());
}
static bool isMatchingNestedMacroAction(MacroAction *action, const Macro *macro)
{
const auto nestedMacroAction = dynamic_cast<MacroActionMacro *>(action);
if (!nestedMacroAction) {
return false;
}
return nestedMacroAction->_nestedMacro.get() == macro;
}
static void appendNestedMacros(std::deque<std::shared_ptr<Macro>> &macros,
Macro *macro)
{
if (!macro) {
return;
}
for (const auto &action : macro->Actions()) {
const auto nestedMacroAction =
dynamic_cast<MacroActionMacro *>(action.get());
if (nestedMacroAction) {
macros.push_back(nestedMacroAction->_nestedMacro);
}
}
for (const auto &action : macro->ElseActions()) {
const auto nestedMacroAction =
dynamic_cast<MacroActionMacro *>(action.get());
if (nestedMacroAction) {
macros.push_back(nestedMacroAction->_nestedMacro);
}
}
}
static Macro *getParentMacro(const Macro *targetMacro,
const std::vector<Macro *> &macros)
{
if (!targetMacro) {
return nullptr;
}
if (macros.empty()) {
return nullptr;
}
std::vector<Macro *> newMacros;
for (const auto &macro : macros) {
for (const auto &action : macro->Actions()) {
const auto nestedMacroAction =
dynamic_cast<MacroActionMacro *>(action.get());
if (!nestedMacroAction) {
continue;
}
if (nestedMacroAction->_nestedMacro.get() ==
targetMacro) {
return nestedMacroAction->GetMacro();
}
newMacros.emplace_back(
nestedMacroAction->_nestedMacro.get());
}
for (const auto &action : macro->ElseActions()) {
const auto nestedMacroAction =
dynamic_cast<MacroActionMacro *>(action.get());
if (!nestedMacroAction) {
continue;
}
if (nestedMacroAction->_nestedMacro.get() ==
targetMacro) {
return nestedMacroAction->GetMacro();
}
newMacros.emplace_back(
nestedMacroAction->_nestedMacro.get());
}
}
return getParentMacro(targetMacro, newMacros);
}
static Macro *getParentMacro(const Macro *macro)
{
std::vector<Macro *> macros;
for (const auto &macro : GetMacros()) {
macros.emplace_back(macro.get());
}
return getParentMacro(macro, macros);
}
static int getDepth(MacroSegment *segment, const Macro *macro, int depth = 0)
{
if (!macro) {
return -1;
}
if (segmentIsPartOfMacro(segment, macro)) {
return depth;
}
return getDepth(segment, getParentMacro(macro), depth + 1);
}
void TempVariableRef::Save(obs_data_t *obj, Macro *macro,
const char *name) const
{
OBSDataAutoRelease data = obs_data_create();
auto type = GetType();
obs_data_set_int(data, "type", static_cast<int>(type));
obs_data_set_int(data, "idx", GetIdx());
obs_data_set_string(data, "id", _id.c_str());
const auto segment = _segment.lock();
if (segment) {
obs_data_set_int(data, "depth", getDepth(segment.get(), macro));
}
obs_data_set_int(data, "version", 1);
obs_data_set_obj(obj, name, data);
}
void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
void TempVariableRef::Load(obs_data_t *obj, Macro *macroPtr, const char *name)
{
if (!macro) {
std::weak_ptr<Macro> macro;
for (const auto &macroShared : GetMacros()) {
if (macroShared.get() == macroPtr) {
macro = macroShared;
break;
}
}
if (macro.expired()) {
_segment.reset();
return;
}
@@ -176,6 +318,7 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
_id = obs_data_get_string(data, "id");
const auto type =
static_cast<SegmentType>(obs_data_get_int(data, "type"));
_depth = obs_data_get_int(data, "depth");
// Backwards compatibility checks
if (obs_data_get_int(data, "version") < 1) {
@@ -189,8 +332,19 @@ void TempVariableRef::Load(obs_data_t *obj, Macro *macro, const char *name)
});
}
void TempVariableRef::PostLoad(int idx, SegmentType type, Macro *macro)
void TempVariableRef::PostLoad(int idx, SegmentType type,
const std::weak_ptr<Macro> &weakMacro)
{
auto childMacro = weakMacro.lock();
if (!childMacro) {
return;
}
Macro *macro = nullptr;
for (int i = 0; i < _depth; i++) {
macro = getParentMacro(childMacro.get());
}
if (!macro) {
return;
}
@@ -247,20 +401,35 @@ bool TempVariableRef::operator==(const TempVariableRef &other) const
return segment == other._segment.lock();
}
static MacroEdit *findMacroEditParent(QWidget *widget)
{
if (!widget) {
return nullptr;
}
auto macroEdit = qobject_cast<MacroEdit *>(widget);
if (macroEdit) {
return macroEdit;
}
return findMacroEditParent(widget->parentWidget());
}
TempVariableSelection::TempVariableSelection(QWidget *parent)
: QWidget(parent),
_selection(new FilterComboBox(
this, obs_module_text("AdvSceneSwitcher.tempVar.select"))),
_info(new AutoUpdateTooltipLabel(this, [this]() {
return SetupInfoLabel();
}))
_info(new AutoUpdateHelpIcon(this,
[this]() { return SetupInfoLabel(); }))
{
QString path = GetThemeTypeName() == "Light"
? ":/res/images/help.svg"
: ":/res/images/help_light.svg";
QIcon icon(path);
QPixmap pixmap = icon.pixmap(QSize(16, 16));
_info->setPixmap(pixmap);
MacroEdit *edit = findMacroEditParent(parent);
assert(edit);
_macroEdits.push_back(edit);
while ((edit = findMacroEditParent(edit->parentWidget()))) {
_macroEdits.push_back(edit);
}
_info->hide();
_selection->setSizeAdjustPolicy(QComboBox::AdjustToContents);
@@ -272,9 +441,11 @@ TempVariableSelection::TempVariableSelection(QWidget *parent)
SLOT(SelectionIdxChanged(int)));
QWidget::connect(_selection, SIGNAL(highlighted(int)), this,
SLOT(HighlightChanged(int)));
QWidget::connect(window(), SIGNAL(MacroSegmentOrderChanged()), this,
SLOT(MacroSegmentsChanged()));
QWidget::connect(window(),
for (const auto macroEdit : _macroEdits) {
QWidget::connect(macroEdit, SIGNAL(MacroSegmentOrderChanged()),
this, SLOT(MacroSegmentsChanged()));
}
QWidget::connect(TempVarSignalManager::Instance(),
SIGNAL(SegmentTempVarsChanged(MacroSegment *)), this,
SLOT(SegmentTempVarsChanged(MacroSegment *)));
@@ -332,19 +503,57 @@ void TempVariableSelection::HighlightChanged(int idx)
HighlightSelection(var);
}
static MacroSegment *
getMacroSegmentFromNestedMacro(Macro *nestedMacro,
const std::vector<MacroEdit *> &macroEdits)
{
for (const auto &macroEdit : macroEdits) {
const auto macro = macroEdit->GetMacro();
if (!macro) {
continue;
}
for (const auto &action : macro->Actions()) {
if (isMatchingNestedMacroAction(action.get(),
nestedMacro)) {
return action.get();
}
}
for (const auto &action : macro->ElseActions()) {
if (isMatchingNestedMacroAction(action.get(),
nestedMacro)) {
return action.get();
}
}
}
return nullptr;
}
void TempVariableSelection::PopulateSelection()
{
auto advssWindow = qobject_cast<AdvSceneSwitcher *>(window());
if (!advssWindow) {
std::vector<TempVariable> vars;
const auto appendVars = [&vars](const Macro *macro,
const MacroSegment *segment) {
const auto newVars = macro->GetTempVars(segment);
vars.insert(vars.end(), newVars.begin(), newVars.end());
};
const auto segment = GetSegment();
if (!segment) {
return;
}
auto macro = advssWindow->GetSelectedMacro();
if (!macro) {
auto macro = segment->GetMacro();
appendVars(macro, segment);
while ((macro = getParentMacro(macro))) {
appendVars(macro,
getMacroSegmentFromNestedMacro(macro, _macroEdits));
}
if (vars.empty()) {
return;
}
auto vars = macro->GetTempVars(GetSegment());
for (const auto &var : vars) {
QVariant variant;
variant.setValue(var.GetRef());
@@ -365,25 +574,49 @@ void TempVariableSelection::PopulateSelection()
updateGeometry();
}
static MacroEdit *
getMacroEditFromMacro(const std::vector<MacroEdit *> &macroEdits, Macro *macro)
{
for (const auto edit : macroEdits) {
if (macro == edit->GetMacro().get()) {
return edit;
}
}
return nullptr;
}
void TempVariableSelection::HighlightSelection(const TempVariableRef &var)
{
auto advssWindow = qobject_cast<AdvSceneSwitcher *>(window());
if (!advssWindow) {
const auto segment = var._segment.lock();
if (!segment) {
return;
}
const auto macro = segment->GetMacro();
if (!macro) {
return;
}
const MacroEdit *macroEdit = getMacroEditFromMacro(_macroEdits, macro);
if (!macroEdit) {
return;
}
const auto color = GetThemeTypeName() == "Dark" ? Qt::white : Qt::blue;
auto type = var.GetType();
switch (type) {
case TempVariableRef::SegmentType::NONE:
return;
case TempVariableRef::SegmentType::CONDITION:
advssWindow->HighlightCondition(var.GetIdx(), Qt::white);
macroEdit->HighlightCondition(var.GetIdx(), color);
return;
case TempVariableRef::SegmentType::ACTION:
advssWindow->HighlightAction(var.GetIdx(), Qt::white);
macroEdit->HighlightAction(var.GetIdx(), color);
return;
case TempVariableRef::SegmentType::ELSEACTION:
advssWindow->HighlightElseAction(var.GetIdx(), Qt::white);
macroEdit->HighlightElseAction(var.GetIdx(), color);
return;
default:
break;
@@ -394,19 +627,20 @@ QString TempVariableSelection::SetupInfoLabel()
{
auto currentSelection = _selection->itemData(_selection->currentIndex())
.value<TempVariableRef>();
auto advssWindow = qobject_cast<AdvSceneSwitcher *>(window());
if (!advssWindow) {
const auto segment = currentSelection._segment.lock();
if (!segment) {
_info->setToolTip("");
_info->hide();
return "";
}
auto macro = advssWindow->GetSelectedMacro();
auto macro = segment->GetMacro();
if (!macro) {
_info->setToolTip("");
_info->hide();
return "";
}
auto var = currentSelection.GetTempVariable(macro.get());
auto var = currentSelection.GetTempVariable(macro);
if (!var) {
_info->setToolTip("");
_info->hide();
@@ -447,15 +681,20 @@ MacroSegment *TempVariableSelection::GetSegment() const
return segmentWidget->Data().get();
}
TempVarSignalManager::TempVarSignalManager() : QObject() {}
TempVarSignalManager *TempVarSignalManager::Instance()
{
static TempVarSignalManager manager;
return &manager;
}
void NotifyUIAboutTempVarChange(MacroSegment *segment)
{
obs_queue_task(
OBS_TASK_UI,
[](void *segment) {
if (!SettingsWindowIsOpened()) {
return;
}
AdvSceneSwitcher::window->SegmentTempVarsChanged(
TempVarSignalManager::Instance()->SegmentTempVarsChanged(
(MacroSegment *)segment);
},
segment, false);

View File

@@ -13,6 +13,7 @@
namespace advss {
class Macro;
class MacroEdit;
class MacroSegment;
class TempVariableRef;
class TempVariableSelection;
@@ -60,7 +61,8 @@ private:
class TempVariableRef {
public:
EXPORT void Save(obs_data_t *, const char *name = "tempVar") const;
EXPORT void Save(obs_data_t *, Macro *,
const char *name = "tempVar") const;
EXPORT void Load(obs_data_t *, Macro *, const char *name = "tempVar");
EXPORT std::optional<const TempVariable> GetTempVariable(Macro *) const;
EXPORT bool operator==(const TempVariableRef &other) const;
@@ -70,10 +72,12 @@ private:
enum class SegmentType { NONE, CONDITION, ACTION, ELSEACTION };
SegmentType GetType() const;
int GetIdx() const;
void PostLoad(int idx, SegmentType, Macro *);
void PostLoad(int idx, SegmentType, const std::weak_ptr<Macro> &);
std::string _id = "";
std::weak_ptr<MacroSegment> _segment;
int _depth = 0;
friend TempVariable;
friend TempVariableSelection;
};
@@ -101,7 +105,20 @@ private:
MacroSegment *GetSegment() const;
FilterComboBox *_selection;
AutoUpdateTooltipLabel *_info;
AutoUpdateHelpIcon *_info;
std::vector<MacroEdit *> _macroEdits;
};
class TempVarSignalManager : public QObject {
Q_OBJECT
public:
static TempVarSignalManager *Instance();
signals:
void SegmentTempVarsChanged(MacroSegment *);
private:
TempVarSignalManager();
};
void NotifyUIAboutTempVarChange(MacroSegment *);

Some files were not shown because too many files have changed in this diff Show More