mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
108 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
07f11c63f0 | ||
|
|
6685a1ccaa | ||
|
|
ecee919e0b | ||
|
|
e8c1b673c0 | ||
|
|
1a8b185f7d | ||
|
|
eb8a9df627 | ||
|
|
123d308a0d | ||
|
|
102b93d3b5 | ||
|
|
eeceb7cbd9 | ||
|
|
92add4f090 | ||
|
|
3ed7727c85 | ||
|
|
a922d6a73d | ||
|
|
9ba8a02a11 | ||
|
|
e61539a878 | ||
|
|
5a78c99703 | ||
|
|
dbbcf04b8a | ||
|
|
6a8066795b | ||
|
|
e0763a4957 | ||
|
|
1ab9a38914 | ||
|
|
a6ca266dab | ||
|
|
0a8f279e97 | ||
|
|
1b63978acb | ||
|
|
8487ef4716 | ||
|
|
ddc2ee4fa5 | ||
|
|
11fede6cc3 | ||
|
|
1d45072c58 | ||
|
|
d450210d39 | ||
|
|
5462334693 | ||
|
|
293d3dd16c | ||
|
|
71b6ae4d78 | ||
|
|
69d6d63dfd | ||
|
|
d693dbc844 | ||
|
|
428e114a0a | ||
|
|
001d8b4714 | ||
|
|
37734445e7 | ||
|
|
d1fe5beaeb | ||
|
|
036afb4a4f | ||
|
|
c87589d534 | ||
|
|
1df513585d | ||
|
|
e1bacd75b6 | ||
|
|
7095f4668c | ||
|
|
8131ad3c24 | ||
|
|
ac5b2c3f9e | ||
|
|
a86f7d0fd4 | ||
|
|
34736ffbda | ||
|
|
da28fea2e0 | ||
|
|
b6b0531dbc | ||
|
|
2de0a94756 | ||
|
|
d018740442 | ||
|
|
d89f8a7972 | ||
|
|
f7a2aa09cb | ||
|
|
04825e4b88 | ||
|
|
1cfd981245 | ||
|
|
6fb8297275 | ||
|
|
4253c99cf2 | ||
|
|
ae13a68cae | ||
|
|
4e5aae7e5f | ||
|
|
88dff37776 | ||
|
|
83df1ab658 | ||
|
|
5298b14803 | ||
|
|
eeedf11c6a | ||
|
|
89efb6d1c8 | ||
|
|
1054bce4a7 | ||
|
|
d3cbddf0a4 | ||
|
|
a91fed1583 | ||
|
|
c94a791d19 | ||
|
|
02e380a9f2 | ||
|
|
4349a98614 | ||
|
|
f294283306 | ||
|
|
cc9d5897da | ||
|
|
8e3eb8519b | ||
|
|
354ef56070 | ||
|
|
352dac5e32 | ||
|
|
584fe4b3b6 | ||
|
|
bdd489aea5 | ||
|
|
0b2e725471 | ||
|
|
45e8378021 | ||
|
|
955f308c59 | ||
|
|
2c25bb4dd4 | ||
|
|
d8dcd91ed9 | ||
|
|
03900e936b | ||
|
|
6d2360eb87 | ||
|
|
ab4a70a758 | ||
|
|
b48276bd5c | ||
|
|
34167366db | ||
|
|
bfa8f4d9d5 | ||
|
|
f8e3a3e275 | ||
|
|
1328896801 | ||
|
|
0effbd8ace | ||
|
|
08a8157f3a | ||
|
|
262bb3f1ee | ||
|
|
415f63c46c | ||
|
|
0771f939a6 | ||
|
|
a81ee605d6 | ||
|
|
1e7726bcfc | ||
|
|
dc698e426d | ||
|
|
a4f66dbbcd | ||
|
|
1516b9a939 | ||
|
|
e13f20c860 | ||
|
|
727992f1d0 | ||
|
|
4e75a15118 | ||
|
|
48368f4bd6 | ||
|
|
61d9031933 | ||
|
|
487c735e13 | ||
|
|
7a34c334bd | ||
|
|
4988683e22 | ||
|
|
cb681643c8 | ||
|
|
d79e10ec97 |
9
.github/workflows/build.yml
vendored
9
.github/workflows/build.yml
vendored
@@ -5,7 +5,7 @@ on: [push, pull_request, workflow_dispatch]
|
||||
env:
|
||||
PLUGIN_NAME: SceneSwitcher
|
||||
LIB_NAME: advanced-scene-switcher
|
||||
OBS_TAG: 27.1.3
|
||||
OBS_TAG: 27.2.0
|
||||
|
||||
jobs:
|
||||
macos64:
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
run: |
|
||||
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
|
||||
sudo hdiutil attach ./Packages.dmg
|
||||
sudo installer -pkg /Volumes/Packages\ 1.2.9/Install\ Packages.pkg -target /
|
||||
sudo installer -pkg /Volumes/Packages\ 1.2.10/Install\ Packages.pkg -target /
|
||||
- name: Package
|
||||
if: success()
|
||||
shell: bash
|
||||
@@ -184,7 +184,8 @@ jobs:
|
||||
libx11-xcb-dev \
|
||||
libxcb-xfixes0-dev \
|
||||
libopencv-dev \
|
||||
libprocps-dev
|
||||
libprocps-dev \
|
||||
libpci-dev
|
||||
- name: "Configure"
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -223,7 +224,7 @@ jobs:
|
||||
arch: [32, 64]
|
||||
env:
|
||||
QT_VERSION: 5.15.2
|
||||
CMAKE_GENERATOR: "Visual Studio 16 2019"
|
||||
CMAKE_GENERATOR: "Visual Studio 17 2022"
|
||||
CMAKE_SYSTEM_VERSION: "10.0.18363.657"
|
||||
WINDOWS_DEPS_VERSION: "2019"
|
||||
steps:
|
||||
|
||||
14
.github/workflows/locale-check.yml
vendored
Normal file
14
.github/workflows/locale-check.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Check locale
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
ubuntu64:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Check locale files
|
||||
run: |
|
||||
python3 ./CI/checkLocale.py -p data/locale/
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -92,6 +92,15 @@ Thumbs.db
|
||||
*.iml
|
||||
*.ipr
|
||||
*.sublime*
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
.history/
|
||||
*.vsix
|
||||
forms/advanced-scene-switcher.ui.autosave
|
||||
|
||||
# Directories #
|
||||
###############
|
||||
|
||||
93
CI/checkLocale.py
Normal file
93
CI/checkLocale.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
defaultLocaleFile = "en-US.ini"
|
||||
|
||||
|
||||
class localeEntry:
|
||||
locale = ""
|
||||
placeholders = []
|
||||
|
||||
def __init__(self, locale, widgets) -> None:
|
||||
self.locale = locale
|
||||
self.placeholders = widgets
|
||||
|
||||
|
||||
def getNonDefaultLocales(dir):
|
||||
files = []
|
||||
for filename in os.listdir(dir):
|
||||
f = os.path.join(dir, filename)
|
||||
if os.path.isfile(f) and not f.endswith(defaultLocaleFile):
|
||||
files.append(f)
|
||||
return files
|
||||
|
||||
|
||||
def getAllLocaleEntriesWithWidgetPlaceholders(file):
|
||||
localeEntries = []
|
||||
with open(file, 'r', encoding='UTF-8') as f:
|
||||
for line in f.readlines():
|
||||
widgetPlaceholders = []
|
||||
for word in line.split("{{"):
|
||||
if not "}}" in word:
|
||||
continue
|
||||
word = "{{" + word[:word.rfind("}}")] + "}}"
|
||||
widgetPlaceholders.append(word)
|
||||
localeEntries.append(localeEntry(
|
||||
line.split("=")[0], widgetPlaceholders))
|
||||
return localeEntries
|
||||
|
||||
|
||||
def getLocaleEntryFrom(entry, list):
|
||||
for element in list:
|
||||
if element.locale == entry.locale:
|
||||
return element
|
||||
return None
|
||||
|
||||
|
||||
def checkWidgetPlacehodlers(file, expectedPlaceholders):
|
||||
localeEntries = getAllLocaleEntriesWithWidgetPlaceholders(file)
|
||||
result = True
|
||||
for localeEntry in localeEntries:
|
||||
expectedEntry = getLocaleEntryFrom(localeEntry, expectedPlaceholders)
|
||||
if expectedEntry is None:
|
||||
print(
|
||||
"WARNING: Locale entry \"{}\" from \"{}\" not found in \"{}\"".format(localeEntry.locale, file, defaultLocaleFile))
|
||||
continue
|
||||
for p in localeEntry.placeholders:
|
||||
if p not in expectedEntry.placeholders:
|
||||
print("WARNING: Locale entry \"{}\" from \"{}\" does contain \"{}\" while \"{}\" does not".format(
|
||||
localeEntry.locale, file, p, defaultLocaleFile))
|
||||
for p in expectedEntry.placeholders:
|
||||
if p not in localeEntry.placeholders:
|
||||
result = False
|
||||
print("ERROR: Locale entry \"{}\" from \"{}\" does not contain \"{}\"".format(
|
||||
localeEntry.locale, file, p))
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Checks for inconsistencies regarding widget placeholders in the different locale files.')
|
||||
parser.add_argument(
|
||||
'-p', '--path', help='Path to locale folder', required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
placeholders = getAllLocaleEntriesWithWidgetPlaceholders(
|
||||
os.path.join(args.path, defaultLocaleFile))
|
||||
nonDefaultLocales = getNonDefaultLocales(args.path)
|
||||
|
||||
result = True
|
||||
for f in nonDefaultLocales:
|
||||
if checkWidgetPlacehodlers(f, placeholders) == False:
|
||||
result = False
|
||||
if result == False:
|
||||
sys.exit(1)
|
||||
print("SUCCESS: No issues found!")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -229,6 +229,7 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/macro-condition-idle.hpp
|
||||
src/headers/macro-condition-macro.hpp
|
||||
src/headers/macro-condition-media.hpp
|
||||
src/headers/macro-condition-obs-stats.hpp
|
||||
src/headers/macro-condition-plugin-state.hpp
|
||||
src/headers/macro-condition-process.hpp
|
||||
src/headers/macro-condition-recording.hpp
|
||||
@@ -245,8 +246,10 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/macro-condition-virtual-cam.hpp
|
||||
src/headers/macro-condition-window.hpp
|
||||
src/headers/macro.hpp
|
||||
src/headers/macro-controls.hpp
|
||||
src/headers/macro-list-entry-widget.hpp
|
||||
src/headers/macro-properties.hpp
|
||||
src/headers/macro-segment.hpp
|
||||
src/headers/macro-segment-list.hpp
|
||||
src/headers/macro-selection.hpp
|
||||
src/headers/curl-helper.hpp
|
||||
src/headers/hotkey.hpp
|
||||
@@ -260,6 +263,7 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/section.hpp
|
||||
src/headers/status-control.hpp
|
||||
src/headers/platform-funcs.hpp
|
||||
src/headers/resizing-text-edit.hpp
|
||||
src/headers/utility.hpp
|
||||
src/headers/volume-control.hpp
|
||||
src/headers/version.h)
|
||||
@@ -329,6 +333,7 @@ set(advanced-scene-switcher_SOURCES
|
||||
src/macro-condition-idle.cpp
|
||||
src/macro-condition-macro.cpp
|
||||
src/macro-condition-media.cpp
|
||||
src/macro-condition-obs-stats.cpp
|
||||
src/macro-condition-plugin-state.cpp
|
||||
src/macro-condition-process.cpp
|
||||
src/macro-condition-recording.cpp
|
||||
@@ -345,8 +350,10 @@ set(advanced-scene-switcher_SOURCES
|
||||
src/macro-condition-virtual-cam.cpp
|
||||
src/macro-condition-window.cpp
|
||||
src/macro.cpp
|
||||
src/macro-controls.cpp
|
||||
src/macro-list-entry-widget.cpp
|
||||
src/macro-properties.cpp
|
||||
src/macro-segment.cpp
|
||||
src/macro-segment-list.cpp
|
||||
src/macro-selection.cpp
|
||||
src/macro-tab.cpp
|
||||
src/curl-helper.cpp
|
||||
@@ -355,6 +362,7 @@ set(advanced-scene-switcher_SOURCES
|
||||
src/screenshot-helper.cpp
|
||||
src/transition-selection.cpp
|
||||
src/name-dialog.cpp
|
||||
src/resizing-text-edit.cpp
|
||||
src/duration-control.cpp
|
||||
src/status-control.cpp
|
||||
src/section.cpp
|
||||
|
||||
@@ -117,7 +117,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="Pausiert"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="Gestoppt"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="Beendet"
|
||||
AdvSceneSwitcher.mediaTab.states.error="Fehler"
|
||||
AdvSceneSwitcher.mediaTab.states.playedToEnd="Zu Ende gespielt"
|
||||
AdvSceneSwitcher.mediaTab.states.any="Beliebig"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="Keine Auswahl"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Dauer kürzer"
|
||||
@@ -223,7 +222,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Dieser Tab ermöglicht es
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Netzwerk"
|
||||
AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann."
|
||||
AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter macOS leider aufgrund von Bibliotheksinkompatibilitäten mit dem obs-websocket plugin deaktiviert werden."
|
||||
AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"
|
||||
|
||||
@@ -30,6 +30,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose loggi
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Show system tray notifications"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="Hide tabs which can be represented via macros"
|
||||
AdvSceneSwitcher.generalTab.priority="Priority"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
|
||||
AdvSceneSwitcher.generalTab.priority.threadPriority="Use thread priority"
|
||||
@@ -76,6 +77,11 @@ AdvSceneSwitcher.macroTab.exists="Macro name exists already"
|
||||
AdvSceneSwitcher.macroTab.copy="Create copy"
|
||||
AdvSceneSwitcher.macroTab.expandAll="Expand all"
|
||||
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
|
||||
AdvSceneSwitcher.macroTab.maximize="Maximize"
|
||||
AdvSceneSwitcher.macroTab.minimize="Minimize"
|
||||
AdvSceneSwitcher.macroTab.highlightExecutedMacros="Highlight recently executed macros"
|
||||
AdvSceneSwitcher.macroTab.highlightTrueConditions="Highlight conditions of currently selected macro that evaluated to true recently"
|
||||
AdvSceneSwitcher.macroTab.highlightPerformedActions="Highlight recently performed actions of currently selected macro"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Ignore entry"
|
||||
@@ -122,6 +128,7 @@ AdvSceneSwitcher.condition.media="Media"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Any media source on"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="All media sources on"
|
||||
AdvSceneSwitcher.condition.media.matchOnChange="Only match on change (Note: This option will be removed in a future version - please use time constraints instead)"
|
||||
AdvSceneSwitcher.condition.media.inconsistencyInfo="Unfortunately not all media source types behave the same (e.g. Media Source vs. VLC Video Source \"Stopped\" state).\nSo please experiment what works for your setup!"
|
||||
AdvSceneSwitcher.condition.media.entry="{{mediaSources}}{{scenes}} state is {{states}} and {{timeRestrictions}} {{time}}"
|
||||
AdvSceneSwitcher.condition.video="Video"
|
||||
AdvSceneSwitcher.condition.video.condition.match="exactly matches"
|
||||
@@ -143,16 +150,25 @@ AdvSceneSwitcher.condition.video.objectScaleThreshold="Scale factor: "
|
||||
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="A lower scale factor will lead to more matches but higher CPU load."
|
||||
AdvSceneSwitcher.condition.video.minNeighborDescription="A higher minimum neighbors value will result in fewer but higher quality matches."
|
||||
AdvSceneSwitcher.condition.video.showMatch="Show match"
|
||||
AdvSceneSwitcher.condition.video.showMatch.loading="Checking for match"
|
||||
AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of source!"
|
||||
AdvSceneSwitcher.condition.video.screenshotEmpty="Screenshot is empty - Is the source visible?"
|
||||
AdvSceneSwitcher.condition.video.patternMatchFail="Pattern was not found!"
|
||||
AdvSceneSwitcher.condition.video.patternMatchSuccess="Pattern is highlighted in red"
|
||||
AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!"
|
||||
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
|
||||
AdvSceneSwitcher.condition.video.modelLoadFail="Model data could not be loaded!"
|
||||
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="Model data (haar cascade classifier): {{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum neighbors: {{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minSize="Minimum size: {{minSizeX}} x {{minSizeY}}"
|
||||
AdvSceneSwitcher.condition.video.entry.maxSize="Maximum size: {{maxSizeX}} x {{maxSizeY}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}Reduce CPU load by performing check only every {{throttleCount}} milliseconds"
|
||||
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}Perform check only in area {{checkArea}} {{selectArea}}"
|
||||
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
|
||||
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
|
||||
AdvSceneSwitcher.condition.video.selectArea="Select area"
|
||||
AdvSceneSwitcher.condition.video.selectArea.status="Only highlighted area will be checked"
|
||||
AdvSceneSwitcher.condition.video.width="Width"
|
||||
AdvSceneSwitcher.condition.video.height="Height"
|
||||
AdvSceneSwitcher.condition.stream="Streaming"
|
||||
AdvSceneSwitcher.condition.stream.state.start="Stream running"
|
||||
AdvSceneSwitcher.condition.stream.state.stop="Stream stopped"
|
||||
@@ -171,6 +187,7 @@ AdvSceneSwitcher.condition.idle.entry="No keyboard or mouse inputs for {{duratio
|
||||
AdvSceneSwitcher.condition.pluginState="Plugin state"
|
||||
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Automated scene change was triggered in this interval"
|
||||
AdvSceneSwitcher.condition.pluginState.state.running="Advanced scene switcher is running"
|
||||
AdvSceneSwitcher.condition.pluginState.state.shutdown="OBS is shutting down"
|
||||
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
||||
AdvSceneSwitcher.condition.timer="Timer"
|
||||
AdvSceneSwitcher.condition.timer.type.fixed="Fixed"
|
||||
@@ -233,12 +250,26 @@ AdvSceneSwitcher.condition.replay.state.started="Replay buffer started"
|
||||
AdvSceneSwitcher.condition.replay.state.saved="Replay buffer saved"
|
||||
AdvSceneSwitcher.condition.replay.entry="{{state}}"
|
||||
AdvSceneSwitcher.condition.date="Date"
|
||||
AdvSceneSwitcher.condition.date.anyDay="Any day"
|
||||
AdvSceneSwitcher.condition.date.monday="Monday"
|
||||
AdvSceneSwitcher.condition.date.tuesday="Tuesday"
|
||||
AdvSceneSwitcher.condition.date.wednesday="Wednesday"
|
||||
AdvSceneSwitcher.condition.date.thursday="Thursday"
|
||||
AdvSceneSwitcher.condition.date.friday="Friday"
|
||||
AdvSceneSwitcher.condition.date.saturday="Saturday"
|
||||
AdvSceneSwitcher.condition.date.sunday="Sunday"
|
||||
AdvSceneSwitcher.condition.date.state.at="At"
|
||||
AdvSceneSwitcher.condition.date.state.after="After"
|
||||
AdvSceneSwitcher.condition.date.state.before="Before"
|
||||
AdvSceneSwitcher.condition.date.state.between="Between"
|
||||
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} Ignore date component {{ignoreTime}} Ignore time component"
|
||||
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} Repeat every {{duration}} on date match"
|
||||
AdvSceneSwitcher.condition.date.separator="and"
|
||||
AdvSceneSwitcher.condition.date.ignoreDate="If unchecked the date component will be ignored"
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="If unchecked the time component will be ignored"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="Show advanced settings"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="Show simple settings"
|
||||
AdvSceneSwitcher.condition.date.entry.simple="On {{dayOfWeek}} at {{weekTime}}"
|
||||
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
|
||||
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} Repeat every {{duration}} on date match"
|
||||
AdvSceneSwitcher.condition.sceneTransform="Scene item transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Get transform"
|
||||
AdvSceneSwitcher.condition.sceneTransform.regex="Use regular expressions"
|
||||
@@ -268,6 +299,25 @@ AdvSceneSwitcher.condition.errorStatus="OpenVR error: "
|
||||
AdvSceneSwitcher.condition.openvr.entry.line1="HMD is in ..."
|
||||
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
|
||||
AdvSceneSwitcher.condition.openvr.entry.line3="HMD is currently at {{xPos}} x {{yPos}} x {{zPos}}"
|
||||
AdvSceneSwitcher.condition.stats="OBS stats"
|
||||
AdvSceneSwitcher.condition.stats.type.fps="FPS"
|
||||
AdvSceneSwitcher.condition.stats.type.CPUUsage="CPU Usage"
|
||||
AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable="Disk space available"
|
||||
AdvSceneSwitcher.condition.stats.type.memoryUsage="Memory Usage"
|
||||
AdvSceneSwitcher.condition.stats.type.averageTimeToRender="Average time to render frame"
|
||||
AdvSceneSwitcher.condition.stats.type.skippedFrames="Skipped frames due to encoding lag"
|
||||
AdvSceneSwitcher.condition.stats.type.missedFrames="Frames missed due to rendering lag"
|
||||
AdvSceneSwitcher.condition.stats.type.droppedFrames.stream="Stream dropped frames"
|
||||
AdvSceneSwitcher.condition.stats.type.megabytesSent.stream="Stream total data output"
|
||||
AdvSceneSwitcher.condition.stats.type.bitrate.stream="Stream bitrate"
|
||||
AdvSceneSwitcher.condition.stats.type.droppedFrames.recording="Recording dropped frames"
|
||||
AdvSceneSwitcher.condition.stats.type.megabytesSent.recording="Recording total data output"
|
||||
AdvSceneSwitcher.condition.stats.type.bitrate.recording="Recording bitrate"
|
||||
AdvSceneSwitcher.condition.stats.condition.above="above"
|
||||
AdvSceneSwitcher.condition.stats.condition.equals="equal to"
|
||||
AdvSceneSwitcher.condition.stats.condition.below="below"
|
||||
AdvSceneSwitcher.condition.stats.dockHint="You can open the \"Stats\" dock to view the current status"
|
||||
AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Switch scene"
|
||||
@@ -283,7 +333,12 @@ AdvSceneSwitcher.action.audio.type.mute="Mute"
|
||||
AdvSceneSwitcher.action.audio.type.unmute="Unmute"
|
||||
AdvSceneSwitcher.action.audio.type.sourceVolume="Set source volume"
|
||||
AdvSceneSwitcher.action.audio.type.masterVolume="Set master volume"
|
||||
AdvSceneSwitcher.action.audio.fade="{{fade}}Fade over {{duration}} seconds"
|
||||
AdvSceneSwitcher.action.audio.fade.type.duration="over a duration of"
|
||||
AdvSceneSwitcher.action.audio.fade.type.rate="at a rate of"
|
||||
AdvSceneSwitcher.action.audio.fade.duration="{{fade}}Fade {{fadeTypes}} {{duration}} seconds."
|
||||
AdvSceneSwitcher.action.audio.fade.rate="{{fade}}Fade {{fadeTypes}} {{rate}}per second."
|
||||
AdvSceneSwitcher.action.audio.fade.wait="Wait for fade to complete."
|
||||
AdvSceneSwitcher.action.audio.fade.abort="Abort already active fade."
|
||||
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
|
||||
AdvSceneSwitcher.action.recording="Recording"
|
||||
AdvSceneSwitcher.action.recording.type.stop="Stop recording"
|
||||
@@ -332,7 +387,8 @@ AdvSceneSwitcher.action.media.type.stop="Stop"
|
||||
AdvSceneSwitcher.action.media.type.restart="Restart"
|
||||
AdvSceneSwitcher.action.media.type.next="Next"
|
||||
AdvSceneSwitcher.action.media.type.previous="Previous"
|
||||
AdvSceneSwitcher.action.media.entry="{{actions}} {{mediaSources}}"
|
||||
AdvSceneSwitcher.action.media.type.seek="Seek to"
|
||||
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
|
||||
AdvSceneSwitcher.action.macro="Macro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Pause"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Unpause"
|
||||
@@ -473,7 +529,7 @@ AdvSceneSwitcher.mediaTab.states.Paused="Paused"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="Stopped"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="Ended"
|
||||
AdvSceneSwitcher.mediaTab.states.error="Error"
|
||||
AdvSceneSwitcher.mediaTab.states.playedToEnd="Played to end"
|
||||
AdvSceneSwitcher.mediaTab.states.playlistEnd="Ended(Playlist)"
|
||||
AdvSceneSwitcher.mediaTab.states.any="Any"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="None"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Time shorter"
|
||||
@@ -580,7 +636,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to
|
||||
AdvSceneSwitcher.networkTab.title="Network"
|
||||
AdvSceneSwitcher.networkTab.description="This tab will allow you to remotely control the active scene of another OBS instance.\nPlease note that the scene names have to match exactly on all OBS instances."
|
||||
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
|
||||
AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately had to be disabled on macOS due to library incompatibilities when running the obs-websocket plugin in parallel."
|
||||
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
|
||||
@@ -653,6 +708,9 @@ AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Toggle Start/Stop for the
|
||||
AdvSceneSwitcher.hotkey.macro.pause="Pause macro %1"
|
||||
AdvSceneSwitcher.hotkey.macro.unpause="Unpause macro %1"
|
||||
AdvSceneSwitcher.hotkey.macro.togglePause="Toggle pause of macro %1"
|
||||
AdvSceneSwitcher.hotkey.upMacroSegmentHotkey="Move macro segment selection up"
|
||||
AdvSceneSwitcher.hotkey.downMacroSegmentHotkey="Move macro segment selection down"
|
||||
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="Remove selected macro segment"
|
||||
|
||||
AdvSceneSwitcher.askBackup="Detected a new version of the Advanced Scene Switcher.\nShould a backup of the old settings be created?"
|
||||
AdvSceneSwitcher.askForMacro="Select macro {{macroSelection}}"
|
||||
@@ -671,6 +729,7 @@ AdvSceneSwitcher.selectWindow="--select window--"
|
||||
AdvSceneSwitcher.selectSource="--select source--"
|
||||
AdvSceneSwitcher.selectAudioSource="--select audio source--"
|
||||
AdvSceneSwitcher.selectVideoSource="--select video source--"
|
||||
AdvSceneSwitcher.OBSVideoOutput="OBS video output"
|
||||
AdvSceneSwitcher.selectMediaSource="--select media source--"
|
||||
AdvSceneSwitcher.selectProcess="--select process--"
|
||||
AdvSceneSwitcher.selectFilter="--select filter--"
|
||||
@@ -692,6 +751,8 @@ AdvSceneSwitcher.stopped="Plugin stopped"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>This seems to be the first time the Advanced Scene Switcher was started.<br>Please have a look at the <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> for a list of guides and examples.<br>Do not hesitate to ask questions in the plugin's <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a> on the OBS forums!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="Development for this tab stopped!\nPlease consider transitioning to using Macros instead.\nThis hint can be disabled on the General tab."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="milliseconds"
|
||||
AdvSceneSwitcher.unit.secends="seconds"
|
||||
AdvSceneSwitcher.unit.minutes="minutes"
|
||||
|
||||
@@ -78,23 +78,16 @@ AdvSceneSwitcher.logic.rootNone="Если"
|
||||
AdvSceneSwitcher.logic.not="Если нет"
|
||||
; Macro Conditions
|
||||
AdvSceneSwitcher.condition.audio="Аудио"
|
||||
AdvSceneSwitcher.ondition.audio.state.below="Ниже"
|
||||
AdvSceneSwitcher.ondition.audio.state.above="Выше"
|
||||
AdvSceneSwitcher.condition.audio.entry="Громкость {{audioSources}} равна {{condition}} {{volume}} в течении {{duration}} секунд"
|
||||
AdvSceneSwitcher.condition.cursor="Область экрана"
|
||||
AdvSceneSwitcher.condition.scene="Сцена"
|
||||
AdvSceneSwitcher.condition.scene.type.current="Текущий"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="Предыдущий"
|
||||
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} сцена является {{scenes}} на {{duration}}"
|
||||
AdvSceneSwitcher.condition.window="Окно"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} существует и ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... это {{fullscreen}} полноэкранный {{maximized}} максимизированный {{focused}} сфокусированный"
|
||||
AdvSceneSwitcher.condition.file="Файл"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="Содержимое {{fileType}} {{filePath}} {{browseButton}} соответствует:"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Медиа"
|
||||
AdvSceneSwitcher.condition.media.entry="{{mediaSources}} состояние {{states}} и {{timeRestrictions}} {{time}}"
|
||||
AdvSceneSwitcher.condition.video="Видео"
|
||||
AdvSceneSwitcher.condition.video.condition.match="точно соответствует"
|
||||
AdvSceneSwitcher.condition.video.condition.differ="не совпадает"
|
||||
@@ -104,16 +97,13 @@ AdvSceneSwitcher.condition.video.condition.noImage="не имеет вывода
|
||||
AdvSceneSwitcher.condition.video.askFileAction="Вы хотите использовать существующий файл или создать скриншот текущего выбранного источника?"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.file="Использовать существующий файл"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Создать скриншот"
|
||||
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{filePath}} {{browseButton}} для {{duration}}"
|
||||
AdvSceneSwitcher.condition.stream="Потоковое вещание"
|
||||
AdvSceneSwitcher.condition.stream.state.start="Поток запущен"
|
||||
AdvSceneSwitcher.condition.stream.state.stop="Поток остановлен"
|
||||
AdvSceneSwitcher.condition.stream.entry="{{streamState}} для {{duration}}"
|
||||
AdvSceneSwitcher.condition.record="Запись"
|
||||
AdvSceneSwitcher.condition.record.state.start="Запись запущена"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Запись приостановлена"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Запись остановлена"
|
||||
AdvSceneSwitcher.condition.record.entry="{{recordState}} для {{duration}}"
|
||||
AdvSceneSwitcher.condition.process="Процесс"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}} запущен {{focused}} и сфокусирован"
|
||||
AdvSceneSwitcher.condition.idle="Простой"
|
||||
@@ -153,7 +143,6 @@ AdvSceneSwitcher.action.streaming.type.stop="Остановить потоков
|
||||
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}"
|
||||
AdvSceneSwitcher.action.run="Запустить"
|
||||
AdvSceneSwitcher.action.run.entry="Запустить {{filePath}} {{browseButton}}"
|
||||
|
||||
|
||||
; Transition Tab
|
||||
@@ -221,7 +210,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="Приостановлено"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="Остановлено"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="Закончилось"
|
||||
AdvSceneSwitcher.mediaTab.states.error="Ошибка"
|
||||
AdvSceneSwitcher.mediaTab.states.playedToEnd="Воспроизведено до конца"
|
||||
AdvSceneSwitcher.mediaTab.states.any="Любой"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="Нет"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Время короче"
|
||||
@@ -327,7 +315,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Эта вкладка по
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Сеть"
|
||||
AdvSceneSwitcher.networkTab.warning="Запуск сервера вне локальной сети позволит третьим лицам читать активную сцену."
|
||||
AdvSceneSwitcher.networkTab.DisabledWarning="ту функциональность, к сожалению, пришлось отключить на macOS из-за несовместимости библиотек при параллельном запуске плагина obs-websocket."
|
||||
AdvSceneSwitcher.networkTab.server="Запустить сервер (отправляет сообщения о переключении сцены всем подключенным клиентам)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Порт"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Заблокировать сервер на использование только IPv4"
|
||||
|
||||
682
data/locale/tr-TR.ini
Normal file
682
data/locale/tr-TR.ini
Normal file
@@ -0,0 +1,682 @@
|
||||
AdvSceneSwitcher.pluginName="Gelismis Otomatik Sahne Degistirici"
|
||||
AdvSceneSwitcher.windowTitle="Gelismis Otomatik Sahne Degistirici"
|
||||
|
||||
; General Tab
|
||||
AdvSceneSwitcher.generalTab.title="Genel"
|
||||
AdvSceneSwitcher.generalTab.status="Durum"
|
||||
AdvSceneSwitcher.generalTab.status.hotkeytips="OBS ayarlarinda yapılandırılabilir Kısayol tusları"
|
||||
AdvSceneSwitcher.generalTab.status.currentStatus="Gelismis Otomatik Sahne Degistirici:"
|
||||
AdvSceneSwitcher.generalTab.status.onStartup="OBS'yi baslatirken:"
|
||||
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="Etkinse, sahne değiştiriciyi etkinleştirin"
|
||||
AdvSceneSwitcher.generalTab.status.onStartup.alwaysStart="Her zaman sahne değiştiriciyi etkinleştirin"
|
||||
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.never="Asla"
|
||||
AdvSceneSwitcher.generalTab.status.autoStart.recording="Kayıt"
|
||||
AdvSceneSwitcher.generalTab.status.autoStart.streaming="Yayın"
|
||||
AdvSceneSwitcher.generalTab.status.autoStart.recordingAndStreaming="Kayıt veya Yayın"
|
||||
AdvSceneSwitcher.generalTab.status.checkInterval="Anahtar koşullarını her seferinde kontrol edin"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior="Genel davranış"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet="...için herhangi bir işlem yapılmazsa"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="Yalnızca yapılandırılmış kontrol aralığı kadar doğru olacaktır."
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="Geçiş Yapma"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="Rastgele sekmesinde herhangi bir sahneye geçin"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Değiştirmek:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Bir eşleşmeden sonra aşağıdakiler için işlem yapmayın:"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="Bu süre zarfında olası eşleşmeler göz ardı edilecektir!"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Ayrıntılı günlük kaydını etkinleştir"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Pencere konumunu ve boyutunu kaydet"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="Sistem tepsisi bildirimlerini göster"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="UI ipuçlarını devre dışı bırak"
|
||||
AdvSceneSwitcher.generalTab.priority="Öncelik"
|
||||
AdvSceneSwitcher.generalTab.priority.description="Geçiş yöntemleri önceliği (En yüksek öncelik en üsttedir)"
|
||||
AdvSceneSwitcher.generalTab.priority.threadPriority="Konu önceliğini kullan"
|
||||
AdvSceneSwitcher.generalTab.priority.threadPriorityNotice="(Önceliği \"Normal\" üzerine çıkarmak önerilmez)"
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings="Kayıt / Yükle Ayarları"
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.export="Dışa Aktar"
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.import="İçe Aktar"
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.exportWindowTitle="Gelişmiş Sahne Değiştirici ayarlarını dosyaya aktar ..."
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.importWindowTitle="Gelişmiş Sahne Değiştirici ayarlarını dosyadan içe aktar ..."
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.textType="Text dosyası (*.txt)"
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadFail="Gelişmiş Sahne Değiştirici ayarları dosyadan içe aktarmakta başarısız oldu"
|
||||
AdvSceneSwitcher.generalTab.saveOrLoadsettings.loadSuccess="Gelişmiş Sahne Değiştirici ayarları başarılı bir şekilde dosyadan içe aktarıldı"
|
||||
AdvSceneSwitcher.generalTab.priority.fileContent="Dosya İçeriği"
|
||||
AdvSceneSwitcher.generalTab.priority.sceneSequence="Sahne Sıralama"
|
||||
AdvSceneSwitcher.generalTab.priority.idleDetection="Boşta Algılama"
|
||||
AdvSceneSwitcher.generalTab.priority.executable="Yürütülebilir"
|
||||
AdvSceneSwitcher.generalTab.priority.screenRegion="Ekran Alanı"
|
||||
AdvSceneSwitcher.generalTab.priority.windowTitle="Pencere Başlığı"
|
||||
AdvSceneSwitcher.generalTab.priority.media="Medya"
|
||||
AdvSceneSwitcher.generalTab.priority.time="Zaman"
|
||||
AdvSceneSwitcher.generalTab.priority.audio="Ses"
|
||||
AdvSceneSwitcher.generalTab.priority.video="Video"
|
||||
AdvSceneSwitcher.generalTab.priority.macro="Makro"
|
||||
|
||||
; Macro Tab
|
||||
AdvSceneSwitcher.macroTab.title="Makro"
|
||||
AdvSceneSwitcher.macroTab.macros="Makrolar"
|
||||
AdvSceneSwitcher.macroTab.priorityWarning="Not: Makroları en yüksek öncelikli işlev olacak şekilde yapılandırmanız önerilir.\n Bu ayar, Genel sekmesinde değiştirilebilir."
|
||||
AdvSceneSwitcher.macroTab.help="Makrolar, birden çok koşula bağlı olarak bir dizi eylemi yürütmenize izin verir.\n\n Yeni bir Makro eklemek için vurgulanan artı sembolüne tıklayın."
|
||||
AdvSceneSwitcher.macroTab.editConditionHelp="Bu bölüm, Makro koşullarını tanımlamanızı sağlar.\n\n Sol taraftan mevcut bir Makro seçin veya yeni bir Makro ekleyin.\n Ardından yeni bir koşul eklemek için aşağıdaki artı düğmesini tıklayın."
|
||||
AdvSceneSwitcher.macroTab.editActionHelp="Bu bölüm, Makro eylemlerini tanımlamanızı sağlar.\n\n Soldan mevcut bir Makro seçin veya yeni bir Makro ekleyin.\n Ardından yeni bir eylem eklemek için aşağıdaki artı düğmesini tıklayın."
|
||||
AdvSceneSwitcher.macroTab.edit="Makro Düzenleme"
|
||||
AdvSceneSwitcher.macroTab.edit.logic="Mantık türü:"
|
||||
AdvSceneSwitcher.macroTab.edit.condition="Durum türü:"
|
||||
AdvSceneSwitcher.macroTab.edit.action="Eylem türü:"
|
||||
AdvSceneSwitcher.macroTab.add="Yeni Makro ekle"
|
||||
AdvSceneSwitcher.macroTab.name="İsim:"
|
||||
AdvSceneSwitcher.macroTab.run="Makro Çalıştırma"
|
||||
AdvSceneSwitcher.macroTab.runFail="\"%1\" çalıştırılamadı!\n Eylemlerden biri başarısız oldu veya makro zaten çalışıyor."
|
||||
AdvSceneSwitcher.macroTab.runInParallel="Makroyu diğer makrolara paralel olarak çalıştırın"
|
||||
AdvSceneSwitcher.macroTab.onChange="Eylemleri yalnızca koşul değişikliğinde gerçekleştirin"
|
||||
AdvSceneSwitcher.macroTab.defaultname="Makro %1"
|
||||
AdvSceneSwitcher.macroTab.exists="Macro adı zaten mevcut"
|
||||
AdvSceneSwitcher.macroTab.copy="Kopya oluştur"
|
||||
AdvSceneSwitcher.macroTab.expandAll="Hepsini Genişlet"
|
||||
AdvSceneSwitcher.macroTab.collapseAll="Hepsini Küçült"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="Girişi yoksay"
|
||||
AdvSceneSwitcher.logic.and="Ve"
|
||||
AdvSceneSwitcher.logic.or="Ya da"
|
||||
AdvSceneSwitcher.logic.andNot="ve değil"
|
||||
AdvSceneSwitcher.logic.orNot="ya da değil"
|
||||
AdvSceneSwitcher.logic.rootNone="Eğer"
|
||||
AdvSceneSwitcher.logic.not="Eğer değilse"
|
||||
|
||||
; Macro Conditions
|
||||
AdvSceneSwitcher.condition.audio="Ses"
|
||||
AdvSceneSwitcher.condition.audio.state.below="altında"
|
||||
AdvSceneSwitcher.condition.audio.state.above="üstünde"
|
||||
AdvSceneSwitcher.condition.audio.state.mute="sessiz"
|
||||
AdvSceneSwitcher.condition.audio.state.unmute="ses açıldı"
|
||||
AdvSceneSwitcher.condition.cursor="İmleç"
|
||||
AdvSceneSwitcher.condition.cursor.type.region="alanında"
|
||||
AdvSceneSwitcher.condition.cursor.type.moving="hareketlidir"
|
||||
AdvSceneSwitcher.condition.cursor.showFrame="Çerçeveyi Göster"
|
||||
AdvSceneSwitcher.condition.cursor.hideFrame="Çerçeveyi Gizle"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line1="İmleç {{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line2="İmleç şuanda {{xPos}} x {{yPos}}"
|
||||
AdvSceneSwitcher.condition.scene="Sahne"
|
||||
AdvSceneSwitcher.condition.scene.type.current="Mevcut Sahne"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="Önceki Sahne"
|
||||
AdvSceneSwitcher.condition.scene.type.changed="Sahne değişti"
|
||||
AdvSceneSwitcher.condition.scene.type.notChanged="Sahne değişmedi"
|
||||
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="Geçiş hedefi sahnesi için geçiş kontrolü sırasında"
|
||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}} {{scenes}}"
|
||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||
AdvSceneSwitcher.condition.window="Pencere"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} Varolan ve ..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... {{fullscreen}} Tam ekran {{maximized}} büyütüldü {{focused}} odaklanıldı {{windowFocusChanged}} ön plan penceresi değişikliği"
|
||||
AdvSceneSwitcher.condition.file="Dosya"
|
||||
AdvSceneSwitcher.condition.file.entry.line1="İçerik {{fileType}} {{filePath}} eşleştirmeler:"
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="Medya"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="Herhangi bir medya kaynağı"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="Tüm medya kaynakları "
|
||||
AdvSceneSwitcher.condition.media.matchOnChange="Yalnızca değişiklikle eşleştirin (Not: Bu seçenek gelecekteki bir sürümde kaldırılacaktır - lütfen bunun yerine zaman kısıtlamalarını kullanın)"
|
||||
AdvSceneSwitcher.condition.media.entry="{{mediaSources}}{{scenes}} durumu {{states}} ve {{timeRestrictions}} {{time}}"
|
||||
AdvSceneSwitcher.condition.video="Video"
|
||||
AdvSceneSwitcher.condition.video.condition.match="Tam olarak eşleşir"
|
||||
AdvSceneSwitcher.condition.video.condition.differ="Eşleştirme"
|
||||
AdvSceneSwitcher.condition.video.condition.hasChanged="Değişti"
|
||||
AdvSceneSwitcher.condition.video.condition.hasNotChanged="Değişmedi"
|
||||
AdvSceneSwitcher.condition.video.condition.noImage="Çıktı yok"
|
||||
AdvSceneSwitcher.condition.video.condition.pattern="Modelle eşleşir."
|
||||
AdvSceneSwitcher.condition.video.condition.object="nesne içerir"
|
||||
AdvSceneSwitcher.condition.video.askFileAction="Mevcut bir dosyayı kullanmak mı yoksa seçili olan kaynağın ekran görüntüsünü mü oluşturmak istiyorsunuz?"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.file="Mevcut dosyayı kullan"
|
||||
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Ekran görüntüsü oluştur"
|
||||
AdvSceneSwitcher.condition.video.usePatternForChangedCheck="Model eşleştirmeyi kullan"
|
||||
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="Bu, koşulun doğru olması için görüntünün ne kadar değişmesi gerektiğini kontrol etmenizi sağlar."
|
||||
AdvSceneSwitcher.condition.video.patternThreshold="Eşik: "
|
||||
AdvSceneSwitcher.condition.video.patternThresholdDescription="Daha yüksek bir eşik değeri, modelin video kaynağıyla daha yakından eşleşmesi gerektiği anlamına gelir."
|
||||
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="Desen için maske olarak alfa kanalını kullanın."
|
||||
AdvSceneSwitcher.condition.video.objectScaleThreshold="Ölçek faktörü: "
|
||||
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="Daha düşük bir ölçek faktörü, daha fazla eşleşmeye, ancak daha yüksek CPU yüküne yol açacaktır."
|
||||
AdvSceneSwitcher.condition.video.minNeighborDescription="Daha yüksek bir minimum komşu değeri, daha az ancak daha kaliteli eşleşmelerle sonuçlanacaktır."
|
||||
AdvSceneSwitcher.condition.video.showMatch="Eşleşmeyi Göster"
|
||||
AdvSceneSwitcher.condition.video.screenshotFail="Kaynağın ekran görüntüsü alınamadı!"
|
||||
AdvSceneSwitcher.condition.video.patternMatchFail="Desen bulunamadı!"
|
||||
AdvSceneSwitcher.condition.video.objectMatchFail="Nesne bulunamadı!"
|
||||
AdvSceneSwitcher.condition.video.modelLoadFail="Model verileri yüklenemedi!"
|
||||
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="Model verileri (haar kademeli sınıflandırıcı):{{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="Minimum komşular: {{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}} Yalnızca her seferinde kontrol gerçekleştirerek CPU yükünü azaltın {{throttleCount}} millisaniyeler"
|
||||
AdvSceneSwitcher.condition.stream="Yayınlama"
|
||||
AdvSceneSwitcher.condition.stream.state.start="Yayın çalışıyor"
|
||||
AdvSceneSwitcher.condition.stream.state.stop="Yayın durdu"
|
||||
AdvSceneSwitcher.condition.stream.state.starting="Yayın başlıyor"
|
||||
AdvSceneSwitcher.condition.stream.state.stopping="Yayın duruyor"
|
||||
AdvSceneSwitcher.condition.stream.entry="{{streamState}}"
|
||||
AdvSceneSwitcher.condition.record="Kayıt"
|
||||
AdvSceneSwitcher.condition.record.state.start="Kayıt Çalışıyor"
|
||||
AdvSceneSwitcher.condition.record.state.pause="Kayıt durakladı"
|
||||
AdvSceneSwitcher.condition.record.state.stop="Kayıt durdu"
|
||||
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
|
||||
AdvSceneSwitcher.condition.process="İşlem"
|
||||
AdvSceneSwitcher.condition.process.entry="{{processes}} çalışıyor {{focused}} ve odaklandı"
|
||||
AdvSceneSwitcher.condition.idle="Boşta"
|
||||
AdvSceneSwitcher.condition.idle.entry="...için klavye veya fare girişi yok {{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="Eklenti durumu"
|
||||
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Bu aralıkta otomatik sahne değişikliği tetiklendi"
|
||||
AdvSceneSwitcher.condition.pluginState.state.running="Gelişmiş sahne değiştirici çalışıyor"
|
||||
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
||||
AdvSceneSwitcher.condition.timer="Zamanlayıcı"
|
||||
AdvSceneSwitcher.condition.timer.type.fixed="Sabit"
|
||||
AdvSceneSwitcher.condition.timer.type.random="Rastgele"
|
||||
AdvSceneSwitcher.condition.timer.pause="Duraklat"
|
||||
AdvSceneSwitcher.condition.timer.continue="Devam etmek"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1.fixed="{{type}} süresi {{duration}} geçti"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1.random="{{type}} itibaren süre {{duration}} to {{duration2}} geçti"
|
||||
AdvSceneSwitcher.condition.timer.entry.line2="Kalan Süre: {{remaining}} saniye"
|
||||
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Kalan zamanı kayıt {{autoReset}} Süreye ulaşıldıktan sonra zamanlayıcıyı otomatik olarak sıfırla"
|
||||
AdvSceneSwitcher.condition.timer.reset="Sıfırlama"
|
||||
AdvSceneSwitcher.condition.macro="Makro"
|
||||
AdvSceneSwitcher.condition.macro.type.count="Saymak"
|
||||
AdvSceneSwitcher.condition.macro.type.state="Belirtmek"
|
||||
AdvSceneSwitcher.condition.macro.type.selection="Koşul türü: {{types}}"
|
||||
AdvSceneSwitcher.condition.macro.count.type.below="Daha az"
|
||||
AdvSceneSwitcher.condition.macro.count.type.above="Daha fazla"
|
||||
AdvSceneSwitcher.condition.macro.count.type.equal="Aynen"
|
||||
AdvSceneSwitcher.condition.macro.count.reset="Sıfırlama"
|
||||
AdvSceneSwitcher.condition.macro.pausedWarning="Seçili makro şu anda duraklatıldı!"
|
||||
AdvSceneSwitcher.condition.macro.state.entry="Koşulları {{macros}} doğru"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} yürütüldü {{conditions}} {{count}} zaman"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line2="Mevcut sayı: {{currentCount}} {{resetCount}}"
|
||||
AdvSceneSwitcher.condition.source="Kaynak"
|
||||
AdvSceneSwitcher.condition.source.type.active="Aktiftir"
|
||||
AdvSceneSwitcher.condition.source.type.showing="gösteriyor"
|
||||
AdvSceneSwitcher.condition.source.type.settings="Ayarlar eşleştirildi"
|
||||
AdvSceneSwitcher.condition.source.regex="Normal ifadeler kullanın"
|
||||
AdvSceneSwitcher.condition.source.getSettings="Mevcut ayarları al"
|
||||
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
|
||||
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
|
||||
AdvSceneSwitcher.condition.source.entry.line3="{{regex}} {{getSettings}}"
|
||||
AdvSceneSwitcher.condition.virtualCamera="Sanal kamera"
|
||||
AdvSceneSwitcher.condition.virtualCamera.state.start="Sanal kamera başladı"
|
||||
AdvSceneSwitcher.condition.virtualCamera.state.stop="Sanal kamera durdu"
|
||||
AdvSceneSwitcher.condition.virtualCamera.entry="{{states}}"
|
||||
AdvSceneSwitcher.condition.filter="Filtrele"
|
||||
AdvSceneSwitcher.condition.filter.type.active="Etkinleştirildi"
|
||||
AdvSceneSwitcher.condition.filter.type.showing="Etkisizleştirildi"
|
||||
AdvSceneSwitcher.condition.filter.type.settings="Ayarlar eşleşti"
|
||||
AdvSceneSwitcher.condition.filter.regex="Normal ifadeler kullanın"
|
||||
AdvSceneSwitcher.condition.filter.getSettings="Mevcut ayarları al"
|
||||
AdvSceneSwitcher.condition.filter.entry.line1="Açık {{sources}} {{filters}} {{conditions}}"
|
||||
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
|
||||
AdvSceneSwitcher.condition.filter.entry.line3="{{regex}} {{getSettings}}"
|
||||
AdvSceneSwitcher.condition.sceneOrder="Sahne öğesi sırası"
|
||||
AdvSceneSwitcher.condition.sceneOrder.type.above="Üstünde"
|
||||
AdvSceneSwitcher.condition.sceneOrder.type.below="Altında"
|
||||
AdvSceneSwitcher.condition.sceneOrder.type.position="konumunda"
|
||||
AdvSceneSwitcher.condition.sceneOrder.positionInfo="Konum değeri en altta 0 ile başlar ve sahne gruplarındakiler dahil her sahne öğesi için bir artar."
|
||||
AdvSceneSwitcher.condition.sceneOrder.entry="Açık{{scenes}}{{sources}}{{conditions}}{{sources2}}{{position}}"
|
||||
AdvSceneSwitcher.condition.hotkey="Kısayol tuşu"
|
||||
AdvSceneSwitcher.condition.hotkey.name="Makro tetik kısayol tuşu"
|
||||
AdvSceneSwitcher.condition.hotkey.tip="Not: Bu kısayol tuşu için tuş atamalarını OBS ayarları penceresinde yapılandırabilirsiniz."
|
||||
AdvSceneSwitcher.condition.replay="Tekrar oynatma arabelleği"
|
||||
AdvSceneSwitcher.condition.replay.state.stopped="Tekrar oynatma arabelleği durdu"
|
||||
AdvSceneSwitcher.condition.replay.state.started="Tekrar oynatma arabelleği başladı"
|
||||
AdvSceneSwitcher.condition.replay.state.saved="Tekrar oynatma arabelleği kaydedildi"
|
||||
AdvSceneSwitcher.condition.replay.entry="{{state}}"
|
||||
AdvSceneSwitcher.condition.date="Tarih"
|
||||
AdvSceneSwitcher.condition.date.state.at="de"
|
||||
AdvSceneSwitcher.condition.date.state.after="Sonra"
|
||||
AdvSceneSwitcher.condition.date.state.before="Önce"
|
||||
AdvSceneSwitcher.condition.date.state.between="Arasında"
|
||||
AdvSceneSwitcher.condition.sceneTransform="Sahne öğesi dönüşümü"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="Dönüşümü al"
|
||||
AdvSceneSwitcher.condition.sceneTransform.regex="Normal ifadeler kullanın"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line1="Açık{{scenes}}{{sources}} dönüşümle eşleşir"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line2="{{settings}}"
|
||||
AdvSceneSwitcher.condition.sceneTransform.entry.line3="{{regex}} {{getSettings}}"
|
||||
AdvSceneSwitcher.condition.transition="Geçiş"
|
||||
AdvSceneSwitcher.condition.transition.type.current="Geçerli geçiş türü"
|
||||
AdvSceneSwitcher.condition.transition.type.duration="Mevcut geçiş süresi"
|
||||
AdvSceneSwitcher.condition.transition.type.started="Geçiş başladı"
|
||||
AdvSceneSwitcher.condition.transition.type.ended="Geçiş sona erdi"
|
||||
AdvSceneSwitcher.condition.transition.type.transitionSource="İtibaren geçiş"
|
||||
AdvSceneSwitcher.condition.transition.type.transitionTarget="Geçiş "
|
||||
AdvSceneSwitcher.condition.transition.durationSuffix="saniyesi"
|
||||
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{scenes}}{{duration}}{{durationSuffix}}"
|
||||
AdvSceneSwitcher.condition.sceneVisibility="Sahne öğesi görünürlüğü"
|
||||
AdvSceneSwitcher.condition.sceneVisibility.type.shown="Gösterilen"
|
||||
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="Gizli"
|
||||
AdvSceneSwitcher.condition.sceneVisibility.entry="Açık {{scenes}}{{sources}}is{{conditions}}"
|
||||
AdvSceneSwitcher.condition.studioMode="Stüdyo ingilizce"
|
||||
AdvSceneSwitcher.condition.studioMode.state.active="Studyo modu Etkinleştirildi"
|
||||
AdvSceneSwitcher.condition.studioMode.state.notActive="Studyo modu aktif değil"
|
||||
AdvSceneSwitcher.condition.studioMode.state.previewScene="Önizleme sahnesi"
|
||||
AdvSceneSwitcher.condition.studioMode.entry="{{conditions}}{{scenes}}"
|
||||
AdvSceneSwitcher.condition.openvr="Aç VR"
|
||||
AdvSceneSwitcher.condition.errorStatus="Aç VR hatası: "
|
||||
AdvSceneSwitcher.condition.openvr.entry.line1="HMD içinde ..."
|
||||
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
|
||||
AdvSceneSwitcher.condition.openvr.entry.line3="HMD mevcut {{xPos}} x {{yPos}} x {{zPos}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="Sahne Degistirici"
|
||||
AdvSceneSwitcher.action.scene.entry="Sahneyi {{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"
|
||||
AdvSceneSwitcher.action.wait.type.random="rastgele"
|
||||
AdvSceneSwitcher.action.wait.entry.fixed="Şunu bekle {{waitType}} süre {{duration}}"
|
||||
AdvSceneSwitcher.action.wait.entry.random="Şunu bekle {{waitType}} bu aralıktan {{duration}} buna {{duration2}}"
|
||||
AdvSceneSwitcher.action.audio="Ses"
|
||||
AdvSceneSwitcher.action.audio.type.mute="Sessiz"
|
||||
AdvSceneSwitcher.action.audio.type.unmute="Ses açmak"
|
||||
AdvSceneSwitcher.action.audio.type.sourceVolume="Kaynak ses seviyesini ayarla"
|
||||
AdvSceneSwitcher.action.audio.type.masterVolume="Ana ses seviyesini ayarla"
|
||||
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
|
||||
AdvSceneSwitcher.action.recording="Kayıt"
|
||||
AdvSceneSwitcher.action.recording.type.stop="Kayıt Durdur"
|
||||
AdvSceneSwitcher.action.recording.type.start="Kayıt Başlat"
|
||||
AdvSceneSwitcher.action.recording.type.pause="Kayıt Duraklat"
|
||||
AdvSceneSwitcher.action.recording.type.unpause="Kayıt Duraklatma"
|
||||
AdvSceneSwitcher.action.recording.pause.hint="Kayıt ayarlarınıza bağlı olarak kaydı duraklatamayabileceğinizi unutmayın."
|
||||
AdvSceneSwitcher.action.recording.entry="{{actions}}{{pauseHint}}"
|
||||
AdvSceneSwitcher.action.replay="Tekrar arabelleği"
|
||||
AdvSceneSwitcher.action.replay.type.stop="Tekrar arabelleğini durdur"
|
||||
AdvSceneSwitcher.action.replay.type.start="Tekrar arabelleğini başlat"
|
||||
AdvSceneSwitcher.action.replay.type.save="Tekrar arabelleğini kaydet"
|
||||
AdvSceneSwitcher.action.replay.entry="{{actions}}"
|
||||
AdvSceneSwitcher.action.streaming="Yayın"
|
||||
AdvSceneSwitcher.action.streaming.type.stop="Yayın durdur"
|
||||
AdvSceneSwitcher.action.streaming.type.start="Yayın başlat"
|
||||
AdvSceneSwitcher.action.streaming.entry="{{actions}}"
|
||||
AdvSceneSwitcher.action.run="Çalıştır"
|
||||
AdvSceneSwitcher.action.run.arguments="Argümanlar:"
|
||||
AdvSceneSwitcher.action.run.addArgument="Argüman ekle"
|
||||
AdvSceneSwitcher.action.run.addArgumentDescription="Yeni Argüman ekle:"
|
||||
AdvSceneSwitcher.action.run.entry="Çalıştır {{filePath}}"
|
||||
AdvSceneSwitcher.action.sceneVisibility="Sahne öğesi görünürlüğü"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.show="Göster"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="Gizle"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.source="Kayıt"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Herhangi"
|
||||
AdvSceneSwitcher.action.sceneVisibility.entry="Açık{{scenes}}{{actions}}{{sourceTypes}}{{sources}}{{sourceGroups}}"
|
||||
AdvSceneSwitcher.action.filter="Filtrele"
|
||||
AdvSceneSwitcher.action.filter.type.enable="Etkin"
|
||||
AdvSceneSwitcher.action.filter.type.disable="Etkisiz"
|
||||
AdvSceneSwitcher.action.filter.type.settings="Ayarları yap"
|
||||
AdvSceneSwitcher.action.filter.entry="Açık {{sources}} {{actions}} {{filters}}"
|
||||
AdvSceneSwitcher.action.filter.getSettings="Mevcut ayarları al"
|
||||
AdvSceneSwitcher.action.source="Kaynak"
|
||||
AdvSceneSwitcher.action.source.type.enable="Etkin"
|
||||
AdvSceneSwitcher.action.source.type.disable="Etkisiz"
|
||||
AdvSceneSwitcher.action.source.type.settings="Ayarları yap"
|
||||
AdvSceneSwitcher.action.source.entry="{{actions}} {{sources}}"
|
||||
AdvSceneSwitcher.action.source.warning="Uyarı: Kaynakların global olarak etkinleştirilmesi ve devre dışı bırakılması, OBS UI tarafından kontrol edilemez"
|
||||
AdvSceneSwitcher.action.source.getSettings="Mevcut Ayarları yap"
|
||||
AdvSceneSwitcher.action.media="Medya"
|
||||
AdvSceneSwitcher.action.media.type.play="Oynat"
|
||||
AdvSceneSwitcher.action.media.type.pause="Duraklat"
|
||||
AdvSceneSwitcher.action.media.type.stop="Dur"
|
||||
AdvSceneSwitcher.action.media.type.restart="Yeniden Başlat"
|
||||
AdvSceneSwitcher.action.media.type.next="Sonraki"
|
||||
AdvSceneSwitcher.action.media.type.previous="Önceki"
|
||||
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
|
||||
AdvSceneSwitcher.action.macro="Makro"
|
||||
AdvSceneSwitcher.action.macro.type.pause="Duraklat"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="Duraklatma"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="Sayacı sıfırla"
|
||||
AdvSceneSwitcher.action.macro.type.run="Çalıştır"
|
||||
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
|
||||
AdvSceneSwitcher.action.pluginState="Eklenti durumu"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="Advanced Scene Switcher eklentisini durdurun"
|
||||
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.virtualCamera="Sanal Kamera"
|
||||
AdvSceneSwitcher.action.virtualCamera.type.stop="Sanal Kamerayı Durdur"
|
||||
AdvSceneSwitcher.action.virtualCamera.type.start="Sanal Kamerayı Başlat"
|
||||
AdvSceneSwitcher.action.virtualCamera.entry="{{actions}}"
|
||||
AdvSceneSwitcher.action.hotkey="Kısayol tuşu"
|
||||
AdvSceneSwitcher.action.hotkey.leftShift="Sol shift"
|
||||
AdvSceneSwitcher.action.hotkey.rightShift="Sağ Shift"
|
||||
AdvSceneSwitcher.action.hotkey.leftCtrl="Sol Ctrl"
|
||||
AdvSceneSwitcher.action.hotkey.rightCtrl="Sağ Ctrl"
|
||||
AdvSceneSwitcher.action.hotkey.leftAlt="Sol Alt"
|
||||
AdvSceneSwitcher.action.hotkey.rightAlt="Sağ Alt"
|
||||
AdvSceneSwitcher.action.hotkey.leftMeta="Sol Meta"
|
||||
AdvSceneSwitcher.action.hotkey.rightMeta="Sağ Meta"
|
||||
AdvSceneSwitcher.action.hotkey.onlyOBS="Tuşa basımı yalnızca OBS'ye gönder"
|
||||
AdvSceneSwitcher.action.hotkey.disabled="Tuşa basma simülasyonu yapılamıyor - işlevsellik devre dışı!"
|
||||
AdvSceneSwitcher.action.hotkey.entry="Bas {{keys}} uygun {{duration}} millisaniye"
|
||||
AdvSceneSwitcher.action.sceneOrder="Sahne öğesi sırası"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Yukarı taşı"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Aşağı indir"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveTop="En üste taşı"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Aşağıya taşı"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Konuma taşı"
|
||||
AdvSceneSwitcher.action.sceneOrder.entry="Açık{{scenes}}{{actions}}{{sources}}{{position}}"
|
||||
AdvSceneSwitcher.action.sceneTransform="Sahne öğesi dönüşümü"
|
||||
AdvSceneSwitcher.action.sceneTransform.getTransform="Dönüşümü al"
|
||||
AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}transform{{sources}}"
|
||||
AdvSceneSwitcher.action.file="Dosya"
|
||||
AdvSceneSwitcher.action.file.type.write="Yaz"
|
||||
AdvSceneSwitcher.action.file.type.append="Ekle"
|
||||
AdvSceneSwitcher.action.file.entry="{{actions}} {{filePath}}:"
|
||||
AdvSceneSwitcher.action.previewScene="Önizleme sahnesini değiştir"
|
||||
AdvSceneSwitcher.action.previewScene.entry="Önizleme sahnesini şu şekilde değiştir: {{scenes}}"
|
||||
AdvSceneSwitcher.action.SceneSwap="Sahneyi değiştir (Studyo modu)"
|
||||
AdvSceneSwitcher.action.SceneSwap.entry="Stüdyo modunda önizleme ve program sahnesini değiştir"
|
||||
AdvSceneSwitcher.action.transition="Geçiş"
|
||||
AdvSceneSwitcher.action.transition.entry.line1="{{setType}}Geçiş türünü ayarla {{transitions}}"
|
||||
AdvSceneSwitcher.action.transition.entry.line2="{{setDuration}}Geçiş süresini şuna ayarla: {{duration}}saniyeler"
|
||||
AdvSceneSwitcher.action.timer="Zamanlayıcı"
|
||||
AdvSceneSwitcher.action.timer.type.pause="Duraklat"
|
||||
AdvSceneSwitcher.action.timer.type.continue="Devam et"
|
||||
AdvSceneSwitcher.action.timer.type.reset="Sıfırla"
|
||||
AdvSceneSwitcher.action.timer.type.setTimeRemaining="Kalan süreyi ayarla"
|
||||
AdvSceneSwitcher.action.timer.entry="{{timerAction}} zamanlayıcılar açık {{macros}} {{duration}}"
|
||||
AdvSceneSwitcher.action.random="Rastgele"
|
||||
AdvSceneSwitcher.action.random.entry="Aşağıdaki makrolardan herhangi birini rastgele çalıştırın (duraklatılmış makrolar yoksayılır)"
|
||||
AdvSceneSwitcher.action.systray="Sistem tepsisi bildirimi"
|
||||
AdvSceneSwitcher.action.systray.entry="Bildirimleri göster: {{message}}"
|
||||
AdvSceneSwitcher.action.screenshot="Ekran görüntüsü"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS'nin ana çıkışı"
|
||||
AdvSceneSwitcher.action.screenshot.entry="Ekran görüntüsü {{sources}}"
|
||||
AdvSceneSwitcher.action.profile="Profil"
|
||||
AdvSceneSwitcher.action.profile.entry="Aktif profili şununla değiştir: {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="Sahne koleksiyonu"
|
||||
AdvSceneSwitcher.action.sceneCollection.entry="Aktif sahne koleksiyonunu şu şekilde değiştir: {{sceneCollections}}"
|
||||
AdvSceneSwitcher.action.sceneCollection.warning="Not: Değişen sahne koleksiyonu sahne değiştirici ayarlarını da yeniden yükleyeceğinden, bundan sonraki eylemler yürütülmeyecektir.\n Ayarlar penceresi açılırken sahne toplama eylemi yok sayılacaktır."
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Geçiş"
|
||||
AdvSceneSwitcher.transitionTab.setTransitionBy="Geçişleri değiştirirken:"
|
||||
AdvSceneSwitcher.transitionTab.transitionOverride="Geçiş geçersiz kılmaları ayarla"
|
||||
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Etkin geçiş türünü değiştir"
|
||||
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="En az bir seçenek etkinleştirilmelidir: \n\n - Geçiş geçersiz kılmalarını kullan \n\n - Etkin geçiş türünü değiştir"
|
||||
AdvSceneSwitcher.transitionTab.transitionForAToB="Sahne A'dan sahne B'ye otomatik sahne geçişi için geçişi kullanın"
|
||||
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>Bu ayarlar <span style=\"font-style:italic;\">yalnızca</span>, sahne değiştiricinin neden olduğu geçişleri etkiler - <a href=\ Göz atın "https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Geçiş Tablosu</span></ a> bunu manuel sahne değişiklikleri için yapılandırmak istiyorsanız.<br/>Burada tanımlanan ayarlar, sahne değiştiricide başka bir yerde yapılandırılmış geçiş ayarlarına göre önceliklidir.<br/><br/>Yeni bir giriş eklemek için aşağıdaki artı simgesini tıklayın. .</p></body></html>"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition="Sahne aktifse geçişi değiştir"
|
||||
AdvSceneSwitcher.transitionTab.entry="Şuradan geçiş yap {{scenes}} buna {{scenes2}} kullanarak {{transitions}} süresinde {{duration}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="Ne zaman sahne {{scenes}} aktifse, varsayılan sahne geçişini değiştir{{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Bir giriş eklemek için artı sembolüne tıklayın."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Geçişi değiştirmek {{defTransitionDelay}} sahne değişikliğinden sonra."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="Gecikme, bir geçiş devam ederken geçiş türü değiştirilirse meydana gelebilecek iptal edilen sahne geçişlerini önlemek için kullanılır."
|
||||
|
||||
; Pause Scenes Tab
|
||||
AdvSceneSwitcher.pauseTab.title="Duraklat"
|
||||
AdvSceneSwitcher.pauseTab.pauseOnScene="Sahne Değiştiriciyi sahnede duraklatın"
|
||||
AdvSceneSwitcher.pauseTab.pauseInFocus1="Sahne Değiştiriciyi duraklat şu durumda: "
|
||||
AdvSceneSwitcher.pauseTab.pauseInFocus2="odakta"
|
||||
AdvSceneSwitcher.pauseTab.pauseTypeScene="sahne aktif"
|
||||
AdvSceneSwitcher.pauseTab.pauseTypeWindow="pencere odakta"
|
||||
AdvSceneSwitcher.pauseTab.pauseTargetAll="tümü"
|
||||
AdvSceneSwitcher.pauseTab.pauseEntry="Duraklat {{pauseTargets}} kontrol edildiğinde {{pauseTypes}} {{scenes}} {{windows}}"
|
||||
AdvSceneSwitcher.pauseTab.help="Bu sekmede, bir sahne etkinse veya pencere odaktaysa bireysel geçiş yöntemlerini duraklatmak için yapılandırabilirsiniz.\ve Devam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; Window Title Tab
|
||||
AdvSceneSwitcher.windowTitleTab.title="Başlık"
|
||||
AdvSceneSwitcher.windowTitleTab.regexrDescription="<html><head/><body><p>Doğrudan pencere başlıklarını veya geçerli normal ifadeyi girin. <a href=\"https://regexr.com\"><span style=\" text-decoration: underline; color:#268bd2;\">RegExr</span'i kullanarak normal ifadeler için söz dizimini ve eşleşmeleri kontrol edebilirsiniz. ></a></p></body></html>"
|
||||
AdvSceneSwitcher.windowTitleTab.stayInFocus1="Bu pencere adını yoksay"
|
||||
AdvSceneSwitcher.windowTitleTab.stayInFocus2=" "
|
||||
AdvSceneSwitcher.windowTitleTab.fullscreen="tam ekran ise"
|
||||
AdvSceneSwitcher.windowTitleTab.maximized="maksimize edilirse"
|
||||
AdvSceneSwitcher.windowTitleTab.focused="eğer odaklanmışsa"
|
||||
AdvSceneSwitcher.windowTitleTab.entry="{{windows}} {{scenes}} {{transitions}} {{fullscreen}} {{maximized}} {{focused}}"
|
||||
AdvSceneSwitcher.windowTitleTab.windowsHelp="Çalışan uygulamaların pencere başlığına göre sahneler arasında geçiş yapın.\nAşağıdaki ek koşullar seçilebilir:\nPencere Tam Ekrandır\nPencere büyütülür\nPencere odaklanılır\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
AdvSceneSwitcher.windowTitleTab.ignoreWindowsHelp="Bir pencere başlığı yoksayılırsa, sahne değiştirici, önceden seçilen pencere hala odaktaymış gibi davranacaktır.\nBu, sık sık farklı bir pencereye geçerseniz, sahne değişikliğini tetiklemeyecek olan sahne geçişlerinden kaçınmanıza olanak tanır.\ n\nBir pencere seçin veya yukarıya bir pencere başlığı girin ve listeye eklemek için aşağıdaki artı sembolüne tıklayın."
|
||||
|
||||
; Executable Tab
|
||||
AdvSceneSwitcher.executableTab.title="yürütülebilir"
|
||||
AdvSceneSwitcher.executableTab.implemented="dasOven tarafından uygulandı"
|
||||
AdvSceneSwitcher.executableTab.requiresFocus="sadece odaklanmışsa"
|
||||
AdvSceneSwitcher.executableTab.entry="Bu {{processes}} çalışıyorsa değiştir {{scenes}} kullanılır {{transitions}} {{requiresFocus}}"
|
||||
AdvSceneSwitcher.executableTab.help="Bu sekme, bir işlem çalışıyorsa sahneleri otomatik olarak değiştirmenize olanak tanır.\nBu, pencere adının değişebileceği veya bilinmediği durumlarda faydalı olabilir.\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; Screen Region Tab
|
||||
AdvSceneSwitcher.screenRegionTab.title="Bölge"
|
||||
AdvSceneSwitcher.screenRegionTab.currentPosition="İmleç şu anda şu konumda:"
|
||||
AdvSceneSwitcher.screenRegionTab.showGuideFrames="Kılavuz çerçeveleri göster"
|
||||
AdvSceneSwitcher.screenRegionTab.hideGuideFrames="Kılavuz çerçeveleri gizle"
|
||||
AdvSceneSwitcher.screenRegionTab.excludeScenes.None="Seçim yok"
|
||||
AdvSceneSwitcher.screenRegionTab.entry="Eğer imleç şuanda böyleyse {{minX}} {{minY}} x {{maxX}} {{maxY}} şuna çevir {{scenes}} kullanarak {{transitions}} sürece {{excludeScenes}}"
|
||||
AdvSceneSwitcher.screenRegionTab.help="Bu sekme, fare imlecinizin geçerli konumuna göre sahneleri otomatik olarak değiştirmenize olanak tanır.\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; Media Tab
|
||||
AdvSceneSwitcher.mediaTab.title="Medya"
|
||||
AdvSceneSwitcher.mediaTab.implemented="Exelero tarafından uygulandı"
|
||||
AdvSceneSwitcher.mediaTab.states.none="Yok"
|
||||
AdvSceneSwitcher.mediaTab.states.playing="Oynamak"
|
||||
AdvSceneSwitcher.mediaTab.states.opening="Açmak"
|
||||
AdvSceneSwitcher.mediaTab.states.buffering="Arabelleğe alma"
|
||||
AdvSceneSwitcher.mediaTab.states.Paused="Duraklatıldı"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="Durduruldu"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="Bitti"
|
||||
AdvSceneSwitcher.mediaTab.states.error="Hata"
|
||||
AdvSceneSwitcher.mediaTab.states.any="Herhangi"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="Yok"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="Zaman daha kısa"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.longer="Zaman daha uzun"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.remainShorter="Kalan süre daha kısa"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.remainLonger="Kalan süre daha uzun"
|
||||
AdvSceneSwitcher.mediaTab.entry="Kaynak {{mediaSources}} durumu {{states}} ve {{timeRestrictions}} {{time}} şuna çevir {{scenes}} kullanarak {{transitions}}"
|
||||
AdvSceneSwitcher.mediaTab.help="Bu sekme, medya kaynaklarının durumuna göre sahneler arasında geçiş yapmanızı sağlar.\nÖrneğin, seçilen medya kaynaklı oynatmayı bitirdiğinde otomatik olarak önceki sahneye dönebilirsiniz.\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; File Tab
|
||||
AdvSceneSwitcher.fileTab.title="Dosya"
|
||||
AdvSceneSwitcher.fileTab.readWriteSceneFile="Oku / Sahneden yazdır / Dosyaya"
|
||||
AdvSceneSwitcher.fileTab.currentSceneOutputFile="Mevcut sahnenin adını bu dosyaya yazın:"
|
||||
AdvSceneSwitcher.fileTab.switchSceneBaseOnFile="Dosya girişine göre sahnelerin değiştirilmesini etkinleştirin"
|
||||
AdvSceneSwitcher.fileTab.switchSceneNameInputFile="Bu dosyadan geçiş yapılacak sahne adını okuyun:"
|
||||
AdvSceneSwitcher.fileTab.switchSceneBaseOnFileContent="Dosya içeriğine göre sahneyi değiştir"
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning="Uzak seçeneğini seçerseniz, sahne değiştiricinin Genel sekmesinde belirtildiği gibi her x ms'de bir uzak konuma erişmeye çalışacağını lütfen unutmayın!"
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning1="Sahne değiştiricinin her seferinde uzak konuma erişmeye çalışacağını unutmayın."
|
||||
AdvSceneSwitcher.fileTab.remoteFileWarning2="ms"
|
||||
AdvSceneSwitcher.fileTab.libcurlWarning="libcurl yüklenemedi! Uzak dosyalara erişim mümkün olmayacak!"
|
||||
AdvSceneSwitcher.fileTab.selectWrite="Yazmak için bir dosya seçin..."
|
||||
AdvSceneSwitcher.fileTab.selectRead="Okumak için bir dosya seçin..."
|
||||
AdvSceneSwitcher.fileTab.textFileType="Text dosyası (*.txt)"
|
||||
AdvSceneSwitcher.fileTab.anyFileType="Herhangi bir dosya türü (*.*)"
|
||||
AdvSceneSwitcher.fileTab.remote="uzak dosya"
|
||||
AdvSceneSwitcher.fileTab.local="yerel dosya"
|
||||
AdvSceneSwitcher.fileTab.useRegExp="normal ifadeler kullan (desen eşleştirme)"
|
||||
AdvSceneSwitcher.fileTab.checkfileContentTime="değişiklik tarihi değiştiyse"
|
||||
AdvSceneSwitcher.fileTab.checkfileContent="İçerik değiştiyse"
|
||||
AdvSceneSwitcher.fileTab.entry="Suna dönüştür {{scenes}} kullanıp {{transitions}} içerik şunlarla {{fileType}} {{filePath}} {{browseButton}} eşleşirse:"
|
||||
AdvSceneSwitcher.fileTab.entry2="{{matchText}}"
|
||||
AdvSceneSwitcher.fileTab.entry3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.fileTab.help="Bu sekme, uzak veya yerel dosyaların içeriğine göre sahneleri otomatik olarak değiştirmenize olanak tanır.\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; Random Tab
|
||||
AdvSceneSwitcher.randomTab.title="Rastgele"
|
||||
AdvSceneSwitcher.randomTab.randomDisabledWarning="İşlevsellik devre dışı - Etkinleştirmek için Genel sekmesinde \"Herhangi bir geçiş koşulu karşılanmazsa Rastgele sekmesinde herhangi bir sahneye geç\"i seçin"
|
||||
AdvSceneSwitcher.randomTab.entry="Herhangi bir geçiş koşulu karşılanmıyorsa, geçiş yapın {{scenes}} kullan {{transitions}} için {{delay}}"
|
||||
AdvSceneSwitcher.randomTab.help="Sahne değiştirici, yapılandırılan süre için geçiş yapmak için bu sekmede rastgele bir giriş seçecektir.\nAynı girişin arka arkaya iki kez seçilmeyeceğini unutmayın.\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; Time Tab
|
||||
AdvSceneSwitcher.timeTab.title="Zaman"
|
||||
AdvSceneSwitcher.timeTab.anyDay="Herhangi bir günde"
|
||||
AdvSceneSwitcher.timeTab.mondays="Pazartesi günleri"
|
||||
AdvSceneSwitcher.timeTab.tuesdays="Salı günleri"
|
||||
AdvSceneSwitcher.timeTab.wednesdays="Çarşamba günleri"
|
||||
AdvSceneSwitcher.timeTab.thursdays="Perşembe günleri"
|
||||
AdvSceneSwitcher.timeTab.fridays="Cuma günleri"
|
||||
AdvSceneSwitcher.timeTab.saturdays="Cumartesi günleri"
|
||||
AdvSceneSwitcher.timeTab.sundays="Pazar günleri"
|
||||
AdvSceneSwitcher.timeTab.afterstart="Akış/kayıt başladıktan sonra"
|
||||
AdvSceneSwitcher.timeTab.afterstart.tip="Akışın / kaydın başlangıcına göre zaman kullanılacaktır"
|
||||
AdvSceneSwitcher.timeTab.entry="{{triggers}} da/de {{time}} Değiştir {{scenes}} kullan {{transitions}}"
|
||||
AdvSceneSwitcher.timeTab.help="Bu sekme, geçerli yerel saate göre otomatik olarak farklı bir sahneye geçmenizi sağlar.\n\nSahne değiştiricinin sahneleri yalnızca tam olarak belirttiğiniz saatte değiştireceğini unutmayın.\nGenel'de öncelik ayarlarını yapılandırdığınızdan emin olun. sekmesini beğeninize göre seçin, böylece daha yüksek önceliğe sahip diğer geçiş yöntemleri nedeniyle seçilen zaman noktası kaçırılmaz.\n\nDevam etmek için vurgulanan artı sembolüne tıklayın."
|
||||
|
||||
; Idle Tab
|
||||
AdvSceneSwitcher.idleTab.title="Boşta"
|
||||
AdvSceneSwitcher.idleTab.enable="Boşta Algılamayı Etkinleştir"
|
||||
AdvSceneSwitcher.idleTab.idleswitch="Sonra {{duration}} klavye veya fare girişi olmadan sahneye geçiş {{scenes}} kullan {{transitions}}"
|
||||
AdvSceneSwitcher.idleTab.dontSwitchIfFocus1="eğer geçiş yapmayın"
|
||||
AdvSceneSwitcher.idleTab.dontSwitchIfFocus2="odakta"
|
||||
|
||||
; Scene Sequence Tab
|
||||
AdvSceneSwitcher.sceneSequenceTab.title="Sıra"
|
||||
AdvSceneSwitcher.sceneSequenceTab.description="Otomatik sahne geçişleri dizisi, sahne değiştiriciyi duraklatarak/durdurarak veya manuel olarak farklı bir sahneye geçerek iptal edilebilir."
|
||||
AdvSceneSwitcher.sceneSequenceTab.save="Sahne dizilerini dosyaya kaydet"
|
||||
AdvSceneSwitcher.sceneSequenceTab.load="Dosyadan sahne dizilerini yükle"
|
||||
AdvSceneSwitcher.sceneSequenceTab.saveTitle="Sahne Sırasını dosyaya kaydet..."
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadTitle="Sahne Sırasını okumak için bir dosya seçin ..."
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadFail="Advanced Scene Switcher ayarları içe aktaramadı!"
|
||||
AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Gelişmiş Sahne Değiştirici ayarları başarıyla içe aktarıldı!"
|
||||
AdvSceneSwitcher.sceneSequenceTab.fileType="Text dosyaları (*.txt)"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptible="Kesilebilir"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Diğer anahtarlama yöntemlerinin bu sahne dizisini kesmesine izin verilir"
|
||||
AdvSceneSwitcher.sceneSequenceTab.entry=" {{startScenes}} aktif olduğunda değiştir {{scenes}} sonra {{delay}} kullan {{transitions}} {{interruptible}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Sırayı Uzat"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEntry="Sonra {{delay}} çevirmek {{scenes}} kullan {{transitions}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.help="Bu sekme, bir sahne yapılandırılmış bir süre boyunca etkinse otomatik olarak farklı bir sahneye geçmenizi sağlar.\nÖrneğin, iki sahne arasında otomatik olarak ileri ve geri geçiş yapabilirsiniz.\n\nVurgulanan artı sembolüne tıklayarak devam et."
|
||||
|
||||
; Audio Tab
|
||||
AdvSceneSwitcher.audioTab.title="Ses"
|
||||
AdvSceneSwitcher.audioTab.condition.above="üstünde"
|
||||
AdvSceneSwitcher.audioTab.condition.below="altında"
|
||||
AdvSceneSwitcher.audioTab.ignoreInactiveSource="kaynak etkin olmadığı sürece"
|
||||
AdvSceneSwitcher.audioTab.entry="Ses Hacmi{{audioSources}} durumu {{condition}} {{volumeWidget}} için {{duration}} saniye geçiş {{scenes}} kullanarak {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="Birden çok giriş eşleşirse..."
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... için {{duration}} saniye geçiş {{scenes}} kullan {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.help="Bu sekme, kaynakların ses düzeyine göre sahneler arasında geçiş yapmanızı sağlar.\nÖrneğin, mikrofonunuzun ses düzeyi belirli bir eşiğe ulaşırsa otomatik olarak farklı bir sahneye geçebilirsiniz.\n\nDevam etmek için vurgulanan artı simgesini tıklayın."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Seçilen giriş için ekran görüntüsü al"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Seçili olan girişin video kaynağının Ekran Görüntüsünü alın ve otomatik olarak hedef görüntü olarak ayarlayın"
|
||||
AdvSceneSwitcher.videoTab.condition.match="tam olarak eşleşir"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="Tam eşleşme, hedef ve kaynak görüntünün aynı çözünürlükte olmasını gerektirir.\nAyrıca her bir pikselin eşleşmesi gerekir, bu nedenle sıkıştırma kullanan görüntü biçimlerinin (ör. .JPG) kullanılması önerilmez!"
|
||||
AdvSceneSwitcher.videoTab.condition.differ="eşleşmiyor"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="değişmedi"
|
||||
AdvSceneSwitcher.videoTab.condition.hasChanged="değişti"
|
||||
AdvSceneSwitcher.videoTab.ignoreInactiveSource="Kaynak etkin olmadığı sürece"
|
||||
AdvSceneSwitcher.videoTab.entry="Zaman {{videoSources}} {{condition}} {{filePath}} {{browseButton}} için {{duration}} şuna dönüştür {{scenes}} kullan {{transitions}} {{ignoreInactiveSource}}"
|
||||
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Bu sekme, seçilen kaynakların mevcut video çıkışına göre sahneler arasında geçiş yapmanızı sağlar.<br/><a href=\"https:// adresini kontrol ettiğinizden emin olun. obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> bu işlevin daha da iyi bir şekilde uygulanması için.<br/><br/> Devam etmek için vurgulanan artı simgesini tıklayın.</p></body></html>"
|
||||
|
||||
; Network Tab
|
||||
AdvSceneSwitcher.networkTab.title="Ağ"
|
||||
AdvSceneSwitcher.networkTab.description="Bu sekme, başka bir OBS örneğinin etkin sahnesini uzaktan kontrol etmenizi sağlar.\nSahne adlarının tüm OBS örneklerinde tam olarak eşleşmesi gerektiğini lütfen unutmayın."
|
||||
AdvSceneSwitcher.networkTab.warning="Sunucuyu yerel bir ağın dışında çalıştırmak, üçüncü tarafların aktif sahneyi okumasına izin verecektir."
|
||||
AdvSceneSwitcher.networkTab.server="Sunucuyu başlat (Bağlı tüm istemcilere sahne değiştirme mesajları gönderir)"
|
||||
AdvSceneSwitcher.networkTab.server.port="Port"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="Sunucuyu yalnızca IPv4 kullanacak şekilde kilitleyin"
|
||||
AdvSceneSwitcher.networkTab.server.sendSceneChange="Sahne değişiklikleri için mesaj gönder"
|
||||
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Yalnızca otomatik sahne anahtarları için mesaj gönder"
|
||||
AdvSceneSwitcher.networkTab.server.sendPreview="Stüdyo modunda çalışırken önizleme sahnesi değişikliği için mesajlar gönderin"
|
||||
AdvSceneSwitcher.networkTab.startFailed.message="WebSockets sunucusu başlatılamadı, bunun nedeni şunlar olabilir:\n - %1 TCP bağlantı noktası şu anda bu sistemde başka bir yerde, muhtemelen başka bir uygulama tarafından kullanılıyor olabilir. WebSocket sunucu ayarlarında farklı bir TCP bağlantı noktası ayarlamayı deneyin veya bu bağlantı noktasını kullanabilecek herhangi bir uygulamayı durdurun.\n - Hata mesajı: %2"
|
||||
AdvSceneSwitcher.networkTab.server.status.currentStatus="Şu anki durum"
|
||||
AdvSceneSwitcher.networkTab.server.status.notRunning="Çalışmıyor"
|
||||
AdvSceneSwitcher.networkTab.server.status.starting="Başlangıç"
|
||||
AdvSceneSwitcher.networkTab.server.status.running="Çalışıyor"
|
||||
AdvSceneSwitcher.networkTab.server.restart="Sunucuyu yeniden başlat"
|
||||
AdvSceneSwitcher.networkTab.client="İstemciyi başlat (Sahne değiştirme mesajlarını alır)"
|
||||
AdvSceneSwitcher.networkTab.client.address="Ana makine adı veya IP adresi"
|
||||
AdvSceneSwitcher.networkTab.client.port="Port"
|
||||
AdvSceneSwitcher.networkTab.client.status.currentStatus="Şu anki durum"
|
||||
AdvSceneSwitcher.networkTab.client.status.disconnected="Bağlantı kesildi"
|
||||
AdvSceneSwitcher.networkTab.client.status.connecting="Bağlanıyor"
|
||||
AdvSceneSwitcher.networkTab.client.status.connected="Bağlandı"
|
||||
AdvSceneSwitcher.networkTab.client.reconnect="Yeniden bağlanmaya zorla"
|
||||
|
||||
; Scene Group Tab
|
||||
AdvSceneSwitcher.sceneGroupTab.title="Sahne Grubu"
|
||||
AdvSceneSwitcher.sceneGroupTab.list="Sahne Grupları"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit="Sahne Gruplarını Düzenle"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.name="İsim:"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.type="Tip: {{type}}"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.count="Saymak"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.time="Zaman"
|
||||
AdvSceneSwitcher.sceneGroupTab.type.random="Rastgele"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.count="Sonra listedeki bir sonraki sahneye ilerleyin {{count}} karşılama"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.time="Sonra listedeki bir sonraki sahneye ilerleyin {{time}} geçtiyse"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.random="Listeden rastgele bir sonraki sahneyi seçin"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.repeat="Sahne listesinin sonuna ulaşılırsa baştan başlayın"
|
||||
AdvSceneSwitcher.sceneGroupTab.edit.addScene="Sahne ekle"
|
||||
AdvSceneSwitcher.sceneGroupTab.add="Sahne Grubu Ekle"
|
||||
AdvSceneSwitcher.sceneGroupTab.defaultname="Sahne Grubu %1"
|
||||
AdvSceneSwitcher.sceneGroupTab.exists="Sahne Grubu veya Sahne adı zaten var"
|
||||
AdvSceneSwitcher.sceneGroupTab.help="Sahne Grupları, normal bir sahne gibi bir hedef olarak seçilebilir.\n\nAdından da anlaşılacağı gibi, bir sahne grubu birden fazla sahneden oluşan bir koleksiyondur.\nSahne grubu, yapılandırılmış ayarlara bağlı olarak kendisine atanan sahneler listesinde ilerleyecektir. sağ tarafta bulunabilir.\n\nSahne grubunu listedeki bir sonraki sahneye geçecek şekilde yapılandırabilirsiniz:\nSahne grubu birkaç kez hedef olarak seçildikten sonra.\nBelirli bir süre sonra geçti.\nVeya rastgele.\n\nÖrneğin, sahneleri içeren bir sahne grubu ...\nSahne 1\nSahne 2\nSahne 3 \n... ilk seçildiğinde \"Sahne 1\"i etkinleştirecek bir hedef olarak.\nİkinci kez \"Sahne 2\"yi etkinleştirecek.\nKalan zamanlar \"Scene 3\" etkinleştirilecek.\n\nYeni bir sahne grubu eklemek için aşağıdaki vurgulanan artı sembolüne tıklayın."
|
||||
AdvSceneSwitcher.sceneGroupTab.scenes.help="Soldan değiştirmek istediğiniz sahne grubunu seçin.\n\nYukarıdaki sahneyi seçip aşağıdaki artı simgesini tıklayarak bu sahne grubuna eklemek için bir sahne seçin.\n\nAynı sahneye birden fazla sahne eklenebilir grup."
|
||||
|
||||
; Scene Trigger Tab
|
||||
AdvSceneSwitcher.sceneTriggerTab.title="Sahne Tetikleyicileri"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.none="--tetikleyiciyi seçin--"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneActive="aktif"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneInactive="aktif değil"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerType.sceneLeave="uzaklaştı"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.none="--eylemi seç--"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startRecording="Kayda başla"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.pauseRecording="Kaydı duraklat"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unpauseRecording="kaydı duraklatma"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopRecording="Kaydetmeyi bırak"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopStreaming="Akışı durdur"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startStreaming="Akışı başlat"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startReplayBuffer="Yeniden oynatma arabelleğini başlat"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopReplayBuffer="Tekrar arabelleğini durdur"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.muteSource="Kaynağı sessize al"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.unmuteSource="Kaynağı sesi aç"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startSwitcher="Sahne değiştiriciyi başlat"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopSwitcher="Sahne değiştiriciyi durdur"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.startVirtualCamera="Sanal kamerayı başlat"
|
||||
AdvSceneSwitcher.sceneTriggerTab.sceneTriggerAction.stopVirtualCamera="Sanal kamerayı durdur"
|
||||
AdvSceneSwitcher.sceneTriggerTab.entry="Şundan {{scenes}} {{triggers}} {{actions}} {{audioSources}} sonra {{duration}}"
|
||||
AdvSceneSwitcher.sceneTriggerTab.help="Bu sekme, kaydı veya akışı durdurma gibi sahne değişikliklerinde eylemleri tetiklemenize olanak tanır."
|
||||
|
||||
; Hotkey
|
||||
AdvSceneSwitcher.hotkey.startSwitcherHotkey="Advanced Scene Switcher'ı başlatın"
|
||||
AdvSceneSwitcher.hotkey.stopSwitcherHotkey="Gelişmiş Sahne Değiştiriciyi Durdurun"
|
||||
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="Gelişmiş Sahne Değiştirici için Başlat/Durdur arasında geçiş yapın"
|
||||
AdvSceneSwitcher.hotkey.macro.pause="%1 makrosunu duraklat"
|
||||
AdvSceneSwitcher.hotkey.macro.unpause="%1 makrosunu duraklatma"
|
||||
AdvSceneSwitcher.hotkey.macro.togglePause="%1 makrosunun duraklatmasını aç/kapat"
|
||||
|
||||
AdvSceneSwitcher.askBackup="Advanced Scene Switcher'ın yeni bir sürümü algılandı.\nEski ayarların bir yedeği oluşturulsun mu?"
|
||||
AdvSceneSwitcher.askForMacro="Makro seç {{macroSelection}}"
|
||||
|
||||
AdvSceneSwitcher.close="Kapat"
|
||||
AdvSceneSwitcher.browse="Araştır"
|
||||
|
||||
AdvSceneSwitcher.selectScene="--sahne seç--"
|
||||
AdvSceneSwitcher.selectPreviousScene="Önceki Sahne"
|
||||
AdvSceneSwitcher.selectCurrentScene="Mevcut Sahne"
|
||||
AdvSceneSwitcher.selectAnyScene="Herhangi bir Sahne"
|
||||
AdvSceneSwitcher.currentTransition="Mevcut Geçiş"
|
||||
AdvSceneSwitcher.anyTransition="Herhangi Bir Geçiş"
|
||||
AdvSceneSwitcher.selectTransition="--geçişi seç--"
|
||||
AdvSceneSwitcher.selectWindow="--pencereyi seç--"
|
||||
AdvSceneSwitcher.selectSource="--kaynak Seç--"
|
||||
AdvSceneSwitcher.selectAudioSource="--ses kaynağını seçin--"
|
||||
AdvSceneSwitcher.selectVideoSource="--video kaynağını seçin--"
|
||||
AdvSceneSwitcher.selectMediaSource="--medya kaynağını seçin--"
|
||||
AdvSceneSwitcher.selectProcess="--işlemi seç--"
|
||||
AdvSceneSwitcher.selectFilter="--filtre seç--"
|
||||
AdvSceneSwitcher.selectMacro="--makroyu seçin--"
|
||||
AdvSceneSwitcher.selectItem="--Öğeyi seçin--"
|
||||
AdvSceneSwitcher.selectProfile="--profil seç--"
|
||||
AdvSceneSwitcher.selectSceneCollection="--sahne koleksiyonunu seç--"
|
||||
AdvSceneSwitcher.enterPath="--yolu girin--"
|
||||
AdvSceneSwitcher.enterText="--metni girin--"
|
||||
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="geçersiz girişler kaydedilmeyecek"
|
||||
AdvSceneSwitcher.selectWindowTip="\"OBS\" OBS penceresini belirlemek için \nKullan \"Görev değiştirme\"için ALT + TAB"
|
||||
AdvSceneSwitcher.sceneItemSelection.all="Hepsi"
|
||||
AdvSceneSwitcher.sceneItemSelection.any="Herhangi"
|
||||
|
||||
AdvSceneSwitcher.status.active="Aktif"
|
||||
AdvSceneSwitcher.status.inactive="İnaktif"
|
||||
AdvSceneSwitcher.running="Eklenti çalışıyor"
|
||||
AdvSceneSwitcher.stopped="Eklenti durdu"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>Gelişmiş Sahne Değiştirici ilk kez başlatılıyor gibi görünüyor.<br>Lütfen <a href=\"https://github.com/ adresine bir göz atın. WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> için kılavuzlar ve örnekler listesi.<br>Yapmayın. eklentinin <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:# sayfasında soru sormaktan çekinmeyin OBS forumlarında 268bd2;\">konu</span></a>!</p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="millisaniye"
|
||||
AdvSceneSwitcher.unit.secends="saniye"
|
||||
AdvSceneSwitcher.unit.minutes="dakika"
|
||||
AdvSceneSwitcher.unit.hours="saat"
|
||||
AdvSceneSwitcher.duration.condition.none="Zaman kısıtlaması yok"
|
||||
AdvSceneSwitcher.duration.condition.more="En azından"
|
||||
AdvSceneSwitcher.duration.condition.equal="Tam olarak"
|
||||
AdvSceneSwitcher.duration.condition.less="En çoğu"
|
||||
@@ -30,6 +30,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="详细日志输出"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="保存窗口位置和大小"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="显示系统托盘通知"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="禁用UI提示"
|
||||
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="隐藏可通过宏操作的分组栏"
|
||||
AdvSceneSwitcher.generalTab.priority="优先级"
|
||||
AdvSceneSwitcher.generalTab.priority.description="切换场景优先级 (最上方的项优先级最高)"
|
||||
AdvSceneSwitcher.generalTab.priority.threadPriority="使用线程优先级"
|
||||
@@ -57,6 +58,7 @@ AdvSceneSwitcher.generalTab.priority.macro="宏"
|
||||
; Macro Tab
|
||||
AdvSceneSwitcher.macroTab.title="宏"
|
||||
AdvSceneSwitcher.macroTab.macros="宏文件"
|
||||
AdvSceneSwitcher.macroTab.priorityWarning="注意:建议将宏配置为最高优先级的功能.\n可以在“常规”选项卡上更改此设置."
|
||||
AdvSceneSwitcher.macroTab.help="宏允许您根据多种条件执行一系列操作。\n\n单击突出显示的加号可添加新宏."
|
||||
AdvSceneSwitcher.macroTab.editConditionHelp="此部分允许您定义宏条件。\n\n在左侧选择现有宏或添加新宏。\n然后单击下面的加号按钮添加新条件。"
|
||||
AdvSceneSwitcher.macroTab.editActionHelp="此部分允许您定义宏操作。\n\n在左侧选择现有宏或添加新宏。\n然后单击下面的加号按钮添加新操作."
|
||||
@@ -66,11 +68,17 @@ AdvSceneSwitcher.macroTab.edit.condition="条件类型:"
|
||||
AdvSceneSwitcher.macroTab.edit.action="动作类型:"
|
||||
AdvSceneSwitcher.macroTab.add="添加新宏"
|
||||
AdvSceneSwitcher.macroTab.name="名称:"
|
||||
AdvSceneSwitcher.macroTab.run="运行宏"
|
||||
AdvSceneSwitcher.macroTab.runFail="运行 \"%1\" 失败!\n其中一个操作失败,或者宏已在运行."
|
||||
AdvSceneSwitcher.macroTab.runInParallel="与其他宏并行运行宏"
|
||||
AdvSceneSwitcher.macroTab.onChange="仅在条件更改时执行操作"
|
||||
AdvSceneSwitcher.macroTab.defaultname="宏 %1"
|
||||
AdvSceneSwitcher.macroTab.exists="宏名称已存在"
|
||||
AdvSceneSwitcher.macroTab.copy="创建副本"
|
||||
AdvSceneSwitcher.macroTab.expandAll="全部展开"
|
||||
AdvSceneSwitcher.macroTab.collapseAll="全部收回"
|
||||
AdvSceneSwitcher.macroTab.maximize="最大化"
|
||||
AdvSceneSwitcher.macroTab.minimize="最小化"
|
||||
|
||||
; Macro Logic
|
||||
AdvSceneSwitcher.logic.none="忽略条目"
|
||||
@@ -83,23 +91,27 @@ AdvSceneSwitcher.logic.not="如果 不"
|
||||
|
||||
; Macro Conditions
|
||||
AdvSceneSwitcher.condition.audio="音频"
|
||||
AdvSceneSwitcher.condition.audio.state.below="小于"
|
||||
AdvSceneSwitcher.condition.audio.state.below="少于"
|
||||
AdvSceneSwitcher.condition.audio.state.exact="相等"
|
||||
AdvSceneSwitcher.condition.audio.state.above="大于"
|
||||
AdvSceneSwitcher.condition.audio.entry="音量 来源 {{audioSources}} 是 {{condition}} {{volume}}"
|
||||
AdvSceneSwitcher.condition.cursor="光标"
|
||||
AdvSceneSwitcher.condition.audio.state.mute="静音"
|
||||
AdvSceneSwitcher.condition.audio.state.unmute="取消静音"
|
||||
AdvSceneSwitcher.condition.audio.type.output="输出音频"
|
||||
AdvSceneSwitcher.condition.audio.type.volume="配置音量级别"
|
||||
AdvSceneSwitcher.condition.cursor="屏幕区域"
|
||||
AdvSceneSwitcher.condition.cursor.type.region="当前位置"
|
||||
AdvSceneSwitcher.condition.cursor.type.moving="正在移动"
|
||||
AdvSceneSwitcher.condition.cursor.showFrame="显示帧率"
|
||||
AdvSceneSwitcher.condition.cursor.hideFrame="隐藏帧率"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line1="光标 处于{{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line2="光标当前位于 {{xPos}} x {{yPos}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line1="鼠标 处于{{conditions}} {{minX}} {{minY}} {{maxX}} {{maxY}} - {{toggleFrameButton}}"
|
||||
AdvSceneSwitcher.condition.cursor.entry.line2="鼠标当前位于 {{xPos}} x {{yPos}}"
|
||||
AdvSceneSwitcher.condition.scene="场景"
|
||||
AdvSceneSwitcher.condition.scene.type.current="当前场景是"
|
||||
AdvSceneSwitcher.condition.scene.type.previous="上一个场景是"
|
||||
AdvSceneSwitcher.condition.scene.type.changed="场景改变了"
|
||||
AdvSceneSwitcher.condition.scene.type.notChanged="场景没有改变"
|
||||
AdvSceneSwitcher.condition.scene.waitForTransition="等待过渡完成"
|
||||
AdvSceneSwitcher.condition.scene.entry="{{sceneType}} {{scenes}} {{waitForTransition}}"
|
||||
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}} {{scenes}}"
|
||||
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
|
||||
AdvSceneSwitcher.condition.window="窗口"
|
||||
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..."
|
||||
AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改"
|
||||
@@ -108,7 +120,9 @@ AdvSceneSwitcher.condition.file.entry.line1="内容 {{fileType}} {{filePath}} (
|
||||
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
|
||||
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
|
||||
AdvSceneSwitcher.condition.media="媒体"
|
||||
AdvSceneSwitcher.condition.media.entry="{{mediaSources}} 状态是 {{states}} 和 {{timeRestrictions}} {{time}}"
|
||||
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体来源"
|
||||
AdvSceneSwitcher.condition.media.allOnScene="所有媒体来源"
|
||||
AdvSceneSwitcher.condition.media.matchOnChange="仅在更改时匹配(注意:此选项将在未来版本中删除-请改用时间限制)"
|
||||
AdvSceneSwitcher.condition.video="视频"
|
||||
AdvSceneSwitcher.condition.video.condition.match="完全匹配"
|
||||
AdvSceneSwitcher.condition.video.condition.differ="不匹配"
|
||||
@@ -129,15 +143,17 @@ AdvSceneSwitcher.condition.video.objectScaleThreshold="模型比例: "
|
||||
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="较低的模比例将导致更多的匹配,但会导致更高的CPU负载."
|
||||
AdvSceneSwitcher.condition.video.minNeighborDescription="较高的“最小区域”值将导致较少但质量较高的匹配."
|
||||
AdvSceneSwitcher.condition.video.showMatch="显示模式"
|
||||
AdvSceneSwitcher.condition.video.showMatch.loading="Checking for match"
|
||||
AdvSceneSwitcher.condition.video.screenshotFail="无法获取源的屏幕截图!"
|
||||
AdvSceneSwitcher.condition.video.screenshotEmpty="Screenshot is empty - Is the source visible?"
|
||||
AdvSceneSwitcher.condition.video.patternMatchFail="没有找到模式!"
|
||||
AdvSceneSwitcher.condition.video.patternMatchSuccess="Pattern is highlighted in red"
|
||||
AdvSceneSwitcher.condition.video.objectMatchFail="找不到对象!"
|
||||
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
|
||||
AdvSceneSwitcher.condition.video.modelLoadFail="无法加载模型数据!"
|
||||
AdvSceneSwitcher.condition.video.entry="{{videoSources}} {{condition}} {{imagePath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minNeighbor="最小区域: {{minNeighbors}}"
|
||||
AdvSceneSwitcher.condition.video.entry.minSize="最小尺寸: {{minSizeX}} x {{minSizeY}}"
|
||||
AdvSceneSwitcher.condition.video.entry.maxSize="最大尺寸: {{maxSizeX}} x {{maxSizeY}}"
|
||||
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}通过只执行每一次检查来减少CPU负载 {{throttleCount}} 毫秒"
|
||||
AdvSceneSwitcher.condition.stream="推流"
|
||||
AdvSceneSwitcher.condition.stream.state.start="推流启动"
|
||||
@@ -156,11 +172,14 @@ AdvSceneSwitcher.condition.idle="闲置检测"
|
||||
AdvSceneSwitcher.condition.idle.entry="没有键盘或鼠标输入{{duration}}"
|
||||
AdvSceneSwitcher.condition.pluginState="插件状态"
|
||||
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="在此时间间隔内触发了自动场景更改"
|
||||
AdvSceneSwitcher.condition.pluginState.state.running="高级场景切换程序正在运行"
|
||||
AdvSceneSwitcher.condition.pluginState.state.shutdown="OBS正在关闭"
|
||||
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
|
||||
AdvSceneSwitcher.condition.timer="计时器"
|
||||
AdvSceneSwitcher.condition.timer.type.fixed="固定"
|
||||
AdvSceneSwitcher.condition.timer.type.random="随机"
|
||||
AdvSceneSwitcher.condition.timer.pause="暂停"
|
||||
AdvSceneSwitcher.condition.timer.continue="继续"
|
||||
AdvSceneSwitcher.condition.timer.entry.line1="{{duration}} 时间倒计时"
|
||||
AdvSceneSwitcher.condition.timer.entry.line2="剩余时间: {{remaining}} 秒"
|
||||
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} 保存剩余时间 {{autoReset}} 达到时间后自动重置计时器"
|
||||
AdvSceneSwitcher.condition.timer.reset="重置"
|
||||
@@ -177,8 +196,8 @@ AdvSceneSwitcher.condition.macro.state.entry="条件 {{macros}} 是真的"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} 运行 {{conditions}} {{count}} 次(相对于下面计数匹配)"
|
||||
AdvSceneSwitcher.condition.macro.count.entry.line2="当前计数: {{currentCount}} {{resetCount}}"
|
||||
AdvSceneSwitcher.condition.source="源"
|
||||
AdvSceneSwitcher.condition.source.type.active="激活状态"
|
||||
AdvSceneSwitcher.condition.source.type.showing="显示状态"
|
||||
AdvSceneSwitcher.condition.source.type.active="是激活状态"
|
||||
AdvSceneSwitcher.condition.source.type.showing="是显示状态"
|
||||
AdvSceneSwitcher.condition.source.type.settings="设置完全匹配"
|
||||
AdvSceneSwitcher.condition.source.regex="使用正则表达式"
|
||||
AdvSceneSwitcher.condition.source.getSettings="获取当前设置"
|
||||
@@ -207,19 +226,34 @@ AdvSceneSwitcher.condition.sceneOrder.entry="在 {{scenes}} {{sources}} {{condit
|
||||
AdvSceneSwitcher.condition.hotkey="热键"
|
||||
AdvSceneSwitcher.condition.hotkey.name="宏触发热键"
|
||||
AdvSceneSwitcher.condition.hotkey.tip="注意:您可以在OBS设置窗口中为此热键配置按键绑定"
|
||||
AdvSceneSwitcher.condition.hotkey.entry="名称: {{name}}"
|
||||
AdvSceneSwitcher.condition.hotkey.entry.line1="热键被按下"
|
||||
AdvSceneSwitcher.condition.hotkey.entry.line2="名称: {{name}}"
|
||||
AdvSceneSwitcher.condition.replay="回放缓存"
|
||||
AdvSceneSwitcher.condition.replay.state.stopped="回放缓存已停止"
|
||||
AdvSceneSwitcher.condition.replay.state.started="回放缓存已启动"
|
||||
AdvSceneSwitcher.condition.replay.state.saved="已保存回放缓存"
|
||||
AdvSceneSwitcher.condition.replay.entry="{{state}}"
|
||||
AdvSceneSwitcher.condition.date="日期"
|
||||
AdvSceneSwitcher.condition.date.anyDay="每一天"
|
||||
AdvSceneSwitcher.condition.date.monday="周一"
|
||||
AdvSceneSwitcher.condition.date.tuesday="周二"
|
||||
AdvSceneSwitcher.condition.date.wednesday="周三"
|
||||
AdvSceneSwitcher.condition.date.thursday="周四"
|
||||
AdvSceneSwitcher.condition.date.friday="周五"
|
||||
AdvSceneSwitcher.condition.date.saturday="周六"
|
||||
AdvSceneSwitcher.condition.date.sunday="周日"
|
||||
AdvSceneSwitcher.condition.date.state.at="现在"
|
||||
AdvSceneSwitcher.condition.date.state.after="之后"
|
||||
AdvSceneSwitcher.condition.date.state.before="之前"
|
||||
AdvSceneSwitcher.condition.date.state.between="之间"
|
||||
AdvSceneSwitcher.condition.date.entry.line1="{{condition}} {{dateTime}} {{dateTime2}} {{ignoreDate}} 忽略日期 {{ignoreTime}} 忽略时间"
|
||||
AdvSceneSwitcher.condition.date.entry.line2="{{repeat}} 重复的每一次 {{duration}} 匹配的时间"
|
||||
AdvSceneSwitcher.condition.date.separator="和"
|
||||
AdvSceneSwitcher.condition.date.ignoreDate="如果未选中,日期组件将被忽略"
|
||||
AdvSceneSwitcher.condition.date.ignoreTime="如果未选中,时间组件将被忽略"
|
||||
AdvSceneSwitcher.condition.date.showAdvancedSettings="显示高级设置"
|
||||
AdvSceneSwitcher.condition.date.showSimpleSettings="显示简单设置"
|
||||
AdvSceneSwitcher.condition.date.entry.simple="在 {{dayOfWeek}} 的 {{weekTime}}"
|
||||
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
|
||||
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} 在匹配到日期时间后,每隔 {{duration}} 重复一次"
|
||||
AdvSceneSwitcher.condition.sceneTransform="场景项目被改变"
|
||||
AdvSceneSwitcher.condition.sceneTransform.getTransform="内容或者设置被改变了"
|
||||
AdvSceneSwitcher.condition.sceneTransform.regex="使用正则表达式"
|
||||
@@ -231,16 +265,48 @@ AdvSceneSwitcher.condition.transition.type.current="当前转场特效类型为"
|
||||
AdvSceneSwitcher.condition.transition.type.duration="当前转场特效持续时间为"
|
||||
AdvSceneSwitcher.condition.transition.type.started="转场特效开始"
|
||||
AdvSceneSwitcher.condition.transition.type.ended="转场特效结束"
|
||||
AdvSceneSwitcher.condition.transition.type.transitionSource="转场特效来自"
|
||||
AdvSceneSwitcher.condition.transition.type.transitionTarget="转场特效到"
|
||||
AdvSceneSwitcher.condition.transition.durationSuffix="秒"
|
||||
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{duration}}{{durationSuffix}}"
|
||||
AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{scenes}}{{duration}}{{durationSuffix}}"
|
||||
AdvSceneSwitcher.condition.sceneVisibility="场景项目可见"
|
||||
AdvSceneSwitcher.condition.sceneVisibility.type.shown="显示的"
|
||||
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="隐藏的"
|
||||
AdvSceneSwitcher.condition.sceneVisibility.entry="在 {{scenes}} {{sources}} 是 {{conditions}} "
|
||||
AdvSceneSwitcher.condition.studioMode="工作室模式"
|
||||
AdvSceneSwitcher.condition.studioMode.state.active="工作室模式处于活动状态"
|
||||
AdvSceneSwitcher.condition.studioMode.state.notActive="工作室模式未处于活动状态"
|
||||
AdvSceneSwitcher.condition.studioMode.state.previewScene="预览场景是"
|
||||
AdvSceneSwitcher.condition.studioMode.entry="{{conditions}}{{scenes}}"
|
||||
AdvSceneSwitcher.condition.openvr="OpenVR"
|
||||
AdvSceneSwitcher.condition.errorStatus="OpenVR 错误: "
|
||||
AdvSceneSwitcher.condition.openvr.entry.line1="HMD在 ..."
|
||||
AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}"
|
||||
AdvSceneSwitcher.condition.openvr.entry.line3="HMD目前处于 {{xPos}} x {{yPos}} x {{zPos}}"
|
||||
AdvSceneSwitcher.condition.stats="OBS统计数据"
|
||||
AdvSceneSwitcher.condition.stats.type.fps="FPS"
|
||||
AdvSceneSwitcher.condition.stats.type.CPUUsage="CPU使用率"
|
||||
AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable="可用磁盘空间"
|
||||
AdvSceneSwitcher.condition.stats.type.memoryUsage="内存使用"
|
||||
AdvSceneSwitcher.condition.stats.type.averageTimeToRender="渲染帧的平均时间"
|
||||
AdvSceneSwitcher.condition.stats.type.skippedFrames="由于编码延迟跳过的帧"
|
||||
AdvSceneSwitcher.condition.stats.type.missedFrames="由于渲染延迟错过的帧"
|
||||
AdvSceneSwitcher.condition.stats.type.droppedFrames.stream="串流丢弃的帧"
|
||||
AdvSceneSwitcher.condition.stats.type.megabytesSent.stream="串流总数据输出"
|
||||
AdvSceneSwitcher.condition.stats.type.bitrate.stream="串流比特率"
|
||||
AdvSceneSwitcher.condition.stats.type.droppedFrames.recording="录制丢弃的帧"
|
||||
AdvSceneSwitcher.condition.stats.type.megabytesSent.recording="录制总数据输出"
|
||||
AdvSceneSwitcher.condition.stats.type.bitrate.recording="录制比特率"
|
||||
AdvSceneSwitcher.condition.stats.condition.above="大于"
|
||||
AdvSceneSwitcher.condition.stats.condition.equals="等于"
|
||||
AdvSceneSwitcher.condition.stats.condition.below="少于"
|
||||
AdvSceneSwitcher.condition.stats.dockHint="你可以打开“统计”窗口查看当前状态"
|
||||
AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
|
||||
|
||||
; Macro Actions
|
||||
AdvSceneSwitcher.action.switchScene="切换场景"
|
||||
AdvSceneSwitcher.action.scene.entry="切换场景 {{scenes}} 使用 {{transitions}} 时长 {{duration}} 秒"
|
||||
AdvSceneSwitcher.action.scene.blockUntilTransitionDone="等待目标场景的过渡完成"
|
||||
AdvSceneSwitcher.action.wait="等待"
|
||||
AdvSceneSwitcher.action.wait.type.fixed="固定数值"
|
||||
AdvSceneSwitcher.action.wait.type.random="随机数值"
|
||||
@@ -251,7 +317,6 @@ AdvSceneSwitcher.action.audio.type.mute="静音"
|
||||
AdvSceneSwitcher.action.audio.type.unmute="取消静音"
|
||||
AdvSceneSwitcher.action.audio.type.sourceVolume="来源音量"
|
||||
AdvSceneSwitcher.action.audio.type.masterVolume="主音量"
|
||||
AdvSceneSwitcher.action.audio.fade="{{fade}}淡出 {{duration}} 秒"
|
||||
AdvSceneSwitcher.action.audio.entry="{{actions}} {{audioSources}} {{volume}}"
|
||||
AdvSceneSwitcher.action.recording="录制"
|
||||
AdvSceneSwitcher.action.recording.type.stop="停止录制"
|
||||
@@ -279,7 +344,6 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="显示"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.source="来源"
|
||||
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="全部"
|
||||
AdvSceneSwitcher.action.sceneVisibility.entry="在 {{scenes}} {{actions}} {{sourceTypes}} {{sources}}"
|
||||
AdvSceneSwitcher.action.filter="滤镜"
|
||||
AdvSceneSwitcher.action.filter.type.enable="开启"
|
||||
AdvSceneSwitcher.action.filter.type.disable="关闭"
|
||||
@@ -300,16 +364,19 @@ AdvSceneSwitcher.action.media.type.stop="停止"
|
||||
AdvSceneSwitcher.action.media.type.restart="重新启动"
|
||||
AdvSceneSwitcher.action.media.type.next="下一个"
|
||||
AdvSceneSwitcher.action.media.type.previous="上一个"
|
||||
AdvSceneSwitcher.action.media.entry="{{actions}} {{mediaSources}}"
|
||||
AdvSceneSwitcher.action.media.entry="{{actions}}{{duration}}{{mediaSources}}"
|
||||
AdvSceneSwitcher.action.macro="宏"
|
||||
AdvSceneSwitcher.action.macro.type.pause="暂停"
|
||||
AdvSceneSwitcher.action.macro.type.unpause="取消暂停"
|
||||
AdvSceneSwitcher.action.macro.type.resetCounter="复位计数器"
|
||||
AdvSceneSwitcher.action.macro.type.run="运行"
|
||||
AdvSceneSwitcher.action.macro.type.stop="停止"
|
||||
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
|
||||
AdvSceneSwitcher.action.pluginState="插件状态"
|
||||
AdvSceneSwitcher.action.pluginState.type.stop="停止高级场景切换插件"
|
||||
AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项:"
|
||||
AdvSceneSwitcher.action.pluginState.entry="{{actions}} {{values}} {{scenes}}"
|
||||
AdvSceneSwitcher.action.pluginState.type.import="从设置导入"
|
||||
AdvSceneSwitcher.action.pluginState.importWarning="注意:当“设置”窗口打开时,操作将被忽略。"
|
||||
AdvSceneSwitcher.action.virtualCamera="虚拟摄像机"
|
||||
AdvSceneSwitcher.action.virtualCamera.type.stop="停止虚拟摄像机"
|
||||
AdvSceneSwitcher.action.virtualCamera.type.start="启动虚拟摄像机"
|
||||
@@ -333,13 +400,13 @@ AdvSceneSwitcher.action.sceneOrder.type.moveTop="顶部"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.moveBottom="底部"
|
||||
AdvSceneSwitcher.action.sceneOrder.type.movePosition="移动到数值位置"
|
||||
AdvSceneSwitcher.action.sceneOrder.entry="在 {{scenes}} {{actions}} {{sources}} {{position}}"
|
||||
AdvSceneSwitcher.action.sceneTransform="场景项变换"
|
||||
AdvSceneSwitcher.action.sceneTransform="场景项目变换"
|
||||
AdvSceneSwitcher.action.sceneTransform.getTransform="得到变换"
|
||||
AdvSceneSwitcher.action.sceneTransform.entry="On {{scenes}} transform {{sources}}"
|
||||
AdvSceneSwitcher.action.sceneTransform.entry="在 {{scenes}} 变换 {{sources}}"
|
||||
AdvSceneSwitcher.action.file="文件"
|
||||
AdvSceneSwitcher.action.file.type.write="写入"
|
||||
AdvSceneSwitcher.action.file.type.append="追加写入"
|
||||
AdvSceneSwitcher.action.file.entry="{{actions}} to {{filePath}}:"
|
||||
AdvSceneSwitcher.action.file.entry="{{actions}} 到 {{filePath}}:"
|
||||
AdvSceneSwitcher.action.previewScene="切换预览场景"
|
||||
AdvSceneSwitcher.action.previewScene.entry="将预览场景切换到 {{scenes}}"
|
||||
AdvSceneSwitcher.action.SceneSwap="交换场景 (Studio mode)"
|
||||
@@ -354,9 +421,22 @@ AdvSceneSwitcher.action.timer.type.reset="重置"
|
||||
AdvSceneSwitcher.action.timer.type.setTimeRemaining="设置剩余时间"
|
||||
AdvSceneSwitcher.action.timer.entry="{{timerAction}} 定时在 {{macros}} {{duration}}"
|
||||
AdvSceneSwitcher.action.random="随机"
|
||||
AdvSceneSwitcher.action.random.arguments="宏:"
|
||||
AdvSceneSwitcher.action.random.addArgument="增加 宏"
|
||||
AdvSceneSwitcher.action.random.entry="运行 {{macroSelection}}"
|
||||
AdvSceneSwitcher.action.systray="系统托盘通知"
|
||||
AdvSceneSwitcher.action.systray.entry="显示通知: {{message}}"
|
||||
AdvSceneSwitcher.action.screenshot="截图"
|
||||
AdvSceneSwitcher.action.screenshot.mainOutput="OBS 预览画面"
|
||||
AdvSceneSwitcher.action.screenshot.entry="截图 {{sources}}"
|
||||
AdvSceneSwitcher.action.profile="配置文件"
|
||||
AdvSceneSwitcher.action.profile.entry="将活动配置文件切换到 {{profiles}}"
|
||||
AdvSceneSwitcher.action.sceneCollection="场景集合"
|
||||
AdvSceneSwitcher.action.sceneCollection.entry="将活动场景集合切换到 {{sceneCollections}}"
|
||||
AdvSceneSwitcher.action.sceneCollection.warning="注意:在此之后的任何操作都不会执行,因为更改场景集合也会重新加载场景切换器设置。\n打开“设置”窗口时,场景采集操作将被忽略."
|
||||
AdvSceneSwitcher.action.sequence="场景序列"
|
||||
AdvSceneSwitcher.action.sequence.entry="每次执行此操作时,运行列表中的下一个宏(暂停的宏将被忽略)"
|
||||
AdvSceneSwitcher.action.sequence.status="上次执行的宏:%1 - 要执行的下一个宏: %2"
|
||||
AdvSceneSwitcher.action.sequence.status.none="无"
|
||||
AdvSceneSwitcher.action.sequence.restart="到达列表末尾后,从开头重新启动"
|
||||
AdvSceneSwitcher.action.sequence.continueFrom="继续所选项目"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="转场特效"
|
||||
@@ -366,8 +446,6 @@ AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="更改激活转场特
|
||||
AdvSceneSwitcher.transitionTab.transitionForAToB="当自动从场景A切换到场景B时使用的转场特效"
|
||||
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>这里的设定<span style=\"font-style:bold;\">只影响由场景切换器引起的转场有效</span>,不影响你手动的引起的转场。<br/>在这里设定的转场特效优先级高于场景切换器其他地方设置的<br/><br/>单击加号添加项目.</p></body></html>"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition="当切换到这个场景时修改默认转场特效"
|
||||
AdvSceneSwitcher.transitionTab.switchDefaultLabel="活跃时切换默认转场特效为"
|
||||
AdvSceneSwitcher.transitionTab.entry="从场景 {{scenes}} 切换到场景 {{scenes2}} 时使用转场特效 {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="当场景 {{scenes}} 被激活时更改默认转场特效为 {{transitions}}"
|
||||
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="单击加号添加项目."
|
||||
AdvSceneSwitcher.transitionTab.defaultTransition.delay="场景更改 {{defTransitionDelay}} 后切换转换特效."
|
||||
@@ -423,7 +501,6 @@ AdvSceneSwitcher.mediaTab.states.Paused="暂停"
|
||||
AdvSceneSwitcher.mediaTab.states.stopped="停止"
|
||||
AdvSceneSwitcher.mediaTab.states.ended="结束"
|
||||
AdvSceneSwitcher.mediaTab.states.error="错误"
|
||||
AdvSceneSwitcher.mediaTab.states.playedToEnd="播放结束"
|
||||
AdvSceneSwitcher.mediaTab.states.any="任意"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.none="无"
|
||||
AdvSceneSwitcher.mediaTab.timeRestriction.shorter="播放时间小于"
|
||||
@@ -475,7 +552,7 @@ AdvSceneSwitcher.timeTab.fridays="每周五"
|
||||
AdvSceneSwitcher.timeTab.saturdays="每周六"
|
||||
AdvSceneSwitcher.timeTab.sundays="每周日"
|
||||
AdvSceneSwitcher.timeTab.afterstart="推流或录制开始后"
|
||||
AdvSceneSwitcher.timeTab.afterstart.tip="相对于直播或录制开始之后的时间点"
|
||||
AdvSceneSwitcher.timeTab.afterstart.tip="相对于串流或录制开始之后的时间点"
|
||||
AdvSceneSwitcher.timeTab.entry="{{triggers}} 的 {{time}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
|
||||
AdvSceneSwitcher.timeTab.help="此选项卡将允许您根据当前本地时间自动切换到其他场景。\n\n请注意,场景切换程序仅在您指定的确切时间切换场景。\n请确保您已根据自己的喜好在“常规”选项卡上配置了优先级设置,以便不会因其他切换而错过选定的时间点具有较高优先级的方法。\n\n单击突出显示的加号继续."
|
||||
|
||||
@@ -508,9 +585,8 @@ AdvSceneSwitcher.audioTab.title="音频"
|
||||
AdvSceneSwitcher.audioTab.condition.above="高于"
|
||||
AdvSceneSwitcher.audioTab.condition.below="低于"
|
||||
AdvSceneSwitcher.audioTab.ignoreInactiveSource="除非源处于非活动状态"
|
||||
AdvSceneSwitcher.audioTab.entry="当{{audioSources}} 在 {{condition}} {{volumeWidget}} for {{duration}} 使用转场特效 {{transitions}} 切换到场景 {{scenes}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="如果多个条目匹配 ..."
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} 秒切换到 {{scenes}} 使用 {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.multiMatchfallback="... {{duration}} 秒切换到 {{scenes}} 使用 {{transitions}}"
|
||||
AdvSceneSwitcher.audioTab.help="此选项卡允许您根据源音量切换场景。\n例如,如果麦克风音量达到某个阈值,您可以自动切换到其他场景。\n\n单击高亮显示的加号继续."
|
||||
|
||||
; Video Tab
|
||||
@@ -530,7 +606,6 @@ AdvSceneSwitcher.videoTab.help="<html><head/><body><p>此选项卡将允许您
|
||||
AdvSceneSwitcher.networkTab.title="网络"
|
||||
AdvSceneSwitcher.networkTab.description="此选项卡将允许您远程控制另一个OBS实例的活动场景。\n请注意,所有OBS实例上的场景名称必须完全匹配."
|
||||
AdvSceneSwitcher.networkTab.warning="在本地网络之外运行服务器将允许第三方读取活动场景."
|
||||
AdvSceneSwitcher.networkTab.DisabledWarning="不幸的是,在并行运行obs websocket插件时,由于库不兼容,不得不在macOS上禁用此功能."
|
||||
AdvSceneSwitcher.networkTab.server="启动服务器(向所有连接的客户端发送场景切换消息)"
|
||||
AdvSceneSwitcher.networkTab.server.port="端口"
|
||||
AdvSceneSwitcher.networkTab.server.lockToIPv4="锁定为仅使用IPv4将服务器"
|
||||
@@ -602,8 +677,11 @@ AdvSceneSwitcher.hotkey.stopSwitcherHotkey="停止高级场景切换器"
|
||||
AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey="切换(启动/停止)高级场景切换器"
|
||||
AdvSceneSwitcher.hotkey.macro.pause="暂停宏 %1"
|
||||
AdvSceneSwitcher.hotkey.macro.unpause="取消暂停宏 %1"
|
||||
AdvSceneSwitcher.hotkey.macro.togglePause="切换暂停宏 %1"
|
||||
AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey="删除选定的宏"
|
||||
|
||||
AdvSceneSwitcher.askBackup="检测到新版本的高级场景切换程序。\n是否要创建旧设置的备份?"
|
||||
AdvSceneSwitcher.askForMacro="选择宏 {{macroSelection}}"
|
||||
|
||||
AdvSceneSwitcher.close="关闭"
|
||||
AdvSceneSwitcher.browse="浏览文件"
|
||||
@@ -621,19 +699,27 @@ AdvSceneSwitcher.selectAudioSource="--选择音频源--"
|
||||
AdvSceneSwitcher.selectVideoSource="--选择视频源--"
|
||||
AdvSceneSwitcher.selectMediaSource="--选择媒体源--"
|
||||
AdvSceneSwitcher.selectProcess="--选择进程--"
|
||||
AdvSceneSwitcher.selectFilter="--select filter--"
|
||||
AdvSceneSwitcher.selectFilter="--选择滤镜--"
|
||||
AdvSceneSwitcher.selectMacro="--选择宏--"
|
||||
AdvSceneSwitcher.selectItem="--选择项目--"
|
||||
AdvSceneSwitcher.selectProfile="--选择配置文件--"
|
||||
AdvSceneSwitcher.selectSceneCollection="--选择场景集合--"
|
||||
AdvSceneSwitcher.enterPath="--输入路径--"
|
||||
AdvSceneSwitcher.enterText="--输入文本--"
|
||||
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="无效项将不会被保存"
|
||||
AdvSceneSwitcher.selectWindowTip="使用 \"OBS\" 来指定OBS窗口\n使用 \"任务切换\" 来指定 Alt + Tab"
|
||||
AdvSceneSwitcher.sceneItemSelection.all="全部"
|
||||
AdvSceneSwitcher.sceneItemSelection.any="任何"
|
||||
|
||||
AdvSceneSwitcher.status.active="运行中"
|
||||
AdvSceneSwitcher.status.inactive="已停止"
|
||||
AdvSceneSwitcher.running="插件运行"
|
||||
AdvSceneSwitcher.stopped="插件停止"
|
||||
|
||||
AdvSceneSwitcher.firstBootMessage="<html><head/><body><p>这似乎是第一次启动高级场景切换程序.<br>请看一下 <a href=\"https://github.com/WarmUpTill/SceneSwitcher/wiki\"><span style=\" text-decoration: underline; color:#268bd2;\">Wiki</span></a> 查看指南和示例列表.<br>如果有问题,在在OBS论坛插件帖子内提问 <a href=\"https://obsproject.com/forum/threads/advanced-scene-switcher.48264\"><span style=\" text-decoration: underline; color:#268bd2;\">thread</span></a></p></body></html>"
|
||||
|
||||
AdvSceneSwitcher.deprecatedTabWarning="此选项卡的开发已停止!请考虑转换为使用宏来代替。\n可以在“常规”选项卡上禁用此提示."
|
||||
|
||||
AdvSceneSwitcher.unit.milliseconds="毫秒"
|
||||
AdvSceneSwitcher.unit.secends="秒"
|
||||
AdvSceneSwitcher.unit.minutes="分钟"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,6 +20,8 @@ SwitcherData *GetSwitcher()
|
||||
return switcher;
|
||||
}
|
||||
|
||||
AdvSceneSwitcher *AdvSceneSwitcher::window = nullptr;
|
||||
|
||||
/******************************************************************************
|
||||
* Create the Advanced Scene Switcher settings window
|
||||
******************************************************************************/
|
||||
@@ -39,6 +41,7 @@ AdvSceneSwitcher::~AdvSceneSwitcher()
|
||||
{
|
||||
if (switcher) {
|
||||
switcher->settingsWindowOpened = false;
|
||||
switcher->lastOpenedTab = ui->tabWidget->currentIndex();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +88,9 @@ void AdvSceneSwitcher::loadUI()
|
||||
setupTriggerTab();
|
||||
setupMacroTab();
|
||||
|
||||
setDeprecationWarnings();
|
||||
setTabOrder();
|
||||
setCurrentTab();
|
||||
restoreWindowGeo();
|
||||
checkFirstTimeSetup();
|
||||
|
||||
@@ -340,7 +345,7 @@ bool SwitcherData::checkForMatch(OBSWeakSource &scene,
|
||||
return match;
|
||||
}
|
||||
|
||||
void switchScene(const sceneSwitchInfo &sceneSwitch)
|
||||
void switchScene(const sceneSwitchInfo &sceneSwitch, bool force)
|
||||
{
|
||||
if (!sceneSwitch.scene && switcher->verbose) {
|
||||
blog(LOG_INFO, "nothing to switch to");
|
||||
@@ -350,7 +355,7 @@ void switchScene(const sceneSwitchInfo &sceneSwitch)
|
||||
obs_source_t *source = obs_weak_source_get_source(sceneSwitch.scene);
|
||||
obs_source_t *currentSource = obs_frontend_get_current_scene();
|
||||
|
||||
if (source && source != currentSource) {
|
||||
if (source && (source != currentSource || force)) {
|
||||
transitionData currentTransitionData;
|
||||
setNextTransition(sceneSwitch, currentSource,
|
||||
currentTransitionData);
|
||||
@@ -432,13 +437,6 @@ void SwitcherData::Stop()
|
||||
server.stop();
|
||||
client.disconnect();
|
||||
|
||||
for (auto &t : audioHelperThreads) {
|
||||
if (t.joinable()) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
audioHelperThreads.clear();
|
||||
|
||||
if (showSystemTrayNotifications) {
|
||||
DisplayTrayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.pluginName"),
|
||||
@@ -574,6 +572,18 @@ void setStreamStopping()
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void handleExit()
|
||||
{
|
||||
switcher->obsIsShuttingDown = true;
|
||||
if (switcher && switcher->obsIsShuttingDown &&
|
||||
switcher->shutdownConditionCount) {
|
||||
switcher->Stop();
|
||||
switcher->checkMacros();
|
||||
switcher->runMacros();
|
||||
}
|
||||
FreeSceneSwitcher();
|
||||
}
|
||||
|
||||
// Note to future self:
|
||||
// be careful using switcher->m here as there is potential for deadlocks when using
|
||||
// frontend functions such as obs_frontend_set_current_scene()
|
||||
@@ -585,7 +595,7 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
|
||||
|
||||
switch (event) {
|
||||
case OBS_FRONTEND_EVENT_EXIT:
|
||||
FreeSceneSwitcher();
|
||||
handleExit();
|
||||
break;
|
||||
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
||||
handleSceneChange();
|
||||
@@ -652,8 +662,6 @@ void LoadPlugins()
|
||||
}
|
||||
}
|
||||
|
||||
AdvSceneSwitcher *ssWindow;
|
||||
|
||||
extern "C" void InitSceneSwitcher()
|
||||
{
|
||||
blog(LOG_INFO, "version: %s", g_GIT_TAG);
|
||||
@@ -671,15 +679,16 @@ extern "C" void InitSceneSwitcher()
|
||||
|
||||
auto cb = []() {
|
||||
if (switcher->settingsWindowOpened) {
|
||||
ssWindow->show();
|
||||
ssWindow->raise();
|
||||
ssWindow->activateWindow();
|
||||
AdvSceneSwitcher::window->show();
|
||||
AdvSceneSwitcher::window->raise();
|
||||
AdvSceneSwitcher::window->activateWindow();
|
||||
} else {
|
||||
ssWindow =
|
||||
AdvSceneSwitcher::window =
|
||||
new AdvSceneSwitcher(static_cast<QMainWindow *>(
|
||||
obs_frontend_get_main_window()));
|
||||
ssWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
ssWindow->show();
|
||||
AdvSceneSwitcher::window->setAttribute(
|
||||
Qt::WA_DeleteOnClose);
|
||||
AdvSceneSwitcher::window->show();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,19 @@ else()
|
||||
endif()
|
||||
|
||||
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../headers")
|
||||
set(module_SOURCES macro-condition-video.cpp macro-condition-video.hpp)
|
||||
set(module_SOURCES
|
||||
area-selection.cpp
|
||||
area-selection.hpp
|
||||
macro-condition-video.cpp
|
||||
macro-condition-video.hpp
|
||||
opencv-helpers.cpp
|
||||
opencv-helpers.hpp
|
||||
preview-dialog.cpp
|
||||
preview-dialog.hpp
|
||||
threshold-slider.cpp
|
||||
threshold-slider.hpp
|
||||
video-selection.cpp
|
||||
video-selection.hpp)
|
||||
add_library(advanced-scene-switcher-opencv MODULE ${module_SOURCES})
|
||||
|
||||
if(BUILD_OUT_OF_TREE)
|
||||
|
||||
130
src/external-macro-modules/opencv/area-selection.cpp
Normal file
130
src/external-macro-modules/opencv/area-selection.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include "area-selection.hpp"
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <obs-module.h>
|
||||
|
||||
void advss::Size::Save(obs_data_t *obj, const char *name)
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_int(data, "width", width);
|
||||
obs_data_set_int(data, "height", height);
|
||||
obs_data_set_obj(obj, name, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void advss::Size::Load(obs_data_t *obj, const char *name)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, name);
|
||||
width = obs_data_get_int(data, "width");
|
||||
height = obs_data_get_int(data, "height");
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
cv::Size advss::Size::CV()
|
||||
{
|
||||
return {width, height};
|
||||
}
|
||||
|
||||
void advss::Area::Save(obs_data_t *obj, const char *name)
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_int(data, "x", x);
|
||||
obs_data_set_int(data, "y", y);
|
||||
obs_data_set_int(data, "width", width);
|
||||
obs_data_set_int(data, "height", height);
|
||||
obs_data_set_obj(obj, name, data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void advss::Area::Load(obs_data_t *obj, const char *name)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, name);
|
||||
x = obs_data_get_int(data, "x");
|
||||
y = obs_data_get_int(data, "y");
|
||||
width = obs_data_get_int(data, "width");
|
||||
height = obs_data_get_int(data, "height");
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
SizeSelection::SizeSelection(int min, int max, QWidget *parent)
|
||||
: QWidget(parent), _x(new QSpinBox), _y(new QSpinBox)
|
||||
{
|
||||
_x->setMinimum(min);
|
||||
_y->setMinimum(min);
|
||||
_x->setMaximum(max);
|
||||
_y->setMaximum(max);
|
||||
|
||||
connect(_x, SIGNAL(valueChanged(int)), this, SLOT(XChanged(int)));
|
||||
connect(_y, SIGNAL(valueChanged(int)), this, SLOT(YChanged(int)));
|
||||
|
||||
auto layout = new QHBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_x);
|
||||
layout->addWidget(new QLabel(" x "));
|
||||
layout->addWidget(_y);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void SizeSelection::SetSize(const advss::Size &s)
|
||||
{
|
||||
_x->setValue(s.width);
|
||||
_y->setValue(s.height);
|
||||
}
|
||||
|
||||
advss::Size SizeSelection::Size()
|
||||
{
|
||||
return advss::Size{_x->value(), _y->value()};
|
||||
}
|
||||
|
||||
void SizeSelection::XChanged(int value)
|
||||
{
|
||||
emit SizeChanged(advss::Size{value, _y->value()});
|
||||
}
|
||||
|
||||
void SizeSelection::YChanged(int value)
|
||||
{
|
||||
emit SizeChanged(advss::Size{_x->value(), value});
|
||||
}
|
||||
|
||||
AreaSelection::AreaSelection(int min, int max, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_x(new SizeSelection(min, max)),
|
||||
_y(new SizeSelection(min, max))
|
||||
{
|
||||
_x->_x->setToolTip("X");
|
||||
_x->_y->setToolTip("Y");
|
||||
_y->_x->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.video.width"));
|
||||
_y->_y->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.video.height"));
|
||||
|
||||
connect(_x, SIGNAL(SizeChanged(advss::Size)), this,
|
||||
SLOT(XSizeChanged(advss::Size)));
|
||||
connect(_y, SIGNAL(SizeChanged(advss::Size)), this,
|
||||
SLOT(YSizeChanged(advss::Size)));
|
||||
|
||||
auto layout = new QVBoxLayout();
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(_x);
|
||||
layout->addWidget(_y);
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void AreaSelection::SetArea(const advss::Area &value)
|
||||
{
|
||||
_x->SetSize({value.x, value.y});
|
||||
_y->SetSize({value.width, value.height});
|
||||
}
|
||||
|
||||
void AreaSelection::XSizeChanged(advss::Size value)
|
||||
{
|
||||
emit AreaChanged(advss::Area{value.width, value.height,
|
||||
_y->Size().width, _y->Size().height});
|
||||
}
|
||||
|
||||
void AreaSelection::YSizeChanged(advss::Size value)
|
||||
{
|
||||
emit AreaChanged(advss::Area{_x->Size().width, _x->Size().height,
|
||||
value.width, value.height});
|
||||
}
|
||||
67
src/external-macro-modules/opencv/area-selection.hpp
Normal file
67
src/external-macro-modules/opencv/area-selection.hpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
#include <QWidget>
|
||||
#include <QSpinBox>
|
||||
#include <obs-data.h>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
namespace advss {
|
||||
|
||||
struct Size {
|
||||
void Save(obs_data_t *obj, const char *name);
|
||||
void Load(obs_data_t *obj, const char *name);
|
||||
cv::Size CV();
|
||||
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
struct Area {
|
||||
void Save(obs_data_t *obj, const char *name);
|
||||
void Load(obs_data_t *obj, const char *name);
|
||||
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
class SizeSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SizeSelection(int min, int max, QWidget *parent = 0);
|
||||
void SetSize(const advss::Size &);
|
||||
advss::Size Size();
|
||||
|
||||
private slots:
|
||||
void XChanged(int);
|
||||
void YChanged(int);
|
||||
signals:
|
||||
void SizeChanged(advss::Size value);
|
||||
|
||||
private:
|
||||
QSpinBox *_x;
|
||||
QSpinBox *_y;
|
||||
|
||||
friend class AreaSelection;
|
||||
};
|
||||
|
||||
class AreaSelection : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AreaSelection(int min, int max, QWidget *parent = 0);
|
||||
void SetArea(const advss::Area &);
|
||||
|
||||
private slots:
|
||||
void XSizeChanged(advss::Size value);
|
||||
void YSizeChanged(advss::Size value);
|
||||
signals:
|
||||
void AreaChanged(advss::Area value);
|
||||
|
||||
private:
|
||||
SizeSelection *_x;
|
||||
SizeSelection *_y;
|
||||
};
|
||||
@@ -72,33 +72,37 @@ bool MacroConditionVideo::CheckShouldBeSkipped()
|
||||
|
||||
bool MacroConditionVideo::CheckCondition()
|
||||
{
|
||||
bool match = false;
|
||||
if (!_video.ValidSelection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
if (CheckShouldBeSkipped()) {
|
||||
return _lastMatchResult;
|
||||
}
|
||||
|
||||
if (_screenshotData && _screenshotData->done) {
|
||||
if (_screenshotData.done) {
|
||||
match = Compare();
|
||||
_lastMatchResult = match;
|
||||
|
||||
if (!requiresFileInput(_condition)) {
|
||||
_matchImage = std::move(_screenshotData->image);
|
||||
_matchImage = std::move(_screenshotData.image);
|
||||
}
|
||||
_screenshotData.reset(nullptr);
|
||||
_getNextScreenshot = true;
|
||||
} else {
|
||||
match = _lastMatchResult;
|
||||
}
|
||||
|
||||
GetScreenshot();
|
||||
if (_getNextScreenshot) {
|
||||
GetScreenshot();
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_string(obj, "videoSource",
|
||||
GetWeakSourceName(_videoSource).c_str());
|
||||
_video.Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "filePath", _file.c_str());
|
||||
obs_data_set_bool(obj, "usePatternForChangedCheck",
|
||||
@@ -108,12 +112,12 @@ bool MacroConditionVideo::Save(obs_data_t *obj)
|
||||
obs_data_set_string(obj, "modelDataPath", _modelDataPath.c_str());
|
||||
obs_data_set_double(obj, "scaleFactor", _scaleFactor);
|
||||
obs_data_set_int(obj, "minNeighbors", _minNeighbors);
|
||||
obs_data_set_int(obj, "minSizeX", _minSizeX);
|
||||
obs_data_set_int(obj, "minSizeY", _minSizeY);
|
||||
obs_data_set_int(obj, "maxSizeX", _maxSizeX);
|
||||
obs_data_set_int(obj, "maxSizeY", _maxSizeY);
|
||||
_minSize.Save(obj, "minSize");
|
||||
_maxSize.Save(obj, "maxSize");
|
||||
obs_data_set_bool(obj, "throttleEnabled", _throttleEnabled);
|
||||
obs_data_set_int(obj, "throttleCount", _throttleCount);
|
||||
obs_data_set_bool(obj, "checkAreaEnabled", _checkAreaEnable);
|
||||
_checkArea.Save(obj, "checkArea");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -131,8 +135,10 @@ bool isMinNeighborsValid(int minNeighbors)
|
||||
bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
const char *videoSourceName = obs_data_get_string(obj, "videoSource");
|
||||
_videoSource = GetWeakSourceByName(videoSourceName);
|
||||
_video.Load(obj);
|
||||
if (obs_data_has_user_value(obj, "videoSource")) {
|
||||
_video.Load(obj, "videoSource");
|
||||
}
|
||||
_condition =
|
||||
static_cast<VideoCondition>(obs_data_get_int(obj, "condition"));
|
||||
_file = obs_data_get_string(obj, "filePath");
|
||||
@@ -149,13 +155,20 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
if (!isMinNeighborsValid(_minNeighbors)) {
|
||||
_minNeighbors = minMinNeighbors;
|
||||
}
|
||||
_minSizeX = obs_data_get_int(obj, "minSizeX");
|
||||
_minSizeY = obs_data_get_int(obj, "minSizeY");
|
||||
_maxSizeX = obs_data_get_int(obj, "maxSizeX");
|
||||
_maxSizeY = obs_data_get_int(obj, "maxSizeY");
|
||||
// TODO: Remove this fallback in future version
|
||||
if (obs_data_has_user_value(obj, "minSizeX")) {
|
||||
_minSize.width = obs_data_get_int(obj, "minSizeX");
|
||||
_minSize.height = obs_data_get_int(obj, "minSizeY");
|
||||
_maxSize.width = obs_data_get_int(obj, "maxSizeX");
|
||||
_maxSize.height = obs_data_get_int(obj, "maxSizeY");
|
||||
} else {
|
||||
_minSize.Load(obj, "minSize");
|
||||
_maxSize.Load(obj, "maxSize");
|
||||
}
|
||||
_throttleEnabled = obs_data_get_bool(obj, "throttleEnabled");
|
||||
_throttleCount = obs_data_get_int(obj, "throttleCount");
|
||||
|
||||
_checkAreaEnable = obs_data_get_bool(obj, "checkAreaEnabled");
|
||||
_checkArea.Load(obj, "checkArea");
|
||||
if (requiresFileInput(_condition)) {
|
||||
(void)LoadImageFromFile();
|
||||
}
|
||||
@@ -169,55 +182,16 @@ bool MacroConditionVideo::Load(obs_data_t *obj)
|
||||
|
||||
std::string MacroConditionVideo::GetShortDesc()
|
||||
{
|
||||
if (_videoSource) {
|
||||
return GetWeakSourceName(_videoSource);
|
||||
}
|
||||
return "";
|
||||
return _video.ToString();
|
||||
}
|
||||
|
||||
void MacroConditionVideo::GetScreenshot()
|
||||
{
|
||||
auto source = obs_weak_source_get_source(_videoSource);
|
||||
_screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
auto source = obs_weak_source_get_source(_video.GetVideo());
|
||||
_screenshotData.~ScreenshotHelper();
|
||||
new (&_screenshotData) ScreenshotHelper(source);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
// Assumption is that QImage uses Format_RGBA8888.
|
||||
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
||||
cv::Mat QImageToMat(const QImage &img)
|
||||
{
|
||||
if (img.isNull()) {
|
||||
return cv::Mat();
|
||||
}
|
||||
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
|
||||
(uchar *)img.bits(), img.bytesPerLine());
|
||||
}
|
||||
|
||||
QImage MatToQImage(const cv::Mat &mat)
|
||||
{
|
||||
if (mat.empty()) {
|
||||
return QImage();
|
||||
}
|
||||
return QImage(mat.data, mat.cols, mat.rows, mat.step,
|
||||
QImage::Format::Format_RGBA8888);
|
||||
}
|
||||
|
||||
PatternMatchData createPatternData(QImage &pattern)
|
||||
{
|
||||
PatternMatchData data;
|
||||
if (pattern.isNull()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data.rgbaPattern = QImageToMat(pattern);
|
||||
std::vector<cv::Mat1b> rgbaChannelsPattern;
|
||||
cv::split(data.rgbaPattern, rgbaChannelsPattern);
|
||||
std::vector<cv::Mat1b> rgbChanlesPattern(
|
||||
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
|
||||
cv::merge(rgbChanlesPattern, data.rgbPattern);
|
||||
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
|
||||
cv::THRESH_BINARY);
|
||||
return data;
|
||||
_getNextScreenshot = false;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::LoadImageFromFile()
|
||||
@@ -241,51 +215,10 @@ bool MacroConditionVideo::LoadModelData(std::string &path)
|
||||
return !_objectCascade.empty();
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, PatternMatchData &patternData, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask = true)
|
||||
{
|
||||
if (img.isNull() || patternData.rgbaPattern.empty()) {
|
||||
return;
|
||||
}
|
||||
if (img.height() < patternData.rgbaPattern.rows ||
|
||||
img.width() < patternData.rgbaPattern.cols) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto i = QImageToMat(img);
|
||||
|
||||
if (useAlphaAsMask) {
|
||||
std::vector<cv::Mat1b> rgbaChannelsImage;
|
||||
cv::split(i, rgbaChannelsImage);
|
||||
std::vector<cv::Mat1b> rgbChanlesImage(
|
||||
rgbaChannelsImage.begin(),
|
||||
rgbaChannelsImage.begin() + 3);
|
||||
|
||||
cv::Mat3b rgbImage;
|
||||
cv::merge(rgbChanlesImage, rgbImage);
|
||||
|
||||
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
|
||||
cv::TM_CCORR_NORMED, patternData.mask);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
|
||||
} else {
|
||||
cv::matchTemplate(i, patternData.rgbaPattern, result,
|
||||
cv::TM_CCOEFF_NORMED);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
}
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, QImage &pattern, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask)
|
||||
{
|
||||
auto data = createPatternData(pattern);
|
||||
matchPattern(img, data, threshold, result, useAlphaAsMask);
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::ScreenshotContainsPattern()
|
||||
{
|
||||
cv::Mat result;
|
||||
matchPattern(_screenshotData->image, _patternData, _patternThreshold,
|
||||
matchPattern(_screenshotData.image, _patternData, _patternThreshold,
|
||||
result, _useAlphaAsMask);
|
||||
return countNonZero(result) > 0;
|
||||
}
|
||||
@@ -295,53 +228,40 @@ bool MacroConditionVideo::OutputChanged()
|
||||
if (_usePatternForChangedCheck) {
|
||||
cv::Mat result;
|
||||
_patternData = createPatternData(_matchImage);
|
||||
matchPattern(_screenshotData->image, _patternData,
|
||||
matchPattern(_screenshotData.image, _patternData,
|
||||
_patternThreshold, result, _useAlphaAsMask);
|
||||
return countNonZero(result) == 0;
|
||||
}
|
||||
return _screenshotData->image != _matchImage;
|
||||
}
|
||||
|
||||
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
double scaleFactor, int minNeighbors,
|
||||
cv::Size minSize, cv::Size maxSize)
|
||||
{
|
||||
if (img.isNull() || cascade.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto i = QImageToMat(img);
|
||||
cv::Mat frameGray;
|
||||
cv::cvtColor(i, frameGray, cv::COLOR_BGR2GRAY);
|
||||
equalizeHist(frameGray, frameGray);
|
||||
std::vector<cv::Rect> objects;
|
||||
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
|
||||
0, minSize, maxSize);
|
||||
return objects;
|
||||
return _screenshotData.image != _matchImage;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::ScreenshotContainsObject()
|
||||
{
|
||||
auto objects = matchObject(_screenshotData->image, _objectCascade,
|
||||
_scaleFactor, _minNeighbors,
|
||||
{_minSizeX, _minSizeY},
|
||||
{_maxSizeX, _maxSizeY});
|
||||
auto objects = matchObject(_screenshotData.image, _objectCascade,
|
||||
_scaleFactor, _minNeighbors, _minSize.CV(),
|
||||
_maxSize.CV());
|
||||
return objects.size() > 0;
|
||||
}
|
||||
|
||||
bool MacroConditionVideo::Compare()
|
||||
{
|
||||
if (_checkAreaEnable && _condition != VideoCondition::NO_IMAGE) {
|
||||
_screenshotData.image = _screenshotData.image.copy(
|
||||
_checkArea.x, _checkArea.y, _checkArea.width,
|
||||
_checkArea.height);
|
||||
}
|
||||
|
||||
switch (_condition) {
|
||||
case VideoCondition::MATCH:
|
||||
return _screenshotData->image == _matchImage;
|
||||
return _screenshotData.image == _matchImage;
|
||||
case VideoCondition::DIFFER:
|
||||
return _screenshotData->image != _matchImage;
|
||||
return _screenshotData.image != _matchImage;
|
||||
case VideoCondition::HAS_CHANGED:
|
||||
return OutputChanged();
|
||||
case VideoCondition::HAS_NOT_CHANGED:
|
||||
return !OutputChanged();
|
||||
case VideoCondition::NO_IMAGE:
|
||||
return _screenshotData->image.isNull();
|
||||
return _screenshotData.image.isNull();
|
||||
case VideoCondition::PATTERN:
|
||||
return ScreenshotContainsPattern();
|
||||
case VideoCondition::OBJECT:
|
||||
@@ -359,108 +279,62 @@ static inline void populateConditionSelection(QComboBox *list)
|
||||
}
|
||||
}
|
||||
|
||||
ThresholdSlider::ThresholdSlider(double min, double max, const QString &label,
|
||||
const QString &description, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_slider = new QSlider();
|
||||
_slider->setOrientation(Qt::Horizontal);
|
||||
_slider->setRange(min * _scale, max * _scale);
|
||||
_value = new QLabel();
|
||||
QString labelText = label + QString("0.");
|
||||
for (int i = 0; i < _precision; i++) {
|
||||
labelText.append(QString("0"));
|
||||
}
|
||||
_value->setText(labelText);
|
||||
connect(_slider, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(NotifyValueChanged(int)));
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
QHBoxLayout *sliderLayout = new QHBoxLayout();
|
||||
sliderLayout->addWidget(_value);
|
||||
sliderLayout->addWidget(_slider);
|
||||
mainLayout->addLayout(sliderLayout);
|
||||
if (!description.isEmpty()) {
|
||||
mainLayout->addWidget(new QLabel(description));
|
||||
}
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void ThresholdSlider::SetDoubleValue(double value)
|
||||
{
|
||||
_slider->setValue(value * _scale);
|
||||
SetDoubleValueText(value);
|
||||
}
|
||||
|
||||
void ThresholdSlider::NotifyValueChanged(int value)
|
||||
{
|
||||
double doubleValue = value / _scale;
|
||||
SetDoubleValueText(doubleValue);
|
||||
emit DoubleValueChanged(doubleValue);
|
||||
}
|
||||
|
||||
void ThresholdSlider::SetDoubleValueText(double value)
|
||||
{
|
||||
QString labelText = _value->text();
|
||||
labelText.chop(_precision + 2); // 2 for the part left of the "."
|
||||
labelText.append(QString::number(value, 'f', _precision));
|
||||
_value->setText(labelText);
|
||||
}
|
||||
|
||||
MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionVideo> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_videoSelection(new VideoSelectionWidget(this)),
|
||||
_condition(new QComboBox()),
|
||||
_usePatternForChangedCheck(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"))),
|
||||
_imagePath(new FileSelection()),
|
||||
_patternThreshold(new ThresholdSlider(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThresholdDescription"))),
|
||||
_useAlphaAsMask(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"))),
|
||||
_modelDataPath(new FileSelection()),
|
||||
_modelPathLayout(new QHBoxLayout),
|
||||
_objectScaleThreshold(new ThresholdSlider(
|
||||
1.1, 5.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectScaleThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectScaleThresholdDescription"))),
|
||||
_neighborsControlLayout(new QHBoxLayout),
|
||||
_minNeighbors(new QSpinBox()),
|
||||
_minNeighborsDescription(new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.minNeighborDescription"))),
|
||||
_sizeLayout(new QHBoxLayout()),
|
||||
_minSize(new SizeSelection(0, 1024)),
|
||||
_maxSize(new SizeSelection(0, 4096)),
|
||||
_checkAreaControlLayout(new QHBoxLayout),
|
||||
_checkAreaEnable(new QCheckBox()),
|
||||
_checkArea(new AreaSelection(0, 99999)),
|
||||
_selectArea(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectArea"))),
|
||||
_throttleControlLayout(new QHBoxLayout),
|
||||
_throttleEnable(new QCheckBox()),
|
||||
_throttleCount(new QSpinBox()),
|
||||
_showMatch(new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.showMatch"))),
|
||||
_previewDialog(this, entryData.get(), &GetSwitcher()->m)
|
||||
{
|
||||
_videoSelection = new QComboBox();
|
||||
_condition = new QComboBox();
|
||||
|
||||
_imagePath = new FileSelection();
|
||||
_imagePath->Button()->disconnect();
|
||||
_usePatternForChangedCheck = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck"));
|
||||
_usePatternForChangedCheck->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip"));
|
||||
_patternThreshold = new ThresholdSlider(
|
||||
0., 1.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThresholdDescription"));
|
||||
_useAlphaAsMask = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask"));
|
||||
|
||||
_modelDataPath = new FileSelection();
|
||||
_objectScaleThreshold = new ThresholdSlider(
|
||||
1.1, 5.,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectScaleThreshold"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectScaleThresholdDescription"));
|
||||
_minNeighbors = new QSpinBox();
|
||||
_minNeighbors->setMinimum(minMinNeighbors);
|
||||
_minNeighbors->setMaximum(maxMinNeighbors);
|
||||
_minNeighborsDescription = new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.minNeighborDescription"));
|
||||
_minSizeX = new QSpinBox();
|
||||
_minSizeY = new QSpinBox();
|
||||
_minSizeX->setMaximum(1024);
|
||||
_minSizeY->setMaximum(1024);
|
||||
_maxSizeX = new QSpinBox();
|
||||
_maxSizeY = new QSpinBox();
|
||||
_maxSizeX->setMaximum(4096);
|
||||
_maxSizeY->setMaximum(4096);
|
||||
|
||||
_throttleEnable = new QCheckBox();
|
||||
_throttleCount = new QSpinBox();
|
||||
_throttleCount->setMinimum(1 * GetSwitcher()->interval);
|
||||
_throttleCount->setMaximum(10 * GetSwitcher()->interval);
|
||||
_throttleCount->setSingleStep(GetSwitcher()->interval);
|
||||
_showMatch = new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.condition.video.showMatch"));
|
||||
|
||||
QWidget::connect(_videoSelection,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(SourceChanged(const QString &)));
|
||||
SIGNAL(VideoSelectionChange(const VideoSelection &)),
|
||||
this,
|
||||
SLOT(VideoSelectionChanged(const VideoSelection &)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_imagePath, SIGNAL(PathChanged(const QString &)), this,
|
||||
@@ -478,14 +352,14 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
SLOT(ObjectScaleThresholdChanged(double)));
|
||||
QWidget::connect(_minNeighbors, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MinNeighborsChanged(int)));
|
||||
QWidget::connect(_minSizeX, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MinSizeXChanged(int)));
|
||||
QWidget::connect(_minSizeY, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MinSizeYChanged(int)));
|
||||
QWidget::connect(_maxSizeX, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MaxSizeXChanged(int)));
|
||||
QWidget::connect(_maxSizeY, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(MaxSizeYChanged(int)));
|
||||
QWidget::connect(_minSize, SIGNAL(SizeChanged(advss::Size)), this,
|
||||
SLOT(MinSizeChanged(advss::Size)));
|
||||
QWidget::connect(_maxSize, SIGNAL(SizeChanged(advss::Size)), this,
|
||||
SLOT(MaxSizeChanged(advss::Size)));
|
||||
QWidget::connect(_checkAreaEnable, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(CheckAreaEnableChanged(int)));
|
||||
QWidget::connect(_checkArea, SIGNAL(AreaChanged(advss::Area)), this,
|
||||
SLOT(CheckAreaChanged(advss::Area)));
|
||||
QWidget::connect(_modelDataPath, SIGNAL(PathChanged(const QString &)),
|
||||
this, SLOT(ModelPathChanged(const QString &)));
|
||||
QWidget::connect(_throttleEnable, SIGNAL(stateChanged(int)), this,
|
||||
@@ -494,8 +368,11 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
SLOT(ThrottleCountChanged(int)));
|
||||
QWidget::connect(_showMatch, SIGNAL(clicked()), this,
|
||||
SLOT(ShowMatchClicked()));
|
||||
QWidget::connect(&_previewDialog, SIGNAL(SelectionAreaChanged(QRect)),
|
||||
this, SLOT(CheckAreaChanged(QRect)));
|
||||
QWidget::connect(_selectArea, SIGNAL(clicked()), this,
|
||||
SLOT(SelectAreaClicked()));
|
||||
|
||||
populateVideoSelection(_videoSelection);
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
QHBoxLayout *entryLine1Layout = new QHBoxLayout;
|
||||
@@ -504,43 +381,47 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
{"{{condition}}", _condition},
|
||||
{"{{imagePath}}", _imagePath},
|
||||
{"{{minNeighbors}}", _minNeighbors},
|
||||
{"{{minSizeX}}", _minSizeX},
|
||||
{"{{minSizeY}}", _minSizeY},
|
||||
{"{{maxSizeX}}", _maxSizeX},
|
||||
{"{{maxSizeY}}", _maxSizeY},
|
||||
{"{{minSize}}", _minSize},
|
||||
{"{{maxSize}}", _maxSize},
|
||||
{"{{modelDataPath}}", _modelDataPath},
|
||||
{"{{throttleEnable}}", _throttleEnable},
|
||||
{"{{throttleCount}}", _throttleCount},
|
||||
{"{{checkAreaEnable}}", _checkAreaEnable},
|
||||
{"{{checkArea}}", _checkArea},
|
||||
{"{{selectArea}}", _selectArea},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.video.entry"),
|
||||
entryLine1Layout, widgetPlaceholders);
|
||||
|
||||
_modelPathLayout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.modelPath"),
|
||||
_modelPathLayout, widgetPlaceholders);
|
||||
|
||||
_neighborsControlLayout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.minNeighbor"),
|
||||
_neighborsControlLayout, widgetPlaceholders);
|
||||
|
||||
_minSizeControlLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.minSize"),
|
||||
_minSizeControlLayout, widgetPlaceholders);
|
||||
|
||||
_maxSizeControlLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.maxSize"),
|
||||
_maxSizeControlLayout, widgetPlaceholders);
|
||||
|
||||
_throttleControlLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.throttle"),
|
||||
_throttleControlLayout, widgetPlaceholders);
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.entry.checkArea"),
|
||||
_checkAreaControlLayout, widgetPlaceholders);
|
||||
|
||||
QGridLayout *sizeGrid = new QGridLayout;
|
||||
sizeGrid->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.minSize")),
|
||||
0, 0);
|
||||
sizeGrid->addWidget(_minSize, 0, 1);
|
||||
sizeGrid->addWidget(
|
||||
new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.maxSize")),
|
||||
1, 0);
|
||||
sizeGrid->addWidget(_maxSize, 1, 1);
|
||||
_sizeLayout->addLayout(sizeGrid);
|
||||
_sizeLayout->addStretch();
|
||||
_sizeLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QHBoxLayout *showMatchLayout = new QHBoxLayout;
|
||||
showMatchLayout->addWidget(_showMatch);
|
||||
@@ -554,10 +435,10 @@ MacroConditionVideoEdit::MacroConditionVideoEdit(
|
||||
mainLayout->addWidget(_objectScaleThreshold);
|
||||
mainLayout->addLayout(_neighborsControlLayout);
|
||||
mainLayout->addWidget(_minNeighborsDescription);
|
||||
mainLayout->addLayout(_minSizeControlLayout);
|
||||
mainLayout->addLayout(_maxSizeControlLayout);
|
||||
mainLayout->addLayout(_sizeLayout);
|
||||
mainLayout->addLayout(showMatchLayout);
|
||||
mainLayout->addLayout(_throttleControlLayout);
|
||||
mainLayout->addLayout(_checkAreaControlLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -591,14 +472,14 @@ void MacroConditionVideoEdit::UpdatePreviewTooltip()
|
||||
this->setToolTip(html);
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::SourceChanged(const QString &text)
|
||||
void MacroConditionVideoEdit::VideoSelectionChanged(const VideoSelection &v)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
|
||||
_entryData->_videoSource = GetWeakSourceByQString(text);
|
||||
_entryData->_video = v;
|
||||
_entryData->ResetLastMatch();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
@@ -653,7 +534,7 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
|
||||
QString path;
|
||||
bool useExistingFile = false;
|
||||
// Ask whether to create screenshot or to select existing file
|
||||
if (_entryData->_videoSource) {
|
||||
if (_entryData->_video.ValidSelection()) {
|
||||
QMessageBox msgBox(
|
||||
QMessageBox::Question,
|
||||
obs_module_text("AdvSceneSwitcher.windowTitle"),
|
||||
@@ -680,9 +561,9 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
|
||||
}
|
||||
|
||||
} else {
|
||||
auto source =
|
||||
obs_weak_source_get_source(_entryData->_videoSource);
|
||||
auto screenshot = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
auto source = obs_weak_source_get_source(
|
||||
_entryData->_video.GetVideo());
|
||||
ScreenshotHelper screenshot(source);
|
||||
obs_source_release(source);
|
||||
|
||||
path = QFileDialog::getSaveFileName(this);
|
||||
@@ -693,16 +574,22 @@ void MacroConditionVideoEdit::ImageBrowseButtonClicked()
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
if (!screenshot->done) { // Screenshot usually completed by now
|
||||
if (!screenshot.done) { // Screenshot usually completed by now
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
if (!screenshot->done) {
|
||||
if (!screenshot.done) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.screenshotFail"));
|
||||
return;
|
||||
}
|
||||
|
||||
screenshot->image.save(path);
|
||||
if (_entryData->_checkAreaEnable) {
|
||||
screenshot.image = screenshot.image.copy(
|
||||
_entryData->_checkArea.x,
|
||||
_entryData->_checkArea.y,
|
||||
_entryData->_checkArea.width,
|
||||
_entryData->_checkArea.height);
|
||||
}
|
||||
screenshot.image.save(path);
|
||||
}
|
||||
_imagePath->SetPath(path);
|
||||
ImagePathChanged(path);
|
||||
@@ -761,44 +648,53 @@ void MacroConditionVideoEdit::MinNeighborsChanged(int value)
|
||||
_entryData->_minNeighbors = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::MinSizeXChanged(int value)
|
||||
void MacroConditionVideoEdit::MinSizeChanged(advss::Size value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
|
||||
_entryData->_minSizeX = value;
|
||||
_entryData->_minSize = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::MinSizeYChanged(int value)
|
||||
void MacroConditionVideoEdit::MaxSizeChanged(advss::Size value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
|
||||
_entryData->_minSizeY = value;
|
||||
_entryData->_maxSize = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::MaxSizeXChanged(int value)
|
||||
void MacroConditionVideoEdit::CheckAreaEnableChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
|
||||
_entryData->_maxSizeX = value;
|
||||
_entryData->_checkAreaEnable = value;
|
||||
_checkArea->setEnabled(value);
|
||||
_selectArea->setEnabled(value);
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::MaxSizeYChanged(int value)
|
||||
void MacroConditionVideoEdit::CheckAreaChanged(advss::Area value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(GetSwitcher()->m);
|
||||
_entryData->_maxSizeY = value;
|
||||
_entryData->_checkArea = value;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::CheckAreaChanged(QRect rect)
|
||||
{
|
||||
advss::Area area{rect.topLeft().x(), rect.y(), rect.width(),
|
||||
rect.height()};
|
||||
_checkArea->SetArea(area);
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::ThrottleEnableChanged(int value)
|
||||
@@ -822,83 +718,20 @@ void MacroConditionVideoEdit::ThrottleCountChanged(int value)
|
||||
_entryData->_throttleCount = value / GetSwitcher()->interval;
|
||||
}
|
||||
|
||||
QImage markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern)
|
||||
{
|
||||
auto matchImg = QImageToMat(image);
|
||||
for (int row = 0; row < matchResult.rows - 1; row++) {
|
||||
for (int col = 0; col < matchResult.cols - 1; col++) {
|
||||
if (matchResult.at<float>(row, col) != 0.0) {
|
||||
rectangle(matchImg, {col, row},
|
||||
cv::Point(col + pattern.width(),
|
||||
row + pattern.height()),
|
||||
cv::Scalar(255, 0, 0, 255), 2, 8, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return MatToQImage(matchImg);
|
||||
}
|
||||
|
||||
QImage markObjects(QImage &image, std::vector<cv::Rect> &objects)
|
||||
{
|
||||
auto frame = QImageToMat(image);
|
||||
for (size_t i = 0; i < objects.size(); i++) {
|
||||
rectangle(frame, cv::Point(objects[i].x, objects[i].y),
|
||||
cv::Point(objects[i].x + objects[i].width,
|
||||
objects[i].y + objects[i].height),
|
||||
cv::Scalar(255, 0, 0, 255), 2, 8, 0);
|
||||
}
|
||||
return MatToQImage(frame);
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::ShowMatchClicked()
|
||||
{
|
||||
auto source = obs_weak_source_get_source(_entryData->_videoSource);
|
||||
auto screenshot = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
obs_source_release(source);
|
||||
if (!screenshot->done) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
if (!screenshot->done) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.screenshotFail"));
|
||||
return;
|
||||
}
|
||||
_previewDialog.show();
|
||||
_previewDialog.raise();
|
||||
_previewDialog.activateWindow();
|
||||
_previewDialog.ShowMatch();
|
||||
}
|
||||
|
||||
QImage markedIamge;
|
||||
if (_entryData->_condition == VideoCondition::PATTERN) {
|
||||
cv::Mat result;
|
||||
QImage pattern = _entryData->GetMatchImage();
|
||||
matchPattern(screenshot->image, pattern,
|
||||
_entryData->_patternThreshold, result,
|
||||
_entryData->_useAlphaAsMask);
|
||||
if (countNonZero(result) == 0) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternMatchFail"));
|
||||
return;
|
||||
}
|
||||
markedIamge = markPatterns(result, screenshot->image, pattern);
|
||||
} else if (_entryData->_condition == VideoCondition::OBJECT) {
|
||||
auto objects = matchObject(
|
||||
screenshot->image, _entryData->_objectCascade,
|
||||
_entryData->_scaleFactor, _entryData->_minNeighbors,
|
||||
{_entryData->_minSizeX, _entryData->_minSizeY},
|
||||
{_entryData->_maxSizeX, _entryData->_maxSizeY});
|
||||
if (objects.empty()) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectMatchFail"));
|
||||
return;
|
||||
}
|
||||
markedIamge = markObjects(screenshot->image, objects);
|
||||
}
|
||||
|
||||
QLabel *label = new QLabel;
|
||||
label->setPixmap(QPixmap::fromImage(markedIamge));
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(label);
|
||||
QDialog dialog;
|
||||
dialog.setLayout(layout);
|
||||
dialog.setWindowTitle("Advanced Scene Switcher");
|
||||
dialog.exec();
|
||||
void MacroConditionVideoEdit::SelectAreaClicked()
|
||||
{
|
||||
_previewDialog.show();
|
||||
_previewDialog.raise();
|
||||
_previewDialog.activateWindow();
|
||||
_previewDialog.SelectArea();
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::ModelPathChanged(const QString &text)
|
||||
@@ -949,6 +782,11 @@ bool patternControlIsOptional(VideoCondition cond)
|
||||
cond == VideoCondition::HAS_NOT_CHANGED;
|
||||
}
|
||||
|
||||
bool needsAreaControls(VideoCondition cond)
|
||||
{
|
||||
return cond != VideoCondition::NO_IMAGE;
|
||||
}
|
||||
|
||||
void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
{
|
||||
_imagePath->setVisible(requiresFileInput(_entryData->_condition));
|
||||
@@ -964,14 +802,14 @@ void MacroConditionVideoEdit::SetWidgetVisibility()
|
||||
needsObjectControls(_entryData->_condition));
|
||||
_minNeighborsDescription->setVisible(
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_minSizeControlLayout,
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_maxSizeControlLayout,
|
||||
setLayoutVisible(_sizeLayout,
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_modelPathLayout,
|
||||
needsObjectControls(_entryData->_condition));
|
||||
setLayoutVisible(_throttleControlLayout,
|
||||
needsThrottleControls(_entryData->_condition));
|
||||
setLayoutVisible(_checkAreaControlLayout,
|
||||
needsAreaControls(_entryData->_condition));
|
||||
|
||||
if (_entryData->_condition == VideoCondition::HAS_CHANGED ||
|
||||
_entryData->_condition == VideoCondition::HAS_NOT_CHANGED) {
|
||||
@@ -988,8 +826,7 @@ void MacroConditionVideoEdit::UpdateEntryData()
|
||||
return;
|
||||
}
|
||||
|
||||
_videoSelection->setCurrentText(
|
||||
GetWeakSourceName(_entryData->_videoSource).c_str());
|
||||
_videoSelection->SetVideoSelection(_entryData->_video);
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_imagePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_usePatternForChangedCheck->setChecked(
|
||||
@@ -999,12 +836,13 @@ void MacroConditionVideoEdit::UpdateEntryData()
|
||||
_modelDataPath->SetPath(_entryData->GetModelDataPath().c_str());
|
||||
_objectScaleThreshold->SetDoubleValue(_entryData->_scaleFactor);
|
||||
_minNeighbors->setValue(_entryData->_minNeighbors);
|
||||
_minSizeX->setValue(_entryData->_minSizeX);
|
||||
_minSizeY->setValue(_entryData->_minSizeY);
|
||||
_maxSizeX->setValue(_entryData->_maxSizeX);
|
||||
_maxSizeY->setValue(_entryData->_maxSizeY);
|
||||
_minSize->SetSize(_entryData->_minSize);
|
||||
_maxSize->SetSize(_entryData->_maxSize);
|
||||
_throttleEnable->setChecked(_entryData->_throttleEnabled);
|
||||
_throttleCount->setValue(_entryData->_throttleCount *
|
||||
GetSwitcher()->interval);
|
||||
_checkAreaEnable->setChecked(_entryData->_checkAreaEnable);
|
||||
_checkArea->SetArea(_entryData->_checkArea);
|
||||
UpdatePreviewTooltip();
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
#pragma once
|
||||
#include "opencv-helpers.hpp"
|
||||
#include "threshold-slider.hpp"
|
||||
#include "preview-dialog.hpp"
|
||||
#include "area-selection.hpp"
|
||||
#include "video-selection.hpp"
|
||||
|
||||
#include <macro.hpp>
|
||||
#include <screenshot-helper.hpp>
|
||||
#include <file-selection.hpp>
|
||||
#include <screenshot-helper.hpp>
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <chrono>
|
||||
#undef NO // MacOS macro that can conflict with OpenCV
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <QGridLayout>
|
||||
#include <QLabel>
|
||||
#include <QRect>
|
||||
|
||||
enum class VideoCondition {
|
||||
MATCH,
|
||||
@@ -21,15 +27,6 @@ enum class VideoCondition {
|
||||
OBJECT,
|
||||
};
|
||||
|
||||
struct PatternMatchData {
|
||||
cv::Mat4b rgbaPattern;
|
||||
cv::Mat3b rgbPattern;
|
||||
cv::Mat1b mask;
|
||||
};
|
||||
|
||||
constexpr int minMinNeighbors = 3;
|
||||
constexpr int maxMinNeighbors = 6;
|
||||
|
||||
class MacroConditionVideo : public MacroCondition {
|
||||
public:
|
||||
MacroConditionVideo(Macro *m) : MacroCondition(m) {}
|
||||
@@ -49,7 +46,7 @@ public:
|
||||
std::string GetModelDataPath() { return _modelDataPath; }
|
||||
void ResetLastMatch() { _lastMatchResult = false; }
|
||||
|
||||
OBSWeakSource _videoSource;
|
||||
VideoSelection _video;
|
||||
VideoCondition _condition = VideoCondition::MATCH;
|
||||
std::string _file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
bool _useAlphaAsMask = false;
|
||||
@@ -59,10 +56,11 @@ public:
|
||||
cv::CascadeClassifier _objectCascade;
|
||||
double _scaleFactor = 1.1;
|
||||
int _minNeighbors = minMinNeighbors;
|
||||
int _minSizeX = 0;
|
||||
int _minSizeY = 0;
|
||||
int _maxSizeX = 0;
|
||||
int _maxSizeY = 0;
|
||||
advss::Size _minSize{0, 0};
|
||||
advss::Size _maxSize{0, 0};
|
||||
|
||||
bool _checkAreaEnable = false;
|
||||
advss::Area _checkArea{0, 0, 0, 0};
|
||||
|
||||
bool _throttleEnabled = false;
|
||||
int _throttleCount = 3;
|
||||
@@ -74,7 +72,8 @@ private:
|
||||
bool Compare();
|
||||
bool CheckShouldBeSkipped();
|
||||
|
||||
std::unique_ptr<AdvSSScreenshotObj> _screenshotData = nullptr;
|
||||
bool _getNextScreenshot = true;
|
||||
ScreenshotHelper _screenshotData;
|
||||
QImage _matchImage;
|
||||
std::string _modelDataPath =
|
||||
obs_get_module_data_path(obs_current_module()) +
|
||||
@@ -87,27 +86,6 @@ private:
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class ThresholdSlider : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ThresholdSlider(double min = 0., double max = 1.,
|
||||
const QString &label = "threshold",
|
||||
const QString &description = "", QWidget *parent = 0);
|
||||
void SetDoubleValue(double);
|
||||
public slots:
|
||||
void NotifyValueChanged(int value);
|
||||
signals:
|
||||
void DoubleValueChanged(double value);
|
||||
|
||||
private:
|
||||
void SetDoubleValueText(double);
|
||||
QLabel *_value;
|
||||
QSlider *_slider;
|
||||
double _scale = 100.0;
|
||||
int _precision = 2;
|
||||
};
|
||||
|
||||
class MacroConditionVideoEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
@@ -127,7 +105,7 @@ public:
|
||||
void UpdatePreviewTooltip();
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const QString &text);
|
||||
void VideoSelectionChanged(const VideoSelection &);
|
||||
void ConditionChanged(int cond);
|
||||
void ImagePathChanged(const QString &text);
|
||||
void ImageBrowseButtonClicked();
|
||||
@@ -138,10 +116,13 @@ private slots:
|
||||
void ModelPathChanged(const QString &text);
|
||||
void ObjectScaleThresholdChanged(double);
|
||||
void MinNeighborsChanged(int value);
|
||||
void MinSizeXChanged(int value);
|
||||
void MinSizeYChanged(int value);
|
||||
void MaxSizeXChanged(int value);
|
||||
void MaxSizeYChanged(int value);
|
||||
void MinSizeChanged(advss::Size value);
|
||||
void MaxSizeChanged(advss::Size value);
|
||||
|
||||
void CheckAreaEnableChanged(int value);
|
||||
void CheckAreaChanged(advss::Area);
|
||||
void CheckAreaChanged(QRect area);
|
||||
void SelectAreaClicked();
|
||||
|
||||
void ThrottleEnableChanged(int value);
|
||||
void ThrottleCountChanged(int value);
|
||||
@@ -150,7 +131,7 @@ signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_videoSelection;
|
||||
VideoSelectionWidget *_videoSelection;
|
||||
QComboBox *_condition;
|
||||
|
||||
QCheckBox *_usePatternForChangedCheck;
|
||||
@@ -164,17 +145,21 @@ protected:
|
||||
QHBoxLayout *_neighborsControlLayout;
|
||||
QSpinBox *_minNeighbors;
|
||||
QLabel *_minNeighborsDescription;
|
||||
QHBoxLayout *_minSizeControlLayout;
|
||||
QSpinBox *_minSizeX;
|
||||
QSpinBox *_minSizeY;
|
||||
QHBoxLayout *_maxSizeControlLayout;
|
||||
QSpinBox *_maxSizeX;
|
||||
QSpinBox *_maxSizeY;
|
||||
QHBoxLayout *_sizeLayout;
|
||||
SizeSelection *_minSize;
|
||||
SizeSelection *_maxSize;
|
||||
|
||||
QHBoxLayout *_checkAreaControlLayout;
|
||||
QCheckBox *_checkAreaEnable;
|
||||
AreaSelection *_checkArea;
|
||||
QPushButton *_selectArea;
|
||||
|
||||
QHBoxLayout *_throttleControlLayout;
|
||||
QCheckBox *_throttleEnable;
|
||||
QSpinBox *_throttleCount;
|
||||
|
||||
QPushButton *_showMatch;
|
||||
PreviewDialog _previewDialog;
|
||||
|
||||
std::shared_ptr<MacroConditionVideo> _entryData;
|
||||
|
||||
|
||||
98
src/external-macro-modules/opencv/opencv-helpers.cpp
Normal file
98
src/external-macro-modules/opencv/opencv-helpers.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "opencv-helpers.hpp"
|
||||
|
||||
PatternMatchData createPatternData(QImage &pattern)
|
||||
{
|
||||
PatternMatchData data;
|
||||
if (pattern.isNull()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
data.rgbaPattern = QImageToMat(pattern);
|
||||
std::vector<cv::Mat1b> rgbaChannelsPattern;
|
||||
cv::split(data.rgbaPattern, rgbaChannelsPattern);
|
||||
std::vector<cv::Mat1b> rgbChanlesPattern(
|
||||
rgbaChannelsPattern.begin(), rgbaChannelsPattern.begin() + 3);
|
||||
cv::merge(rgbChanlesPattern, data.rgbPattern);
|
||||
cv::threshold(rgbaChannelsPattern[3], data.mask, 0, 255,
|
||||
cv::THRESH_BINARY);
|
||||
return data;
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, PatternMatchData &patternData, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask)
|
||||
{
|
||||
if (img.isNull() || patternData.rgbaPattern.empty()) {
|
||||
return;
|
||||
}
|
||||
if (img.height() < patternData.rgbaPattern.rows ||
|
||||
img.width() < patternData.rgbaPattern.cols) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto i = QImageToMat(img);
|
||||
|
||||
if (useAlphaAsMask) {
|
||||
std::vector<cv::Mat1b> rgbaChannelsImage;
|
||||
cv::split(i, rgbaChannelsImage);
|
||||
std::vector<cv::Mat1b> rgbChanlesImage(
|
||||
rgbaChannelsImage.begin(),
|
||||
rgbaChannelsImage.begin() + 3);
|
||||
|
||||
cv::Mat3b rgbImage;
|
||||
cv::merge(rgbChanlesImage, rgbImage);
|
||||
|
||||
cv::matchTemplate(rgbImage, patternData.rgbPattern, result,
|
||||
cv::TM_CCORR_NORMED, patternData.mask);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
|
||||
} else {
|
||||
cv::matchTemplate(i, patternData.rgbaPattern, result,
|
||||
cv::TM_CCOEFF_NORMED);
|
||||
cv::threshold(result, result, threshold, 0, cv::THRESH_TOZERO);
|
||||
}
|
||||
}
|
||||
|
||||
void matchPattern(QImage &img, QImage &pattern, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask)
|
||||
{
|
||||
auto data = createPatternData(pattern);
|
||||
matchPattern(img, data, threshold, result, useAlphaAsMask);
|
||||
}
|
||||
|
||||
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
double scaleFactor, int minNeighbors,
|
||||
cv::Size minSize, cv::Size maxSize)
|
||||
{
|
||||
if (img.isNull() || cascade.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto i = QImageToMat(img);
|
||||
cv::Mat frameGray;
|
||||
cv::cvtColor(i, frameGray, cv::COLOR_BGR2GRAY);
|
||||
equalizeHist(frameGray, frameGray);
|
||||
std::vector<cv::Rect> objects;
|
||||
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
|
||||
0, minSize, maxSize);
|
||||
return objects;
|
||||
}
|
||||
|
||||
// Assumption is that QImage uses Format_RGBA8888.
|
||||
// Conversion from: https://github.com/dbzhang800/QtOpenCV
|
||||
cv::Mat QImageToMat(const QImage &img)
|
||||
{
|
||||
if (img.isNull()) {
|
||||
return cv::Mat();
|
||||
}
|
||||
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
|
||||
(uchar *)img.bits(), img.bytesPerLine());
|
||||
}
|
||||
|
||||
QImage MatToQImage(const cv::Mat &mat)
|
||||
{
|
||||
if (mat.empty()) {
|
||||
return QImage();
|
||||
}
|
||||
return QImage(mat.data, mat.cols, mat.rows,
|
||||
QImage::Format::Format_RGBA8888);
|
||||
}
|
||||
24
src/external-macro-modules/opencv/opencv-helpers.hpp
Normal file
24
src/external-macro-modules/opencv/opencv-helpers.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
#include <QImage>
|
||||
#undef NO // MacOS macro that can conflict with OpenCV
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
constexpr int minMinNeighbors = 3;
|
||||
constexpr int maxMinNeighbors = 6;
|
||||
|
||||
struct PatternMatchData {
|
||||
cv::Mat4b rgbaPattern;
|
||||
cv::Mat3b rgbPattern;
|
||||
cv::Mat1b mask;
|
||||
};
|
||||
|
||||
PatternMatchData createPatternData(QImage &pattern);
|
||||
void matchPattern(QImage &img, PatternMatchData &patternData, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask = true);
|
||||
void matchPattern(QImage &img, QImage &pattern, double threshold,
|
||||
cv::Mat &result, bool useAlphaAsMask);
|
||||
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
|
||||
double scaleFactor, int minNeighbors,
|
||||
cv::Size minSize, cv::Size maxSize);
|
||||
cv::Mat QImageToMat(const QImage &img);
|
||||
QImage MatToQImage(const cv::Mat &mat);
|
||||
249
src/external-macro-modules/opencv/preview-dialog.cpp
Normal file
249
src/external-macro-modules/opencv/preview-dialog.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "preview-dialog.hpp"
|
||||
#include "macro-condition-video.hpp"
|
||||
#include "opencv-helpers.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
#include <condition_variable>
|
||||
|
||||
PreviewDialog::PreviewDialog(QWidget *parent,
|
||||
MacroConditionVideo *conditionData,
|
||||
std::mutex *mutex)
|
||||
: QDialog(parent),
|
||||
_conditionData(conditionData),
|
||||
_scrollArea(new QScrollArea),
|
||||
_imageLabel(new QLabel(this)),
|
||||
_rubberBand(new QRubberBand(QRubberBand::Rectangle, this)),
|
||||
_mtx(mutex)
|
||||
{
|
||||
setWindowTitle("Advanced Scene Switcher");
|
||||
_statusLabel = new QLabel(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.showMatch.loading"));
|
||||
resize(640, 480);
|
||||
|
||||
_scrollArea->setBackgroundRole(QPalette::Dark);
|
||||
_scrollArea->setWidget(_imageLabel);
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(_statusLabel);
|
||||
layout->addWidget(_scrollArea);
|
||||
setLayout(layout);
|
||||
|
||||
// This is a workaround to handle random segfaults triggered when using:
|
||||
// QMetaObject::invokeMethod(this, "RedrawImage", Qt::QueuedConnection,
|
||||
// Q_ARG(QImage, image));
|
||||
// from within CheckForMatchLoop().
|
||||
// Even using BlockingQueuedConnection causes deadlocks
|
||||
_timer.setInterval(500);
|
||||
QWidget::connect(&_timer, &QTimer::timeout, this,
|
||||
&PreviewDialog::Resize);
|
||||
_timer.start();
|
||||
}
|
||||
|
||||
void PreviewDialog::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
_selectingArea = true;
|
||||
if (_type == Type::SHOW_MATCH) {
|
||||
return;
|
||||
}
|
||||
_origin = event->pos();
|
||||
_rubberBand->setGeometry(QRect(_origin, QSize()));
|
||||
_rubberBand->show();
|
||||
}
|
||||
|
||||
void PreviewDialog::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (_type == Type::SHOW_MATCH) {
|
||||
return;
|
||||
}
|
||||
_rubberBand->setGeometry(QRect(_origin, event->pos()).normalized());
|
||||
}
|
||||
|
||||
void PreviewDialog::mouseReleaseEvent(QMouseEvent *)
|
||||
{
|
||||
if (_type == Type::SHOW_MATCH) {
|
||||
return;
|
||||
}
|
||||
auto selectionStart =
|
||||
_rubberBand->mapToGlobal(_rubberBand->rect().topLeft());
|
||||
QRect selectionArea(selectionStart, _rubberBand->size());
|
||||
|
||||
auto imageStart =
|
||||
_imageLabel->mapToGlobal(_imageLabel->rect().topLeft());
|
||||
QRect imageArea(imageStart, _imageLabel->size());
|
||||
|
||||
auto intersected = imageArea.intersected(selectionArea);
|
||||
QRect checksize(QPoint(intersected.topLeft() - imageStart),
|
||||
intersected.size());
|
||||
if (checksize.x() >= 0 && checksize.y() >= 0 && checksize.width() > 0 &&
|
||||
checksize.height() > 0) {
|
||||
emit SelectionAreaChanged(checksize);
|
||||
}
|
||||
_selectingArea = false;
|
||||
}
|
||||
|
||||
PreviewDialog::~PreviewDialog()
|
||||
{
|
||||
_stop = true;
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewDialog::ShowMatch()
|
||||
{
|
||||
Start();
|
||||
_rubberBand->hide();
|
||||
_type = Type::SHOW_MATCH;
|
||||
}
|
||||
|
||||
void PreviewDialog::SelectArea()
|
||||
{
|
||||
_selectingArea = false;
|
||||
Start();
|
||||
_type = Type::SELECT_AREA;
|
||||
DrawFrame();
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.selectArea.status"));
|
||||
}
|
||||
|
||||
void PreviewDialog::Resize()
|
||||
{
|
||||
_imageLabel->adjustSize();
|
||||
if (_type == Type::SELECT_AREA && !_selectingArea) {
|
||||
DrawFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewDialog::Start()
|
||||
{
|
||||
if (_thread.joinable()) {
|
||||
return;
|
||||
}
|
||||
if (!_conditionData) {
|
||||
DisplayMessage(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.screenshotFail"));
|
||||
return;
|
||||
}
|
||||
_thread = std::thread(&PreviewDialog::CheckForMatchLoop, this);
|
||||
}
|
||||
|
||||
void PreviewDialog::CheckForMatchLoop()
|
||||
{
|
||||
std::condition_variable cv;
|
||||
while (!_stop) {
|
||||
std::unique_lock<std::mutex> lock(*_mtx);
|
||||
auto source = obs_weak_source_get_source(
|
||||
_conditionData->_video.GetVideo());
|
||||
ScreenshotHelper screenshot(source);
|
||||
obs_source_release(source);
|
||||
cv.wait_for(lock, std::chrono::seconds(1));
|
||||
if (_stop) {
|
||||
return;
|
||||
}
|
||||
if (isHidden()) {
|
||||
continue;
|
||||
}
|
||||
if (!screenshot.done ||
|
||||
!_conditionData->_video.ValidSelection()) {
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.screenshotFail"));
|
||||
_imageLabel->setPixmap(QPixmap());
|
||||
continue;
|
||||
}
|
||||
if (screenshot.image.width() == 0 ||
|
||||
screenshot.image.height() == 0) {
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.screenshotEmpty"));
|
||||
_imageLabel->setPixmap(QPixmap());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_type == Type::SHOW_MATCH) {
|
||||
if (_conditionData->_checkAreaEnable) {
|
||||
screenshot.image = screenshot.image.copy(
|
||||
_conditionData->_checkArea.x,
|
||||
_conditionData->_checkArea.y,
|
||||
_conditionData->_checkArea.width,
|
||||
_conditionData->_checkArea.height);
|
||||
}
|
||||
MarkMatch(screenshot.image);
|
||||
}
|
||||
_imageLabel->setPixmap(QPixmap::fromImage(screenshot.image));
|
||||
}
|
||||
}
|
||||
|
||||
void markPatterns(cv::Mat &matchResult, QImage &image, QImage &pattern)
|
||||
{
|
||||
auto matchImg = QImageToMat(image);
|
||||
for (int row = 0; row < matchResult.rows - 1; row++) {
|
||||
for (int col = 0; col < matchResult.cols - 1; col++) {
|
||||
if (matchResult.at<float>(row, col) != 0.0) {
|
||||
rectangle(matchImg, {col, row},
|
||||
cv::Point(col + pattern.width(),
|
||||
row + pattern.height()),
|
||||
cv::Scalar(255, 0, 0, 255), 2, 8, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void markObjects(QImage &image, std::vector<cv::Rect> &objects)
|
||||
{
|
||||
auto frame = QImageToMat(image);
|
||||
for (size_t i = 0; i < objects.size(); i++) {
|
||||
rectangle(frame, cv::Point(objects[i].x, objects[i].y),
|
||||
cv::Point(objects[i].x + objects[i].width,
|
||||
objects[i].y + objects[i].height),
|
||||
cv::Scalar(255, 0, 0, 255), 2, 8, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewDialog::MarkMatch(QImage &screenshot)
|
||||
{
|
||||
if (_conditionData->_condition == VideoCondition::PATTERN) {
|
||||
cv::Mat result;
|
||||
QImage pattern = _conditionData->GetMatchImage();
|
||||
matchPattern(screenshot, pattern,
|
||||
_conditionData->_patternThreshold, result,
|
||||
_conditionData->_useAlphaAsMask);
|
||||
if (countNonZero(result) == 0) {
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternMatchFail"));
|
||||
} else {
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.patternMatchSuccess"));
|
||||
markPatterns(result, screenshot, pattern);
|
||||
}
|
||||
} else if (_conditionData->_condition == VideoCondition::OBJECT) {
|
||||
auto objects = matchObject(screenshot,
|
||||
_conditionData->_objectCascade,
|
||||
_conditionData->_scaleFactor,
|
||||
_conditionData->_minNeighbors,
|
||||
_conditionData->_minSize.CV(),
|
||||
_conditionData->_maxSize.CV());
|
||||
if (objects.empty()) {
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectMatchFail"));
|
||||
} else {
|
||||
_statusLabel->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.video.objectMatchSuccess"));
|
||||
markObjects(screenshot, objects);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PreviewDialog::DrawFrame()
|
||||
{
|
||||
if (!_conditionData) {
|
||||
return;
|
||||
}
|
||||
auto imageStart =
|
||||
_imageLabel->mapToGlobal(_imageLabel->rect().topLeft());
|
||||
auto windowStart = mapToGlobal(rect().topLeft());
|
||||
_rubberBand->resize(_conditionData->_checkArea.width,
|
||||
_conditionData->_checkArea.height);
|
||||
_rubberBand->move(_conditionData->_checkArea.x +
|
||||
(imageStart.x() - windowStart.x()),
|
||||
_conditionData->_checkArea.y +
|
||||
(imageStart.y() - windowStart.y()));
|
||||
_rubberBand->show();
|
||||
}
|
||||
61
src/external-macro-modules/opencv/preview-dialog.hpp
Normal file
61
src/external-macro-modules/opencv/preview-dialog.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
#include <QTimer>
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QRubberBand>
|
||||
#include <QPoint>
|
||||
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
class MacroConditionVideo;
|
||||
|
||||
class PreviewDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
PreviewDialog(QWidget *parent, MacroConditionVideo *_conditionData,
|
||||
std::mutex *mutex);
|
||||
virtual ~PreviewDialog();
|
||||
void ShowMatch();
|
||||
void SelectArea();
|
||||
|
||||
private slots:
|
||||
void Resize();
|
||||
signals:
|
||||
void SelectionAreaChanged(QRect area);
|
||||
|
||||
private:
|
||||
void Start();
|
||||
void CheckForMatchLoop();
|
||||
void MarkMatch(QImage &screenshot);
|
||||
void DrawFrame();
|
||||
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
|
||||
MacroConditionVideo *_conditionData;
|
||||
QScrollArea *_scrollArea;
|
||||
QLabel *_statusLabel;
|
||||
QLabel *_imageLabel;
|
||||
QTimer _timer;
|
||||
|
||||
QPoint _origin;
|
||||
QRubberBand *_rubberBand = nullptr;
|
||||
std::atomic_bool _selectingArea = {false};
|
||||
|
||||
std::mutex *_mtx;
|
||||
std::thread _thread;
|
||||
std::atomic_bool _stop = {false};
|
||||
|
||||
enum class Type {
|
||||
SHOW_MATCH,
|
||||
SELECT_AREA,
|
||||
};
|
||||
Type _type = Type::SHOW_MATCH;
|
||||
};
|
||||
50
src/external-macro-modules/opencv/threshold-slider.cpp
Normal file
50
src/external-macro-modules/opencv/threshold-slider.cpp
Normal file
@@ -0,0 +1,50 @@
|
||||
#include "threshold-slider.hpp"
|
||||
#include <QHBoxLayout>
|
||||
|
||||
ThresholdSlider::ThresholdSlider(double min, double max, const QString &label,
|
||||
const QString &description, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_slider = new QSlider();
|
||||
_slider->setOrientation(Qt::Horizontal);
|
||||
_slider->setRange(min * _scale, max * _scale);
|
||||
_value = new QLabel();
|
||||
QString labelText = label + QString("0.");
|
||||
for (int i = 0; i < _precision; i++) {
|
||||
labelText.append(QString("0"));
|
||||
}
|
||||
_value->setText(labelText);
|
||||
connect(_slider, SIGNAL(valueChanged(int)), this,
|
||||
SLOT(NotifyValueChanged(int)));
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout();
|
||||
QHBoxLayout *sliderLayout = new QHBoxLayout();
|
||||
sliderLayout->addWidget(_value);
|
||||
sliderLayout->addWidget(_slider);
|
||||
mainLayout->addLayout(sliderLayout);
|
||||
if (!description.isEmpty()) {
|
||||
mainLayout->addWidget(new QLabel(description));
|
||||
}
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
|
||||
void ThresholdSlider::SetDoubleValue(double value)
|
||||
{
|
||||
_slider->setValue(value * _scale);
|
||||
SetDoubleValueText(value);
|
||||
}
|
||||
|
||||
void ThresholdSlider::NotifyValueChanged(int value)
|
||||
{
|
||||
double doubleValue = value / _scale;
|
||||
SetDoubleValueText(doubleValue);
|
||||
emit DoubleValueChanged(doubleValue);
|
||||
}
|
||||
|
||||
void ThresholdSlider::SetDoubleValueText(double value)
|
||||
{
|
||||
QString labelText = _value->text();
|
||||
labelText.chop(_precision + 2); // 2 for the part left of the "."
|
||||
labelText.append(QString::number(value, 'f', _precision));
|
||||
_value->setText(labelText);
|
||||
}
|
||||
25
src/external-macro-modules/opencv/threshold-slider.hpp
Normal file
25
src/external-macro-modules/opencv/threshold-slider.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <QWidget>
|
||||
#include <QSlider>
|
||||
#include <QLabel>
|
||||
|
||||
class ThresholdSlider : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ThresholdSlider(double min = 0., double max = 1.,
|
||||
const QString &label = "threshold",
|
||||
const QString &description = "", QWidget *parent = 0);
|
||||
void SetDoubleValue(double);
|
||||
public slots:
|
||||
void NotifyValueChanged(int value);
|
||||
signals:
|
||||
void DoubleValueChanged(double value);
|
||||
|
||||
private:
|
||||
void SetDoubleValueText(double);
|
||||
QLabel *_value;
|
||||
QSlider *_slider;
|
||||
double _scale = 100.0;
|
||||
int _precision = 2;
|
||||
};
|
||||
122
src/external-macro-modules/opencv/video-selection.cpp
Normal file
122
src/external-macro-modules/opencv/video-selection.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "video-selection.hpp"
|
||||
#include "utility.hpp"
|
||||
|
||||
void VideoSelection::Save(obs_data_t *obj, const char *name,
|
||||
const char *typeName)
|
||||
{
|
||||
obs_data_set_int(obj, typeName, static_cast<int>(_type));
|
||||
|
||||
switch (_type) {
|
||||
case VideoSelectionType::SOURCE:
|
||||
obs_data_set_string(obj, name,
|
||||
GetWeakSourceName(_source).c_str());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSelection::Load(obs_data_t *obj, const char *name,
|
||||
const char *typeName)
|
||||
{
|
||||
_type = static_cast<VideoSelectionType>(
|
||||
obs_data_get_int(obj, typeName));
|
||||
auto target = obs_data_get_string(obj, name);
|
||||
switch (_type) {
|
||||
case VideoSelectionType::SOURCE:
|
||||
_source = GetWeakSourceByName(target);
|
||||
break;
|
||||
case VideoSelectionType::OBS_MAIN:
|
||||
_source = nullptr;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
OBSWeakSource VideoSelection::GetVideo()
|
||||
{
|
||||
if (_type == VideoSelectionType::SOURCE) {
|
||||
return _source;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string VideoSelection::ToString()
|
||||
{
|
||||
switch (_type) {
|
||||
case VideoSelectionType::SOURCE:
|
||||
return GetWeakSourceName(_source);
|
||||
case VideoSelectionType::OBS_MAIN:
|
||||
return obs_module_text("AdvSceneSwitcher.OBSVideoOutput");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
bool VideoSelection::ValidSelection()
|
||||
{
|
||||
return _type == VideoSelectionType::OBS_MAIN || !!_source;
|
||||
}
|
||||
|
||||
VideoSelectionWidget::VideoSelectionWidget(QWidget *parent, bool addOBSVideoOut)
|
||||
: QComboBox(parent)
|
||||
{
|
||||
setDuplicatesEnabled(true);
|
||||
populateVideoSelection(this, addOBSVideoOut);
|
||||
QWidget::connect(this, SIGNAL(currentTextChanged(const QString &)),
|
||||
this, SLOT(SelectionChanged(const QString &)));
|
||||
}
|
||||
|
||||
void VideoSelectionWidget::SetVideoSelection(VideoSelection &t)
|
||||
{
|
||||
int idx;
|
||||
switch (t.GetType()) {
|
||||
case VideoSelectionType::SOURCE:
|
||||
setCurrentText(QString::fromStdString(t.ToString()));
|
||||
break;
|
||||
case VideoSelectionType::OBS_MAIN:
|
||||
idx = findText(QString::fromStdString(obs_module_text(
|
||||
obs_module_text("AdvSceneSwitcher.OBSVideoOutput"))));
|
||||
if (idx != -1) {
|
||||
setCurrentIndex(idx);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
setCurrentIndex(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool isFirstEntry(QComboBox *l, QString name, int idx)
|
||||
{
|
||||
for (int i = 0; i < l->count(); i++) {
|
||||
if (l->itemText(i) == name) {
|
||||
return idx == i;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool VideoSelectionWidget::IsOBSVideoOutSelected(const QString &name)
|
||||
{
|
||||
if (name == QString::fromStdString(obs_module_text(
|
||||
"AdvSceneSwitcher.OBSVideoOutput"))) {
|
||||
return isFirstEntry(this, name, currentIndex());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void VideoSelectionWidget::SelectionChanged(const QString &name)
|
||||
{
|
||||
VideoSelection t;
|
||||
if (IsOBSVideoOutSelected(name)) {
|
||||
t._type = VideoSelectionType::OBS_MAIN;
|
||||
} else {
|
||||
auto source = GetWeakSourceByQString(name);
|
||||
t._type = VideoSelectionType::SOURCE;
|
||||
t._source = source;
|
||||
}
|
||||
emit VideoSelectionChange(t);
|
||||
}
|
||||
43
src/external-macro-modules/opencv/video-selection.hpp
Normal file
43
src/external-macro-modules/opencv/video-selection.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <QComboBox>
|
||||
#include <obs-module.h>
|
||||
#include <obs.hpp>
|
||||
|
||||
enum class VideoSelectionType {
|
||||
SOURCE,
|
||||
OBS_MAIN,
|
||||
};
|
||||
|
||||
class VideoSelection {
|
||||
public:
|
||||
void Save(obs_data_t *obj, const char *name = "video",
|
||||
const char *typeName = "videoType");
|
||||
void Load(obs_data_t *obj, const char *name = "video",
|
||||
const char *typeName = "videoType");
|
||||
|
||||
VideoSelectionType GetType() { return _type; }
|
||||
OBSWeakSource GetVideo();
|
||||
std::string ToString();
|
||||
bool ValidSelection();
|
||||
|
||||
private:
|
||||
OBSWeakSource _source;
|
||||
VideoSelectionType _type = VideoSelectionType::SOURCE;
|
||||
friend class VideoSelectionWidget;
|
||||
};
|
||||
|
||||
class VideoSelectionWidget : public QComboBox {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoSelectionWidget(QWidget *parent, bool addOBSVideoOut = true);
|
||||
void SetVideoSelection(VideoSelection &);
|
||||
signals:
|
||||
void VideoSelectionChange(const VideoSelection &);
|
||||
|
||||
private slots:
|
||||
void SelectionChanged(const QString &name);
|
||||
|
||||
private:
|
||||
bool IsOBSVideoOutSelected(const QString &name);
|
||||
};
|
||||
@@ -166,6 +166,9 @@ MacroConditionOpenVREdit::MacroConditionOpenVREdit(
|
||||
controlsLayout->addWidget(_maxY, 1, 1);
|
||||
controlsLayout->addWidget(_maxZ, 1, 2);
|
||||
QWidget *controls = new QWidget;
|
||||
controls->setObjectName("openVRControls");
|
||||
controls->setStyleSheet(
|
||||
"#openVRControls { background-color: rgba(0,0,0,0); }");
|
||||
controls->setLayout(controlsLayout);
|
||||
controls->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
|
||||
101
src/general.cpp
101
src/general.cpp
@@ -192,6 +192,62 @@ void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
|
||||
switcher->disableHints = state;
|
||||
}
|
||||
|
||||
bool isLegacyTab(const QString &name)
|
||||
{
|
||||
return name == obs_module_text(
|
||||
"AdvSceneSwitcher.transitionTab.title") ||
|
||||
name == obs_module_text(
|
||||
"AdvSceneSwitcher.windowTitleTab.title") ||
|
||||
name == obs_module_text(
|
||||
"AdvSceneSwitcher.executableTab.title") ||
|
||||
name == obs_module_text(
|
||||
"AdvSceneSwitcher.screenRegionTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.mediaTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.fileTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.randomTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.timeTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.idleTab.title") ||
|
||||
name == obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.audioTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.videoTab.title") ||
|
||||
name == obs_module_text("AdvSceneSwitcher.pauseTab.title") ||
|
||||
name == obs_module_text(
|
||||
"AdvSceneSwitcher.sceneTriggerTab.title");
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_hideLegacyTabs_stateChanged(int state)
|
||||
{
|
||||
switcher->hideLegacyTabs = state;
|
||||
|
||||
for (int idx = 0; idx < ui->tabWidget->count(); idx++) {
|
||||
if (isLegacyTab(ui->tabWidget->tabText(idx))) {
|
||||
#if defined(_WIN32) || defined(__APPLE__)
|
||||
ui->tabWidget->setTabVisible(idx, !state);
|
||||
#else
|
||||
// TODO: Switch to setTabVisible() once QT 5.15 is more wide spread
|
||||
ui->tabWidget->setTabEnabled(idx, !state);
|
||||
ui->tabWidget->setStyleSheet(
|
||||
"QTabBar::tab::disabled {width: 0; height: 0; margin: 0; padding: 0; border: none;} ");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setDeprecationWarnings()
|
||||
{
|
||||
QString toolTip =
|
||||
switcher->disableHints
|
||||
? ""
|
||||
: obs_module_text(
|
||||
"AdvSceneSwitcher.deprecatedTabWarning");
|
||||
for (int idx = 0; idx < ui->tabWidget->count(); idx++) {
|
||||
if (isLegacyTab(ui->tabWidget->tabText(idx))) {
|
||||
ui->tabWidget->widget(idx)->setToolTip(toolTip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString getDefaultSaveLocation()
|
||||
{
|
||||
QString desktopPath = QStandardPaths::writableLocation(
|
||||
@@ -399,6 +455,13 @@ void AdvSceneSwitcher::setTabOrder()
|
||||
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setCurrentTab()
|
||||
{
|
||||
if (switcher->lastOpenedTab >= 0) {
|
||||
ui->tabWidget->setCurrentIndex(switcher->lastOpenedTab);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::restoreWindowGeo()
|
||||
{
|
||||
if (switcher->saveWindowGeo && windowPosValid(switcher->windowPos)) {
|
||||
@@ -410,6 +473,7 @@ void AdvSceneSwitcher::restoreWindowGeo()
|
||||
void AdvSceneSwitcher::checkFirstTimeSetup()
|
||||
{
|
||||
if (switcher->firstBoot && !switcher->disableHints) {
|
||||
switcher->firstBoot = false;
|
||||
DisplayMessage(
|
||||
obs_module_text("AdvSceneSwitcher.firstBootMessage"));
|
||||
}
|
||||
@@ -456,6 +520,9 @@ void SwitcherData::loadSettings(obs_data_t *obj)
|
||||
loadSceneTriggers(obj);
|
||||
loadGeneralSettings(obj);
|
||||
loadHotkeys(obj);
|
||||
|
||||
// Reset on startup and scene collection change
|
||||
switcher->lastOpenedTab = -1;
|
||||
}
|
||||
|
||||
void SwitcherData::saveSettings(obs_data_t *obj)
|
||||
@@ -511,6 +578,7 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
|
||||
obs_data_set_bool(obj, "showSystemTrayNotifications",
|
||||
showSystemTrayNotifications);
|
||||
obs_data_set_bool(obj, "disableHints", disableHints);
|
||||
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
|
||||
|
||||
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
|
||||
obs_data_set_int(obj, "priority1", functionNamesByPriority[1]);
|
||||
@@ -584,6 +652,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
showSystemTrayNotifications =
|
||||
obs_data_get_bool(obj, "showSystemTrayNotifications");
|
||||
disableHints = obs_data_get_bool(obj, "disableHints");
|
||||
obs_data_set_default_bool(obj, "hideLegacyTabs", true);
|
||||
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
|
||||
|
||||
obs_data_set_default_int(obj, "priority0", default_priority_0);
|
||||
obs_data_set_default_int(obj, "priority1", default_priority_1);
|
||||
@@ -626,21 +696,21 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
|
||||
obs_data_set_default_int(obj, "generalTabPos", 0);
|
||||
obs_data_set_default_int(obj, "macroTabPos", 1);
|
||||
obs_data_set_default_int(obj, "transitionTabPos", 2);
|
||||
obs_data_set_default_int(obj, "pauseTabPos", 3);
|
||||
obs_data_set_default_int(obj, "titleTabPos", 4);
|
||||
obs_data_set_default_int(obj, "exeTabPos", 5);
|
||||
obs_data_set_default_int(obj, "regionTabPos", 6);
|
||||
obs_data_set_default_int(obj, "mediaTabPos", 7);
|
||||
obs_data_set_default_int(obj, "fileTabPos", 8);
|
||||
obs_data_set_default_int(obj, "randomTabPos", 9);
|
||||
obs_data_set_default_int(obj, "timeTabPos", 10);
|
||||
obs_data_set_default_int(obj, "idleTabPos", 11);
|
||||
obs_data_set_default_int(obj, "sequenceTabPos", 12);
|
||||
obs_data_set_default_int(obj, "audioTabPos", 13);
|
||||
obs_data_set_default_int(obj, "videoTabPos", 14);
|
||||
obs_data_set_default_int(obj, "networkTabPos", 15);
|
||||
obs_data_set_default_int(obj, "sceneGroupTabPos", 16);
|
||||
obs_data_set_default_int(obj, "networkTabPos", 13);
|
||||
obs_data_set_default_int(obj, "sceneGroupTabPos", 14);
|
||||
obs_data_set_default_int(obj, "transitionTabPos", 15);
|
||||
obs_data_set_default_int(obj, "pauseTabPos", 16);
|
||||
obs_data_set_default_int(obj, "titleTabPos", 2);
|
||||
obs_data_set_default_int(obj, "exeTabPos", 3);
|
||||
obs_data_set_default_int(obj, "regionTabPos", 4);
|
||||
obs_data_set_default_int(obj, "mediaTabPos", 5);
|
||||
obs_data_set_default_int(obj, "fileTabPos", 6);
|
||||
obs_data_set_default_int(obj, "randomTabPos", 7);
|
||||
obs_data_set_default_int(obj, "timeTabPos", 8);
|
||||
obs_data_set_default_int(obj, "idleTabPos", 9);
|
||||
obs_data_set_default_int(obj, "sequenceTabPos", 10);
|
||||
obs_data_set_default_int(obj, "audioTabPos", 11);
|
||||
obs_data_set_default_int(obj, "videoTabPos", 12);
|
||||
obs_data_set_default_int(obj, "triggerTabPos", 17);
|
||||
|
||||
tabOrder.clear();
|
||||
@@ -797,6 +867,7 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
ui->showTrayNotifications->setChecked(
|
||||
switcher->showSystemTrayNotifications);
|
||||
ui->uiHintsDisable->setChecked(switcher->disableHints);
|
||||
ui->hideLegacyTabs->setChecked(switcher->hideLegacyTabs);
|
||||
|
||||
for (int p : switcher->functionNamesByPriority) {
|
||||
std::string s = "";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#else
|
||||
#include "ui_advanced-scene-switcher.h"
|
||||
#endif
|
||||
#include "macro-segment-list.hpp"
|
||||
#include "switcher-data-structs.hpp"
|
||||
#include "platform-funcs.hpp"
|
||||
|
||||
@@ -32,6 +33,8 @@ public:
|
||||
AdvSceneSwitcher(QWidget *parent);
|
||||
~AdvSceneSwitcher();
|
||||
|
||||
static AdvSceneSwitcher *window;
|
||||
|
||||
void reject() override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
@@ -46,15 +49,16 @@ public:
|
||||
void OpenSequenceExtendEdit(SequenceWidget *sw);
|
||||
void SetEditSceneGroup(SceneGroup &sg);
|
||||
|
||||
bool addNewMacro(std::string &name);
|
||||
bool addNewMacro(std::string &name, std::string format = "");
|
||||
Macro *getSelectedMacro();
|
||||
void SetEditMacro(Macro &m);
|
||||
void SetMacroEditAreaDisabled(bool);
|
||||
void HighlightAction(int idx);
|
||||
void HighlightCondition(int idx);
|
||||
void PopulateMacroActions(Macro &m, uint32_t afterIdx = 0);
|
||||
void PopulateMacroConditions(Macro &m, uint32_t afterIdx = 0);
|
||||
void ConnectControlSignals(MacroActionEdit *);
|
||||
void ConnectControlSignals(MacroConditionEdit *);
|
||||
void SetActionData(Macro &m);
|
||||
void SetConditionData(Macro &m);
|
||||
void SwapActions(Macro *m, int pos1, int pos2);
|
||||
void SwapConditions(Macro *m, int pos1, int pos2);
|
||||
|
||||
@@ -77,7 +81,9 @@ public:
|
||||
void setupVideoTab();
|
||||
void setupNetworkTab();
|
||||
void setupMacroTab();
|
||||
void setDeprecationWarnings();
|
||||
void setTabOrder();
|
||||
void setCurrentTab();
|
||||
void restoreWindowGeo();
|
||||
void checkFirstTimeSetup();
|
||||
|
||||
@@ -85,6 +91,9 @@ signals:
|
||||
void MacroAdded(const QString &name);
|
||||
void MacroRemoved(const QString &name);
|
||||
void MacroRenamed(const QString &oldName, const QString newName);
|
||||
void HighlightMacrosChanged(bool value);
|
||||
void HighlightActionsChanged(bool value);
|
||||
void HighlightConditionsChanged(bool value);
|
||||
void SceneGroupAdded(const QString &name);
|
||||
void SceneGroupRemoved(const QString &name);
|
||||
void SceneGroupRenamed(const QString &oldName, const QString newName);
|
||||
@@ -117,11 +126,17 @@ public slots:
|
||||
void on_runMacroInParallel_stateChanged(int value);
|
||||
void on_runMacroOnChange_stateChanged(int value);
|
||||
void on_macros_currentRowChanged(int idx);
|
||||
void on_macros_itemChanged(QListWidgetItem *);
|
||||
void on_conditionAdd_clicked();
|
||||
void on_conditionRemove_clicked();
|
||||
void on_conditionUp_clicked();
|
||||
void on_conditionDown_clicked();
|
||||
void on_actionAdd_clicked();
|
||||
void on_actionRemove_clicked();
|
||||
void on_actionUp_clicked();
|
||||
void on_actionDown_clicked();
|
||||
void UpMacroSegementHotkey();
|
||||
void DownMacroSegementHotkey();
|
||||
void DeleteMacroSegementHotkey();
|
||||
void ShowMacroContextMenu(const QPoint &);
|
||||
void ShowMacroActionsContextMenu(const QPoint &);
|
||||
void ShowMacroConditionsContextMenu(const QPoint &);
|
||||
@@ -130,14 +145,28 @@ public slots:
|
||||
void ExpandAllConditions();
|
||||
void CollapseAllActions();
|
||||
void CollapseAllConditions();
|
||||
void MinimizeActions();
|
||||
void MinimizeConditions();
|
||||
void MacroActionSelectionChanged(int idx);
|
||||
void MacroActionReorder(int to, int target);
|
||||
void AddMacroAction(int idx);
|
||||
void RemoveMacroAction(int idx);
|
||||
void MoveMacroActionUp(int idx);
|
||||
void MoveMacroActionDown(int idx);
|
||||
void MacroConditionSelectionChanged(int idx);
|
||||
void MacroConditionReorder(int to, int target);
|
||||
void AddMacroCondition(int idx);
|
||||
void RemoveMacroCondition(int idx);
|
||||
void MoveMacroConditionUp(int idx);
|
||||
void MoveMacroConditionDown(int idx);
|
||||
void FadeOutActionControls();
|
||||
void FadeOutConditionControls();
|
||||
void ResetOpacityActionControls();
|
||||
void ResetOpacityConditionControls();
|
||||
void HighlightControls();
|
||||
void MacroDragDropReorder(QModelIndex, int, int, QModelIndex, int);
|
||||
void HighlightOnChange();
|
||||
void on_macroProperties_clicked();
|
||||
|
||||
void on_screenRegionSwitches_currentRowChanged(int idx);
|
||||
void on_showFrame_clicked();
|
||||
@@ -168,6 +197,7 @@ public slots:
|
||||
void on_saveWindowGeo_stateChanged(int state);
|
||||
void on_showTrayNotifications_stateChanged(int state);
|
||||
void on_uiHintsDisable_stateChanged(int state);
|
||||
void on_hideLegacyTabs_stateChanged(int state);
|
||||
|
||||
void on_exportSettings_clicked();
|
||||
void on_importSettings_clicked();
|
||||
@@ -271,6 +301,18 @@ public slots:
|
||||
void on_close_clicked();
|
||||
|
||||
private:
|
||||
bool MacroTabIsInFocus();
|
||||
|
||||
MacroSegmentList *conditionsList = nullptr;
|
||||
MacroSegmentList *actionsList = nullptr;
|
||||
|
||||
enum class MacroSection {
|
||||
CONDITIONS,
|
||||
ACTIONS,
|
||||
};
|
||||
MacroSection lastInteracted = MacroSection::CONDITIONS;
|
||||
int currentConditionIdx = -1;
|
||||
int currentActionIdx = -1;
|
||||
};
|
||||
|
||||
/******************************************************************************
|
||||
@@ -282,7 +324,7 @@ void setNextTransition(const sceneSwitchInfo &ssi, obs_source_t *currentSource,
|
||||
void overwriteTransitionOverride(const sceneSwitchInfo &ssi,
|
||||
transitionData &td);
|
||||
void restoreTransitionOverride(obs_source_t *scene, const transitionData &td);
|
||||
void switchScene(const sceneSwitchInfo &ssi);
|
||||
void switchScene(const sceneSwitchInfo &ssi, bool force = false);
|
||||
void switchPreviewScene(const OBSWeakSource &ws);
|
||||
|
||||
/******************************************************************************
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
|
||||
@@ -13,6 +14,11 @@ enum class AudioAction {
|
||||
MASTER_VOLUME,
|
||||
};
|
||||
|
||||
enum class FadeType {
|
||||
DURATION,
|
||||
RATE,
|
||||
};
|
||||
|
||||
class MacroActionAudio : public MacroAction {
|
||||
public:
|
||||
MacroActionAudio(Macro *m) : MacroAction(m) {}
|
||||
@@ -29,11 +35,23 @@ public:
|
||||
|
||||
OBSWeakSource _audioSource;
|
||||
AudioAction _action = AudioAction::MUTE;
|
||||
FadeType _fadeType = FadeType::DURATION;
|
||||
int _volume = 0;
|
||||
bool _fade = false;
|
||||
Duration _duration;
|
||||
double _rate = 100.;
|
||||
bool _wait = false;
|
||||
bool _abortActiveFade = false;
|
||||
|
||||
private:
|
||||
void StartFade();
|
||||
void FadeVolume();
|
||||
void SetVolume(float vol);
|
||||
float GetVolume();
|
||||
void SetFadeActive(bool value);
|
||||
bool FadeActive();
|
||||
std::atomic_int *GetFadeIdPtr();
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
@@ -45,7 +63,6 @@ public:
|
||||
MacroActionAudioEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroActionAudio> entryData = nullptr);
|
||||
void SetWidgetVisibility();
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> action)
|
||||
@@ -61,18 +78,29 @@ private slots:
|
||||
void VolumeChanged(int value);
|
||||
void FadeChanged(int value);
|
||||
void DurationChanged(double seconds);
|
||||
void RateChanged(double value);
|
||||
void WaitChanged(int value);
|
||||
void AbortActiveFadeChanged(int value);
|
||||
void FadeTypeChanged(int value);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_audioSources;
|
||||
QComboBox *_actions;
|
||||
QComboBox *_fadeTypes;
|
||||
QSpinBox *_volumePercent;
|
||||
QCheckBox *_fade;
|
||||
DurationSelection *_duration;
|
||||
QHBoxLayout *_fadeLayout;
|
||||
QDoubleSpinBox *_rate;
|
||||
QCheckBox *_wait;
|
||||
QCheckBox *_abortActiveFade;
|
||||
QHBoxLayout *_fadeTypeLayout;
|
||||
QVBoxLayout *_fadeOptionsLayout;
|
||||
std::shared_ptr<MacroActionAudio> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -34,6 +34,7 @@ public:
|
||||
std::shared_ptr<MacroAction> * = nullptr,
|
||||
const std::string &id = "scene_switch");
|
||||
void UpdateEntryData(const std::string &id);
|
||||
void SetEntryData(std::shared_ptr<MacroAction> *);
|
||||
|
||||
private slots:
|
||||
void ActionSelectionChanged(const QString &text);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#pragma once
|
||||
#include <QSpinBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
enum class FileAction {
|
||||
WRITE,
|
||||
@@ -57,7 +58,7 @@ signals:
|
||||
|
||||
protected:
|
||||
FileSelection *_filePath;
|
||||
QPlainTextEdit *_text;
|
||||
ResizingPlainTextEdit *_text;
|
||||
QComboBox *_actions;
|
||||
std::shared_ptr<MacroActionFile> _entryData;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
#include <QSpinBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
|
||||
enum class FilterAction {
|
||||
ENABLE,
|
||||
@@ -63,7 +64,7 @@ protected:
|
||||
QComboBox *_filters;
|
||||
QComboBox *_actions;
|
||||
QPushButton *_getSettings;
|
||||
QPlainTextEdit *_settings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
std::shared_ptr<MacroActionFilter> _entryData;
|
||||
|
||||
private:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QHBoxLayout>
|
||||
@@ -11,6 +12,7 @@ enum class MediaAction {
|
||||
RESTART,
|
||||
NEXT,
|
||||
PREVIOUS,
|
||||
SEEK,
|
||||
};
|
||||
|
||||
class MacroActionMedia : public MacroAction {
|
||||
@@ -29,6 +31,7 @@ public:
|
||||
|
||||
OBSWeakSource _mediaSource;
|
||||
MediaAction _action = MediaAction::PLAY;
|
||||
Duration _seek;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
@@ -54,15 +57,20 @@ public:
|
||||
private slots:
|
||||
void SourceChanged(const QString &text);
|
||||
void ActionChanged(int value);
|
||||
void DurationChanged(double value);
|
||||
void DurationUnitChanged(DurationUnit unit);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_mediaSources;
|
||||
QComboBox *_actions;
|
||||
DurationSelection *_seek;
|
||||
std::shared_ptr<MacroActionMedia> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
QHBoxLayout *_mainLayout;
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ private slots:
|
||||
void RemoveArg();
|
||||
void ArgUp();
|
||||
void ArgDown();
|
||||
void ArgItemClicked(QListWidgetItem *);
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
class MacroActionSceneTransform : public MacroAction {
|
||||
public:
|
||||
@@ -61,7 +61,7 @@ protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QPushButton *_getSettings;
|
||||
QPlainTextEdit *_settings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
std::shared_ptr<MacroActionSceneTransform> _entryData;
|
||||
|
||||
private:
|
||||
|
||||
@@ -53,6 +53,7 @@ private slots:
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
void MacroItemClicked(QListWidgetItem *);
|
||||
void ContinueFromClicked();
|
||||
void RestartChanged(int state);
|
||||
void UpdateStatusLine();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
enum class SourceAction {
|
||||
ENABLE,
|
||||
@@ -63,7 +63,7 @@ protected:
|
||||
QComboBox *_sources;
|
||||
QComboBox *_actions;
|
||||
QPushButton *_getSettings;
|
||||
QPlainTextEdit *_settings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QLabel *_warning;
|
||||
std::shared_ptr<MacroActionSource> _entryData;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
#pragma once
|
||||
#include "macro-action-edit.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QComboBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <chrono>
|
||||
|
||||
enum class StreamAction {
|
||||
STOP,
|
||||
@@ -27,9 +27,8 @@ public:
|
||||
StreamAction _action = StreamAction::STOP;
|
||||
|
||||
private:
|
||||
// Acts as a safeguard for misconfigured streaming setups leading to an
|
||||
// endless error spam.
|
||||
Duration _retryCooldown;
|
||||
bool CooldownDurationReached();
|
||||
static std::chrono::high_resolution_clock::time_point s_lastAttempt;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
|
||||
@@ -13,6 +13,17 @@ enum class DateCondition {
|
||||
BETWEEN,
|
||||
};
|
||||
|
||||
enum class DayOfWeekSelection {
|
||||
ANY = 0,
|
||||
MONDAY,
|
||||
TUESDAY,
|
||||
WEDNESDAY,
|
||||
THURSDAY,
|
||||
FRIDAY,
|
||||
SATURDAY,
|
||||
SUNDAY,
|
||||
};
|
||||
|
||||
class MacroConditionDate : public MacroCondition {
|
||||
public:
|
||||
MacroConditionDate(Macro *m) : MacroCondition(m) {}
|
||||
@@ -26,15 +37,20 @@ public:
|
||||
return std::make_shared<MacroConditionDate>(m);
|
||||
}
|
||||
|
||||
QDateTime _dateTime;
|
||||
QDateTime _dateTime2;
|
||||
DayOfWeekSelection _dayOfWeek = DayOfWeekSelection::ANY;
|
||||
QDateTime _dateTime = QDateTime::currentDateTime();
|
||||
QDateTime _dateTime2 = QDateTime::currentDateTime();
|
||||
bool _ignoreDate = false;
|
||||
bool _ignoreTime = false;
|
||||
bool _repeat = false;
|
||||
Duration _duration;
|
||||
DateCondition _condition = DateCondition::AT;
|
||||
bool _dayOfWeekCheck = false;
|
||||
|
||||
private:
|
||||
bool CheckDayOfWeek(int64_t);
|
||||
bool CheckRegularDate(int64_t);
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
@@ -56,28 +72,45 @@ public:
|
||||
}
|
||||
|
||||
private slots:
|
||||
void DayOfWeekChanged(int day);
|
||||
void ConditionChanged(int cond);
|
||||
void DateTimeChanged(const QDateTime &datetime);
|
||||
void DateTime2Changed(const QDateTime &datetime);
|
||||
void DateChanged(const QDate &date);
|
||||
void TimeChanged(const QTime &time);
|
||||
void Date2Changed(const QDate &date);
|
||||
void Time2Changed(const QTime &time);
|
||||
void IgnoreDateChanged(int state);
|
||||
void IgnoreTimeChanged(int state);
|
||||
void RepeatChanged(int state);
|
||||
void DurationChanged(double seconds);
|
||||
void DurationUnitChanged(DurationUnit unit);
|
||||
void AdvancedSettingsToggleClicked();
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_dayOfWeek;
|
||||
QTimeEdit *_weekTime;
|
||||
|
||||
QComboBox *_condition;
|
||||
QDateTimeEdit *_dateTime;
|
||||
QDateTimeEdit *_dateTime2;
|
||||
QDateEdit *_date;
|
||||
QTimeEdit *_time;
|
||||
QLabel *_separator;
|
||||
QDateEdit *_date2;
|
||||
QTimeEdit *_time2;
|
||||
QCheckBox *_ignoreDate;
|
||||
QCheckBox *_ignoreTime;
|
||||
QCheckBox *_repeat;
|
||||
DurationSelection *_duration;
|
||||
|
||||
QPushButton *_advancedSettingsTooggle;
|
||||
QHBoxLayout *_simpleLayout;
|
||||
QHBoxLayout *_advancedLayout;
|
||||
QHBoxLayout *_repeatLayout;
|
||||
|
||||
std::shared_ptr<MacroConditionDate> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetStatus();
|
||||
void ShowSecondDateSelection(bool visible);
|
||||
bool _loading = true;
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
bool IsRootNode();
|
||||
void SetRootNode(bool);
|
||||
void UpdateEntryData(const std::string &id);
|
||||
void SetEntryData(std::shared_ptr<MacroCondition> *);
|
||||
|
||||
private slots:
|
||||
void LogicSelectionChanged(int idx);
|
||||
@@ -47,6 +48,7 @@ private slots:
|
||||
void DurationUnitChanged(DurationUnit unit);
|
||||
|
||||
private:
|
||||
void SetLogicSelection();
|
||||
MacroSegment *Data();
|
||||
|
||||
QComboBox *_logicSelection;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "file-selection.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QDateTime>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QCheckBox>
|
||||
|
||||
enum class FileType {
|
||||
@@ -75,7 +75,7 @@ signals:
|
||||
protected:
|
||||
QComboBox *_fileType;
|
||||
FileSelection *_filePath;
|
||||
QPlainTextEdit *_matchText;
|
||||
ResizingPlainTextEdit *_matchText;
|
||||
QCheckBox *_useRegex;
|
||||
QCheckBox *_checkModificationDate;
|
||||
QCheckBox *_checkFileContent;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QCheckBox>
|
||||
|
||||
enum class FilterCondition {
|
||||
@@ -67,7 +67,7 @@ protected:
|
||||
QComboBox *_filters;
|
||||
QComboBox *_conditions;
|
||||
QPushButton *_getSettings;
|
||||
QPlainTextEdit *_settings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QCheckBox *_regex;
|
||||
|
||||
std::shared_ptr<MacroConditionFilter> _entryData;
|
||||
|
||||
@@ -31,7 +31,7 @@ enum class MediaState {
|
||||
// Just a marker
|
||||
LAST_OBS_MEDIA_STATE,
|
||||
// states added for use in the plugin
|
||||
PLAYED_TO_END = custom_media_states_offset,
|
||||
PLAYLIST_ENDED = custom_media_states_offset,
|
||||
ANY,
|
||||
};
|
||||
|
||||
@@ -58,6 +58,7 @@ public:
|
||||
void ResetSignalHandler();
|
||||
static void MediaStopped(void *data, calldata_t *);
|
||||
static void MediaEnded(void *data, calldata_t *);
|
||||
static void MediaNext(void *data, calldata_t *);
|
||||
|
||||
MediaSourceType _sourceType = MediaSourceType::SOURCE;
|
||||
SceneSelection _scene;
|
||||
@@ -70,17 +71,19 @@ public:
|
||||
bool _onlyMatchonChagne = false;
|
||||
|
||||
private:
|
||||
bool CheckTime();
|
||||
bool CheckState();
|
||||
bool CheckPlaylistEnd(const obs_media_state);
|
||||
bool CheckMediaMatch();
|
||||
|
||||
bool _stopped = false;
|
||||
bool _ended = false;
|
||||
bool _next = false;
|
||||
// TODO: Remove _alreadyMatched as it does not make much sense when
|
||||
// time restrictions for macro conditions are available.
|
||||
// Trigger scene change only once even if media state might trigger repeatedly
|
||||
bool _alreadyMatched = false;
|
||||
// Workaround to enable use of "ended" to specify end of VLC playlist
|
||||
bool _previousStateEnded = false;
|
||||
bool _playedToEnd = false;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
|
||||
118
src/headers/macro-condition-obs-stats.hpp
Normal file
118
src/headers/macro-condition-obs-stats.hpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include <QWidget>
|
||||
#include <QComboBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <util/platform.h>
|
||||
|
||||
enum class StatsType {
|
||||
FPS,
|
||||
CPU_USAGE,
|
||||
DISK_USAGE, // not implemented
|
||||
MEM_USAGE,
|
||||
AVG_FRAMETIME,
|
||||
RENDER_LAG,
|
||||
ENCODE_LAG,
|
||||
STREAM_DROPPED_FRAMES,
|
||||
STREAM_BITRATE,
|
||||
STREAM_MB_SENT,
|
||||
RECORDING_DROPPED_FRAMES, // not sure if this makes sense
|
||||
RECORDING_BITRATE,
|
||||
RECORDING_MB_SENT,
|
||||
};
|
||||
|
||||
enum class StatsCondition {
|
||||
ABOVE,
|
||||
EQUALS,
|
||||
BELOW,
|
||||
};
|
||||
|
||||
class MacroConditionStats : public MacroCondition {
|
||||
public:
|
||||
MacroConditionStats(Macro *m);
|
||||
~MacroConditionStats();
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj);
|
||||
bool Load(obs_data_t *obj);
|
||||
std::string GetShortDesc();
|
||||
std::string GetId() { return id; };
|
||||
static std::shared_ptr<MacroCondition> Create(Macro *m)
|
||||
{
|
||||
return std::make_shared<MacroConditionStats>(m);
|
||||
}
|
||||
|
||||
double _value = 0;
|
||||
StatsType _type = StatsType::FPS;
|
||||
StatsCondition _condition = StatsCondition::ABOVE;
|
||||
|
||||
private:
|
||||
bool CheckFPS();
|
||||
bool CheckCPU();
|
||||
bool CheckMemory();
|
||||
bool CheckAvgFrametime();
|
||||
bool CheckRenderLag();
|
||||
bool CheckEncodeLag();
|
||||
bool CheckStreamDroppedFrames();
|
||||
bool CheckStreamBitrate();
|
||||
bool CheckStreamMBSent();
|
||||
bool CheckRecordingDroppedFrames();
|
||||
bool CheckRecordingBitrate();
|
||||
bool CheckRecordingMBSent();
|
||||
|
||||
os_cpu_usage_info_t *_cpu_info = nullptr;
|
||||
uint32_t _first_encoded = 0xFFFFFFFF;
|
||||
uint32_t _first_skipped = 0xFFFFFFFF;
|
||||
uint32_t _first_rendered = 0xFFFFFFFF;
|
||||
uint32_t _first_lagged = 0xFFFFFFFF;
|
||||
struct OutputInfo {
|
||||
void Update(obs_output_t *output);
|
||||
|
||||
uint64_t lastBytesSent = 0;
|
||||
uint64_t lastBytesSentTime = 0;
|
||||
int first_total = 0;
|
||||
int first_dropped = 0;
|
||||
double dropped_relative = 0.0;
|
||||
long double kbps = 0.0l;
|
||||
};
|
||||
OutputInfo _streamInfo;
|
||||
OutputInfo _recordingInfo;
|
||||
|
||||
static bool _registered;
|
||||
static const std::string id;
|
||||
};
|
||||
|
||||
class MacroConditionStatsEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroConditionStatsEdit(
|
||||
QWidget *parent,
|
||||
std::shared_ptr<MacroConditionStats> cond = nullptr);
|
||||
void UpdateEntryData();
|
||||
static QWidget *Create(QWidget *parent,
|
||||
std::shared_ptr<MacroCondition> cond)
|
||||
{
|
||||
return new MacroConditionStatsEdit(
|
||||
parent,
|
||||
std::dynamic_pointer_cast<MacroConditionStats>(cond));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void ValueChanged(double value);
|
||||
void StatsTypeChanged(int type);
|
||||
void ConditionChanged(int cond);
|
||||
|
||||
signals:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
|
||||
protected:
|
||||
QComboBox *_stats;
|
||||
QComboBox *_condition;
|
||||
QDoubleSpinBox *_value;
|
||||
std::shared_ptr<MacroConditionStats> _entryData;
|
||||
|
||||
private:
|
||||
void SetWidgetVisibility();
|
||||
|
||||
bool _loading = true;
|
||||
};
|
||||
@@ -4,13 +4,15 @@
|
||||
#include <QComboBox>
|
||||
|
||||
enum class PluginStateCondition {
|
||||
SCENESWITCHED,
|
||||
SCENE_SWITCHED,
|
||||
RUNNING,
|
||||
SHUTDOWN,
|
||||
};
|
||||
|
||||
class MacroConditionPluginState : public MacroCondition {
|
||||
public:
|
||||
MacroConditionPluginState(Macro *m) : MacroCondition(m) {}
|
||||
~MacroConditionPluginState();
|
||||
bool CheckCondition();
|
||||
bool Save(obs_data_t *obj);
|
||||
bool Load(obs_data_t *obj);
|
||||
@@ -20,7 +22,7 @@ public:
|
||||
return std::make_shared<MacroConditionPluginState>(m);
|
||||
}
|
||||
|
||||
PluginStateCondition _condition = PluginStateCondition::SCENESWITCHED;
|
||||
PluginStateCondition _condition = PluginStateCondition::SCENE_SWITCHED;
|
||||
|
||||
private:
|
||||
static bool _registered;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
#include "macro.hpp"
|
||||
#include "scene-selection.hpp"
|
||||
#include "scene-item-selection.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QCheckBox>
|
||||
|
||||
class MacroConditionSceneTransform : public MacroCondition {
|
||||
@@ -61,7 +61,7 @@ protected:
|
||||
SceneSelectionWidget *_scenes;
|
||||
SceneItemSelectionWidget *_sources;
|
||||
QPushButton *_getSettings;
|
||||
QPlainTextEdit *_settings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QCheckBox *_regex;
|
||||
|
||||
std::shared_ptr<MacroConditionSceneTransform> _entryData;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
#include "macro.hpp"
|
||||
#include "resizing-text-edit.hpp"
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QPushButton>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QCheckBox>
|
||||
|
||||
enum class SourceCondition {
|
||||
@@ -64,7 +64,7 @@ protected:
|
||||
QComboBox *_sources;
|
||||
QComboBox *_conditions;
|
||||
QPushButton *_getSettings;
|
||||
QPlainTextEdit *_settings;
|
||||
ResizingPlainTextEdit *_settings;
|
||||
QCheckBox *_regex;
|
||||
|
||||
std::shared_ptr<MacroConditionSource> _entryData;
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QFrame>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QScrollArea>
|
||||
#include <QPushButton>
|
||||
#include <QLayout>
|
||||
#include <QWidget>
|
||||
|
||||
class MacroEntryControls : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MacroEntryControls(const int animationDuration = 300,
|
||||
QWidget *parent = 0);
|
||||
|
||||
void Show(bool visible = true);
|
||||
|
||||
signals:
|
||||
void Add();
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
|
||||
private:
|
||||
QPushButton *_add;
|
||||
QPushButton *_remove;
|
||||
QPushButton *_up;
|
||||
QPushButton *_down;
|
||||
QPropertyAnimation *_animation = nullptr;
|
||||
};
|
||||
32
src/headers/macro-list-entry-widget.hpp
Normal file
32
src/headers/macro-list-entry-widget.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QLabel>
|
||||
#include <QCheckBox>
|
||||
#include <QTimer>
|
||||
#include <memory>
|
||||
|
||||
class Macro;
|
||||
|
||||
class MacroListEntryWidget : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroListEntryWidget(std::shared_ptr<Macro>, bool highlight,
|
||||
QWidget *parent);
|
||||
void SetName(const QString &);
|
||||
void SetMacro(std::shared_ptr<Macro> &);
|
||||
|
||||
private slots:
|
||||
void PauseChanged(int);
|
||||
void HighlightExecuted();
|
||||
void UpdatePaused();
|
||||
void EnableHighlight(bool);
|
||||
|
||||
private:
|
||||
QTimer _timer;
|
||||
QLabel *_name;
|
||||
QCheckBox *_running;
|
||||
std::shared_ptr<Macro> _macro;
|
||||
|
||||
bool _highlightExecutedMacros = false;
|
||||
};
|
||||
29
src/headers/macro-properties.hpp
Normal file
29
src/headers/macro-properties.hpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QDialog>
|
||||
#include <QCheckBox>
|
||||
#include <obs-data.h>
|
||||
|
||||
class MacroProperties {
|
||||
public:
|
||||
void Save(obs_data_t *obj);
|
||||
void Load(obs_data_t *obj);
|
||||
|
||||
bool _highlightExecuted = false;
|
||||
bool _highlightConditions = false;
|
||||
bool _highlightActions = false;
|
||||
};
|
||||
|
||||
class MacroPropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroPropertiesDialog(QWidget *parent, const MacroProperties &);
|
||||
static bool AskForSettings(QWidget *parent, MacroProperties &userInput);
|
||||
|
||||
private:
|
||||
QCheckBox *_executed;
|
||||
QCheckBox *_conditions;
|
||||
QCheckBox *_actions;
|
||||
};
|
||||
61
src/headers/macro-segment-list.hpp
Normal file
61
src/headers/macro-segment-list.hpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
#include "macro-segment.hpp"
|
||||
|
||||
#include <QWidget>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <thread>
|
||||
|
||||
class MacroSegmentList : public QScrollArea {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroSegmentList(QWidget *parent = nullptr);
|
||||
virtual ~MacroSegmentList();
|
||||
void SetHelpMsg(const QString &msg);
|
||||
void SetHelpMsgVisible(bool visible);
|
||||
MacroSegmentEdit *WidgetAt(int idx);
|
||||
void Insert(int idx, QWidget *widget);
|
||||
void Add(QWidget *widget);
|
||||
void Remove(int idx);
|
||||
void Clear(int idx = 0); // Clear all elements >= idx
|
||||
void Highlight(int idx);
|
||||
void SetCollapsed(bool);
|
||||
void SetSelection(int idx);
|
||||
QVBoxLayout *ContentLayout() { return _contentLayout; }
|
||||
|
||||
signals:
|
||||
void SelectionChagned(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);
|
||||
|
||||
private:
|
||||
int GetDragIndex(const QPoint &);
|
||||
int GetDropIndex(const QPoint &);
|
||||
int GetWidgetIdx(const QPoint &);
|
||||
void CheckScroll();
|
||||
void CheckDropLine(const QPoint &);
|
||||
bool IsInListArea(const QPoint &);
|
||||
QRect GetContentItemRectWithPadding(int idx);
|
||||
void HideLastDropLine();
|
||||
|
||||
int _dragPosition = -1;
|
||||
int _dropLineIdx = -1;
|
||||
QPoint _dragCursorPos;
|
||||
std::thread _autoScrollThread;
|
||||
std::atomic_bool _autoScroll{false};
|
||||
|
||||
QVBoxLayout *_layout;
|
||||
QVBoxLayout *_contentLayout;
|
||||
QLabel *_helpMsg;
|
||||
};
|
||||
@@ -1,5 +1,8 @@
|
||||
#pragma once
|
||||
#include <QWidget>
|
||||
#include <QFrame>
|
||||
#include <QVBoxLayout>
|
||||
#include <QTimer>
|
||||
#include <obs.hpp>
|
||||
|
||||
class Macro;
|
||||
@@ -16,10 +19,14 @@ public:
|
||||
virtual bool Load(obs_data_t *obj) = 0;
|
||||
virtual std::string GetShortDesc();
|
||||
virtual std::string GetId() = 0;
|
||||
void SetHighlight();
|
||||
bool Highlight();
|
||||
|
||||
protected:
|
||||
int _idx = 0;
|
||||
bool _collapsed = false;
|
||||
// UI helper
|
||||
bool _highlight = false;
|
||||
|
||||
private:
|
||||
Macro *_macro = nullptr;
|
||||
@@ -27,25 +34,23 @@ private:
|
||||
|
||||
class Section;
|
||||
class QLabel;
|
||||
class MacroEntryControls;
|
||||
|
||||
class MacroSegmentEdit : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MacroSegmentEdit(QWidget *parent = nullptr);
|
||||
MacroSegmentEdit(bool highlight, QWidget *parent = nullptr);
|
||||
// Use this function to avoid accidental edits when scrolling through
|
||||
// list of actions and conditions
|
||||
void SetFocusPolicyOfWidgets();
|
||||
void SetCollapsed(bool collapsed);
|
||||
void SetSelected(bool);
|
||||
|
||||
protected slots:
|
||||
void HeaderInfoChanged(const QString &);
|
||||
void Add();
|
||||
void Remove();
|
||||
void Up();
|
||||
void Down();
|
||||
void Collapsed(bool);
|
||||
void Highlight();
|
||||
void EnableHighlight(bool);
|
||||
signals:
|
||||
void MacroAdded(const QString &name);
|
||||
void MacroRemoved(const QString &name);
|
||||
@@ -53,21 +58,43 @@ signals:
|
||||
void SceneGroupAdded(const QString &name);
|
||||
void SceneGroupRemoved(const QString &name);
|
||||
void SceneGroupRenamed(const QString &oldName, const QString newName);
|
||||
void AddAt(int idx);
|
||||
void RemoveAt(int idx);
|
||||
void UpAt(int idx);
|
||||
void DownAt(int idx);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEvent *e);
|
||||
void leaveEvent(QEvent *e);
|
||||
|
||||
Section *_section;
|
||||
QLabel *_headerInfo;
|
||||
MacroEntryControls *_controls;
|
||||
QWidget *_frame;
|
||||
QVBoxLayout *_contentLayout;
|
||||
|
||||
private:
|
||||
enum class DropLineState {
|
||||
NONE,
|
||||
ABOVE,
|
||||
BELOW,
|
||||
};
|
||||
|
||||
virtual MacroSegment *Data() = 0;
|
||||
void ShowDropLine(DropLineState);
|
||||
|
||||
// The reason for using two separate frame widget each with their own
|
||||
// stylesheet and changing their visibility vs. using a single frame
|
||||
// and changing the stylesheet at runtime is that the operation of
|
||||
// adjusting the stylesheet is very expensive and can take multiple
|
||||
// hundred milliseconds per widget.
|
||||
// This performance impact would hurt in areas like drag and drop or
|
||||
// emitting the "SelectionChanged" signal.
|
||||
QFrame *_noBorderframe;
|
||||
QFrame *_borderFrame;
|
||||
|
||||
// In most cases the line above the widget will be used.
|
||||
// The lower one will only be used if the segment is the last one in
|
||||
// the list.
|
||||
QFrame *_dropLineAbove;
|
||||
QFrame *_dropLineBelow;
|
||||
|
||||
bool _showHighlight;
|
||||
QTimer _timer;
|
||||
|
||||
friend class MacroSegmentList;
|
||||
};
|
||||
|
||||
class MouseWheelWidgetAdjustmentGuard : public QObject {
|
||||
|
||||
@@ -75,9 +75,10 @@ public:
|
||||
virtual ~Macro();
|
||||
|
||||
bool CeckMatch();
|
||||
bool PerformAction(bool forceParallel = false,
|
||||
bool ignorePause = false);
|
||||
bool PerformActions(bool forceParallel = false,
|
||||
bool ignorePause = false);
|
||||
bool Matched() { return _matched; }
|
||||
int64_t MsSinceLastCheck();
|
||||
std::string Name() { return _name; }
|
||||
void SetName(const std::string &name);
|
||||
void SetRunInParallel(bool parallel) { _runInParallel = parallel; }
|
||||
@@ -88,7 +89,9 @@ public:
|
||||
bool MatchOnChange() { return _matchOnChange; }
|
||||
int GetCount() { return _count; };
|
||||
void ResetCount() { _count = 0; };
|
||||
void Stop() { _stop = true; }
|
||||
void AddHelperThread(std::thread &&);
|
||||
bool GetStop() { return _stop; }
|
||||
void Stop();
|
||||
std::deque<std::shared_ptr<MacroCondition>> &Conditions()
|
||||
{
|
||||
return _conditions;
|
||||
@@ -106,6 +109,11 @@ public:
|
||||
// Helper function for plugin state condition regarding scene change
|
||||
bool SwitchesScene();
|
||||
|
||||
// UI helpers for the macro tab
|
||||
bool WasExecutedRecently();
|
||||
bool OnChangePreventedActionsRecently();
|
||||
void ResetUIHelpers();
|
||||
|
||||
private:
|
||||
void SetupHotkeys();
|
||||
void ClearHotkeys();
|
||||
@@ -113,6 +121,7 @@ private:
|
||||
void ResetTimers();
|
||||
void RunActions(bool &ret, bool ignorePause);
|
||||
void RunActions(bool ignorePause);
|
||||
void SetOnChangeHighlight();
|
||||
|
||||
std::string _name = "";
|
||||
std::deque<std::shared_ptr<MacroCondition>> _conditions;
|
||||
@@ -127,10 +136,17 @@ private:
|
||||
obs_hotkey_id _unpauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id _togglePauseHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
|
||||
// UI helpers for the macro tab
|
||||
bool _wasExecutedRecently = false;
|
||||
bool _onChangeTriggered = false;
|
||||
|
||||
std::chrono::high_resolution_clock::time_point _lastCheckTime{};
|
||||
|
||||
bool _die = false;
|
||||
bool _stop = false;
|
||||
bool _done = true;
|
||||
std::thread _thread;
|
||||
std::thread _backgroundThread;
|
||||
std::vector<std::thread> _helperThreads;
|
||||
};
|
||||
|
||||
Macro *GetMacroByName(const char *name);
|
||||
|
||||
17
src/headers/resizing-text-edit.hpp
Normal file
17
src/headers/resizing-text-edit.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include <QPlainTextEdit>
|
||||
|
||||
class ResizingPlainTextEdit : public QPlainTextEdit {
|
||||
Q_OBJECT
|
||||
public:
|
||||
ResizingPlainTextEdit(QWidget *parent, const int scrollAt = 10,
|
||||
const int minLines = 3,
|
||||
const int paddingLines = 2);
|
||||
private slots:
|
||||
void ResizeTexteditArea();
|
||||
|
||||
private:
|
||||
const int _scrollAt;
|
||||
const int _minLines;
|
||||
const int _paddingLines;
|
||||
};
|
||||
@@ -4,10 +4,13 @@
|
||||
#include <QImage>
|
||||
#include <chrono>
|
||||
|
||||
class AdvSSScreenshotObj {
|
||||
class ScreenshotHelper {
|
||||
public:
|
||||
AdvSSScreenshotObj(obs_source_t *source);
|
||||
~AdvSSScreenshotObj();
|
||||
ScreenshotHelper() = default;
|
||||
ScreenshotHelper(obs_source_t *source);
|
||||
ScreenshotHelper &operator=(const ScreenshotHelper &) = delete;
|
||||
ScreenshotHelper(const ScreenshotHelper &) = delete;
|
||||
~ScreenshotHelper();
|
||||
|
||||
void Screenshot();
|
||||
void Download();
|
||||
@@ -17,7 +20,6 @@ public:
|
||||
gs_texrender_t *texrender = nullptr;
|
||||
gs_stagesurf_t *stagesurf = nullptr;
|
||||
OBSWeakSource weakSource;
|
||||
std::string path;
|
||||
QImage image;
|
||||
uint32_t cx = 0;
|
||||
uint32_t cy = 0;
|
||||
@@ -26,4 +28,7 @@ public:
|
||||
|
||||
bool done = false;
|
||||
std::chrono::high_resolution_clock::time_point time;
|
||||
|
||||
private:
|
||||
bool _initDone = false;
|
||||
};
|
||||
|
||||
@@ -14,7 +14,8 @@ public:
|
||||
explicit Section(const int animationDuration = 300,
|
||||
QWidget *parent = 0);
|
||||
|
||||
void SetContent(QWidget *w, bool collapsed = true);
|
||||
void SetContent(QWidget *w);
|
||||
void SetContent(QWidget *w, bool collapsed);
|
||||
void AddHeaderWidget(QWidget *);
|
||||
void SetCollapsed(bool);
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ struct VideoSwitch : virtual SceneSwitcherEntry {
|
||||
double duration = 0;
|
||||
bool ignoreInactiveSource = false;
|
||||
|
||||
std::unique_ptr<AdvSSScreenshotObj> screenshotData = nullptr;
|
||||
std::unique_ptr<ScreenshotHelper> screenshotData = nullptr;
|
||||
std::chrono::high_resolution_clock::time_point previousTime{};
|
||||
QImage matchImage;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "switch-network.hpp"
|
||||
|
||||
#include "macro.hpp"
|
||||
#include "macro-properties.hpp"
|
||||
#include "duration-control.hpp"
|
||||
|
||||
constexpr auto default_interval = 300;
|
||||
@@ -77,6 +78,7 @@ struct SwitcherData {
|
||||
SwitcherThread *th = nullptr;
|
||||
|
||||
bool settingsWindowOpened = false;
|
||||
int lastOpenedTab = -1;
|
||||
bool firstBoot = true;
|
||||
|
||||
std::condition_variable cv;
|
||||
@@ -86,6 +88,7 @@ struct SwitcherData {
|
||||
bool stop = false;
|
||||
bool verbose = false;
|
||||
bool disableHints = false;
|
||||
bool hideLegacyTabs = true;
|
||||
bool showSystemTrayNotifications = false;
|
||||
bool showFrame = false;
|
||||
bool transitionOverrideOverride = false;
|
||||
@@ -108,19 +111,25 @@ struct SwitcherData {
|
||||
StartupBehavior startupBehavior = PERSIST;
|
||||
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
|
||||
|
||||
std::vector<std::thread> audioHelperThreads;
|
||||
std::atomic_bool masterAudioFadeActive = {false};
|
||||
std::unordered_map<std::string, std::atomic_bool> activeAudioFades;
|
||||
struct AudioFadeInfo {
|
||||
std::atomic_bool active = {false};
|
||||
std::atomic_int id = {0};
|
||||
};
|
||||
AudioFadeInfo masterAudioFade;
|
||||
std::unordered_map<std::string, AudioFadeInfo> activeAudioFades;
|
||||
|
||||
Duration cooldown;
|
||||
std::chrono::high_resolution_clock::time_point lastMatchTime;
|
||||
|
||||
MacroProperties macroProperties;
|
||||
std::deque<std::shared_ptr<Macro>> macros;
|
||||
std::condition_variable macroWaitCv;
|
||||
std::atomic_bool abortMacroWait = {false};
|
||||
std::condition_variable macroTransitionCv;
|
||||
bool macroSceneSwitched = false;
|
||||
bool replayBufferSaved = false;
|
||||
bool obsIsShuttingDown = false;
|
||||
int shutdownConditionCount = 0;
|
||||
|
||||
std::deque<WindowSwitch> windowSwitches;
|
||||
std::vector<std::string> ignoreIdleWindows;
|
||||
@@ -210,6 +219,9 @@ struct SwitcherData {
|
||||
obs_hotkey_id startHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id stopHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id toggleHotkey = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id upMacroSegment = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id downMacroSegment = OBS_INVALID_HOTKEY_ID;
|
||||
obs_hotkey_id removeMacroSegment = OBS_INVALID_HOTKEY_ID;
|
||||
|
||||
bool saveWindowGeo = false;
|
||||
QPoint windowPos = {};
|
||||
|
||||
@@ -43,12 +43,11 @@ std::string getSceneItemTransform(obs_scene_item *item);
|
||||
void placeWidgets(std::string text, QBoxLayout *layout,
|
||||
std::unordered_map<std::string, QWidget *> placeholders,
|
||||
bool addStretch = true);
|
||||
void deleteLayoutItem(QLayoutItem *item);
|
||||
void deleteLayoutItemWidget(QLayoutItem *item);
|
||||
void clearLayout(QLayout *layout, int afterIdx = 0);
|
||||
void setLayoutVisible(QLayout *layout, bool visible);
|
||||
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
|
||||
QColor = QColor(0, 0, 0, 0),
|
||||
QString specifier = "QLabel ",
|
||||
QColor endColor = QColor(0, 0, 0, 0),
|
||||
bool once = false);
|
||||
void listAddClicked(QListWidget *list, QWidget *newWidget,
|
||||
QPushButton *addButton = nullptr,
|
||||
@@ -64,8 +63,8 @@ void populateTransitionSelection(QComboBox *sel, bool addCurrent = true,
|
||||
bool addAny = false);
|
||||
void populateWindowSelection(QComboBox *sel, bool addSelect = true);
|
||||
void populateAudioSelection(QComboBox *sel, bool addSelect = true);
|
||||
void populateVideoSelection(QComboBox *sel, bool addScenes = false,
|
||||
bool addSelect = true);
|
||||
void populateVideoSelection(QComboBox *sel, bool addMainOutput = false,
|
||||
bool addScenes = false, bool addSelect = true);
|
||||
void populateMediaSelection(QComboBox *sel, bool addSelect = true);
|
||||
void populateProcessSelection(QComboBox *sel, bool addSelect = true);
|
||||
void populateSourceSelection(QComboBox *list, bool addSelect = true);
|
||||
@@ -83,3 +82,4 @@ void populateSceneItemSelection(QComboBox *list,
|
||||
void populateSceneItemSelection(QComboBox *list, SceneSelection &s);
|
||||
void populateSourceGroupSelection(QComboBox *list);
|
||||
bool windowPosValid(QPoint pos);
|
||||
bool doubleEquals(double left, double right, double epsilon);
|
||||
|
||||
@@ -34,6 +34,39 @@ void startStopToggleHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
|
||||
}
|
||||
}
|
||||
|
||||
void upMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
|
||||
bool pressed)
|
||||
{
|
||||
if (pressed && switcher->settingsWindowOpened &&
|
||||
AdvSceneSwitcher::window) {
|
||||
QMetaObject::invokeMethod(AdvSceneSwitcher::window,
|
||||
"UpMacroSegementHotkey",
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void downMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
|
||||
bool pressed)
|
||||
{
|
||||
if (pressed && switcher->settingsWindowOpened &&
|
||||
AdvSceneSwitcher::window) {
|
||||
QMetaObject::invokeMethod(AdvSceneSwitcher::window,
|
||||
"DownMacroSegementHotkey",
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void removeMacroSegmentHotkeyFunc(void *, obs_hotkey_id, obs_hotkey_t *,
|
||||
bool pressed)
|
||||
{
|
||||
if (pressed && switcher->settingsWindowOpened &&
|
||||
AdvSceneSwitcher::window) {
|
||||
QMetaObject::invokeMethod(AdvSceneSwitcher::window,
|
||||
"DeleteMacroSegementHotkey",
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void registerHotkeys()
|
||||
{
|
||||
switcher->startHotkey = obs_hotkey_register_frontend(
|
||||
@@ -49,24 +82,46 @@ void registerHotkeys()
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.hotkey.startStopToggleSwitcherHotkey"),
|
||||
startStopToggleHotkeyFunc, NULL);
|
||||
switcher->upMacroSegment = obs_hotkey_register_frontend(
|
||||
"upMacroSegmentSwitcherHotkey",
|
||||
obs_module_text("AdvSceneSwitcher.hotkey.upMacroSegmentHotkey"),
|
||||
upMacroSegmentHotkeyFunc, NULL);
|
||||
switcher->downMacroSegment = obs_hotkey_register_frontend(
|
||||
"downMacroSegmentSwitcherHotkey",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.hotkey.downMacroSegmentHotkey"),
|
||||
downMacroSegmentHotkeyFunc, NULL);
|
||||
switcher->removeMacroSegment = obs_hotkey_register_frontend(
|
||||
"removeMacroSegmentSwitcherHotkey",
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.hotkey.removeMacroSegmentHotkey"),
|
||||
removeMacroSegmentHotkeyFunc, NULL);
|
||||
|
||||
switcher->hotkeysRegistered = true;
|
||||
}
|
||||
|
||||
void saveHotkey(obs_data_t *obj, obs_hotkey_id id, const char *name)
|
||||
{
|
||||
obs_data_array_t *a = obs_hotkey_save(id);
|
||||
obs_data_set_array(obj, name, a);
|
||||
obs_data_array_release(a);
|
||||
}
|
||||
|
||||
void SwitcherData::saveHotkeys(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *startHotkeyArrray = obs_hotkey_save(startHotkey);
|
||||
obs_data_set_array(obj, "startHotkey", startHotkeyArrray);
|
||||
obs_data_array_release(startHotkeyArrray);
|
||||
saveHotkey(obj, startHotkey, "startHotkey");
|
||||
saveHotkey(obj, stopHotkey, "stopHotkey");
|
||||
saveHotkey(obj, toggleHotkey, "toggleHotkey");
|
||||
saveHotkey(obj, upMacroSegment, "upMacroSegmentHotkey");
|
||||
saveHotkey(obj, downMacroSegment, "downMacroSegmentHotkey");
|
||||
saveHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey");
|
||||
}
|
||||
|
||||
obs_data_array_t *stopHotkeyArrray = obs_hotkey_save(stopHotkey);
|
||||
|
||||
obs_data_set_array(obj, "stopHotkey", stopHotkeyArrray);
|
||||
obs_data_array_release(stopHotkeyArrray);
|
||||
|
||||
obs_data_array_t *toggleHotkeyArrray = obs_hotkey_save(toggleHotkey);
|
||||
obs_data_set_array(obj, "toggleHotkey", toggleHotkeyArrray);
|
||||
obs_data_array_release(toggleHotkeyArrray);
|
||||
void loadHotkey(obs_data_t *obj, obs_hotkey_id id, const char *name)
|
||||
{
|
||||
obs_data_array_t *a = obs_data_get_array(obj, name);
|
||||
obs_hotkey_load(id, a);
|
||||
obs_data_array_release(a);
|
||||
}
|
||||
|
||||
void SwitcherData::loadHotkeys(obs_data_t *obj)
|
||||
@@ -74,19 +129,10 @@ void SwitcherData::loadHotkeys(obs_data_t *obj)
|
||||
if (!hotkeysRegistered) {
|
||||
registerHotkeys();
|
||||
}
|
||||
|
||||
obs_data_array_t *startHotkeyArrray =
|
||||
obs_data_get_array(obj, "startHotkey");
|
||||
obs_hotkey_load(startHotkey, startHotkeyArrray);
|
||||
obs_data_array_release(startHotkeyArrray);
|
||||
|
||||
obs_data_array_t *stopHotkeyArrray =
|
||||
obs_data_get_array(obj, "stopHotkey");
|
||||
obs_hotkey_load(stopHotkey, stopHotkeyArrray);
|
||||
obs_data_array_release(stopHotkeyArrray);
|
||||
|
||||
obs_data_array_t *toggleHotkeyArrray =
|
||||
obs_data_get_array(obj, "toggleHotkey");
|
||||
obs_hotkey_load(toggleHotkey, toggleHotkeyArrray);
|
||||
obs_data_array_release(toggleHotkeyArrray);
|
||||
loadHotkey(obj, startHotkey, "startHotkey");
|
||||
loadHotkey(obj, stopHotkey, "stopHotkey");
|
||||
loadHotkey(obj, toggleHotkey, "toggleHotkey");
|
||||
loadHotkey(obj, upMacroSegment, "upMacroSegmentHotkey");
|
||||
loadHotkey(obj, downMacroSegment, "downMacroSegmentHotkey");
|
||||
loadHotkey(obj, removeMacroSegment, "removeMacroSegmentHotkey");
|
||||
}
|
||||
|
||||
@@ -18,92 +18,147 @@ const static std::map<AudioAction, std::string> actionTypes = {
|
||||
"AdvSceneSwitcher.action.audio.type.masterVolume"},
|
||||
};
|
||||
|
||||
const static std::map<FadeType, std::string> fadeTypes = {
|
||||
{FadeType::DURATION,
|
||||
"AdvSceneSwitcher.action.audio.fade.type.duration"},
|
||||
{FadeType::RATE, "AdvSceneSwitcher.action.audio.fade.type.rate"},
|
||||
};
|
||||
|
||||
constexpr auto fadeInterval = std::chrono::milliseconds(100);
|
||||
constexpr float minFade = 0.000001f;
|
||||
|
||||
void fadeSourceVolume(Duration duration, float vol, OBSWeakSource audioSource)
|
||||
void MacroActionAudio::SetFadeActive(bool value)
|
||||
{
|
||||
auto s = obs_weak_source_get_source(audioSource);
|
||||
if (!s) {
|
||||
return;
|
||||
if (_action == AudioAction::SOURCE_VOLUME) {
|
||||
switcher->activeAudioFades[GetWeakSourceName(_audioSource)]
|
||||
.active = value;
|
||||
} else {
|
||||
switcher->masterAudioFade.active = value;
|
||||
}
|
||||
float curVol = obs_source_get_volume(s);
|
||||
obs_source_release(s);
|
||||
bool volIncrease = curVol <= vol;
|
||||
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
|
||||
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
|
||||
float volStep = volDiff / nrSteps;
|
||||
}
|
||||
|
||||
if (volStep < minFade) {
|
||||
switcher->activeAudioFades[GetWeakSourceName(audioSource)] =
|
||||
false;
|
||||
return;
|
||||
}
|
||||
|
||||
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
|
||||
auto s = obs_weak_source_get_source(audioSource);
|
||||
if (!s) {
|
||||
return;
|
||||
bool MacroActionAudio::FadeActive()
|
||||
{
|
||||
bool active = true;
|
||||
if (_action == AudioAction::SOURCE_VOLUME) {
|
||||
auto it = switcher->activeAudioFades.find(
|
||||
GetWeakSourceName(_audioSource));
|
||||
if (it == switcher->activeAudioFades.end()) {
|
||||
return false;
|
||||
}
|
||||
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
|
||||
obs_source_set_volume(s, curVol);
|
||||
std::this_thread::sleep_for(fadeInterval);
|
||||
active = it->second.active;
|
||||
} else {
|
||||
active = switcher->masterAudioFade.active;
|
||||
}
|
||||
|
||||
return active;
|
||||
}
|
||||
|
||||
std::atomic_int *MacroActionAudio::GetFadeIdPtr()
|
||||
{
|
||||
|
||||
if (_action == AudioAction::SOURCE_VOLUME) {
|
||||
auto it = switcher->activeAudioFades.find(
|
||||
GetWeakSourceName(_audioSource));
|
||||
if (it == switcher->activeAudioFades.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return &it->second.id;
|
||||
}
|
||||
return &switcher->masterAudioFade.id;
|
||||
}
|
||||
|
||||
void MacroActionAudio::SetVolume(float vol)
|
||||
{
|
||||
if (_action == AudioAction::SOURCE_VOLUME) {
|
||||
auto s = obs_weak_source_get_source(_audioSource);
|
||||
obs_source_set_volume(s, vol);
|
||||
obs_source_release(s);
|
||||
} else {
|
||||
obs_set_master_volume(vol);
|
||||
}
|
||||
}
|
||||
|
||||
float MacroActionAudio::GetVolume()
|
||||
{
|
||||
float curVol;
|
||||
if (_action == AudioAction::SOURCE_VOLUME) {
|
||||
auto s = obs_weak_source_get_source(_audioSource);
|
||||
if (!s) {
|
||||
return 0.;
|
||||
}
|
||||
curVol = obs_source_get_volume(s);
|
||||
obs_source_release(s);
|
||||
} else {
|
||||
curVol = obs_get_master_volume();
|
||||
}
|
||||
return curVol;
|
||||
}
|
||||
|
||||
void MacroActionAudio::FadeVolume()
|
||||
{
|
||||
float vol = (float)_volume / 100.0f;
|
||||
float curVol = GetVolume();
|
||||
bool volIncrease = curVol <= vol;
|
||||
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
|
||||
int nrSteps = 0;
|
||||
float volStep = 0.;
|
||||
if (_fadeType == FadeType::DURATION) {
|
||||
nrSteps = _duration.seconds * 1000 / fadeInterval.count();
|
||||
volStep = volDiff / nrSteps;
|
||||
} else {
|
||||
volStep = _rate / 1000.0f;
|
||||
nrSteps = volDiff / volStep;
|
||||
}
|
||||
|
||||
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = false;
|
||||
}
|
||||
void fadeMasterVolume(Duration duration, float vol)
|
||||
{
|
||||
float curVol = obs_get_master_volume();
|
||||
bool volIncrease = curVol <= vol;
|
||||
int nrSteps = duration.seconds * 1000 / fadeInterval.count();
|
||||
float volDiff = (volIncrease) ? vol - curVol : curVol - vol;
|
||||
float volStep = volDiff / nrSteps;
|
||||
|
||||
if (volStep < minFade) {
|
||||
switcher->masterAudioFadeActive = false;
|
||||
if (volStep < minFade || nrSteps <= 1) {
|
||||
SetVolume(vol);
|
||||
SetFadeActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int step = 0; step < nrSteps && !switcher->stop; ++step) {
|
||||
auto macro = GetMacro();
|
||||
int step = 0;
|
||||
auto fadeId = GetFadeIdPtr();
|
||||
int expectedFadeId = ++(*fadeId);
|
||||
for (; step < nrSteps && !macro->GetStop() && expectedFadeId == *fadeId;
|
||||
++step) {
|
||||
curVol = (volIncrease) ? curVol + volStep : curVol - volStep;
|
||||
obs_set_master_volume(curVol);
|
||||
SetVolume(curVol);
|
||||
std::this_thread::sleep_for(fadeInterval);
|
||||
}
|
||||
|
||||
switcher->masterAudioFadeActive = false;
|
||||
// As a final step set desired volume once again in case floating-point
|
||||
// precision errors compounded to a noticeable error
|
||||
if (step == nrSteps) {
|
||||
SetVolume(vol);
|
||||
}
|
||||
|
||||
SetFadeActive(false);
|
||||
}
|
||||
|
||||
void startSourceFade(Duration &duration, float vol, OBSWeakSource audioSource)
|
||||
void MacroActionAudio::StartFade()
|
||||
{
|
||||
if (!audioSource) {
|
||||
if (_action == AudioAction::SOURCE_VOLUME && !_audioSource) {
|
||||
return;
|
||||
}
|
||||
auto it =
|
||||
switcher->activeAudioFades.find(GetWeakSourceName(audioSource));
|
||||
if (it != switcher->activeAudioFades.end() && it->second == true) {
|
||||
|
||||
if (FadeActive() && !_abortActiveFade) {
|
||||
blog(LOG_WARNING,
|
||||
"Audio fade for volume of %s already active! New fade request will be ignored!",
|
||||
GetWeakSourceName(audioSource).c_str());
|
||||
(_action == AudioAction::SOURCE_VOLUME)
|
||||
? GetWeakSourceName(_audioSource).c_str()
|
||||
: "master volume");
|
||||
return;
|
||||
}
|
||||
switcher->activeAudioFades[GetWeakSourceName(audioSource)] = true;
|
||||
switcher->audioHelperThreads.emplace_back(fadeSourceVolume, duration,
|
||||
vol, audioSource);
|
||||
}
|
||||
SetFadeActive(true);
|
||||
|
||||
void startMasterFade(Duration &duration, float vol)
|
||||
{
|
||||
|
||||
if (switcher->masterAudioFadeActive) {
|
||||
blog(LOG_WARNING,
|
||||
"Audio fade for master volume already active! New fade request will be ignored!");
|
||||
return;
|
||||
if (_wait) {
|
||||
FadeVolume();
|
||||
} else {
|
||||
GetMacro()->AddHelperThread(
|
||||
std::thread(&MacroActionAudio::FadeVolume, this));
|
||||
}
|
||||
switcher->masterAudioFadeActive = true;
|
||||
switcher->audioHelperThreads.emplace_back(fadeMasterVolume, duration,
|
||||
vol);
|
||||
}
|
||||
|
||||
bool MacroActionAudio::PerformAction()
|
||||
@@ -117,18 +172,11 @@ bool MacroActionAudio::PerformAction()
|
||||
obs_source_set_muted(s, false);
|
||||
break;
|
||||
case AudioAction::SOURCE_VOLUME:
|
||||
if (_fade && _duration.seconds != 0) {
|
||||
startSourceFade(_duration, (float)_volume / 100.0f,
|
||||
_audioSource);
|
||||
} else {
|
||||
obs_source_set_volume(s, (float)_volume / 100.0f);
|
||||
}
|
||||
break;
|
||||
case AudioAction::MASTER_VOLUME:
|
||||
if (_fade && _duration.seconds != 0) {
|
||||
startMasterFade(_duration, (float)_volume / 100.0f);
|
||||
if (_fade) {
|
||||
StartFade();
|
||||
} else {
|
||||
obs_set_master_volume((float)_volume / 100.0f);
|
||||
SetVolume((float)_volume / 100.0f);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -161,7 +209,11 @@ bool MacroActionAudio::Save(obs_data_t *obj)
|
||||
GetWeakSourceName(_audioSource).c_str());
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
obs_data_set_int(obj, "volume", _volume);
|
||||
obs_data_set_double(obj, "rate", _rate);
|
||||
obs_data_set_bool(obj, "fade", _fade);
|
||||
obs_data_set_int(obj, "fadeType", static_cast<int>(_fadeType));
|
||||
obs_data_set_bool(obj, "wait", _wait);
|
||||
obs_data_set_bool(obj, "abortActiveFade", _abortActiveFade);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -173,7 +225,24 @@ bool MacroActionAudio::Load(obs_data_t *obj)
|
||||
_audioSource = GetWeakSourceByName(audioSourceName);
|
||||
_action = static_cast<AudioAction>(obs_data_get_int(obj, "action"));
|
||||
_volume = obs_data_get_int(obj, "volume");
|
||||
_rate = obs_data_get_double(obj, "rate");
|
||||
_fade = obs_data_get_bool(obj, "fade");
|
||||
if (obs_data_has_user_value(obj, "wait")) {
|
||||
_wait = obs_data_get_bool(obj, "wait");
|
||||
} else {
|
||||
_wait = false;
|
||||
}
|
||||
if (obs_data_has_user_value(obj, "fadeType")) {
|
||||
_fadeType = static_cast<FadeType>(
|
||||
obs_data_get_int(obj, "fadeType"));
|
||||
} else {
|
||||
_fadeType = FadeType::DURATION;
|
||||
}
|
||||
if (obs_data_has_user_value(obj, "abortActiveFade")) {
|
||||
_abortActiveFade = obs_data_get_bool(obj, "abortActiveFade");
|
||||
} else {
|
||||
_abortActiveFade = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -192,21 +261,41 @@ static inline void populateActionSelection(QComboBox *list)
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateFadeTypeSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : fadeTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroActionAudioEdit::MacroActionAudioEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionAudio> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_audioSources(new QComboBox),
|
||||
_actions(new QComboBox),
|
||||
_fadeTypes(new QComboBox),
|
||||
_volumePercent(new QSpinBox),
|
||||
_fade(new QCheckBox),
|
||||
_duration(new DurationSelection(parent, false)),
|
||||
_rate(new QDoubleSpinBox),
|
||||
_wait(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.audio.fade.wait"))),
|
||||
_abortActiveFade(new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.action.audio.fade.abort"))),
|
||||
_fadeTypeLayout(new QHBoxLayout),
|
||||
_fadeOptionsLayout(new QVBoxLayout)
|
||||
{
|
||||
_audioSources = new QComboBox();
|
||||
_actions = new QComboBox();
|
||||
_volumePercent = new QSpinBox();
|
||||
_volumePercent->setMinimum(0);
|
||||
_volumePercent->setMaximum(2000);
|
||||
_volumePercent->setSuffix("%");
|
||||
_fade = new QCheckBox();
|
||||
_duration = new DurationSelection(parent, false);
|
||||
|
||||
_rate->setMinimum(0.01);
|
||||
_rate->setMaximum(999.);
|
||||
_rate->setSuffix("%");
|
||||
|
||||
populateActionSelection(_actions);
|
||||
populateAudioSelection(_audioSources);
|
||||
populateFadeTypeSelection(_fadeTypes);
|
||||
|
||||
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ActionChanged(int)));
|
||||
@@ -219,21 +308,41 @@ MacroActionAudioEdit::MacroActionAudioEdit(
|
||||
SLOT(FadeChanged(int)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(_rate, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(RateChanged(double)));
|
||||
QWidget::connect(_wait, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(WaitChanged(int)));
|
||||
QWidget::connect(_abortActiveFade, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(AbortActiveFadeChanged(int)));
|
||||
QWidget::connect(_fadeTypes, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(FadeTypeChanged(int)));
|
||||
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{audioSources}}", _audioSources}, {"{{actions}}", _actions},
|
||||
{"{{volume}}", _volumePercent}, {"{{fade}}", _fade},
|
||||
{"{{audioSources}}", _audioSources},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{volume}}", _volumePercent},
|
||||
{"{{fade}}", _fade},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{rate}}", _rate},
|
||||
{"{{wait}}", _wait},
|
||||
{"{{abortActiveFade}}", _abortActiveFade},
|
||||
{"{{fadeTypes}}", _fadeTypes},
|
||||
};
|
||||
QHBoxLayout *entryLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.entry"),
|
||||
entryLayout, widgetPlaceholders);
|
||||
_fadeLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.audio.fade"),
|
||||
_fadeLayout, widgetPlaceholders);
|
||||
_fadeTypeLayout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.action.audio.fade.duration"),
|
||||
_fadeTypeLayout, widgetPlaceholders);
|
||||
|
||||
_fadeOptionsLayout->addLayout(_fadeTypeLayout);
|
||||
_fadeOptionsLayout->addWidget(_abortActiveFade);
|
||||
_fadeOptionsLayout->addWidget(_wait);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(entryLayout);
|
||||
mainLayout->addLayout(_fadeLayout);
|
||||
mainLayout->addLayout(_fadeOptionsLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -256,7 +365,42 @@ void MacroActionAudioEdit::SetWidgetVisibility()
|
||||
{
|
||||
_volumePercent->setVisible(hasVolumeControl(_entryData->_action));
|
||||
_audioSources->setVisible(hasSourceControl(_entryData->_action));
|
||||
setLayoutVisible(_fadeLayout, hasVolumeControl(_entryData->_action));
|
||||
|
||||
_fadeTypes->setDisabled(!_entryData->_fade);
|
||||
_wait->setDisabled(!_entryData->_fade);
|
||||
_abortActiveFade->setDisabled(!_entryData->_fade);
|
||||
_duration->setDisabled(!_entryData->_fade);
|
||||
_rate->setDisabled(!_entryData->_fade);
|
||||
|
||||
_fadeTypeLayout->removeWidget(_fade);
|
||||
_fadeTypeLayout->removeWidget(_fadeTypes);
|
||||
_fadeTypeLayout->removeWidget(_duration);
|
||||
_fadeTypeLayout->removeWidget(_rate);
|
||||
clearLayout(_fadeTypeLayout);
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{fade}}", _fade},
|
||||
{"{{duration}}", _duration},
|
||||
{"{{rate}}", _rate},
|
||||
{"{{fadeTypes}}", _fadeTypes},
|
||||
};
|
||||
if (_entryData->_fadeType == FadeType::DURATION) {
|
||||
placeWidgets(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.audio.fade.duration"),
|
||||
_fadeTypeLayout, widgetPlaceholders);
|
||||
} else {
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.action.audio.fade.rate"),
|
||||
_fadeTypeLayout, widgetPlaceholders);
|
||||
}
|
||||
|
||||
_duration->setVisible(_entryData->_fadeType == FadeType::DURATION);
|
||||
_rate->setVisible(_entryData->_fadeType == FadeType::RATE);
|
||||
|
||||
setLayoutVisible(_fadeTypeLayout,
|
||||
hasVolumeControl(_entryData->_action));
|
||||
setLayoutVisible(_fadeOptionsLayout,
|
||||
hasVolumeControl(_entryData->_action));
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
@@ -272,6 +416,10 @@ void MacroActionAudioEdit::UpdateEntryData()
|
||||
_volumePercent->setValue(_entryData->_volume);
|
||||
_fade->setChecked(_entryData->_fade);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_rate->setValue(_entryData->_rate);
|
||||
_wait->setChecked(_entryData->_wait);
|
||||
_abortActiveFade->setChecked(_entryData->_abortActiveFade);
|
||||
_fadeTypes->setCurrentIndex(static_cast<int>(_entryData->_fadeType));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -316,6 +464,7 @@ void MacroActionAudioEdit::FadeChanged(int value)
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_fade = value;
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::DurationChanged(double seconds)
|
||||
@@ -327,3 +476,44 @@ void MacroActionAudioEdit::DurationChanged(double seconds)
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_duration.seconds = seconds;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::RateChanged(double value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_rate = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::WaitChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_wait = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::AbortActiveFadeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_abortActiveFade = value;
|
||||
}
|
||||
|
||||
void MacroActionAudioEdit::FadeTypeChanged(int value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_fadeType = static_cast<FadeType>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "headers/macro-action-edit.hpp"
|
||||
#include "headers/macro-action-scene-switch.hpp"
|
||||
#include "headers/section.hpp"
|
||||
#include "headers/macro-controls.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
std::map<std::string, MacroActionInfo> MacroActionFactory::_methods;
|
||||
@@ -64,22 +63,31 @@ static inline void populateActionSelection(QComboBox *list)
|
||||
MacroActionEdit::MacroActionEdit(QWidget *parent,
|
||||
std::shared_ptr<MacroAction> *entryData,
|
||||
const std::string &id)
|
||||
: MacroSegmentEdit(parent), _entryData(entryData)
|
||||
: MacroSegmentEdit(switcher->macroProperties._highlightActions, parent),
|
||||
_entryData(entryData)
|
||||
{
|
||||
_actionSelection = new QComboBox();
|
||||
|
||||
QWidget::connect(_actionSelection,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(ActionSelectionChanged(const QString &)));
|
||||
QWidget::connect(window(), SIGNAL(HighlightActionsChanged(bool)), this,
|
||||
SLOT(EnableHighlight(bool)));
|
||||
|
||||
populateActionSelection(_actionSelection);
|
||||
|
||||
_section->AddHeaderWidget(_actionSelection);
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(_section);
|
||||
mainLayout->addWidget(_controls);
|
||||
QVBoxLayout *actionLayout = new QVBoxLayout;
|
||||
actionLayout->setContentsMargins({7, 7, 7, 7});
|
||||
actionLayout->addWidget(_section);
|
||||
_contentLayout->addLayout(actionLayout);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
mainLayout->addWidget(_frame);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -98,15 +106,16 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
|
||||
auto macro = _entryData->get()->GetMacro();
|
||||
std::string id = MacroActionFactory::GetIdByName(text);
|
||||
HeaderInfoChanged("");
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->reset();
|
||||
*_entryData = MacroActionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->reset();
|
||||
*_entryData = MacroActionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
}
|
||||
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
_section->SetContent(widget, false);
|
||||
_section->SetContent(widget);
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
|
||||
@@ -123,6 +132,11 @@ void MacroActionEdit::UpdateEntryData(const std::string &id)
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
|
||||
void MacroActionEdit::SetEntryData(std::shared_ptr<MacroAction> *data)
|
||||
{
|
||||
_entryData = data;
|
||||
}
|
||||
|
||||
MacroSegment *MacroActionEdit::Data()
|
||||
{
|
||||
return _entryData->get();
|
||||
@@ -146,19 +160,22 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
|
||||
MacroActionSwitchScene temp(nullptr);
|
||||
id = temp.GetId();
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->Actions().emplace(macro->Actions().begin() + idx,
|
||||
MacroActionFactory::Create(id, macro));
|
||||
if (idx - 1 >= 0) {
|
||||
auto data = obs_data_create();
|
||||
macro->Actions().at(idx - 1)->Save(data);
|
||||
macro->Actions().at(idx)->Load(data);
|
||||
obs_data_release(data);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->Actions().emplace(macro->Actions().begin() + idx,
|
||||
MacroActionFactory::Create(id, macro));
|
||||
if (idx - 1 >= 0) {
|
||||
auto data = obs_data_create();
|
||||
macro->Actions().at(idx - 1)->Save(data);
|
||||
macro->Actions().at(idx)->Load(data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
macro->UpdateActionIndices();
|
||||
actionsList->Insert(
|
||||
idx,
|
||||
new MacroActionEdit(this, ¯o->Actions()[idx], id));
|
||||
SetActionData(*macro);
|
||||
}
|
||||
macro->UpdateActionIndices();
|
||||
|
||||
clearLayout(ui->macroEditActionLayout, idx);
|
||||
PopulateMacroActions(*macro, idx);
|
||||
HighlightAction(idx);
|
||||
}
|
||||
|
||||
@@ -169,8 +186,19 @@ void AdvSceneSwitcher::on_actionAdd_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
AddMacroAction((int)macro->Actions().size());
|
||||
ui->macroEditActionHelp->setVisible(false);
|
||||
if (currentActionIdx == -1) {
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
AddMacroAction((int)macro->Actions().size());
|
||||
} else {
|
||||
AddMacroAction(currentActionIdx + 1);
|
||||
}
|
||||
if (currentActionIdx != -1) {
|
||||
MacroActionSelectionChanged(currentActionIdx + 1);
|
||||
}
|
||||
actionsList->SetHelpMsgVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
||||
@@ -184,23 +212,48 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->Actions().erase(macro->Actions().begin() + idx);
|
||||
switcher->abortMacroWait = true;
|
||||
switcher->macroWaitCv.notify_all();
|
||||
macro->UpdateActionIndices();
|
||||
|
||||
clearLayout(ui->macroEditActionLayout, idx);
|
||||
PopulateMacroActions(*macro, idx);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->Actions().erase(macro->Actions().begin() + idx);
|
||||
switcher->abortMacroWait = true;
|
||||
switcher->macroWaitCv.notify_all();
|
||||
macro->UpdateActionIndices();
|
||||
actionsList->Remove(idx);
|
||||
SetActionData(*macro);
|
||||
}
|
||||
MacroActionSelectionChanged(-1);
|
||||
lastInteracted = MacroSection::ACTIONS;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_actionRemove_clicked()
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
if (currentActionIdx == -1) {
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
RemoveMacroAction((int)macro->Actions().size() - 1);
|
||||
} else {
|
||||
RemoveMacroAction(currentActionIdx);
|
||||
}
|
||||
MacroActionSelectionChanged(-1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_actionUp_clicked()
|
||||
{
|
||||
if (currentActionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
RemoveMacroAction((int)macro->Actions().size() - 1);
|
||||
MoveMacroActionUp(currentActionIdx);
|
||||
MacroActionSelectionChanged(currentActionIdx - 1);
|
||||
}
|
||||
void AdvSceneSwitcher::on_actionDown_clicked()
|
||||
{
|
||||
if (currentActionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
MoveMacroActionDown(currentActionIdx);
|
||||
MacroActionSelectionChanged(currentActionIdx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
|
||||
@@ -215,20 +268,13 @@ void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
iter_swap(m->Actions().begin() + pos1, m->Actions().begin() + pos2);
|
||||
m->UpdateActionIndices();
|
||||
|
||||
auto a1 = m->Actions().begin() + pos1;
|
||||
auto a2 = m->Actions().begin() + pos2;
|
||||
|
||||
auto item1 = ui->macroEditActionLayout->takeAt(pos1);
|
||||
auto item2 = ui->macroEditActionLayout->takeAt(pos2 - 1);
|
||||
deleteLayoutItem(item1);
|
||||
deleteLayoutItem(item2);
|
||||
auto widget1 = new MacroActionEdit(this, &(*a1), (*a1)->GetId());
|
||||
auto widget2 = new MacroActionEdit(this, &(*a2), (*a2)->GetId());
|
||||
ConnectControlSignals(widget1);
|
||||
ConnectControlSignals(widget2);
|
||||
ui->macroEditActionLayout->insertWidget(pos1, widget1);
|
||||
ui->macroEditActionLayout->insertWidget(pos2, widget2);
|
||||
auto widget1 = static_cast<MacroActionEdit *>(
|
||||
actionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
auto widget2 = static_cast<MacroActionEdit *>(
|
||||
actionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||
actionsList->Insert(pos1, widget2);
|
||||
actionsList->Insert(pos2, widget1);
|
||||
SetActionData(*m);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MoveMacroActionUp(int idx)
|
||||
@@ -243,6 +289,7 @@ void AdvSceneSwitcher::MoveMacroActionUp(int idx)
|
||||
}
|
||||
|
||||
SwapActions(macro, idx, idx - 1);
|
||||
HighlightAction(idx - 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MoveMacroActionDown(int idx)
|
||||
@@ -257,4 +304,49 @@ void AdvSceneSwitcher::MoveMacroActionDown(int idx)
|
||||
}
|
||||
|
||||
SwapActions(macro, idx, idx + 1);
|
||||
HighlightAction(idx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroActionSelectionChanged(int idx)
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
actionsList->SetSelection(idx);
|
||||
conditionsList->SetSelection(-1);
|
||||
|
||||
if (idx < 0 || (unsigned)idx >= macro->Actions().size()) {
|
||||
currentActionIdx = -1;
|
||||
} else {
|
||||
currentActionIdx = idx;
|
||||
lastInteracted = MacroSection::ACTIONS;
|
||||
}
|
||||
currentConditionIdx = -1;
|
||||
HighlightControls();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroActionReorder(int to, int from)
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from < 0 || from > (int)macro->Actions().size() || to < 0 ||
|
||||
to > (int)macro->Actions().size()) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto action = macro->Actions().at(from);
|
||||
macro->Actions().erase(macro->Actions().begin() + from);
|
||||
macro->Actions().insert(macro->Actions().begin() + to, action);
|
||||
macro->UpdateActionIndices();
|
||||
actionsList->ContentLayout()->insertItem(
|
||||
to, actionsList->ContentLayout()->takeAt(from));
|
||||
SetActionData(*macro);
|
||||
}
|
||||
HighlightAction(to);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ MacroActionFileEdit::MacroActionFileEdit(
|
||||
: QWidget(parent)
|
||||
{
|
||||
_filePath = new FileSelection(FileSelection::Type::WRITE);
|
||||
_text = new QPlainTextEdit();
|
||||
_text = new ResizingPlainTextEdit(this);
|
||||
_actions = new QComboBox();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
@@ -128,6 +128,9 @@ void MacroActionFileEdit::UpdateEntryData()
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_filePath->SetPath(QString::fromStdString(_entryData->_file));
|
||||
_text->setPlainText(QString::fromStdString(_entryData->_text));
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFileEdit::PathChanged(const QString &text)
|
||||
@@ -150,6 +153,9 @@ void MacroActionFileEdit::TextChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_text = _text->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFileEdit::ActionChanged(int value)
|
||||
|
||||
@@ -98,7 +98,7 @@ MacroActionFilterEdit::MacroActionFilterEdit(
|
||||
_actions = new QComboBox();
|
||||
_getSettings = new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.action.filter.getSettings"));
|
||||
_settings = new QPlainTextEdit();
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
|
||||
populateActionSelection(_actions);
|
||||
populateSourcesWithFilterSelection(_sources);
|
||||
@@ -153,6 +153,9 @@ void MacroActionFilterEdit::UpdateEntryData()
|
||||
GetWeakSourceName(_entryData->_filter).c_str());
|
||||
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
|
||||
SetWidgetVisibility(_entryData->_action == FilterAction::SETTINGS);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SourceChanged(const QString &text)
|
||||
@@ -211,6 +214,9 @@ void MacroActionFilterEdit::SettingsChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionFilterEdit::SetWidgetVisibility(bool showSettings)
|
||||
|
||||
@@ -36,7 +36,7 @@ bool MacroActionMacro::PerformAction()
|
||||
_macro->ResetCount();
|
||||
break;
|
||||
case PerformMacroAction::RUN:
|
||||
_macro->PerformAction();
|
||||
_macro->PerformActions();
|
||||
break;
|
||||
case PerformMacroAction::STOP:
|
||||
_macro->Stop();
|
||||
|
||||
@@ -16,6 +16,7 @@ const static std::map<MediaAction, std::string> actionTypes = {
|
||||
{MediaAction::RESTART, "AdvSceneSwitcher.action.media.type.restart"},
|
||||
{MediaAction::NEXT, "AdvSceneSwitcher.action.media.type.next"},
|
||||
{MediaAction::PREVIOUS, "AdvSceneSwitcher.action.media.type.previous"},
|
||||
{MediaAction::SEEK, "AdvSceneSwitcher.action.media.type.seek"},
|
||||
};
|
||||
|
||||
bool MacroActionMedia::PerformAction()
|
||||
@@ -46,6 +47,9 @@ bool MacroActionMedia::PerformAction()
|
||||
case MediaAction::PREVIOUS:
|
||||
obs_source_media_previous(source);
|
||||
break;
|
||||
case MediaAction::SEEK:
|
||||
obs_source_media_set_time(source, _seek.seconds * 1000);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -72,6 +76,7 @@ bool MacroActionMedia::Save(obs_data_t *obj)
|
||||
obs_data_set_string(obj, "mediaSource",
|
||||
GetWeakSourceName(_mediaSource).c_str());
|
||||
obs_data_set_int(obj, "action", static_cast<int>(_action));
|
||||
_seek.Save(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -81,6 +86,7 @@ bool MacroActionMedia::Load(obs_data_t *obj)
|
||||
const char *MediaSourceName = obs_data_get_string(obj, "mediaSource");
|
||||
_mediaSource = GetWeakSourceByName(MediaSourceName);
|
||||
_action = static_cast<MediaAction>(obs_data_get_int(obj, "action"));
|
||||
_seek.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -101,11 +107,11 @@ static inline void populateActionSelection(QComboBox *list)
|
||||
|
||||
MacroActionMediaEdit::MacroActionMediaEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionMedia> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_mediaSources(new QComboBox()),
|
||||
_actions(new QComboBox()),
|
||||
_seek(new DurationSelection())
|
||||
{
|
||||
_mediaSources = new QComboBox();
|
||||
_actions = new QComboBox();
|
||||
|
||||
populateActionSelection(_actions);
|
||||
populateMediaSelection(_mediaSources);
|
||||
|
||||
@@ -114,11 +120,16 @@ MacroActionMediaEdit::MacroActionMediaEdit(
|
||||
QWidget::connect(_mediaSources,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(SourceChanged(const QString &)));
|
||||
QWidget::connect(_seek, SIGNAL(DurationChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(_seek, SIGNAL(UnitChanged(DurationUnit)), this,
|
||||
SLOT(DurationUnitChanged(DurationUnit)));
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{mediaSources}}", _mediaSources},
|
||||
{"{{actions}}", _actions},
|
||||
{"{{duration}}", _seek},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.action.media.entry"),
|
||||
mainLayout, widgetPlaceholders);
|
||||
@@ -138,6 +149,8 @@ void MacroActionMediaEdit::UpdateEntryData()
|
||||
_mediaSources->setCurrentText(
|
||||
GetWeakSourceName(_entryData->_mediaSource).c_str());
|
||||
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
|
||||
_seek->SetDuration(_entryData->_seek);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SourceChanged(const QString &text)
|
||||
@@ -160,4 +173,34 @@ void MacroActionMediaEdit::ActionChanged(int value)
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_action = static_cast<MediaAction>(value);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::DurationChanged(double seconds)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_seek.seconds = seconds;
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::DurationUnitChanged(DurationUnit unit)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_seek.displayUnit = unit;
|
||||
}
|
||||
|
||||
void MacroActionMediaEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
_seek->setVisible(_entryData->_action == MediaAction::SEEK);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ MacroActionPreviewSceneEdit::MacroActionPreviewSceneEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroActionPreviewScene> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_scenes = new SceneSelectionWidget(window(), false, false, false);
|
||||
_scenes = new SceneSelectionWidget(window(), true, true, true);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
|
||||
@@ -44,12 +44,12 @@ bool MacroActionRandom::PerformAction()
|
||||
}
|
||||
if (macros.size() == 1) {
|
||||
lastRandomMacro = macros[0];
|
||||
return macros[0]->PerformAction();
|
||||
return macros[0]->PerformActions();
|
||||
}
|
||||
|
||||
srand((unsigned int)time(0));
|
||||
size_t idx = std::rand() % (macros.size());
|
||||
lastRandomMacro = macros[idx];
|
||||
return macros[idx]->PerformAction();
|
||||
return macros[idx]->PerformActions();
|
||||
}
|
||||
|
||||
void MacroActionRandom::LogAction()
|
||||
@@ -107,8 +107,6 @@ MacroActionRandomEdit::MacroActionRandomEdit(
|
||||
|
||||
QWidget::connect(_add, SIGNAL(clicked()), this, SLOT(AddMacro()));
|
||||
QWidget::connect(_remove, SIGNAL(clicked()), this, SLOT(RemoveMacro()));
|
||||
QWidget::connect(_macroList, SIGNAL(currentRowChanged(int)), this,
|
||||
SLOT(MacroSelectionChanged(int)));
|
||||
QWidget::connect(window(),
|
||||
SIGNAL(MacroRenamed(const QString &, const QString &)),
|
||||
this,
|
||||
|
||||
@@ -102,6 +102,8 @@ MacroActionRunEdit::MacroActionRunEdit(
|
||||
SLOT(RemoveArg()));
|
||||
QWidget::connect(_argUp, SIGNAL(clicked()), this, SLOT(ArgUp()));
|
||||
QWidget::connect(_argDown, SIGNAL(clicked()), this, SLOT(ArgDown()));
|
||||
connect(_argList, SIGNAL(itemDoubleClicked(QListWidgetItem *)), this,
|
||||
SLOT(ArgItemClicked(QListWidgetItem *)));
|
||||
|
||||
auto *entryLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
@@ -235,6 +237,33 @@ void MacroActionRunEdit::ArgDown()
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::ArgItemClicked(QListWidgetItem *item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name;
|
||||
bool accepted = AdvSSNameDialog::AskForName(
|
||||
this,
|
||||
obs_module_text("AdvSceneSwitcher.action.run.addArgument"),
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.action.run.addArgumentDescription"),
|
||||
name, item->text(), 170, false);
|
||||
|
||||
if (!accepted || name.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto arg = QString::fromStdString(name);
|
||||
QVariant v = QVariant::fromValue(arg);
|
||||
item->setText(arg);
|
||||
item->setData(Qt::UserRole, arg);
|
||||
int idx = _argList->currentRow();
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_args[idx] = arg;
|
||||
}
|
||||
|
||||
void MacroActionRunEdit::SetArgListSize()
|
||||
{
|
||||
setHeightToContentHeight(_argList);
|
||||
|
||||
@@ -9,39 +9,57 @@ bool MacroActionSwitchScene::_registered = MacroActionFactory::Register(
|
||||
{MacroActionSwitchScene::Create, MacroActionSwitchSceneEdit::Create,
|
||||
"AdvSceneSwitcher.action.switchScene"});
|
||||
|
||||
void waitForTransitionChange(OBSWeakSource &target)
|
||||
void waitForTransitionChange(int duration)
|
||||
{
|
||||
const int maxNrChecks = 100;
|
||||
int curCheckNr = 0;
|
||||
bool transitionEnded = false;
|
||||
duration += 200; // It seems to be necessary to add a small buffer
|
||||
auto time = std::chrono::high_resolution_clock::now() +
|
||||
std::chrono::milliseconds(duration);
|
||||
|
||||
auto sceneCheckStartedOn = obs_frontend_get_current_scene();
|
||||
obs_source_release(sceneCheckStartedOn);
|
||||
switcher->abortMacroWait = false;
|
||||
std::unique_lock<std::mutex> lock(switcher->m);
|
||||
while (!transitionEnded && curCheckNr < maxNrChecks &&
|
||||
!switcher->abortMacroWait) {
|
||||
switcher->abortMacroWait = false;
|
||||
switcher->macroTransitionCv.wait_for(
|
||||
lock,
|
||||
std::chrono::milliseconds(
|
||||
(long long)switcher->interval),
|
||||
[] { return switcher->abortMacroWait.load(); });
|
||||
auto curScene = obs_frontend_get_current_scene();
|
||||
obs_source_release(curScene);
|
||||
transitionEnded = curScene != sceneCheckStartedOn ||
|
||||
switcher->currentScene == target;
|
||||
++curCheckNr;
|
||||
while (!switcher->abortMacroWait) {
|
||||
if (switcher->macroTransitionCv.wait_until(lock, time) ==
|
||||
std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int getTransitionOverrideDuration(OBSWeakSource scene)
|
||||
{
|
||||
int duration = 0;
|
||||
obs_source_t *source = obs_weak_source_get_source(scene);
|
||||
obs_data_t *data = obs_source_get_private_settings(source);
|
||||
auto name = obs_data_get_string(data, "transition");
|
||||
if (strlen(name) != 0) {
|
||||
duration = obs_data_get_int(data, "transition_duration");
|
||||
}
|
||||
obs_data_release(data);
|
||||
obs_source_release(source);
|
||||
return duration;
|
||||
}
|
||||
|
||||
int getExpectedTransitionDuration(OBSWeakSource scene, double duration)
|
||||
{
|
||||
int overrideDuration = getTransitionOverrideDuration(scene);
|
||||
if (overrideDuration && !switcher->transitionOverrideOverride) {
|
||||
return overrideDuration;
|
||||
}
|
||||
if (duration != 0) {
|
||||
return duration * 1000;
|
||||
}
|
||||
return obs_frontend_get_transition_duration();
|
||||
}
|
||||
|
||||
bool MacroActionSwitchScene::PerformAction()
|
||||
{
|
||||
auto scene = _scene.GetScene();
|
||||
switchScene({scene, _transition.GetTransition(),
|
||||
(int)(_duration.seconds * 1000)});
|
||||
if (_blockUntilTransitionDone) {
|
||||
waitForTransitionChange(scene);
|
||||
(int)(_duration.seconds * 1000)},
|
||||
obs_frontend_preview_program_mode_active());
|
||||
if (_blockUntilTransitionDone && scene) {
|
||||
waitForTransitionChange(getExpectedTransitionDuration(
|
||||
scene, _duration.seconds));
|
||||
return !switcher->abortMacroWait;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -111,7 +111,7 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
|
||||
_sources = new SceneItemSelectionWidget(parent);
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.action.sceneTransform.getTransform"));
|
||||
_settings = new QPlainTextEdit();
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
|
||||
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
|
||||
this, SLOT(SceneChanged(const SceneSelection &)));
|
||||
@@ -161,6 +161,9 @@ void MacroActionSceneTransformEdit::UpdateEntryData()
|
||||
_scenes->SetScene(_entryData->_scene);
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_settings->setPlainText(formatJsonString(_entryData->GetSettings()));
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSceneTransformEdit::SceneChanged(const SceneSelection &s)
|
||||
@@ -212,4 +215,7 @@ void MacroActionSceneTransformEdit::SettingsChanged()
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto json = _settings->toPlainText().toStdString();
|
||||
_entryData->SetSettings(json);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ bool MacroActionSequence::_registered = MacroActionFactory::Register(
|
||||
|
||||
int getNextUnpausedMacroIdx(std::vector<MacroRef> ¯os, int startIdx)
|
||||
{
|
||||
for (; macros.size() > startIdx; ++startIdx) {
|
||||
if (!macros[startIdx]->Paused()) {
|
||||
for (; (int)macros.size() > startIdx; ++startIdx) {
|
||||
if (macros[startIdx].get() && !macros[startIdx]->Paused()) {
|
||||
return startIdx;
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ bool MacroActionSequence::PerformAction()
|
||||
return true;
|
||||
}
|
||||
|
||||
return macro->PerformAction();
|
||||
return macro->PerformActions();
|
||||
}
|
||||
|
||||
void MacroActionSequence::LogAction()
|
||||
@@ -141,8 +141,10 @@ MacroActionSequenceEdit::MacroActionSequenceEdit(
|
||||
QWidget::connect(_down, SIGNAL(clicked()), this, SLOT(Down()));
|
||||
QWidget::connect(_continueFrom, SIGNAL(clicked()), this,
|
||||
SLOT(ContinueFromClicked()));
|
||||
QWidget::connect(_macroList, SIGNAL(currentRowChanged(int)), this,
|
||||
SLOT(MacroSelectionChanged(int)));
|
||||
QWidget::connect(_macroList,
|
||||
SIGNAL(itemDoubleClicked(QListWidgetItem *)), this,
|
||||
SLOT(MacroItemClicked(QListWidgetItem *)));
|
||||
|
||||
QWidget::connect(window(),
|
||||
SIGNAL(MacroRenamed(const QString &, const QString &)),
|
||||
this,
|
||||
@@ -312,6 +314,32 @@ void MacroActionSequenceEdit::Down()
|
||||
}
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::MacroItemClicked(QListWidgetItem *item)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string macroName;
|
||||
bool accepted = MacroSelectionDialog::AskForMacro(this, macroName);
|
||||
|
||||
if (!accepted || macroName.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MacroRef macro(macroName);
|
||||
|
||||
if (!macro.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
item->setText(QString::fromStdString(macroName));
|
||||
int idx = _macroList->currentRow();
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_macros[idx] = macro;
|
||||
SetMacroListSize();
|
||||
}
|
||||
|
||||
void MacroActionSequenceEdit::ContinueFromClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
|
||||
@@ -90,7 +90,7 @@ MacroActionSourceEdit::MacroActionSourceEdit(
|
||||
_actions = new QComboBox();
|
||||
_getSettings = new QPushButton(
|
||||
obs_module_text("AdvSceneSwitcher.action.source.getSettings"));
|
||||
_settings = new QPlainTextEdit();
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_warning = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.action.source.warning"));
|
||||
|
||||
@@ -141,6 +141,9 @@ void MacroActionSourceEdit::UpdateEntryData()
|
||||
GetWeakSourceName(_entryData->_source).c_str());
|
||||
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
|
||||
SetWidgetVisibility(_entryData->_action == SourceAction::SETTINGS);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SourceChanged(const QString &text)
|
||||
@@ -184,6 +187,9 @@ void MacroActionSourceEdit::SettingsChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroActionSourceEdit::SetWidgetVisibility(bool showSettings)
|
||||
|
||||
@@ -14,6 +14,17 @@ const static std::map<StreamAction, std::string> actionTypes = {
|
||||
{StreamAction::START, "AdvSceneSwitcher.action.streaming.type.start"},
|
||||
};
|
||||
|
||||
constexpr int streamStartCooldown = 5;
|
||||
std::chrono::high_resolution_clock::time_point MacroActionStream::s_lastAttempt =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
|
||||
bool MacroActionStream::CooldownDurationReached()
|
||||
{
|
||||
auto timePassed = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::high_resolution_clock::now() - s_lastAttempt);
|
||||
return timePassed.count() >= streamStartCooldown;
|
||||
}
|
||||
|
||||
bool MacroActionStream::PerformAction()
|
||||
{
|
||||
switch (_action) {
|
||||
@@ -24,10 +35,10 @@ bool MacroActionStream::PerformAction()
|
||||
break;
|
||||
case StreamAction::START:
|
||||
if (!obs_frontend_streaming_active() &&
|
||||
_retryCooldown.DurationReached()) {
|
||||
CooldownDurationReached()) {
|
||||
obs_frontend_streaming_start();
|
||||
_retryCooldown.seconds++;
|
||||
_retryCooldown.Reset();
|
||||
s_lastAttempt =
|
||||
std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -21,9 +21,9 @@ static std::default_random_engine re(rd());
|
||||
|
||||
bool MacroActionWait::PerformAction()
|
||||
{
|
||||
double sleep_duration;
|
||||
double sleepDuration;
|
||||
if (_waitType == WaitType::FIXED) {
|
||||
sleep_duration = _duration.seconds;
|
||||
sleepDuration = _duration.seconds;
|
||||
} else {
|
||||
double min = (_duration.seconds < _duration2.seconds)
|
||||
? _duration.seconds
|
||||
@@ -32,17 +32,23 @@ bool MacroActionWait::PerformAction()
|
||||
? _duration2.seconds
|
||||
: _duration.seconds;
|
||||
std::uniform_real_distribution<double> unif(min, max);
|
||||
sleep_duration = unif(re);
|
||||
sleepDuration = unif(re);
|
||||
}
|
||||
vblog(LOG_INFO, "perform action wait with duration of %f",
|
||||
sleep_duration);
|
||||
sleepDuration);
|
||||
|
||||
std::unique_lock<std::mutex> lock(switcher->m);
|
||||
auto time = std::chrono::high_resolution_clock::now() +
|
||||
std::chrono::milliseconds((int)(sleepDuration * 1000));
|
||||
auto macro = GetMacro();
|
||||
switcher->abortMacroWait = false;
|
||||
switcher->macroWaitCv.wait_for(
|
||||
lock,
|
||||
std::chrono::milliseconds((long long)(sleep_duration * 1000)),
|
||||
[] { return switcher->abortMacroWait.load(); });
|
||||
std::unique_lock<std::mutex> lock(switcher->m);
|
||||
while (!switcher->abortMacroWait && !macro->GetStop()) {
|
||||
if (switcher->macroWaitCv.wait_until(lock, time) ==
|
||||
std::cv_status::timeout) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !switcher->abortMacroWait;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "headers/utility.hpp"
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
|
||||
#include <QCalendarWidget>
|
||||
|
||||
const std::string MacroConditionDate::id = "date";
|
||||
|
||||
bool MacroConditionDate::_registered = MacroConditionFactory::Register(
|
||||
@@ -18,7 +20,33 @@ static std::map<DateCondition, std::string> dateConditionTypes = {
|
||||
"AdvSceneSwitcher.condition.date.state.between"},
|
||||
};
|
||||
|
||||
bool MacroConditionDate::CheckCondition()
|
||||
static std::map<DayOfWeekSelection, std::string> dayOfWeekNames = {
|
||||
{DayOfWeekSelection::ANY, "AdvSceneSwitcher.condition.date.anyDay"},
|
||||
{DayOfWeekSelection::MONDAY, "AdvSceneSwitcher.condition.date.monday"},
|
||||
{DayOfWeekSelection::TUESDAY,
|
||||
"AdvSceneSwitcher.condition.date.tuesday"},
|
||||
{DayOfWeekSelection::WEDNESDAY,
|
||||
"AdvSceneSwitcher.condition.date.wednesday"},
|
||||
{DayOfWeekSelection::THURSDAY,
|
||||
"AdvSceneSwitcher.condition.date.thursday"},
|
||||
{DayOfWeekSelection::FRIDAY, "AdvSceneSwitcher.condition.date.friday"},
|
||||
{DayOfWeekSelection::SATURDAY,
|
||||
"AdvSceneSwitcher.condition.date.saturday"},
|
||||
{DayOfWeekSelection::SUNDAY, "AdvSceneSwitcher.condition.date.sunday"},
|
||||
};
|
||||
|
||||
bool MacroConditionDate::CheckDayOfWeek(int64_t msSinceLastCheck)
|
||||
{
|
||||
QDateTime cur = QDateTime::currentDateTime();
|
||||
if (_dayOfWeek != DayOfWeekSelection::ANY &&
|
||||
cur.date().dayOfWeek() != static_cast<int>(_dayOfWeek)) {
|
||||
return false;
|
||||
}
|
||||
_dateTime.setDate(cur.date());
|
||||
return _dateTime <= cur && _dateTime >= cur.addMSecs(-msSinceLastCheck);
|
||||
}
|
||||
|
||||
bool MacroConditionDate::CheckRegularDate(int64_t msSinceLastCheck)
|
||||
{
|
||||
bool match = false;
|
||||
QDateTime cur = QDateTime::currentDateTime();
|
||||
@@ -33,8 +61,8 @@ bool MacroConditionDate::CheckCondition()
|
||||
|
||||
switch (_condition) {
|
||||
case DateCondition::AT:
|
||||
match = _dateTime >= cur &&
|
||||
_dateTime <= cur.addMSecs(switcher->interval);
|
||||
match = _dateTime <= cur &&
|
||||
_dateTime >= cur.addMSecs(-msSinceLastCheck);
|
||||
break;
|
||||
case DateCondition::AFTER:
|
||||
match = cur >= _dateTime;
|
||||
@@ -61,9 +89,23 @@ bool MacroConditionDate::CheckCondition()
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::CheckCondition()
|
||||
{
|
||||
auto m = GetMacro();
|
||||
if (!m) {
|
||||
return false;
|
||||
}
|
||||
auto msSinceLastCheck = m->MsSinceLastCheck();
|
||||
if (_dayOfWeekCheck) {
|
||||
return CheckDayOfWeek(msSinceLastCheck);
|
||||
}
|
||||
return CheckRegularDate(msSinceLastCheck);
|
||||
}
|
||||
|
||||
bool MacroConditionDate::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "dayOfWeek", static_cast<int>(_dayOfWeek));
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "dateTime",
|
||||
_dateTime.toString().toStdString().c_str());
|
||||
@@ -73,13 +115,15 @@ bool MacroConditionDate::Save(obs_data_t *obj)
|
||||
obs_data_set_bool(obj, "ignoreTime", _ignoreTime);
|
||||
obs_data_set_bool(obj, "repeat", _repeat);
|
||||
_duration.Save(obj);
|
||||
|
||||
obs_data_set_bool(obj, "dayOfWeekCheck", _dayOfWeekCheck);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_dayOfWeek = static_cast<DayOfWeekSelection>(
|
||||
obs_data_get_int(obj, "dayOfWeek"));
|
||||
_condition =
|
||||
static_cast<DateCondition>(obs_data_get_int(obj, "condition"));
|
||||
_dateTime = QDateTime::fromString(
|
||||
@@ -90,12 +134,43 @@ bool MacroConditionDate::Load(obs_data_t *obj)
|
||||
_ignoreTime = obs_data_get_bool(obj, "ignoreTime");
|
||||
_repeat = obs_data_get_bool(obj, "repeat");
|
||||
_duration.Load(obj);
|
||||
|
||||
// TODO: Remove this fallback in future version
|
||||
if (!obs_data_has_user_value(obj, "dayOfWeekCheck")) {
|
||||
_dayOfWeekCheck = false;
|
||||
} else {
|
||||
_dayOfWeekCheck = obs_data_get_bool(obj, "dayOfWeekCheck");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionDate::GetShortDesc()
|
||||
{
|
||||
return _dateTime.toString().toStdString();
|
||||
if (_dayOfWeekCheck) {
|
||||
auto it = dayOfWeekNames.find(_dayOfWeek);
|
||||
if (it == dayOfWeekNames.end()) {
|
||||
return "";
|
||||
}
|
||||
std::string dayName = obs_module_text(it->second.c_str());
|
||||
return dayName + " " +
|
||||
_dateTime.time().toString().toStdString();
|
||||
} else {
|
||||
if (!_ignoreDate && !_ignoreTime) {
|
||||
return _dateTime.toString().toStdString();
|
||||
} else if (_ignoreDate && !_ignoreTime) {
|
||||
return _dateTime.time().toString().toStdString();
|
||||
} else if (!_ignoreDate && _ignoreTime) {
|
||||
return _dateTime.date().toString().toStdString();
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateDaySelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : dayOfWeekNames) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
@@ -109,24 +184,53 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionDate> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_dayOfWeek = new QComboBox();
|
||||
_weekTime = new QTimeEdit();
|
||||
_weekTime->setDisplayFormat("hh:mm:ss");
|
||||
|
||||
_condition = new QComboBox();
|
||||
_dateTime = new QDateTimeEdit();
|
||||
_dateTime->setCalendarPopup(true);
|
||||
_dateTime->setDisplayFormat("yyyy.MM.dd hh:mm:ss");
|
||||
_dateTime2 = new QDateTimeEdit();
|
||||
_dateTime2->setCalendarPopup(true);
|
||||
_dateTime2->setDisplayFormat("yyyy.MM.dd hh:mm:ss");
|
||||
_date = new QDateEdit();
|
||||
_date->setDisplayFormat("yyyy.MM.dd ");
|
||||
_date->setCalendarPopup(true);
|
||||
auto cal = _date->calendarWidget();
|
||||
cal->showSelectedDate();
|
||||
_time = new QTimeEdit();
|
||||
_time->setDisplayFormat("hh:mm:ss");
|
||||
_separator = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.separator"));
|
||||
_date2 = new QDateEdit();
|
||||
_date2->setDisplayFormat("yyyy.MM.dd ");
|
||||
_date2->setCalendarPopup(true);
|
||||
cal = _date2->calendarWidget();
|
||||
cal->showSelectedDate();
|
||||
_time2 = new QTimeEdit();
|
||||
_time2->setDisplayFormat("hh:mm:ss");
|
||||
_ignoreDate = new QCheckBox();
|
||||
_ignoreDate->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreDate"));
|
||||
_ignoreTime = new QCheckBox();
|
||||
_ignoreTime->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreTime"));
|
||||
|
||||
_repeat = new QCheckBox();
|
||||
_duration = new DurationSelection();
|
||||
_advancedSettingsTooggle = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showAdvancedSettings"));
|
||||
|
||||
QWidget::connect(_dayOfWeek, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(DayOfWeekChanged(int)));
|
||||
QWidget::connect(_weekTime, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(TimeChanged(const QTime &)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_dateTime, SIGNAL(dateTimeChanged(const QDateTime &)),
|
||||
this, SLOT(DateTimeChanged(const QDateTime &)));
|
||||
QWidget::connect(_dateTime2, SIGNAL(dateTimeChanged(const QDateTime &)),
|
||||
this, SLOT(DateTime2Changed(const QDateTime &)));
|
||||
QWidget::connect(_date, SIGNAL(dateChanged(const QDate &)), this,
|
||||
SLOT(DateChanged(const QDate &)));
|
||||
QWidget::connect(_date2, SIGNAL(dateChanged(const QDate &)), this,
|
||||
SLOT(Date2Changed(const QDate &)));
|
||||
QWidget::connect(_time, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(TimeChanged(const QTime &)));
|
||||
QWidget::connect(_time2, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(Time2Changed(const QTime &)));
|
||||
QWidget::connect(_ignoreDate, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreDateChanged(int)));
|
||||
QWidget::connect(_ignoreTime, SIGNAL(stateChanged(int)), this,
|
||||
@@ -137,30 +241,47 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
|
||||
SLOT(DurationUnitChanged(DurationUnit)));
|
||||
QWidget::connect(_advancedSettingsTooggle, SIGNAL(clicked()), this,
|
||||
SLOT(AdvancedSettingsToggleClicked()));
|
||||
|
||||
populateDaySelection(_dayOfWeek);
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
auto line1Layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{dayOfWeek}}", _dayOfWeek},
|
||||
{"{{weekTime}}", _weekTime},
|
||||
{"{{condition}}", _condition},
|
||||
{"{{dateTime}}", _dateTime},
|
||||
{"{{dateTime2}}", _dateTime2},
|
||||
{"{{date}}", _date},
|
||||
{"{{time}}", _time},
|
||||
{"{{separator}}", _separator},
|
||||
{"{{date2}}", _date2},
|
||||
{"{{time2}}", _time2},
|
||||
{"{{ignoreDate}}", _ignoreDate},
|
||||
{"{{ignoreTime}}", _ignoreTime},
|
||||
{"{{repeat}}", _repeat},
|
||||
{"{{duration}}", _duration},
|
||||
};
|
||||
_simpleLayout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.line1"),
|
||||
line1Layout, widgetPlaceholders);
|
||||
auto line2Layout = new QHBoxLayout;
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.simple"),
|
||||
_simpleLayout, widgetPlaceholders);
|
||||
_advancedLayout = new QHBoxLayout;
|
||||
placeWidgets(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.entry.advanced"),
|
||||
_advancedLayout, widgetPlaceholders);
|
||||
_repeatLayout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.line2"),
|
||||
line2Layout, widgetPlaceholders);
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.repeat"),
|
||||
_repeatLayout, widgetPlaceholders);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(line1Layout);
|
||||
mainLayout->addLayout(line2Layout);
|
||||
mainLayout->addLayout(_simpleLayout);
|
||||
mainLayout->addLayout(_advancedLayout);
|
||||
mainLayout->addLayout(_repeatLayout);
|
||||
auto *_advancedToggleLayout = new QHBoxLayout;
|
||||
_advancedToggleLayout->addWidget(_advancedSettingsTooggle);
|
||||
_advancedToggleLayout->addStretch();
|
||||
mainLayout->addLayout(_advancedToggleLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
@@ -168,6 +289,18 @@ MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DayOfWeekChanged(int day)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dayOfWeek = static_cast<DayOfWeekSelection>(day);
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -180,27 +313,50 @@ void MacroConditionDateEdit::ConditionChanged(int cond)
|
||||
DateCondition::BETWEEN);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DateTimeChanged(const QDateTime &datetime)
|
||||
void MacroConditionDateEdit::DateChanged(const QDate &date)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime = datetime;
|
||||
_entryData->_dateTime.setDate(date);
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DateTime2Changed(const QDateTime &datetime)
|
||||
void MacroConditionDateEdit::TimeChanged(const QTime &time)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime2 = datetime;
|
||||
_entryData->_dateTime.setTime(time);
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::Date2Changed(const QDate &date)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime2.setDate(date);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::Time2Changed(const QTime &time)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime2.setTime(time);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::IgnoreDateChanged(int state)
|
||||
@@ -210,7 +366,8 @@ void MacroConditionDateEdit::IgnoreDateChanged(int state)
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_ignoreDate = state;
|
||||
_entryData->_ignoreDate = !state;
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::IgnoreTimeChanged(int state)
|
||||
@@ -220,7 +377,8 @@ void MacroConditionDateEdit::IgnoreTimeChanged(int state)
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_ignoreTime = state;
|
||||
_entryData->_ignoreTime = !state;
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::RepeatChanged(int state)
|
||||
@@ -254,26 +412,74 @@ void MacroConditionDateEdit::DurationUnitChanged(DurationUnit unit)
|
||||
_entryData->_duration.displayUnit = unit;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::AdvancedSettingsToggleClicked()
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dayOfWeekCheck = !_entryData->_dayOfWeekCheck;
|
||||
SetWidgetStatus();
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_dayOfWeek->setCurrentIndex(
|
||||
static_cast<int>(static_cast<int>(_entryData->_dayOfWeek)));
|
||||
_weekTime->setTime(_entryData->_dateTime.time());
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_dateTime->setDateTime(_entryData->_dateTime);
|
||||
_dateTime2->setDateTime(_entryData->_dateTime2);
|
||||
_ignoreDate->setChecked(_entryData->_ignoreDate);
|
||||
_ignoreTime->setChecked(_entryData->_ignoreTime);
|
||||
_date->setDate(_entryData->_dateTime.date());
|
||||
_time->setTime(_entryData->_dateTime.time());
|
||||
_date2->setDate(_entryData->_dateTime2.date());
|
||||
_time2->setTime(_entryData->_dateTime2.time());
|
||||
_ignoreDate->setChecked(!_entryData->_ignoreDate);
|
||||
_ignoreTime->setChecked(!_entryData->_ignoreTime);
|
||||
_repeat->setChecked(_entryData->_repeat);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_duration->setDisabled(!_entryData->_repeat);
|
||||
ShowSecondDateSelection(_entryData->_condition ==
|
||||
DateCondition::BETWEEN);
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetWidgetStatus()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
setLayoutVisible(_simpleLayout, _entryData->_dayOfWeekCheck);
|
||||
setLayoutVisible(_advancedLayout, !_entryData->_dayOfWeekCheck);
|
||||
setLayoutVisible(_repeatLayout, !_entryData->_dayOfWeekCheck);
|
||||
if (_entryData->_dayOfWeekCheck) {
|
||||
_advancedSettingsTooggle->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showAdvancedSettings"));
|
||||
} else {
|
||||
_advancedSettingsTooggle->setText(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.date.showSimpleSettings"));
|
||||
_date->setDisabled(_entryData->_ignoreDate);
|
||||
_date2->setDisabled(_entryData->_ignoreDate);
|
||||
_time->setDisabled(_entryData->_ignoreTime);
|
||||
_time2->setDisabled(_entryData->_ignoreTime);
|
||||
ShowSecondDateSelection(_entryData->_condition ==
|
||||
DateCondition::BETWEEN);
|
||||
}
|
||||
|
||||
const QSignalBlocker b1(_weekTime);
|
||||
const QSignalBlocker b2(_weekTime);
|
||||
_weekTime->setTime(_entryData->_dateTime.time());
|
||||
_weekTime->setTime(_entryData->_dateTime.time());
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ShowSecondDateSelection(bool visible)
|
||||
{
|
||||
_dateTime2->setVisible(visible);
|
||||
adjustSize();
|
||||
_separator->setVisible(visible);
|
||||
_date2->setVisible(visible);
|
||||
_time2->setVisible(visible);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "headers/macro-condition-edit.hpp"
|
||||
#include "headers/macro-condition-scene.hpp"
|
||||
#include "headers/section.hpp"
|
||||
#include "headers/macro-controls.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
std::map<std::string, MacroConditionInfo> MacroConditionFactory::_methods;
|
||||
@@ -93,7 +92,10 @@ static inline void populateConditionSelection(QComboBox *list)
|
||||
MacroConditionEdit::MacroConditionEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroCondition> *entryData,
|
||||
const std::string &id, bool root)
|
||||
: MacroSegmentEdit(parent), _entryData(entryData), _isRoot(root)
|
||||
: MacroSegmentEdit(switcher->macroProperties._highlightConditions,
|
||||
parent),
|
||||
_entryData(entryData),
|
||||
_isRoot(root)
|
||||
{
|
||||
_logicSelection = new QComboBox();
|
||||
_conditionSelection = new QComboBox();
|
||||
@@ -111,6 +113,8 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
QWidget::connect(_dur, SIGNAL(ConditionChanged(DurationCondition)),
|
||||
this,
|
||||
SLOT(DurationConditionChanged(DurationCondition)));
|
||||
QWidget::connect(window(), SIGNAL(HighlightConditionsChanged(bool)),
|
||||
this, SLOT(EnableHighlight(bool)));
|
||||
|
||||
populateLogicSelection(_logicSelection, root);
|
||||
populateConditionSelection(_conditionSelection);
|
||||
@@ -120,9 +124,15 @@ MacroConditionEdit::MacroConditionEdit(
|
||||
_section->AddHeaderWidget(_headerInfo);
|
||||
_section->AddHeaderWidget(_dur);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(_section);
|
||||
mainLayout->addWidget(_controls);
|
||||
QVBoxLayout *conditionLayout = new QVBoxLayout;
|
||||
conditionLayout->setContentsMargins({7, 7, 7, 7});
|
||||
conditionLayout->addWidget(_section);
|
||||
_contentLayout->addLayout(conditionLayout);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->setSpacing(0);
|
||||
mainLayout->addWidget(_frame);
|
||||
setLayout(mainLayout);
|
||||
|
||||
UpdateEntryData(id);
|
||||
@@ -151,11 +161,24 @@ bool MacroConditionEdit::IsRootNode()
|
||||
return _isRoot;
|
||||
}
|
||||
|
||||
void MacroConditionEdit::SetLogicSelection()
|
||||
{
|
||||
auto logic = (*_entryData)->GetLogicType();
|
||||
if (IsRootNode()) {
|
||||
_logicSelection->setCurrentIndex(static_cast<int>(logic));
|
||||
} else {
|
||||
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
|
||||
logic_root_offset);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionEdit::SetRootNode(bool root)
|
||||
{
|
||||
_isRoot = root;
|
||||
const QSignalBlocker blocker(_logicSelection);
|
||||
_logicSelection->clear();
|
||||
populateLogicSelection(_logicSelection, root);
|
||||
SetLogicSelection();
|
||||
}
|
||||
|
||||
void MacroConditionEdit::UpdateEntryData(const std::string &id)
|
||||
@@ -168,13 +191,7 @@ void MacroConditionEdit::UpdateEntryData(const std::string &id)
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
HeaderInfoChanged(
|
||||
QString::fromStdString((*_entryData)->GetShortDesc()));
|
||||
auto logic = (*_entryData)->GetLogicType();
|
||||
if (IsRootNode()) {
|
||||
_logicSelection->setCurrentIndex(static_cast<int>(logic));
|
||||
} else {
|
||||
_logicSelection->setCurrentIndex(static_cast<int>(logic) -
|
||||
logic_root_offset);
|
||||
}
|
||||
SetLogicSelection();
|
||||
_section->SetContent(widget, (*_entryData)->GetCollapsed());
|
||||
|
||||
_dur->setVisible(MacroConditionFactory::UsesDurationConstraint(id));
|
||||
@@ -183,6 +200,11 @@ void MacroConditionEdit::UpdateEntryData(const std::string &id)
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
|
||||
void MacroConditionEdit::SetEntryData(std::shared_ptr<MacroCondition> *data)
|
||||
{
|
||||
_entryData = data;
|
||||
}
|
||||
|
||||
void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
@@ -196,18 +218,19 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
|
||||
auto temp = DurationConstraint();
|
||||
_dur->SetValue(temp);
|
||||
HeaderInfoChanged("");
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto logic = (*_entryData)->GetLogicType();
|
||||
_entryData->reset();
|
||||
*_entryData = MacroConditionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
(*_entryData)->SetLogicType(logic);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto logic = (*_entryData)->GetLogicType();
|
||||
_entryData->reset();
|
||||
*_entryData = MacroConditionFactory::Create(id, macro);
|
||||
(*_entryData)->SetIndex(idx);
|
||||
(*_entryData)->SetLogicType(logic);
|
||||
}
|
||||
auto widget =
|
||||
MacroConditionFactory::CreateWidget(id, this, *_entryData);
|
||||
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
|
||||
this, SLOT(HeaderInfoChanged(const QString &)));
|
||||
_section->SetContent(widget, false);
|
||||
_section->SetContent(widget);
|
||||
_dur->setVisible(MacroConditionFactory::UsesDurationConstraint(id));
|
||||
SetFocusPolicyOfWidgets();
|
||||
}
|
||||
@@ -259,29 +282,38 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
|
||||
}
|
||||
|
||||
std::string id;
|
||||
if (idx - 1 >= 0) {
|
||||
LogicType logic;
|
||||
if (idx >= 1) {
|
||||
id = macro->Conditions().at(idx - 1)->GetId();
|
||||
if (idx == 1) {
|
||||
logic = LogicType::NONE;
|
||||
} else {
|
||||
logic = macro->Conditions().at(idx - 1)->GetLogicType();
|
||||
}
|
||||
} else {
|
||||
MacroConditionScene temp(macro);
|
||||
id = temp.GetId();
|
||||
logic = LogicType::ROOT_NONE;
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
bool root = idx == 0;
|
||||
auto cond = macro->Conditions().emplace(
|
||||
macro->Conditions().begin() + idx,
|
||||
MacroConditionFactory::Create(id, macro));
|
||||
if (idx - 1 >= 0) {
|
||||
auto data = obs_data_create();
|
||||
macro->Conditions().at(idx - 1)->Save(data);
|
||||
macro->Conditions().at(idx)->Load(data);
|
||||
obs_data_release(data);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto cond = macro->Conditions().emplace(
|
||||
macro->Conditions().begin() + idx,
|
||||
MacroConditionFactory::Create(id, macro));
|
||||
if (idx - 1 >= 0) {
|
||||
auto data = obs_data_create();
|
||||
macro->Conditions().at(idx - 1)->Save(data);
|
||||
macro->Conditions().at(idx)->Load(data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
(*cond)->SetLogicType(logic);
|
||||
macro->UpdateConditionIndices();
|
||||
conditionsList->Insert(
|
||||
idx,
|
||||
new MacroConditionEdit(this, ¯o->Conditions()[idx],
|
||||
id, idx == 0));
|
||||
SetConditionData(*macro);
|
||||
}
|
||||
auto logic = root ? LogicType::ROOT_NONE : LogicType::NONE;
|
||||
(*cond)->SetLogicType(logic);
|
||||
macro->UpdateConditionIndices();
|
||||
|
||||
clearLayout(ui->macroEditConditionLayout, idx);
|
||||
PopulateMacroConditions(*macro, idx);
|
||||
HighlightCondition(idx);
|
||||
}
|
||||
|
||||
@@ -292,8 +324,19 @@ void AdvSceneSwitcher::on_conditionAdd_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
AddMacroCondition((int)macro->Conditions().size());
|
||||
ui->macroEditConditionHelp->setVisible(false);
|
||||
if (currentConditionIdx == -1) {
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
AddMacroCondition((int)macro->Conditions().size());
|
||||
} else {
|
||||
AddMacroCondition(currentConditionIdx + 1);
|
||||
}
|
||||
if (currentConditionIdx != -1) {
|
||||
MacroConditionSelectionChanged(currentConditionIdx + 1);
|
||||
}
|
||||
conditionsList->SetHelpMsgVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||
@@ -307,26 +350,51 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->Conditions().erase(macro->Conditions().begin() + idx);
|
||||
macro->UpdateConditionIndices();
|
||||
|
||||
if (idx == 0 && macro->Conditions().size() > 0) {
|
||||
auto newRoot = macro->Conditions().at(0);
|
||||
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
macro->Conditions().erase(macro->Conditions().begin() + idx);
|
||||
macro->UpdateConditionIndices();
|
||||
if (idx == 0 && macro->Conditions().size() > 0) {
|
||||
auto newRoot = macro->Conditions().at(0);
|
||||
newRoot->SetLogicType(LogicType::ROOT_NONE);
|
||||
}
|
||||
conditionsList->Remove(idx);
|
||||
SetConditionData(*macro);
|
||||
}
|
||||
|
||||
clearLayout(ui->macroEditConditionLayout, idx);
|
||||
PopulateMacroConditions(*macro, idx);
|
||||
MacroConditionSelectionChanged(-1);
|
||||
lastInteracted = MacroSection::CONDITIONS;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_conditionRemove_clicked()
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
if (currentConditionIdx == -1) {
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
RemoveMacroCondition((int)macro->Conditions().size() - 1);
|
||||
} else {
|
||||
RemoveMacroCondition(currentConditionIdx);
|
||||
}
|
||||
MacroConditionSelectionChanged(-1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_conditionUp_clicked()
|
||||
{
|
||||
if (currentConditionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
RemoveMacroCondition((int)macro->Conditions().size() - 1);
|
||||
MoveMacroConditionUp(currentConditionIdx);
|
||||
MacroConditionSelectionChanged(currentConditionIdx - 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_conditionDown_clicked()
|
||||
{
|
||||
if (currentConditionIdx == -1) {
|
||||
return;
|
||||
}
|
||||
MoveMacroConditionDown(currentConditionIdx);
|
||||
MacroConditionSelectionChanged(currentConditionIdx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
|
||||
@@ -353,18 +421,15 @@ void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
|
||||
(*c2)->SetLogicType(logic1);
|
||||
}
|
||||
|
||||
auto item1 = ui->macroEditConditionLayout->takeAt(pos1);
|
||||
auto item2 = ui->macroEditConditionLayout->takeAt(pos2 - 1);
|
||||
deleteLayoutItem(item1);
|
||||
deleteLayoutItem(item2);
|
||||
auto widget1 =
|
||||
new MacroConditionEdit(this, &(*c1), (*c1)->GetId(), root);
|
||||
auto widget2 =
|
||||
new MacroConditionEdit(this, &(*c2), (*c2)->GetId(), false);
|
||||
ConnectControlSignals(widget1);
|
||||
ConnectControlSignals(widget2);
|
||||
ui->macroEditConditionLayout->insertWidget(pos1, widget1);
|
||||
ui->macroEditConditionLayout->insertWidget(pos2, widget2);
|
||||
auto widget1 = static_cast<MacroConditionEdit *>(
|
||||
conditionsList->ContentLayout()->takeAt(pos1)->widget());
|
||||
auto widget2 = static_cast<MacroConditionEdit *>(
|
||||
conditionsList->ContentLayout()->takeAt(pos2 - 1)->widget());
|
||||
conditionsList->Insert(pos1, widget2);
|
||||
conditionsList->Insert(pos2, widget1);
|
||||
SetConditionData(*m);
|
||||
widget2->SetRootNode(root);
|
||||
widget1->SetRootNode(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MoveMacroConditionUp(int idx)
|
||||
@@ -379,6 +444,7 @@ void AdvSceneSwitcher::MoveMacroConditionUp(int idx)
|
||||
}
|
||||
|
||||
SwapConditions(macro, idx, idx - 1);
|
||||
HighlightCondition(idx - 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MoveMacroConditionDown(int idx)
|
||||
@@ -393,4 +459,71 @@ void AdvSceneSwitcher::MoveMacroConditionDown(int idx)
|
||||
}
|
||||
|
||||
SwapConditions(macro, idx, idx + 1);
|
||||
HighlightCondition(idx + 1);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
conditionsList->SetSelection(idx);
|
||||
actionsList->SetSelection(-1);
|
||||
|
||||
if (idx < 0 || (unsigned)idx >= macro->Conditions().size()) {
|
||||
currentConditionIdx = -1;
|
||||
} else {
|
||||
currentConditionIdx = idx;
|
||||
lastInteracted = MacroSection::CONDITIONS;
|
||||
}
|
||||
currentActionIdx = -1;
|
||||
HighlightControls();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from < 0 || from > (int)macro->Conditions().size() || to < 0 ||
|
||||
to > (int)macro->Conditions().size()) {
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
auto condition = macro->Conditions().at(from);
|
||||
if (to == 0) {
|
||||
condition->SetLogicType(LogicType::ROOT_NONE);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(from))
|
||||
->SetRootNode(true);
|
||||
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(0))
|
||||
->SetRootNode(false);
|
||||
}
|
||||
if (from == 0) {
|
||||
condition->SetLogicType(LogicType::AND);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(from))
|
||||
->SetRootNode(false);
|
||||
macro->Conditions().at(1)->SetLogicType(
|
||||
LogicType::ROOT_NONE);
|
||||
static_cast<MacroConditionEdit *>(
|
||||
conditionsList->WidgetAt(1))
|
||||
->SetRootNode(true);
|
||||
}
|
||||
macro->Conditions().erase(macro->Conditions().begin() + from);
|
||||
macro->Conditions().insert(macro->Conditions().begin() + to,
|
||||
condition);
|
||||
macro->UpdateConditionIndices();
|
||||
conditionsList->ContentLayout()->insertItem(
|
||||
to, conditionsList->ContentLayout()->takeAt(from));
|
||||
SetConditionData(*macro);
|
||||
}
|
||||
HighlightCondition(to);
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ MacroConditionFileEdit::MacroConditionFileEdit(
|
||||
{
|
||||
_fileType = new QComboBox();
|
||||
_filePath = new FileSelection();
|
||||
_matchText = new QPlainTextEdit();
|
||||
_matchText = new ResizingPlainTextEdit(this);
|
||||
_useRegex = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.fileTab.useRegExp"));
|
||||
_checkModificationDate = new QCheckBox(obs_module_text(
|
||||
@@ -208,6 +208,9 @@ void MacroConditionFileEdit::UpdateEntryData()
|
||||
_useRegex->setChecked(_entryData->_useRegex);
|
||||
_checkModificationDate->setChecked(_entryData->_useTime);
|
||||
_checkFileContent->setChecked(_entryData->_onlyMatchIfChanged);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::FileTypeChanged(int index)
|
||||
@@ -250,6 +253,9 @@ void MacroConditionFileEdit::MatchTextChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_text = _matchText->toPlainText().toUtf8().constData();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFileEdit::UseRegexChanged(int state)
|
||||
|
||||
@@ -100,7 +100,7 @@ MacroConditionFilterEdit::MacroConditionFilterEdit(
|
||||
_conditions = new QComboBox();
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.filter.getSettings"));
|
||||
_settings = new QPlainTextEdit();
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_regex = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.condition.filter.regex"));
|
||||
|
||||
@@ -208,6 +208,9 @@ void MacroConditionFilterEdit::SettingsChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionFilterEdit::RegexChanged(int state)
|
||||
@@ -244,4 +247,7 @@ void MacroConditionFilterEdit::UpdateEntryData()
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
||||
FilterCondition::SETTINGS);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ static std::map<MediaState, std::string> mediaStates = {
|
||||
"AdvSceneSwitcher.mediaTab.states.ended"},
|
||||
{MediaState::OBS_MEDIA_STATE_ERROR,
|
||||
"AdvSceneSwitcher.mediaTab.states.error"},
|
||||
{MediaState::PLAYED_TO_END,
|
||||
"AdvSceneSwitcher.mediaTab.states.playedToEnd"},
|
||||
{MediaState::PLAYLIST_ENDED,
|
||||
"AdvSceneSwitcher.mediaTab.states.playlistEnd"},
|
||||
{MediaState::ANY, "AdvSceneSwitcher.mediaTab.states.any"},
|
||||
};
|
||||
|
||||
@@ -51,111 +51,113 @@ MacroConditionMedia::~MacroConditionMedia()
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_disconnect(sh, "media_next", MediaNext, this);
|
||||
obs_source_release(mediasource);
|
||||
}
|
||||
|
||||
bool matchTime(const int64_t currentTime, const int64_t duration,
|
||||
const MediaTimeRestriction restriction, const int64_t time)
|
||||
bool MacroConditionMedia::CheckTime()
|
||||
{
|
||||
bool matchedTimeNone =
|
||||
(restriction == MediaTimeRestriction::TIME_RESTRICTION_NONE);
|
||||
bool matchedTimeLonger =
|
||||
(restriction ==
|
||||
MediaTimeRestriction::TIME_RESTRICTION_LONGER) &&
|
||||
(currentTime > time);
|
||||
bool matchedTimeShorter =
|
||||
(restriction ==
|
||||
MediaTimeRestriction::TIME_RESTRICTION_SHORTER) &&
|
||||
(currentTime < time);
|
||||
bool matchedTimeRemainLonger =
|
||||
(restriction ==
|
||||
MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER) &&
|
||||
(duration > currentTime && duration - currentTime > time);
|
||||
bool matchedTimeRemainShorter =
|
||||
(restriction ==
|
||||
MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER) &&
|
||||
(duration > currentTime && duration - currentTime < time);
|
||||
obs_source_t *s = obs_weak_source_get_source(_source);
|
||||
auto duration = obs_source_media_get_duration(s);
|
||||
auto currentTime = obs_source_media_get_time(s);
|
||||
obs_source_release(s);
|
||||
|
||||
return matchedTimeNone || matchedTimeLonger || matchedTimeShorter ||
|
||||
matchedTimeRemainLonger || matchedTimeRemainShorter;
|
||||
bool match = false;
|
||||
|
||||
switch (_restriction) {
|
||||
case MediaTimeRestriction::TIME_RESTRICTION_NONE:
|
||||
match = true;
|
||||
break;
|
||||
case MediaTimeRestriction::TIME_RESTRICTION_SHORTER:
|
||||
match = currentTime < _time.seconds * 1000;
|
||||
break;
|
||||
case MediaTimeRestriction::TIME_RESTRICTION_LONGER:
|
||||
match = currentTime > _time.seconds * 1000;
|
||||
break;
|
||||
case MediaTimeRestriction::TIME_RESTRICTION_REMAINING_SHORTER:
|
||||
match = duration > currentTime &&
|
||||
duration - currentTime < _time.seconds * 1000;
|
||||
break;
|
||||
case MediaTimeRestriction::TIME_RESTRICTION_REMAINING_LONGER:
|
||||
match = duration > currentTime &&
|
||||
duration - currentTime > _time.seconds * 1000;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckState()
|
||||
{
|
||||
obs_source_t *s = obs_weak_source_get_source(_source);
|
||||
obs_media_state currentState = obs_source_media_get_state(s);
|
||||
obs_source_release(s);
|
||||
|
||||
bool match = false;
|
||||
// To be able to compare to obs_media_state more easily
|
||||
int expectedState = static_cast<int>(_state);
|
||||
|
||||
switch (_state) {
|
||||
case MediaState::OBS_MEDIA_STATE_STOPPED:
|
||||
match = _stopped || currentState == OBS_MEDIA_STATE_STOPPED;
|
||||
break;
|
||||
case MediaState::OBS_MEDIA_STATE_ENDED:
|
||||
match = _ended || currentState == OBS_MEDIA_STATE_ENDED;
|
||||
break;
|
||||
case MediaState::PLAYLIST_ENDED:
|
||||
match = CheckPlaylistEnd(currentState);
|
||||
break;
|
||||
case MediaState::ANY:
|
||||
match = true;
|
||||
break;
|
||||
case MediaState::OBS_MEDIA_STATE_NONE:
|
||||
case MediaState::OBS_MEDIA_STATE_PLAYING:
|
||||
case MediaState::OBS_MEDIA_STATE_OPENING:
|
||||
case MediaState::OBS_MEDIA_STATE_BUFFERING:
|
||||
case MediaState::OBS_MEDIA_STATE_PAUSED:
|
||||
case MediaState::OBS_MEDIA_STATE_ERROR:
|
||||
match = currentState == expectedState;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckPlaylistEnd(const obs_media_state currentState)
|
||||
{
|
||||
bool consecutiveEndedStates = false;
|
||||
if (_next || currentState != OBS_MEDIA_STATE_ENDED) {
|
||||
_previousStateEnded = false;
|
||||
}
|
||||
if (currentState == OBS_MEDIA_STATE_ENDED && _previousStateEnded) {
|
||||
consecutiveEndedStates = true;
|
||||
}
|
||||
_previousStateEnded = _ended || currentState == OBS_MEDIA_STATE_ENDED;
|
||||
return consecutiveEndedStates;
|
||||
}
|
||||
|
||||
bool MacroConditionMedia::CheckMediaMatch()
|
||||
{
|
||||
|
||||
if (!_source) {
|
||||
return false;
|
||||
}
|
||||
bool match = false;
|
||||
|
||||
obs_source_t *s = obs_weak_source_get_source(_source);
|
||||
auto duration = obs_source_media_get_duration(s);
|
||||
auto time = obs_source_media_get_time(s);
|
||||
obs_media_state currentState = obs_source_media_get_state(s);
|
||||
obs_source_release(s);
|
||||
|
||||
// To be able to compare to obs_media_state more easily
|
||||
int expectedState = static_cast<int>(_state);
|
||||
|
||||
bool matchedStopped = expectedState == OBS_MEDIA_STATE_STOPPED &&
|
||||
_stopped;
|
||||
|
||||
// The signal for the state ended is intentionally not used here
|
||||
// so matchedEnded can be used to specify the end of a VLC playlist
|
||||
// by two consequtive matches of OBS_MEDIA_STATE_ENDED
|
||||
//
|
||||
// This was done to reduce the likelyhood of interpreting a single
|
||||
// OBS_MEDIA_STATE_ENDED caught by obs_source_media_get_state()
|
||||
// as the end of the playlist of the VLC source, while actually being
|
||||
// in the middle of switching to the next item of the playlist
|
||||
//
|
||||
// If there is a separate obs_media_sate in the future for the
|
||||
// "end of playlist reached" signal the line below can be used
|
||||
// and an additional check for this new singal can be introduced
|
||||
//
|
||||
// bool matchedEnded = _state == OBS_MEDIA_STATE_ENDED && _ended;
|
||||
|
||||
bool ended = false;
|
||||
|
||||
if (currentState == OBS_MEDIA_STATE_ENDED) {
|
||||
ended = _previousStateEnded;
|
||||
_previousStateEnded = true;
|
||||
} else {
|
||||
_previousStateEnded = false;
|
||||
}
|
||||
|
||||
bool matchedEnded = ended && (expectedState == OBS_MEDIA_STATE_ENDED);
|
||||
|
||||
// match if playedToEnd was true in last interval
|
||||
// and playback is currently ended
|
||||
bool matchedPlayedToEnd = _state == MediaState::PLAYED_TO_END &&
|
||||
_playedToEnd && ended;
|
||||
|
||||
// interval * 2 to make sure not to miss any state changes
|
||||
// which happened during check of the conditions
|
||||
_playedToEnd = _playedToEnd ||
|
||||
(duration - time <= switcher->interval * 2);
|
||||
|
||||
// reset for next check
|
||||
if (ended) {
|
||||
_playedToEnd = false;
|
||||
}
|
||||
_stopped = false;
|
||||
_ended = false;
|
||||
|
||||
bool matchedState =
|
||||
(currentState == expectedState || _state == MediaState::ANY) ||
|
||||
matchedStopped || matchedEnded || matchedPlayedToEnd;
|
||||
|
||||
bool matchedTime =
|
||||
matchTime(time, duration, _restriction, _time.seconds * 1000);
|
||||
bool matched = matchedState && matchedTime;
|
||||
bool matched = CheckState() && CheckTime();
|
||||
|
||||
if (matched && !(_onlyMatchonChagne && _alreadyMatched)) {
|
||||
match = true;
|
||||
}
|
||||
_alreadyMatched = matched;
|
||||
|
||||
// reset for next check
|
||||
_stopped = false;
|
||||
_ended = false;
|
||||
_next = false;
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
@@ -195,6 +197,7 @@ bool MacroConditionMedia::Save(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "restriction", static_cast<int>(_restriction));
|
||||
_time.Save(obj);
|
||||
obs_data_set_bool(obj, "matchOnChagne", _onlyMatchonChagne);
|
||||
obs_data_set_int(obj, "version", 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -250,11 +253,7 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
|
||||
_restriction = static_cast<MediaTimeRestriction>(
|
||||
obs_data_get_int(obj, "restriction"));
|
||||
_time.Load(obj);
|
||||
if (!obs_data_has_user_value(obj, "matchOnChagne")) {
|
||||
_onlyMatchonChagne = true;
|
||||
} else {
|
||||
_onlyMatchonChagne = obs_data_get_bool(obj, "matchOnChagne");
|
||||
}
|
||||
_onlyMatchonChagne = obs_data_get_bool(obj, "matchOnChagne");
|
||||
|
||||
if (_sourceType == MediaSourceType::SOURCE) {
|
||||
obs_source_t *mediasource = obs_weak_source_get_source(_source);
|
||||
@@ -262,11 +261,17 @@ bool MacroConditionMedia::Load(obs_data_t *obj)
|
||||
obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_connect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_connect(sh, "media_next", MediaNext, this);
|
||||
obs_source_release(mediasource);
|
||||
}
|
||||
|
||||
forMediaSourceOnSceneAddMediaCondition(_scene.GetScene(), this,
|
||||
_sources);
|
||||
if (!obs_data_has_user_value(obj, "version")) {
|
||||
if (_state == MediaState::OBS_MEDIA_STATE_ENDED) {
|
||||
_state = MediaState::PLAYLIST_ENDED;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -304,6 +309,7 @@ void MacroConditionMedia::ClearSignalHandler()
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_disconnect(sh, "media_next", MediaNext, this);
|
||||
obs_source_release(mediasource);
|
||||
}
|
||||
|
||||
@@ -313,8 +319,10 @@ void MacroConditionMedia::ResetSignalHandler()
|
||||
signal_handler_t *sh = obs_source_get_signal_handler(mediasource);
|
||||
signal_handler_disconnect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_disconnect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_disconnect(sh, "media_next", MediaNext, this);
|
||||
signal_handler_connect(sh, "media_stopped", MediaStopped, this);
|
||||
signal_handler_connect(sh, "media_ended", MediaEnded, this);
|
||||
signal_handler_connect(sh, "media_next", MediaNext, this);
|
||||
obs_source_release(mediasource);
|
||||
}
|
||||
|
||||
@@ -330,6 +338,12 @@ void MacroConditionMedia::MediaEnded(void *data, calldata_t *)
|
||||
media->_ended = true;
|
||||
}
|
||||
|
||||
void MacroConditionMedia::MediaNext(void *data, calldata_t *)
|
||||
{
|
||||
MacroConditionMedia *media = static_cast<MacroConditionMedia *>(data);
|
||||
media->_next = true;
|
||||
}
|
||||
|
||||
static void populateMediaTimeRestrictions(QComboBox *list)
|
||||
{
|
||||
for (auto entry : mediaTimeRestrictions) {
|
||||
@@ -356,15 +370,18 @@ static void addAnyAndAllStates(QComboBox *list)
|
||||
|
||||
MacroConditionMediaEdit::MacroConditionMediaEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionMedia> entryData)
|
||||
: QWidget(parent)
|
||||
: QWidget(parent),
|
||||
_scenes(new SceneSelectionWidget(window())),
|
||||
_mediaSources(new QComboBox()),
|
||||
_states(new QComboBox()),
|
||||
_timeRestrictions(new QComboBox()),
|
||||
_time(new DurationSelection()),
|
||||
_onChange(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.media.matchOnChange")))
|
||||
|
||||
{
|
||||
_mediaSources = new QComboBox();
|
||||
_scenes = new SceneSelectionWidget(window());
|
||||
_states = new QComboBox();
|
||||
_timeRestrictions = new QComboBox();
|
||||
_time = new DurationSelection();
|
||||
_onChange = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.media.matchOnChange"));
|
||||
_states->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.media.inconsistencyInfo"));
|
||||
|
||||
QWidget::connect(_mediaSources,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
@@ -548,6 +565,9 @@ void MacroConditionMediaEdit::OnChangeChanged(int value)
|
||||
void MacroConditionMediaEdit::SetWidgetVisibility()
|
||||
{
|
||||
_scenes->setVisible(_entryData->_sourceType != MediaSourceType::SOURCE);
|
||||
if (!_onChange->isChecked()) {
|
||||
_onChange->hide();
|
||||
}
|
||||
}
|
||||
|
||||
int getIdxFromMediaState(MediaState state)
|
||||
|
||||
579
src/macro-condition-obs-stats.cpp
Normal file
579
src/macro-condition-obs-stats.cpp
Normal file
@@ -0,0 +1,579 @@
|
||||
#include "headers/macro-condition-edit.hpp"
|
||||
#include "headers/macro-condition-obs-stats.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
|
||||
const std::string MacroConditionStats::id = "obs_stats";
|
||||
|
||||
bool MacroConditionStats::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionStats::id,
|
||||
{MacroConditionStats::Create, MacroConditionStatsEdit::Create,
|
||||
"AdvSceneSwitcher.condition.stats"});
|
||||
|
||||
static std::map<StatsType, std::string> statsTypes = {
|
||||
{StatsType::FPS, "AdvSceneSwitcher.condition.stats.type.fps"},
|
||||
{StatsType::CPU_USAGE,
|
||||
"AdvSceneSwitcher.condition.stats.type.CPUUsage"},
|
||||
{StatsType::DISK_USAGE,
|
||||
"AdvSceneSwitcher.condition.stats.type.HDDSpaceAvailable"},
|
||||
{StatsType::MEM_USAGE,
|
||||
"AdvSceneSwitcher.condition.stats.type.memoryUsage"},
|
||||
{StatsType::AVG_FRAMETIME,
|
||||
"AdvSceneSwitcher.condition.stats.type.averageTimeToRender"},
|
||||
{StatsType::RENDER_LAG,
|
||||
"AdvSceneSwitcher.condition.stats.type.missedFrames"},
|
||||
{StatsType::ENCODE_LAG,
|
||||
"AdvSceneSwitcher.condition.stats.type.skippedFrames"},
|
||||
{StatsType::STREAM_DROPPED_FRAMES,
|
||||
"AdvSceneSwitcher.condition.stats.type.droppedFrames.stream"},
|
||||
{StatsType::STREAM_BITRATE,
|
||||
"AdvSceneSwitcher.condition.stats.type.bitrate.stream"},
|
||||
{StatsType::STREAM_MB_SENT,
|
||||
"AdvSceneSwitcher.condition.stats.type.megabytesSent.stream"},
|
||||
{StatsType::RECORDING_DROPPED_FRAMES,
|
||||
"AdvSceneSwitcher.condition.stats.type.droppedFrames.recording"},
|
||||
{StatsType::RECORDING_BITRATE,
|
||||
"AdvSceneSwitcher.condition.stats.type.bitrate.recording"},
|
||||
{StatsType::RECORDING_MB_SENT,
|
||||
"AdvSceneSwitcher.condition.stats.type.megabytesSent.recording"},
|
||||
};
|
||||
|
||||
static std::map<StatsCondition, std::string> statsConditionTypes = {
|
||||
{StatsCondition::ABOVE,
|
||||
"AdvSceneSwitcher.condition.stats.condition.above"},
|
||||
{StatsCondition::EQUALS,
|
||||
"AdvSceneSwitcher.condition.stats.condition.equals"},
|
||||
{StatsCondition::BELOW,
|
||||
"AdvSceneSwitcher.condition.stats.condition.below"},
|
||||
};
|
||||
|
||||
MacroConditionStats::MacroConditionStats(Macro *m)
|
||||
: MacroCondition(m), _cpu_info(os_cpu_usage_info_start())
|
||||
{
|
||||
}
|
||||
|
||||
MacroConditionStats::~MacroConditionStats()
|
||||
{
|
||||
os_cpu_usage_info_destroy(_cpu_info);
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckFPS()
|
||||
{
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return obs_get_active_fps() > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(obs_get_active_fps(), _value, 0.01);
|
||||
case StatsCondition::BELOW:
|
||||
return obs_get_active_fps() < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckCPU()
|
||||
{
|
||||
double usage = os_cpu_usage_info_query(_cpu_info);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return usage > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(usage, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return usage < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckMemory()
|
||||
{
|
||||
auto rss =
|
||||
(long double)os_get_proc_resident_size() / (1024.0l * 1024.0l);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return rss > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(rss, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return rss < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckAvgFrametime()
|
||||
{
|
||||
auto num = (long double)obs_get_average_frame_time_ns() / 1000000.0l;
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return num > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(num, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRenderLag()
|
||||
{
|
||||
uint32_t total_rendered = obs_get_total_frames();
|
||||
uint32_t total_lagged = obs_get_lagged_frames();
|
||||
|
||||
if (total_rendered < _first_rendered || total_lagged < _first_lagged) {
|
||||
_first_rendered = total_rendered;
|
||||
_first_lagged = total_lagged;
|
||||
}
|
||||
total_rendered -= _first_rendered;
|
||||
total_lagged -= _first_lagged;
|
||||
|
||||
auto num = total_rendered ? (long double)total_lagged /
|
||||
(long double)total_rendered
|
||||
: 0.0l;
|
||||
num *= 100.0l;
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return num > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(num, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckEncodeLag()
|
||||
{
|
||||
video_t *video = obs_get_video();
|
||||
uint32_t total_encoded = video_output_get_total_frames(video);
|
||||
uint32_t total_skipped = video_output_get_skipped_frames(video);
|
||||
|
||||
if (total_encoded < _first_encoded || total_skipped < _first_encoded) {
|
||||
_first_encoded = total_encoded;
|
||||
_first_skipped = total_skipped;
|
||||
}
|
||||
total_encoded -= _first_encoded;
|
||||
total_skipped -= _first_skipped;
|
||||
|
||||
auto num = total_encoded ? (long double)total_skipped /
|
||||
(long double)total_encoded
|
||||
: 0.0l;
|
||||
num *= 100.0l;
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return num > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(num, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroConditionStats::OutputInfo::Update(obs_output_t *output)
|
||||
{
|
||||
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
||||
uint64_t curTime = os_gettime_ns();
|
||||
uint64_t bytesSent = totalBytes;
|
||||
|
||||
if (bytesSent < lastBytesSent)
|
||||
bytesSent = 0;
|
||||
if (bytesSent == 0)
|
||||
lastBytesSent = 0;
|
||||
|
||||
uint64_t bitsBetween = (bytesSent - lastBytesSent) * 8;
|
||||
long double timePassed =
|
||||
(long double)(curTime - lastBytesSentTime) / 1000000000.0l;
|
||||
kbps = (long double)bitsBetween / timePassed / 1000.0l;
|
||||
|
||||
if (timePassed < 0.01l)
|
||||
kbps = 0.0l;
|
||||
|
||||
int total = output ? obs_output_get_total_frames(output) : 0;
|
||||
int dropped = output ? obs_output_get_frames_dropped(output) : 0;
|
||||
|
||||
if (total < first_total || dropped < first_dropped) {
|
||||
first_total = 0;
|
||||
first_dropped = 0;
|
||||
}
|
||||
|
||||
total -= first_total;
|
||||
dropped -= first_dropped;
|
||||
|
||||
dropped_relative =
|
||||
total ? (long double)dropped / (long double)total * 100.0l
|
||||
: 0.0l;
|
||||
|
||||
lastBytesSent = bytesSent;
|
||||
lastBytesSentTime = curTime;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckStreamDroppedFrames()
|
||||
{
|
||||
auto output = obs_frontend_get_streaming_output();
|
||||
_streamInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return _streamInfo.dropped_relative > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(_streamInfo.dropped_relative, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return _streamInfo.dropped_relative < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckStreamBitrate()
|
||||
{
|
||||
auto output = obs_frontend_get_streaming_output();
|
||||
_streamInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return _streamInfo.kbps > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(_streamInfo.kbps, _value, 1.0);
|
||||
case StatsCondition::BELOW:
|
||||
return _streamInfo.kbps < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckStreamMBSent()
|
||||
{
|
||||
auto output = obs_frontend_get_streaming_output();
|
||||
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
||||
obs_output_release(output);
|
||||
long double num = (long double)totalBytes / (1024.0l * 1024.0l);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return num > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(num, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRecordingDroppedFrames()
|
||||
{
|
||||
auto output = obs_frontend_get_recording_output();
|
||||
_recordingInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return _recordingInfo.dropped_relative > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(_recordingInfo.dropped_relative, _value,
|
||||
0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return _recordingInfo.dropped_relative < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRecordingBitrate()
|
||||
{
|
||||
auto output = obs_frontend_get_recording_output();
|
||||
_recordingInfo.Update(output);
|
||||
obs_output_release(output);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return _recordingInfo.kbps > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(_recordingInfo.kbps, _value, 1.0);
|
||||
case StatsCondition::BELOW:
|
||||
return _recordingInfo.kbps < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckRecordingMBSent()
|
||||
{
|
||||
auto output = obs_frontend_get_recording_output();
|
||||
uint64_t totalBytes = output ? obs_output_get_total_bytes(output) : 0;
|
||||
obs_output_release(output);
|
||||
long double num = (long double)totalBytes / (1024.0l * 1024.0l);
|
||||
|
||||
switch (_condition) {
|
||||
case StatsCondition::ABOVE:
|
||||
return num > _value;
|
||||
case StatsCondition::EQUALS:
|
||||
return doubleEquals(num, _value, 0.1);
|
||||
case StatsCondition::BELOW:
|
||||
return num < _value;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::CheckCondition()
|
||||
{
|
||||
switch (_type) {
|
||||
case StatsType::FPS:
|
||||
return CheckFPS();
|
||||
case StatsType::CPU_USAGE:
|
||||
return CheckCPU();
|
||||
case StatsType::DISK_USAGE:
|
||||
// TODO: not implemented
|
||||
break;
|
||||
case StatsType::MEM_USAGE:
|
||||
return CheckMemory();
|
||||
case StatsType::AVG_FRAMETIME:
|
||||
return CheckAvgFrametime();
|
||||
case StatsType::RENDER_LAG:
|
||||
return CheckRenderLag();
|
||||
case StatsType::ENCODE_LAG:
|
||||
return CheckEncodeLag();
|
||||
case StatsType::STREAM_DROPPED_FRAMES:
|
||||
return CheckStreamDroppedFrames();
|
||||
case StatsType::STREAM_BITRATE:
|
||||
return CheckStreamBitrate();
|
||||
case StatsType::STREAM_MB_SENT:
|
||||
return CheckStreamMBSent();
|
||||
case StatsType::RECORDING_DROPPED_FRAMES:
|
||||
return CheckRecordingDroppedFrames();
|
||||
case StatsType::RECORDING_BITRATE:
|
||||
return CheckRecordingBitrate();
|
||||
case StatsType::RECORDING_MB_SENT:
|
||||
return CheckRecordingMBSent();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_double(obj, "value", _value);
|
||||
obs_data_set_int(obj, "type", static_cast<int>(_type));
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionStats::Load(obs_data_t *obj)
|
||||
{
|
||||
|
||||
MacroCondition::Load(obj);
|
||||
_value = obs_data_get_double(obj, "value");
|
||||
_type = static_cast<StatsType>(obs_data_get_int(obj, "type"));
|
||||
_condition =
|
||||
static_cast<StatsCondition>(obs_data_get_int(obj, "condition"));
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionStats::GetShortDesc()
|
||||
{
|
||||
auto it = statsTypes.find(_type);
|
||||
if (it != statsTypes.end()) {
|
||||
return obs_module_text(it->second.c_str());
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static inline void populateStatsTypes(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (auto entry : statsTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
// TODO: not implemented
|
||||
if (entry.first == StatsType::DISK_USAGE) {
|
||||
qobject_cast<QListView *>(list->view())
|
||||
->setRowHidden(list->count() - 1, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->clear();
|
||||
for (auto entry : statsConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
MacroConditionStatsEdit::MacroConditionStatsEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionStats> entryData)
|
||||
: QWidget(parent),
|
||||
_stats(new QComboBox),
|
||||
_condition(new QComboBox),
|
||||
_value(new QDoubleSpinBox)
|
||||
{
|
||||
_value->setMaximum(999999999999);
|
||||
|
||||
populateStatsTypes(_stats);
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.stats.dockHint"));
|
||||
|
||||
QWidget::connect(_value, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(ValueChanged(double)));
|
||||
QWidget::connect(_stats, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(StatsTypeChanged(int)));
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{value}}", _value},
|
||||
{"{{stats}}", _stats},
|
||||
{"{{condition}}", _condition},
|
||||
};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.condition.stats.entry"),
|
||||
layout, widgetPlaceholders);
|
||||
setLayout(layout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::ValueChanged(double value)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_value = value;
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::StatsTypeChanged(int type)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_type = static_cast<StatsType>(type);
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
_value->setValue(0);
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_condition = static_cast<StatsCondition>(cond);
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_value->setValue(_entryData->_value);
|
||||
_stats->setCurrentIndex(static_cast<int>(_entryData->_type));
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
SetWidgetVisibility();
|
||||
}
|
||||
|
||||
void MacroConditionStatsEdit::SetWidgetVisibility()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_entryData->_type) {
|
||||
case StatsType::FPS:
|
||||
_value->setMaximum(1000);
|
||||
_value->setSuffix("");
|
||||
break;
|
||||
case StatsType::CPU_USAGE:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case StatsType::DISK_USAGE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
case StatsType::MEM_USAGE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
case StatsType::AVG_FRAMETIME:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("ms");
|
||||
break;
|
||||
case StatsType::RENDER_LAG:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case StatsType::ENCODE_LAG:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case StatsType::STREAM_DROPPED_FRAMES:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case StatsType::STREAM_BITRATE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("kb/s");
|
||||
break;
|
||||
case StatsType::STREAM_MB_SENT:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
case StatsType::RECORDING_DROPPED_FRAMES:
|
||||
_value->setMaximum(100);
|
||||
_value->setSuffix("%");
|
||||
break;
|
||||
case StatsType::RECORDING_BITRATE:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("kb/s");
|
||||
break;
|
||||
case StatsType::RECORDING_MB_SENT:
|
||||
_value->setMaximum(999999999999);
|
||||
_value->setSuffix("MB");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
@@ -12,19 +12,30 @@ bool MacroConditionPluginState::_registered = MacroConditionFactory::Register(
|
||||
"AdvSceneSwitcher.condition.pluginState"});
|
||||
|
||||
static std::map<PluginStateCondition, std::string> pluginStateConditionTypes = {
|
||||
{PluginStateCondition::SCENESWITCHED,
|
||||
{PluginStateCondition::SCENE_SWITCHED,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.sceneSwitched"},
|
||||
{PluginStateCondition::RUNNING,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.running"},
|
||||
{PluginStateCondition::SHUTDOWN,
|
||||
"AdvSceneSwitcher.condition.pluginState.state.shutdown"},
|
||||
};
|
||||
|
||||
MacroConditionPluginState::~MacroConditionPluginState()
|
||||
{
|
||||
if (_condition == PluginStateCondition::SHUTDOWN) {
|
||||
switcher->shutdownConditionCount--;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroConditionPluginState::CheckCondition()
|
||||
{
|
||||
switch (_condition) {
|
||||
case PluginStateCondition::SCENESWITCHED:
|
||||
case PluginStateCondition::SCENE_SWITCHED:
|
||||
return switcher->macroSceneSwitched;
|
||||
case PluginStateCondition::RUNNING:
|
||||
return true;
|
||||
case PluginStateCondition::SHUTDOWN:
|
||||
return switcher->obsIsShuttingDown;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -43,6 +54,9 @@ bool MacroConditionPluginState::Load(obs_data_t *obj)
|
||||
MacroCondition::Load(obj);
|
||||
_condition = static_cast<PluginStateCondition>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
if (_condition == PluginStateCondition::SHUTDOWN) {
|
||||
switcher->shutdownConditionCount++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -89,7 +103,13 @@ void MacroConditionPluginStateEdit::ConditionChanged(int cond)
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
if (_entryData->_condition == PluginStateCondition::SHUTDOWN) {
|
||||
switcher->shutdownConditionCount--;
|
||||
}
|
||||
_entryData->_condition = static_cast<PluginStateCondition>(cond);
|
||||
if (_entryData->_condition == PluginStateCondition::SHUTDOWN) {
|
||||
switcher->shutdownConditionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void MacroConditionPluginStateEdit::UpdateEntryData()
|
||||
|
||||
@@ -73,7 +73,7 @@ MacroConditionSceneTransformEdit::MacroConditionSceneTransformEdit(
|
||||
parent, true, SceneItemSelectionWidget::AllSelectionType::ANY);
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.getTransform"));
|
||||
_settings = new QPlainTextEdit();
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_regex = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.sceneTransform.regex"));
|
||||
|
||||
@@ -134,6 +134,9 @@ void MacroConditionSceneTransformEdit::UpdateEntryData()
|
||||
_sources->SetSceneItem(_entryData->_source);
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
_settings->setPlainText(QString::fromStdString(_entryData->_settings));
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::SceneChanged(const SceneSelection &s)
|
||||
@@ -190,6 +193,9 @@ void MacroConditionSceneTransformEdit::SettingsChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSceneTransformEdit::RegexChanged(int state)
|
||||
|
||||
@@ -94,7 +94,7 @@ MacroConditionSourceEdit::MacroConditionSourceEdit(
|
||||
_conditions = new QComboBox();
|
||||
_getSettings = new QPushButton(obs_module_text(
|
||||
"AdvSceneSwitcher.condition.source.getSettings"));
|
||||
_settings = new QPlainTextEdit();
|
||||
_settings = new ResizingPlainTextEdit(this);
|
||||
_regex = new QCheckBox(
|
||||
obs_module_text("AdvSceneSwitcher.condition.source.regex"));
|
||||
|
||||
@@ -185,6 +185,9 @@ void MacroConditionSourceEdit::SettingsChanged()
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_settings = _settings->toPlainText().toStdString();
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
void MacroConditionSourceEdit::RegexChanged(int state)
|
||||
@@ -218,4 +221,7 @@ void MacroConditionSourceEdit::UpdateEntryData()
|
||||
_regex->setChecked(_entryData->_regex);
|
||||
SetSettingsSelectionVisible(_entryData->_condition ==
|
||||
SourceCondition::SETTINGS);
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
|
||||
344
src/macro-condition-weekday.cpp
Normal file
344
src/macro-condition-weekday.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
#include "headers/macro-condition-edit.hpp"
|
||||
#include "headers/macro-condition-date.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
|
||||
const std::string MacroConditionDate::id = "date";
|
||||
|
||||
bool MacroConditionDate::_registered = MacroConditionFactory::Register(
|
||||
MacroConditionDate::id,
|
||||
{MacroConditionDate::Create, MacroConditionDateEdit::Create,
|
||||
"AdvSceneSwitcher.condition.date", false});
|
||||
|
||||
static std::map<DateCondition, std::string> dateConditionTypes = {
|
||||
{DateCondition::AT, "AdvSceneSwitcher.condition.date.state.at"},
|
||||
{DateCondition::AFTER, "AdvSceneSwitcher.condition.date.state.after"},
|
||||
{DateCondition::BEFORE, "AdvSceneSwitcher.condition.date.state.before"},
|
||||
{DateCondition::BETWEEN,
|
||||
"AdvSceneSwitcher.condition.date.state.between"},
|
||||
};
|
||||
|
||||
bool MacroConditionDate::CheckCondition()
|
||||
{
|
||||
bool match = false;
|
||||
QDateTime cur = QDateTime::currentDateTime();
|
||||
if (_ignoreDate) {
|
||||
_dateTime.setDate(cur.date());
|
||||
_dateTime2.setDate(cur.date());
|
||||
}
|
||||
if (_ignoreTime) {
|
||||
_dateTime.setTime(cur.time());
|
||||
_dateTime2.setTime(cur.time());
|
||||
}
|
||||
|
||||
switch (_condition) {
|
||||
case DateCondition::AT:
|
||||
match = _dateTime >= cur &&
|
||||
_dateTime <= cur.addMSecs(switcher->interval);
|
||||
break;
|
||||
case DateCondition::AFTER:
|
||||
match = cur >= _dateTime;
|
||||
break;
|
||||
case DateCondition::BEFORE:
|
||||
match = cur <= _dateTime;
|
||||
break;
|
||||
case DateCondition::BETWEEN:
|
||||
if (_dateTime2 > _dateTime) {
|
||||
match = cur >= _dateTime && cur <= _dateTime2;
|
||||
} else {
|
||||
match = cur >= _dateTime2 && cur <= _dateTime;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (match && _repeat) {
|
||||
_dateTime = _dateTime.addSecs(_duration.seconds);
|
||||
_dateTime2 = _dateTime2.addSecs(_duration.seconds);
|
||||
}
|
||||
|
||||
return match;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::Save(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Save(obj);
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
|
||||
obs_data_set_string(obj, "dateTime",
|
||||
_dateTime.toString().toStdString().c_str());
|
||||
obs_data_set_string(obj, "dateTime2",
|
||||
_dateTime2.toString().toStdString().c_str());
|
||||
obs_data_set_bool(obj, "ignoreDate", _ignoreDate);
|
||||
obs_data_set_bool(obj, "ignoreTime", _ignoreTime);
|
||||
obs_data_set_bool(obj, "repeat", _repeat);
|
||||
_duration.Save(obj);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MacroConditionDate::Load(obs_data_t *obj)
|
||||
{
|
||||
MacroCondition::Load(obj);
|
||||
_condition =
|
||||
static_cast<DateCondition>(obs_data_get_int(obj, "condition"));
|
||||
_dateTime = QDateTime::fromString(
|
||||
QString::fromStdString(obs_data_get_string(obj, "dateTime")));
|
||||
_dateTime2 = QDateTime::fromString(
|
||||
QString::fromStdString(obs_data_get_string(obj, "dateTime2")));
|
||||
_ignoreDate = obs_data_get_bool(obj, "ignoreDate");
|
||||
_ignoreTime = obs_data_get_bool(obj, "ignoreTime");
|
||||
_repeat = obs_data_get_bool(obj, "repeat");
|
||||
_duration.Load(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MacroConditionDate::GetShortDesc()
|
||||
{
|
||||
return _dateTime.toString().toStdString();
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
for (auto entry : dateConditionTypes) {
|
||||
list->addItem(obs_module_text(entry.second.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
#include <QCalendarWidget>
|
||||
|
||||
MacroConditionDateEdit::MacroConditionDateEdit(
|
||||
QWidget *parent, std::shared_ptr<MacroConditionDate> entryData)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_condition = new QComboBox();
|
||||
_date = new QDateEdit();
|
||||
_date->setDisplayFormat("yyyy.MM.dd ");
|
||||
_date->setCalendarPopup(true);
|
||||
auto cal = _date->calendarWidget();
|
||||
cal->showSelectedDate();
|
||||
_time = new QTimeEdit();
|
||||
_time->setDisplayFormat("hh:mm:ss");
|
||||
_separator = new QLabel(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.separator"));
|
||||
_date2 = new QDateEdit();
|
||||
_date2->setDisplayFormat("yyyy.MM.dd ");
|
||||
_date2->setCalendarPopup(true);
|
||||
cal = _date2->calendarWidget();
|
||||
cal->showSelectedDate();
|
||||
_time2 = new QTimeEdit();
|
||||
_time2->setDisplayFormat("hh:mm:ss");
|
||||
_ignoreDate = new QCheckBox();
|
||||
_ignoreDate->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreDate"));
|
||||
_ignoreTime = new QCheckBox();
|
||||
_ignoreTime->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.ignoreTime"));
|
||||
_repeat = new QCheckBox();
|
||||
_duration = new DurationSelection();
|
||||
|
||||
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(_date, SIGNAL(dateChanged(const QDate &)), this,
|
||||
SLOT(DateChanged(const QDate &)));
|
||||
QWidget::connect(_date2, SIGNAL(dateChanged(const QDate &)), this,
|
||||
SLOT(Date2Changed(const QDate &)));
|
||||
QWidget::connect(_time, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(TimeChanged(const QTime &)));
|
||||
QWidget::connect(_time2, SIGNAL(timeChanged(const QTime &)), this,
|
||||
SLOT(Time2Changed(const QTime &)));
|
||||
QWidget::connect(_ignoreDate, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreDateChanged(int)));
|
||||
QWidget::connect(_ignoreTime, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreTimeChanged(int)));
|
||||
QWidget::connect(_repeat, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(RepeatChanged(int)));
|
||||
QWidget::connect(_duration, SIGNAL(DurationChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(_duration, SIGNAL(UnitChanged(DurationUnit)), this,
|
||||
SLOT(DurationUnitChanged(DurationUnit)));
|
||||
|
||||
populateConditionSelection(_condition);
|
||||
|
||||
auto line1Layout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{condition}}", _condition},
|
||||
{"{{date}}", _date},
|
||||
{"{{time}}", _time},
|
||||
{"{{separator}}", _separator},
|
||||
{"{{date2}}", _date2},
|
||||
{"{{time2}}", _time2},
|
||||
{"{{ignoreDate}}", _ignoreDate},
|
||||
{"{{ignoreTime}}", _ignoreTime},
|
||||
{"{{repeat}}", _repeat},
|
||||
{"{{duration}}", _duration},
|
||||
};
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.line1"),
|
||||
line1Layout, widgetPlaceholders);
|
||||
auto line2Layout = new QHBoxLayout;
|
||||
placeWidgets(
|
||||
obs_module_text("AdvSceneSwitcher.condition.date.entry.line2"),
|
||||
line2Layout, widgetPlaceholders);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addLayout(line1Layout);
|
||||
mainLayout->addLayout(line2Layout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
_entryData = entryData;
|
||||
UpdateEntryData();
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ConditionChanged(int cond)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_condition = static_cast<DateCondition>(cond);
|
||||
ShowSecondDateSelection(_entryData->_condition ==
|
||||
DateCondition::BETWEEN);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DateChanged(const QDate &date)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime.setDate(date);
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::TimeChanged(const QTime &time)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime.setTime(time);
|
||||
|
||||
emit HeaderInfoChanged(
|
||||
QString::fromStdString(_entryData->GetShortDesc()));
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::Date2Changed(const QDate &date)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime2.setDate(date);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::Time2Changed(const QTime &time)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_dateTime2.setTime(time);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::IgnoreDateChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_ignoreDate = !state;
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::IgnoreTimeChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_ignoreTime = !state;
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::RepeatChanged(int state)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_repeat = state;
|
||||
_duration->setDisabled(!state);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DurationChanged(double seconds)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_duration.seconds = seconds;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::DurationUnitChanged(DurationUnit unit)
|
||||
{
|
||||
if (_loading || !_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
_entryData->_duration.displayUnit = unit;
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::UpdateEntryData()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_condition->setCurrentIndex(static_cast<int>(_entryData->_condition));
|
||||
_date->setDate(_entryData->_dateTime.date());
|
||||
_time->setTime(_entryData->_dateTime.time());
|
||||
_date2->setDate(_entryData->_dateTime2.date());
|
||||
_time2->setTime(_entryData->_dateTime2.time());
|
||||
_ignoreDate->setChecked(!_entryData->_ignoreDate);
|
||||
_ignoreTime->setChecked(!_entryData->_ignoreTime);
|
||||
_repeat->setChecked(_entryData->_repeat);
|
||||
_duration->SetDuration(_entryData->_duration);
|
||||
_duration->setDisabled(!_entryData->_repeat);
|
||||
SetWidgetStatus();
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::SetWidgetStatus()
|
||||
{
|
||||
if (!_entryData) {
|
||||
return;
|
||||
}
|
||||
|
||||
_date->setDisabled(_entryData->_ignoreDate);
|
||||
_date2->setDisabled(_entryData->_ignoreDate);
|
||||
_time->setDisabled(_entryData->_ignoreTime);
|
||||
_time2->setDisabled(_entryData->_ignoreTime);
|
||||
ShowSecondDateSelection(_entryData->_condition ==
|
||||
DateCondition::BETWEEN);
|
||||
}
|
||||
|
||||
void MacroConditionDateEdit::ShowSecondDateSelection(bool visible)
|
||||
{
|
||||
_separator->setVisible(visible);
|
||||
_date2->setVisible(visible);
|
||||
_time2->setVisible(visible);
|
||||
adjustSize();
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
#include "headers/macro-controls.hpp"
|
||||
|
||||
MacroEntryControls::MacroEntryControls(const int animationDuration,
|
||||
QWidget *parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
_add = new QPushButton(this);
|
||||
_add->setMaximumSize(QSize(22, 22));
|
||||
_add->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("addIconSmall")));
|
||||
_add->setFlat(true);
|
||||
_remove = new QPushButton(this);
|
||||
_remove->setMaximumSize(QSize(22, 22));
|
||||
_remove->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("removeIconSmall")));
|
||||
_remove->setFlat(true);
|
||||
_up = new QPushButton(this);
|
||||
_up->setMaximumSize(QSize(22, 22));
|
||||
_up->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("upArrowIconSmall")));
|
||||
_up->setFlat(true);
|
||||
_down = new QPushButton(this);
|
||||
_down->setMaximumSize(QSize(22, 22));
|
||||
_down->setProperty("themeID",
|
||||
QVariant(QString::fromUtf8("downArrowIconSmall")));
|
||||
_down->setFlat(true);
|
||||
|
||||
auto line = new QFrame(this);
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
line->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
|
||||
|
||||
auto mainLayout = new QHBoxLayout(this);
|
||||
|
||||
mainLayout->addWidget(_add);
|
||||
mainLayout->addWidget(_remove);
|
||||
|
||||
auto *separator = new QFrame(this);
|
||||
separator->setFrameShape(QFrame::VLine);
|
||||
separator->setFrameShadow(QFrame::Sunken);
|
||||
mainLayout->addWidget(separator);
|
||||
|
||||
mainLayout->addWidget(_up);
|
||||
mainLayout->addWidget(_down);
|
||||
mainLayout->addWidget(line);
|
||||
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
_animation = new QPropertyAnimation(this, "maximumHeight");
|
||||
|
||||
_animation->setDuration(animationDuration);
|
||||
_animation->setStartValue(0);
|
||||
_animation->setEndValue(mainLayout->sizeHint().height());
|
||||
|
||||
setMaximumHeight(0);
|
||||
setLayout(mainLayout);
|
||||
|
||||
connect(_add, &QPushButton::clicked, this, &MacroEntryControls::Add);
|
||||
connect(_remove, &QPushButton::clicked, this,
|
||||
&MacroEntryControls::Remove);
|
||||
connect(_up, &QPushButton::clicked, this, &MacroEntryControls::Up);
|
||||
connect(_down, &QPushButton::clicked, this, &MacroEntryControls::Down);
|
||||
}
|
||||
|
||||
void MacroEntryControls::Show(bool visible)
|
||||
{
|
||||
_animation->setDirection(visible ? QAbstractAnimation::Forward
|
||||
: QAbstractAnimation::Backward);
|
||||
_animation->start();
|
||||
}
|
||||
71
src/macro-list-entry-widget.cpp
Normal file
71
src/macro-list-entry-widget.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include "headers/macro-list-entry-widget.hpp"
|
||||
#include "headers/macro.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
MacroListEntryWidget::MacroListEntryWidget(std::shared_ptr<Macro> macro,
|
||||
bool highlight, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_name(new QLabel(QString::fromStdString(macro->Name()))),
|
||||
_running(new QCheckBox),
|
||||
_macro(macro),
|
||||
_highlightExecutedMacros(highlight)
|
||||
{
|
||||
_running->setChecked(!macro->Paused());
|
||||
|
||||
setStyleSheet("\
|
||||
QCheckBox { background-color: rgba(0,0,0,0); }\
|
||||
QLabel { background-color: rgba(0,0,0,0); }");
|
||||
|
||||
auto layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(3, 3, 3, 3);
|
||||
layout->addWidget(_running);
|
||||
layout->addWidget(_name);
|
||||
layout->addStretch();
|
||||
setLayout(layout);
|
||||
|
||||
connect(_running, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(PauseChanged(int)));
|
||||
connect(window(), SIGNAL(HighlightMacrosChanged(bool)), this,
|
||||
SLOT(EnableHighlight(bool)));
|
||||
_timer.setInterval(1500);
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(HighlightExecuted()));
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdatePaused()));
|
||||
_timer.start();
|
||||
}
|
||||
|
||||
void MacroListEntryWidget::PauseChanged(int state)
|
||||
{
|
||||
_macro->SetPaused(!state);
|
||||
}
|
||||
|
||||
void MacroListEntryWidget::SetName(const QString &name)
|
||||
{
|
||||
_name->setText(name);
|
||||
}
|
||||
|
||||
void MacroListEntryWidget::SetMacro(std::shared_ptr<Macro> &m)
|
||||
{
|
||||
_macro = m;
|
||||
}
|
||||
|
||||
void MacroListEntryWidget::EnableHighlight(bool value)
|
||||
{
|
||||
_highlightExecutedMacros = value;
|
||||
}
|
||||
|
||||
void MacroListEntryWidget::HighlightExecuted()
|
||||
{
|
||||
if (!_highlightExecutedMacros) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_macro && _macro->WasExecutedRecently()) {
|
||||
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroListEntryWidget::UpdatePaused()
|
||||
{
|
||||
const QSignalBlocker b(_running);
|
||||
_running->setChecked(!_macro->Paused());
|
||||
}
|
||||
79
src/macro-properties.cpp
Normal file
79
src/macro-properties.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "headers/macro-properties.hpp"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QDialogButtonBox>
|
||||
#include <obs-module.h>
|
||||
|
||||
void MacroProperties::Save(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_create();
|
||||
obs_data_set_bool(data, "highlightExecuted", _highlightExecuted);
|
||||
obs_data_set_bool(data, "highlightConditions", _highlightConditions);
|
||||
obs_data_set_bool(data, "highlightActions", _highlightActions);
|
||||
obs_data_set_obj(obj, "macroProperties", data);
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void MacroProperties::Load(obs_data_t *obj)
|
||||
{
|
||||
auto data = obs_data_get_obj(obj, "macroProperties");
|
||||
// TODO: Remove in future version
|
||||
if (obs_data_has_user_value(obj, "highlightExecutedMacros")) {
|
||||
_highlightExecuted =
|
||||
obs_data_get_bool(obj, "highlightExecutedMacros");
|
||||
} else {
|
||||
_highlightExecuted =
|
||||
obs_data_get_bool(data, "highlightExecuted");
|
||||
}
|
||||
_highlightConditions = obs_data_get_bool(data, "highlightConditions");
|
||||
_highlightActions = obs_data_get_bool(data, "highlightActions");
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
|
||||
const MacroProperties &prop)
|
||||
: QDialog(parent),
|
||||
_executed(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.highlightExecutedMacros"))),
|
||||
_conditions(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.highlightTrueConditions"))),
|
||||
_actions(new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.highlightPerformedActions")))
|
||||
{
|
||||
setModal(true);
|
||||
setWindowModality(Qt::WindowModality::WindowModal);
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setFixedWidth(555);
|
||||
setMinimumHeight(100);
|
||||
|
||||
_executed->setChecked(prop._highlightExecuted);
|
||||
_conditions->setChecked(prop._highlightConditions);
|
||||
_actions->setChecked(prop._highlightActions);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
layout->addWidget(_executed);
|
||||
layout->addWidget(_conditions);
|
||||
layout->addWidget(_actions);
|
||||
setLayout(layout);
|
||||
|
||||
QDialogButtonBox *buttonbox = new QDialogButtonBox(
|
||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
layout->addWidget(buttonbox);
|
||||
buttonbox->setCenterButtons(true);
|
||||
connect(buttonbox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(buttonbox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
|
||||
MacroProperties &userInput)
|
||||
{
|
||||
MacroPropertiesDialog dialog(parent, userInput);
|
||||
dialog.setWindowTitle(obs_module_text("AdvSceneSwitcher.windowTitle"));
|
||||
if (dialog.exec() != DialogCode::Accepted) {
|
||||
return false;
|
||||
}
|
||||
userInput._highlightExecuted = dialog._executed->isChecked();
|
||||
userInput._highlightConditions = dialog._conditions->isChecked();
|
||||
userInput._highlightActions = dialog._actions->isChecked();
|
||||
return true;
|
||||
}
|
||||
421
src/macro-segment-list.cpp
Normal file
421
src/macro-segment-list.cpp
Normal file
@@ -0,0 +1,421 @@
|
||||
#include "headers/macro-segment-list.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QSpacerItem>
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QDrag>
|
||||
#include <QMimeData>
|
||||
#include <QScrollBar>
|
||||
|
||||
MacroSegmentList::MacroSegmentList(QWidget *parent)
|
||||
: QScrollArea(parent),
|
||||
_layout(new QVBoxLayout),
|
||||
_contentLayout(new QVBoxLayout),
|
||||
_helpMsg(new QLabel)
|
||||
{
|
||||
_helpMsg->setWordWrap(true);
|
||||
_helpMsg->setAlignment(Qt::AlignCenter);
|
||||
_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);
|
||||
setWidgetResizable(true);
|
||||
setAcceptDrops(true);
|
||||
}
|
||||
|
||||
MacroSegmentList::~MacroSegmentList()
|
||||
{
|
||||
if (_autoScrollThread.joinable()) {
|
||||
_autoScroll = false;
|
||||
_autoScrollThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
int MacroSegmentList::GetDragIndex(const QPoint &pos)
|
||||
{
|
||||
for (int idx = 0; idx < _contentLayout->count(); ++idx) {
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
const auto geo = item->geometry();
|
||||
int scrollOffset = 0;
|
||||
if (verticalScrollBar()) {
|
||||
scrollOffset = verticalScrollBar()->value();
|
||||
}
|
||||
const QRect rect(
|
||||
mapToGlobal(QPoint(geo.topLeft().x(),
|
||||
geo.topLeft().y() - scrollOffset)),
|
||||
geo.size());
|
||||
if (rect.contains(pos)) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MacroSegmentList::SetHelpMsg(const QString &msg)
|
||||
{
|
||||
_helpMsg->setText(msg);
|
||||
}
|
||||
|
||||
void MacroSegmentList::SetHelpMsgVisible(bool visible)
|
||||
{
|
||||
_helpMsg->setVisible(visible);
|
||||
}
|
||||
|
||||
void MacroSegmentList::Insert(int idx, QWidget *widget)
|
||||
{
|
||||
widget->installEventFilter(this);
|
||||
_contentLayout->insertWidget(idx, widget);
|
||||
}
|
||||
|
||||
void MacroSegmentList::Add(QWidget *widget)
|
||||
{
|
||||
widget->installEventFilter(this);
|
||||
_contentLayout->addWidget(widget);
|
||||
}
|
||||
|
||||
void MacroSegmentList::Remove(int idx)
|
||||
{
|
||||
deleteLayoutItemWidget(_contentLayout->takeAt(idx));
|
||||
}
|
||||
|
||||
void MacroSegmentList::Clear(int idx)
|
||||
{
|
||||
clearLayout(_contentLayout, idx);
|
||||
}
|
||||
|
||||
void MacroSegmentList::Highlight(int idx)
|
||||
{
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto widget = item->widget();
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
|
||||
void MacroSegmentList::SetCollapsed(bool collapse)
|
||||
{
|
||||
QLayoutItem *item = nullptr;
|
||||
for (int i = 0; i < _contentLayout->count(); i++) {
|
||||
item = _contentLayout->itemAt(i);
|
||||
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
|
||||
if (segment) {
|
||||
segment->SetCollapsed(collapse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentList::SetSelection(int idx)
|
||||
{
|
||||
for (int i = 0; i < _contentLayout->count(); ++i) {
|
||||
auto widget = static_cast<MacroSegmentEdit *>(
|
||||
_contentLayout->itemAt(i)->widget());
|
||||
if (widget) {
|
||||
widget->SetSelected(i == idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroSegmentList::eventFilter(QObject *object, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
}
|
||||
switch (event->type()) {
|
||||
case QEvent::MouseButtonPress:
|
||||
mousePressEvent(static_cast<QMouseEvent *>(event));
|
||||
break;
|
||||
case QEvent::MouseMove:
|
||||
mouseMoveEvent(static_cast<QMouseEvent *>(event));
|
||||
break;
|
||||
case QEvent::MouseButtonRelease:
|
||||
mouseReleaseEvent(static_cast<QMouseEvent *>(event));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void MacroSegmentList::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
_dragPosition = GetDragIndex(event->globalPos());
|
||||
emit SelectionChagned(_dragPosition);
|
||||
} else {
|
||||
_dragPosition = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentList::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton && _dragPosition != -1) {
|
||||
auto item = _contentLayout->itemAt(_dragPosition);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto widget = item->widget();
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
QDrag *drag = new QDrag(widget);
|
||||
auto img = widget->grab();
|
||||
auto mimedata = new QMimeData();
|
||||
mimedata->setImageData(img);
|
||||
drag->setMimeData(mimedata);
|
||||
drag->setPixmap(img);
|
||||
drag->setHotSpot(event->pos());
|
||||
_autoScroll = true;
|
||||
_autoScrollThread =
|
||||
std::thread(&MacroSegmentList::CheckScroll, this);
|
||||
drag->exec();
|
||||
_autoScroll = false;
|
||||
_autoScrollThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentList::mouseReleaseEvent(QMouseEvent *)
|
||||
{
|
||||
_dragPosition = -1;
|
||||
}
|
||||
|
||||
void MacroSegmentList::dragLeaveEvent(QDragLeaveEvent *)
|
||||
{
|
||||
HideLastDropLine();
|
||||
}
|
||||
|
||||
void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData() && event->mimeData()->hasImage()) {
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= _contentLayout->count()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<MacroSegmentEdit *>(item->widget());
|
||||
}
|
||||
|
||||
void MacroSegmentList::HideLastDropLine()
|
||||
{
|
||||
if (_dropLineIdx >= 0 && _dropLineIdx < _contentLayout->count()) {
|
||||
auto widget = WidgetAt(_dropLineIdx);
|
||||
if (widget) {
|
||||
widget->ShowDropLine(
|
||||
MacroSegmentEdit::DropLineState::NONE);
|
||||
}
|
||||
}
|
||||
_dropLineIdx = -1;
|
||||
}
|
||||
|
||||
bool isInUpperHalfOf(const QPoint &pos, const QRect &rect)
|
||||
{
|
||||
return QRect(rect.topLeft(),
|
||||
QSize(rect.size().width(), rect.size().height() / 2))
|
||||
.contains(pos);
|
||||
}
|
||||
|
||||
bool widgetIsInLayout(QWidget *w, QLayout *l)
|
||||
{
|
||||
if (w == nullptr) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < l->count(); ++i) {
|
||||
auto item = l->itemAt(i);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
if (item->widget() == w) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MacroSegmentList::dragMoveEvent(QDragMoveEvent *event)
|
||||
{
|
||||
auto widget = qobject_cast<QWidget *>(event->source());
|
||||
if (!widgetIsInLayout(widget, _contentLayout)) {
|
||||
return;
|
||||
}
|
||||
|
||||
_dragCursorPos = (mapToGlobal(event->pos()));
|
||||
CheckDropLine(_dragCursorPos);
|
||||
}
|
||||
|
||||
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
|
||||
{
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
return {};
|
||||
}
|
||||
int scrollOffset = 0;
|
||||
if (verticalScrollBar()) {
|
||||
scrollOffset = verticalScrollBar()->value();
|
||||
}
|
||||
const QRect itemRect = item->geometry().marginsAdded(
|
||||
_contentLayout->contentsMargins());
|
||||
const QRect rect(
|
||||
mapToGlobal(QPoint(itemRect.topLeft().x(),
|
||||
itemRect.topLeft().y() -
|
||||
_contentLayout->spacing() -
|
||||
scrollOffset)),
|
||||
QSize(itemRect.size().width(),
|
||||
itemRect.size().height() + _contentLayout->spacing()));
|
||||
return rect;
|
||||
}
|
||||
|
||||
int MacroSegmentList::GetWidgetIdx(const QPoint &pos)
|
||||
{
|
||||
int idx = -1;
|
||||
for (int i = 0; i < _contentLayout->count(); ++i) {
|
||||
if (GetContentItemRectWithPadding(i).contains(pos)) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void MacroSegmentList::CheckScroll()
|
||||
{
|
||||
while (_autoScroll) {
|
||||
const int scrollTrigger = 15;
|
||||
const int scrollAmount = 1;
|
||||
const QRect rect(mapToGlobal(QPoint(0, 0)), size());
|
||||
const QRect upperScrollTrigger(
|
||||
QPoint(rect.topLeft().x(),
|
||||
rect.topLeft().y() - scrollTrigger),
|
||||
QSize(rect.width(), scrollTrigger * 2));
|
||||
if (upperScrollTrigger.contains(_dragCursorPos)) {
|
||||
verticalScrollBar()->setValue(
|
||||
verticalScrollBar()->value() - scrollAmount);
|
||||
}
|
||||
const QRect lowerScrollTrigger(
|
||||
QPoint(rect.bottomLeft().x(),
|
||||
rect.bottomLeft().y() - scrollTrigger),
|
||||
QSize(rect.width(), scrollTrigger * 2));
|
||||
if (lowerScrollTrigger.contains(_dragCursorPos)) {
|
||||
verticalScrollBar()->setValue(
|
||||
verticalScrollBar()->value() + scrollAmount);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(50));
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentList::CheckDropLine(const QPoint &pos)
|
||||
{
|
||||
int idx = GetWidgetIdx(pos);
|
||||
if (idx == _dragPosition) {
|
||||
return;
|
||||
}
|
||||
auto action = MacroSegmentEdit::DropLineState::ABOVE;
|
||||
if (idx == -1) {
|
||||
if (IsInListArea(pos)) {
|
||||
idx = _contentLayout->count() - 1;
|
||||
action = MacroSegmentEdit::DropLineState::BELOW;
|
||||
} else {
|
||||
HideLastDropLine();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
auto rect = GetContentItemRectWithPadding(idx);
|
||||
if (idx == _contentLayout->count() - 1 &&
|
||||
!isInUpperHalfOf(pos, rect)) {
|
||||
action = MacroSegmentEdit::DropLineState::BELOW;
|
||||
} else {
|
||||
if (!isInUpperHalfOf(pos, rect)) {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (idx == _dragPosition ||
|
||||
(idx - 1 == _dragPosition &&
|
||||
action != MacroSegmentEdit::DropLineState::BELOW)) {
|
||||
HideLastDropLine();
|
||||
return;
|
||||
}
|
||||
auto widget = WidgetAt(idx);
|
||||
if (!widget) {
|
||||
HideLastDropLine();
|
||||
return;
|
||||
}
|
||||
widget->ShowDropLine(action);
|
||||
if (_dropLineIdx != idx) {
|
||||
HideLastDropLine();
|
||||
_dropLineIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
bool MacroSegmentList::IsInListArea(const QPoint &pos)
|
||||
{
|
||||
const QRect layoutRect(mapToGlobal(_layout->contentsRect().topLeft()),
|
||||
_layout->contentsRect().size());
|
||||
return layoutRect.contains(pos);
|
||||
}
|
||||
|
||||
int MacroSegmentList::GetDropIndex(const QPoint &pos)
|
||||
{
|
||||
int idx = GetWidgetIdx(pos);
|
||||
if (idx == _dragPosition) {
|
||||
return -1;
|
||||
}
|
||||
if (idx == -1) {
|
||||
if (IsInListArea(pos)) {
|
||||
return _contentLayout->count() - 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
auto rect = GetContentItemRectWithPadding(idx);
|
||||
if (idx == _contentLayout->count() - 1 && !isInUpperHalfOf(pos, rect)) {
|
||||
return idx;
|
||||
} else if (!isInUpperHalfOf(pos, rect)) {
|
||||
idx++;
|
||||
}
|
||||
if (_dragPosition < idx) {
|
||||
idx--;
|
||||
}
|
||||
if (idx == _dragPosition) {
|
||||
return -1;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void MacroSegmentList::dropEvent(QDropEvent *event)
|
||||
{
|
||||
HideLastDropLine();
|
||||
auto widget = qobject_cast<QWidget *>(event->source());
|
||||
if (widget && !widget->geometry().contains(event->pos()) &&
|
||||
widgetIsInLayout(widget, _contentLayout)) {
|
||||
int dropPosition = GetDropIndex(mapToGlobal(event->pos()));
|
||||
if (dropPosition == -1) {
|
||||
return;
|
||||
}
|
||||
emit Reorder(dropPosition, _dragPosition);
|
||||
}
|
||||
_dragPosition = -1;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
#include "headers/macro-segment.hpp"
|
||||
#include "headers/macro-controls.hpp"
|
||||
#include "headers/section.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <QEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QScrollBar>
|
||||
|
||||
@@ -24,6 +25,20 @@ std::string MacroSegment::GetShortDesc()
|
||||
return "";
|
||||
}
|
||||
|
||||
void MacroSegment::SetHighlight()
|
||||
{
|
||||
_highlight = true;
|
||||
}
|
||||
|
||||
bool MacroSegment::Highlight()
|
||||
{
|
||||
if (_highlight) {
|
||||
_highlight = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
MouseWheelWidgetAdjustmentGuard::MouseWheelWidgetAdjustmentGuard(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
@@ -40,15 +55,58 @@ bool MouseWheelWidgetAdjustmentGuard::eventFilter(QObject *o, QEvent *e)
|
||||
return QObject::eventFilter(o, e);
|
||||
}
|
||||
|
||||
MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(parent)
|
||||
MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
|
||||
: QWidget(parent),
|
||||
_section(new Section(300)),
|
||||
_headerInfo(new QLabel()),
|
||||
_frame(new QWidget),
|
||||
_contentLayout(new QVBoxLayout),
|
||||
_noBorderframe(new QFrame),
|
||||
_borderFrame(new QFrame),
|
||||
_dropLineAbove(new QFrame),
|
||||
_dropLineBelow(new QFrame),
|
||||
_showHighlight(highlight)
|
||||
{
|
||||
_section = new Section(300);
|
||||
_headerInfo = new QLabel();
|
||||
_controls = new MacroEntryControls();
|
||||
_dropLineAbove->setLineWidth(3);
|
||||
_dropLineAbove->setFixedHeight(11);
|
||||
_dropLineBelow->setLineWidth(3);
|
||||
_dropLineBelow->setFixedHeight(11);
|
||||
|
||||
_borderFrame->setObjectName("border");
|
||||
_borderFrame->setStyleSheet("#border {"
|
||||
"border-color: rgba(0, 0, 0, 255);"
|
||||
"border-width: 2px;"
|
||||
"border-style: dashed;"
|
||||
"border-radius: 4px;"
|
||||
"background-color: rgba(0,0,0,100);"
|
||||
"}");
|
||||
_noBorderframe->setObjectName("noBorder");
|
||||
_noBorderframe->setStyleSheet("#noBorder {"
|
||||
"border-color: rgba(0, 0, 0, 0);"
|
||||
"border-width: 2px;"
|
||||
"border-style: dashed;"
|
||||
"border-radius: 4px;"
|
||||
"background-color: rgba(0,0,0,50);"
|
||||
"}");
|
||||
_frame->setObjectName("frameWrapper");
|
||||
_frame->setStyleSheet("#frameWrapper {"
|
||||
"border-width: 2px;"
|
||||
"border-radius: 4px;"
|
||||
"background-color: rgba(0,0,0,0);"
|
||||
"}");
|
||||
|
||||
// Set background of these widget types to be transparent to avoid
|
||||
// blocking highlight frame background
|
||||
setStyleSheet("QCheckBox { background-color: rgba(0,0,0,0); }"
|
||||
"QLabel { background-color: rgba(0,0,0,0); }"
|
||||
"QSlider { background-color: rgba(0,0,0,0); }");
|
||||
|
||||
// Keep the size of macro segments consistent, even if there is room in
|
||||
// the edit areas
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
QWidget::connect(_section, &Section::Collapsed, this,
|
||||
&MacroSegmentEdit::Collapsed);
|
||||
|
||||
// Macro signals
|
||||
QWidget::connect(parent, SIGNAL(MacroAdded(const QString &)), this,
|
||||
SIGNAL(MacroAdded(const QString &)));
|
||||
@@ -58,7 +116,6 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(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 &)));
|
||||
@@ -69,15 +126,25 @@ MacroSegmentEdit::MacroSegmentEdit(QWidget *parent) : QWidget(parent)
|
||||
SIGNAL(SceneGroupRenamed(const QString &, const QString)), this,
|
||||
SIGNAL(SceneGroupRenamed(const QString &, const QString)));
|
||||
|
||||
// Control signals
|
||||
QWidget::connect(_controls, &MacroEntryControls::Add, this,
|
||||
&MacroSegmentEdit::Add);
|
||||
QWidget::connect(_controls, &MacroEntryControls::Remove, this,
|
||||
&MacroSegmentEdit::Remove);
|
||||
QWidget::connect(_controls, &MacroEntryControls::Up, this,
|
||||
&MacroSegmentEdit::Up);
|
||||
QWidget::connect(_controls, &MacroEntryControls::Down, this,
|
||||
&MacroSegmentEdit::Down);
|
||||
auto frameLayout = new QGridLayout;
|
||||
frameLayout->setContentsMargins(0, 0, 0, 0);
|
||||
frameLayout->addLayout(_contentLayout, 0, 0);
|
||||
frameLayout->addWidget(_noBorderframe, 0, 0);
|
||||
frameLayout->addWidget(_borderFrame, 0, 0);
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(_dropLineAbove);
|
||||
layout->addLayout(frameLayout);
|
||||
layout->addWidget(_dropLineBelow);
|
||||
_frame->setLayout(layout);
|
||||
|
||||
SetSelected(false);
|
||||
ShowDropLine(DropLineState::NONE);
|
||||
|
||||
_timer.setInterval(1500);
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
|
||||
_timer.start();
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::HeaderInfoChanged(const QString &text)
|
||||
@@ -86,35 +153,6 @@ void MacroSegmentEdit::HeaderInfoChanged(const QString &text)
|
||||
_headerInfo->setText(text);
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::Add()
|
||||
{
|
||||
if (Data()) {
|
||||
// Insert after current entry
|
||||
emit AddAt(Data()->GetIndex() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::Remove()
|
||||
{
|
||||
if (Data()) {
|
||||
emit RemoveAt(Data()->GetIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::Up()
|
||||
{
|
||||
if (Data()) {
|
||||
emit UpAt(Data()->GetIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::Down()
|
||||
{
|
||||
if (Data()) {
|
||||
emit DownAt(Data()->GetIndex());
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::Collapsed(bool collapsed)
|
||||
{
|
||||
if (Data()) {
|
||||
@@ -122,14 +160,20 @@ void MacroSegmentEdit::Collapsed(bool collapsed)
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::enterEvent(QEvent *)
|
||||
void MacroSegmentEdit::Highlight()
|
||||
{
|
||||
_controls->Show(true);
|
||||
if (!Data()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_showHighlight && Data()->Highlight()) {
|
||||
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
|
||||
}
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::leaveEvent(QEvent *)
|
||||
void MacroSegmentEdit::EnableHighlight(bool value)
|
||||
{
|
||||
_controls->Show(false);
|
||||
_showHighlight = value;
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::SetFocusPolicyOfWidgets()
|
||||
@@ -150,3 +194,34 @@ void MacroSegmentEdit::SetCollapsed(bool collapsed)
|
||||
{
|
||||
_section->SetCollapsed(collapsed);
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::SetSelected(bool selected)
|
||||
{
|
||||
_borderFrame->setVisible(selected);
|
||||
_noBorderframe->setVisible(!selected);
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::ShowDropLine(DropLineState state)
|
||||
{
|
||||
switch (state) {
|
||||
case MacroSegmentEdit::DropLineState::NONE:
|
||||
_dropLineAbove->setFrameShadow(QFrame::Plain);
|
||||
_dropLineAbove->setFrameShape(QFrame::NoFrame);
|
||||
_dropLineBelow->hide();
|
||||
break;
|
||||
case MacroSegmentEdit::DropLineState::ABOVE:
|
||||
_dropLineAbove->setFrameShadow(QFrame::Sunken);
|
||||
_dropLineAbove->setFrameShape(QFrame::Panel);
|
||||
_dropLineBelow->hide();
|
||||
break;
|
||||
case MacroSegmentEdit::DropLineState::BELOW:
|
||||
_dropLineAbove->setFrameShadow(QFrame::Plain);
|
||||
_dropLineAbove->setFrameShape(QFrame::NoFrame);
|
||||
_dropLineBelow->setFrameShadow(QFrame::Sunken);
|
||||
_dropLineBelow->setFrameShape(QFrame::Panel);
|
||||
_dropLineBelow->show();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,40 @@
|
||||
#include "headers/macro.hpp"
|
||||
#include "headers/macro-list-entry-widget.hpp"
|
||||
#include "headers/macro-action-edit.hpp"
|
||||
#include "headers/macro-condition-edit.hpp"
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/name-dialog.hpp"
|
||||
#include "headers/macro-properties.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
#include <QColor>
|
||||
#include <QMenu>
|
||||
#include <QGraphicsOpacityEffect>
|
||||
#include <QPropertyAnimation>
|
||||
|
||||
static QMetaObject::Connection addPulse;
|
||||
static QTimer onChangeHighlightTimer;
|
||||
|
||||
bool macroNameExists(std::string name)
|
||||
{
|
||||
return !!GetMacroByName(name.c_str());
|
||||
}
|
||||
|
||||
bool AdvSceneSwitcher::addNewMacro(std::string &name)
|
||||
bool AdvSceneSwitcher::addNewMacro(std::string &name, std::string format)
|
||||
{
|
||||
QString format{
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.defaultname")};
|
||||
|
||||
QString fmt;
|
||||
int i = 1;
|
||||
QString placeHolderText = format.arg(i);
|
||||
if (format.empty()) {
|
||||
fmt = {obs_module_text(
|
||||
"AdvSceneSwitcher.macroTab.defaultname")};
|
||||
} else {
|
||||
fmt = QString::fromStdString(format);
|
||||
i = 2;
|
||||
}
|
||||
|
||||
QString placeHolderText = fmt.arg(i);
|
||||
while ((macroNameExists(placeHolderText.toUtf8().constData()))) {
|
||||
placeHolderText = format.arg(++i);
|
||||
placeHolderText = fmt.arg(++i);
|
||||
}
|
||||
|
||||
bool accepted = AdvSSNameDialog::AskForName(
|
||||
@@ -52,6 +63,18 @@ bool AdvSceneSwitcher::addNewMacro(std::string &name)
|
||||
return true;
|
||||
}
|
||||
|
||||
QListWidgetItem *AddNewMacroListEntry(QListWidget *list,
|
||||
std::shared_ptr<Macro> ¯o)
|
||||
{
|
||||
QListWidgetItem *item = new QListWidgetItem(list);
|
||||
item->setData(Qt::UserRole, QString::fromStdString(macro->Name()));
|
||||
auto listEntry = new MacroListEntryWidget(
|
||||
macro, switcher->macroProperties._highlightExecuted, list);
|
||||
item->setSizeHint(listEntry->minimumSizeHint());
|
||||
list->setItemWidget(item, listEntry);
|
||||
return item;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_macroAdd_clicked()
|
||||
{
|
||||
std::string name;
|
||||
@@ -60,10 +83,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
|
||||
}
|
||||
QString text = QString::fromStdString(name);
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
|
||||
item->setData(Qt::UserRole, text);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Checked);
|
||||
auto item = AddNewMacroListEntry(ui->macros, switcher->macros.back());
|
||||
ui->macros->setCurrentItem(item);
|
||||
|
||||
ui->macroAdd->disconnect(addPulse);
|
||||
@@ -102,34 +122,44 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
|
||||
void AdvSceneSwitcher::on_macroUp_clicked()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
int index = ui->macros->currentRow();
|
||||
if (index != -1 && index != 0) {
|
||||
ui->macros->insertItem(index - 1, ui->macros->takeItem(index));
|
||||
ui->macros->setCurrentRow(index - 1);
|
||||
if (!listMoveUp(ui->macros)) {
|
||||
return;
|
||||
}
|
||||
|
||||
iter_swap(switcher->macros.begin() + index,
|
||||
switcher->macros.begin() + index - 1);
|
||||
int index = ui->macros->currentRow() + 1;
|
||||
auto *entry1 = static_cast<MacroListEntryWidget *>(
|
||||
ui->macros->itemWidget(ui->macros->item(index)));
|
||||
auto *entry2 = static_cast<MacroListEntryWidget *>(
|
||||
ui->macros->itemWidget(ui->macros->item(index - 1)));
|
||||
entry1->SetMacro(*(switcher->macros.begin() + index - 1));
|
||||
entry2->SetMacro(*(switcher->macros.begin() + index));
|
||||
iter_swap(switcher->macros.begin() + index,
|
||||
switcher->macros.begin() + index - 1);
|
||||
|
||||
for (auto &m : switcher->macros) {
|
||||
m->ResolveMacroRef();
|
||||
}
|
||||
for (auto &m : switcher->macros) {
|
||||
m->ResolveMacroRef();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_macroDown_clicked()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
int index = ui->macros->currentRow();
|
||||
if (index != -1 && index != ui->macros->count() - 1) {
|
||||
ui->macros->insertItem(index + 1, ui->macros->takeItem(index));
|
||||
ui->macros->setCurrentRow(index + 1);
|
||||
if (!listMoveDown(ui->macros)) {
|
||||
return;
|
||||
}
|
||||
|
||||
iter_swap(switcher->macros.begin() + index,
|
||||
switcher->macros.begin() + index + 1);
|
||||
int index = ui->macros->currentRow() - 1;
|
||||
auto *entry1 = static_cast<MacroListEntryWidget *>(
|
||||
ui->macros->itemWidget(ui->macros->item(index)));
|
||||
auto *entry2 = static_cast<MacroListEntryWidget *>(
|
||||
ui->macros->itemWidget(ui->macros->item(index + 1)));
|
||||
entry1->SetMacro(*(switcher->macros.begin() + index + 1));
|
||||
entry2->SetMacro(*(switcher->macros.begin() + index));
|
||||
iter_swap(switcher->macros.begin() + index,
|
||||
switcher->macros.begin() + index + 1);
|
||||
|
||||
for (auto &m : switcher->macros) {
|
||||
m->ResolveMacroRef();
|
||||
}
|
||||
for (auto &m : switcher->macros) {
|
||||
m->ResolveMacroRef();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,11 +190,10 @@ void AdvSceneSwitcher::on_macroName_editingFinished()
|
||||
if (nameValid) {
|
||||
macro->SetName(newName.toUtf8().constData());
|
||||
QListWidgetItem *item = ui->macros->currentItem();
|
||||
// Don't trigger itemChanged()
|
||||
// pause state remains as is
|
||||
ui->macros->blockSignals(true);
|
||||
item->setText(newName);
|
||||
ui->macros->blockSignals(false);
|
||||
item->setData(Qt::UserRole, newName);
|
||||
auto listEntry = static_cast<MacroListEntryWidget *>(
|
||||
ui->macros->itemWidget(item));
|
||||
listEntry->SetName(newName);
|
||||
} else {
|
||||
ui->macroName->setText(oldName);
|
||||
}
|
||||
@@ -180,7 +209,7 @@ void AdvSceneSwitcher::on_runMacro_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
bool ret = macro->PerformAction(true, true);
|
||||
bool ret = macro->PerformActions(true, true);
|
||||
if (!ret) {
|
||||
QString err =
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.runFail");
|
||||
@@ -214,11 +243,9 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
|
||||
for (; afterIdx < actions.size(); afterIdx++) {
|
||||
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
|
||||
actions[afterIdx]->GetId());
|
||||
ConnectControlSignals(newEntry);
|
||||
ui->macroEditActionLayout->addWidget(newEntry);
|
||||
ui->macroEditActionHelp->setVisible(false);
|
||||
actionsList->Add(newEntry);
|
||||
}
|
||||
ui->macroEditActionHelp->setVisible(actions.size() == 0);
|
||||
actionsList->SetHelpMsgVisible(actions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
|
||||
@@ -229,12 +256,43 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
|
||||
auto newEntry = new MacroConditionEdit(
|
||||
this, &conditions[afterIdx],
|
||||
conditions[afterIdx]->GetId(), root);
|
||||
ConnectControlSignals(newEntry);
|
||||
ui->macroEditConditionLayout->addWidget(newEntry);
|
||||
ui->macroEditConditionHelp->setVisible(false);
|
||||
conditionsList->Add(newEntry);
|
||||
root = false;
|
||||
}
|
||||
ui->macroEditConditionHelp->setVisible(conditions.size() == 0);
|
||||
conditionsList->SetHelpMsgVisible(conditions.size() == 0);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetActionData(Macro &m)
|
||||
{
|
||||
auto &actions = m.Actions();
|
||||
for (int idx = 0; idx < actionsList->ContentLayout()->count(); idx++) {
|
||||
auto item = actionsList->ContentLayout()->itemAt(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
auto widget = static_cast<MacroActionEdit *>(item->widget());
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
widget->SetEntryData(&*(actions.begin() + idx));
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetConditionData(Macro &m)
|
||||
{
|
||||
auto &conditions = m.Conditions();
|
||||
for (int idx = 0; idx < conditionsList->ContentLayout()->count();
|
||||
idx++) {
|
||||
auto item = conditionsList->ContentLayout()->itemAt(idx);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
auto widget = static_cast<MacroConditionEdit *>(item->widget());
|
||||
if (!widget) {
|
||||
continue;
|
||||
}
|
||||
widget->SetEntryData(&*(conditions.begin() + idx));
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
||||
@@ -247,64 +305,39 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
|
||||
ui->runMacroInParallel->setChecked(m.RunInParallel());
|
||||
ui->runMacroOnChange->setChecked(m.MatchOnChange());
|
||||
}
|
||||
clearLayout(ui->macroEditConditionLayout);
|
||||
clearLayout(ui->macroEditActionLayout);
|
||||
conditionsList->Clear();
|
||||
actionsList->Clear();
|
||||
|
||||
m.ResetUIHelpers();
|
||||
|
||||
PopulateMacroConditions(m);
|
||||
PopulateMacroActions(m);
|
||||
ui->macroEdit->setDisabled(false);
|
||||
SetMacroEditAreaDisabled(false);
|
||||
|
||||
currentActionIdx = -1;
|
||||
currentConditionIdx = -1;
|
||||
HighlightControls();
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::SetMacroEditAreaDisabled(bool disable)
|
||||
{
|
||||
ui->macroName->setDisabled(disable);
|
||||
ui->runMacro->setDisabled(disable);
|
||||
ui->runMacroInParallel->setDisabled(disable);
|
||||
ui->runMacroOnChange->setDisabled(disable);
|
||||
ui->macroActions->setDisabled(disable);
|
||||
ui->macroConditions->setDisabled(disable);
|
||||
ui->macroSplitter->setDisabled(disable);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightAction(int idx)
|
||||
{
|
||||
auto item = ui->macroEditActionLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto widget = item->widget();
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), "QLabel ",
|
||||
true);
|
||||
actionsList->Highlight(idx);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightCondition(int idx)
|
||||
{
|
||||
auto item = ui->macroEditConditionLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
auto widget = item->widget();
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), "QLabel ",
|
||||
true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ConnectControlSignals(MacroActionEdit *c)
|
||||
{
|
||||
connect(c, &MacroActionEdit::AddAt, this,
|
||||
&AdvSceneSwitcher::AddMacroAction);
|
||||
connect(c, &MacroActionEdit::RemoveAt, this,
|
||||
&AdvSceneSwitcher::RemoveMacroAction);
|
||||
connect(c, &MacroActionEdit::UpAt, this,
|
||||
&AdvSceneSwitcher::MoveMacroActionUp);
|
||||
connect(c, &MacroActionEdit::DownAt, this,
|
||||
&AdvSceneSwitcher::MoveMacroActionDown);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ConnectControlSignals(MacroConditionEdit *c)
|
||||
{
|
||||
connect(c, &MacroConditionEdit::AddAt, this,
|
||||
&AdvSceneSwitcher::AddMacroCondition);
|
||||
connect(c, &MacroConditionEdit::RemoveAt, this,
|
||||
&AdvSceneSwitcher::RemoveMacroCondition);
|
||||
connect(c, &MacroConditionEdit::UpAt, this,
|
||||
&AdvSceneSwitcher::MoveMacroConditionUp);
|
||||
connect(c, &MacroConditionEdit::DownAt, this,
|
||||
&AdvSceneSwitcher::MoveMacroConditionDown);
|
||||
conditionsList->Highlight(idx);
|
||||
}
|
||||
|
||||
Macro *AdvSceneSwitcher::getSelectedMacro()
|
||||
@@ -315,7 +348,7 @@ Macro *AdvSceneSwitcher::getSelectedMacro()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString name = item->text();
|
||||
QString name = item->data(Qt::UserRole).toString();
|
||||
return GetMacroByQString(name);
|
||||
}
|
||||
|
||||
@@ -326,12 +359,12 @@ void AdvSceneSwitcher::on_macros_currentRowChanged(int idx)
|
||||
}
|
||||
|
||||
if (idx == -1) {
|
||||
ui->macroEdit->setDisabled(true);
|
||||
SetMacroEditAreaDisabled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
QListWidgetItem *item = ui->macros->item(idx);
|
||||
QString macroName = item->text();
|
||||
QString macroName = item->data(Qt::UserRole).toString();
|
||||
|
||||
auto macro = GetMacroByQString(macroName);
|
||||
if (macro) {
|
||||
@@ -339,34 +372,58 @@ void AdvSceneSwitcher::on_macros_currentRowChanged(int idx)
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_macros_itemChanged(QListWidgetItem *item)
|
||||
void AdvSceneSwitcher::MacroDragDropReorder(QModelIndex, int from, int,
|
||||
QModelIndex, int to)
|
||||
{
|
||||
if (loading) {
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
if (from > to) {
|
||||
std::rotate(switcher->macros.rend() - from - 1,
|
||||
switcher->macros.rend() - from,
|
||||
switcher->macros.rend() - to);
|
||||
} else {
|
||||
std::rotate(switcher->macros.begin() + from,
|
||||
switcher->macros.begin() + from + 1,
|
||||
switcher->macros.begin() + to);
|
||||
}
|
||||
|
||||
for (auto &m : switcher->macros) {
|
||||
m->ResolveMacroRef();
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightOnChange()
|
||||
{
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
QString name = item->text();
|
||||
|
||||
auto m = GetMacroByQString(name);
|
||||
if (m) {
|
||||
m->SetPaused(item->checkState() != Qt::Checked);
|
||||
if (switcher->macroProperties._highlightExecuted &&
|
||||
macro->OnChangePreventedActionsRecently()) {
|
||||
PulseWidget(ui->runMacroOnChange, Qt::yellow, Qt::transparent,
|
||||
true);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_macroProperties_clicked()
|
||||
{
|
||||
MacroProperties prop = switcher->macroProperties;
|
||||
bool accepted = MacroPropertiesDialog::AskForSettings(this, prop);
|
||||
if (!accepted) {
|
||||
return;
|
||||
}
|
||||
switcher->macroProperties = prop;
|
||||
emit HighlightMacrosChanged(prop._highlightExecuted);
|
||||
emit HighlightActionsChanged(prop._highlightActions);
|
||||
emit HighlightConditionsChanged(prop._highlightConditions);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupMacroTab()
|
||||
{
|
||||
const QSignalBlocker signalBlocker(ui->macros);
|
||||
ui->macros->clear();
|
||||
for (auto &m : switcher->macros) {
|
||||
QString text = QString::fromStdString(m->Name());
|
||||
|
||||
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
|
||||
item->setData(Qt::UserRole, text);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
if (m->Paused()) {
|
||||
item->setCheckState(Qt::Unchecked);
|
||||
} else {
|
||||
item->setCheckState(Qt::Checked);
|
||||
}
|
||||
AddNewMacroListEntry(ui->macros, m);
|
||||
}
|
||||
|
||||
if (switcher->macros.size() == 0) {
|
||||
@@ -378,20 +435,60 @@ void AdvSceneSwitcher::setupMacroTab()
|
||||
ui->macroHelp->setVisible(false);
|
||||
}
|
||||
|
||||
connect(ui->macros->model(),
|
||||
SIGNAL(rowsMoved(QModelIndex, int, int, QModelIndex, int)),
|
||||
this,
|
||||
SLOT(MacroDragDropReorder(QModelIndex, int, int, QModelIndex,
|
||||
int)));
|
||||
|
||||
delete conditionsList;
|
||||
conditionsList = new MacroSegmentList(this);
|
||||
conditionsList->SetHelpMsg(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
|
||||
connect(conditionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
&AdvSceneSwitcher::MacroConditionSelectionChanged);
|
||||
connect(conditionsList, &MacroSegmentList::Reorder, this,
|
||||
&AdvSceneSwitcher::MacroConditionReorder);
|
||||
ui->macroConditionsLayout->insertWidget(0, conditionsList);
|
||||
|
||||
delete actionsList;
|
||||
actionsList = new MacroSegmentList(this);
|
||||
actionsList->SetHelpMsg(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
|
||||
connect(actionsList, &MacroSegmentList::SelectionChagned, this,
|
||||
&AdvSceneSwitcher::MacroActionSelectionChanged);
|
||||
connect(actionsList, &MacroSegmentList::Reorder, this,
|
||||
&AdvSceneSwitcher::MacroActionReorder);
|
||||
ui->macroActionsLayout->insertWidget(0, actionsList);
|
||||
|
||||
ui->macros->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->macros, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroContextMenu);
|
||||
ui->macroActions->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->macroActions, &QWidget::customContextMenuRequested, this,
|
||||
actionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(actionsList, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroActionsContextMenu);
|
||||
ui->macroConditions->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->macroConditions, &QWidget::customContextMenuRequested, this,
|
||||
conditionsList->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(conditionsList, &QWidget::customContextMenuRequested, this,
|
||||
&AdvSceneSwitcher::ShowMacroConditionsContextMenu);
|
||||
|
||||
ui->macroEdit->setDisabled(true);
|
||||
|
||||
SetMacroEditAreaDisabled(true);
|
||||
ui->macroPriorityWarning->setVisible(
|
||||
switcher->functionNamesByPriority[0] != macro_func);
|
||||
|
||||
onChangeHighlightTimer.setInterval(1500);
|
||||
connect(&onChangeHighlightTimer, SIGNAL(timeout()), this,
|
||||
SLOT(HighlightOnChange()));
|
||||
onChangeHighlightTimer.start();
|
||||
|
||||
// Move condition controls into splitter handle layout
|
||||
auto handle = ui->macroSplitter->handle(1);
|
||||
auto item = ui->macroConditionsLayout->takeAt(1);
|
||||
if (item) {
|
||||
auto layout = item->layout();
|
||||
layout->setContentsMargins(7, 7, 7, 7);
|
||||
handle->setLayout(layout);
|
||||
ui->macroSplitter->setHandleWidth(38);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
||||
@@ -405,70 +502,66 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroActionsContextMenu(const QPoint &pos)
|
||||
{
|
||||
QPoint globalPos = ui->macroActions->mapToGlobal(pos);
|
||||
QPoint globalPos = actionsList->mapToGlobal(pos);
|
||||
QMenu myMenu;
|
||||
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
||||
this, &AdvSceneSwitcher::ExpandAllActions);
|
||||
myMenu.addAction(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), this,
|
||||
&AdvSceneSwitcher::CollapseAllActions);
|
||||
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
|
||||
this, &AdvSceneSwitcher::MinimizeConditions);
|
||||
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
|
||||
this, &AdvSceneSwitcher::MinimizeActions);
|
||||
myMenu.exec(globalPos);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ShowMacroConditionsContextMenu(const QPoint &pos)
|
||||
{
|
||||
QPoint globalPos = ui->macroConditions->mapToGlobal(pos);
|
||||
QPoint globalPos = conditionsList->mapToGlobal(pos);
|
||||
QMenu myMenu;
|
||||
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.expandAll"),
|
||||
this, &AdvSceneSwitcher::ExpandAllConditions);
|
||||
myMenu.addAction(
|
||||
obs_module_text("AdvSceneSwitcher.macroTab.collapseAll"), this,
|
||||
&AdvSceneSwitcher::CollapseAllConditions);
|
||||
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.maximize"),
|
||||
this, &AdvSceneSwitcher::MinimizeActions);
|
||||
myMenu.addAction(obs_module_text("AdvSceneSwitcher.macroTab.minimize"),
|
||||
this, &AdvSceneSwitcher::MinimizeConditions);
|
||||
myMenu.exec(globalPos);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CopyMacro()
|
||||
{
|
||||
obs_data_t *data = obs_data_create();
|
||||
getSelectedMacro()->Save(data);
|
||||
|
||||
std::string name;
|
||||
if (!addNewMacro(name)) {
|
||||
obs_data_release(data);
|
||||
const auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string format = macro->Name() + " %1";
|
||||
std::string name;
|
||||
if (!addNewMacro(name, format)) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
macro->Save(data);
|
||||
switcher->macros.back()->Load(data);
|
||||
switcher->macros.back()->SetName(name);
|
||||
obs_data_release(data);
|
||||
|
||||
QString text = QString::fromStdString(name);
|
||||
QListWidgetItem *item = new QListWidgetItem(text, ui->macros);
|
||||
item->setData(Qt::UserRole, text);
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(Qt::Checked);
|
||||
auto item = AddNewMacroListEntry(ui->macros, switcher->macros.back());
|
||||
ui->macros->setCurrentItem(item);
|
||||
}
|
||||
|
||||
void setCollapsedStateOfSegmentsIn(QLayout *layout, bool collapse)
|
||||
{
|
||||
QLayoutItem *item = nullptr;
|
||||
for (int i = 0; i < layout->count(); i++) {
|
||||
item = layout->itemAt(i);
|
||||
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
|
||||
if (segment) {
|
||||
segment->SetCollapsed(collapse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllActions()
|
||||
{
|
||||
auto m = getSelectedMacro();
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, false);
|
||||
actionsList->SetCollapsed(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ExpandAllConditions()
|
||||
@@ -477,7 +570,7 @@ void AdvSceneSwitcher::ExpandAllConditions()
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, false);
|
||||
conditionsList->SetCollapsed(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllActions()
|
||||
@@ -486,7 +579,7 @@ void AdvSceneSwitcher::CollapseAllActions()
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditActionLayout, true);
|
||||
actionsList->SetCollapsed(true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::CollapseAllConditions()
|
||||
@@ -495,5 +588,236 @@ void AdvSceneSwitcher::CollapseAllConditions()
|
||||
if (!m) {
|
||||
return;
|
||||
}
|
||||
setCollapsedStateOfSegmentsIn(ui->macroEditConditionLayout, true);
|
||||
conditionsList->SetCollapsed(true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MinimizeActions()
|
||||
{
|
||||
QList<int> sizes = ui->macroSplitter->sizes();
|
||||
int sum = sizes[0] + sizes[1];
|
||||
int actionsHeight = sum / 10;
|
||||
sizes[1] = actionsHeight;
|
||||
sizes[0] = sum - actionsHeight;
|
||||
ui->macroSplitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::MinimizeConditions()
|
||||
{
|
||||
QList<int> sizes = ui->macroSplitter->sizes();
|
||||
int sum = sizes[0] + sizes[1];
|
||||
int conditionsHeight = sum / 10;
|
||||
sizes[0] = conditionsHeight;
|
||||
sizes[1] = sum - conditionsHeight;
|
||||
ui->macroSplitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
bool AdvSceneSwitcher::MacroTabIsInFocus()
|
||||
{
|
||||
return isActiveWindow() && isAncestorOf(focusWidget()) &&
|
||||
(ui->tabWidget->currentWidget()->objectName() == "macroTab");
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::UpMacroSegementHotkey()
|
||||
{
|
||||
if (!MacroTabIsInFocus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
int actionSize = macro->Actions().size();
|
||||
int conditionSize = macro->Conditions().size();
|
||||
|
||||
if (currentActionIdx == -1 && currentConditionIdx == -1) {
|
||||
if (lastInteracted == MacroSection::CONDITIONS) {
|
||||
if (conditionSize == 0) {
|
||||
MacroActionSelectionChanged(0);
|
||||
} else {
|
||||
MacroConditionSelectionChanged(0);
|
||||
}
|
||||
} else {
|
||||
if (actionSize == 0) {
|
||||
MacroConditionSelectionChanged(0);
|
||||
} else {
|
||||
MacroActionSelectionChanged(0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentActionIdx > 0) {
|
||||
MacroActionSelectionChanged(currentActionIdx - 1);
|
||||
return;
|
||||
}
|
||||
if (currentConditionIdx > 0) {
|
||||
MacroConditionSelectionChanged(currentConditionIdx - 1);
|
||||
return;
|
||||
}
|
||||
if (currentActionIdx == 0) {
|
||||
if (conditionSize == 0) {
|
||||
MacroActionSelectionChanged(actionSize - 1);
|
||||
} else {
|
||||
MacroConditionSelectionChanged(conditionSize - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (currentConditionIdx == 0) {
|
||||
if (actionSize == 0) {
|
||||
MacroConditionSelectionChanged(conditionSize - 1);
|
||||
} else {
|
||||
MacroActionSelectionChanged(actionSize - 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::DownMacroSegementHotkey()
|
||||
{
|
||||
if (!MacroTabIsInFocus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto macro = getSelectedMacro();
|
||||
if (!macro) {
|
||||
return;
|
||||
}
|
||||
int actionSize = macro->Actions().size();
|
||||
int conditionSize = macro->Conditions().size();
|
||||
|
||||
if (currentActionIdx == -1 && currentConditionIdx == -1) {
|
||||
if (lastInteracted == MacroSection::CONDITIONS) {
|
||||
if (conditionSize == 0) {
|
||||
MacroActionSelectionChanged(0);
|
||||
} else {
|
||||
MacroConditionSelectionChanged(0);
|
||||
}
|
||||
} else {
|
||||
if (actionSize == 0) {
|
||||
MacroConditionSelectionChanged(0);
|
||||
} else {
|
||||
MacroActionSelectionChanged(0);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentActionIdx < actionSize - 1) {
|
||||
MacroActionSelectionChanged(currentActionIdx + 1);
|
||||
return;
|
||||
}
|
||||
if (currentConditionIdx < conditionSize - 1) {
|
||||
MacroConditionSelectionChanged(currentConditionIdx + 1);
|
||||
return;
|
||||
}
|
||||
if (currentActionIdx == actionSize - 1) {
|
||||
if (conditionSize == 0) {
|
||||
MacroActionSelectionChanged(0);
|
||||
} else {
|
||||
MacroConditionSelectionChanged(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (currentConditionIdx == conditionSize - 1) {
|
||||
if (actionSize == 0) {
|
||||
MacroConditionSelectionChanged(0);
|
||||
} else {
|
||||
MacroActionSelectionChanged(0);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::DeleteMacroSegementHotkey()
|
||||
{
|
||||
if (!MacroTabIsInFocus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentActionIdx != -1) {
|
||||
RemoveMacroAction(currentActionIdx);
|
||||
} else if (currentConditionIdx != -1) {
|
||||
RemoveMacroCondition(currentConditionIdx);
|
||||
}
|
||||
}
|
||||
|
||||
void fade(QWidget *widget, bool fadeOut)
|
||||
{
|
||||
const double fadeOutOpacity = 0.3;
|
||||
// Don't use exactly 1.0 as for some reason this causes buttons in
|
||||
// macroSplitter handle layout to not be redrawn unless mousing over
|
||||
// them
|
||||
const double fadeInOpacity = 0.99;
|
||||
auto curEffect = widget->graphicsEffect();
|
||||
if (curEffect) {
|
||||
auto curOpacity =
|
||||
dynamic_cast<QGraphicsOpacityEffect *>(curEffect);
|
||||
if (curOpacity &&
|
||||
((fadeOut && doubleEquals(curOpacity->opacity(),
|
||||
fadeOutOpacity, 0.0001)) ||
|
||||
(!fadeOut && doubleEquals(curOpacity->opacity(),
|
||||
fadeInOpacity, 0.0001)))) {
|
||||
return;
|
||||
}
|
||||
} else if (!fadeOut) {
|
||||
return;
|
||||
}
|
||||
delete curEffect;
|
||||
QGraphicsOpacityEffect *opacityEffect = new QGraphicsOpacityEffect();
|
||||
widget->setGraphicsEffect(opacityEffect);
|
||||
QPropertyAnimation *animation =
|
||||
new QPropertyAnimation(opacityEffect, "opacity");
|
||||
animation->setDuration(350);
|
||||
animation->setStartValue(fadeOut ? fadeInOpacity : fadeOutOpacity);
|
||||
animation->setEndValue(fadeOut ? fadeOutOpacity : fadeInOpacity);
|
||||
animation->setEasingCurve(QEasingCurve::OutQuint);
|
||||
animation->start(QPropertyAnimation::DeleteWhenStopped);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::FadeOutActionControls()
|
||||
{
|
||||
fade(ui->actionAdd, true);
|
||||
fade(ui->actionRemove, true);
|
||||
fade(ui->actionUp, true);
|
||||
fade(ui->actionDown, true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::FadeOutConditionControls()
|
||||
{
|
||||
fade(ui->conditionAdd, true);
|
||||
fade(ui->conditionRemove, true);
|
||||
fade(ui->conditionUp, true);
|
||||
fade(ui->conditionDown, true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ResetOpacityActionControls()
|
||||
{
|
||||
fade(ui->actionAdd, false);
|
||||
fade(ui->actionRemove, false);
|
||||
fade(ui->actionUp, false);
|
||||
fade(ui->actionDown, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::ResetOpacityConditionControls()
|
||||
{
|
||||
fade(ui->conditionAdd, false);
|
||||
fade(ui->conditionRemove, false);
|
||||
fade(ui->conditionUp, false);
|
||||
fade(ui->conditionDown, false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::HighlightControls()
|
||||
{
|
||||
if ((currentActionIdx == -1 && currentConditionIdx == -1) ||
|
||||
(currentActionIdx != -1 && currentConditionIdx != -1)) {
|
||||
ResetOpacityActionControls();
|
||||
ResetOpacityConditionControls();
|
||||
} else if (currentActionIdx != -1) {
|
||||
FadeOutConditionControls();
|
||||
ResetOpacityActionControls();
|
||||
} else {
|
||||
FadeOutActionControls();
|
||||
ResetOpacityConditionControls();
|
||||
}
|
||||
}
|
||||
|
||||
119
src/macro.cpp
119
src/macro.cpp
@@ -30,9 +30,7 @@ Macro::Macro(const std::string &name)
|
||||
Macro::~Macro()
|
||||
{
|
||||
_die = true;
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
}
|
||||
Stop();
|
||||
ClearHotkeys();
|
||||
}
|
||||
|
||||
@@ -67,24 +65,40 @@ bool Macro::CeckMatch()
|
||||
"ignoring condition check 'none' for '%s'",
|
||||
_name.c_str());
|
||||
continue;
|
||||
break;
|
||||
case LogicType::AND:
|
||||
_matched = _matched && cond;
|
||||
if (cond) {
|
||||
c->SetHighlight();
|
||||
}
|
||||
break;
|
||||
case LogicType::OR:
|
||||
_matched = _matched || cond;
|
||||
if (cond) {
|
||||
c->SetHighlight();
|
||||
}
|
||||
break;
|
||||
case LogicType::AND_NOT:
|
||||
_matched = _matched && !cond;
|
||||
if (!cond) {
|
||||
c->SetHighlight();
|
||||
}
|
||||
break;
|
||||
case LogicType::OR_NOT:
|
||||
_matched = _matched || !cond;
|
||||
if (!cond) {
|
||||
c->SetHighlight();
|
||||
}
|
||||
break;
|
||||
case LogicType::ROOT_NONE:
|
||||
_matched = cond;
|
||||
if (cond) {
|
||||
c->SetHighlight();
|
||||
}
|
||||
break;
|
||||
case LogicType::ROOT_NOT:
|
||||
_matched = !cond;
|
||||
if (!cond) {
|
||||
c->SetHighlight();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
blog(LOG_WARNING,
|
||||
@@ -102,6 +116,7 @@ bool Macro::CeckMatch()
|
||||
vblog(LOG_INFO, "ignore match for Macro %s (on change)",
|
||||
_name.c_str());
|
||||
_matched = false;
|
||||
SetOnChangeHighlight();
|
||||
}
|
||||
_lastMatched = newLastMatched;
|
||||
|
||||
@@ -111,11 +126,11 @@ bool Macro::CeckMatch()
|
||||
if (_matched && _count != std::numeric_limits<int>::max()) {
|
||||
_count++;
|
||||
}
|
||||
|
||||
_lastCheckTime = std::chrono::high_resolution_clock::now();
|
||||
return _matched;
|
||||
}
|
||||
|
||||
bool Macro::PerformAction(bool forceParallel, bool ignorePause)
|
||||
bool Macro::PerformActions(bool forceParallel, bool ignorePause)
|
||||
{
|
||||
if (!_done) {
|
||||
vblog(LOG_INFO, "macro %s already running", _name.c_str());
|
||||
@@ -125,17 +140,30 @@ bool Macro::PerformAction(bool forceParallel, bool ignorePause)
|
||||
_done = false;
|
||||
bool ret = true;
|
||||
if (_runInParallel || forceParallel) {
|
||||
if (_thread.joinable()) {
|
||||
_thread.join();
|
||||
if (_backgroundThread.joinable()) {
|
||||
_backgroundThread.join();
|
||||
}
|
||||
_thread = std::thread(
|
||||
_backgroundThread = std::thread(
|
||||
[this, ignorePause] { RunActions(ignorePause); });
|
||||
} else {
|
||||
RunActions(ret, ignorePause);
|
||||
}
|
||||
_wasExecutedRecently = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int64_t Macro::MsSinceLastCheck()
|
||||
{
|
||||
if (_lastCheckTime.time_since_epoch().count() == 0) {
|
||||
return 0;
|
||||
}
|
||||
const auto timePassed =
|
||||
std::chrono::high_resolution_clock::now() - _lastCheckTime;
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(timePassed)
|
||||
.count() +
|
||||
1;
|
||||
}
|
||||
|
||||
void Macro::SetName(const std::string &name)
|
||||
{
|
||||
_name = name;
|
||||
@@ -147,6 +175,7 @@ void Macro::ResetTimers()
|
||||
for (auto &c : _conditions) {
|
||||
c->ResetDuration();
|
||||
}
|
||||
_lastCheckTime = {};
|
||||
}
|
||||
|
||||
void Macro::RunActions(bool &retVal, bool ignorePause)
|
||||
@@ -157,9 +186,9 @@ void Macro::RunActions(bool &retVal, bool ignorePause)
|
||||
ret = ret && a->PerformAction();
|
||||
if (!ret || (_paused && !ignorePause) || _stop || _die) {
|
||||
retVal = ret;
|
||||
_done = true;
|
||||
return;
|
||||
break;
|
||||
}
|
||||
a->SetHighlight();
|
||||
}
|
||||
_done = true;
|
||||
}
|
||||
@@ -170,6 +199,11 @@ void Macro::RunActions(bool ignorePause)
|
||||
RunActions(unused, ignorePause);
|
||||
}
|
||||
|
||||
void Macro::SetOnChangeHighlight()
|
||||
{
|
||||
_onChangeTriggered = true;
|
||||
}
|
||||
|
||||
void Macro::SetPaused(bool pause)
|
||||
{
|
||||
if (_paused && !pause) {
|
||||
@@ -178,6 +212,31 @@ void Macro::SetPaused(bool pause)
|
||||
_paused = pause;
|
||||
}
|
||||
|
||||
void Macro::AddHelperThread(std::thread &&newThread)
|
||||
{
|
||||
for (unsigned int i = 0; i < _helperThreads.size(); i++) {
|
||||
if (!_helperThreads[i].joinable()) {
|
||||
_helperThreads[i] = std::move(newThread);
|
||||
return;
|
||||
}
|
||||
}
|
||||
_helperThreads.push_back(std::move(newThread));
|
||||
}
|
||||
|
||||
void Macro::Stop()
|
||||
{
|
||||
_stop = true;
|
||||
switcher->macroWaitCv.notify_all();
|
||||
for (auto &t : _helperThreads) {
|
||||
if (t.joinable()) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
if (_backgroundThread.joinable()) {
|
||||
_backgroundThread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void Macro::UpdateActionIndices()
|
||||
{
|
||||
int idx = 0;
|
||||
@@ -382,6 +441,35 @@ bool Macro::SwitchesScene()
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Macro::WasExecutedRecently()
|
||||
{
|
||||
if (_wasExecutedRecently) {
|
||||
_wasExecutedRecently = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Macro::OnChangePreventedActionsRecently()
|
||||
{
|
||||
if (_onChangeTriggered) {
|
||||
_onChangeTriggered = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Macro::ResetUIHelpers()
|
||||
{
|
||||
_onChangeTriggered = false;
|
||||
for (auto c : _conditions) {
|
||||
c->Highlight();
|
||||
}
|
||||
for (auto a : _actions) {
|
||||
a->Highlight();
|
||||
}
|
||||
}
|
||||
|
||||
static void pauseCB(void *data, obs_hotkey_id, obs_hotkey_t *, bool pressed)
|
||||
{
|
||||
if (pressed) {
|
||||
@@ -525,6 +613,8 @@ void MacroAction::LogAction()
|
||||
|
||||
void SwitcherData::saveMacros(obs_data_t *obj)
|
||||
{
|
||||
switcher->macroProperties.Save(obj);
|
||||
|
||||
obs_data_array_t *macroArray = obs_data_array_create();
|
||||
for (auto &m : macros) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
@@ -540,8 +630,9 @@ void SwitcherData::saveMacros(obs_data_t *obj)
|
||||
|
||||
void SwitcherData::loadMacros(obs_data_t *obj)
|
||||
{
|
||||
macros.clear();
|
||||
switcher->macroProperties.Load(obj);
|
||||
|
||||
macros.clear();
|
||||
obs_data_array_t *macroArray = obs_data_get_array(obj, "macros");
|
||||
size_t count = obs_data_array_count(macroArray);
|
||||
|
||||
@@ -582,7 +673,7 @@ bool SwitcherData::runMacros()
|
||||
for (auto m : macros) {
|
||||
if (m->Matched()) {
|
||||
vblog(LOG_INFO, "running macro: %s", m->Name().c_str());
|
||||
if (!m->PerformAction()) {
|
||||
if (!m->PerformActions()) {
|
||||
blog(LOG_WARNING, "abort macro: %s",
|
||||
m->Name().c_str());
|
||||
}
|
||||
|
||||
31
src/resizing-text-edit.cpp
Normal file
31
src/resizing-text-edit.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "headers/resizing-text-edit.hpp"
|
||||
|
||||
ResizingPlainTextEdit::ResizingPlainTextEdit(QWidget *parent,
|
||||
const int scrollAt,
|
||||
const int minLines,
|
||||
const int paddingLines)
|
||||
: QPlainTextEdit(parent),
|
||||
_scrollAt(scrollAt),
|
||||
_minLines(minLines),
|
||||
_paddingLines(paddingLines)
|
||||
{
|
||||
QWidget::connect(this, SIGNAL(textChanged()), this,
|
||||
SLOT(ResizeTexteditArea()));
|
||||
}
|
||||
|
||||
void ResizingPlainTextEdit::ResizeTexteditArea()
|
||||
{
|
||||
QFontMetrics f(font());
|
||||
int rowHeight = f.lineSpacing();
|
||||
int numLines = document()->blockCount();
|
||||
if (numLines + _paddingLines < _minLines) {
|
||||
setFixedHeight(_minLines * rowHeight);
|
||||
} else if (numLines + _paddingLines < _scrollAt) {
|
||||
setFixedHeight((numLines + _paddingLines) * rowHeight);
|
||||
} else {
|
||||
setFixedHeight(_scrollAt * rowHeight);
|
||||
}
|
||||
|
||||
adjustSize();
|
||||
updateGeometry();
|
||||
}
|
||||
@@ -89,7 +89,7 @@ SceneItemSelection::GetSceneItems(SceneSelection &sceneSelection)
|
||||
// As this might be confusing reverse that order internally
|
||||
int idx = count - 1 - _idx;
|
||||
|
||||
if (idx >= 0 && idx < items.size()) {
|
||||
if (idx >= 0 && idx < (int)items.size()) {
|
||||
obs_sceneitem_addref(items[idx]);
|
||||
ret.emplace_back(items[idx]);
|
||||
}
|
||||
|
||||
@@ -3,23 +3,26 @@
|
||||
|
||||
static void ScreenshotTick(void *param, float);
|
||||
|
||||
AdvSSScreenshotObj::AdvSSScreenshotObj(obs_source_t *source)
|
||||
ScreenshotHelper::ScreenshotHelper(obs_source_t *source)
|
||||
: weakSource(OBSGetWeakRef(source))
|
||||
{
|
||||
_initDone = true;
|
||||
obs_add_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
|
||||
AdvSSScreenshotObj::~AdvSSScreenshotObj()
|
||||
ScreenshotHelper::~ScreenshotHelper()
|
||||
{
|
||||
obs_enter_graphics();
|
||||
gs_stagesurface_destroy(stagesurf);
|
||||
gs_texrender_destroy(texrender);
|
||||
obs_leave_graphics();
|
||||
if (_initDone) {
|
||||
obs_enter_graphics();
|
||||
gs_stagesurface_destroy(stagesurf);
|
||||
gs_texrender_destroy(texrender);
|
||||
obs_leave_graphics();
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Screenshot()
|
||||
void ScreenshotHelper::Screenshot()
|
||||
{
|
||||
OBSSource source = OBSGetStrongRef(weakSource);
|
||||
|
||||
@@ -69,12 +72,12 @@ void AdvSSScreenshotObj::Screenshot()
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Download()
|
||||
void ScreenshotHelper::Download()
|
||||
{
|
||||
gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender));
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Copy()
|
||||
void ScreenshotHelper::Copy()
|
||||
{
|
||||
uint8_t *videoData = nullptr;
|
||||
uint32_t videoLinesize = 0;
|
||||
@@ -91,7 +94,7 @@ void AdvSSScreenshotObj::Copy()
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::MarkDone()
|
||||
void ScreenshotHelper::MarkDone()
|
||||
{
|
||||
time = std::chrono::high_resolution_clock::now();
|
||||
done = true;
|
||||
@@ -104,8 +107,7 @@ void AdvSSScreenshotObj::MarkDone()
|
||||
|
||||
static void ScreenshotTick(void *param, float)
|
||||
{
|
||||
AdvSSScreenshotObj *data =
|
||||
reinterpret_cast<AdvSSScreenshotObj *>(param);
|
||||
ScreenshotHelper *data = reinterpret_cast<ScreenshotHelper *>(param);
|
||||
|
||||
if (data->stage == STAGE_FINISH) {
|
||||
return;
|
||||
|
||||
@@ -12,7 +12,8 @@ Section::Section(const int animationDuration, QWidget *parent)
|
||||
_mainLayout = new QGridLayout(this);
|
||||
_headerWidgetLayout = new QHBoxLayout();
|
||||
|
||||
_toggleButton->setStyleSheet("QToolButton {border: none;}");
|
||||
_toggleButton->setStyleSheet(
|
||||
"QToolButton {border: none; background-color: rgba(0,0,0,0);}");
|
||||
_toggleButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
_toggleButton->setArrowType(Qt::ArrowType::RightArrow);
|
||||
_toggleButton->setCheckable(true);
|
||||
@@ -31,10 +32,8 @@ Section::Section(const int animationDuration, QWidget *parent)
|
||||
_headerWidgetLayout->setSpacing(11);
|
||||
_headerWidgetLayout->addWidget(_toggleButton);
|
||||
|
||||
int row = 0;
|
||||
_mainLayout->addLayout(_headerWidgetLayout, row, 0, 1, 1,
|
||||
Qt::AlignLeft);
|
||||
_mainLayout->addWidget(_headerLine, row++, 2, 1, 1);
|
||||
_mainLayout->addLayout(_headerWidgetLayout, 0, 0, 1, 1, Qt::AlignLeft);
|
||||
_mainLayout->addWidget(_headerLine, 0, 2, 1, 1);
|
||||
setLayout(_mainLayout);
|
||||
|
||||
connect(_toggleButton, &QToolButton::toggled, this, &Section::Collapse);
|
||||
@@ -43,17 +42,21 @@ Section::Section(const int animationDuration, QWidget *parent)
|
||||
void Section::Collapse(bool collapse)
|
||||
{
|
||||
_toggleButton->setChecked(collapse);
|
||||
_toggleButton->setArrowType(!collapse ? Qt::ArrowType::DownArrow
|
||||
: Qt::ArrowType::RightArrow);
|
||||
_toggleAnimation->setDirection(!collapse
|
||||
? QAbstractAnimation::Forward
|
||||
: QAbstractAnimation::Backward);
|
||||
_toggleButton->setArrowType(collapse ? Qt::ArrowType::RightArrow
|
||||
: Qt::ArrowType::DownArrow);
|
||||
_toggleAnimation->setDirection(collapse ? QAbstractAnimation::Backward
|
||||
: QAbstractAnimation::Forward);
|
||||
_transitioning = true;
|
||||
_collapsed = collapse;
|
||||
_toggleAnimation->start();
|
||||
emit Collapsed(collapse);
|
||||
}
|
||||
|
||||
void Section::SetContent(QWidget *w)
|
||||
{
|
||||
SetContent(w, _collapsed);
|
||||
}
|
||||
|
||||
void Section::SetContent(QWidget *w, bool collapsed)
|
||||
{
|
||||
CleanUpPreviousContent();
|
||||
@@ -61,13 +64,15 @@ void Section::SetContent(QWidget *w, bool collapsed)
|
||||
|
||||
// Setup contentArea
|
||||
_contentArea = new QScrollArea(this);
|
||||
_contentArea->setObjectName("macroSegmentContent");
|
||||
_contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
_contentArea->setStyleSheet("QScrollArea { border: none; }");
|
||||
_contentArea->setStyleSheet(
|
||||
"#macroSegmentContent { border: none; background-color: rgba(0,0,0,0); }");
|
||||
_contentArea->setMaximumHeight(0);
|
||||
_contentArea->setMinimumHeight(0);
|
||||
|
||||
w->installEventFilter(this);
|
||||
_content = w;
|
||||
_content->installEventFilter(this);
|
||||
auto newLayout = new QVBoxLayout();
|
||||
newLayout->setContentsMargins(0, 0, 0, 0);
|
||||
newLayout->addWidget(w);
|
||||
@@ -75,7 +80,9 @@ void Section::SetContent(QWidget *w, bool collapsed)
|
||||
_mainLayout->addWidget(_contentArea, 1, 0, 1, 3);
|
||||
|
||||
_headerHeight = sizeHint().height() - _contentArea->maximumHeight();
|
||||
_contentHeight = newLayout->sizeHint().height();
|
||||
_contentHeight = _content->sizeHint().height();
|
||||
|
||||
SetupAnimations();
|
||||
|
||||
if (collapsed) {
|
||||
this->setMinimumHeight(_headerHeight);
|
||||
@@ -84,8 +91,11 @@ void Section::SetContent(QWidget *w, bool collapsed)
|
||||
this->setMinimumHeight(_headerHeight + _contentHeight);
|
||||
_contentArea->setMaximumHeight(_contentHeight);
|
||||
}
|
||||
SetupAnimations();
|
||||
Collapse(collapsed);
|
||||
const QSignalBlocker b(_toggleButton);
|
||||
_toggleButton->setChecked(collapsed);
|
||||
_toggleButton->setArrowType(collapsed ? Qt::ArrowType::RightArrow
|
||||
: Qt::ArrowType::DownArrow);
|
||||
_collapsed = collapsed;
|
||||
}
|
||||
|
||||
void Section::AddHeaderWidget(QWidget *w)
|
||||
@@ -95,17 +105,24 @@ void Section::AddHeaderWidget(QWidget *w)
|
||||
|
||||
void Section::SetCollapsed(bool collapsed)
|
||||
{
|
||||
if (_collapsed == collapsed) {
|
||||
return;
|
||||
}
|
||||
Collapse(collapsed);
|
||||
}
|
||||
|
||||
bool Section::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::Resize && !_transitioning && !_collapsed) {
|
||||
_contentHeight = _content->sizeHint().height();
|
||||
setMaximumHeight(_headerHeight + _contentHeight);
|
||||
setMinimumHeight(_headerHeight + _contentHeight);
|
||||
_contentArea->setMaximumHeight(_contentHeight);
|
||||
SetupAnimations();
|
||||
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();
|
||||
}
|
||||
}
|
||||
return QObject::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,12 @@ StatusControl::StatusControl(QWidget *parent, bool noLayout) : QWidget(parent)
|
||||
{
|
||||
_button = new QPushButton("-", this);
|
||||
_status = new QLabel("-", this);
|
||||
_status->setStyleSheet("QLabel{ \
|
||||
border-style: outset; \
|
||||
border-width: 2px; \
|
||||
border-radius: 7px; \
|
||||
border-color: rgb(0,0,0,0) \
|
||||
}");
|
||||
_statusPrefix = new QLabel(
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.status.currentStatus"),
|
||||
@@ -87,7 +93,7 @@ void StatusControl::SetStopped()
|
||||
_status->setText(obs_module_text("AdvSceneSwitcher.status.inactive"));
|
||||
if (!switcher->disableHints) {
|
||||
_pulse = PulseWidget(_status, QColor(Qt::red),
|
||||
QColor(0, 0, 0, 0), "QLabel ");
|
||||
QColor(0, 0, 0, 0));
|
||||
}
|
||||
_setToStopped = true;
|
||||
}
|
||||
|
||||
@@ -134,10 +134,16 @@ void AdvSceneSwitcher::setupRandomTab()
|
||||
ui->randomHelp->setVisible(false);
|
||||
}
|
||||
|
||||
ui->randomDisabledWarning->setStyleSheet("QLabel{ \
|
||||
border-style: outset; \
|
||||
border-width: 2px; \
|
||||
border-radius: 7px; \
|
||||
border-color: rgb(0,0,0,0) \
|
||||
}");
|
||||
if (switcher->switchIfNotMatching != RANDOM_SWITCH) {
|
||||
if (!switcher->disableHints) {
|
||||
PulseWidget(ui->randomDisabledWarning, QColor(Qt::red),
|
||||
QColor(0, 0, 0, 0), "QLabel ");
|
||||
QColor(0, 0, 0, 0));
|
||||
}
|
||||
} else {
|
||||
ui->randomDisabledWarning->setVisible(false);
|
||||
|
||||
@@ -98,7 +98,7 @@ void AdvSceneSwitcher::on_getScreenshot_clicked()
|
||||
}
|
||||
|
||||
auto source = obs_weak_source_get_source(s->videoSource);
|
||||
auto screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
auto screenshotData = std::make_unique<ScreenshotHelper>(source);
|
||||
obs_source_release(source);
|
||||
|
||||
QString filePath = QFileDialog::getSaveFileName(this);
|
||||
@@ -254,7 +254,7 @@ void VideoSwitch::load(obs_data_t *obj)
|
||||
void VideoSwitch::getScreenshot()
|
||||
{
|
||||
auto source = obs_weak_source_get_source(videoSource);
|
||||
screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
screenshotData = std::make_unique<ScreenshotHelper>(source);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
|
||||
@@ -204,12 +204,13 @@ void placeWidgets(std::string text, QBoxLayout *layout,
|
||||
}
|
||||
}
|
||||
|
||||
void deleteLayoutItem(QLayoutItem *item)
|
||||
void deleteLayoutItemWidget(QLayoutItem *item)
|
||||
{
|
||||
if (item) {
|
||||
auto widget = item->widget();
|
||||
if (widget) {
|
||||
widget->setVisible(false);
|
||||
widget->deleteLater();
|
||||
}
|
||||
delete item;
|
||||
}
|
||||
@@ -223,20 +224,24 @@ void clearLayout(QLayout *layout, int afterIdx)
|
||||
clearLayout(item->layout());
|
||||
delete item->layout();
|
||||
}
|
||||
if (item->widget()) {
|
||||
delete item->widget();
|
||||
}
|
||||
delete item;
|
||||
deleteLayoutItemWidget(item);
|
||||
}
|
||||
}
|
||||
|
||||
void setLayoutVisible(QLayout *layout, bool visible)
|
||||
{
|
||||
if (!layout) {
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < layout->count(); ++i) {
|
||||
QWidget *widget = layout->itemAt(i)->widget();
|
||||
if (widget != NULL) {
|
||||
QLayout *nestedLayout = layout->itemAt(i)->layout();
|
||||
if (widget) {
|
||||
widget->setVisible(visible);
|
||||
}
|
||||
if (nestedLayout) {
|
||||
setLayoutVisible(nestedLayout, visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +600,8 @@ void populateAudioSelection(QComboBox *sel, bool addSelect)
|
||||
sel->setCurrentIndex(0);
|
||||
}
|
||||
|
||||
void populateVideoSelection(QComboBox *sel, bool addScenes, bool addSelect)
|
||||
void populateVideoSelection(QComboBox *sel, bool addMainOutput, bool addScenes,
|
||||
bool addSelect)
|
||||
{
|
||||
|
||||
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
|
||||
@@ -623,6 +629,10 @@ void populateVideoSelection(QComboBox *sel, bool addScenes, bool addSelect)
|
||||
}
|
||||
|
||||
sel->model()->sort(0);
|
||||
if (addMainOutput) {
|
||||
sel->insertItem(
|
||||
0, obs_module_text("AdvSceneSwitcher.OBSVideoOutput"));
|
||||
}
|
||||
if (addSelect) {
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
@@ -881,37 +891,42 @@ bool windowPosValid(QPoint pos)
|
||||
}
|
||||
|
||||
QMetaObject::Connection PulseWidget(QWidget *widget, QColor startColor,
|
||||
QColor endColor, QString specifier,
|
||||
bool once)
|
||||
QColor endColor, bool once)
|
||||
{
|
||||
widget->setStyleSheet(specifier + "{ \
|
||||
border-style: outset; \
|
||||
border-width: 2px; \
|
||||
border-radius: 10px; \
|
||||
border-color: rgb(0,0,0,0) \
|
||||
}");
|
||||
|
||||
QGraphicsColorizeEffect *eEffect = new QGraphicsColorizeEffect(widget);
|
||||
widget->setGraphicsEffect(eEffect);
|
||||
QPropertyAnimation *paAnimation =
|
||||
new QPropertyAnimation(eEffect, "color");
|
||||
paAnimation->setStartValue(startColor);
|
||||
paAnimation->setEndValue(endColor);
|
||||
paAnimation->setDuration(1000);
|
||||
QGraphicsColorizeEffect *effect = new QGraphicsColorizeEffect(widget);
|
||||
widget->setGraphicsEffect(effect);
|
||||
QPropertyAnimation *animation =
|
||||
new QPropertyAnimation(effect, "color", widget);
|
||||
animation->setStartValue(startColor);
|
||||
animation->setEndValue(endColor);
|
||||
animation->setDuration(1000);
|
||||
|
||||
QMetaObject::Connection con;
|
||||
if (!once) {
|
||||
if (once) {
|
||||
auto widgetPtr = widget;
|
||||
con = QWidget::connect(
|
||||
paAnimation, &QPropertyAnimation::finished,
|
||||
[paAnimation]() {
|
||||
QTimer::singleShot(1000, [paAnimation] {
|
||||
paAnimation->start();
|
||||
});
|
||||
animation, &QPropertyAnimation::finished,
|
||||
[widgetPtr]() {
|
||||
if (widgetPtr) {
|
||||
widgetPtr->setGraphicsEffect(nullptr);
|
||||
}
|
||||
});
|
||||
animation->start(QPropertyAnimation::DeleteWhenStopped);
|
||||
} else {
|
||||
auto widgetPtr = widget;
|
||||
con = QWidget::connect(
|
||||
animation, &QPropertyAnimation::finished,
|
||||
[animation, widgetPtr]() {
|
||||
QTimer *timer = new QTimer(widgetPtr);
|
||||
QWidget::connect(timer, &QTimer::timeout,
|
||||
[animation] {
|
||||
animation->start();
|
||||
});
|
||||
timer->setSingleShot(true);
|
||||
timer->start(1000);
|
||||
});
|
||||
animation->start();
|
||||
}
|
||||
|
||||
paAnimation->start();
|
||||
|
||||
return con;
|
||||
}
|
||||
|
||||
@@ -984,3 +999,8 @@ void setHeightToContentHeight(QListWidget *list)
|
||||
2 * list->frameWidth());
|
||||
}
|
||||
}
|
||||
|
||||
bool doubleEquals(double left, double right, double epsilon)
|
||||
{
|
||||
return (fabs(left - right) < epsilon);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user