Compare commits

...

84 Commits

Author SHA1 Message Date
WarmUpTill
666c52556e Fix plugin state condition not functioning on OBS shutdown
Also added note indicating the limitations of running macros on OBS
shutdown
2023-08-05 15:43:44 +02:00
WarmUpTill
1c0734d1cb Add media_playlist_source_codeyan to supported media source types
Source at: https://github.com/CodeYan01/media-playlist-source/
2023-08-05 13:35:19 +02:00
WarmUpTill
1304ac7336 Add tooltip to regex configure widget 2023-08-03 21:44:17 +02:00
WarmUpTill
884fb1b637 Adjust to SceneItemSelection rework
And do minor cleanup
2023-08-03 21:44:17 +02:00
WarmUpTill
a82bce947f Rework scene item selection
Added support for:
* Index based scelection
* Type based selection
* Name pattern based selection
2023-08-03 21:44:17 +02:00
WarmUpTill
d5241f0700 Add tooltip for variable and connection selection 2023-08-03 21:44:17 +02:00
WarmUpTill
f24f258c9c Add option to hide enable button and set default enable value 2023-08-03 21:44:17 +02:00
WarmUpTill
eddd9fa8ab Add helper to check if value is a valid number 2023-08-03 21:44:17 +02:00
WarmUpTill
6cc224aeec Add helper to get int from variable 2023-08-03 21:44:17 +02:00
WarmUpTill
4b0a631987 Add export / import functionality to macro tab
This enables the easy sharing of single / multiple macros across scene
collections.
Previously either all settings had to be copied via the export / import
functionality of the General tab or none at all.
2023-08-02 20:55:59 +02:00
WarmUpTill
dcae0e8e8b Reset scene switcher state when new scene collection is created
This avoids copying all macros of the previous scene collection to the
newly created one.
2023-08-02 20:55:59 +02:00
WarmUpTill
29f810a2a0 Add buttons to move macro segments to top / bottom of segment list 2023-08-02 17:00:34 +02:00
WarmUpTill
719dfa7b72 Fix segment selection being lost when using up / down button 2023-08-02 17:00:34 +02:00
WarmUpTill
259aac8fcd Perform actions only on condition change by default 2023-08-02 17:00:34 +02:00
WarmUpTill
5be2cd278f Allow empty string as argument and increase max length of argument 2023-08-02 17:00:34 +02:00
WarmUpTill
211eac5313 Add slide show condition
It allows you to check ...
* if the slide changed
* the current slide index
* the current slide path

Important limitation:
Its internal state is only updated whenever the "slide_changed" signal
is sent by the particular source.
2023-08-01 23:52:44 +02:00
WarmUpTill
f92ed3139d Add more transform actions
* Reset
* Rotation
* Flip (horizontal / vertical)
* Fit to screen
* Stretch to screen
* Center (horizontal / vertical / to screen)
2023-07-28 18:51:30 +02:00
WarmUpTill
bdff84c605 Use MacroSegmentSelection for index selection in "Variable" action 2023-07-27 21:06:54 +02:00
WarmUpTill
08e430396a Add option to check action enable state to "Macro" condition 2023-07-27 21:06:54 +02:00
WarmUpTill
df7fc7ec01 Add option to enable / disable actions of macros via "Macro" action 2023-07-27 21:06:54 +02:00
WarmUpTill
9eef581161 Add MacroSegmentSelection
Utility class to enable selection of index of a macro segments.
Add description of selected segment.
Highlights segment if selected macro is current macro.
2023-07-27 21:06:54 +02:00
WarmUpTill
52811e44f4 Add option to disable variable selection 2023-07-27 21:06:54 +02:00
WarmUpTill
7b1c56877e Add GetMacroSegmentDescription() and IsValidMacroSegmentIndex() 2023-07-27 21:06:54 +02:00
WarmUpTill
78d73af589 Adjust to FilterComboBox 2023-07-27 21:06:54 +02:00
WarmUpTill
d9c1ef4c22 Only emit signals if valid entry was found 2023-07-27 21:06:54 +02:00
WarmUpTill
93f96ee660 Add support for custom URIs 2023-07-27 19:53:25 +02:00
WarmUpTill
d25a8d4efb Remove unnecessary include 2023-07-27 19:53:25 +02:00
WarmUpTill
1f22278496 Move SetGridLayoutRowVisible() to utility 2023-07-27 19:53:25 +02:00
WarmUpTill
70b705e0fd Add variable support to timer condition 2023-07-26 13:34:47 +02:00
WarmUpTill
9e1dfdc3f0 Enable use of duration modifiers for hotkey condition 2023-07-26 08:00:16 +02:00
WarmUpTill
47d6d4ac06 Handle potential horizontal scrollbar when resizing StringListEdit 2023-07-23 00:29:14 +02:00
WarmUpTill
fe3586b6f4 Set maximum header size to 4096 2023-07-23 00:29:14 +02:00
WarmUpTill
650881204f Adjust capitalization of HTTP action 2023-07-23 00:29:14 +02:00
WarmUpTill
8c0ba112e2 Add SetMaxStringSize()
A fixed size of 170 chars caused issues with the http action
2023-07-23 00:29:14 +02:00
WarmUpTill
a17d8f7e09 Add support for sending generic OBS websocket messages
This should allow to send OBS websocket vendor requests to other plugins
which offer an API via this method (e.g. vertical OBS)
2023-07-15 14:48:45 +02:00
WarmUpTill
371b4ae05d Add option to disable individual actions of macros 2023-07-15 11:05:19 +02:00
WarmUpTill
1d7ce510f7 Add SwitchButton
Simple switch toogle button widget based on
https://github.com/KDAB/kdabtv/blob/master/Styling-Qt-Widgets/toggleswitch.h
2023-07-15 11:05:19 +02:00
WarmUpTill
d759ded64d Use FilterComboBox instead of regular QComboBox
This required the following adjustments:

Instead of having a dedicated entry indicating the empty selection the
setPlaceholderText() mechanism is used.
Thus the locations where the 1st entry was assumed to be the empty
selection would have to be adjusted.

Additional checks for the empty string / index -1 have been added.

FindIdxInRagne() was adjusted to return -1 instead of 0 in case the
given string was not found.

Switched to index based singal instead of text based signal to be
notified about selection changes.
2023-07-15 11:05:19 +02:00
WarmUpTill
0b2e1b88cc Add FilterComboBox
This helper class is based on QComboBox, which has filtering of items
enabled by default.
It is intended to be used in places where QComboBoxes have a lot of
entries.
For example, the condition, action, or macro selection widgets.
2023-07-15 11:05:19 +02:00
WarmUpTill
72a11eedfc Make selection entry optional for transition list
Needed for FilterComboBox
2023-07-15 11:05:19 +02:00
WarmUpTill
1f02226620 Add option to set flags in FindIdxInRagne() 2023-07-15 11:05:19 +02:00
WarmUpTill
ad8c29997c Fix group creation starting at incorrect index 2023-07-15 04:22:08 +02:00
WarmUpTill
2c7c40c3f0 Fix reordering issues in macro tree 2023-07-15 04:22:08 +02:00
WarmUpTill
c9c0e4db2e Improve OCR option of video condition
* Improve preprocessing of image by separating text color from the rest
  of the image properly
* Add option to choose how similar the matched color can be to other
  colors for it to still be counted as a match
* Show the prepreprocessed image in the "show match" preview dialog
2023-07-09 17:58:38 +02:00
WarmUpTill
dd880b13c0 Cleanup invertPatternMatchResult() 2023-07-09 17:58:38 +02:00
WarmUpTill
76c759c8f4 Display different message if whole image matches pattern
The "pattern is highlighted in red" message does not make sense in this
case as there is no room to display the red border around the match.
2023-07-09 17:58:38 +02:00
WarmUpTill
284c8020b2 Add OCL support to improve performance 2023-07-09 17:58:38 +02:00
WarmUpTill
f506600b8c Enable use of other OCR language models 2023-07-09 17:58:38 +02:00
WarmUpTill
339418b5f3 Add color matching 2023-07-09 17:58:38 +02:00
WarmUpTill
2eca2cc2e9 Cleanup video condition
* Style changes
* Separate functionality into different widgets
* Fix typos
2023-07-09 17:58:38 +02:00
WarmUpTill
4315d309f2 Add option to show description as tooltip 2023-07-09 17:58:38 +02:00
WarmUpTill
8d18bb363a Add options to disable plugins 2023-07-07 21:35:51 +02:00
WarmUpTill
592f31f059 Use the correct build type for dependencies (Windows)
Always building with the Release configuration caused issues (e.g.
linking) when attempting to build non-release configurations when using
the CI build scripts.
This is not done for OpenCV as it does not support all build types.
2023-07-07 21:35:51 +02:00
WarmUpTill
300ad15ad0 Reduce size of audio action widget
Only show audio fade options when they are needed.
2023-07-07 21:35:51 +02:00
WarmUpTill
91f93169eb Add option to set deinterlace mode and deinterlace field order 2023-07-07 19:19:40 +02:00
WarmUpTill
5d95069291 Cleanup
* Move run count increment back to PerformActions()
 * Rename variable
2023-07-06 19:59:46 +02:00
WarmUpTill
e5bb9162ef Prevent flicker when creating MacroConditionMacroEdit
Seems to be caused by the fact that some widgets were not part of any
layout while setLayout() was already called
2023-07-06 19:59:46 +02:00
WarmUpTill
6869cc8a01 Add option to highlight macro dock if conditions of macro are true 2023-07-06 19:59:46 +02:00
WarmUpTill
55c7ac8a32 Rework WasExecutedRecenty()
Added ExecutedSince() instead as WasExecutedRecenty() did not support
checking if a macro was executed recently when using it multiple times
in quick succession.

This could be the case if the MacroTree and a MacroDock would both
attempt to use this function at the same time.
2023-07-06 19:59:46 +02:00
WarmUpTill
101a85b063 Enable use of variables in macro dock widgets 2023-07-06 19:59:46 +02:00
WarmUpTill
e7c6afbc6a Fix potential crash when resolving variables during shutdown 2023-07-06 19:59:46 +02:00
WarmUpTill
18e19fb6c8 Add support for adding status labels to macro docks 2023-07-06 19:59:46 +02:00
WarmUpTill
a7ccfc3282 Add action to toggle lock of scene items 2023-07-05 00:53:03 +02:00
Joao Eriberto Mota Filho
85eb2a4235 Fix spelling errors 2023-07-01 20:51:31 +02:00
WarmUpTill
3e3805b542 Ignore libremidi warnings 2023-06-17 13:57:35 +02:00
WarmUpTill
c59acd836a Reduce size of audio action widget
Only show audio fade options when they are needed.
2023-06-17 13:57:35 +02:00
WarmUpTill
1e81181312 Add option to toggle filter 2023-06-09 20:28:01 +02:00
WarmUpTill
8a796efdb3 Allow specifying custom prompts for variable input 2023-06-07 12:36:39 +02:00
WarmUpTill
1a07a93552 Add spacer if not modifying strValue and remove it otherwise 2023-06-07 12:36:39 +02:00
WarmUpTill
bd74629420 Add helpers to add and remove trailing spacer items in QBoxLayouts 2023-06-07 12:36:39 +02:00
WarmUpTill
dabb293cc0 Mark locale maps as const
There is no reason to have these not be const as new locale entries will
not be added at runtime.
2023-06-03 22:38:42 +02:00
WarmUpTill
b6031bd29a Fix typo in locale name 2023-06-03 22:38:42 +02:00
WarmUpTill
05fa3af2ac Fix crash while waiting for transition and no transition is selected 2023-05-31 13:39:47 +02:00
WarmUpTill
c52166bfec Add option to trigger OBS hotkeys directly
Main use case would be to interact with other plugins which offer
hotkeys.
The benefit of this method is that it does not require a key to be bound
to the particular hotkey to trigger the functionality.
2023-05-30 13:14:58 +02:00
WarmUpTill
6d3964fa5d Fix non-modal-dialog answer being ignored 2023-05-30 13:14:58 +02:00
WarmUpTill
65545d85ba Let OBS handle dock restoration on first startup and cleanup on shutdown
The "manual" attempt at restoring the docks should only be performed on
scene collection change as OBS will already take care of this on
starutp.

Do not delete the macro dock widgets on shutdown so OBS is able to save
their location and size properly.
2023-05-30 12:58:23 +02:00
WarmUpTill
dea4ff47cd Add option to prompt user for value to be assigned to variable 2023-05-28 19:59:12 +02:00
WarmUpTill
258a5f6ab7 Move NonModalMessageDialog to separate file 2023-05-28 19:59:12 +02:00
WarmUpTill
2048ca2816 Add scenes to possible selection of "Source" condition type
It might be useful to check if a given scene is visible outside of it
being the currently active scene, as scenes can also be used as sources
within other scenes.
2023-05-26 21:45:45 +02:00
WarmUpTill
eb9e2e8062 Const correctness 2023-05-26 21:45:45 +02:00
WarmUpTill
3229bf0150 Clean up source condition and action
* Add hints directing to the scene item visibility action / condition as
  they frequently get mixed up
* Clean up layout of the source condition (removed spaces in locale as
  this unnecessary spacing in the layout when the settings check was not
  selected
2023-05-26 21:45:45 +02:00
WarmUpTill
dfa12f9807 Add support for configuring additional streaming options
* Set server
* Set stream key
* Set username
* Set password
2023-05-26 20:42:16 +02:00
WarmUpTill
e0917a35fd Fix function priorities not being saved 2023-05-20 06:18:32 -07:00
WarmUpTill
686b00ffd1 Update zh-CN.ini locale (provided by qwe1154323937) 2023-05-20 01:59:32 -07:00
146 changed files with 7110 additions and 2213 deletions

View File

@@ -104,7 +104,7 @@ function Build {
'-G', $CmakeGenerator
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_BUILD_TYPE=${Configuration}"
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${DepInstallPath}"
"-DSW_BUILD=OFF"
@@ -115,7 +115,7 @@ function Build {
Invoke-External cmake -S ${LeptonicaPath} -B ${LeptonicaBuildPath} @LeptonicaCmakeArgs
$LeptonicaCmakeArgs = @(
'--config', "Release"
'--config', "${Configuration}"
)
if ( $VerbosePreference -eq 'Continue' ) {
@@ -140,7 +140,7 @@ function Build {
'-G', $CmakeGenerator
"-DCMAKE_SYSTEM_VERSION=${script:PlatformSDK}"
"-DCMAKE_GENERATOR_PLATFORM=$(if (${script:Target} -eq "x86") { "Win32" } else { "x64" })"
"-DCMAKE_BUILD_TYPE=Release"
"-DCMAKE_BUILD_TYPE=${Configuration}"
"-DCMAKE_PREFIX_PATH:PATH=${DepInstallPath}"
"-DCMAKE_INSTALL_PREFIX:PATH=${DepInstallPath}"
"-DSW_BUILD=OFF"
@@ -154,7 +154,7 @@ function Build {
Invoke-External cmake -S ${TesseractPath} -B ${TesseractBuildPath} @TesseractCmakeArgs
$TesseractCmakeArgs = @(
'--config', "Release"
'--config', "${Configuration}"
)
if ( $VerbosePreference -eq 'Continue' ) {

View File

@@ -125,6 +125,8 @@ target_sources(
src/macro-core/macro-action-run.hpp
src/macro-core/macro-action-scene-collection.cpp
src/macro-core/macro-action-scene-collection.hpp
src/macro-core/macro-action-scene-lock.cpp
src/macro-core/macro-action-scene-lock.hpp
src/macro-core/macro-action-scene-order.cpp
src/macro-core/macro-action-scene-order.hpp
src/macro-core/macro-action-scene-switch.cpp
@@ -201,6 +203,8 @@ target_sources(
src/macro-core/macro-condition-scene-transform.hpp
src/macro-core/macro-condition-scene-visibility.cpp
src/macro-core/macro-condition-scene-visibility.hpp
src/macro-core/macro-condition-slideshow.cpp
src/macro-core/macro-condition-slideshow.hpp
src/macro-core/macro-condition-scene.cpp
src/macro-core/macro-condition-scene.hpp
src/macro-core/macro-condition-source.cpp
@@ -255,16 +259,24 @@ target_sources(
src/utils/log-helper.hpp
src/utils/file-selection.cpp
src/utils/file-selection.hpp
src/utils/filter-combo-box.cpp
src/utils/filter-combo-box.hpp
src/utils/filter-selection.cpp
src/utils/filter-selection.hpp
src/utils/macro-export-import-dialog.cpp
src/utils/macro-export-import-dialog.hpp
src/utils/macro-list.cpp
src/utils/macro-list.hpp
src/utils/macro-segment-selection.cpp
src/utils/macro-segment-selection.hpp
src/utils/math-helpers.cpp
src/utils/math-helpers.hpp
src/utils/mouse-wheel-guard.cpp
src/utils/mouse-wheel-guard.hpp
src/utils/name-dialog.cpp
src/utils/name-dialog.hpp
src/utils/non-modal-dialog.cpp
src/utils/non-modal-dialog.hpp
src/utils/obs-dock.hpp
src/utils/obs-module-helper.cpp
src/utils/obs-module-helper.hpp
@@ -296,6 +308,8 @@ target_sources(
src/utils/string-list.hpp
src/utils/striped-frame.cpp
src/utils/striped-frame.hpp
src/utils/switch-button.cpp
src/utils/switch-button.hpp
src/utils/sync-helper.cpp
src/utils/sync-helper.hpp
src/utils/transition-selection.cpp

View File

@@ -438,7 +438,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Stream"
AdvSceneSwitcher.action.streaming.type.stop="Stream stoppen"
AdvSceneSwitcher.action.streaming.type.start="Stream starten"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Ausführen"
AdvSceneSwitcher.action.run.arguments="Argumente:"
AdvSceneSwitcher.action.run.addArgument="Argument hinzufügen"
@@ -451,7 +451,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Verstecken"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Umschalten"
AdvSceneSwitcher.action.sceneVisibility.type.source="Quelle"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Beliebig"
AdvSceneSwitcher.action.sceneVisibility.entry="Auf{{scenes}}{{actions}}{{sourceTypes}}{{sources}}{{sourceGroups}}"
AdvSceneSwitcher.action.sceneVisibility.entry="Auf{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Aktivieren"
AdvSceneSwitcher.action.filter.type.disable="Deaktivieren"
@@ -466,7 +466,7 @@ AdvSceneSwitcher.action.source.type.refreshSettings="Aktualisieren der Quellenei
AdvSceneSwitcher.action.source.type.pressSettingsButton="Drücken der Einstellungstaste"
AdvSceneSwitcher.action.source.type.refreshSettings.tooltip="Kann verwendet werden, um Browser-, Medien- usw. Quellen zu aktualisieren"
AdvSceneSwitcher.action.source.noSettingsButtons="Keine Buttons gefunden!"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
AdvSceneSwitcher.action.source.warning="Warnung: Das globale Aktivieren und Deaktivieren von Quellen kann nicht über die OBS-Benutzeroberfläche gesteuert werden"
AdvSceneSwitcher.action.source.getSettings="Aktuelle Einstellungen abfragen"
AdvSceneSwitcher.action.media="Medien"
@@ -484,7 +484,7 @@ AdvSceneSwitcher.action.macro.type.unpause="Nicht mehr pausieren"
AdvSceneSwitcher.action.macro.type.resetCounter="Zähler zurücksetzen"
AdvSceneSwitcher.action.macro.type.run="Aktionen ausführen"
AdvSceneSwitcher.action.macro.type.stop="Aktionen stoppen"
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Plugin-Status"
AdvSceneSwitcher.action.pluginState.type.stop="Erweiterten Automatischen Szenenwechsler stoppen"
AdvSceneSwitcher.action.pluginState.type.noMatch="Ändern des Nichtübereinstimmungsverhaltens:"
@@ -516,7 +516,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="An Position verschieben"
AdvSceneSwitcher.action.sceneOrder.entry="Auf{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Szenenelement transformieren"
AdvSceneSwitcher.action.sceneTransform.getTransform="Transformation erhalten"
AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}transformieren{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.file="Datei"
AdvSceneSwitcher.action.file.type.write="Schreiben"
AdvSceneSwitcher.action.file.type.append="Anhängen"
@@ -646,7 +646,7 @@ AdvSceneSwitcher.mediaTab.states.none="Keine Auswahl"
AdvSceneSwitcher.mediaTab.states.playing="Abspielen"
AdvSceneSwitcher.mediaTab.states.opening="Öffnen"
AdvSceneSwitcher.mediaTab.states.buffering="Buffern"
AdvSceneSwitcher.mediaTab.states.Paused="Pausiert"
AdvSceneSwitcher.mediaTab.states.paused="Pausiert"
AdvSceneSwitcher.mediaTab.states.stopped="Gestoppt"
AdvSceneSwitcher.mediaTab.states.ended="Beendet"
AdvSceneSwitcher.mediaTab.states.error="Fehler"

View File

@@ -92,6 +92,12 @@ AdvSceneSwitcher.macroTab.group="Group Selected Macros"
AdvSceneSwitcher.macroTab.ungroup="Ungroup Selected Groups"
AdvSceneSwitcher.macroTab.rename="Rename"
AdvSceneSwitcher.macroTab.remove="Remove"
AdvSceneSwitcher.macroTab.export="Export"
AdvSceneSwitcher.macroTab.export.info="Paste the string below into the import dialog to import the selected macros:"
AdvSceneSwitcher.macroTab.import="Import"
AdvSceneSwitcher.macroTab.import.info="Paste the export string into the below text box to import macros:"
AdvSceneSwitcher.macroTab.import.invalid="Invalid import data provided!"
AdvSceneSwitcher.macroTab.import.nameConflict="Cannot continue importing macro \"%1\" as a macro with the same name already exists!\nDo you want to continue with the import of \"%2\" and choose a new name? (Will be skipped otherwise)"
AdvSceneSwitcher.macroTab.expandAll="Expand all"
AdvSceneSwitcher.macroTab.collapseAll="Collapse all"
AdvSceneSwitcher.macroTab.maximize="Maximize"
@@ -107,13 +113,19 @@ AdvSceneSwitcher.macroTab.currentDisableHotkeys="Register hotkeys to control the
AdvSceneSwitcher.macroTab.currentRegisterDock="Register dock widget to control the pause state of selected macro or run it manually"
AdvSceneSwitcher.macroTab.currentDockAddRunButton="Add button to run the macro"
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="Add button to pause or unpause the macro"
AdvSceneSwitcher.macroTab.currentDockAddStatusLabel="Add status label"
AdvSceneSwitcher.macroTab.currentDockButtonText.run="Run button text:"
AdvSceneSwitcher.macroTab.currentDockButtonText.pause="Pause button text:"
AdvSceneSwitcher.macroTab.currentDockButtonText.unpause="Unpause button text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.true="Conditions true text:"
AdvSceneSwitcher.macroTab.currentDockStatusText.false="Conditions false text:"
AdvSceneSwitcher.macroTab.currentDockHighlightIfExecuted="Highlight dock if macro actions were recently executed"
AdvSceneSwitcher.macroDock.pause="Pause"
AdvSceneSwitcher.macroDock.unpause="Unpause"
AdvSceneSwitcher.macroDock.run="Run"
AdvSceneSwitcher.macroDock.statusLabel.true="Conditions are true."
AdvSceneSwitcher.macroDock.statusLabel.false="Conditions are false."
; Macro List
AdvSceneSwitcher.macroList.deleted="deleted"
@@ -198,6 +210,7 @@ AdvSceneSwitcher.condition.video.condition.pattern="matches pattern"
AdvSceneSwitcher.condition.video.condition.object="contains object"
AdvSceneSwitcher.condition.video.condition.brightness="brightness"
AdvSceneSwitcher.condition.video.condition.ocr="contains text"
AdvSceneSwitcher.condition.video.condition.color="matches color"
AdvSceneSwitcher.condition.video.askFileAction="Do you want to use an existing file or create a screenshot of the currently selected source?"
AdvSceneSwitcher.condition.video.askFileAction.file="Use existing file"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="Create screenshot"
@@ -225,6 +238,7 @@ AdvSceneSwitcher.condition.video.screenshotFail="Failed to get screenshot of sou
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.patternMatchSuccessFullImage="Full image matches the pattern"
AdvSceneSwitcher.condition.video.objectMatchFail="Object was not found!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
AdvSceneSwitcher.condition.video.ocrMatchSuccess="Detected text:\n\n%1"
@@ -239,6 +253,11 @@ AdvSceneSwitcher.condition.video.ocrMode.circleWord="Single word in a circle"
AdvSceneSwitcher.condition.video.ocrMode.singleChar="Single character"
AdvSceneSwitcher.condition.video.ocrMode.sparseText="Find text in no particular order"
AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD="Find text in no particular order (OSD)"
AdvSceneSwitcher.condition.video.ocrLanguageNotFound="<html><head/><body><p>Language model for \"%1\" was not found in:<br>\"%2\"<br><br>You can find additional language models <a href=\"https://github.com/tesseract-ocr/tessdata\"><span style=\" text-decoration: underline; color:#268bd2;\">here</span></a>."
AdvSceneSwitcher.condition.video.colorMatchThreshold="Match threshold:"
AdvSceneSwitcher.condition.video.colorMatchThresholdDescription="How much of the image has to match the given color?\nA value of 1 requires every pixel of the input image to match the given color."
AdvSceneSwitcher.condition.video.colorDeviationThreshold="Color deviation:"
AdvSceneSwitcher.condition.video.colorDeviationThresholdDescription="How similar can the clolor be to the provided input color for it to still count as a match?\nA value of 0 requires a perfect color match, while a higher value includes similar colors also."
AdvSceneSwitcher.condition.video.type.main="OBS's main output"
AdvSceneSwitcher.condition.video.type.source="Source"
AdvSceneSwitcher.condition.video.type.scene="Scene"
@@ -250,6 +269,8 @@ AdvSceneSwitcher.condition.video.entry.checkAreaEnable="Perform check only in ar
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="Check for text color:{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="Check for text type:{{textType}}"
AdvSceneSwitcher.condition.video.entry.orcLanguage="Check for language:{{languageCode}}"
AdvSceneSwitcher.condition.video.entry.color="Check for color:{{color}}{{selectColor}}"
AdvSceneSwitcher.condition.video.minSize="Minimum size:"
AdvSceneSwitcher.condition.video.maxSize="Maximum size:"
AdvSceneSwitcher.condition.video.selectArea="Select area"
@@ -278,6 +299,7 @@ AdvSceneSwitcher.condition.pluginState.state.start="Plugin started"
AdvSceneSwitcher.condition.pluginState.state.restart="Plugin restarted"
AdvSceneSwitcher.condition.pluginState.state.running="Plugin is running"
AdvSceneSwitcher.condition.pluginState.state.shutdown="OBS is shutting down"
AdvSceneSwitcher.condition.pluginState.state.shutdown.limitation="Note that while actions performing OBS specific changes will still be executed on shutdown those changes will not be saved as OBS is already shutting down!"
AdvSceneSwitcher.condition.pluginState.state.sceneCollection="Scene collection loaded"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="Scene changed by previous macro"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
@@ -292,10 +314,12 @@ AdvSceneSwitcher.condition.timer.entry.line2="Time remaining: {{remaining}} seco
AdvSceneSwitcher.condition.timer.entry.line3="{{pauseContinue}} {{reset}} {{saveRemaining}} Save time remaining {{autoReset}} Automatically reset timer after duration was reached"
AdvSceneSwitcher.condition.timer.reset="Reset"
AdvSceneSwitcher.condition.macro="Macro"
AdvSceneSwitcher.condition.macro.type.count="Count"
AdvSceneSwitcher.condition.macro.type.state="State"
AdvSceneSwitcher.condition.macro.type.multiState="Multiple states"
AdvSceneSwitcher.condition.macro.type.selection="Condition type: {{types}}"
AdvSceneSwitcher.condition.macro.type.count="Macro run count"
AdvSceneSwitcher.condition.macro.type.state="Macro condition state"
AdvSceneSwitcher.condition.macro.type.multiState="Multiple macro condition state"
AdvSceneSwitcher.condition.macro.type.actionDisabled="Macro action disabled"
AdvSceneSwitcher.condition.macro.type.actionEnabled="Macro action enabled"
AdvSceneSwitcher.condition.macro.type.selection="{{types}}"
AdvSceneSwitcher.condition.macro.count.type.below="Less than"
AdvSceneSwitcher.condition.macro.count.type.above="More than"
AdvSceneSwitcher.condition.macro.count.type.equal="Exactly"
@@ -308,14 +332,17 @@ AdvSceneSwitcher.condition.macro.state.entry="Conditions of {{macros}} are true"
AdvSceneSwitcher.condition.macro.multistate.entry="Conditions of {{multiStateConditions}}{{multiStateCount}} of the following macros are true:"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} was executed {{conditions}} {{count}} times"
AdvSceneSwitcher.condition.macro.count.entry.line2="Current count: {{currentCount}} {{resetCount}}"
AdvSceneSwitcher.condition.macro.actionState.disabled.entry="Action{{actionIndex}}of{{macros}}is disabled"
AdvSceneSwitcher.condition.macro.actionState.enabled.entry="Action{{actionIndex}}of{{macros}}is enabled"
AdvSceneSwitcher.condition.source="Source"
AdvSceneSwitcher.condition.source.type.active="Is active"
AdvSceneSwitcher.condition.source.type.showing="Is showing"
AdvSceneSwitcher.condition.source.type.settings="Settings match"
AdvSceneSwitcher.condition.source.sceneVisibilityHint="Scene specific visibility can be checked using the \"Scene item visibility\" condition"
AdvSceneSwitcher.condition.source.getSettings="Get current settings"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}} {{conditions}}"
AdvSceneSwitcher.condition.source.entry.line1="{{sources}}{{conditions}}"
AdvSceneSwitcher.condition.source.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.source.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.source.entry.line3="{{regex}}{{getSettings}}"
AdvSceneSwitcher.condition.virtualCamera="Virtual camera"
AdvSceneSwitcher.condition.virtualCamera.state.start="Virtual camera started"
AdvSceneSwitcher.condition.virtualCamera.state.stop="Virtual camera stopped"
@@ -420,8 +447,8 @@ AdvSceneSwitcher.condition.stats.entry="{{stats}} is {{condition}} {{value}}"
AdvSceneSwitcher.condition.profile="Profile"
AdvSceneSwitcher.condition.profile.entry="Current active profile is {{profiles}}"
AdvSceneSwitcher.condition.websocket="Websocket"
AdvSceneSwitcher.condition.websocket.type.request="Request"
AdvSceneSwitcher.condition.websocket.type.event="Event"
AdvSceneSwitcher.condition.websocket.type.request="Scene Switcher Request"
AdvSceneSwitcher.condition.websocket.type.event="Scene Switcher Event"
AdvSceneSwitcher.condition.websocket.useRegex="Use regular expressions"
AdvSceneSwitcher.condition.websocket.entry.request="{{type}} was received:"
AdvSceneSwitcher.condition.websocket.entry.event="{{type}} was received from {{connection}}:"
@@ -446,6 +473,12 @@ AdvSceneSwitcher.condition.display="Display"
AdvSceneSwitcher.condition.display.type.displayName="Name of connected displays matches"
AdvSceneSwitcher.condition.display.type.displayCount="Number of connected displays is"
AdvSceneSwitcher.condition.display.entry="{{conditions}}{{displays}}{{regex}}{{displayCount}}"
AdvSceneSwitcher.condition.slideshow="Slide Show"
AdvSceneSwitcher.condition.slideshow.condition.slideChanged="Slide changed"
AdvSceneSwitcher.condition.slideshow.condition.slideIndex="Current slide number is"
AdvSceneSwitcher.condition.slideshow.condition.slidePath="Current slide path is"
AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip="Information about the slide show status will only be updated based on the configured time between slides"
AdvSceneSwitcher.condition.slideshow.entry="{{sources}}{{conditions}}{{index}}{{path}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="Switch scene"
@@ -494,7 +527,11 @@ AdvSceneSwitcher.action.streaming="Streaming"
AdvSceneSwitcher.action.streaming.type.stop="Stop streaming"
AdvSceneSwitcher.action.streaming.type.start="Start streaming"
AdvSceneSwitcher.action.streaming.type.keyFrameInterval="Set keyframe interval"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}"
AdvSceneSwitcher.action.streaming.type.server="Set server URL"
AdvSceneSwitcher.action.streaming.type.streamKey="Set stream key"
AdvSceneSwitcher.action.streaming.type.username="Set username"
AdvSceneSwitcher.action.streaming.type.password="Set password"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Run"
AdvSceneSwitcher.action.sceneVisibility="Scene item visibility"
AdvSceneSwitcher.action.sceneVisibility.type.show="Show"
@@ -502,10 +539,11 @@ AdvSceneSwitcher.action.sceneVisibility.type.hide="Hide"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="Toggle"
AdvSceneSwitcher.action.sceneVisibility.type.source="Source"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Any"
AdvSceneSwitcher.action.sceneVisibility.entry="On{{scenes}}{{actions}}{{sourceTypes}}{{sources}}{{sourceGroups}}"
AdvSceneSwitcher.action.sceneVisibility.entry="On{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filter"
AdvSceneSwitcher.action.filter.type.enable="Enable"
AdvSceneSwitcher.action.filter.type.disable="Disable"
AdvSceneSwitcher.action.filter.type.toggle="Toggle"
AdvSceneSwitcher.action.filter.type.settings="Set settings"
AdvSceneSwitcher.action.filter.entry="On {{sources}} {{actions}} {{filters}}"
AdvSceneSwitcher.action.filter.getSettings="Get current settings"
@@ -516,10 +554,23 @@ AdvSceneSwitcher.action.source.type.settings="Set settings"
AdvSceneSwitcher.action.source.type.refreshSettings="Refresh source settings"
AdvSceneSwitcher.action.source.type.pressSettingsButton="Press settings button"
AdvSceneSwitcher.action.source.type.refreshSettings.tooltip="Can be used to refresh browser, media, etc. sources"
AdvSceneSwitcher.action.source.type.deinterlaceMode="Set deinterlace mode"
AdvSceneSwitcher.action.source.type.deinterlaceOrder="Set deinterlace field order"
AdvSceneSwitcher.action.source.noSettingsButtons="No buttons found!"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}"
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
AdvSceneSwitcher.action.source.warning="Warning: Enabling and disabling sources globally cannot be controlled by the OBS UI\nYou might be looking for \"Scene item visibility\""
AdvSceneSwitcher.action.source.getSettings="Get current settings"
AdvSceneSwitcher.action.source.deinterlaceMode.disable="Disable"
AdvSceneSwitcher.action.source.deinterlaceMode.discard="Discard"
AdvSceneSwitcher.action.source.deinterlaceMode.retro="Retro"
AdvSceneSwitcher.action.source.deinterlaceMode.blend="Blend"
AdvSceneSwitcher.action.source.deinterlaceMode.blend2x="Blend 2x"
AdvSceneSwitcher.action.source.deinterlaceMode.linear="Linear"
AdvSceneSwitcher.action.source.deinterlaceMode.linear2x="Linear 2x"
AdvSceneSwitcher.action.source.deinterlaceMode.yadif="Yadif"
AdvSceneSwitcher.action.source.deinterlaceMode.yadif2x="Yadif 2x"
AdvSceneSwitcher.action.source.deinterlaceOrder.topFieldFirst="Top Field First"
AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst="Bottom Field First"
AdvSceneSwitcher.action.media="Media"
AdvSceneSwitcher.action.media.type.play="Play"
AdvSceneSwitcher.action.media.type.pause="Pause"
@@ -535,7 +586,10 @@ AdvSceneSwitcher.action.macro.type.unpause="Unpause"
AdvSceneSwitcher.action.macro.type.resetCounter="Reset counter"
AdvSceneSwitcher.action.macro.type.run="Run actions"
AdvSceneSwitcher.action.macro.type.stop="Stop actions"
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
AdvSceneSwitcher.action.macro.type.disableAction="Disable action"
AdvSceneSwitcher.action.macro.type.enableAction="Enable action"
AdvSceneSwitcher.action.macro.type.toggleAction="Toggle action"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Plugin state"
AdvSceneSwitcher.action.pluginState.type.stop="Stop the Advanced Scene Switcher plugin"
AdvSceneSwitcher.action.pluginState.type.noMatch="Change the no-match behaviour:"
@@ -549,6 +603,13 @@ AdvSceneSwitcher.action.virtualCamera.type.stop="Stop virtual camera"
AdvSceneSwitcher.action.virtualCamera.type.start="Start virtual camera"
AdvSceneSwitcher.action.virtualCamera.entry="{{actions}}"
AdvSceneSwitcher.action.hotkey="Hotkey"
AdvSceneSwitcher.action.hotkey.type.obs="OBS hotkey"
AdvSceneSwitcher.action.hotkey.type.obs.type.frontend="Frontend"
AdvSceneSwitcher.action.hotkey.type.obs.type.soruce="Source"
AdvSceneSwitcher.action.hotkey.type.obs.type.output="Output"
AdvSceneSwitcher.action.hotkey.type.obs.type.encoder="Encoder"
AdvSceneSwitcher.action.hotkey.type.obs.type.service="Service"
AdvSceneSwitcher.action.hotkey.type.custom="Custom hotkey"
AdvSceneSwitcher.action.hotkey.leftShift="Left Shift"
AdvSceneSwitcher.action.hotkey.rightShift="Right Shift"
AdvSceneSwitcher.action.hotkey.leftCtrl="Left Ctrl"
@@ -559,7 +620,8 @@ AdvSceneSwitcher.action.hotkey.leftMeta="Left Meta"
AdvSceneSwitcher.action.hotkey.rightMeta="Right Meta"
AdvSceneSwitcher.action.hotkey.onlyOBS="Send keypress only to OBS"
AdvSceneSwitcher.action.hotkey.disabled="Cannot simulate global key presses - functionality limited to sending key press to OBS!"
AdvSceneSwitcher.action.hotkey.entry="Press {{keys}} for {{duration}}"
AdvSceneSwitcher.action.hotkey.entry.custom="Press{{actionType}}{{keys}}for{{duration}}"
AdvSceneSwitcher.action.hotkey.entry.obs="Press{{actionType}}{{hotkeyType}}{{obsHotkeys}}"
AdvSceneSwitcher.action.sceneOrder="Scene item order"
AdvSceneSwitcher.action.sceneOrder.type.moveUp="Move up"
AdvSceneSwitcher.action.sceneOrder.type.moveDown="Move down"
@@ -568,8 +630,18 @@ AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Move to bottom"
AdvSceneSwitcher.action.sceneOrder.type.movePosition="Move to position"
AdvSceneSwitcher.action.sceneOrder.entry="On{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Scene item transform"
AdvSceneSwitcher.action.sceneTransform.type.manual="Transform"
AdvSceneSwitcher.action.sceneTransform.type.reset="Reset transform"
AdvSceneSwitcher.action.sceneTransform.type.rotate="Rotate by"
AdvSceneSwitcher.action.sceneTransform.type.flipHorizontal="Flip horizontally"
AdvSceneSwitcher.action.sceneTransform.type.flipVertical="Flip vertically"
AdvSceneSwitcher.action.sceneTransform.type.fitToScreen="Fit to screen"
AdvSceneSwitcher.action.sceneTransform.type.stretchToScreen="Stretch to screen"
AdvSceneSwitcher.action.sceneTransform.type.centerToScreen="Center to screen"
AdvSceneSwitcher.action.sceneTransform.type.centerVertically="Center vertically"
AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally="Center horizontally"
AdvSceneSwitcher.action.sceneTransform.getTransform="Get transform"
AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}transform{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.file="File"
AdvSceneSwitcher.action.file.type.write="Write"
AdvSceneSwitcher.action.file.type.append="Append"
@@ -622,11 +694,17 @@ AdvSceneSwitcher.action.sequence.status.none="none"
AdvSceneSwitcher.action.sequence.restart="Restart from beginning once end of list is reached"
AdvSceneSwitcher.action.sequence.continueFrom="Continue with selected item"
AdvSceneSwitcher.action.websocket="Websocket"
AdvSceneSwitcher.action.websocket.api.sceneSwitcher="Scene Switcher message"
AdvSceneSwitcher.action.websocket.api.obsWebsocket="OBS websocket message"
AdvSceneSwitcher.action.websocket.api.genericWebsocket="Generic websocket message"
AdvSceneSwitcher.action.websocket.type.request="Request"
AdvSceneSwitcher.action.websocket.type.event="Event"
AdvSceneSwitcher.action.websocket.entry.request="Send scene switcher {{type}} via {{connection}}"
AdvSceneSwitcher.action.websocket.entry.event="Send scene switcher {{type}} to connected clients"
AdvSceneSwitcher.action.http="Http"
AdvSceneSwitcher.action.websocket.settingsConflictGeneric="Possible settings conflict: Generic websocket message selection with connection which follows the OBS protocol!"
AdvSceneSwitcher.action.websocket.settingsConflictOBS="Possible settings conflict: Connection selected which only supports generic websockte messages while message type requires OBS protocol!"
AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.request="Send{{api}}of type{{type}}via{{connection}}"
AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event="Send{{api}}of type{{type}}to connected clients"
AdvSceneSwitcher.action.websocket.entry.generic="Send{{api}}via{{connection}}"
AdvSceneSwitcher.action.http="HTTP"
AdvSceneSwitcher.action.http.setHeaders="Set headers"
AdvSceneSwitcher.action.http.headers="Headers:"
AdvSceneSwitcher.action.http.addHeader="Add header"
@@ -646,6 +724,9 @@ AdvSceneSwitcher.action.variable.type.roundToInt="Round to nearest integer"
AdvSceneSwitcher.action.variable.type.subString="Set to substring of current value"
AdvSceneSwitcher.action.variable.type.findAndReplace="Find and replace in current value"
AdvSceneSwitcher.action.variable.type.mathExpression="Mathematical expression"
AdvSceneSwitcher.action.variable.type.askForValue="Get user input"
AdvSceneSwitcher.action.variable.askForValuePromptDefault="Assign value to variable \"%1\":"
AdvSceneSwitcher.action.variable.askForValuePrompt="Assign value to variable:"
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
AdvSceneSwitcher.action.variable.findAndReplace.find="Text to find"
AdvSceneSwitcher.action.variable.findAndReplace.replace="Text to replace with"
@@ -659,6 +740,7 @@ AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{
AdvSceneSwitcher.action.variable.entry.substringIndex="Substring start:{{subStringStart}} Substring size:{{subStringSize}}"
AdvSceneSwitcher.action.variable.entry.substringRegex="Assign value of{{regexMatchIdx}}match using regular expression:"
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
AdvSceneSwitcher.action.variable.entry.userInput="{{useCustomPrompt}}Use custom prompt{{inputPrompt}}"
AdvSceneSwitcher.action.projector="Projector"
AdvSceneSwitcher.action.projector.type.source="Source"
AdvSceneSwitcher.action.projector.type.scene="Scene"
@@ -674,6 +756,11 @@ AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="Send message to {{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="Set MIDI message selection to messages incoming on {{listenDevices}}: {{listenButton}}"
AdvSceneSwitcher.action.osc="Open Sound Control"
AdvSceneSwitcher.action.sceneLock="Scene item lock"
AdvSceneSwitcher.action.sceneLock.type.lock="lock"
AdvSceneSwitcher.action.sceneLock.type.unlock="unlock"
AdvSceneSwitcher.action.sceneLock.type.toggle="toggle lock of"
AdvSceneSwitcher.action.sceneLock.entry="On{{scenes}}{{actions}}{{sources}}"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
@@ -732,7 +819,7 @@ AdvSceneSwitcher.mediaTab.states.none="None"
AdvSceneSwitcher.mediaTab.states.playing="Playing"
AdvSceneSwitcher.mediaTab.states.opening="Opening"
AdvSceneSwitcher.mediaTab.states.buffering="Buffering"
AdvSceneSwitcher.mediaTab.states.Paused="Paused"
AdvSceneSwitcher.mediaTab.states.paused="Paused"
AdvSceneSwitcher.mediaTab.states.stopped="Stopped"
AdvSceneSwitcher.mediaTab.states.ended="Ended"
AdvSceneSwitcher.mediaTab.states.error="Error"
@@ -935,8 +1022,11 @@ AdvSceneSwitcher.item.emptyName="Empty name not allowed!"
AdvSceneSwitcher.item.nameNotAvailable="Name not available!"
AdvSceneSwitcher.item.nameReserved="Name reserved!"
AdvSceneSwitcher.macroSegmentSelection.invalid="Invalid selection!"
AdvSceneSwitcher.variable.select="--select variable--"
AdvSceneSwitcher.variable.add="Add new variable"
AdvSceneSwitcher.variable.configure="Configure variable settings"
AdvSceneSwitcher.variable.name="Name:"
AdvSceneSwitcher.variable.value="Current value:"
AdvSceneSwitcher.variable.save="Save / load behavior"
@@ -946,7 +1036,10 @@ AdvSceneSwitcher.variable.save.default="Set to value"
AdvSceneSwitcher.connection.select="--select connection--"
AdvSceneSwitcher.connection.add="Add new connection"
AdvSceneSwitcher.connection.configure="Configure connection settings"
AdvSceneSwitcher.connection.name="Name:"
AdvSceneSwitcher.connection.useCustomURI="Use custom URI"
AdvSceneSwitcher.connection.customURI="Address:"
AdvSceneSwitcher.connection.address="Address:"
AdvSceneSwitcher.connection.port="Port:"
AdvSceneSwitcher.connection.password="Password:"
@@ -961,6 +1054,7 @@ AdvSceneSwitcher.connection.status.connected="Connected, but not authenticated"
AdvSceneSwitcher.connection.status.authenticated="Connected and authenticated"
AdvSceneSwitcher.regex.enable="Enable regular expressions"
AdvSceneSwitcher.regex.configure="Configure regular expression settings"
AdvSceneSwitcher.regex.partialMatch="Allow partial match"
AdvSceneSwitcher.regex.caseInsensitive="Match case insensitive"
AdvSceneSwitcher.regex.dotMatchNewline=". matches newlines"
@@ -1052,8 +1146,25 @@ AdvSceneSwitcher.selectSceneCollection="--select scene collection--"
AdvSceneSwitcher.enterPath="--enter path--"
AdvSceneSwitcher.enterText="--enter text--"
AdvSceneSwitcher.enterURL="--enter URL--"
AdvSceneSwitcher.selectHotkey="--select hotkey--"
AdvSceneSwitcher.invaildEntriesWillNotBeSaved="invalid entries will not be saved"
AdvSceneSwitcher.selectWindowTip="Use \"OBS\" to specify OBS window\nUse \"Task Switching\"to specify ALT + TAB"
AdvSceneSwitcher.sceneItemSelection.configure="Configure scene item selection type"
AdvSceneSwitcher.sceneItemSelection.type.sourceName="Source name"
AdvSceneSwitcher.sceneItemSelection.type.sourceName.entry="{{nameConflictIndex}}{{sourceName}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable="Variable name"
AdvSceneSwitcher.sceneItemSelection.type.sourceVariable.entry="{{nameConflictIndex}}{{variable}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern="Source name matches pattern"
AdvSceneSwitcher.sceneItemSelection.type.sourceNamePattern.entry="{{nameConflictIndex}}matching{{pattern}}{{regex}}"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup="Sources of type"
AdvSceneSwitcher.sceneItemSelection.type.sourceGroup.entry="{{nameConflictIndex}}Source type{{sourceGroups}}"
AdvSceneSwitcher.sceneItemSelection.type.index="Source with index"
AdvSceneSwitcher.sceneItemSelection.type.index.entry="{{index}}source"
AdvSceneSwitcher.sceneItemSelection.type.indexRange="Sources in index range"
AdvSceneSwitcher.sceneItemSelection.type.indexRange.entry="Sources from{{index}}to{{indexEnd}}"
AdvSceneSwitcher.sceneItemSelection.type.all="All sources"
AdvSceneSwitcher.sceneItemSelection.type.all.entry="All sources"
AdvSceneSwitcher.sceneItemSelection.all="All"
AdvSceneSwitcher.sceneItemSelection.any="Any"

View File

@@ -360,7 +360,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Transmisión"
AdvSceneSwitcher.action.streaming.type.stop="Detener transmisión"
AdvSceneSwitcher.action.streaming.type.start="Iniciar transmisión"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Ejecutar"
AdvSceneSwitcher.action.run.arguments="Argumentos:"
AdvSceneSwitcher.action.run.addArgument="Agregar argumento"
@@ -371,7 +371,7 @@ AdvSceneSwitcher.action.sceneVisibility.type.show="Mostrar"
AdvSceneSwitcher.action.sceneVisibility.type.hide="Ocultar"
AdvSceneSwitcher.action.sceneVisibility.type.source="Fuente"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="Cualquiera"
AdvSceneSwitcher.action.sceneVisibility.entry="En{{scenes}}{{actions}}{{sourceTypes}}{{sources}}{{sourceGroups}}"
AdvSceneSwitcher.action.sceneVisibility.entry="En{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtro"
AdvSceneSwitcher.action.filter.type.enable="Habilitar"
AdvSceneSwitcher.action.filter.type.disable="Deshabilitar"
@@ -382,7 +382,7 @@ AdvSceneSwitcher.action.source="Fuente"
AdvSceneSwitcher.action.source.type.enable="Habilitar"
AdvSceneSwitcher.action.source.type.disable="Deshabilitar"
AdvSceneSwitcher.action.source.type.settings="Establecer configuración"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
AdvSceneSwitcher.action.source.warning="Advertencia: la IU de OBS no puede controlar la habilitación y deshabilitación global de fuentes"
AdvSceneSwitcher.action.source.getSettings="Obtener la configuración actual"
AdvSceneSwitcher.action.media="Medios"
@@ -400,7 +400,7 @@ AdvSceneSwitcher.action.macro.type.unpause="Reanudar"
AdvSceneSwitcher.action.macro.type.resetCounter="Reiniciar contador"
AdvSceneSwitcher.action.macro.type.run="Ejecutar"
AdvSceneSwitcher.action.macro.type.stop="Detener"
AdvSceneSwitcher.action.macro.entry="{{actions}} {{macros}}"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="Estado del complemento"
AdvSceneSwitcher.action.pluginState.type.stop="Detener el complemento Advanced Scene Switcher"
AdvSceneSwitcher.action.pluginState.type.noMatch="Cambiar el comportamiento de no coincidencia:"
@@ -432,7 +432,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="Mover a la posición"
AdvSceneSwitcher.action.sceneOrder.entry="En{{scenes}}{{actions}}{{sources}}{{position}}"
AdvSceneSwitcher.action.sceneTransform="Transformar elemento de escena"
AdvSceneSwitcher.action.sceneTransform.getTransform="Obtener transformación"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}transformar{{sources}}"
AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.file="Archivo"
AdvSceneSwitcher.action.file.type.write="Escribir"
AdvSceneSwitcher.action.file.type.append="Agregar"
@@ -531,7 +531,7 @@ AdvSceneSwitcher.mediaTab.states.none="Ninguno"
AdvSceneSwitcher.mediaTab.states.playing="Reproduciéndose"
AdvSceneSwitcher.mediaTab.states.opening="Abriendo"
AdvSceneSwitcher.mediaTab.states.buffering="Almacenando en búfer"
AdvSceneSwitcher.mediaTab.states.Paused="Pausado"
AdvSceneSwitcher.mediaTab.states.paused="Pausado"
AdvSceneSwitcher.mediaTab.states.stopped="Detenido"
AdvSceneSwitcher.mediaTab.states.ended="Finalizado"
AdvSceneSwitcher.mediaTab.states.error="Error"

View File

@@ -144,7 +144,7 @@ AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="Потоковое вещание"
AdvSceneSwitcher.action.streaming.type.stop="Остановить потоковое вещание"
AdvSceneSwitcher.action.streaming.type.start="Начать потоковое вещание"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Запустить"
@@ -205,7 +205,7 @@ AdvSceneSwitcher.mediaTab.states.none="Нет"
AdvSceneSwitcher.mediaTab.states.playing="Играет"
AdvSceneSwitcher.mediaTab.states.opening="Открытие"
AdvSceneSwitcher.mediaTab.states.buffering="Буферизация"
AdvSceneSwitcher.mediaTab.states.Paused="Приостановлено"
AdvSceneSwitcher.mediaTab.states.paused="Приостановлено"
AdvSceneSwitcher.mediaTab.states.stopped="Остановлено"
AdvSceneSwitcher.mediaTab.states.ended="Закончилось"
AdvSceneSwitcher.mediaTab.states.error="Ошибка"

View File

@@ -290,7 +290,7 @@ 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}}{{keyFrameInterval}}"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="Çalıştır"
AdvSceneSwitcher.action.run.arguments="Argümanlar:"
AdvSceneSwitcher.action.run.addArgument="Argüman ekle"
@@ -301,7 +301,7 @@ 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.sceneVisibility.entry="Açık{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="Filtrele"
AdvSceneSwitcher.action.filter.type.enable="Etkin"
AdvSceneSwitcher.action.filter.type.disable="Etkisiz"
@@ -312,7 +312,7 @@ 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}}{{settingsButtons}}"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
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"
@@ -328,7 +328,7 @@ 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.macro.entry="{{actions}}{{actionIndex}}{{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:"
@@ -360,7 +360,7 @@ 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.sceneTransform.entry="Açık{{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.file="Dosya"
AdvSceneSwitcher.action.file.type.write="Yaz"
AdvSceneSwitcher.action.file.type.append="Ekle"
@@ -442,7 +442,7 @@ 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.paused="Duraklatıldı"
AdvSceneSwitcher.mediaTab.states.stopped="Durduruldu"
AdvSceneSwitcher.mediaTab.states.ended="Bitti"
AdvSceneSwitcher.mediaTab.states.error="Hata"

View File

@@ -4,7 +4,7 @@ AdvSceneSwitcher.windowTitle="高级场景切换器"
; General Tab
AdvSceneSwitcher.generalTab.title="通用"
AdvSceneSwitcher.generalTab.status="状态"
AdvSceneSwitcher.generalTab.status.hotkeytips="快捷键可在OBS设置中设置"
AdvSceneSwitcher.generalTab.status.hotkeytips="快捷键可在OBS设置中设置<br><br>汉化作者:[ 从不咕咕的阿坤 ] - 全平台统一名 - 如对你有所帮助,劳烦点个关注!"
AdvSceneSwitcher.generalTab.status.currentStatus="当前状态:"
AdvSceneSwitcher.generalTab.status.onStartup="在OBS启动时"
AdvSceneSwitcher.generalTab.status.onStartup.asLastRun="按照最后运行时的状态"
@@ -24,11 +24,14 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMetDelayTooltip="时间精度最
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.dontSwitch="不切换"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchToRandom="切换到随机场景列表中任意随机场景"
AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="切换到"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="执行完操作后,跳过执行操作,为"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="在此期间,可能的匹配项将被忽略!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="详细日志输出"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="保存窗口位置和大小"
AdvSceneSwitcher.generalTab.generalBehavior.showTrayNotifications="显示系统托盘通知"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="禁用UI提示"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailure="如果无法加载插件显示警告"
AdvSceneSwitcher.generalTab.generalBehavior.warnPluginLoadFailureMessage="<html><body>加载以下插件库失败,这可能导致某些高级场景切换器功能不可用:%1 查看 OBS 日志了解详细信息.<br>可以在“常规”选项卡上禁用此消息.</body></html>"
AdvSceneSwitcher.generalTab.generalBehavior.hideLegacyTabs="隐藏可通过宏操作的分组栏"
AdvSceneSwitcher.generalTab.matchBehavior="场景匹配设置"
AdvSceneSwitcher.generalTab.priority="优先级"
@@ -78,17 +81,39 @@ AdvSceneSwitcher.macroTab.runFail="运行 \"%1\" 失败!\n其中一个操作失
AdvSceneSwitcher.macroTab.runInParallel="与其他宏同时运行宏"
AdvSceneSwitcher.macroTab.onChange="仅在条件更改时执行操作"
AdvSceneSwitcher.macroTab.defaultname="宏 %1"
AdvSceneSwitcher.macroTab.defaultGroupName="组 %1"
AdvSceneSwitcher.macroTab.exists="宏名称已存在"
AdvSceneSwitcher.macroTab.groupDeleteConfirm="您确定要删除 \"%1\"及其所有元素吗?"
AdvSceneSwitcher.macroTab.deleteMultipleMacrosConfirmation="您确定要删除 %1 宏吗?"
AdvSceneSwitcher.macroTab.deleteSingleMacroConfirmation="你确定你要删除 \"%1\"?"
AdvSceneSwitcher.macroTab.contextMenuAdd="增加"
AdvSceneSwitcher.macroTab.copy="创建副本"
AdvSceneSwitcher.macroTab.group="对选定的宏进行分组"
AdvSceneSwitcher.macroTab.ungroup="解除选定的组的分组"
AdvSceneSwitcher.macroTab.rename="重命名"
AdvSceneSwitcher.macroTab.remove="删除"
AdvSceneSwitcher.macroTab.expandAll="全部展开"
AdvSceneSwitcher.macroTab.collapseAll="全部收回"
AdvSceneSwitcher.macroTab.maximize="最大化"
AdvSceneSwitcher.macroTab.minimize="最小化"
AdvSceneSwitcher.macroTab.highlightSettings="显示 设置"
AdvSceneSwitcher.macroTab.hotkeySettings="热键 设置"
AdvSceneSwitcher.macroTab.dockSettings="停靠 设置"
AdvSceneSwitcher.macroTab.highlightExecutedMacros="突出显示最近执行的宏"
AdvSceneSwitcher.macroTab.highlightTrueConditions="突出显示最近运算为 真 的当前选定宏的条件"
AdvSceneSwitcher.macroTab.highlightPerformedActions="突出显示当前选定宏的最近执行的操作"
AdvSceneSwitcher.macroTab.newMacroRegisterHotkey="使用快捷键控制新增宏的暂停状态"
AdvSceneSwitcher.macroTab.currentDisableHotkeys="使用快捷键控制所选宏的暂停状态"
AdvSceneSwitcher.macroTab.currentRegisterDock="注册停靠小部件以控制所选宏的暂停状态或手动运行它"
AdvSceneSwitcher.macroTab.currentDockAddRunButton="添加按钮以运行宏"
AdvSceneSwitcher.macroTab.currentDockAddPauseButton="添加按钮以暂停或取消暂停宏"
AdvSceneSwitcher.macroTab.currentDockButtonText.run="运行按钮文本:"
AdvSceneSwitcher.macroTab.currentDockButtonText.pause="暂停按钮文本:"
AdvSceneSwitcher.macroTab.currentDockButtonText.unpause="取消暂停按钮文本:"
AdvSceneSwitcher.macroDock.pause="暂停"
AdvSceneSwitcher.macroDock.unpause="取消暂停"
AdvSceneSwitcher.macroDock.run="运行"
; Macro List
AdvSceneSwitcher.macroList.deleted="删除"
@@ -112,10 +137,17 @@ AdvSceneSwitcher.condition.audio.state.mute="静音"
AdvSceneSwitcher.condition.audio.state.unmute="取消静音"
AdvSceneSwitcher.condition.audio.type.output="输出音频"
AdvSceneSwitcher.condition.audio.type.volume="配置音量级别"
AdvSceneSwitcher.condition.audio.type.syncOffset="同步偏移"
AdvSceneSwitcher.condition.audio.type.monitor="音频监控"
AdvSceneSwitcher.condition.audio.type.balance="音频平衡"
AdvSceneSwitcher.condition.audio.entry="{{checkType}} 的 {{audioSources}} 是 {{condition}}{{volume}}{{syncOffset}}{{monitorTypes}}"
AdvSceneSwitcher.condition.cursor="屏幕区域"
AdvSceneSwitcher.condition.cursor.type.region="当前位置"
AdvSceneSwitcher.condition.cursor.type.moving="正在移动"
AdvSceneSwitcher.condition.cursor.type.click="点击"
AdvSceneSwitcher.condition.cursor.button.left="鼠标左键"
AdvSceneSwitcher.condition.cursor.button.middle="鼠标中键"
AdvSceneSwitcher.condition.cursor.button.right="鼠标右键"
AdvSceneSwitcher.condition.cursor.showFrame="显示帧率"
AdvSceneSwitcher.condition.cursor.hideFrame="隐藏帧率"
AdvSceneSwitcher.condition.cursor.entry.line1="鼠标 处于{{conditions}}{{buttons}}{{minX}}{{minY}}{{maxX}}{{maxY}}{{toggleFrameButton}}"
@@ -125,36 +157,47 @@ AdvSceneSwitcher.condition.scene.type.current="当前场景是"
AdvSceneSwitcher.condition.scene.type.previous="上一个场景是"
AdvSceneSwitcher.condition.scene.type.changed="场景改变了"
AdvSceneSwitcher.condition.scene.type.notChanged="场景没有改变"
AdvSceneSwitcher.condition.scene.type.currentPattern="当前场景匹配"
AdvSceneSwitcher.condition.scene.type.previousPattern="上一个场景匹配"
AdvSceneSwitcher.condition.scene.currentSceneTransitionBehaviour="在转场过程中检查转场源场景"
AdvSceneSwitcher.condition.scene.previousSceneTransitionBehaviour="在转场过程中检查转场源场景"
AdvSceneSwitcher.condition.scene.entry.line1="{{sceneType}}{{scenes}}{{pattern}}"
AdvSceneSwitcher.condition.scene.entry.line2="{{useTransitionTargetScene}}"
AdvSceneSwitcher.condition.window="窗口"
AdvSceneSwitcher.condition.window.entry.line1="{{windows}} 存在..."
AdvSceneSwitcher.condition.window.entry.line2="... 并且是 {{fullscreen}} 全屏 {{maximized}} 最大化 {{focused}} 获得焦点 {{windowFocusChanged}} 焦点窗口已更改"
AdvSceneSwitcher.condition.window.entry.line3="当前焦点窗口: {{focusWindow}}"
AdvSceneSwitcher.condition.window.entry.window="{{checkTitle}}窗口标题匹配{{windowRegex}}{{windows}}"
AdvSceneSwitcher.condition.window.entry.fullscreen="{{fullscreen}}窗口为全屏"
AdvSceneSwitcher.condition.window.entry.maximized="{{maximized}}窗口为最大化"
AdvSceneSwitcher.condition.window.entry.focused="{{focused}}窗口为焦点"
AdvSceneSwitcher.condition.window.entry.focusedChange="{{windowFocusChanged}}焦点窗口被改变"
AdvSceneSwitcher.condition.window.entry.text="{{checkText}}窗口包含文本{{textRegex}}{{windowText}}"
AdvSceneSwitcher.condition.window.entry.text.note="此选项可能不会对窗口中显示的每个文本起作用。\n如果是这种情况您可以考虑使用视频条件OCR检查来代替。"
AdvSceneSwitcher.condition.window.entry.currentFocus="当前焦点窗口:{{focusWindow}}"
AdvSceneSwitcher.condition.file="文件"
AdvSceneSwitcher.condition.file.type.match="内容完全匹配"
AdvSceneSwitcher.condition.file.type.contentChange="内容已更改"
AdvSceneSwitcher.condition.file.type.dateChange="修改日期已更改"
AdvSceneSwitcher.condition.file.entry.line1="文件内容来自{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.remote="远程文件"
AdvSceneSwitcher.condition.file.local="本地文件"
AdvSceneSwitcher.condition.file.entry.line1="{{fileType}}{{filePath}}{{conditions}}"
AdvSceneSwitcher.condition.file.entry.line2="{{matchText}}"
AdvSceneSwitcher.condition.file.entry.line3="{{useRegex}} {{checkModificationDate}} {{checkFileContent}}"
AdvSceneSwitcher.condition.media="媒体"
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体来源"
AdvSceneSwitcher.condition.media.allOnScene="所有媒体源"
AdvSceneSwitcher.condition.media.source="源"
AdvSceneSwitcher.condition.media.anyOnScene="任何媒体源"
AdvSceneSwitcher.condition.media.allOnScene="所有媒体源"
AdvSceneSwitcher.condition.media.matchOnChange="仅在更改时匹配(注意:此选项将在未来版本中删除-请改用时间限制)"
AdvSceneSwitcher.condition.media.inconsistencyInfo="不幸的是并非所有媒体源类型的行为都相同例如媒体源与VLC视频源“停止”状态。\n所以请尝试适合您的设置"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}}的状态是{{states}}{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.media.entry="{{sourceTypes}}{{mediaSources}}{{scenes}} 的状态是 {{states}}{{timeRestrictions}}{{time}}"
AdvSceneSwitcher.condition.video="视频"
AdvSceneSwitcher.condition.video.condition.match="完全匹配"
AdvSceneSwitcher.condition.video.condition.differ="不匹配"
AdvSceneSwitcher.condition.video.condition.hasChanged="改变"
AdvSceneSwitcher.condition.video.condition.hasNotChanged="没有改变"
AdvSceneSwitcher.condition.video.condition.hasChanged="改变"
AdvSceneSwitcher.condition.video.condition.hasNotChanged="改变"
AdvSceneSwitcher.condition.video.condition.noImage="没有输出"
AdvSceneSwitcher.condition.video.condition.pattern="匹配模式"
AdvSceneSwitcher.condition.video.condition.object="包含对象"
AdvSceneSwitcher.condition.video.condition.brightness="亮度"
AdvSceneSwitcher.condition.video.condition.ocr="包含文本"
AdvSceneSwitcher.condition.video.askFileAction="是否要使用现有文件或创建当前选定源的屏幕截图?"
AdvSceneSwitcher.condition.video.askFileAction.file="使用现有文件"
AdvSceneSwitcher.condition.video.askFileAction.screenshot="创建屏幕截图"
@@ -164,28 +207,49 @@ AdvSceneSwitcher.condition.video.usePatternForChangedCheck="使用模式匹配"
AdvSceneSwitcher.condition.video.usePatternForChangedCheck.tooltip="这将允许您控制图像必须更改多少才能使条件为真."
AdvSceneSwitcher.condition.video.patternThreshold="阈值: "
AdvSceneSwitcher.condition.video.patternThresholdDescription="较高的阈值意味着模式需要更紧密地匹配视频源."
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="使用alpha通道作为图案的遮罩."
AdvSceneSwitcher.condition.video.patternThresholdUseAlphaAsMask="使用alpha通道作为模式的遮罩."
AdvSceneSwitcher.condition.video.patternMatchMode="使用模式匹配方式 {{patternMatchingModes}}"
AdvSceneSwitcher.condition.video.patternMatchMode.crossCorrelation="交叉相关"
AdvSceneSwitcher.condition.video.patternMatchMode.correlationCoefficient="相关系数"
AdvSceneSwitcher.condition.video.patternMatchMode.squaredDifference="平方差"
AdvSceneSwitcher.condition.video.patternMatchMode.tip="使用的最佳方法取决于具体问题和所涉及图像的特性。\n在实践中通常需要使用不同的方法和参数进行实验以找到对特定用例的最佳匹配。"
AdvSceneSwitcher.condition.video.brightnessThreshold="平均亮度:"
AdvSceneSwitcher.condition.video.brightnessThresholdDescription="高的值表示图像较亮,较低的值表示图像较暗。"
AdvSceneSwitcher.condition.video.brightnessThresholdDescription="高的值表示明亮的图像,低的值表示较暗的图像。"
AdvSceneSwitcher.condition.video.currentBrightness="当前平均亮度: %1"
AdvSceneSwitcher.condition.video.objectScaleThreshold="模型比例: "
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="较低的模比例将导致更多的匹配,但导致更高的CPU负载."
AdvSceneSwitcher.condition.video.objectScaleThresholdDescription="较低的模比例会产生更多的匹配,但导致CPU负载更高."
AdvSceneSwitcher.condition.video.minNeighborDescription="较高的“最小区域”值将导致较少但质量较高的匹配."
AdvSceneSwitcher.condition.video.showMatch="显示模式"
AdvSceneSwitcher.condition.video.showMatch.loading="Checking for match"
AdvSceneSwitcher.condition.video.showMatch.loading="检查是否匹配"
AdvSceneSwitcher.condition.video.screenshotFail="无法获取源的屏幕截图!"
AdvSceneSwitcher.condition.video.screenshotEmpty="Screenshot is empty - Is the source visible?"
AdvSceneSwitcher.condition.video.screenshotEmpty="屏幕截图为空 - 源是否可见?"
AdvSceneSwitcher.condition.video.patternMatchFail="没有找到模式!"
AdvSceneSwitcher.condition.video.patternMatchSuccess="Pattern is highlighted in red"
AdvSceneSwitcher.condition.video.patternMatchSuccess="模式以红色突出显示"
AdvSceneSwitcher.condition.video.objectMatchFail="找不到对象!"
AdvSceneSwitcher.condition.video.objectMatchSuccess="Object is highlighted in red"
AdvSceneSwitcher.condition.video.objectMatchSuccess="对象以红色突出显示"
AdvSceneSwitcher.condition.video.ocrMatchSuccess="检测到的文本:\n\n%1"
AdvSceneSwitcher.condition.video.modelLoadFail="无法加载模型数据!"
AdvSceneSwitcher.condition.video.selectColor="选择颜色"
AdvSceneSwitcher.condition.video.ocrMode.singleColumn="单列可变大小的文本"
AdvSceneSwitcher.condition.video.ocrMode.singleBlockVertText="垂直对齐的单个统一文本块"
AdvSceneSwitcher.condition.video.ocrMode.singleBlock="单个统一的文本块"
AdvSceneSwitcher.condition.video.ocrMode.singleLine="单个文本行"
AdvSceneSwitcher.condition.video.ocrMode.singleWord="单词"
AdvSceneSwitcher.condition.video.ocrMode.circleWord="圆圈中的单个单词"
AdvSceneSwitcher.condition.video.ocrMode.singleChar="单个字符"
AdvSceneSwitcher.condition.video.ocrMode.sparseText="不按特定顺序查找文本"
AdvSceneSwitcher.condition.video.ocrMode.sparseTextOSD="不按特定顺序查找文本OSD"
AdvSceneSwitcher.condition.video.type.main="OBS的主输出"
AdvSceneSwitcher.condition.video.type.source="源"
AdvSceneSwitcher.condition.video.type.scene="场景"
AdvSceneSwitcher.condition.video.entry="{{videoInputTypes}}{{sources}}{{scenes}}{{condition}}{{imagePath}}"
AdvSceneSwitcher.condition.video.entry.modelPath="模型数据 (haar级联分类器): {{modelDataPath}}"
AdvSceneSwitcher.condition.video.entry.minNeighbor="最小区域: {{minNeighbors}}"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}通过只执行每一次检查来减少CPU负载 {{throttleCount}} 毫秒"
AdvSceneSwitcher.condition.video.entry.throttle="{{throttleEnable}}每隔一段时间检查一次,来减少CPU负载 {{throttleCount}} 毫秒"
AdvSceneSwitcher.condition.video.entry.checkAreaEnable="仅在区域内执行检查"
AdvSceneSwitcher.condition.video.entry.checkArea="{{checkAreaEnable}}{{checkArea}}{{selectArea}}"
AdvSceneSwitcher.condition.video.entry.orcColorPick="检查文本颜色:{{textColor}}{{selectColor}}"
AdvSceneSwitcher.condition.video.entry.orcTextType="检查文本类型:{{textType}}"
AdvSceneSwitcher.condition.video.minSize="最小尺寸:"
AdvSceneSwitcher.condition.video.maxSize="最大尺寸:"
AdvSceneSwitcher.condition.video.selectArea="选择区域"
@@ -197,6 +261,7 @@ AdvSceneSwitcher.condition.stream.state.start="推流启动"
AdvSceneSwitcher.condition.stream.state.stop="推流停止"
AdvSceneSwitcher.condition.stream.state.starting="推流启动中"
AdvSceneSwitcher.condition.stream.state.stopping="推流停止中"
AdvSceneSwitcher.condition.stream.state.keyFrameInterval="关键帧间隔等于"
AdvSceneSwitcher.condition.stream.entry="{{streamState}}{{keyFrameInterval}}"
AdvSceneSwitcher.condition.record="录制"
AdvSceneSwitcher.condition.record.state.start="录制运行中"
@@ -204,14 +269,17 @@ AdvSceneSwitcher.condition.record.state.pause="录制暂停"
AdvSceneSwitcher.condition.record.state.stop="录制停止"
AdvSceneSwitcher.condition.record.entry="{{recordState}}"
AdvSceneSwitcher.condition.process="进程"
AdvSceneSwitcher.condition.process.entry="{{processes}} 是正在运行 {{focused}} 且是获得焦点"
AdvSceneSwitcher.condition.process.entry="{{processes}} 是正在运行 {{focused}} 且是获得焦点"
AdvSceneSwitcher.condition.process.entry.focus="当前焦点进程: {{focusProcess}}"
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.start="插件已启动"
AdvSceneSwitcher.condition.pluginState.state.restart="插件已重新启动"
AdvSceneSwitcher.condition.pluginState.state.running="插件正在运行"
AdvSceneSwitcher.condition.pluginState.state.shutdown="OBS正在关闭"
AdvSceneSwitcher.condition.pluginState.state.sceneCollection="场景集合已加载"
AdvSceneSwitcher.condition.pluginState.state.sceneSwitched="场景被上一个宏改变"
AdvSceneSwitcher.condition.pluginState.entry="{{condition}}"
AdvSceneSwitcher.condition.timer="计时器"
AdvSceneSwitcher.condition.timer.type.fixed="固定"
@@ -236,11 +304,11 @@ AdvSceneSwitcher.condition.macro.state.type.above="大于"
AdvSceneSwitcher.condition.macro.state.type.equal="等于"
AdvSceneSwitcher.condition.macro.count.reset="重置"
AdvSceneSwitcher.condition.macro.pausedWarning="所选宏当前已暂停!"
AdvSceneSwitcher.condition.macro.state.entry="条件 {{macros}} 运行结果为真"
AdvSceneSwitcher.condition.macro.multistate.entry="条件 {{multiStateConditions}}{{multiStateCount}} 以下宏运行结果为真:"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} 行 {{conditions}} {{count}} 次(相对于下面计数匹配)"
AdvSceneSwitcher.condition.macro.state.entry="{{macros}} 的条件为真"
AdvSceneSwitcher.condition.macro.multistate.entry=" 以下宏的 {{multiStateConditions}}{{multiStateCount}} 的条件为真:"
AdvSceneSwitcher.condition.macro.count.entry.line1="{{macros}} 行 {{conditions}} {{count}} 次(相对于下面计数匹配)"
AdvSceneSwitcher.condition.macro.count.entry.line2="当前计数: {{currentCount}} {{resetCount}}"
AdvSceneSwitcher.condition.source="源"
AdvSceneSwitcher.condition.source="源"
AdvSceneSwitcher.condition.source.type.active="是激活状态"
AdvSceneSwitcher.condition.source.type.showing="是显示状态"
AdvSceneSwitcher.condition.source.type.settings="设置完全匹配"
@@ -257,7 +325,7 @@ AdvSceneSwitcher.condition.filter.type.active="已启用"
AdvSceneSwitcher.condition.filter.type.showing="已关闭"
AdvSceneSwitcher.condition.filter.type.settings="设置完全匹配"
AdvSceneSwitcher.condition.filter.getSettings="获取当前设置"
AdvSceneSwitcher.condition.filter.entry.line1="在 {{sources}} {{filters}} {{conditions}}"
AdvSceneSwitcher.condition.filter.entry.line1="在 {{sources}} {{filters}} {{conditions}}"
AdvSceneSwitcher.condition.filter.entry.line2="{{settings}}"
AdvSceneSwitcher.condition.filter.entry.line3="{{regex}} {{getSettings}}"
AdvSceneSwitcher.condition.sceneOrder="场景项目顺序"
@@ -289,7 +357,8 @@ AdvSceneSwitcher.condition.date.state.at="现在"
AdvSceneSwitcher.condition.date.state.after="之后"
AdvSceneSwitcher.condition.date.state.before="之前"
AdvSceneSwitcher.condition.date.state.between="之间"
AdvSceneSwitcher.condition.date.separator=""
AdvSceneSwitcher.condition.date.state.pattern="模式"
AdvSceneSwitcher.condition.date.separator="到"
AdvSceneSwitcher.condition.date.ignoreDate="如果未选中,日期组件将被忽略"
AdvSceneSwitcher.condition.date.ignoreTime="如果未选中,时间组件将被忽略"
AdvSceneSwitcher.condition.date.showAdvancedSettings="显示高级设置"
@@ -297,8 +366,9 @@ AdvSceneSwitcher.condition.date.showSimpleSettings="显示简单设置"
AdvSceneSwitcher.condition.date.entry.simple="在 {{dayOfWeek}} {{weekCondition}} {{ignoreWeekTime}}{{weekTime}}"
AdvSceneSwitcher.condition.date.entry.advanced="{{condition}} {{ignoreDate}}{{date}} {{ignoreTime}}{{time}} {{separator}} {{date2}} {{time2}}"
AdvSceneSwitcher.condition.date.entry.repeat="{{repeat}} 在匹配到日期时间后,每隔 {{duration}} 重复一次"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="Next match at: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} On repeat update selected date to repeat date"
AdvSceneSwitcher.condition.date.entry.pattern="当前日期 \"{{currentDate}}\" 匹配模式 {{pattern}}"
AdvSceneSwitcher.condition.date.entry.nextMatchDate="下一次匹配 在: %1"
AdvSceneSwitcher.condition.date.entry.updateOnRepeat="{{updateOnRepeat}} 重复时,将选定的日期更新为重复日期"
AdvSceneSwitcher.condition.sceneTransform="场景项目被改变"
AdvSceneSwitcher.condition.sceneTransform.getTransform="内容或者设置被改变了"
AdvSceneSwitcher.condition.sceneTransform.entry.line1="在 {{scenes}} {{sources}} 匹配变换"
@@ -316,6 +386,7 @@ AdvSceneSwitcher.condition.transition.entry="{{conditions}}{{transitions}}{{scen
AdvSceneSwitcher.condition.sceneVisibility="场景项目可见"
AdvSceneSwitcher.condition.sceneVisibility.type.shown="显示的"
AdvSceneSwitcher.condition.sceneVisibility.type.hidden="隐藏的"
AdvSceneSwitcher.condition.sceneVisibility.type.changed="可见性已更改"
AdvSceneSwitcher.condition.sceneVisibility.entry="在 {{scenes}} {{sources}} 是 {{conditions}} "
AdvSceneSwitcher.condition.studioMode="工作室模式"
AdvSceneSwitcher.condition.studioMode.state.active="工作室模式处于活动状态"
@@ -345,7 +416,7 @@ 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}}"
AdvSceneSwitcher.condition.stats.entry="{{stats}} {{condition}} {{value}}"
AdvSceneSwitcher.condition.profile="配置文件"
AdvSceneSwitcher.condition.profile.entry="当前活动的配置文件为 {{profiles}}"
AdvSceneSwitcher.condition.websocket="websocket"
@@ -361,7 +432,20 @@ AdvSceneSwitcher.condition.variable.type.number="是一个数字"
AdvSceneSwitcher.condition.variable.type.lessThan="小于"
AdvSceneSwitcher.condition.variable.type.greaterThan="大于"
AdvSceneSwitcher.condition.variable.type.valueChanged="值已更改"
AdvSceneSwitcher.condition.variable.type.equalsVariable="等于变量"
AdvSceneSwitcher.condition.variable.type.lessThanVariable="小于变量"
AdvSceneSwitcher.condition.variable.type.greaterThanVariable="大于变量"
AdvSceneSwitcher.condition.variable.entry="{{variables}}{{conditions}}{{strValue}}{{numValue}}{{variables2}}"
AdvSceneSwitcher.condition.run="运行"
AdvSceneSwitcher.condition.run.entry="进程在超时 {{timeout}} 秒之前退出"
AdvSceneSwitcher.condition.run.entry.exit="{{checkExitCode}}检查退出代码{{exitCode}}"
AdvSceneSwitcher.condition.midi="MIDI"
AdvSceneSwitcher.condition.midi.entry="从{{device}} 收到的信息,符合:(从不咕咕的阿坤:这个好像是声卡工作是录音相关的功能,英文太抽象了,我就硬译过来了,如有相关设备,音乐好的,可以根据英文翻译好发给我!)"
AdvSceneSwitcher.condition.midi.entry.listen="将MIDI信息选择设置为从选定设备上接收的信息: {{listenButton}}"
AdvSceneSwitcher.condition.display="显示器"
AdvSceneSwitcher.condition.display.type.displayName="连接的显示器的名称匹配"
AdvSceneSwitcher.condition.display.type.displayCount="连接的显示器的数量为"
AdvSceneSwitcher.condition.display.entry="{{conditions}}{{displays}}{{regex}}{{displayCount}}"
; Macro Actions
AdvSceneSwitcher.action.switchScene="切换场景"
@@ -378,6 +462,10 @@ AdvSceneSwitcher.action.audio.type.mute="静音"
AdvSceneSwitcher.action.audio.type.unmute="取消静音"
AdvSceneSwitcher.action.audio.type.sourceVolume="来源音量"
AdvSceneSwitcher.action.audio.type.masterVolume="主音量"
AdvSceneSwitcher.action.audio.type.syncOffset="设置同步偏移"
AdvSceneSwitcher.action.audio.type.monitor="设置音频监听"
AdvSceneSwitcher.action.audio.type.balance="设置平衡"
AdvSceneSwitcher.action.audio.balance.description="选择的音频源将滑块方向移动(从不咕咕的阿坤的理解:左/右声道偏侧向)"
AdvSceneSwitcher.action.audio.fade.type.duration="持续时间"
AdvSceneSwitcher.action.audio.fade.type.rate="百分比"
AdvSceneSwitcher.action.audio.fade.duration="{{fade}}淡出 {{fadeTypes}} {{duration}} 秒."
@@ -391,43 +479,45 @@ AdvSceneSwitcher.action.recording.type.start="开始录制"
AdvSceneSwitcher.action.recording.type.pause="暂停录制"
AdvSceneSwitcher.action.recording.type.unpause="取消录制暂停"
AdvSceneSwitcher.action.recording.type.split="分割录制文件"
AdvSceneSwitcher.action.recording.type.changeOutputFolder="更改输出文件夹"
AdvSceneSwitcher.action.recording.type.changeOutputFileFormat="改变文件名的格式"
AdvSceneSwitcher.action.recording.pause.hint="请注意,根据您的录制设置,您可能无法暂停录制"
AdvSceneSwitcher.action.recording.split.hint="确保先在OBS设置中启用自动分割文件!"
AdvSceneSwitcher.action.recording.entry="{{actions}}{{recordFolder}}{{recordFileFormat}}{{pauseHint}}{{splitHint}}"
AdvSceneSwitcher.action.replay="回放缓冲区"
AdvSceneSwitcher.action.replay.saveWarn="警告:保存过于频繁可能会导致重播缓冲区实际上未保存!"
AdvSceneSwitcher.action.replay.type.stop="停止回放缓冲区"
AdvSceneSwitcher.action.replay.type.start="启动回放缓冲区"
AdvSceneSwitcher.action.replay.type.save="保存回放缓冲区"
AdvSceneSwitcher.action.replay="回放缓"
AdvSceneSwitcher.action.replay.saveWarn="警告:保存过于频繁可能会导致回放缓存实际上未保存!"
AdvSceneSwitcher.action.replay.type.stop="停止回放缓"
AdvSceneSwitcher.action.replay.type.start="启动回放缓"
AdvSceneSwitcher.action.replay.type.save="保存回放缓"
AdvSceneSwitcher.action.replay.entry="{{actions}}"
AdvSceneSwitcher.action.streaming="推流"
AdvSceneSwitcher.action.streaming.type.stop="停止推流"
AdvSceneSwitcher.action.streaming.type.start="开始推流"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}"
AdvSceneSwitcher.action.streaming.type.keyFrameInterval="设置关键帧间隔"
AdvSceneSwitcher.action.streaming.entry="{{actions}}{{keyFrameInterval}}{{stringValue}}{{showPassword}}"
AdvSceneSwitcher.action.run="运行"
AdvSceneSwitcher.action.run.arguments="参数:"
AdvSceneSwitcher.action.run.addArgument="添加参数"
AdvSceneSwitcher.action.run.addArgumentDescription="添加新参数:"
AdvSceneSwitcher.action.run.entry="运行 {{filePath}}"
AdvSceneSwitcher.action.sceneVisibility="场景项目可见性"
AdvSceneSwitcher.action.sceneVisibility.type.show="显示"
AdvSceneSwitcher.action.sceneVisibility.type.hide="隐藏"
AdvSceneSwitcher.action.sceneVisibility.type.toggle="切换"
AdvSceneSwitcher.action.sceneVisibility.type.source="来源"
AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup="全部"
AdvSceneSwitcher.action.sceneVisibility.entry="置{{scenes}}{{actions}}{{sourceTypes}}{{sources}}{{sourceGroups}}"
AdvSceneSwitcher.action.sceneVisibility.entry="置{{scenes}}{{actions}}{{sources}}"
AdvSceneSwitcher.action.filter="滤镜"
AdvSceneSwitcher.action.filter.type.enable="开启"
AdvSceneSwitcher.action.filter.type.disable="关闭"
AdvSceneSwitcher.action.filter.type.settings="参数设置"
AdvSceneSwitcher.action.filter.entry=" {{sources}} {{actions}} {{filters}}"
AdvSceneSwitcher.action.filter.entry=" {{sources}} {{actions}} {{filters}}"
AdvSceneSwitcher.action.filter.getSettings="获取当前设置"
AdvSceneSwitcher.action.source="源"
AdvSceneSwitcher.action.source="源"
AdvSceneSwitcher.action.source.type.enable="启用"
AdvSceneSwitcher.action.source.type.disable="停用"
AdvSceneSwitcher.action.source.type.settings="配置设置"
AdvSceneSwitcher.action.source.type.refreshSettings="刷新源设置"
AdvSceneSwitcher.action.source.type.refreshSettings.tooltip="可用于刷新浏览器、媒体等源"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}"
AdvSceneSwitcher.action.source.type.pressSettingsButton="按下设置按钮"
AdvSceneSwitcher.action.source.type.refreshSettings.tooltip="可用于刷新浏览器、媒体、源、etc"
AdvSceneSwitcher.action.source.noSettingsButtons="未找到按钮!"
AdvSceneSwitcher.action.source.entry="{{actions}}{{sources}}{{settingsButtons}}{{deinterlaceMode}}{{deinterlaceOrder}}"
AdvSceneSwitcher.action.source.warning="警告OBS UI无法控制全局启用和禁用源注意通过这个设置禁用我测试你必须通过这个在启用才行不然点击小眼睛你无法显示"
AdvSceneSwitcher.action.source.getSettings="获取当前设置"
AdvSceneSwitcher.action.media="媒体"
@@ -443,14 +533,16 @@ 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.macro.type.run="运行操作"
AdvSceneSwitcher.action.macro.type.stop="停止操作"
AdvSceneSwitcher.action.macro.entry="{{actions}}{{actionIndex}}{{macros}}"
AdvSceneSwitcher.action.pluginState="插件状态"
AdvSceneSwitcher.action.pluginState.type.stop="停止高级场景切换插件"
AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项:"
AdvSceneSwitcher.action.pluginState.type.noMatch="没有匹配项:"
AdvSceneSwitcher.action.pluginState.type.import="从设置导入"
AdvSceneSwitcher.action.pluginState.importWarning="注意:当“设置”窗口打开时,操作将被忽略。"
AdvSceneSwitcher.action.pluginState.type.terminate="关闭 OBS"
AdvSceneSwitcher.action.pluginState.terminateConfirm="OBS将在10秒后自动关闭/n你想继续关闭OBS吗"
AdvSceneSwitcher.action.pluginState.entry="{{actions}}{{values}}{{scenes}}{{settings}}{{settingsWarning}}"
AdvSceneSwitcher.action.virtualCamera="虚拟摄像机"
AdvSceneSwitcher.action.virtualCamera.type.stop="停止虚拟摄像机"
@@ -467,7 +559,7 @@ AdvSceneSwitcher.action.hotkey.leftMeta="左 Meta"
AdvSceneSwitcher.action.hotkey.rightMeta="右 Meta"
AdvSceneSwitcher.action.hotkey.onlyOBS="仅向OBS发送按键"
AdvSceneSwitcher.action.hotkey.disabled="无法模拟按键-功能已禁用!"
AdvSceneSwitcher.action.hotkey.entry="按下 {{keys}} for {{duration}}"
AdvSceneSwitcher.action.hotkey.entry="按下 {{keys}} {{duration}}"
AdvSceneSwitcher.action.sceneOrder="场景项目顺序"
AdvSceneSwitcher.action.sceneOrder.type.moveUp="上移"
AdvSceneSwitcher.action.sceneOrder.type.moveDown="下移"
@@ -475,9 +567,9 @@ 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.getTransform="得到变换"
AdvSceneSwitcher.action.sceneTransform.entry="在 {{scenes}} 变换 {{sources}}"
AdvSceneSwitcher.action.sceneTransform="场景项目换"
AdvSceneSwitcher.action.sceneTransform.getTransform="获得转换"
AdvSceneSwitcher.action.sceneTransform.entry="在 {{scenes}}{{action}}{{rotation}}{{sources}}"
AdvSceneSwitcher.action.file="文件"
AdvSceneSwitcher.action.file.type.write="写入"
AdvSceneSwitcher.action.file.type.append="追加写入"
@@ -503,8 +595,13 @@ AdvSceneSwitcher.action.timer.type.reset="重置"
AdvSceneSwitcher.action.timer.type.setTimeRemaining="设置剩余时间"
AdvSceneSwitcher.action.timer.entry="{{timerAction}} 定时在 {{macros}} {{duration}}"
AdvSceneSwitcher.action.random="随机"
AdvSceneSwitcher.action.random.allowRepeat="允许连续执行同一宏"
AdvSceneSwitcher.action.random.entry="随机运行以下任何宏(忽略暂停的宏)"
AdvSceneSwitcher.action.systray="系统托盘通知"
AdvSceneSwitcher.action.systray.title="标题:"
AdvSceneSwitcher.action.systray.message="信息:"
AdvSceneSwitcher.action.systray.icon="图标:"
AdvSceneSwitcher.action.systray.iconHint="留空以使用默认图标"
AdvSceneSwitcher.action.screenshot="截图"
AdvSceneSwitcher.action.screenshot.save.default="默认"
AdvSceneSwitcher.action.screenshot.save.custom="其他"
@@ -527,21 +624,56 @@ AdvSceneSwitcher.action.sequence.continueFrom="继续所选项目"
AdvSceneSwitcher.action.websocket="websocket"
AdvSceneSwitcher.action.websocket.type.request="request"
AdvSceneSwitcher.action.websocket.type.event="event"
AdvSceneSwitcher.action.websocket.entry.request="发送场景切换器 {{type}}{{connection}}"
AdvSceneSwitcher.action.websocket.entry.request="通过{{connection}} 发送场景切换器 {{type}}"
AdvSceneSwitcher.action.websocket.entry.event="发送场景切换器 {{type}} 到连接的客户端"
AdvSceneSwitcher.action.http="Http"
AdvSceneSwitcher.action.http.type.get="GET"
AdvSceneSwitcher.action.http.type.post="POST"
AdvSceneSwitcher.action.http.setHeaders="设置头信息(SET headers)"
AdvSceneSwitcher.action.http.headers="头信息(headers):"
AdvSceneSwitcher.action.http.addHeader="增加头信息(ADD headers)"
AdvSceneSwitcher.action.http.type.get="获取(GET)"
AdvSceneSwitcher.action.http.type.post="发送(POST)"
AdvSceneSwitcher.action.http.entry.line1="发送 {{method}} 到 {{url}}"
AdvSceneSwitcher.action.http.entry.line2="超时: {{timeout}} 秒"
AdvSceneSwitcher.action.variable="变量"
AdvSceneSwitcher.action.variable.type.set="设置"
AdvSceneSwitcher.action.variable.type.set="设置为固定值"
AdvSceneSwitcher.action.variable.type.append="追加"
AdvSceneSwitcher.action.variable.type.appendVar="追加变量"
AdvSceneSwitcher.action.variable.type.increment="增加"
AdvSceneSwitcher.action.variable.type.decrement="减少"
AdvSceneSwitcher.action.variable.type.setActionValue="设置为操作值"
AdvSceneSwitcher.action.variable.type.setConditionValue="设置为条件值"
AdvSceneSwitcher.action.variable.type.roundToInt="取整到最近的整数"
AdvSceneSwitcher.action.variable.type.subString="设置为当前值的子字符串"
AdvSceneSwitcher.action.variable.type.findAndReplace="在当前值中查找并替换"
AdvSceneSwitcher.action.variable.type.mathExpression="数学表达式"
AdvSceneSwitcher.action.variable.mathExpression.example="( 1 + 2 * 3 ) / 4"
AdvSceneSwitcher.action.variable.findAndReplace.find="要查找的文本"
AdvSceneSwitcher.action.variable.findAndReplace.replace="要替换为的文本"
AdvSceneSwitcher.action.variable.subString.begin="开始"
AdvSceneSwitcher.action.variable.subString.all="全部"
AdvSceneSwitcher.action.variable.invalidSelection="无效选择!"
AdvSceneSwitcher.action.variable.actionNoVariableSupport="不支持从 %1 个操作获取变量值!"
AdvSceneSwitcher.action.variable.conditionNoVariableSupport="不支持从 %1 条件中获取变量值!"
AdvSceneSwitcher.action.variable.currentSegmentValue="当前值:"
AdvSceneSwitcher.action.variable.entry="{{actions}}{{variables}}{{variables2}}{{strValue}}{{numValue}}{{segmentIndex}}{{mathExpression}}"
AdvSceneSwitcher.action.variable.entry.substringIndex="子字符串开始:{{subStringStart}} 子字符串大小:{{subStringSize}}"
AdvSceneSwitcher.action.variable.entry.substringRegex="使用正则表达式为 {{regexMatchIdx}} 匹配的值:"
AdvSceneSwitcher.action.variable.entry.findAndReplace="{{findStr}}{{replaceStr}}"
AdvSceneSwitcher.action.projector="投影仪"
AdvSceneSwitcher.action.projector.type.source="源"
AdvSceneSwitcher.action.projector.type.scene="场景"
AdvSceneSwitcher.action.projector.type.preview="预览"
AdvSceneSwitcher.action.projector.type.program="程序"
AdvSceneSwitcher.action.projector.type.multiview="多视图"
AdvSceneSwitcher.action.projector.display="显示"
AdvSceneSwitcher.action.projector.windowed="窗口"
AdvSceneSwitcher.action.projector.fullscreen="全屏"
AdvSceneSwitcher.action.projector.entry="打开为{{windowTypes}} 的 {{types}}{{scenes}}{{sources}}投影"
AdvSceneSwitcher.action.projector.entry.monitor="在 {{monitors}} 上 (从不咕咕的阿坤说:谨慎开启!)"
AdvSceneSwitcher.action.midi="MIDI"
AdvSceneSwitcher.action.midi.entry="发送消息到 {{device}}:"
AdvSceneSwitcher.action.midi.entry.listen="将MIDI消息选择设置为 {{listenDevices}} 上传入的消息: {{listenButton}}"
AdvSceneSwitcher.action.osc="打开声音控制"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="转场特效"
@@ -600,7 +732,7 @@ AdvSceneSwitcher.mediaTab.states.none="无"
AdvSceneSwitcher.mediaTab.states.playing="播放中"
AdvSceneSwitcher.mediaTab.states.opening="打开中"
AdvSceneSwitcher.mediaTab.states.buffering="缓冲中"
AdvSceneSwitcher.mediaTab.states.Paused="暂停"
AdvSceneSwitcher.mediaTab.states.paused="暂停"
AdvSceneSwitcher.mediaTab.states.stopped="停止"
AdvSceneSwitcher.mediaTab.states.ended="结束"
AdvSceneSwitcher.mediaTab.states.error="错误"
@@ -821,6 +953,7 @@ AdvSceneSwitcher.connection.password="密码:"
AdvSceneSwitcher.connection.reconnect="自动重新连接:"
AdvSceneSwitcher.connection.reconnectDelay="自动重新连接延迟:"
AdvSceneSwitcher.connection.connectOnStart="启动时自动连接:"
AdvSceneSwitcher.connection.useOBSWebsocketProtocol="<html><head/><body><p>如何使用 obs-websocket <a href=\"https://github.com/obsproject/obs-websocket/blob/master/docs/generated/protocol.md\"><span style=\" text-decoration: underline; color:#268bd2;\">protocol</span></a></p></body></html>"
AdvSceneSwitcher.connection.test="测试连接"
AdvSceneSwitcher.connection.status.disconnected="断开"
AdvSceneSwitcher.connection.status.connecting="连接"
@@ -834,6 +967,68 @@ AdvSceneSwitcher.regex.dotMatchNewline=". 匹配换行符"
AdvSceneSwitcher.regex.multiLine="^ 和 $ 是匹配行的开始/结束"
AdvSceneSwitcher.regex.extendedPattern="启用Qt的扩展模式语法"
AdvSceneSwitcher.process.showAdvanced="显示高级设置"
AdvSceneSwitcher.process.arguments="参数:"
AdvSceneSwitcher.process.addArgument="增加参数"
AdvSceneSwitcher.process.addArgumentDescription="添加新参数:"
AdvSceneSwitcher.process.entry="运行{{filePath}}{{advancedSettings}}"
AdvSceneSwitcher.process.entry.workingDirectory="工作目录:{{workingDirectory}}"
AdvSceneSwitcher.math.expressionFail="计算表达式失败"
AdvSceneSwitcher.midi.deviceNamePattern="[端口 #%1] %2"
AdvSceneSwitcher.midi.message.type="类型:"
AdvSceneSwitcher.midi.message.type.optional="任意"
AdvSceneSwitcher.midi.message.type.invalid="无效"
AdvSceneSwitcher.midi.message.type.noteOff="关闭音符"
AdvSceneSwitcher.midi.message.type.noteOn="打开音符"
AdvSceneSwitcher.midi.message.type.polyphonicPressure="合成音压力"
AdvSceneSwitcher.midi.message.type.controlChange="控制更改"
AdvSceneSwitcher.midi.message.type.programChange="程序更改"
AdvSceneSwitcher.midi.message.type.channelAftertouch="频道余音"
AdvSceneSwitcher.midi.message.type.pitchBend="音高弯曲"
AdvSceneSwitcher.midi.message.type.systemExclusive="系统独占"
AdvSceneSwitcher.midi.message.type.timeCode="时间码"
AdvSceneSwitcher.midi.message.type.songPositionPointer="歌曲位置指针"
AdvSceneSwitcher.midi.message.type.songSelect="歌曲选择"
AdvSceneSwitcher.midi.message.type.reserved1="保留 (1)"
AdvSceneSwitcher.midi.message.type.reserved2="保留 (2)"
AdvSceneSwitcher.midi.message.type.tuneRequest="调音请求"
AdvSceneSwitcher.midi.message.type.endOfSystemExclusive="系统独占结束"
AdvSceneSwitcher.midi.message.type.timeClock="时间时钟"
AdvSceneSwitcher.midi.message.type.reserved3="保留 (3)"
AdvSceneSwitcher.midi.message.type.startFile="开始文件"
AdvSceneSwitcher.midi.message.type.continueFile="继续文件"
AdvSceneSwitcher.midi.message.type.stopFile="停止文件"
AdvSceneSwitcher.midi.message.type.reserved4="保留 (4)"
AdvSceneSwitcher.midi.message.type.activeSensing="主动感应"
AdvSceneSwitcher.midi.message.type.systemReset="系统充值"
AdvSceneSwitcher.midi.message.type.unknown="未知"
AdvSceneSwitcher.midi.message.channel="频道 :"
AdvSceneSwitcher.midi.message.note="音符 / 值(1):"
AdvSceneSwitcher.midi.message.value="值(2):"
AdvSceneSwitcher.midi.message.placeholder="任意"
AdvSceneSwitcher.midi.resetDevices="重新连接所有 MIDI 设备"
AdvSceneSwitcher.midi.startListen="开始监听"
AdvSceneSwitcher.midi.stopListen="停止监听"
AdvSceneSwitcher.midi.startListenFail="设备正忙!\n其他设备已经在监听!"
AdvSceneSwitcher.midi.deviceOpenFail="初始化 MIDI 设备失败!"
AdvSceneSwitcher.osc.network="网络"
AdvSceneSwitcher.osc.network.protocol="协议:"
AdvSceneSwitcher.osc.network.address="地址:"
AdvSceneSwitcher.osc.network.port="端口:"
AdvSceneSwitcher.osc.message="信息"
AdvSceneSwitcher.osc.message.type.none="无"
AdvSceneSwitcher.osc.message.type.float="浮点数哦"
AdvSceneSwitcher.osc.message.type.int="整数"
AdvSceneSwitcher.osc.message.type.string="字符串"
AdvSceneSwitcher.osc.message.type.binaryBlob="二进制"
AdvSceneSwitcher.osc.message.type.true="真"
AdvSceneSwitcher.osc.message.type.false="假"
AdvSceneSwitcher.osc.message.type.infinity="无限"
AdvSceneSwitcher.osc.message.type.null="无"
AdvSceneSwitcher.selectScene="--选择场景--"
AdvSceneSwitcher.selectPreviousScene="上个场景"
AdvSceneSwitcher.selectCurrentScene="当前场景"
@@ -843,7 +1038,7 @@ AdvSceneSwitcher.currentTransition="当前转场特效"
AdvSceneSwitcher.anyTransition="任何转场特效"
AdvSceneSwitcher.selectTransition="--选择转场特效--"
AdvSceneSwitcher.selectWindow="--选择窗口--"
AdvSceneSwitcher.selectSource="--选择源--"
AdvSceneSwitcher.selectSource="--选择源--"
AdvSceneSwitcher.selectAudioSource="--选择音频源--"
AdvSceneSwitcher.selectVideoSource="--选择视频源--"
AdvSceneSwitcher.OBSVideoOutput="OBS的主要输出"
@@ -864,8 +1059,8 @@ AdvSceneSwitcher.sceneItemSelection.any="任何"
AdvSceneSwitcher.status.active="运行中"
AdvSceneSwitcher.status.inactive="已停止"
AdvSceneSwitcher.running="插件运行"
AdvSceneSwitcher.stopped="插件停止"
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>"
@@ -879,4 +1074,8 @@ AdvSceneSwitcher.duration.condition.none="没有时间限制"
AdvSceneSwitcher.duration.condition.more="时间少于"
AdvSceneSwitcher.duration.condition.equal="时间等于"
AdvSceneSwitcher.duration.condition.less="时间大于"
AdvSceneSwitcher.duration.condition.within="保留结果后余存"
AdvSceneSwitcher.duration.condition.within="在最后"
AdvSceneSwitcher.audio.monitor.none="关闭监听"
AdvSceneSwitcher.audio.monitor.monitorOnly="仅监听(输出静音)"
AdvSceneSwitcher.audio.monitor.both="监听并输出"

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<path d="m1,2c0,-0.27 0.11,-0.52 0.29,-0.71c0.39,-0.39 1.03,-0.39 1.42,0l5.29,5.3l5.29,-5.3c0.39,-0.39 1.03,-0.39 1.42,0c0.18,0.19 0.29,0.44 0.29,0.71s-0.11,0.52 -0.29,0.71l-6,6c-0.39,0.39 -1.03,0.39 -1.42,0l-6,-6c-0.18,-0.19 -0.29,-0.44 -0.29,-0.71zm0,0" fill="#fefefe" id="svg_1" transform="matrix(1, 0, 0, 1, 0, 0)"/>
<path d="m1,8c0,-0.27 0.09,-0.52 0.28,-0.71c0.38,-0.39 1.03,-0.39 1.41,0l5.31,5.3l5.28,-5.3c0.38,-0.39 1.03,-0.39 1.41,0c0.19,0.19 0.31,0.44 0.31,0.71s-0.13,0.52 -0.31,0.71l-6,6c-0.38,0.39 -1.03,0.39 -1.41,0l-6,-6c-0.19,-0.19 -0.28,-0.44 -0.28,-0.71zm0,0" fill="#fefefe" id="svg_2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 791 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<path d="m1,8c0,-0.27 0.11,-0.52 0.29,-0.71l6,-6c0.39,-0.39 1.03,-0.39 1.42,0l6,6c0.18,0.19 0.29,0.44 0.29,0.71s-0.11,0.52 -0.29,0.71c-0.39,0.39 -1.03,0.39 -1.42,0l-5.29,-5.3l-5.29,5.3c-0.39,0.39 -1.03,0.39 -1.42,0c-0.18,-0.19 -0.29,-0.44 -0.29,-0.71zm0,0" fill="#fefefe" id="svg_1"/>
<path d="m1,14c0,-0.27 0.11,-0.52 0.28,-0.71l6,-6c0.39,-0.39 1.03,-0.39 1.42,0l6,6c0.19,0.19 0.3,0.44 0.3,0.71s-0.11,0.52 -0.3,0.71c-0.39,0.39 -1.03,0.39 -1.42,0l-5.28,-5.3l-5.3,5.3c-0.39,0.39 -1.03,0.39 -1.42,0c-0.17,-0.19 -0.28,-0.44 -0.28,-0.71zm0,0" fill="#fefefe" id="svg_2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 753 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<path d="m1,2c0,-0.27 0.11,-0.52 0.29,-0.71c0.39,-0.39 1.03,-0.39 1.42,0l5.29,5.3l5.29,-5.3c0.39,-0.39 1.03,-0.39 1.42,0c0.18,0.19 0.29,0.44 0.29,0.71s-0.11,0.52 -0.29,0.71l-6,6c-0.39,0.39 -1.03,0.39 -1.42,0l-6,-6c-0.18,-0.19 -0.29,-0.44 -0.29,-0.71zm0,0" fill="#202020" id="svg_1"/>
<path d="m1,8c0,-0.27 0.11,-0.52 0.29,-0.71c0.39,-0.39 1.03,-0.39 1.42,0l5.29,5.3l5.29,-5.3c0.39,-0.39 1.03,-0.39 1.42,0c0.18,0.19 0.29,0.44 0.29,0.71s-0.11,0.52 -0.29,0.71l-6,6c-0.39,0.39 -1.03,0.39 -1.42,0l-6,-6c-0.18,-0.19 -0.29,-0.44 -0.29,-0.71zm0,0" fill="#202020" id="svg_4" transform="matrix(1, 0, 0, 1, 0, 0)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 791 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0"?>
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<g class="layer">
<title>Layer 1</title>
<path d="m1,8c0,-0.27 0.11,-0.52 0.29,-0.71l6,-6c0.39,-0.39 1.03,-0.39 1.42,0l6,6c0.18,0.19 0.29,0.44 0.29,0.71s-0.11,0.52 -0.29,0.71c-0.39,0.39 -1.03,0.39 -1.42,0l-5.29,-5.3l-5.29,5.3c-0.39,0.39 -1.03,0.39 -1.42,0c-0.18,-0.19 -0.29,-0.44 -0.29,-0.71zm0,0" fill="#202020" id="svg_1"/>
<path d="m1,14c0,-0.27 0.11,-0.52 0.29,-0.71l6,-6c0.39,-0.39 1.03,-0.39 1.42,0l6,6c0.18,0.19 0.29,0.44 0.29,0.71s-0.11,0.52 -0.29,0.71c-0.39,0.39 -1.03,0.39 -1.42,0l-5.29,-5.3l-5.29,5.3c-0.39,0.39 -1.03,0.39 -1.42,0c-0.18,-0.19 -0.29,-0.44 -0.29,-0.71zm0,0" fill="#202020" id="svg_2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 757 B

View File

@@ -988,6 +988,19 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="conditionTop">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="conditionUp">
<property name="maximumSize">
@@ -1023,6 +1036,19 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="conditionBottom">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_15">
<property name="orientation">
@@ -1130,6 +1156,19 @@
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="actionTop">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="actionUp">
<property name="maximumSize">
@@ -1162,6 +1201,19 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="actionBottom">
<property name="maximumSize">
<size>
<width>22</width>
<height>16777215</height>
</size>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_14">
<property name="orientation">

View File

@@ -123,6 +123,10 @@ static void AskForBackup(obs_data_t *obj);
static void SaveSceneSwitcher(obs_data_t *save_data, bool saving, void *)
{
if (!switcher) {
return;
}
if (saving) {
std::lock_guard<std::mutex> lock(switcher->m);
switcher->Prune();
@@ -439,6 +443,8 @@ void SwitcherData::Start()
}
}
bool CloseAllInputDialogs();
void SwitcherData::Stop()
{
if (th && th->isRunning()) {
@@ -447,10 +453,23 @@ void SwitcherData::Stop()
abortMacroWait = true;
macroWaitCv.notify_all();
macroTransitionCv.notify_all();
th->wait();
delete th;
th = nullptr;
// Not waiting if a dialog was closed is a workaround to avoid
// deadlocks when a variable input dialog is opened while Stop()
// is called.
//
// The QEventLoop handling the dialog would not exit until Stop()
// would return, which itself would wait for the switcher thread
// to complete, which itself is blocked by the variable input
// dialog.
//
// Must be reworked in the future, as this will not go well when
// OBS is shutting down and a dialog is still opened.
if (!CloseAllInputDialogs()) {
th->wait();
delete th;
th = nullptr;
}
writeToStatusFile("Advanced Scene Switcher stopped");
}
@@ -599,7 +618,7 @@ static void setStreamStopping()
std::chrono::high_resolution_clock::now();
}
static void handleExit()
static void handleShutdown()
{
if (!switcher) {
return;
@@ -609,8 +628,14 @@ static void handleExit()
switcher->Stop();
switcher->CheckMacros();
switcher->RunMacros();
// Unfortunately this will not work as OBS will now allow saving
// the scene collection data at this point, So any OBS specific
// changes done during shutdown will be lost
//
// TODO: Look for a way to possibly resolve this
obs_frontend_save();
}
FreeSceneSwitcher();
}
static void handleSceneCollectionChanging()
@@ -634,8 +659,12 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
}
switch (event) {
case OBS_FRONTEND_EVENT_EXIT:
handleExit();
case OBS_FRONTEND_EVENT_SCRIPTING_SHUTDOWN:
// Note: We are intentionally not listening for
// OBS_FRONTEND_EVENT_EXIT here as at that point all scene
// collection data will already have been cleared and thus all
// macros will have already been cleared
handleShutdown();
break;
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
handleSceneChange();
@@ -673,6 +702,9 @@ static void OBSEvent(enum obs_frontend_event event, void *switcher)
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
handleSceneCollectionChanging();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CLEANUP:
SaveSceneSwitcher(nullptr, false, nullptr);
break;
#endif
default:
break;

View File

@@ -102,14 +102,17 @@ public slots:
void on_runMacroOnChange_stateChanged(int value);
void on_conditionAdd_clicked();
void on_conditionRemove_clicked();
void on_conditionTop_clicked();
void on_conditionUp_clicked();
void on_conditionDown_clicked();
void on_conditionBottom_clicked();
void on_actionAdd_clicked();
void on_actionRemove_clicked();
void on_actionTop_clicked();
void on_actionUp_clicked();
void on_actionDown_clicked();
void MacroSelectionChanged(const QItemSelection &,
const QItemSelection &);
void on_actionBottom_clicked();
void MacroSelectionChanged();
void UpMacroSegementHotkey();
void DownMacroSegementHotkey();
void DeleteMacroSegementHotkey();
@@ -118,6 +121,8 @@ public slots:
void ShowMacroConditionsContextMenu(const QPoint &);
void CopyMacro();
void RenameCurrentMacro();
void ExportMacros();
void ImportMacros();
void ExpandAllActions();
void ExpandAllConditions();
void CollapseAllActions();
@@ -161,6 +166,7 @@ signals:
void VariableRemoved(const QString &);
private:
bool ResolveMacroImportNameConflict(std::shared_ptr<Macro> &);
bool MacroTabIsInFocus();
MacroSegmentList *conditionsList = nullptr;

View File

@@ -516,6 +516,7 @@ void SwitcherData::LoadSettings(obs_data_t *obj)
// Reset on startup and scene collection change
switcher->lastOpenedTab = -1;
startupLoadDone = true;
}
void SwitcherData::SaveSettings(obs_data_t *obj)
@@ -577,17 +578,7 @@ void SwitcherData::SaveGeneralSettings(obs_data_t *obj)
obs_data_set_bool(obj, "warnPluginLoadFailure", warnPluginLoadFailure);
obs_data_set_bool(obj, "hideLegacyTabs", hideLegacyTabs);
obs_data_set_int(obj, "priority0", functionNamesByPriority[0]);
obs_data_set_int(obj, "priority1", functionNamesByPriority[1]);
obs_data_set_int(obj, "priority2", functionNamesByPriority[2]);
obs_data_set_int(obj, "priority3", functionNamesByPriority[3]);
obs_data_set_int(obj, "priority4", functionNamesByPriority[4]);
obs_data_set_int(obj, "priority5", functionNamesByPriority[5]);
obs_data_set_int(obj, "priority6", functionNamesByPriority[6]);
obs_data_set_int(obj, "priority7", functionNamesByPriority[7]);
obs_data_set_int(obj, "priority8", functionNamesByPriority[8]);
obs_data_set_int(obj, "priority9", functionNamesByPriority[9]);
obs_data_set_int(obj, "priority10", functionNamesByPriority[10]);
SaveFunctionPriorities(obj, functionNamesByPriority);
obs_data_set_int(obj, "threadPriority", threadPriority);
@@ -638,6 +629,7 @@ void SwitcherData::LoadGeneralSettings(obs_data_t *obj)
hideLegacyTabs = obs_data_get_bool(obj, "hideLegacyTabs");
SetDefaultFunctionPriorities(obj);
LoadFunctionPriorities(obj, functionNamesByPriority);
if (!PrioFuncsValid()) {
functionNamesByPriority = GetDefaultFunctionPriorityList();
}

View File

@@ -437,12 +437,6 @@ void MacroActionAudioEdit::SetWidgetVisibility()
_balance->setVisible(_entryData->_action ==
MacroActionAudio::Action::BALANCE);
_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);
@@ -474,6 +468,12 @@ void MacroActionAudioEdit::SetWidgetVisibility()
hasVolumeControl(_entryData->_action));
SetLayoutVisible(_fadeOptionsLayout,
hasVolumeControl(_entryData->_action));
_abortActiveFade->setVisible(hasVolumeControl(_entryData->_action) &&
_entryData->_fade);
_wait->setVisible(hasVolumeControl(_entryData->_action) &&
_entryData->_fade);
updateGeometry();
adjustSize();
}

View File

@@ -3,8 +3,11 @@
#include "macro-action-edit.hpp"
#include "macro-action-scene-switch.hpp"
#include "section.hpp"
#include "switch-button.hpp"
#include "utility.hpp"
#include <QGraphicsOpacityEffect>
namespace advss {
std::map<std::string, MacroActionInfo> &MacroActionFactory::GetMap()
@@ -78,26 +81,32 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
std::shared_ptr<MacroAction> *entryData,
const std::string &id)
: MacroSegmentEdit(switcher->macroProperties._highlightActions, parent),
_actionSelection(new QComboBox()),
_actionSelection(new FilterComboBox()),
_enable(new SwitchButton()),
_entryData(entryData)
{
QWidget::connect(_actionSelection,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(ActionSelectionChanged(const QString &)));
QWidget::connect(_enable, SIGNAL(checked(bool)), this,
SLOT(ActionEnableChanged(bool)));
QWidget::connect(window(), SIGNAL(HighlightActionsChanged(bool)), this,
SLOT(EnableHighlight(bool)));
QWidget::connect(&_actionStateTimer, SIGNAL(timeout()), this,
SLOT(UpdateActionState()));
populateActionSelection(_actionSelection);
_section->AddHeaderWidget(_enable);
_section->AddHeaderWidget(_actionSelection);
_section->AddHeaderWidget(_headerInfo);
QVBoxLayout *actionLayout = new QVBoxLayout;
auto actionLayout = new QVBoxLayout;
actionLayout->setContentsMargins({7, 7, 7, 7});
actionLayout->addWidget(_section);
_contentLayout->addLayout(actionLayout);
QHBoxLayout *mainLayout = new QHBoxLayout;
auto mainLayout = new QHBoxLayout;
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
mainLayout->addWidget(_frame);
@@ -106,6 +115,7 @@ MacroActionEdit::MacroActionEdit(QWidget *parent,
_entryData = entryData;
UpdateEntryData(id);
_actionStateTimer.start(300);
_loading = false;
}
@@ -115,10 +125,14 @@ void MacroActionEdit::ActionSelectionChanged(const QString &text)
return;
}
std::string id = MacroActionFactory::GetIdByName(text);
if (id.empty()) {
return;
}
HeaderInfoChanged("");
auto idx = _entryData->get()->GetIndex();
auto macro = _entryData->get()->GetMacro();
std::string id = MacroActionFactory::GetIdByName(text);
HeaderInfoChanged("");
{
std::lock_guard<std::mutex> lock(switcher->m);
_entryData->reset();
@@ -136,6 +150,9 @@ void MacroActionEdit::UpdateEntryData(const std::string &id)
{
_actionSelection->setCurrentText(
obs_module_text(MacroActionFactory::GetActionName(id).c_str()));
const bool enabled = (*_entryData)->Enabled();
_enable->setChecked(enabled);
SetDisableEffect(!enabled);
auto widget = MacroActionFactory::CreateWidget(id, this, *_entryData);
QWidget::connect(widget, SIGNAL(HeaderInfoChanged(const QString &)),
this, SLOT(HeaderInfoChanged(const QString &)));
@@ -150,6 +167,43 @@ void MacroActionEdit::SetEntryData(std::shared_ptr<MacroAction> *data)
_entryData = data;
}
void MacroActionEdit::SetDisableEffect(bool value)
{
if (value) {
auto effect = new QGraphicsOpacityEffect(this);
effect->setOpacity(0.5);
_section->setGraphicsEffect(effect);
} else {
_section->setGraphicsEffect(nullptr);
}
}
void MacroActionEdit::ActionEnableChanged(bool value)
{
if (_loading || !_entryData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
(*_entryData)->SetEnabled(value);
SetDisableEffect(!value);
}
void MacroActionEdit::UpdateActionState()
{
if (_loading || !_entryData) {
return;
}
SetEnableAppearance((*_entryData)->Enabled());
}
void MacroActionEdit::SetEnableAppearance(bool value)
{
_enable->setChecked(value);
SetDisableEffect(!value);
}
std::shared_ptr<MacroSegment> MacroActionEdit::Data()
{
return *_entryData;
@@ -251,23 +305,43 @@ void AdvSceneSwitcher::on_actionRemove_clicked()
MacroActionSelectionChanged(-1);
}
void AdvSceneSwitcher::on_actionUp_clicked()
void AdvSceneSwitcher::on_actionTop_clicked()
{
if (currentActionIdx == -1) {
return;
}
MacroActionReorder(0, currentActionIdx);
MacroActionSelectionChanged(0);
}
void AdvSceneSwitcher::on_actionUp_clicked()
{
if (currentActionIdx == -1 || currentActionIdx == 0) {
return;
}
MoveMacroActionUp(currentActionIdx);
MacroActionSelectionChanged(currentActionIdx - 1);
}
void AdvSceneSwitcher::on_actionDown_clicked()
{
if (currentActionIdx == -1) {
if (currentActionIdx == -1 ||
currentActionIdx == actionsList->ContentLayout()->count() - 1) {
return;
}
MoveMacroActionDown(currentActionIdx);
MacroActionSelectionChanged(currentActionIdx + 1);
}
void AdvSceneSwitcher::on_actionBottom_clicked()
{
if (currentActionIdx == -1) {
return;
}
const int newIdx = actionsList->ContentLayout()->count() - 1;
MacroActionReorder(newIdx, currentActionIdx);
MacroActionSelectionChanged(newIdx);
}
void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
{
if (pos1 == pos2) {
@@ -347,8 +421,8 @@ void AdvSceneSwitcher::MacroActionReorder(int to, int from)
return;
}
if (from < 0 || from > (int)macro->Actions().size() || to < 0 ||
to > (int)macro->Actions().size()) {
if (to == from || from < 0 || from > (int)macro->Actions().size() ||
to < 0 || to > (int)macro->Actions().size()) {
return;
}
{

View File

@@ -1,11 +1,13 @@
#pragma once
#include "macro-action.hpp"
#include "filter-combo-box.hpp"
#include <QComboBox>
#include <memory>
namespace advss {
class SwitchButton;
struct MacroActionInfo {
using TCreateMethod = std::shared_ptr<MacroAction> (*)(Macro *m);
using TCreateWidgetMethod = QWidget *(*)(QWidget *parent,
@@ -44,13 +46,19 @@ public:
private slots:
void ActionSelectionChanged(const QString &text);
void ActionEnableChanged(bool);
void UpdateActionState();
private:
std::shared_ptr<MacroSegment> Data();
void SetDisableEffect(bool);
void SetEnableAppearance(bool);
QComboBox *_actionSelection;
FilterComboBox *_actionSelection;
SwitchButton *_enable;
std::shared_ptr<MacroAction> *_entryData;
QTimer _actionStateTimer;
bool _loading = true;
};

View File

@@ -15,6 +15,8 @@ const static std::map<MacroActionFilter::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.filter.type.enable"},
{MacroActionFilter::Action::DISABLE,
"AdvSceneSwitcher.action.filter.type.disable"},
{MacroActionFilter::Action::TOGGLE,
"AdvSceneSwitcher.action.filter.type.toggle"},
{MacroActionFilter::Action::SETTINGS,
"AdvSceneSwitcher.action.filter.type.settings"},
};
@@ -29,6 +31,9 @@ bool MacroActionFilter::PerformAction()
case Action::DISABLE:
obs_source_set_enabled(s, false);
break;
case Action::TOGGLE:
obs_source_set_enabled(s, !obs_source_enabled(s));
break;
case Action::SETTINGS:
SetSourceSettings(s, _settings);
break;
@@ -60,6 +65,7 @@ bool MacroActionFilter::Save(obs_data_t *obj) const
_filter.Save(obj, "filter");
obs_data_set_int(obj, "action", static_cast<int>(_action));
_settings.Save(obj, "settings");
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -68,7 +74,17 @@ bool MacroActionFilter::Load(obs_data_t *obj)
MacroAction::Load(obj);
_source.Load(obj);
_filter.Load(obj, _source, "filter");
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
// TODO: Remove this fallback in future version
if (!obs_data_has_user_value(obj, "version")) {
const auto value = obs_data_get_int(obj, "action");
if (value == 2) {
_action = Action::SETTINGS;
} else {
_action = static_cast<Action>(value);
}
} else {
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
}
_settings.Load(obj, "settings");
return true;
}

View File

@@ -27,6 +27,7 @@ public:
enum class Action {
ENABLE,
DISABLE,
TOGGLE,
SETTINGS,
};

View File

@@ -13,6 +13,26 @@ bool MacroActionHotkey::_registered = MacroActionFactory::Register(
{MacroActionHotkey::Create, MacroActionHotkeyEdit::Create,
"AdvSceneSwitcher.action.hotkey"});
const static std::map<MacroActionHotkey::Action, std::string> actionTypes = {
{MacroActionHotkey::Action::OBS_HOTKEY,
"AdvSceneSwitcher.action.hotkey.type.obs"},
{MacroActionHotkey::Action::CUSTOM,
"AdvSceneSwitcher.action.hotkey.type.custom"},
};
const static std::map<obs_hotkey_registerer_type, std::string> hotkeyTypes = {
{OBS_HOTKEY_REGISTERER_FRONTEND,
"AdvSceneSwitcher.action.hotkey.type.obs.type.frontend"},
{OBS_HOTKEY_REGISTERER_SOURCE,
"AdvSceneSwitcher.action.hotkey.type.obs.type.soruce"},
{OBS_HOTKEY_REGISTERER_OUTPUT,
"AdvSceneSwitcher.action.hotkey.type.obs.type.output"},
{OBS_HOTKEY_REGISTERER_ENCODER,
"AdvSceneSwitcher.action.hotkey.type.obs.type.encoder"},
{OBS_HOTKEY_REGISTERER_SERVICE,
"AdvSceneSwitcher.action.hotkey.type.obs.type.service"},
};
static std::unordered_map<HotkeyType, long> keyTable = {
// Chars
{HotkeyType::Key_A, OBS_KEY_A},
@@ -196,7 +216,92 @@ static void InjectKeys(const std::vector<HotkeyType> &keys, int duration)
obs_hotkey_inject_event(combo, false);
}
bool MacroActionHotkey::PerformAction()
static void addNamePrefix(std::string &name, obs_hotkey_t *hotkey)
{
const auto type = obs_hotkey_get_registerer_type(hotkey);
std::string prefix;
if (type == OBS_HOTKEY_REGISTERER_SOURCE) {
auto source = reinterpret_cast<obs_weak_source_t *>(
obs_hotkey_get_registerer(hotkey));
prefix = "[" + GetWeakSourceName(source) + "] ";
} else if (type == OBS_HOTKEY_REGISTERER_OUTPUT) {
auto weakOutput = reinterpret_cast<obs_weak_output_t *>(
obs_hotkey_get_registerer(hotkey));
std::string name;
auto output = obs_weak_output_get_output(weakOutput);
if (output) {
name = obs_output_get_name(output);
obs_output_release(output);
}
prefix = "[" + name + "] ";
} else if (type == OBS_HOTKEY_REGISTERER_ENCODER) {
auto weakEncoder = reinterpret_cast<obs_weak_encoder_t *>(
obs_hotkey_get_registerer(hotkey));
std::string name;
auto encoder = obs_weak_encoder_get_encoder(weakEncoder);
if (encoder) {
name = obs_encoder_get_name(encoder);
obs_encoder_release(encoder);
}
prefix = "[" + name + "] ";
} else if (type == OBS_HOTKEY_REGISTERER_SERVICE) {
auto weakService = reinterpret_cast<obs_weak_service_t *>(
obs_hotkey_get_registerer(hotkey));
std::string name;
auto service = obs_weak_service_get_service(weakService);
if (service) {
name = obs_service_get_name(service);
obs_service_release(service);
}
prefix = "[" + name + "] ";
}
name = prefix + name;
}
static obs_hotkey_id getHotkeyIdByName(const std::string &name)
{
struct Parameters {
std::string name;
obs_hotkey_id id = OBS_INVALID_HOTKEY_ID;
} params;
auto func = [](void *param, obs_hotkey_id id, obs_hotkey_t *hotkey) {
auto params = static_cast<Parameters *>(param);
std::string name = obs_hotkey_get_name(hotkey);
addNamePrefix(name, hotkey);
if (name == params->name) {
params->id = id;
return false;
}
return true;
};
params.name = name;
obs_enum_hotkeys(func, &params);
return params.id;
}
void MacroActionHotkey::SendOBSHotkey()
{
auto id = getHotkeyIdByName(_hotkeyName);
if (id == OBS_INVALID_HOTKEY_ID) {
blog(LOG_WARNING,
"failed to get hotkey id for \"%s\" - key will not be pressed",
_hotkeyName.c_str());
return;
}
obs_queue_task(
OBS_TASK_UI,
[](void *param) {
auto id = (size_t *)param;
obs_hotkey_trigger_routed_callback(*id, false);
obs_hotkey_trigger_routed_callback(*id, true);
obs_hotkey_trigger_routed_callback(*id, false);
},
&id, true);
}
void MacroActionHotkey::SendCustomHotkey()
{
std::vector<HotkeyType> keys;
if (_leftShift) {
@@ -237,18 +342,35 @@ bool MacroActionHotkey::PerformAction()
t.detach();
}
}
}
bool MacroActionHotkey::PerformAction()
{
switch (_action) {
case advss::MacroActionHotkey::Action::OBS_HOTKEY:
SendOBSHotkey();
break;
case advss::MacroActionHotkey::Action::CUSTOM:
SendCustomHotkey();
break;
default:
break;
}
return true;
}
void MacroActionHotkey::LogAction() const
{
vblog(LOG_INFO, "sent hotkey");
vblog(LOG_INFO, "sent hotkey type %d", static_cast<int>(_action));
}
bool MacroActionHotkey::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "hotkeyType", _hotkeyType);
obs_data_set_string(obj, "hotkeyName", _hotkeyName.c_str());
obs_data_set_int(obj, "key", static_cast<int>(_key));
obs_data_set_bool(obj, "left_shift", _leftShift);
obs_data_set_bool(obj, "right_shift", _rightShift);
@@ -260,13 +382,22 @@ bool MacroActionHotkey::Save(obs_data_t *obj) const
obs_data_set_bool(obj, "right_meta", _rightMeta);
_duration.Save(obj);
obs_data_set_bool(obj, "onlyOBS", _onlySendToObs);
obs_data_set_int(obj, "version", 1);
obs_data_set_int(obj, "version", 2);
return true;
}
bool MacroActionHotkey::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
int version = obs_data_get_int(obj, "version");
if (version != 2) {
_action = Action::CUSTOM;
} else {
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
}
_hotkeyType = static_cast<obs_hotkey_registerer_type>(
obs_data_get_int(obj, "hotkeyType"));
_hotkeyName = obs_data_get_string(obj, "hotkeyName");
_key = static_cast<HotkeyType>(obs_data_get_int(obj, "key"));
_leftShift = obs_data_get_bool(obj, "left_shift");
_rightShift = obs_data_get_bool(obj, "right_shift");
@@ -276,7 +407,7 @@ bool MacroActionHotkey::Load(obs_data_t *obj)
_rightAlt = obs_data_get_bool(obj, "right_alt");
_leftMeta = obs_data_get_bool(obj, "left_meta");
_rightMeta = obs_data_get_bool(obj, "right_meta");
if (!obs_data_has_user_value(obj, "version")) {
if (version == 0) {
_duration = obs_data_get_int(obj, "duration") / 1000.0;
} else {
_duration.Load(obj);
@@ -395,9 +526,65 @@ static inline void populateKeySelection(QComboBox *list)
list->addItem("NumpadEnter");
}
static void populateActionSelection(QComboBox *list)
{
for (const auto &[_, value] : actionTypes) {
list->addItem(obs_module_text(value.c_str()));
}
}
static void populateHotkeyTypeSelection(QComboBox *list)
{
for (const auto &[_, value] : hotkeyTypes) {
list->addItem(obs_module_text(value.c_str()));
}
}
static void addNamePrefix(QString &name, obs_hotkey_t *hotkey)
{
auto temp = name.toStdString();
addNamePrefix(temp, hotkey);
name = QString::fromStdString(temp);
}
static void populateHotkeyNames(QComboBox *list,
obs_hotkey_registerer_type type)
{
struct Parameters {
QStringList names;
QStringList descriptions;
obs_hotkey_registerer_type type;
} params;
auto func = [](void *param, obs_hotkey_id, obs_hotkey_t *hotkey) {
auto params = static_cast<Parameters *>(param);
if (obs_hotkey_get_registerer_type(hotkey) == params->type) {
QString description(obs_hotkey_get_description(hotkey));
addNamePrefix(description, hotkey);
params->descriptions.append(description);
QString name(obs_hotkey_get_name(hotkey));
addNamePrefix(name, hotkey);
params->names.append(name);
}
return true;
};
params.type = type;
obs_enum_hotkeys(func, &params);
list->clear();
for (int i = 0; i < params.names.size(); i++) {
list->addItem(params.descriptions.at(i), params.names.at(i));
}
AddSelectionEntry(list,
obs_module_text("AdvSceneSwitcher.selectHotkey"));
}
MacroActionHotkeyEdit::MacroActionHotkeyEdit(
QWidget *parent, std::shared_ptr<MacroActionHotkey> entryData)
: QWidget(parent),
_actionType(new QComboBox()),
_hotkeyType(new QComboBox()),
_obsHotkeys(new QComboBox()),
_keys(new QComboBox()),
_leftShift(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.hotkey.leftShift"))),
@@ -419,10 +606,20 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
_onlySendToOBS(new QCheckBox(
obs_module_text("AdvSceneSwitcher.action.hotkey.onlyOBS"))),
_noKeyPressSimulationWarning(new QLabel(
obs_module_text("AdvSceneSwitcher.action.hotkey.disabled")))
obs_module_text("AdvSceneSwitcher.action.hotkey.disabled"))),
_entryLayout(new QHBoxLayout()),
_keyConfigLayout(new QHBoxLayout())
{
populateKeySelection(_keys);
populateActionSelection(_actionType);
populateHotkeyTypeSelection(_hotkeyType);
QWidget::connect(_actionType, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_hotkeyType, SIGNAL(currentIndexChanged(int)), this,
SLOT(HotkeyTypeChanged(int)));
QWidget::connect(_obsHotkeys, SIGNAL(currentIndexChanged(int)), this,
SLOT(OBSHotkeyChanged(int)));
QWidget::connect(_keys, SIGNAL(currentIndexChanged(int)), this,
SLOT(KeyChanged(int)));
QWidget::connect(_leftShift, SIGNAL(stateChanged(int)), this,
@@ -446,28 +643,21 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
QWidget::connect(_onlySendToOBS, SIGNAL(stateChanged(int)), this,
SLOT(OnlySendToOBSChanged(int)));
QHBoxLayout *line1Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{keys}}", _keys},
{"{{duration}}", _duration},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.hotkey.entry"),
line1Layout, widgetPlaceholders);
_entryLayout->setContentsMargins(0, 0, 0, 0);
_keyConfigLayout->setContentsMargins(0, 0, 0, 0);
_keyConfigLayout->addWidget(_leftShift);
_keyConfigLayout->addWidget(_rightShift);
_keyConfigLayout->addWidget(_leftCtrl);
_keyConfigLayout->addWidget(_rightCtrl);
_keyConfigLayout->addWidget(_leftAlt);
_keyConfigLayout->addWidget(_rightAlt);
_keyConfigLayout->addWidget(_leftMeta);
_keyConfigLayout->addWidget(_rightMeta);
_keyConfigLayout->addStretch();
QHBoxLayout *line2Layout = new QHBoxLayout;
line2Layout->addWidget(_leftShift);
line2Layout->addWidget(_rightShift);
line2Layout->addWidget(_leftCtrl);
line2Layout->addWidget(_rightCtrl);
line2Layout->addWidget(_leftAlt);
line2Layout->addWidget(_rightAlt);
line2Layout->addWidget(_leftMeta);
line2Layout->addWidget(_rightMeta);
line2Layout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(_entryLayout);
mainLayout->addLayout(_keyConfigLayout);
mainLayout->addWidget(_onlySendToOBS);
mainLayout->addWidget(_noKeyPressSimulationWarning);
setLayout(mainLayout);
@@ -479,10 +669,76 @@ MacroActionHotkeyEdit::MacroActionHotkeyEdit(
_loading = false;
}
void MacroActionHotkeyEdit::SetWarningVisibility()
void MacroActionHotkeyEdit::RepopulateOBSHotkeySelection()
{
populateHotkeyNames(_obsHotkeys, _entryData->_hotkeyType);
}
void MacroActionHotkeyEdit::SetWidgetVisibility()
{
_entryLayout->removeWidget(_actionType);
_entryLayout->removeWidget(_hotkeyType);
_entryLayout->removeWidget(_obsHotkeys);
_entryLayout->removeWidget(_keys);
_entryLayout->removeWidget(_duration);
ClearLayout(_entryLayout);
PlaceWidgets(
obs_module_text(
_entryData->_action ==
MacroActionHotkey::Action::OBS_HOTKEY
? "AdvSceneSwitcher.action.hotkey.entry.obs"
: "AdvSceneSwitcher.action.hotkey.entry.custom"),
_entryLayout,
{
{"{{actionType}}", _actionType},
{"{{hotkeyType}}", _hotkeyType},
{"{{obsHotkeys}}", _obsHotkeys},
{"{{keys}}", _keys},
{"{{duration}}", _duration},
});
_noKeyPressSimulationWarning->setVisible(!_entryData->_onlySendToObs &&
!canSimulateKeyPresses);
SetLayoutVisible(_keyConfigLayout,
_entryData->_action ==
MacroActionHotkey::Action::CUSTOM);
_duration->setVisible(_entryData->_action ==
MacroActionHotkey::Action::CUSTOM);
_keys->setVisible(_entryData->_action ==
MacroActionHotkey::Action::CUSTOM);
_onlySendToOBS->setVisible(_entryData->_action ==
MacroActionHotkey::Action::CUSTOM);
_hotkeyType->setVisible(_entryData->_action ==
MacroActionHotkey::Action::OBS_HOTKEY);
_obsHotkeys->setVisible(_entryData->_action ==
MacroActionHotkey::Action::OBS_HOTKEY);
adjustSize();
updateGeometry();
}
static QString getHotkeyDescriptionByName(const std::string &name)
{
struct Parameters {
std::string name;
QString description = "";
} params;
auto func = [](void *param, obs_hotkey_id id, obs_hotkey_t *hotkey) {
auto params = static_cast<Parameters *>(param);
std::string name = obs_hotkey_get_name(hotkey);
addNamePrefix(name, hotkey);
if (name == params->name) {
params->description =
obs_hotkey_get_description(hotkey);
addNamePrefix(params->description, hotkey);
return false;
}
return true;
};
params.name = name;
obs_enum_hotkeys(func, &params);
return params.description;
}
void MacroActionHotkeyEdit::UpdateEntryData()
@@ -491,6 +747,11 @@ void MacroActionHotkeyEdit::UpdateEntryData()
return;
}
_actionType->setCurrentIndex(static_cast<int>(_entryData->_action));
_hotkeyType->setCurrentIndex(static_cast<int>(_entryData->_hotkeyType));
RepopulateOBSHotkeySelection();
_obsHotkeys->setCurrentText(
getHotkeyDescriptionByName(_entryData->_hotkeyName));
_keys->setCurrentIndex(static_cast<int>(_entryData->_key));
_leftShift->setChecked(_entryData->_leftShift);
_rightShift->setChecked(_entryData->_rightShift);
@@ -503,7 +764,7 @@ void MacroActionHotkeyEdit::UpdateEntryData()
_duration->SetDuration(_entryData->_duration);
_onlySendToOBS->setChecked(_entryData->_onlySendToObs ||
!canSimulateKeyPresses);
SetWarningVisibility();
SetWidgetVisibility();
}
void MacroActionHotkeyEdit::LShiftChanged(int state)
@@ -604,7 +865,7 @@ void MacroActionHotkeyEdit::OnlySendToOBSChanged(int state)
auto lock = LockContext();
_entryData->_onlySendToObs = state;
SetWarningVisibility();
SetWidgetVisibility();
}
void MacroActionHotkeyEdit::KeyChanged(int key)
@@ -617,4 +878,43 @@ void MacroActionHotkeyEdit::KeyChanged(int key)
_entryData->_key = static_cast<HotkeyType>(key);
}
void MacroActionHotkeyEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionHotkey::Action>(value);
SetWidgetVisibility();
}
void MacroActionHotkeyEdit::HotkeyTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
{
auto lock = LockContext();
_entryData->_hotkeyType =
static_cast<obs_hotkey_registerer_type>(value);
}
RepopulateOBSHotkeySelection();
}
void MacroActionHotkeyEdit::OBSHotkeyChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
if (idx == -1) {
_entryData->_hotkeyName = "";
return;
}
_entryData->_hotkeyName =
_obsHotkeys->itemData(idx).toString().toStdString();
}
} // namespace advss

View File

@@ -23,7 +23,15 @@ public:
return std::make_shared<MacroActionHotkey>(m);
}
OBSWeakSource _HotkeySource;
enum class Action {
OBS_HOTKEY,
CUSTOM,
};
Action _action = Action::OBS_HOTKEY;
obs_hotkey_registerer_type _hotkeyType = OBS_HOTKEY_REGISTERER_FRONTEND;
std::string _hotkeyName;
HotkeyType _key = HotkeyType::Key_NoKey;
bool _leftShift = false;
bool _rightShift = false;
@@ -41,6 +49,9 @@ public:
#endif
private:
void SendOBSHotkey();
void SendCustomHotkey();
static bool _registered;
static const std::string id;
};
@@ -62,6 +73,9 @@ public:
}
private slots:
void ActionChanged(int key);
void HotkeyTypeChanged(int key);
void OBSHotkeyChanged(int);
void KeyChanged(int key);
void LShiftChanged(int state);
void RShiftChanged(int state);
@@ -75,6 +89,9 @@ private slots:
void OnlySendToOBSChanged(int state);
protected:
QComboBox *_actionType;
QComboBox *_hotkeyType;
QComboBox *_obsHotkeys;
QComboBox *_keys;
QCheckBox *_leftShift;
QCheckBox *_rightShift;
@@ -91,8 +108,11 @@ protected:
std::shared_ptr<MacroActionHotkey> _entryData;
private:
void SetWarningVisibility();
QHBoxLayout *_mainLayout;
void RepopulateOBSHotkeySelection();
void SetWidgetVisibility();
QHBoxLayout *_entryLayout;
QHBoxLayout *_keyConfigLayout;
bool _loading = true;
};

View File

@@ -156,6 +156,7 @@ MacroActionHttpEdit::MacroActionHttpEdit(
_timeout(new DurationSelection(this, false))
{
populateMethodSelection(_methods);
_headerList->SetMaxStringSize(4096);
QWidget::connect(_url, SIGNAL(editingFinished()), this,
SLOT(URLChanged()));

View File

@@ -11,14 +11,23 @@ bool MacroActionMacro::_registered = MacroActionFactory::Register(
{MacroActionMacro::Create, MacroActionMacroEdit::Create,
"AdvSceneSwitcher.action.macro"});
const static std::map<PerformMacroAction, std::string> actionTypes = {
{PerformMacroAction::PAUSE, "AdvSceneSwitcher.action.macro.type.pause"},
{PerformMacroAction::UNPAUSE,
const static std::map<MacroActionMacro::Action, std::string> actionTypes = {
{MacroActionMacro::Action::PAUSE,
"AdvSceneSwitcher.action.macro.type.pause"},
{MacroActionMacro::Action::UNPAUSE,
"AdvSceneSwitcher.action.macro.type.unpause"},
{PerformMacroAction::RESET_COUNTER,
{MacroActionMacro::Action::RESET_COUNTER,
"AdvSceneSwitcher.action.macro.type.resetCounter"},
{PerformMacroAction::RUN, "AdvSceneSwitcher.action.macro.type.run"},
{PerformMacroAction::STOP, "AdvSceneSwitcher.action.macro.type.stop"},
{MacroActionMacro::Action::RUN,
"AdvSceneSwitcher.action.macro.type.run"},
{MacroActionMacro::Action::STOP,
"AdvSceneSwitcher.action.macro.type.stop"},
{MacroActionMacro::Action::DISABLE_ACTION,
"AdvSceneSwitcher.action.macro.type.disableAction"},
{MacroActionMacro::Action::ENABLE_ACTION,
"AdvSceneSwitcher.action.macro.type.enableAction"},
{MacroActionMacro::Action::TOGGLE_ACTION,
"AdvSceneSwitcher.action.macro.type.toggleAction"},
};
bool MacroActionMacro::PerformAction()
@@ -29,23 +38,42 @@ bool MacroActionMacro::PerformAction()
}
switch (_action) {
case PerformMacroAction::PAUSE:
case Action::PAUSE:
macro->SetPaused();
break;
case PerformMacroAction::UNPAUSE:
case Action::UNPAUSE:
macro->SetPaused(false);
break;
case PerformMacroAction::RESET_COUNTER:
case Action::RESET_COUNTER:
macro->ResetRunCount();
break;
case PerformMacroAction::RUN:
case Action::RUN:
if (!macro->Paused()) {
macro->PerformActions();
}
break;
case PerformMacroAction::STOP:
case Action::STOP:
macro->Stop();
break;
case Action::DISABLE_ACTION:
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
false)) {
macro->Actions().at(_actionIndex - 1)->SetEnabled(false);
}
break;
case Action::ENABLE_ACTION:
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
false)) {
macro->Actions().at(_actionIndex - 1)->SetEnabled(true);
}
break;
case Action::TOGGLE_ACTION:
if (IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1,
false)) {
auto action = macro->Actions().at(_actionIndex - 1);
action->SetEnabled(!action->Enabled());
}
break;
default:
break;
}
@@ -59,23 +87,35 @@ void MacroActionMacro::LogAction() const
return;
}
switch (_action) {
case PerformMacroAction::PAUSE:
case Action::PAUSE:
vblog(LOG_INFO, "paused \"%s\"", macro->Name().c_str());
break;
case PerformMacroAction::UNPAUSE:
case Action::UNPAUSE:
vblog(LOG_INFO, "unpaused \"%s\"", macro->Name().c_str());
break;
case PerformMacroAction::RESET_COUNTER:
case Action::RESET_COUNTER:
vblog(LOG_INFO, "reset counter for \"%s\"",
macro->Name().c_str());
break;
case PerformMacroAction::RUN:
case Action::RUN:
vblog(LOG_INFO, "run nested macro \"%s\"",
macro->Name().c_str());
break;
case PerformMacroAction::STOP:
case Action::STOP:
vblog(LOG_INFO, "stopped macro \"%s\"", macro->Name().c_str());
break;
case Action::DISABLE_ACTION:
vblog(LOG_INFO, "disabled action %d of macro \"%s\"",
_actionIndex.GetValue(), macro->Name().c_str());
break;
case Action::ENABLE_ACTION:
vblog(LOG_INFO, "enabled action %d of macro \"%s\"",
_actionIndex.GetValue(), macro->Name().c_str());
break;
case Action::TOGGLE_ACTION:
vblog(LOG_INFO, "toggled action %d of macro \"%s\"",
_actionIndex.GetValue(), macro->Name().c_str());
break;
default:
break;
}
@@ -85,6 +125,7 @@ bool MacroActionMacro::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_macro.Save(obj);
_actionIndex.Save(obj, "actionIndex");
obs_data_set_int(obj, "action", static_cast<int>(_action));
return true;
}
@@ -93,7 +134,8 @@ bool MacroActionMacro::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_macro.Load(obj);
_action = static_cast<PerformMacroAction>(
_actionIndex.Load(obj, "actionIndex");
_action = static_cast<MacroActionMacro::Action>(
obs_data_get_int(obj, "action"));
return true;
}
@@ -112,25 +154,28 @@ static inline void populateActionSelection(QComboBox *list)
MacroActionMacroEdit::MacroActionMacroEdit(
QWidget *parent, std::shared_ptr<MacroActionMacro> entryData)
: QWidget(parent)
: QWidget(parent),
_macros(new MacroSelection(parent)),
_actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION)),
_actions(new QComboBox())
{
_macros = new MacroSelection(parent);
_actions = new QComboBox();
populateActionSelection(_actions);
QWidget::connect(_macros, SIGNAL(currentTextChanged(const QString &)),
this, SLOT(MacroChanged(const QString &)));
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_actionIndex,
SIGNAL(SelectionChanged(const IntVariable &)), this,
SLOT(ActionIndexChanged(const IntVariable &)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{actions}}", _actions},
{"{{macros}}", _macros},
};
auto mainLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.macro.entry"),
mainLayout, widgetPlaceholders);
mainLayout,
{{"{{actions}}", _actions},
{"{{actionIndex}}", _actionIndex},
{"{{macros}}", _macros}});
setLayout(mainLayout);
_entryData = entryData;
@@ -144,11 +189,10 @@ void MacroActionMacroEdit::UpdateEntryData()
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_actionIndex->SetValue(_entryData->_actionIndex);
_actionIndex->SetMacro(_entryData->_macro.GetMacro());
_macros->SetCurrentMacro(_entryData->_macro);
if (_entryData->_action == PerformMacroAction::RUN ||
_entryData->_action == PerformMacroAction::STOP) {
_macros->HideSelectedMacro();
}
SetWidgetVisibility();
}
void MacroActionMacroEdit::MacroChanged(const QString &text)
@@ -159,6 +203,7 @@ void MacroActionMacroEdit::MacroChanged(const QString &text)
auto lock = LockContext();
_entryData->_macro = text;
_actionIndex->SetMacro(_entryData->_macro.GetMacro());
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -170,14 +215,36 @@ void MacroActionMacroEdit::ActionChanged(int value)
}
auto lock = LockContext();
_entryData->_action = static_cast<PerformMacroAction>(value);
_entryData->_action = static_cast<MacroActionMacro::Action>(value);
SetWidgetVisibility();
}
if (_entryData->_action == PerformMacroAction::RUN ||
_entryData->_action == PerformMacroAction::STOP) {
void MacroActionMacroEdit::ActionIndexChanged(const IntVariable &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_actionIndex = value;
}
void MacroActionMacroEdit::SetWidgetVisibility()
{
if (_entryData->_action == MacroActionMacro::Action::RUN ||
_entryData->_action == MacroActionMacro::Action::STOP) {
_macros->HideSelectedMacro();
} else {
_macros->ShowAllMacros();
}
const bool isModifyingActionState =
_entryData->_action ==
MacroActionMacro::Action::DISABLE_ACTION ||
_entryData->_action ==
MacroActionMacro::Action::ENABLE_ACTION ||
_entryData->_action == MacroActionMacro::Action::TOGGLE_ACTION;
_actionIndex->setVisible(isModifyingActionState);
}
} // namespace advss

View File

@@ -1,19 +1,12 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-selection.hpp"
#include "macro-segment-selection.hpp"
#include <QHBoxLayout>
namespace advss {
enum class PerformMacroAction {
PAUSE,
UNPAUSE,
RESET_COUNTER,
RUN,
STOP,
};
class MacroActionMacro : public MacroRefAction {
public:
MacroActionMacro(Macro *m) : MacroAction(m), MacroRefAction(m) {}
@@ -28,7 +21,18 @@ public:
return std::make_shared<MacroActionMacro>(m);
}
PerformMacroAction _action = PerformMacroAction::PAUSE;
enum class Action {
PAUSE,
UNPAUSE,
RESET_COUNTER,
RUN,
STOP,
DISABLE_ACTION,
ENABLE_ACTION,
TOGGLE_ACTION,
};
Action _action = Action::PAUSE;
IntVariable _actionIndex = 1;
private:
static bool _registered;
@@ -54,16 +58,19 @@ public:
private slots:
void MacroChanged(const QString &text);
void ActionChanged(int value);
void ActionIndexChanged(const IntVariable &value);
signals:
void HeaderInfoChanged(const QString &);
protected:
MacroSelection *_macros;
MacroSegmentSelection *_actionIndex;
QComboBox *_actions;
std::shared_ptr<MacroActionMacro> _entryData;
private:
QHBoxLayout *_mainLayout;
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -0,0 +1,176 @@
#include "macro-action-scene-lock.hpp"
#include "utility.hpp"
namespace advss {
const std::string MacroActionSceneLock::id = "scene_lock";
bool MacroActionSceneLock::_registered = MacroActionFactory::Register(
MacroActionSceneLock::id,
{MacroActionSceneLock::Create, MacroActionSceneLockEdit::Create,
"AdvSceneSwitcher.action.sceneLock"});
const static std::map<MacroActionSceneLock::Action, std::string> actionTypes = {
{MacroActionSceneLock::Action::LOCK,
"AdvSceneSwitcher.action.sceneLock.type.lock"},
{MacroActionSceneLock::Action::UNLOCK,
"AdvSceneSwitcher.action.sceneLock.type.unlock"},
{MacroActionSceneLock::Action::TOGGLE,
"AdvSceneSwitcher.action.sceneLock.type.toggle"},
};
static void setSceneItemLock(obs_sceneitem_t *item,
MacroActionSceneLock::Action action)
{
switch (action) {
case MacroActionSceneLock::Action::LOCK:
obs_sceneitem_set_locked(item, true);
break;
case MacroActionSceneLock::Action::UNLOCK:
obs_sceneitem_set_locked(item, false);
break;
case MacroActionSceneLock::Action::TOGGLE:
obs_sceneitem_set_locked(item, !obs_sceneitem_locked(item));
break;
}
}
bool MacroActionSceneLock::PerformAction()
{
auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) {
setSceneItemLock(item, _action);
}
return true;
}
void MacroActionSceneLock::LogAction() const
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
vblog(LOG_INFO,
"performed action \"%s\" for source \"%s\" on scene \"%s\"",
it->second.c_str(), _source.ToString(true).c_str(),
_scene.ToString(true).c_str());
} else {
blog(LOG_WARNING, "ignored unknown scene lock action %d",
static_cast<int>(_action));
}
}
bool MacroActionSceneLock::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_scene.Save(obj);
_source.Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
return true;
}
bool MacroActionSceneLock::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_scene.Load(obj);
_source.Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
return true;
}
std::string MacroActionSceneLock::GetShortDesc() const
{
if (!_source.ToString().empty()) {
return _scene.ToString() + " - " + _source.ToString();
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (const auto &[_, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
MacroActionSceneLockEdit::MacroActionSceneLockEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneLock> entryData)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window(), true, false, true, true)),
_sources(new SceneItemSelectionWidget(parent)),
_actions(new QComboBox())
{
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
_sources, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(const SceneItemSelection &)));
auto layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{actions}}", _actions},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.sceneLock.entry"),
layout, widgetPlaceholders);
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroActionSceneLockEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem((_entryData->_source));
}
void MacroActionSceneLockEdit::SceneChanged(const SceneSelection &s)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_scene = s;
}
void MacroActionSceneLockEdit::SourceChanged(const SceneItemSelection &item)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroActionSceneLockEdit::ActionChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionSceneLock::Action>(value);
}
} // namespace advss

View File

@@ -0,0 +1,69 @@
#pragma once
#include "macro-action-edit.hpp"
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
namespace advss {
class MacroActionSceneLock : public MacroAction {
public:
MacroActionSceneLock(Macro *m) : MacroAction(m) {}
bool PerformAction();
void LogAction() const;
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroAction> Create(Macro *m)
{
return std::make_shared<MacroActionSceneLock>(m);
}
enum class Action {
LOCK,
UNLOCK,
TOGGLE,
};
Action _action = Action::LOCK;
SceneSelection _scene;
SceneItemSelection _source;
private:
static bool _registered;
static const std::string id;
};
class MacroActionSceneLockEdit : public QWidget {
Q_OBJECT
public:
MacroActionSceneLockEdit(
QWidget *parent,
std::shared_ptr<MacroActionSceneLock> entryData = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroAction> action)
{
return new MacroActionSceneLockEdit(
parent, std::dynamic_pointer_cast<MacroActionSceneLock>(
action));
}
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const SceneItemSelection &);
void ActionChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
SceneItemSelectionWidget *_sources;
QComboBox *_actions;
std::shared_ptr<MacroActionSceneLock> _entryData;
private:
bool _loading = true;
};
} // namespace advss

View File

@@ -23,7 +23,7 @@ const static std::map<SceneOrderAction, std::string> actionTypes = {
"AdvSceneSwitcher.action.sceneOrder.type.movePosition"},
};
static void moveSceneItemsUp(std::vector<obs_scene_item *> &items)
static void moveSceneItemsUp(std::vector<OBSSceneItem> &items)
{
// In the case of the same source being in two sequential positions
// moving the sources up will cause the sources to swap positions due to
@@ -33,41 +33,36 @@ static void moveSceneItemsUp(std::vector<obs_scene_item *> &items)
// will be moved up.
std::reverse(items.begin(), items.end());
for (auto &i : items) {
for (const auto &i : items) {
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_UP);
obs_sceneitem_release(i);
}
}
static void moveSceneItemsDown(std::vector<obs_scene_item *> &items)
static void moveSceneItemsDown(std::vector<OBSSceneItem> &items)
{
for (auto &i : items) {
for (const auto &i : items) {
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_DOWN);
obs_sceneitem_release(i);
}
}
static void moveSceneItemsTop(std::vector<obs_scene_item *> &items)
static void moveSceneItemsTop(std::vector<OBSSceneItem> &items)
{
for (auto &i : items) {
for (const auto &i : items) {
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_TOP);
obs_sceneitem_release(i);
}
}
static void moveSceneItemsBottom(std::vector<obs_scene_item *> &items)
static void moveSceneItemsBottom(std::vector<OBSSceneItem> &items)
{
for (auto &i : items) {
for (const auto &i : items) {
obs_sceneitem_set_order(i, OBS_ORDER_MOVE_BOTTOM);
obs_sceneitem_release(i);
}
}
static void moveSceneItemsPos(std::vector<obs_scene_item *> &items, int pos)
static void moveSceneItemsPos(std::vector<OBSSceneItem> &items, int pos)
{
for (auto &i : items) {
for (const auto &i : items) {
obs_sceneitem_set_order_position(i, pos);
obs_sceneitem_release(i);
}
}
@@ -149,7 +144,7 @@ std::string MacroActionSceneOrder::GetShortDesc() const
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
for (const auto &entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
@@ -228,6 +223,8 @@ void MacroActionSceneOrderEdit::SourceChanged(const SceneItemSelection &item)
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroActionSceneOrderEdit::ActionChanged(int value)

View File

@@ -20,6 +20,9 @@ static void waitForTransitionChange(OBSWeakSource &transition,
{
const auto time = 100ms;
obs_source_t *source = obs_weak_source_get_source(transition);
if (!source) {
return;
}
bool stillTransitioning = true;
while (stillTransitioning && !switcher->abortMacroWait &&

View File

@@ -1,6 +1,8 @@
#include "macro-action-scene-transform.hpp"
#include "utility.hpp"
#include <graphics/matrix4.h>
namespace advss {
const std::string MacroActionSceneTransform::id = "scene_transform";
@@ -11,18 +13,242 @@ bool MacroActionSceneTransform::_registered = MacroActionFactory::Register(
MacroActionSceneTransformEdit::Create,
"AdvSceneSwitcher.action.sceneTransform"});
bool MacroActionSceneTransform::PerformAction()
{
ApplySettings(_settings); // Resolves variables
const static std::map<MacroActionSceneTransform::Action, std::string> actions = {
{MacroActionSceneTransform::Action::MANUAL_TRANSFORM,
"AdvSceneSwitcher.action.sceneTransform.type.manual"},
{MacroActionSceneTransform::Action::RESET,
"AdvSceneSwitcher.action.sceneTransform.type.reset"},
{MacroActionSceneTransform::Action::ROTATE,
"AdvSceneSwitcher.action.sceneTransform.type.rotate"},
{MacroActionSceneTransform::Action::FLIP_HORIZONTAL,
"AdvSceneSwitcher.action.sceneTransform.type.flipHorizontal"},
{MacroActionSceneTransform::Action::FLIP_VERTICAL,
"AdvSceneSwitcher.action.sceneTransform.type.flipVertical"},
{MacroActionSceneTransform::Action::FIT_TO_SCREEN,
"AdvSceneSwitcher.action.sceneTransform.type.fitToScreen"},
{MacroActionSceneTransform::Action::STRETCH_TO_SCREEN,
"AdvSceneSwitcher.action.sceneTransform.type.stretchToScreen"},
{MacroActionSceneTransform::Action::CENTER_TO_SCREEN,
"AdvSceneSwitcher.action.sceneTransform.type.centerToScreen"},
{MacroActionSceneTransform::Action::CENTER_VERTICALLY,
"AdvSceneSwitcher.action.sceneTransform.type.centerVertically"},
{MacroActionSceneTransform::Action::CENTER_HORIZONTALLY,
"AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally"},
};
auto items = _source.GetSceneItems(_scene);
for (auto &item : items) {
/* --------------- Helpers based on OBS window-basic-main.cpp --------------- */
static void getItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
{
matrix4 boxTransform;
obs_sceneitem_get_box_transform(item, &boxTransform);
vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
auto GetMinPos = [&](float x, float y) {
vec3 pos;
vec3_set(&pos, x, y, 0.0f);
vec3_transform(&pos, &pos, &boxTransform);
vec3_min(&tl, &tl, &pos);
vec3_max(&br, &br, &pos);
};
GetMinPos(0.0f, 0.0f);
GetMinPos(1.0f, 0.0f);
GetMinPos(0.0f, 1.0f);
GetMinPos(1.0f, 1.0f);
}
static vec3 getItemTL(obs_sceneitem_t *item)
{
vec3 tl, br;
getItemBox(item, tl, br);
return tl;
}
static void setItemTL(obs_sceneitem_t *item, const vec3 &tl)
{
vec3 newTL;
vec2 pos;
obs_sceneitem_get_pos(item, &pos);
newTL = getItemTL(item);
pos.x += tl.x - newTL.x;
pos.y += tl.y - newTL.y;
obs_sceneitem_set_pos(item, &pos);
}
static void reset(obs_sceneitem_t *item)
{
obs_transform_info info;
vec2_set(&info.pos, 0.0f, 0.0f);
vec2_set(&info.scale, 1.0f, 1.0f);
info.rot = 0.0f;
info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
info.bounds_type = OBS_BOUNDS_NONE;
info.bounds_alignment = OBS_ALIGN_CENTER;
vec2_set(&info.bounds, 0.0f, 0.0f);
obs_sceneitem_set_info(item, &info);
obs_sceneitem_crop crop = {};
obs_sceneitem_set_crop(item, &crop);
}
static void rotate(obs_sceneitem_t *item, float rot)
{
vec3 tl = getItemTL(item);
rot += obs_sceneitem_get_rot(item);
if (rot >= 360.0f) {
rot -= 360.0f;
} else if (rot <= -360.0f) {
rot += 360.0f;
}
obs_sceneitem_set_rot(item, rot);
obs_sceneitem_force_update_transform(item);
setItemTL(item, tl);
};
static void flip(obs_sceneitem_t *item, const vec2 &mul)
{
vec3 tl = getItemTL(item);
vec2 scale;
obs_sceneitem_get_scale(item, &scale);
vec2_mul(&scale, &scale, &mul);
obs_sceneitem_set_scale(item, &scale);
obs_sceneitem_force_update_transform(item);
setItemTL(item, tl);
}
static void centerAlign(obs_sceneitem_t *item, obs_bounds_type boundsType)
{
obs_video_info ovi;
obs_get_video_info(&ovi);
obs_transform_info itemInfo;
vec2_set(&itemInfo.pos, 0.0f, 0.0f);
vec2_set(&itemInfo.scale, 1.0f, 1.0f);
itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
itemInfo.rot = 0.0f;
vec2_set(&itemInfo.bounds, float(ovi.base_width),
float(ovi.base_height));
itemInfo.bounds_type = boundsType;
itemInfo.bounds_alignment = OBS_ALIGN_CENTER;
obs_sceneitem_set_info(item, &itemInfo);
}
enum class CenterType {
SCREEN,
VERTICAL,
HORIZONTAL,
};
static void center(obs_sceneitem_t *item, const CenterType &centerType)
{
// Get center x, y coordinates of items
vec3 tl, br;
getItemBox(item, tl, br);
float left = tl.x;
float top = tl.y;
float right = br.x;
float bottom = br.y;
vec3 center;
center.x = (right + left) / 2.0f;
center.y = (top + bottom) / 2.0f;
center.z = 0.0f;
// Get coordinates of screen center
obs_video_info ovi;
obs_get_video_info(&ovi);
vec3 screenCenter;
vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height),
0.0f);
vec3_mulf(&screenCenter, &screenCenter, 0.5f);
// Calculate difference between screen center and item center
vec3 offset;
vec3_sub(&offset, &screenCenter, &center);
// Shift item by offset
vec3_add(&tl, &tl, &offset);
vec3 itemTL = getItemTL(item);
if (centerType == CenterType::VERTICAL) {
tl.x = itemTL.x;
} else if (centerType == CenterType::HORIZONTAL) {
tl.y = itemTL.y;
}
setItemTL(item, tl);
}
/* ------------------------------------------------------------------------- */
void MacroActionSceneTransform::Transform(obs_scene_item *item)
{
if (!item) {
return;
}
switch (_action) {
case MacroActionSceneTransform::Action::RESET:
obs_sceneitem_defer_update_begin(item);
reset(item);
obs_sceneitem_defer_update_end(item);
break;
case MacroActionSceneTransform::Action::ROTATE:
rotate(item, _rotation);
break;
case MacroActionSceneTransform::Action::FLIP_HORIZONTAL: {
vec2 scale;
vec2_set(&scale, -1.0f, 1.0f);
flip(item, scale);
break;
}
case MacroActionSceneTransform::Action::FLIP_VERTICAL: {
vec2 scale;
vec2_set(&scale, 1.0f, -1.0f);
flip(item, scale);
break;
}
case MacroActionSceneTransform::Action::FIT_TO_SCREEN:
centerAlign(item, OBS_BOUNDS_SCALE_INNER);
break;
case MacroActionSceneTransform::Action::STRETCH_TO_SCREEN:
centerAlign(item, OBS_BOUNDS_STRETCH);
break;
case MacroActionSceneTransform::Action::CENTER_TO_SCREEN:
center(item, CenterType::SCREEN);
break;
case MacroActionSceneTransform::Action::CENTER_VERTICALLY:
center(item, CenterType::VERTICAL);
break;
case MacroActionSceneTransform::Action::CENTER_HORIZONTALLY:
center(item, CenterType::HORIZONTAL);
break;
case MacroActionSceneTransform::Action::MANUAL_TRANSFORM:
ApplySettings(_settings); // Resolves variables
obs_sceneitem_defer_update_begin(item);
obs_sceneitem_set_info(item, &_info);
obs_sceneitem_set_crop(item, &_crop);
obs_sceneitem_defer_update_end(item);
break;
}
}
obs_sceneitem_release(item);
bool MacroActionSceneTransform::PerformAction()
{
auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) {
Transform(item);
}
return true;
}
@@ -30,15 +256,18 @@ bool MacroActionSceneTransform::PerformAction()
void MacroActionSceneTransform::LogAction() const
{
vblog(LOG_INFO,
"performed transform action for source \"%s\" on scene \"%s\"",
_source.ToString(true).c_str(), _scene.ToString(true).c_str());
"performed transform action %d for source \"%s\" on scene \"%s\"",
static_cast<int>(_action), _source.ToString(true).c_str(),
_scene.ToString(true).c_str());
}
bool MacroActionSceneTransform::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_scene.Save(obj);
_source.Save(obj);
_rotation.Save(obj, "rotation");
_settings.Save(obj, "settings");
return true;
}
@@ -53,8 +282,15 @@ bool MacroActionSceneTransform::Load(obs_data_t *obj)
}
MacroAction::Load(obj);
if (!obs_data_has_user_value(obj, "action")) {
_action =
Action::MANUAL_TRANSFORM; // TODO: Remove fallback in future
} else {
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
}
_scene.Load(obj);
_source.Load(obj);
_rotation.Load(obj, "rotation");
_settings.Load(obj, "settings");
// Convert old data format
@@ -107,21 +343,34 @@ void MacroActionSceneTransform::ApplySettings(const std::string &settings)
}
obs_data_release(obj);
}
for (auto item : items) {
obs_sceneitem_release(item);
}
obs_data_release(data);
}
static inline void populateActionSelection(QComboBox *list)
{
for (const auto &[type, name] : actions) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(type));
}
}
MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneTransform> entryData)
: QWidget(parent)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window(), true, false, false, true)),
_sources(new SceneItemSelectionWidget(parent)),
_action(new QComboBox()),
_rotation(new VariableDoubleSpinBox()),
_getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.sceneTransform.getTransform"))),
_settings(new VariableTextEdit(this)),
_buttonLayout(new QHBoxLayout())
{
_scenes = new SceneSelectionWidget(window(), true, false, false, true);
_sources = new SceneItemSelectionWidget(parent);
_getSettings = new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.sceneTransform.getTransform"));
_settings = new VariableTextEdit(this);
_rotation->setMinimum(-360);
_rotation->setMaximum(360);
_rotation->setSuffix("°");
populateActionSelection(_action);
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
@@ -130,6 +379,12 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(const SceneItemSelection &)));
QWidget::connect(_action, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(
_rotation,
SIGNAL(NumberVariableChanged(const NumberVariable<double> &)),
this, SLOT(RotationChanged(const NumberVariable<double> &)));
QWidget::connect(_getSettings, SIGNAL(clicked()), this,
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
@@ -138,23 +393,21 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit(
QHBoxLayout *entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{settings}}", _settings},
{"{{getSettings}}", _getSettings},
{"{{scenes}}", _scenes}, {"{{rotation}}", _rotation},
{"{{sources}}", _sources}, {"{{action}}", _action},
{"{{settings}}", _settings}, {"{{getSettings}}", _getSettings},
};
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.action.sceneTransform.entry"),
entryLayout, widgetPlaceholders);
QHBoxLayout *buttonLayout = new QHBoxLayout;
buttonLayout->addWidget(_getSettings);
buttonLayout->addStretch();
_buttonLayout->addWidget(_getSettings);
_buttonLayout->addStretch();
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(_settings);
mainLayout->addLayout(buttonLayout);
mainLayout->addLayout(_buttonLayout);
setLayout(mainLayout);
_entryData = entryData;
@@ -170,10 +423,11 @@ void MacroActionSceneTransformEdit::UpdateEntryData()
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem(_entryData->_source);
_action->setCurrentIndex(
_action->findData(static_cast<int>(_entryData->_action)));
_rotation->SetValue(_entryData->_rotation);
_settings->setPlainText(_entryData->_settings);
adjustSize();
updateGeometry();
SetWidgetVisibility();
}
void MacroActionSceneTransformEdit::SceneChanged(const SceneSelection &s)
@@ -196,6 +450,30 @@ void MacroActionSceneTransformEdit::SourceChanged(const SceneItemSelection &item
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroActionSceneTransformEdit::ActionChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionSceneTransform::Action>(
_action->itemData(idx).toInt());
SetWidgetVisibility();
}
void MacroActionSceneTransformEdit::RotationChanged(const DoubleVariable &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_rotation = value;
}
void MacroActionSceneTransformEdit::GetSettingsClicked()
@@ -211,9 +489,6 @@ void MacroActionSceneTransformEdit::GetSettingsClicked()
auto settings = GetSceneItemTransform(items[0]);
_settings->setPlainText(FormatJsonString(settings));
for (auto item : items) {
obs_sceneitem_release(item);
}
}
void MacroActionSceneTransformEdit::SettingsChanged()
@@ -229,4 +504,19 @@ void MacroActionSceneTransformEdit::SettingsChanged()
updateGeometry();
}
void MacroActionSceneTransformEdit::SetWidgetVisibility()
{
SetLayoutVisible(
_buttonLayout,
_entryData->_action ==
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
_settings->setVisible(
_entryData->_action ==
MacroActionSceneTransform::Action::MANUAL_TRANSFORM);
_rotation->setVisible(_entryData->_action ==
MacroActionSceneTransform::Action::ROTATE);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -3,6 +3,7 @@
#include "scene-selection.hpp"
#include "scene-item-selection.hpp"
#include "variable-text-edit.hpp"
#include "variable-spinbox.hpp"
#include <QSpinBox>
@@ -22,11 +23,27 @@ public:
return std::make_shared<MacroActionSceneTransform>(m);
}
enum class Action {
RESET,
ROTATE,
FLIP_HORIZONTAL,
FLIP_VERTICAL,
FIT_TO_SCREEN,
STRETCH_TO_SCREEN,
CENTER_TO_SCREEN,
CENTER_VERTICALLY,
CENTER_HORIZONTALLY,
MANUAL_TRANSFORM = 100,
};
Action _action = Action::RESET;
SceneSelection _scene;
SceneItemSelection _source;
StringVariable _settings = "";
DoubleVariable _rotation = 90.0;
private:
void Transform(obs_scene_item *);
void ApplySettings(const std::string &);
std::string ConvertSettings();
@@ -57,19 +74,25 @@ public:
private slots:
void SceneChanged(const SceneSelection &);
void SourceChanged(const SceneItemSelection &);
void ActionChanged(int);
void RotationChanged(const NumberVariable<double> &value);
void GetSettingsClicked();
void SettingsChanged();
signals:
void HeaderInfoChanged(const QString &);
protected:
private:
void SetWidgetVisibility();
SceneSelectionWidget *_scenes;
SceneItemSelectionWidget *_sources;
QComboBox *_action;
VariableDoubleSpinBox *_rotation;
QPushButton *_getSettings;
VariableTextEdit *_settings;
std::shared_ptr<MacroActionSceneTransform> _entryData;
QHBoxLayout *_buttonLayout;
private:
std::shared_ptr<MacroActionSceneTransform> _entryData;
bool _loading = true;
};

View File

@@ -11,85 +11,38 @@ bool MacroActionSceneVisibility::_registered = MacroActionFactory::Register(
MacroActionSceneVisibilityEdit::Create,
"AdvSceneSwitcher.action.sceneVisibility"});
const static std::map<SceneVisibilityAction, std::string> actionTypes = {
{SceneVisibilityAction::SHOW,
"AdvSceneSwitcher.action.sceneVisibility.type.show"},
{SceneVisibilityAction::HIDE,
"AdvSceneSwitcher.action.sceneVisibility.type.hide"},
{SceneVisibilityAction::TOGGLE,
"AdvSceneSwitcher.action.sceneVisibility.type.toggle"},
};
const static std::map<SceneItemSourceType, std::string> sourceItemSourceTypes = {
{SceneItemSourceType::SOURCE,
"AdvSceneSwitcher.action.sceneVisibility.type.source"},
{SceneItemSourceType::SOURCE_GROUP,
"AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup"},
};
struct VisibilityData {
std::string name;
SceneVisibilityAction action;
const static std::map<MacroActionSceneVisibility::Action, std::string>
actionTypes = {
{MacroActionSceneVisibility::Action::SHOW,
"AdvSceneSwitcher.action.sceneVisibility.type.show"},
{MacroActionSceneVisibility::Action::HIDE,
"AdvSceneSwitcher.action.sceneVisibility.type.hide"},
{MacroActionSceneVisibility::Action::TOGGLE,
"AdvSceneSwitcher.action.sceneVisibility.type.toggle"},
};
static void setSceneItemVisibility(obs_sceneitem_t *item,
SceneVisibilityAction action)
MacroActionSceneVisibility::Action action)
{
switch (action) {
case SceneVisibilityAction::SHOW:
case MacroActionSceneVisibility::Action::SHOW:
obs_sceneitem_set_visible(item, true);
break;
case SceneVisibilityAction::HIDE:
case MacroActionSceneVisibility::Action::HIDE:
obs_sceneitem_set_visible(item, false);
break;
case SceneVisibilityAction::TOGGLE:
case MacroActionSceneVisibility::Action::TOGGLE:
obs_sceneitem_set_visible(item, !obs_sceneitem_visible(item));
break;
}
}
static bool visibilitySourceTypeEnum(obs_scene_t *, obs_sceneitem_t *item,
void *ptr)
{
VisibilityData *vInfo = reinterpret_cast<VisibilityData *>(ptr);
auto sourceTypeName = obs_source_get_display_name(
obs_source_get_id(obs_sceneitem_get_source(item)));
if (sourceTypeName && vInfo->name == sourceTypeName) {
setSceneItemVisibility(item, vInfo->action);
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, visibilitySourceTypeEnum, ptr);
}
return true;
}
bool MacroActionSceneVisibility::PerformAction()
{
switch (_sourceType) {
case SceneItemSourceType::SOURCE: {
auto items = _source.GetSceneItems(_scene);
for (auto item : items) {
setSceneItemVisibility(item, _action);
obs_sceneitem_release(item);
}
break;
auto items = _source.GetSceneItems(_scene);
for (const auto &item : items) {
setSceneItemVisibility(item, _action);
}
case SceneItemSourceType::SOURCE_GROUP: {
auto s = obs_weak_source_get_source(_scene.GetScene());
auto scene = obs_scene_from_source(s);
VisibilityData vInfo = {_sourceGroup, _action};
obs_scene_enum_items(scene, visibilitySourceTypeEnum, &vInfo);
obs_source_release(s);
break;
}
default:
break;
}
return true;
}
@@ -97,18 +50,11 @@ void MacroActionSceneVisibility::LogAction() const
{
auto it = actionTypes.find(_action);
if (it != actionTypes.end()) {
if (_sourceType == SceneItemSourceType::SOURCE) {
vblog(LOG_INFO,
"performed visibility action \"%s\" for source \"%s\" on scene \"%s\"",
it->second.c_str(),
_source.ToString(true).c_str(),
_scene.ToString(true).c_str());
} else {
vblog(LOG_INFO,
"performed visibility action \"%s\" for any source type \"%s\" on scene \"%s\"",
it->second.c_str(), _sourceGroup.c_str(),
_scene.ToString(true).c_str());
}
vblog(LOG_INFO,
"performed visibility action \"%s\" for source \"%s\" on scene \"%s\"",
it->second.c_str(), _source.ToString(true).c_str(),
_scene.ToString(true).c_str());
} else {
blog(LOG_WARNING, "ignored unknown SceneVisibility action %d",
static_cast<int>(_action));
@@ -119,13 +65,8 @@ bool MacroActionSceneVisibility::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
_scene.Save(obj);
if (_sourceType == SceneItemSourceType::SOURCE) {
_source.Save(obj);
} else {
obs_data_set_string(obj, "sourceGroup", _sourceGroup.c_str());
}
_source.Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
obs_data_set_int(obj, "sourceType", static_cast<int>(_sourceType));
return true;
}
@@ -136,89 +77,64 @@ bool MacroActionSceneVisibility::Load(obs_data_t *obj)
if (obs_data_has_user_value(obj, "source")) {
auto sourceName = obs_data_get_string(obj, "source");
obs_data_set_string(obj, "sceneItem", sourceName);
obs_data_set_string(obj, "sourceGroup", sourceName);
}
MacroAction::Load(obj);
_scene.Load(obj);
_source.Load(obj);
_sourceType = static_cast<SceneItemSourceType>(
obs_data_get_int(obj, "sourceType"));
_sourceGroup = obs_data_get_string(obj, "sourceGroup");
_action = static_cast<SceneVisibilityAction>(
_action = static_cast<MacroActionSceneVisibility::Action>(
obs_data_get_int(obj, "action"));
// TODO: Remove in future version
if (obs_data_get_int(obj, "sourceType") != 0) {
auto sourceGroup = obs_data_get_string(obj, "sourceGroup");
_source.SetSourceTypeSelection(sourceGroup);
}
return true;
}
std::string MacroActionSceneVisibility::GetShortDesc() const
{
if (_sourceType == SceneItemSourceType::SOURCE &&
!_source.ToString().empty()) {
if (!_source.ToString().empty()) {
return _scene.ToString() + " - " + _source.ToString();
}
if (_sourceType == SceneItemSourceType::SOURCE_GROUP &&
!_sourceGroup.empty()) {
return _scene.ToString() + " - " +
obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.type.sourceGroup") +
" " + _sourceGroup;
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
static inline void populateSourceItemTypeSelection(QComboBox *list)
{
for (auto entry : sourceItemSourceTypes) {
for (const auto &entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
MacroActionSceneVisibilityEdit::MacroActionSceneVisibilityEdit(
QWidget *parent, std::shared_ptr<MacroActionSceneVisibility> entryData)
: QWidget(parent)
: QWidget(parent),
_scenes(new SceneSelectionWidget(window(), true, false, true, true)),
_sources(new SceneItemSelectionWidget(parent)),
_actions(new QComboBox())
{
_scenes = new SceneSelectionWidget(window(), true, false, true, true);
_sourceTypes = new QComboBox();
_sources = new SceneItemSelectionWidget(parent);
_sourceGroups = new QComboBox();
_actions = new QComboBox();
populateSourceItemTypeSelection(_sourceTypes);
PopulateSourceGroupSelection(_sourceGroups);
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
this, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sourceTypes, SIGNAL(currentIndexChanged(int)), this,
SLOT(SourceTypeChanged(int)));
QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)),
_sources, SLOT(SceneChanged(const SceneSelection &)));
QWidget::connect(_sources,
SIGNAL(SceneItemChanged(const SceneItemSelection &)),
this, SLOT(SourceChanged(const SceneItemSelection &)));
QWidget::connect(_sourceGroups,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceGroupChanged(const QString &)));
QHBoxLayout *mainLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{scenes}}", _scenes}, {"{{sourceTypes}}", _sourceTypes},
{"{{sources}}", _sources}, {"{{sourceGroups}}", _sourceGroups},
{"{{actions}}", _actions},
};
auto layout = new QHBoxLayout;
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.sceneVisibility.entry"),
mainLayout, widgetPlaceholders);
setLayout(mainLayout);
layout,
{{"{{scenes}}", _scenes},
{"{{sources}}", _sources},
{"{{actions}}", _actions}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
@@ -232,13 +148,8 @@ void MacroActionSceneVisibilityEdit::UpdateEntryData()
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_sourceTypes->setCurrentIndex(
static_cast<int>(_entryData->_sourceType));
_scenes->SetScene(_entryData->_scene);
_sources->SetSceneItem((_entryData->_source));
_sourceGroups->setCurrentText(
QString::fromStdString(_entryData->_sourceGroup));
SetWidgetVisibility();
}
void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
@@ -251,17 +162,6 @@ void MacroActionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)
_entryData->_scene = s;
}
void MacroActionSceneVisibilityEdit::SourceTypeChanged(int value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_sourceType = static_cast<SceneItemSourceType>(value);
SetWidgetVisibility();
}
void MacroActionSceneVisibilityEdit::SourceChanged(
const SceneItemSelection &item)
{
@@ -273,22 +173,8 @@ void MacroActionSceneVisibilityEdit::SourceChanged(
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionSceneVisibilityEdit::SourceGroupChanged(const QString &text)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
if (text == obs_module_text("AdvSceneSwitcher.selectItem")) {
_entryData->_sourceGroup = "";
} else {
_entryData->_sourceGroup = text.toStdString();
}
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroActionSceneVisibilityEdit::ActionChanged(int value)
@@ -298,19 +184,8 @@ void MacroActionSceneVisibilityEdit::ActionChanged(int value)
}
auto lock = LockContext();
_entryData->_action = static_cast<SceneVisibilityAction>(value);
}
void MacroActionSceneVisibilityEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_sources->setVisible(_entryData->_sourceType ==
SceneItemSourceType::SOURCE);
_sourceGroups->setVisible(_entryData->_sourceType ==
SceneItemSourceType::SOURCE_GROUP);
adjustSize();
_entryData->_action =
static_cast<MacroActionSceneVisibility::Action>(value);
}
} // namespace advss

View File

@@ -7,17 +7,6 @@
namespace advss {
enum class SceneVisibilityAction {
SHOW,
HIDE,
TOGGLE,
};
enum class SceneItemSourceType {
SOURCE,
SOURCE_GROUP,
};
class MacroActionSceneVisibility : public MacroAction {
public:
MacroActionSceneVisibility(Macro *m) : MacroAction(m) {}
@@ -33,10 +22,14 @@ public:
}
SceneSelection _scene;
SceneItemSourceType _sourceType = SceneItemSourceType::SOURCE;
SceneItemSelection _source;
std::string _sourceGroup = "";
SceneVisibilityAction _action = SceneVisibilityAction::SHOW;
enum class Action {
SHOW,
HIDE,
TOGGLE,
};
Action _action = Action::SHOW;
private:
static bool _registered;
@@ -62,25 +55,18 @@ public:
private slots:
void SceneChanged(const SceneSelection &);
void SourceTypeChanged(int value);
void SourceChanged(const SceneItemSelection &);
void SourceGroupChanged(const QString &text);
void ActionChanged(int value);
signals:
void HeaderInfoChanged(const QString &);
protected:
SceneSelectionWidget *_scenes;
QComboBox *_sourceTypes;
SceneItemSelectionWidget *_sources;
QComboBox *_sourceGroups;
QComboBox *_actions;
std::shared_ptr<MacroActionSceneVisibility> _entryData;
private:
void SetWidgetVisibility();
QHBoxLayout *_mainLayout;
bool _loading = true;
};

View File

@@ -12,15 +12,50 @@ bool MacroActionSource::_registered = MacroActionFactory::Register(
{MacroActionSource::Create, MacroActionSourceEdit::Create,
"AdvSceneSwitcher.action.source"});
const static std::map<SourceAction, std::string> actionTypes = {
{SourceAction::ENABLE, "AdvSceneSwitcher.action.source.type.enable"},
{SourceAction::DISABLE, "AdvSceneSwitcher.action.source.type.disable"},
{SourceAction::SETTINGS,
const static std::map<MacroActionSource::Action, std::string> actionTypes = {
{MacroActionSource::Action::ENABLE,
"AdvSceneSwitcher.action.source.type.enable"},
{MacroActionSource::Action::DISABLE,
"AdvSceneSwitcher.action.source.type.disable"},
{MacroActionSource::Action::SETTINGS,
"AdvSceneSwitcher.action.source.type.settings"},
{SourceAction::REFRESH_SETTINGS,
{MacroActionSource::Action::REFRESH_SETTINGS,
"AdvSceneSwitcher.action.source.type.refreshSettings"},
{SourceAction::SETTINGS_BUTTON,
{MacroActionSource::Action::SETTINGS_BUTTON,
"AdvSceneSwitcher.action.source.type.pressSettingsButton"},
{MacroActionSource::Action::DEINTERLACE_MODE,
"AdvSceneSwitcher.action.source.type.deinterlaceMode"},
{MacroActionSource::Action::DEINTERLACE_FIELD_ORDER,
"AdvSceneSwitcher.action.source.type.deinterlaceOrder"},
};
const static std::map<obs_deinterlace_mode, std::string> deinterlaceModes = {
{OBS_DEINTERLACE_MODE_DISABLE,
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
{OBS_DEINTERLACE_MODE_DISCARD,
"AdvSceneSwitcher.action.source.deinterlaceMode.disable"},
{OBS_DEINTERLACE_MODE_RETRO,
"AdvSceneSwitcher.action.source.deinterlaceMode.retro"},
{OBS_DEINTERLACE_MODE_BLEND,
"AdvSceneSwitcher.action.source.deinterlaceMode.blend"},
{OBS_DEINTERLACE_MODE_BLEND_2X,
"AdvSceneSwitcher.action.source.deinterlaceMode.blend2x"},
{OBS_DEINTERLACE_MODE_LINEAR,
"AdvSceneSwitcher.action.source.deinterlaceMode.linear"},
{OBS_DEINTERLACE_MODE_LINEAR_2X,
"AdvSceneSwitcher.action.source.deinterlaceMode.linear2x"},
{OBS_DEINTERLACE_MODE_YADIF,
"AdvSceneSwitcher.action.source.deinterlaceMode.yadif"},
{OBS_DEINTERLACE_MODE_YADIF_2X,
"AdvSceneSwitcher.action.source.deinterlaceMode.yadif2x"},
};
const static std::map<obs_deinterlace_field_order, std::string>
deinterlaceFieldOrders = {
{OBS_DEINTERLACE_FIELD_ORDER_TOP,
"AdvSceneSwitcher.action.source.deinterlaceOrder.topFieldFirst"},
{OBS_DEINTERLACE_FIELD_ORDER_BOTTOM,
"AdvSceneSwitcher.action.source.deinterlaceOrder.bottomFieldFirst"},
};
static std::vector<SourceSettingButton> getSourceButtons(OBSWeakSource source)
@@ -79,21 +114,27 @@ bool MacroActionSource::PerformAction()
{
auto s = obs_weak_source_get_source(_source.GetSource());
switch (_action) {
case SourceAction::ENABLE:
case Action::ENABLE:
obs_source_set_enabled(s, true);
break;
case SourceAction::DISABLE:
case Action::DISABLE:
obs_source_set_enabled(s, false);
break;
case SourceAction::SETTINGS:
case Action::SETTINGS:
SetSourceSettings(s, _settings);
break;
case SourceAction::REFRESH_SETTINGS:
case Action::REFRESH_SETTINGS:
refreshSourceSettings(s);
break;
case SourceAction::SETTINGS_BUTTON:
case Action::SETTINGS_BUTTON:
pressSourceButton(_button, s);
break;
case Action::DEINTERLACE_MODE:
obs_source_set_deinterlace_mode(s, _deinterlaceMode);
break;
case Action::DEINTERLACE_FIELD_ORDER:
obs_source_set_deinterlace_field_order(s, _deinterlaceOrder);
break;
default:
break;
}
@@ -120,6 +161,10 @@ bool MacroActionSource::Save(obs_data_t *obj) const
obs_data_set_int(obj, "action", static_cast<int>(_action));
_button.Save(obj);
_settings.Save(obj, "settings");
obs_data_set_int(obj, "deinterlaceMode",
static_cast<int>(_deinterlaceMode));
obs_data_set_int(obj, "deinterlaceOrder",
static_cast<int>(_deinterlaceOrder));
return true;
}
@@ -127,9 +172,13 @@ bool MacroActionSource::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_source.Load(obj);
_action = static_cast<SourceAction>(obs_data_get_int(obj, "action"));
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_button.Load(obj);
_settings.Load(obj, "settings");
_deinterlaceMode = static_cast<obs_deinterlace_mode>(
obs_data_get_int(obj, "deinterlaceMode"));
_deinterlaceOrder = static_cast<obs_deinterlace_field_order>(
obs_data_get_int(obj, "deinterlaceOrder"));
return true;
}
@@ -143,7 +192,7 @@ static inline void populateActionSelection(QComboBox *list)
{
for (auto &[actionType, name] : actionTypes) {
list->addItem(obs_module_text(name.c_str()));
if (actionType == SourceAction::REFRESH_SETTINGS) {
if (actionType == MacroActionSource::Action::REFRESH_SETTINGS) {
list->setItemData(
list->count() - 1,
obs_module_text(
@@ -170,23 +219,44 @@ static inline void populateSourceButtonSelection(QComboBox *list,
}
}
static inline void populateDeinterlaceModeSelection(QComboBox *list)
{
list->clear();
for (const auto &[value, name] : deinterlaceModes) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
static inline void populateDeinterlaceFieldOrderSelection(QComboBox *list)
{
list->clear();
for (const auto &[value, name] : deinterlaceFieldOrders) {
list->addItem(obs_module_text(name.c_str()),
static_cast<int>(value));
}
}
MacroActionSourceEdit::MacroActionSourceEdit(
QWidget *parent, std::shared_ptr<MacroActionSource> entryData)
: QWidget(parent)
: QWidget(parent),
_sources(new SourceSelectionWidget(this, QStringList(), true)),
_actions(new QComboBox()),
_settingsButtons(new QComboBox()),
_getSettings(new QPushButton(obs_module_text(
"AdvSceneSwitcher.action.source.getSettings"))),
_settings(new VariableTextEdit(this)),
_deinterlaceMode(new QComboBox()),
_deinterlaceOrder(new QComboBox()),
_warning(new QLabel(
obs_module_text("AdvSceneSwitcher.action.source.warning")))
{
_sources = new SourceSelectionWidget(this, QStringList(), true);
_actions = new QComboBox();
_settingsButtons = new QComboBox();
_getSettings = new QPushButton(
obs_module_text("AdvSceneSwitcher.action.source.getSettings"));
_settings = new VariableTextEdit(this);
_warning = new QLabel(
obs_module_text("AdvSceneSwitcher.action.source.warning"));
populateActionSelection(_actions);
auto sources = GetSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
populateDeinterlaceModeSelection(_deinterlaceMode);
populateDeinterlaceFieldOrderSelection(_deinterlaceOrder);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
@@ -199,6 +269,10 @@ MacroActionSourceEdit::MacroActionSourceEdit(
SLOT(GetSettingsClicked()));
QWidget::connect(_settings, SIGNAL(textChanged()), this,
SLOT(SettingsChanged()));
QWidget::connect(_deinterlaceMode, SIGNAL(currentIndexChanged(int)),
this, SLOT(DeinterlaceModeChanged(int)));
QWidget::connect(_deinterlaceOrder, SIGNAL(currentIndexChanged(int)),
this, SLOT(DeinterlaceOrderChanged(int)));
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *entryLayout = new QHBoxLayout;
@@ -209,6 +283,8 @@ MacroActionSourceEdit::MacroActionSourceEdit(
{"{{settings}}", _settings},
{"{{getSettings}}", _getSettings},
{"{{settingsButtons}}", _settingsButtons},
{"{{deinterlaceMode}}", _deinterlaceMode},
{"{{deinterlaceOrder}}", _deinterlaceOrder},
};
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.source.entry"),
entryLayout, widgetPlaceholders);
@@ -238,10 +314,11 @@ void MacroActionSourceEdit::UpdateEntryData()
_settingsButtons->setCurrentText(
QString::fromStdString(_entryData->_button.ToString()));
_settings->setPlainText(_entryData->_settings);
_deinterlaceMode->setCurrentIndex(_deinterlaceMode->findData(
static_cast<int>(_entryData->_deinterlaceMode)));
_deinterlaceOrder->setCurrentIndex(_deinterlaceOrder->findData(
static_cast<int>(_entryData->_deinterlaceOrder)));
SetWidgetVisibility();
adjustSize();
updateGeometry();
}
void MacroActionSourceEdit::SourceChanged(const SourceSelection &source)
@@ -267,7 +344,7 @@ void MacroActionSourceEdit::ActionChanged(int value)
}
auto lock = LockContext();
_entryData->_action = static_cast<SourceAction>(value);
_entryData->_action = static_cast<MacroActionSource::Action>(value);
SetWidgetVisibility();
}
@@ -305,17 +382,50 @@ void MacroActionSourceEdit::SettingsChanged()
updateGeometry();
}
void MacroActionSourceEdit::DeinterlaceModeChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_deinterlaceMode = static_cast<obs_deinterlace_mode>(
_deinterlaceMode->itemData(idx).toInt());
}
void MacroActionSourceEdit::DeinterlaceOrderChanged(int idx)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_deinterlaceOrder =
static_cast<obs_deinterlace_field_order>(
_deinterlaceOrder->itemData(idx).toInt());
}
void MacroActionSourceEdit::SetWidgetVisibility()
{
const bool showSettings = _entryData->_action == SourceAction::SETTINGS;
const bool showWarning = _entryData->_action == SourceAction::ENABLE ||
_entryData->_action == SourceAction::DISABLE;
const bool showSettings = _entryData->_action ==
MacroActionSource::Action::SETTINGS;
const bool showWarning =
_entryData->_action == MacroActionSource::Action::ENABLE ||
_entryData->_action == MacroActionSource::Action::DISABLE;
_settings->setVisible(showSettings);
_getSettings->setVisible(showSettings);
_warning->setVisible(showWarning);
_settingsButtons->setVisible(_entryData->_action ==
SourceAction::SETTINGS_BUTTON);
_settingsButtons->setVisible(
_entryData->_action ==
MacroActionSource::Action::SETTINGS_BUTTON);
_deinterlaceMode->setVisible(
_entryData->_action ==
MacroActionSource::Action::DEINTERLACE_MODE);
_deinterlaceOrder->setVisible(
_entryData->_action ==
MacroActionSource::Action::DEINTERLACE_FIELD_ORDER);
adjustSize();
updateGeometry();
}
bool SourceSettingButton::Save(obs_data_t *obj) const

View File

@@ -10,14 +10,6 @@
namespace advss {
enum class SourceAction {
ENABLE,
DISABLE,
SETTINGS,
REFRESH_SETTINGS,
SETTINGS_BUTTON,
};
struct SourceSettingButton {
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -44,7 +36,20 @@ public:
SourceSelection _source;
SourceSettingButton _button;
StringVariable _settings = "";
SourceAction _action = SourceAction::ENABLE;
obs_deinterlace_mode _deinterlaceMode = OBS_DEINTERLACE_MODE_DISABLE;
obs_deinterlace_field_order _deinterlaceOrder =
OBS_DEINTERLACE_FIELD_ORDER_TOP;
enum class Action {
ENABLE,
DISABLE,
SETTINGS,
REFRESH_SETTINGS,
SETTINGS_BUTTON,
DEINTERLACE_MODE,
DEINTERLACE_FIELD_ORDER,
};
Action _action = Action::ENABLE;
private:
static bool _registered;
@@ -73,6 +78,8 @@ private slots:
void ButtonChanged(int idx);
void GetSettingsClicked();
void SettingsChanged();
void DeinterlaceModeChanged(int);
void DeinterlaceOrderChanged(int);
signals:
void HeaderInfoChanged(const QString &);
@@ -82,6 +89,8 @@ protected:
QComboBox *_settingsButtons;
QPushButton *_getSettings;
VariableTextEdit *_settings;
QComboBox *_deinterlaceMode;
QComboBox *_deinterlaceOrder;
QLabel *_warning;
std::shared_ptr<MacroActionSource> _entryData;

View File

@@ -19,26 +19,65 @@ const static std::map<MacroActionStream::Action, std::string> actionTypes = {
"AdvSceneSwitcher.action.streaming.type.start"},
{MacroActionStream::Action::KEYFRAME_INTERVAL,
"AdvSceneSwitcher.action.streaming.type.keyFrameInterval"},
{MacroActionStream::Action::SERVER,
"AdvSceneSwitcher.action.streaming.type.server"},
{MacroActionStream::Action::STREAM_KEY,
"AdvSceneSwitcher.action.streaming.type.streamKey"},
{MacroActionStream::Action::USERNAME,
"AdvSceneSwitcher.action.streaming.type.username"},
{MacroActionStream::Action::PASSWORD,
"AdvSceneSwitcher.action.streaming.type.password"},
};
constexpr int streamStartCooldown = 5;
std::chrono::high_resolution_clock::time_point MacroActionStream::s_lastAttempt =
std::chrono::high_resolution_clock::now();
void MacroActionStream::SetKeyFrameInterval()
void MacroActionStream::SetKeyFrameInterval() const
{
const auto configPath = GetPathInProfileDir("streamEncoder.json");
obs_data_t *settings =
auto settings =
obs_data_create_from_json_file_safe(configPath.c_str(), "bak");
obs_data_set_int(settings, "keyint_sec", _keyFrameInterval);
if (settings) {
obs_data_save_json_safe(settings, configPath.c_str(), "tmp",
"bak");
obs_data_release(settings);
if (!settings) {
blog(LOG_WARNING, "failed to set key frame interval");
return;
}
obs_data_set_int(settings, "keyint_sec", _keyFrameInterval);
obs_data_save_json_safe(settings, configPath.c_str(), "tmp", "bak");
obs_data_release(settings);
}
bool MacroActionStream::CooldownDurationReached()
void MacroActionStream::SetStreamSettingsValue(const char *name,
const std::string &value,
bool enableAuth) const
{
const auto configPath = GetPathInProfileDir("service.json");
auto data =
obs_data_create_from_json_file_safe(configPath.c_str(), "bak");
if (!data) {
blog(LOG_WARNING, "failed to set %s", name);
return;
}
auto settings = obs_data_get_obj(data, "settings");
if (!settings) {
blog(LOG_WARNING, "failed to set %s", name);
obs_data_release(data);
return;
}
obs_data_set_string(settings, name, value.c_str());
if (enableAuth) {
obs_data_set_bool(settings, "use_auth", true);
}
obs_data_set_obj(data, "settings", settings);
auto service = obs_frontend_get_streaming_service();
obs_service_update(service, settings);
obs_frontend_save_streaming_service();
obs_frontend_set_streaming_service(service);
obs_data_release(settings);
obs_data_release(data);
}
bool MacroActionStream::CooldownDurationReached() const
{
auto timePassed = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() - s_lastAttempt);
@@ -64,6 +103,18 @@ bool MacroActionStream::PerformAction()
case Action::KEYFRAME_INTERVAL:
SetKeyFrameInterval();
break;
case Action::SERVER:
SetStreamSettingsValue("server", _stringValue);
break;
case Action::STREAM_KEY:
SetStreamSettingsValue("key", _stringValue);
break;
case Action::USERNAME:
SetStreamSettingsValue("username", _stringValue, true);
break;
case Action::PASSWORD:
SetStreamSettingsValue("password", _stringValue, true);
break;
default:
break;
}
@@ -86,6 +137,7 @@ bool MacroActionStream::Save(obs_data_t *obj) const
MacroAction::Save(obj);
obs_data_set_int(obj, "action", static_cast<int>(_action));
_keyFrameInterval.Save(obj, "keyFrameInterval");
_stringValue.Save(obj, "stringValue");
return true;
}
@@ -94,6 +146,7 @@ bool MacroActionStream::Load(obs_data_t *obj)
MacroAction::Load(obj);
_action = static_cast<Action>(obs_data_get_int(obj, "action"));
_keyFrameInterval.Load(obj, "keyFrameInterval");
_stringValue.Load(obj, "stringValue");
return true;
}
@@ -108,11 +161,19 @@ MacroActionStreamEdit::MacroActionStreamEdit(
QWidget *parent, std::shared_ptr<MacroActionStream> entryData)
: QWidget(parent),
_actions(new QComboBox()),
_keyFrameInterval(new VariableSpinBox())
_keyFrameInterval(new VariableSpinBox()),
_stringValue(new VariableLineEdit(this)),
_showPassword(new QPushButton()),
_layout(new QHBoxLayout())
{
_keyFrameInterval->setMinimum(0);
_keyFrameInterval->setMaximum(25);
_showPassword->setMaximumWidth(22);
_showPassword->setFlat(true);
_showPassword->setStyleSheet(
"QPushButton { background-color: transparent; border: 0px }");
populateActionSelection(_actions);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
@@ -122,13 +183,20 @@ MacroActionStreamEdit::MacroActionStreamEdit(
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this,
SLOT(KeyFrameIntervalChanged(const NumberVariable<int> &)));
QWidget::connect(_stringValue, SIGNAL(editingFinished()), this,
SLOT(StringValueChanged()));
QWidget::connect(_showPassword, SIGNAL(pressed()), this,
SLOT(ShowPassword()));
QWidget::connect(_showPassword, SIGNAL(released()), this,
SLOT(HidePassword()));
QHBoxLayout *mainLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.streaming.entry"),
mainLayout,
_layout,
{{"{{actions}}", _actions},
{"{{keyFrameInterval}}", _keyFrameInterval}});
setLayout(mainLayout);
{"{{keyFrameInterval}}", _keyFrameInterval},
{"{{stringValue}}", _stringValue},
{"{{showPassword}}", _showPassword}});
setLayout(_layout);
_entryData = entryData;
UpdateEntryData();
@@ -142,6 +210,7 @@ void MacroActionStreamEdit::UpdateEntryData()
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_action));
_keyFrameInterval->SetValue(_entryData->_keyFrameInterval);
_stringValue->setText(_entryData->_stringValue);
SetWidgetVisiblity();
}
@@ -156,15 +225,59 @@ void MacroActionStreamEdit::KeyFrameIntervalChanged(
_entryData->_keyFrameInterval = value;
}
void MacroActionStreamEdit::StringValueChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_stringValue = _stringValue->text().toStdString();
SetWidgetVisiblity();
}
void MacroActionStreamEdit::ShowPassword()
{
SetButtonIcon(_showPassword, ":res/images/visible.svg");
_stringValue->setEchoMode(QLineEdit::Normal);
}
void MacroActionStreamEdit::HidePassword()
{
SetButtonIcon(_showPassword, ":res/images/invisible.svg");
_stringValue->setEchoMode(QLineEdit::PasswordEchoOnEdit);
}
void MacroActionStreamEdit::SetWidgetVisiblity()
{
if (!_entryData) {
return;
}
const auto action = _entryData->_action;
_keyFrameInterval->setVisible(
_entryData->_action ==
MacroActionStream::Action::KEYFRAME_INTERVAL);
action == MacroActionStream::Action::KEYFRAME_INTERVAL);
const bool modifyingStringValue =
action == MacroActionStream::Action::SERVER ||
action == MacroActionStream::Action::STREAM_KEY ||
action == MacroActionStream::Action::USERNAME ||
action == MacroActionStream::Action::PASSWORD;
_stringValue->setVisible(modifyingStringValue);
if (modifyingStringValue) {
// Give as much space to string value line edit as posssible
RemoveStretchIfPresent(_layout);
} else {
AddStretchIfNecessary(_layout);
}
if (action == MacroActionStream::Action::PASSWORD ||
action == MacroActionStream::Action::STREAM_KEY) {
_stringValue->setEchoMode(QLineEdit::PasswordEchoOnEdit);
_showPassword->show();
HidePassword();
} else {
_stringValue->setEchoMode(QLineEdit::Normal);
_showPassword->hide();
}
}
void MacroActionStreamEdit::ActionChanged(int value)
@@ -172,9 +285,12 @@ void MacroActionStreamEdit::ActionChanged(int value)
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_action = static_cast<MacroActionStream::Action>(value);
{
auto lock = LockContext();
_entryData->_action =
static_cast<MacroActionStream::Action>(value);
}
_stringValue->setText(QString(""));
SetWidgetVisiblity();
}

View File

@@ -1,6 +1,7 @@
#pragma once
#include "macro-action-edit.hpp"
#include "variable-spinbox.hpp"
#include "variable-line-edit.hpp"
#include <QDoubleSpinBox>
#include <QComboBox>
@@ -26,13 +27,20 @@ public:
STOP,
START,
KEYFRAME_INTERVAL,
SERVER,
STREAM_KEY,
USERNAME,
PASSWORD,
};
Action _action = Action::STOP;
NumberVariable<int> _keyFrameInterval = 0;
IntVariable _keyFrameInterval = 0;
StringVariable _stringValue;
private:
void SetKeyFrameInterval();
bool CooldownDurationReached();
void SetKeyFrameInterval() const;
void SetStreamSettingsValue(const char *name, const std::string &value,
bool enableAuth = false) const;
bool CooldownDurationReached() const;
static std::chrono::high_resolution_clock::time_point s_lastAttempt;
static bool _registered;
@@ -58,16 +66,21 @@ public:
private slots:
void ActionChanged(int value);
void KeyFrameIntervalChanged(const NumberVariable<int> &);
void StringValueChanged();
void ShowPassword();
void HidePassword();
protected:
QComboBox *_actions;
VariableSpinBox *_keyFrameInterval;
VariableLineEdit *_stringValue;
QPushButton *_showPassword;
std::shared_ptr<MacroActionStream> _entryData;
private:
void SetWidgetVisiblity();
QHBoxLayout *_mainLayout;
QHBoxLayout *_layout;
bool _loading = true;
};

View File

@@ -14,7 +14,7 @@ bool MacroActionTimer::_registered = MacroActionFactory::Register(
{MacroActionTimer::Create, MacroActionTimerEdit::Create,
"AdvSceneSwitcher.action.timer"});
static std::map<TimerAction, std::string> timerActions = {
const static std::map<TimerAction, std::string> timerActions = {
{TimerAction::PAUSE, "AdvSceneSwitcher.action.timer.type.pause"},
{TimerAction::CONTINUE, "AdvSceneSwitcher.action.timer.type.continue"},
{TimerAction::RESET, "AdvSceneSwitcher.action.timer.type.reset"},

View File

@@ -88,7 +88,7 @@ void MacroActionTransition::SetSourceTransition(bool show)
obs_source_release(transition);
const auto items = _source.GetSceneItems(_scene);
for (auto &item : items) {
for (const auto &item : items) {
if (_setTransitionType) {
obs_sceneitem_set_transition(item, show, t);
}
@@ -96,7 +96,6 @@ void MacroActionTransition::SetSourceTransition(bool show)
obs_sceneitem_set_transition_duration(
item, show, _duration.Milliseconds());
}
obs_sceneitem_release(item);
}
obs_source_release(t);
@@ -200,7 +199,7 @@ std::string MacroActionTransition::GetShortDesc() const
static inline void populateActionSelection(QComboBox *list)
{
for (auto entry : actionTypes) {
for (const auto &entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
@@ -298,6 +297,8 @@ void MacroActionTransitionEdit::SourceChanged(const SceneItemSelection &item)
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroActionTransitionEdit::ActionChanged(int value)

View File

@@ -4,6 +4,7 @@
#include "advanced-scene-switcher.hpp"
#include "macro.hpp"
#include "math-helpers.hpp"
#include "non-modal-dialog.hpp"
#include "utility.hpp"
namespace advss {
@@ -15,7 +16,7 @@ bool MacroActionVariable::_registered = MacroActionFactory::Register(
{MacroActionVariable::Create, MacroActionVariableEdit::Create,
"AdvSceneSwitcher.action.variable"});
static std::map<MacroActionVariable::Type, std::string> actionTypes = {
const static std::map<MacroActionVariable::Type, std::string> actionTypes = {
{MacroActionVariable::Type::SET_FIXED_VALUE,
"AdvSceneSwitcher.action.variable.type.set"},
{MacroActionVariable::Type::APPEND,
@@ -38,6 +39,8 @@ static std::map<MacroActionVariable::Type, std::string> actionTypes = {
"AdvSceneSwitcher.action.variable.type.findAndReplace"},
{MacroActionVariable::Type::MATH_EXPRESSION,
"AdvSceneSwitcher.action.variable.type.mathExpression"},
{MacroActionVariable::Type::USER_INPUT,
"AdvSceneSwitcher.action.variable.type.askForValue"},
};
static void apppend(Variable &var, const std::string &value)
@@ -121,6 +124,19 @@ void MacroActionVariable::HandleMathExpression(Variable *var)
var->SetValue(std::get<double>(result));
}
struct AskForInputParams {
QString prompt;
std::optional<std::string> result;
};
static void askForInput(void *param)
{
auto parameters = static_cast<AskForInputParams *>(param);
auto dialog = new NonModalMessageDialog(
parameters->prompt, NonModalMessageDialog::Type::INPUT);
parameters->result = dialog->GetInput();
}
bool MacroActionVariable::PerformAction()
{
auto var = _variable.lock();
@@ -189,6 +205,22 @@ bool MacroActionVariable::PerformAction()
HandleMathExpression(var.get());
return true;
}
case Type::USER_INPUT: {
AskForInputParams params{
_useCustomPrompt
? QString::fromStdString(_inputPrompt)
: QString(obs_module_text(
"AdvSceneSwitcher.action.variable.askForValuePromptDefault"))
.arg(QString::fromStdString(
var->Name())),
{}};
obs_queue_task(OBS_TASK_UI, askForInput, &params, true);
if (!params.result.has_value()) {
return false;
}
var->SetValue(*params.result);
return true;
}
}
return true;
@@ -213,6 +245,8 @@ bool MacroActionVariable::Save(obs_data_t *obj) const
obs_data_set_string(obj, "replaceStr", _replaceStr.c_str());
_regex.Save(obj);
_mathExpression.Save(obj, "mathExpression");
obs_data_set_bool(obj, "useCustomPrompt", _useCustomPrompt);
_inputPrompt.Save(obj, "inputPrompt");
return true;
}
@@ -235,6 +269,8 @@ bool MacroActionVariable::Load(obs_data_t *obj)
_findStr = obs_data_get_string(obj, "findStr");
_replaceStr = obs_data_get_string(obj, "replaceStr");
_mathExpression.Load(obj, "mathExpression");
_useCustomPrompt = obs_data_get_bool(obj, "useCustomPrompt");
_inputPrompt.Load(obj, "inputPrompt");
return true;
}
@@ -337,7 +373,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_actions(new QComboBox()),
_strValue(new ResizingPlainTextEdit(this, 5, 1, 1)),
_numValue(new QDoubleSpinBox()),
_segmentIdx(new QSpinBox()),
_segmentIdx(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::CONDITION, false)),
_segmentValueStatus(new QLabel()),
_segmentValue(new ResizingPlainTextEdit(this, 10, 1, 1)),
_substringLayout(new QVBoxLayout()),
@@ -352,13 +389,13 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_findStr(new ResizingPlainTextEdit(this, 10, 1, 1)),
_replaceStr(new ResizingPlainTextEdit(this, 10, 1, 1)),
_mathExpression(new VariableLineEdit(this)),
_mathExpressionResult(new QLabel())
_mathExpressionResult(new QLabel()),
_promptLayout(new QHBoxLayout()),
_useCustomPrompt(new QCheckBox()),
_inputPrompt(new VariableLineEdit(this))
{
_numValue->setMinimum(-9999999999);
_numValue->setMaximum(9999999999);
_segmentIdx->setMinimum(0);
_segmentIdx->setMaximum(99);
_segmentIdx->setSpecialValueText("-");
_segmentValue->setReadOnly(true);
_subStringStart->setMinimum(1);
_subStringStart->setMaximum(99999);
@@ -371,6 +408,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
_regexMatchIdx->setMinimum(1);
_regexMatchIdx->setMaximum(999);
_regexMatchIdx->setSuffix(".");
_inputPrompt->setSizePolicy(QSizePolicy::MinimumExpanding,
QSizePolicy::Preferred);
populateTypeSelection(_actions);
QWidget::connect(_variables, SIGNAL(SelectionChanged(const QString &)),
@@ -383,8 +422,9 @@ MacroActionVariableEdit::MacroActionVariableEdit(
SLOT(StrValueChanged()));
QWidget::connect(_numValue, SIGNAL(valueChanged(double)), this,
SLOT(NumValueChanged(double)));
QWidget::connect(_segmentIdx, SIGNAL(valueChanged(int)), this,
SLOT(SegmentIndexChanged(int)));
QWidget::connect(_segmentIdx,
SIGNAL(SelectionChanged(const IntVariable &)), this,
SLOT(SegmentIndexChanged(const IntVariable &)));
QWidget::connect(window(), SIGNAL(MacroSegmentOrderChanged()), this,
SLOT(MacroSegmentOrderChanged()));
QWidget::connect(_subStringStart, SIGNAL(valueChanged(int)), this,
@@ -403,6 +443,10 @@ MacroActionVariableEdit::MacroActionVariableEdit(
SLOT(ReplaceStrValueChanged()));
QWidget::connect(_mathExpression, SIGNAL(editingFinished()), this,
SLOT(MathExpressionChanged()));
QWidget::connect(_useCustomPrompt, SIGNAL(stateChanged(int)), this,
SLOT(UseCustomPromptChanged(int)));
QWidget::connect(_inputPrompt, SIGNAL(editingFinished()), this,
SLOT(InputPromptChanged()));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{variables}}", _variables},
@@ -417,6 +461,8 @@ MacroActionVariableEdit::MacroActionVariableEdit(
{"{{findStr}}", _findStr},
{"{{replaceStr}}", _replaceStr},
{"{{mathExpression}}", _mathExpression},
{"{{useCustomPrompt}}", _useCustomPrompt},
{"{{inputPrompt}}", _inputPrompt},
};
auto entryLayout = new QHBoxLayout;
PlaceWidgets(obs_module_text("AdvSceneSwitcher.action.variable.entry"),
@@ -437,6 +483,11 @@ MacroActionVariableEdit::MacroActionVariableEdit(
"AdvSceneSwitcher.action.variable.entry.findAndReplace"),
_findReplaceLayout, widgetPlaceholders, false);
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.variable.entry.userInput"),
_promptLayout, widgetPlaceholders);
auto regexConfigLayout = new QHBoxLayout;
regexConfigLayout->addWidget(_regex);
regexConfigLayout->addStretch();
@@ -453,6 +504,7 @@ MacroActionVariableEdit::MacroActionVariableEdit(
layout->addWidget(_segmentValue);
layout->addLayout(_findReplaceLayout);
layout->addWidget(_mathExpressionResult);
layout->addLayout(_promptLayout);
setLayout(layout);
_entryData = entryData;
@@ -476,7 +528,13 @@ void MacroActionVariableEdit::UpdateEntryData()
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_strValue->setPlainText(QString::fromStdString(_entryData->_strValue));
_numValue->setValue(_entryData->_numValue);
_segmentIdx->setValue(_entryData->GetSegmentIndexValue() + 1);
_segmentIdx->SetValue(_entryData->GetSegmentIndexValue() + 1);
_segmentIdx->SetMacro(_entryData->GetMacro());
_segmentIdx->SetType(
_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE
? MacroSegmentSelection::Type::CONDITION
: MacroSegmentSelection::Type::ACTION);
_subStringStart->setValue(_entryData->_subStringStart + 1);
_subStringSize->setValue(_entryData->_subStringSize);
_regex->SetRegexConfig(_entryData->_regex);
@@ -487,6 +545,8 @@ void MacroActionVariableEdit::UpdateEntryData()
_replaceStr->setPlainText(
QString::fromStdString(_entryData->_replaceStr));
_mathExpression->setText(_entryData->_mathExpression);
_useCustomPrompt->setChecked(_entryData->_useCustomPrompt);
_inputPrompt->setText(_entryData->_inputPrompt);
SetWidgetVisibility();
}
@@ -518,13 +578,14 @@ void MacroActionVariableEdit::ActionChanged(int value)
auto lock = LockContext();
_entryData->_type = static_cast<MacroActionVariable::Type>(value);
SetWidgetVisibility();
if (_entryData->_type == MacroActionVariable::Type::SET_ACTION_VALUE ||
_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE) {
MarkSelectedSegment();
if (_entryData->_type == MacroActionVariable::Type::SET_ACTION_VALUE) {
_segmentIdx->SetType(MacroSegmentSelection::Type::ACTION);
} else if (_entryData->_type ==
MacroActionVariable::Type::SET_CONDITION_VALUE) {
_segmentIdx->SetType(MacroSegmentSelection::Type::CONDITION);
}
SetWidgetVisibility();
}
void MacroActionVariableEdit::StrValueChanged()
@@ -549,7 +610,7 @@ void MacroActionVariableEdit::NumValueChanged(double val)
_entryData->_numValue = val;
}
void MacroActionVariableEdit::SegmentIndexChanged(int val)
void MacroActionVariableEdit::SegmentIndexChanged(const IntVariable &val)
{
if (_loading || !_entryData) {
return;
@@ -557,7 +618,6 @@ void MacroActionVariableEdit::SegmentIndexChanged(int val)
auto lock = LockContext();
_entryData->SetSegmentIndexValue(val - 1);
MarkSelectedSegment();
}
void MacroActionVariableEdit::SetSegmentValueError(const QString &text)
@@ -590,7 +650,7 @@ void MacroActionVariableEdit::UpdateSegmentVariableValue()
SetSegmentValueError(obs_module_text(
"AdvSceneSwitcher.action.variable.invalidSelection"));
const QSignalBlocker b(_segmentIdx);
_segmentIdx->setValue(index);
_segmentIdx->SetValue(0);
return;
}
@@ -649,7 +709,7 @@ void MacroActionVariableEdit::UpdateSegmentVariableValue()
void MacroActionVariableEdit::MacroSegmentOrderChanged()
{
const QSignalBlocker b(_segmentIdx);
_segmentIdx->setValue(_entryData->GetSegmentIndexValue() + 1);
_segmentIdx->SetValue(_entryData->GetSegmentIndexValue() + 1);
}
void MacroActionVariableEdit::SubStringStartChanged(int val)
@@ -752,40 +812,31 @@ void MacroActionVariableEdit::MathExpressionChanged()
updateGeometry();
}
void MacroActionVariableEdit::MarkSelectedSegment()
void MacroActionVariableEdit::UseCustomPromptChanged(int value)
{
if (switcher->disableHints) {
if (_loading || !_entryData) {
return;
}
auto m = _entryData->GetMacro();
if (!m) {
return;
}
int index = _entryData->GetSegmentIndexValue();
if (index < 0) {
return;
}
if (_entryData->_type == MacroActionVariable::Type::SET_ACTION_VALUE) {
const auto &actions = m->Actions();
if (index >= (int)actions.size()) {
return;
}
AdvSceneSwitcher::window->HighlightAction(
index, QColor(Qt::lightGray));
_inputPrompt->setVisible(value);
if (value) {
RemoveStretchIfPresent(_promptLayout);
} else {
const auto &conditions = m->Conditions();
if (index >= (int)conditions.size()) {
return;
}
AdvSceneSwitcher::window->HighlightCondition(
index, QColor(Qt::lightGray));
AddStretchIfNecessary(_promptLayout);
}
PulseWidget(_segmentIdx, QColor(Qt::lightGray), QColor(0, 0, 0, 0),
true);
auto lock = LockContext();
_entryData->_useCustomPrompt = value;
}
void MacroActionVariableEdit::InputPromptChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_inputPrompt = _inputPrompt->text().toStdString();
}
void MacroActionVariableEdit::SetWidgetVisibility()
@@ -833,7 +884,10 @@ void MacroActionVariableEdit::SetWidgetVisibility()
_mathExpression->setVisible(_entryData->_type ==
MacroActionVariable::Type::MATH_EXPRESSION);
_mathExpressionResult->hide();
SetLayoutVisible(_promptLayout,
_entryData->_type ==
MacroActionVariable::Type::USER_INPUT);
_inputPrompt->setVisible(_entryData->_useCustomPrompt);
adjustSize();
updateGeometry();
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-action-edit.hpp"
#include "macro-segment-selection.hpp"
#include "regex-config.hpp"
#include "resizing-text-edit.hpp"
#include "variable-line-edit.hpp"
@@ -35,6 +36,7 @@ public:
SUBSTRING,
FIND_AND_REPLACE,
MATH_EXPRESSION,
USER_INPUT,
};
Type _type = Type::SET_FIXED_VALUE;
@@ -53,6 +55,9 @@ public:
"AdvSceneSwitcher.action.variable.findAndReplace.replace");
StringVariable _mathExpression = obs_module_text(
"AdvSceneSwitcher.action.variable.mathExpression.example");
bool _useCustomPrompt = false;
StringVariable _inputPrompt = obs_module_text(
"AdvSceneSwitcher.action.variable.askForValuePrompt");
private:
void DecrementCurrentSegmentVariableRef();
@@ -89,7 +94,7 @@ private slots:
void ActionChanged(int);
void StrValueChanged();
void NumValueChanged(double);
void SegmentIndexChanged(int val);
void SegmentIndexChanged(const IntVariable &);
void UpdateSegmentVariableValue();
void MacroSegmentOrderChanged();
void SubStringStartChanged(int val);
@@ -100,6 +105,8 @@ private slots:
void FindStrValueChanged();
void ReplaceStrValueChanged();
void MathExpressionChanged();
void UseCustomPromptChanged(int);
void InputPromptChanged();
signals:
void HeaderInfoChanged(const QString &);
@@ -110,7 +117,7 @@ protected:
QComboBox *_actions;
ResizingPlainTextEdit *_strValue;
QDoubleSpinBox *_numValue;
QSpinBox *_segmentIdx;
MacroSegmentSelection *_segmentIdx;
QLabel *_segmentValueStatus;
ResizingPlainTextEdit *_segmentValue;
QVBoxLayout *_substringLayout;
@@ -126,10 +133,12 @@ protected:
ResizingPlainTextEdit *_replaceStr;
VariableLineEdit *_mathExpression;
QLabel *_mathExpressionResult;
QHBoxLayout *_promptLayout;
QCheckBox *_useCustomPrompt;
VariableLineEdit *_inputPrompt;
std::shared_ptr<MacroActionVariable> _entryData;
private:
void MarkSelectedSegment();
void SetWidgetVisibility();
void SetSegmentValueError(const QString &);

View File

@@ -13,7 +13,7 @@ bool MacroActionWait::_registered = MacroActionFactory::Register(
{MacroActionWait::Create, MacroActionWaitEdit::Create,
"AdvSceneSwitcher.action.wait"});
static std::map<WaitType, std::string> waitTypes = {
const static std::map<WaitType, std::string> waitTypes = {
{WaitType::FIXED, "AdvSceneSwitcher.action.wait.type.fixed"},
{WaitType::RANDOM, "AdvSceneSwitcher.action.wait.type.random"},
};

View File

@@ -10,47 +10,75 @@ bool MacroActionWebsocket::_registered = MacroActionFactory::Register(
{MacroActionWebsocket::Create, MacroActionWebsocketEdit::Create,
"AdvSceneSwitcher.action.websocket"});
static std::map<MacroActionWebsocket::Type, std::string> actionTypes = {
{MacroActionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.action.websocket.type.request"},
{MacroActionWebsocket::Type::EVENT,
"AdvSceneSwitcher.action.websocket.type.event"},
const static std::map<MacroActionWebsocket::API, std::string> apiTypes = {
{MacroActionWebsocket::API::SCENE_SWITCHER,
"AdvSceneSwitcher.action.websocket.api.sceneSwitcher"},
{MacroActionWebsocket::API::OBS_WEBSOCKET,
"AdvSceneSwitcher.action.websocket.api.obsWebsocket"},
{MacroActionWebsocket::API::GENERIC_WEBSOCKET,
"AdvSceneSwitcher.action.websocket.api.genericWebsocket"},
};
void MacroActionWebsocket::SendRequest()
const static std::map<MacroActionWebsocket::MessageType, std::string>
messageTypes = {
{MacroActionWebsocket::MessageType::REQUEST,
"AdvSceneSwitcher.action.websocket.type.request"},
{MacroActionWebsocket::MessageType::EVENT,
"AdvSceneSwitcher.action.websocket.type.event"},
};
void MacroActionWebsocket::SendRequest(const std::string &msg)
{
auto connection = _connection.lock();
if (!connection) {
return;
}
connection->SendMsg(_message);
connection->SendMsg(msg);
}
bool MacroActionWebsocket::PerformAction()
{
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
SendRequest();
break;
case MacroActionWebsocket::Type::EVENT:
SendWebsocketEvent(_message);
break;
default:
break;
if (_api != MacroActionWebsocket::API::SCENE_SWITCHER) {
SendRequest(_message);
return true;
}
if (_type == MacroActionWebsocket::MessageType::REQUEST) {
auto vendorRequest = ConstructVendorRequestMessage(_message);
SendRequest(vendorRequest);
} else {
SendWebsocketEvent(_message);
}
return true;
}
void MacroActionWebsocket::LogAction() const
{
if (_api == API::GENERIC_WEBSOCKET) {
vblog(LOG_INFO,
"sent generic websocket message \"%s\" via \"%s\"",
_message.c_str(),
GetWeakConnectionName(_connection).c_str());
return;
}
if (_api == API::OBS_WEBSOCKET) {
vblog(LOG_INFO, "sent obs websocket message \"%s\" via \"%s\"",
_message.c_str(),
GetWeakConnectionName(_connection).c_str());
return;
}
switch (_type) {
case MacroActionWebsocket::Type::REQUEST:
vblog(LOG_INFO, "sent msg \"%s\" via \"%s\"", _message.c_str(),
case MacroActionWebsocket::MessageType::REQUEST:
vblog(LOG_INFO, "sent scene switcher message \"%s\" via \"%s\"",
_message.c_str(),
GetWeakConnectionName(_connection).c_str());
break;
case MacroActionWebsocket::Type::EVENT:
vblog(LOG_INFO, "sent event \"%s\" to connected clients",
case MacroActionWebsocket::MessageType::EVENT:
vblog(LOG_INFO,
"sent scene switcher event \"%s\" to connected clients",
_message.c_str());
break;
default:
@@ -61,6 +89,7 @@ void MacroActionWebsocket::LogAction() const
bool MacroActionWebsocket::Save(obs_data_t *obj) const
{
MacroAction::Save(obj);
obs_data_set_int(obj, "api", static_cast<int>(_api));
obs_data_set_int(obj, "type", static_cast<int>(_type));
_message.Save(obj, "message");
obs_data_set_string(obj, "connection",
@@ -71,7 +100,8 @@ bool MacroActionWebsocket::Save(obs_data_t *obj) const
bool MacroActionWebsocket::Load(obs_data_t *obj)
{
MacroAction::Load(obj);
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_api = static_cast<API>(obs_data_get_int(obj, "api"));
_type = static_cast<MessageType>(obs_data_get_int(obj, "type"));
_message.Load(obj, "message");
_connection =
GetWeakConnectionByName(obs_data_get_string(obj, "connection"));
@@ -80,40 +110,54 @@ bool MacroActionWebsocket::Load(obs_data_t *obj)
std::string MacroActionWebsocket::GetShortDesc() const
{
if (_type == Type::REQUEST) {
if (_type == MessageType::REQUEST) {
return GetWeakConnectionName(_connection);
}
return "";
}
static inline void populateActionSelection(QComboBox *list)
static inline void populateAPISelection(QComboBox *list)
{
for (auto entry : actionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
for (const auto &[_, name] : apiTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
static inline void populateMessageTypeSelection(QComboBox *list)
{
for (const auto &[_, name] : messageTypes) {
list->addItem(obs_module_text(name.c_str()));
}
}
MacroActionWebsocketEdit::MacroActionWebsocketEdit(
QWidget *parent, std::shared_ptr<MacroActionWebsocket> entryData)
: QWidget(parent),
_actions(new QComboBox(this)),
_apiType(new QComboBox(this)),
_messageType(new QComboBox(this)),
_message(new VariableTextEdit(this)),
_connection(new ConnectionSelection(this)),
_editLayout(new QHBoxLayout())
_editLayout(new QHBoxLayout()),
_settingsConflict(new QLabel())
{
populateActionSelection(_actions);
populateAPISelection(_apiType);
populateMessageTypeSelection(_messageType);
_settingsConflict->setWordWrap(true);
QWidget::connect(_actions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ActionChanged(int)));
QWidget::connect(_apiType, SIGNAL(currentIndexChanged(int)), this,
SLOT(APITypeChanged(int)));
QWidget::connect(_messageType, SIGNAL(currentIndexChanged(int)), this,
SLOT(MessageTypeChanged(int)));
QWidget::connect(_message, SIGNAL(textChanged()), this,
SLOT(MessageChanged()));
QWidget::connect(_connection, SIGNAL(SelectionChanged(const QString &)),
this,
SLOT(ConnectionSelectionChanged(const QString &)));
QVBoxLayout *mainLayout = new QVBoxLayout;
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(_editLayout);
mainLayout->addWidget(_message);
mainLayout->addWidget(_settingsConflict);
setLayout(mainLayout);
_entryData = entryData;
@@ -121,34 +165,111 @@ MacroActionWebsocketEdit::MacroActionWebsocketEdit(
_loading = false;
}
void MacroActionWebsocketEdit::CheckForSettingsConflict()
{
auto connection = _entryData->_connection.lock();
if (!connection) {
_settingsConflict->hide();
return;
}
if (_entryData->_api == MacroActionWebsocket::API::GENERIC_WEBSOCKET &&
connection->IsUsingOBSProtocol()) {
_settingsConflict->show();
_settingsConflict->setText(obs_module_text(
"AdvSceneSwitcher.action.websocket.settingsConflictGeneric"));
} else if (_entryData->_api !=
MacroActionWebsocket::API::GENERIC_WEBSOCKET &&
!connection->IsUsingOBSProtocol()) {
_settingsConflict->show();
_settingsConflict->setText(obs_module_text(
"AdvSceneSwitcher.action.websocket.settingsConflictOBS"));
} else {
_settingsConflict->hide();
}
adjustSize();
updateGeometry();
}
void MacroActionWebsocketEdit::SetupWidgetVisibility()
{
_messageType->setVisible(_entryData->_api ==
MacroActionWebsocket::API::SCENE_SWITCHER);
switch (_entryData->_api) {
case MacroActionWebsocket::API::SCENE_SWITCHER:
if (_entryData->_type ==
MacroActionWebsocket::MessageType::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
break;
case MacroActionWebsocket::API::OBS_WEBSOCKET:
case MacroActionWebsocket::API::GENERIC_WEBSOCKET:
SetupGenericEdit();
break;
default:
break;
}
CheckForSettingsConflict();
adjustSize();
updateGeometry();
}
void MacroActionWebsocketEdit::SetupRequestEdit()
{
_editLayout->removeWidget(_actions);
_editLayout->removeWidget(_connection);
ClearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{connection}}", _connection},
};
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.request"),
_editLayout, widgetPlaceholders);
ClearWidgets();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.request"),
_editLayout,
{{"{{api}}", _apiType},
{"{{type}}", _messageType},
{"{{connection}}", _connection}});
_connection->show();
}
void MacroActionWebsocketEdit::SetupEventEdit()
{
_editLayout->removeWidget(_actions);
ClearWidgets();
PlaceWidgets(
obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.sceneSwitcher.event"),
_editLayout,
{{"{{api}}", _apiType},
{"{{type}}", _messageType},
{"{{connection}}", _connection}});
// Add widget to layout but hide it
_editLayout->addWidget(_connection);
_connection->hide();
}
void MacroActionWebsocketEdit::SetupGenericEdit()
{
ClearWidgets();
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.generic"),
_editLayout,
{{"{{api}}", _apiType},
{"{{type}}", _messageType},
{"{{connection}}", _connection}});
_connection->show();
// Add widget to layout but hide it
_editLayout->addWidget(_messageType);
_messageType->hide();
}
void MacroActionWebsocketEdit::ClearWidgets()
{
_editLayout->removeWidget(_apiType);
_editLayout->removeWidget(_messageType);
_editLayout->removeWidget(_connection);
ClearLayout(_editLayout);
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{type}}", _actions},
{"{{connection}}", _connection},
};
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.action.websocket.entry.event"),
_editLayout, widgetPlaceholders);
_connection->hide();
}
void MacroActionWebsocketEdit::UpdateEntryData()
@@ -157,33 +278,37 @@ void MacroActionWebsocketEdit::UpdateEntryData()
return;
}
_actions->setCurrentIndex(static_cast<int>(_entryData->_type));
_apiType->setCurrentIndex(static_cast<int>(_entryData->_api));
_messageType->setCurrentIndex(static_cast<int>(_entryData->_type));
_message->setPlainText(_entryData->_message);
_connection->SetConnection(_entryData->_connection);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
}
adjustSize();
updateGeometry();
SetupWidgetVisibility();
}
void MacroActionWebsocketEdit::ActionChanged(int index)
void MacroActionWebsocketEdit::APITypeChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_type = static_cast<MacroActionWebsocket::Type>(index);
if (_entryData->_type == MacroActionWebsocket::Type::REQUEST) {
SetupRequestEdit();
} else {
SetupEventEdit();
_entryData->_api = static_cast<MacroActionWebsocket::API>(index);
SetupWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroActionWebsocketEdit::MessageTypeChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_type =
static_cast<MacroActionWebsocket::MessageType>(index);
SetupWidgetVisibility();
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -210,6 +335,7 @@ void MacroActionWebsocketEdit::ConnectionSelectionChanged(
auto lock = LockContext();
_entryData->_connection = GetWeakConnectionByQString(connection);
CheckForSettingsConflict();
emit(HeaderInfoChanged(connection));
}

View File

@@ -24,17 +24,24 @@ public:
return std::make_shared<MacroActionWebsocket>(m);
}
enum class Type {
enum class API {
SCENE_SWITCHER,
OBS_WEBSOCKET,
GENERIC_WEBSOCKET,
};
enum class MessageType {
REQUEST,
EVENT,
};
Type _type = Type::REQUEST;
API _api = API::SCENE_SWITCHER;
MessageType _type = MessageType::REQUEST;
StringVariable _message = obs_module_text("AdvSceneSwitcher.enterText");
std::weak_ptr<Connection> _connection;
private:
void SendRequest();
void SendRequest(const std::string &msg);
static bool _registered;
static const std::string id;
@@ -57,7 +64,8 @@ public:
}
private slots:
void ActionChanged(int);
void APITypeChanged(int);
void MessageTypeChanged(int);
void MessageChanged();
void ConnectionSelectionChanged(const QString &);
signals:
@@ -67,13 +75,20 @@ protected:
std::shared_ptr<MacroActionWebsocket> _entryData;
private:
void CheckForSettingsConflict();
void SetupWidgetVisibility();
void SetupRequestEdit();
void SetupEventEdit();
void SetupGenericEdit();
void ClearWidgets();
QComboBox *_actions;
QComboBox *_apiType;
QComboBox *_messageType;
VariableTextEdit *_message;
ConnectionSelection *_connection;
QHBoxLayout *_editLayout;
QLabel *_settingsConflict;
bool _loading = true;
};

View File

@@ -11,12 +11,15 @@ bool MacroAction::Save(obs_data_t *obj) const
{
MacroSegment::Save(obj);
obs_data_set_string(obj, "id", GetId().c_str());
obs_data_set_bool(obj, "enabled", _enabled);
return true;
}
bool MacroAction::Load(obs_data_t *obj)
{
MacroSegment::Load(obj);
obs_data_set_default_bool(obj, "enabled", true);
_enabled = obs_data_get_bool(obj, "enabled");
return true;
}
@@ -25,6 +28,16 @@ void MacroAction::LogAction() const
vblog(LOG_INFO, "performed action %s", GetId().c_str());
}
void MacroAction::SetEnabled(bool value)
{
_enabled = value;
}
bool MacroAction::Enabled() const
{
return _enabled;
}
MacroRefAction::MacroRefAction(Macro *m, bool supportsVariableValue)
: MacroAction(m, supportsVariableValue)
{

View File

@@ -12,6 +12,11 @@ public:
virtual bool Save(obs_data_t *obj) const = 0;
virtual bool Load(obs_data_t *obj) = 0;
virtual void LogAction() const;
void SetEnabled(bool);
bool Enabled() const;
private:
bool _enabled = true;
};
class MacroRefAction : virtual public MacroAction {

View File

@@ -12,7 +12,7 @@ bool MacroConditionAudio::_registered = MacroConditionFactory::Register(
{MacroConditionAudio::Create, MacroConditionAudioEdit::Create,
"AdvSceneSwitcher.condition.audio"});
static std::map<MacroConditionAudio::Type, std::string> checkTypes = {
const static std::map<MacroConditionAudio::Type, std::string> checkTypes = {
{MacroConditionAudio::Type::OUTPUT_VOLUME,
"AdvSceneSwitcher.condition.audio.type.output"},
{MacroConditionAudio::Type::CONFIGURED_VOLUME,
@@ -25,7 +25,7 @@ static std::map<MacroConditionAudio::Type, std::string> checkTypes = {
"AdvSceneSwitcher.condition.audio.type.balance"},
};
static std::map<MacroConditionAudio::OutputCondition, std::string>
const static std::map<MacroConditionAudio::OutputCondition, std::string>
audioOutputConditionTypes = {
{MacroConditionAudio::OutputCondition::ABOVE,
"AdvSceneSwitcher.condition.audio.state.above"},
@@ -33,7 +33,7 @@ static std::map<MacroConditionAudio::OutputCondition, std::string>
"AdvSceneSwitcher.condition.audio.state.below"},
};
static std::map<MacroConditionAudio::VolumeCondition, std::string>
const static std::map<MacroConditionAudio::VolumeCondition, std::string>
audioVolumeConditionTypes = {
{MacroConditionAudio::VolumeCondition::ABOVE,
"AdvSceneSwitcher.condition.audio.state.above"},

View File

@@ -15,7 +15,7 @@ bool MacroConditionCursor::_registered = MacroConditionFactory::Register(
{MacroConditionCursor::Create, MacroConditionCursorEdit::Create,
"AdvSceneSwitcher.condition.cursor"});
static std::map<MacroConditionCursor::Condition, std::string>
const static std::map<MacroConditionCursor::Condition, std::string>
cursorConditionTypes = {
{MacroConditionCursor::Condition::REGION,
"AdvSceneSwitcher.condition.cursor.type.region"},
@@ -27,7 +27,7 @@ static std::map<MacroConditionCursor::Condition, std::string>
#endif
};
static std::map<MacroConditionCursor::Button, std::string> buttonTypes = {
const static std::map<MacroConditionCursor::Button, std::string> buttonTypes = {
{MacroConditionCursor::Button::LEFT,
"AdvSceneSwitcher.condition.cursor.button.left"},
{MacroConditionCursor::Button::MIDDLE,

View File

@@ -14,8 +14,8 @@ bool MacroConditionDate::_registered = MacroConditionFactory::Register(
{MacroConditionDate::Create, MacroConditionDateEdit::Create,
"AdvSceneSwitcher.condition.date", false});
static std::map<MacroConditionDate::Condition, std::string> dateConditionTypes =
{
const static std::map<MacroConditionDate::Condition, std::string>
dateConditionTypes = {
{MacroConditionDate::Condition::AT,
"AdvSceneSwitcher.condition.date.state.at"},
{MacroConditionDate::Condition::AFTER,
@@ -28,8 +28,8 @@ static std::map<MacroConditionDate::Condition, std::string> dateConditionTypes =
"AdvSceneSwitcher.condition.date.state.pattern"},
};
static std::map<MacroConditionDate::Condition, std::string> weekConditionTypes =
{
const static std::map<MacroConditionDate::Condition, std::string>
weekConditionTypes = {
{MacroConditionDate::Condition::AT,
"AdvSceneSwitcher.condition.date.state.at"},
{MacroConditionDate::Condition::AFTER,
@@ -38,7 +38,7 @@ static std::map<MacroConditionDate::Condition, std::string> weekConditionTypes =
"AdvSceneSwitcher.condition.date.state.before"},
};
static std::map<MacroConditionDate::Day, std::string> dayOfWeekNames = {
const static std::map<MacroConditionDate::Day, std::string> dayOfWeekNames = {
{MacroConditionDate::Day::ANY,
"AdvSceneSwitcher.condition.date.anyDay"},
{MacroConditionDate::Day::MONDAY,

View File

@@ -12,11 +12,12 @@ bool MacroConditionDisplay::_registered = MacroConditionFactory::Register(
{MacroConditionDisplay::Create, MacroConditionDisplayEdit::Create,
"AdvSceneSwitcher.condition.display"});
static std::map<MacroConditionDisplay::Condition, std::string> conditionTypes = {
{MacroConditionDisplay::Condition::DISPLAY_NAME,
"AdvSceneSwitcher.condition.display.type.displayName"},
{MacroConditionDisplay::Condition::DISPLAY_COUNT,
"AdvSceneSwitcher.condition.display.type.displayCount"},
const static std::map<MacroConditionDisplay::Condition, std::string>
conditionTypes = {
{MacroConditionDisplay::Condition::DISPLAY_NAME,
"AdvSceneSwitcher.condition.display.type.displayName"},
{MacroConditionDisplay::Condition::DISPLAY_COUNT,
"AdvSceneSwitcher.condition.display.type.displayCount"},
};
static bool matchDisplayNames(const std::string &name, const RegexConfig &regex)

View File

@@ -182,7 +182,7 @@ MacroConditionEdit::MacroConditionEdit(
: MacroSegmentEdit(switcher->macroProperties._highlightConditions,
parent),
_logicSelection(new QComboBox()),
_conditionSelection(new QComboBox()),
_conditionSelection(new FilterComboBox()),
_dur(new DurationModifierEdit()),
_entryData(entryData),
_isRoot(root)
@@ -295,13 +295,16 @@ void MacroConditionEdit::ConditionSelectionChanged(const QString &text)
return;
}
auto idx = _entryData->get()->GetIndex();
auto macro = _entryData->get()->GetMacro();
std::string id = MacroConditionFactory::GetIdByName(text);
if (id.empty()) {
return;
}
auto temp = DurationModifier();
_dur->SetValue(temp);
HeaderInfoChanged("");
auto idx = _entryData->get()->GetIndex();
auto macro = _entryData->get()->GetMacro();
{
auto lock = LockContext();
auto logic = (*_entryData)->GetLogicType();
@@ -454,24 +457,45 @@ void AdvSceneSwitcher::on_conditionRemove_clicked()
MacroConditionSelectionChanged(-1);
}
void AdvSceneSwitcher::on_conditionUp_clicked()
void AdvSceneSwitcher::on_conditionTop_clicked()
{
if (currentConditionIdx == -1) {
return;
}
MacroConditionReorder(0, currentConditionIdx);
MacroConditionSelectionChanged(0);
}
void AdvSceneSwitcher::on_conditionUp_clicked()
{
if (currentConditionIdx == -1 || currentConditionIdx == 0) {
return;
}
MoveMacroConditionUp(currentConditionIdx);
MacroConditionSelectionChanged(currentConditionIdx - 1);
}
void AdvSceneSwitcher::on_conditionDown_clicked()
{
if (currentConditionIdx == -1) {
if (currentConditionIdx == -1 ||
currentConditionIdx ==
conditionsList->ContentLayout()->count() - 1) {
return;
}
MoveMacroConditionDown(currentConditionIdx);
MacroConditionSelectionChanged(currentConditionIdx + 1);
}
void AdvSceneSwitcher::on_conditionBottom_clicked()
{
if (currentConditionIdx == -1) {
return;
}
const int newIdx = conditionsList->ContentLayout()->count() - 1;
MacroConditionReorder(newIdx, currentConditionIdx);
MacroConditionSelectionChanged(newIdx);
}
void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
{
if (pos1 == pos2) {
@@ -565,8 +589,8 @@ void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
return;
}
if (from < 0 || from > (int)macro->Conditions().size() || to < 0 ||
to > (int)macro->Conditions().size()) {
if (to == from || from < 0 || from > (int)macro->Conditions().size() ||
to < 0 || to > (int)macro->Conditions().size()) {
return;
}
{

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro-condition.hpp"
#include "filter-combo-box.hpp"
#include <memory>
@@ -77,7 +78,7 @@ private:
std::shared_ptr<MacroSegment> Data();
QComboBox *_logicSelection;
QComboBox *_conditionSelection;
FilterComboBox *_conditionSelection;
DurationModifierEdit *_dur;
std::shared_ptr<MacroCondition> *_entryData;

View File

@@ -12,7 +12,7 @@ bool MacroConditionFilter::_registered = MacroConditionFactory::Register(
{MacroConditionFilter::Create, MacroConditionFilterEdit::Create,
"AdvSceneSwitcher.condition.filter"});
static std::map<MacroConditionFilter::Condition, std::string>
const static std::map<MacroConditionFilter::Condition, std::string>
filterConditionTypes = {
{MacroConditionFilter::Condition::ENABLED,
"AdvSceneSwitcher.condition.filter.type.active"},

View File

@@ -8,7 +8,7 @@ const std::string MacroConditionHotkey::id = "hotkey";
bool MacroConditionHotkey::_registered = MacroConditionFactory::Register(
MacroConditionHotkey::id,
{MacroConditionHotkey::Create, MacroConditionHotkeyEdit::Create,
"AdvSceneSwitcher.condition.hotkey", false});
"AdvSceneSwitcher.condition.hotkey"});
static uint32_t count = 1;

View File

@@ -1,5 +1,6 @@
#include "macro-condition-macro.hpp"
#include "macro.hpp"
#include "macro-action-edit.hpp"
#include "utility.hpp"
namespace advss {
@@ -11,16 +12,21 @@ bool MacroConditionMacro::_registered = MacroConditionFactory::Register(
{MacroConditionMacro::Create, MacroConditionMacroEdit::Create,
"AdvSceneSwitcher.condition.macro"});
static std::map<MacroConditionMacro::Type, std::string> macroConditionTypes = {
{MacroConditionMacro::Type::COUNT,
"AdvSceneSwitcher.condition.macro.type.count"},
{MacroConditionMacro::Type::STATE,
"AdvSceneSwitcher.condition.macro.type.state"},
{MacroConditionMacro::Type::MULTI_STATE,
"AdvSceneSwitcher.condition.macro.type.multiState"},
const static std::map<MacroConditionMacro::Type, std::string>
macroConditionTypes = {
{MacroConditionMacro::Type::COUNT,
"AdvSceneSwitcher.condition.macro.type.count"},
{MacroConditionMacro::Type::STATE,
"AdvSceneSwitcher.condition.macro.type.state"},
{MacroConditionMacro::Type::MULTI_STATE,
"AdvSceneSwitcher.condition.macro.type.multiState"},
{MacroConditionMacro::Type::ACTION_DISABLED,
"AdvSceneSwitcher.condition.macro.type.actionDisabled"},
{MacroConditionMacro::Type::ACTION_ENABLED,
"AdvSceneSwitcher.condition.macro.type.actionEnabled"},
};
static std::map<MacroConditionMacro::CounterCondition, std::string>
const static std::map<MacroConditionMacro::CounterCondition, std::string>
counterConditionTypes = {
{MacroConditionMacro::CounterCondition::BELOW,
"AdvSceneSwitcher.condition.macro.count.type.below"},
@@ -30,7 +36,7 @@ static std::map<MacroConditionMacro::CounterCondition, std::string>
"AdvSceneSwitcher.condition.macro.count.type.equal"},
};
static std::map<MacroConditionMacro::MultiStateCondition, std::string>
const static std::map<MacroConditionMacro::MultiStateCondition, std::string>
multiStateConditionTypes = {
{MacroConditionMacro::MultiStateCondition::BELOW,
"AdvSceneSwitcher.condition.macro.state.type.below"},
@@ -83,6 +89,24 @@ bool MacroConditionMacro::CheckMultiStateCondition()
return false;
}
bool MacroConditionMacro::CheckActionStateCondition()
{
auto macro = _macro.GetMacro();
if (!macro) {
return false;
}
if (!IsValidMacroSegmentIndex(macro.get(), _actionIndex - 1, false)) {
return false;
}
if (_type == Type::ACTION_DISABLED) {
return !macro->Actions().at(_actionIndex - 1)->Enabled();
} else if (_type == Type::ACTION_ENABLED) {
return macro->Actions().at(_actionIndex - 1)->Enabled();
}
return false;
}
bool MacroConditionMacro::CheckCountCondition()
{
auto macro = _macro.GetMacro();
@@ -113,6 +137,9 @@ bool MacroConditionMacro::CheckCondition()
return CheckMultiStateCondition();
case Type::COUNT:
return CheckCountCondition();
case Type::ACTION_DISABLED:
case Type::ACTION_ENABLED:
return CheckActionStateCondition();
default:
break;
}
@@ -131,6 +158,7 @@ bool MacroConditionMacro::Save(obs_data_t *obj) const
_multiSateCount.Save(obj, "multiStateCount");
obs_data_set_int(obj, "multiStateCondition",
static_cast<int>(_multiSateCondition));
_actionIndex.Save(obj, "actionIndex");
obs_data_set_int(obj, "version", 1);
return true;
}
@@ -143,6 +171,7 @@ bool MacroConditionMacro::Load(obs_data_t *obj)
_type = static_cast<Type>(obs_data_get_int(obj, "type"));
_counterCondition = static_cast<CounterCondition>(
obs_data_get_int(obj, "condition"));
_actionIndex.Load(obj, "actionIndex");
// TODO: Remove this fallback in future version
if (!obs_data_has_user_value(obj, "multiStateCondition")) {
_multiSateCondition = MultiStateCondition::ABOVE;
@@ -210,7 +239,9 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
_settingsLine2(new QHBoxLayout()),
_macroList(new MacroList(this, false, false)),
_multiStateConditions(new QComboBox()),
_multiStateCount(new VariableSpinBox())
_multiStateCount(new VariableSpinBox()),
_actionIndex(new MacroSegmentSelection(
this, MacroSegmentSelection::Type::ACTION))
{
_count->setMaximum(10000000);
populateTypeSelection(_types);
@@ -245,6 +276,9 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this,
SLOT(MultiStateCountChanged(const NumberVariable<int> &)));
QWidget::connect(_actionIndex,
SIGNAL(SelectionChanged(const IntVariable &)), this,
SLOT(ActionIndexChanged(const IntVariable &)));
auto typesLayout = new QHBoxLayout();
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
@@ -254,6 +288,14 @@ MacroConditionMacroEdit::MacroConditionMacroEdit(
"AdvSceneSwitcher.condition.macro.type.selection"),
typesLayout, widgetPlaceholders);
_settingsLine1->addWidget(_macros);
_settingsLine1->addWidget(_counterConditions);
_settingsLine1->addWidget(_count);
_settingsLine2->addWidget(_currentCount);
_settingsLine2->addWidget(_resetCount);
_settingsLine1->addWidget(_multiStateConditions);
_settingsLine1->addWidget(_multiStateCount);
auto mainLayout = new QVBoxLayout(this);
mainLayout->addLayout(typesLayout);
mainLayout->addLayout(_settingsLine1);
@@ -286,10 +328,34 @@ void MacroConditionMacroEdit::ClearLayouts()
_settingsLine2->removeWidget(_resetCount);
_settingsLine1->removeWidget(_multiStateConditions);
_settingsLine1->removeWidget(_multiStateCount);
_settingsLine1->removeWidget(_actionIndex);
ClearLayout(_settingsLine1);
ClearLayout(_settingsLine2);
}
void MacroConditionMacroEdit::SetupWidgets()
{
switch (_entryData->_type) {
case MacroConditionMacro::Type::COUNT:
SetupCountWidgets();
break;
case MacroConditionMacro::Type::STATE:
SetupStateWidgets();
break;
case MacroConditionMacro::Type::MULTI_STATE:
SetupMultiStateWidgets();
break;
case MacroConditionMacro::Type::ACTION_DISABLED:
SetupActionStateWidgets(false);
break;
case MacroConditionMacro::Type::ACTION_ENABLED:
SetupActionStateWidgets(true);
break;
default:
break;
}
}
void MacroConditionMacroEdit::SetupStateWidgets()
{
SetWidgetVisibility();
@@ -340,43 +406,51 @@ void MacroConditionMacroEdit::SetupCountWidgets()
_settingsLine2, widgetPlaceholders);
}
void MacroConditionMacroEdit::SetupActionStateWidgets(bool enable)
{
SetWidgetVisibility();
ClearLayouts();
PlaceWidgets(
obs_module_text(
enable ? "AdvSceneSwitcher.condition.macro.actionState.enabled.entry"
: "AdvSceneSwitcher.condition.macro.actionState.disabled.entry"),
_settingsLine1,
{{"{{macros}}", _macros}, {"{{actionIndex}}", _actionIndex}});
}
void MacroConditionMacroEdit::SetWidgetVisibility()
{
switch (_entryData->_type) {
case MacroConditionMacro::Type::COUNT:
_macros->show();
_counterConditions->show();
_count->show();
_currentCount->show();
_resetCount->show();
_macroList->hide();
_multiStateConditions->hide();
_multiStateCount->hide();
break;
case MacroConditionMacro::Type::STATE:
_macros->show();
_counterConditions->hide();
_count->hide();
_currentCount->hide();
_resetCount->hide();
_macroList->hide();
_multiStateConditions->hide();
_multiStateCount->hide();
break;
case MacroConditionMacro::Type::MULTI_STATE:
_macros->hide();
_counterConditions->hide();
_count->hide();
_currentCount->hide();
_macros->setVisible(
_entryData->_type == MacroConditionMacro::Type::COUNT ||
_entryData->_type == MacroConditionMacro::Type::STATE ||
_entryData->_type ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED);
_counterConditions->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_count->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_currentCount->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_resetCount->setVisible(_entryData->_type ==
MacroConditionMacro::Type::COUNT);
_macroList->setVisible(_entryData->_type ==
MacroConditionMacro::Type::MULTI_STATE);
_multiStateConditions->setVisible(
_entryData->_type == MacroConditionMacro::Type::MULTI_STATE);
_multiStateCount->setVisible(_entryData->_type ==
MacroConditionMacro::Type::MULTI_STATE);
_actionIndex->setVisible(
_entryData->_type ==
MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED);
if (_entryData->_type == MacroConditionMacro::Type::MULTI_STATE ||
_entryData->_type == MacroConditionMacro::Type::ACTION_DISABLED ||
_entryData->_type == MacroConditionMacro::Type::ACTION_ENABLED) {
_pausedWarning->hide();
_resetCount->hide();
_macroList->show();
_multiStateConditions->show();
_multiStateCount->show();
break;
default:
break;
}
adjustSize();
updateGeometry();
}
@@ -387,20 +461,7 @@ void MacroConditionMacroEdit::UpdateEntryData()
return;
}
switch (_entryData->_type) {
case MacroConditionMacro::Type::COUNT:
SetupCountWidgets();
break;
case MacroConditionMacro::Type::STATE:
SetupStateWidgets();
break;
case MacroConditionMacro::Type::MULTI_STATE:
SetupMultiStateWidgets();
break;
default:
break;
}
SetupWidgets();
_macros->SetCurrentMacro(_entryData->_macro);
_types->setCurrentIndex(static_cast<int>(_entryData->_type));
_counterConditions->setCurrentIndex(
@@ -410,6 +471,9 @@ void MacroConditionMacroEdit::UpdateEntryData()
_multiStateConditions->setCurrentIndex(
static_cast<int>(_entryData->_multiSateCondition));
_multiStateCount->SetValue(_entryData->_multiSateCount);
_actionIndex->SetValue(_entryData->_actionIndex);
_actionIndex->SetMacro(_entryData->_macro.GetMacro());
SetWidgetVisibility();
}
void MacroConditionMacroEdit::MacroChanged(const QString &text)
@@ -420,6 +484,7 @@ void MacroConditionMacroEdit::MacroChanged(const QString &text)
auto lock = LockContext();
_entryData->_macro = text;
_actionIndex->SetMacro(_entryData->_macro.GetMacro());
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
@@ -460,6 +525,7 @@ void MacroConditionMacroEdit::MacroRemove(const QString &)
}
}
adjustSize();
updateGeometry();
}
void MacroConditionMacroEdit::TypeChanged(int type)
@@ -470,20 +536,7 @@ void MacroConditionMacroEdit::TypeChanged(int type)
auto lock = LockContext();
_entryData->_type = static_cast<MacroConditionMacro::Type>(type);
switch (_entryData->_type) {
case MacroConditionMacro::Type::COUNT:
SetupCountWidgets();
break;
case MacroConditionMacro::Type::STATE:
SetupStateWidgets();
break;
case MacroConditionMacro::Type::MULTI_STATE:
SetupMultiStateWidgets();
break;
default:
break;
}
SetupWidgets();
}
void MacroConditionMacroEdit::ResetClicked()
@@ -521,6 +574,7 @@ void MacroConditionMacroEdit::UpdatePaused()
_entryData->_type != MacroConditionMacro::Type::MULTI_STATE &&
macro && macro->Paused());
adjustSize();
updateGeometry();
}
void MacroConditionMacroEdit::MultiStateConditionChanged(int cond)
@@ -555,6 +609,7 @@ void MacroConditionMacroEdit::Add(const std::string &name)
MacroRef macro(name);
_entryData->_macros.push_back(macro);
adjustSize();
updateGeometry();
}
void MacroConditionMacroEdit::Remove(int idx)
@@ -566,6 +621,7 @@ void MacroConditionMacroEdit::Remove(int idx)
auto lock = LockContext();
_entryData->_macros.erase(std::next(_entryData->_macros.begin(), idx));
adjustSize();
updateGeometry();
}
void MacroConditionMacroEdit::Replace(int idx, const std::string &name)
@@ -578,6 +634,17 @@ void MacroConditionMacroEdit::Replace(int idx, const std::string &name)
auto lock = LockContext();
_entryData->_macros[idx] = macro;
adjustSize();
updateGeometry();
}
void MacroConditionMacroEdit::ActionIndexChanged(const IntVariable &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_actionIndex = value;
}
} // namespace advss

View File

@@ -2,7 +2,7 @@
#include "macro-condition-edit.hpp"
#include "macro-selection.hpp"
#include "macro-list.hpp"
#include "variable-spinbox.hpp"
#include "macro-segment-selection.hpp"
#include <QComboBox>
#include <QSpinBox>
@@ -37,6 +37,8 @@ public:
COUNT,
STATE,
MULTI_STATE,
ACTION_DISABLED,
ACTION_ENABLED,
};
Type _type = Type::STATE;
@@ -55,11 +57,13 @@ public:
};
MultiStateCondition _multiSateCondition = MultiStateCondition::ABOVE;
NumberVariable<int> _multiSateCount = 0;
IntVariable _actionIndex = 1;
private:
bool CheckCountCondition();
bool CheckStateCondition();
bool CheckMultiStateCondition();
bool CheckActionStateCondition();
static bool _registered;
static const std::string id;
@@ -95,6 +99,7 @@ private slots:
void Add(const std::string &);
void Remove(int);
void Replace(int, const std::string &);
void ActionIndexChanged(const IntVariable &value);
signals:
void HeaderInfoChanged(const QString &);
@@ -111,15 +116,18 @@ protected:
MacroList *_macroList;
QComboBox *_multiStateConditions;
VariableSpinBox *_multiStateCount;
MacroSegmentSelection *_actionIndex;
QTimer _countTimer;
QTimer _pausedTimer;
std::shared_ptr<MacroConditionMacro> _entryData;
private:
void ClearLayouts();
void SetupWidgets();
void SetupStateWidgets();
void SetupMultiStateWidgets();
void SetupCountWidgets();
void SetupActionStateWidgets(bool enable);
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -10,7 +10,7 @@ bool MacroConditionStats::_registered = MacroConditionFactory::Register(
{MacroConditionStats::Create, MacroConditionStatsEdit::Create,
"AdvSceneSwitcher.condition.stats"});
static std::map<MacroConditionStats::Type, std::string> statsTypes = {
const static std::map<MacroConditionStats::Type, std::string> statsTypes = {
{MacroConditionStats::Type::FPS,
"AdvSceneSwitcher.condition.stats.type.fps"},
{MacroConditionStats::Type::CPU_USAGE,
@@ -39,7 +39,7 @@ static std::map<MacroConditionStats::Type, std::string> statsTypes = {
"AdvSceneSwitcher.condition.stats.type.megabytesSent.recording"},
};
static std::map<MacroConditionStats::Condition, std::string>
const static std::map<MacroConditionStats::Condition, std::string>
statsConditionTypes = {
{MacroConditionStats::Condition::ABOVE,
"AdvSceneSwitcher.condition.stats.condition.above"},

View File

@@ -14,7 +14,7 @@ bool MacroConditionPluginState::_registered = MacroConditionFactory::Register(
MacroConditionPluginStateEdit::Create,
"AdvSceneSwitcher.condition.pluginState"});
static std::map<MacroConditionPluginState::Condition, std::string>
const static std::map<MacroConditionPluginState::Condition, std::string>
pluginStateConditionTypes = {
{MacroConditionPluginState::Condition::PLUGIN_START,
"AdvSceneSwitcher.condition.pluginState.state.start"},
@@ -112,24 +112,28 @@ static inline void populateConditionSelection(QComboBox *list)
MacroConditionPluginStateEdit::MacroConditionPluginStateEdit(
QWidget *parent, std::shared_ptr<MacroConditionPluginState> entryData)
: QWidget(parent)
: QWidget(parent),
_condition(new QComboBox()),
_shutdownLimitation(new QLabel(obs_module_text(
"AdvSceneSwitcher.condition.pluginState.state.shutdown.limitation")))
{
_condition = new QComboBox();
_shutdownLimitation->setWordWrap(true);
QWidget::connect(_condition, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
populateConditionSelection(_condition);
QHBoxLayout *switchLayout = new QHBoxLayout;
auto entryLayout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{condition}}", _condition},
};
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.pluginState.entry"),
switchLayout, widgetPlaceholders);
entryLayout, widgetPlaceholders);
entryLayout->setContentsMargins(0, 0, 0, 0);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addLayout(switchLayout);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(entryLayout);
mainLayout->addWidget(_shutdownLimitation);
setLayout(mainLayout);
_entryData = entryData;
@@ -155,6 +159,7 @@ void MacroConditionPluginStateEdit::ConditionChanged(int idx)
MacroConditionPluginState::Condition::OBS_SHUTDOWN) {
switcher->shutdownConditionCount++;
}
SetWidgetVisibility();
}
void MacroConditionPluginStateEdit::UpdateEntryData()
@@ -165,6 +170,16 @@ void MacroConditionPluginStateEdit::UpdateEntryData()
_condition->setCurrentIndex(
_condition->findData(static_cast<int>(_entryData->_condition)));
SetWidgetVisibility();
}
void MacroConditionPluginStateEdit::SetWidgetVisibility()
{
_shutdownLimitation->setVisible(
_entryData->_condition ==
MacroConditionPluginState::Condition::OBS_SHUTDOWN);
adjustSize();
updateGeometry();
}
} // namespace advss

View File

@@ -58,7 +58,10 @@ private slots:
void ConditionChanged(int idx);
protected:
void SetWidgetVisibility();
QComboBox *_condition;
QLabel *_shutdownLimitation;
std::shared_ptr<MacroConditionPluginState> _entryData;
private:

View File

@@ -10,7 +10,7 @@ bool MacroConditionRecord::_registered = MacroConditionFactory::Register(
{MacroConditionRecord::Create, MacroConditionRecordEdit::Create,
"AdvSceneSwitcher.condition.record"});
static std::map<RecordState, std::string> recordStates = {
const static std::map<RecordState, std::string> recordStates = {
{RecordState::STOP, "AdvSceneSwitcher.condition.record.state.stop"},
{RecordState::PAUSE, "AdvSceneSwitcher.condition.record.state.pause"},
{RecordState::START, "AdvSceneSwitcher.condition.record.state.start"},

View File

@@ -11,7 +11,7 @@ bool MacroConditionReplayBuffer::_registered = MacroConditionFactory::Register(
MacroConditionReplayBufferEdit::Create,
"AdvSceneSwitcher.condition.replay"});
static std::map<ReplayBufferState, std::string> ReplayBufferStates = {
const static std::map<ReplayBufferState, std::string> ReplayBufferStates = {
{ReplayBufferState::STOP,
"AdvSceneSwitcher.condition.replay.state.stopped"},
{ReplayBufferState::START,

View File

@@ -10,7 +10,7 @@ bool MacroConditionSceneOrder::_registered = MacroConditionFactory::Register(
{MacroConditionSceneOrder::Create, MacroConditionSceneOrderEdit::Create,
"AdvSceneSwitcher.condition.sceneOrder"});
static std::map<MacroConditionSceneOrder::Condition, std::string>
const static std::map<MacroConditionSceneOrder::Condition, std::string>
sceneOrderConditionTypes = {
{MacroConditionSceneOrder::Condition::ABOVE,
"AdvSceneSwitcher.condition.sceneOrder.type.above"},
@@ -29,16 +29,16 @@ struct PosInfo2 {
static bool getSceneItemPositionHelper(obs_scene_t *, obs_sceneitem_t *item,
void *ptr)
{
PosInfo2 *posInfo = reinterpret_cast<PosInfo2 *>(ptr);
auto posInfo = reinterpret_cast<PosInfo2 *>(ptr);
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, getSceneItemPositionHelper, ptr);
}
if (posInfo->item == item) {
posInfo->pos = posInfo->curPos;
return false;
}
if (obs_sceneitem_is_group(item)) {
obs_scene_t *scene = obs_sceneitem_group_get_scene(item);
obs_scene_enum_items(scene, getSceneItemPositionHelper, ptr);
}
posInfo->curPos += 1;
return true;
}
@@ -50,11 +50,11 @@ static PosInfo2 getSceneItemPos(obs_scene_item *item, obs_scene *scene)
return pos;
}
std::vector<int> getSceneItemPositions(std::vector<obs_scene_item *> &items,
obs_scene *scene)
static std::vector<int> getSceneItemPositions(std::vector<OBSSceneItem> &items,
obs_scene *scene)
{
std::vector<int> positions;
for (auto item : items) {
for (const auto &item : items) {
auto pos = getSceneItemPos(item, scene);
if (pos.pos != -1) {
positions.emplace_back(pos.pos);
@@ -106,13 +106,6 @@ bool MacroConditionSceneOrder::CheckCondition()
auto positions1 = getSceneItemPositions(items1, scene);
auto positions2 = getSceneItemPositions(items2, scene);
for (auto i : items1) {
obs_sceneitem_release(i);
}
for (auto i : items2) {
obs_sceneitem_release(i);
}
bool ret = false;
switch (_condition) {
@@ -124,8 +117,9 @@ bool MacroConditionSceneOrder::CheckCondition()
break;
case Condition::POSITION:
for (int p : positions1) {
if (p == _position)
if (p == _position) {
ret = true;
}
}
break;
default:
@@ -195,7 +189,7 @@ std::string MacroConditionSceneOrder::GetShortDesc() const
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : sceneOrderConditionTypes) {
for (const auto &entry : sceneOrderConditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
@@ -282,6 +276,8 @@ void MacroConditionSceneOrderEdit::SourceChanged(const SceneItemSelection &item)
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroConditionSceneOrderEdit::Source2Changed(const SceneItemSelection &item)
@@ -294,6 +290,8 @@ void MacroConditionSceneOrderEdit::Source2Changed(const SceneItemSelection &item
_entryData->_source2 = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroConditionSceneOrderEdit::ConditionChanged(int index)

View File

@@ -18,12 +18,11 @@ bool MacroConditionSceneTransform::CheckCondition()
auto items = _source.GetSceneItems(_scene);
std::string json;
for (auto &item : items) {
for (const auto &item : items) {
json = GetSceneItemTransform(item);
if (MatchJson(json, _settings, _regex)) {
ret = true;
}
obs_sceneitem_release(item);
}
SetVariableValue(json);
return ret;
@@ -165,6 +164,8 @@ void MacroConditionSceneTransformEdit::SourceChanged(
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroConditionSceneTransformEdit::GetSettingsClicked()
@@ -184,10 +185,6 @@ void MacroConditionSceneTransformEdit::GetSettingsClicked()
settings = EscapeForRegex(settings);
}
_settings->setPlainText(settings);
for (auto item : items) {
obs_sceneitem_release(item);
}
}
void MacroConditionSceneTransformEdit::SettingsChanged()

View File

@@ -14,7 +14,7 @@ bool MacroConditionSceneVisibility::_registered =
MacroConditionSceneVisibilityEdit::Create,
"AdvSceneSwitcher.condition.sceneVisibility"});
static std::map<MacroConditionSceneVisibility::Condition, std::string>
const static std::map<MacroConditionSceneVisibility::Condition, std::string>
conditionTypes = {
{MacroConditionSceneVisibility::Condition::SHOWN,
"AdvSceneSwitcher.condition.sceneVisibility.type.shown"},
@@ -24,32 +24,30 @@ static std::map<MacroConditionSceneVisibility::Condition, std::string>
"AdvSceneSwitcher.condition.sceneVisibility.type.changed"},
};
static bool areAllSceneItemsShown(const std::vector<obs_scene_item *> &items)
static bool areAllSceneItemsShown(const std::vector<OBSSceneItem> &items)
{
bool ret = true;
for (auto item : items) {
for (const auto &item : items) {
if (!obs_sceneitem_visible(item)) {
ret = false;
}
obs_sceneitem_release(item);
}
return ret;
}
static bool areAllSceneItemsHidden(const std::vector<obs_scene_item *> &items)
static bool areAllSceneItemsHidden(const std::vector<OBSSceneItem> &items)
{
bool ret = true;
for (auto item : items) {
for (const auto &item : items) {
if (obs_sceneitem_visible(item)) {
ret = false;
}
obs_sceneitem_release(item);
}
return ret;
}
static bool
didVisibilityOfAnySceneItemsChange(const std::vector<obs_scene_item *> &items,
didVisibilityOfAnySceneItemsChange(const std::vector<OBSSceneItem> &items,
std::vector<bool> &previousVisibility)
{
std::vector<bool> currentVisibility;
@@ -64,10 +62,6 @@ didVisibilityOfAnySceneItemsChange(const std::vector<obs_scene_item *> &items,
ret = previousVisibility != currentVisibility;
}
previousVisibility = currentVisibility;
for (const auto &item : items) {
obs_sceneitem_release(item);
}
return ret;
}
@@ -86,10 +80,10 @@ bool MacroConditionSceneVisibility::CheckCondition()
case Condition::CHANGED:
return didVisibilityOfAnySceneItemsChange(items,
_previousVisibilty);
break;
default:
break;
}
return false;
}
@@ -129,7 +123,7 @@ std::string MacroConditionSceneVisibility::GetShortDesc() const
static inline void populateConditionSelection(QComboBox *list)
{
for (auto entry : conditionTypes) {
for (const auto &entry : conditionTypes) {
list->addItem(obs_module_text(entry.second.c_str()));
}
}
@@ -183,6 +177,8 @@ void MacroConditionSceneVisibilityEdit::SourceChanged(
_entryData->_source = item;
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
adjustSize();
updateGeometry();
}
void MacroConditionSceneVisibilityEdit::SceneChanged(const SceneSelection &s)

View File

@@ -11,7 +11,7 @@ bool MacroConditionScene::_registered = MacroConditionFactory::Register(
{MacroConditionScene::Create, MacroConditionSceneEdit::Create,
"AdvSceneSwitcher.condition.scene"});
static std::map<MacroConditionScene::Type, std::string> sceneTypes = {
const static std::map<MacroConditionScene::Type, std::string> sceneTypes = {
{MacroConditionScene::Type::CURRENT,
"AdvSceneSwitcher.condition.scene.type.current"},
{MacroConditionScene::Type::PREVIOUS,

View File

@@ -0,0 +1,312 @@
#include "macro-condition-slideshow.hpp"
#include "macro.hpp"
#include "utility.hpp"
namespace advss {
const std::string MacroConditionSlideshow::id = "slideshow";
bool MacroConditionSlideshow::_registered = MacroConditionFactory::Register(
MacroConditionSlideshow::id,
{MacroConditionSlideshow::Create, MacroConditionSlideshowEdit::Create,
"AdvSceneSwitcher.condition.slideshow"});
static const std::map<MacroConditionSlideshow::Condition, std::string>
conditions = {
{MacroConditionSlideshow::Condition::SLIDE_CHANGED,
"AdvSceneSwitcher.condition.slideshow.condition.slideChanged"},
{MacroConditionSlideshow::Condition::SLIDE_INDEX,
"AdvSceneSwitcher.condition.slideshow.condition.slideIndex"},
{MacroConditionSlideshow::Condition::SLIDE_PATH,
"AdvSceneSwitcher.condition.slideshow.condition.slidePath"},
};
MacroConditionSlideshow::MacroConditionSlideshow(Macro *m)
: MacroCondition(m, true)
{
}
MacroConditionSlideshow::~MacroConditionSlideshow()
{
RemoveSignalHandler();
}
bool MacroConditionSlideshow::CheckCondition()
{
auto source = _source.GetSource();
if (_currentSignalSource != source) {
Reset();
RemoveSignalHandler();
AddSignalHandler(source);
}
if (!source) {
return false;
}
switch (_condition) {
case MacroConditionSlideshow::Condition::SLIDE_CHANGED:
if (!_slideChanged) {
SetVariableValue("false");
return false;
}
_slideChanged = false;
SetVariableValue("true");
return true;
case MacroConditionSlideshow::Condition::SLIDE_INDEX:
if (_currentIndex == -1) {
SetVariableValue("-1");
return false;
}
SetVariableValue(std::to_string(_currentIndex + 1));
return _currentIndex == _index - 1;
case MacroConditionSlideshow::Condition::SLIDE_PATH:
if (_currentPath[0] == '\0') {
SetVariableValue("");
return false;
}
SetVariableValue(_currentPath);
return std::string(_path) == std::string(_currentPath);
}
return false;
}
bool MacroConditionSlideshow::Save(obs_data_t *obj) const
{
MacroCondition::Save(obj);
obs_data_set_int(obj, "condition", static_cast<int>(_condition));
_source.Save(obj);
_index.Save(obj, "index");
_path.Save(obj, "path");
return true;
}
bool MacroConditionSlideshow::Load(obs_data_t *obj)
{
MacroCondition::Load(obj);
_condition = static_cast<Condition>(obs_data_get_int(obj, "condition"));
_source.Load(obj);
_index.Load(obj, "index");
_path.Load(obj, "path");
auto s = _source.GetSource();
AddSignalHandler(s);
return true;
}
std::string MacroConditionSlideshow::GetShortDesc() const
{
return _source.ToString();
}
void MacroConditionSlideshow::SetSource(const SourceSelection &source)
{
Reset();
_source = source;
RemoveSignalHandler();
auto s = _source.GetSource();
AddSignalHandler(s);
}
const SourceSelection &MacroConditionSlideshow::GetSource() const
{
return _source;
}
void MacroConditionSlideshow::SlideChanged(void *c, calldata_t *data)
{
auto condition = static_cast<MacroConditionSlideshow *>(c);
const auto macro = condition->GetMacro();
if (macro && macro->Paused()) {
return;
}
long long newIndex = 0;
if (!calldata_get_int(data, "index", &newIndex)) {
condition->_currentIndex = -1;
}
// Function will be called every time the slide_time timeout is reached
// regardless of if there was actually a change in slide index.
//
// Thus we need to manually check if the slide index really changed.
if (newIndex != condition->_currentIndex) {
condition->_slideChanged = true;
}
condition->_currentIndex = newIndex;
if (!calldata_get_string(data, "path", &condition->_currentPath)) {
condition->_currentPath = "";
}
}
void MacroConditionSlideshow::RemoveSignalHandler()
{
obs_source_t *source = obs_weak_source_get_source(_currentSignalSource);
if (!source) {
return;
}
signal_handler_t *sh = obs_source_get_signal_handler(source);
signal_handler_disconnect(sh, "slide_changed", SlideChanged, this);
obs_source_release(source);
}
void MacroConditionSlideshow::AddSignalHandler(const OBSWeakSource &s)
{
_currentSignalSource = s;
if (!s) {
return;
}
obs_source_t *source = obs_weak_source_get_source(s);
signal_handler_t *sh = obs_source_get_signal_handler(source);
signal_handler_connect(sh, "slide_changed", SlideChanged, this);
obs_source_release(source);
}
void MacroConditionSlideshow::Reset()
{
_slideChanged = false;
_currentIndex = -1;
_currentPath = "";
}
static void populateConditionSelection(QComboBox *list)
{
for (const auto &[_, name] : conditions) {
list->addItem(obs_module_text(name.c_str()));
}
}
static QStringList getSlideshowNames()
{
auto sourceEnum = [](void *param, obs_source_t *source) -> bool /* -- */
{
QStringList *list = reinterpret_cast<QStringList *>(param);
std::string sourceId = obs_source_get_id(source);
if (sourceId.compare("slideshow") == 0) {
*list << obs_source_get_name(source);
}
return true;
};
QStringList list;
obs_enum_sources(sourceEnum, &list);
return list;
}
MacroConditionSlideshowEdit::MacroConditionSlideshowEdit(
QWidget *parent, std::shared_ptr<MacroConditionSlideshow> entryData)
: QWidget(parent),
_conditions(new QComboBox(this)),
_sources(new SourceSelectionWidget(this, QStringList(), true)),
_index(new VariableSpinBox(this)),
_path(new VariableLineEdit(this))
{
setToolTip(obs_module_text(
"AdvSceneSwitcher.condition.slideshow.updateIntervalTooltip"));
_index->setMinimum(1);
populateConditionSelection(_conditions);
auto sources = getSlideshowNames();
sources.sort();
_sources->SetSourceNameList(sources);
QWidget::connect(_conditions, SIGNAL(currentIndexChanged(int)), this,
SLOT(ConditionChanged(int)));
QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this,
SLOT(SourceChanged(const SourceSelection &)));
QWidget::connect(
_index,
SIGNAL(NumberVariableChanged(const NumberVariable<int> &)),
this, SLOT(IndexChanged(const NumberVariable<int> &)));
QWidget::connect(_path, SIGNAL(editingFinished()), this,
SLOT(PathChanged()));
auto layout = new QHBoxLayout;
PlaceWidgets(
obs_module_text("AdvSceneSwitcher.condition.slideshow.entry"),
layout,
{{"{{conditions}}", _conditions},
{"{{sources}}", _sources},
{"{{index}}", _index},
{"{{path}}", _path}});
setLayout(layout);
_entryData = entryData;
UpdateEntryData();
_loading = false;
}
void MacroConditionSlideshowEdit::ConditionChanged(int index)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_condition =
static_cast<MacroConditionSlideshow::Condition>(index);
SetWidgetVisibility();
}
void MacroConditionSlideshowEdit::SourceChanged(const SourceSelection &source)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->SetSource(source);
emit HeaderInfoChanged(
QString::fromStdString(_entryData->GetShortDesc()));
}
void MacroConditionSlideshowEdit::IndexChanged(const NumberVariable<int> &value)
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_index = value;
}
void MacroConditionSlideshowEdit::PathChanged()
{
if (_loading || !_entryData) {
return;
}
auto lock = LockContext();
_entryData->_path = _path->text().toStdString();
}
void MacroConditionSlideshowEdit::UpdateEntryData()
{
if (!_entryData) {
return;
}
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_sources->SetSource(_entryData->GetSource());
_index->SetValue(_entryData->_index);
_path->setText(_entryData->_path);
SetWidgetVisibility();
}
void MacroConditionSlideshowEdit::SetWidgetVisibility()
{
if (!_entryData) {
return;
}
_index->setVisible(_entryData->_condition ==
MacroConditionSlideshow::Condition::SLIDE_INDEX);
_path->setVisible(_entryData->_condition ==
MacroConditionSlideshow::Condition::SLIDE_PATH);
}
} // namespace advss

View File

@@ -0,0 +1,88 @@
#pragma once
#include "macro-condition-edit.hpp"
#include "source-selection.hpp"
#include "variable-spinbox.hpp"
#include "variable-line-edit.hpp"
namespace advss {
class MacroConditionSlideshow : public MacroCondition {
public:
MacroConditionSlideshow(Macro *m);
~MacroConditionSlideshow();
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
std::string GetShortDesc() const;
std::string GetId() const { return id; };
static std::shared_ptr<MacroCondition> Create(Macro *m)
{
return std::make_shared<MacroConditionSlideshow>(m);
}
void SetSource(const SourceSelection &source);
const SourceSelection &GetSource() const;
enum class Condition {
SLIDE_CHANGED,
SLIDE_INDEX,
SLIDE_PATH,
};
Condition _condition = Condition::SLIDE_CHANGED;
IntVariable _index;
StringVariable _path;
private:
static void SlideChanged(void *condition, calldata_t *);
void RemoveSignalHandler();
void AddSignalHandler(const OBSWeakSource &);
void Reset();
SourceSelection _source;
OBSWeakSource _currentSignalSource;
bool _slideChanged = false;
long long _currentIndex = -1;
const char *_currentPath = "";
static bool _registered;
static const std::string id;
};
class MacroConditionSlideshowEdit : public QWidget {
Q_OBJECT
public:
MacroConditionSlideshowEdit(
QWidget *parent,
std::shared_ptr<MacroConditionSlideshow> cond = nullptr);
void UpdateEntryData();
static QWidget *Create(QWidget *parent,
std::shared_ptr<MacroCondition> cond)
{
return new MacroConditionSlideshowEdit(
parent,
std::dynamic_pointer_cast<MacroConditionSlideshow>(
cond));
}
private slots:
void ConditionChanged(int index);
void SourceChanged(const SourceSelection &);
void IndexChanged(const NumberVariable<int> &);
void PathChanged();
signals:
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
QComboBox *_conditions;
VariableSpinBox *_index;
VariableLineEdit *_path;
SourceSelectionWidget *_sources;
std::shared_ptr<MacroConditionSlideshow> _entryData;
bool _loading = true;
};
} // namespace advss

View File

@@ -10,7 +10,7 @@ bool MacroConditionSource::_registered = MacroConditionFactory::Register(
{MacroConditionSource::Create, MacroConditionSourceEdit::Create,
"AdvSceneSwitcher.condition.source"});
static std::map<SourceCondition, std::string> sourceConditionTypes = {
const static std::map<SourceCondition, std::string> sourceConditionTypes = {
{SourceCondition::ACTIVE,
"AdvSceneSwitcher.condition.source.type.active"},
{SourceCondition::SHOWING,
@@ -107,7 +107,9 @@ MacroConditionSourceEdit::MacroConditionSourceEdit(
populateConditionSelection(_conditions);
auto sources = GetSourceNames();
sources.sort();
_sources->SetSourceNameList(sources);
auto scenes = GetSceneNames();
scenes.sort();
_sources->SetSourceNameList(sources + scenes);
QWidget::connect(_sources,
SIGNAL(SourceChanged(const SourceSelection &)), this,
@@ -121,24 +123,27 @@ MacroConditionSourceEdit::MacroConditionSourceEdit(
QWidget::connect(_regex, SIGNAL(RegexConfigChanged(RegexConfig)), this,
SLOT(RegexChanged(RegexConfig)));
QVBoxLayout *mainLayout = new QVBoxLayout;
QHBoxLayout *line1Layout = new QHBoxLayout;
QHBoxLayout *line2Layout = new QHBoxLayout;
QHBoxLayout *line3Layout = new QHBoxLayout;
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{sources}}", _sources}, {"{{conditions}}", _conditions},
{"{{settings}}", _settings}, {"{{getSettings}}", _getSettings},
{"{{regex}}", _regex},
};
auto line1Layout = new QHBoxLayout;
line1Layout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.source.entry.line1"),
line1Layout, widgetPlaceholders);
auto line2Layout = new QHBoxLayout;
line2Layout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.source.entry.line2"),
line2Layout, widgetPlaceholders, false);
auto line3Layout = new QHBoxLayout;
line3Layout->setContentsMargins(0, 0, 0, 0);
PlaceWidgets(obs_module_text(
"AdvSceneSwitcher.condition.source.entry.line3"),
line3Layout, widgetPlaceholders);
auto mainLayout = new QVBoxLayout;
mainLayout->addLayout(line1Layout);
mainLayout->addLayout(line2Layout);
mainLayout->addLayout(line3Layout);
@@ -169,8 +174,7 @@ void MacroConditionSourceEdit::ConditionChanged(int index)
auto lock = LockContext();
_entryData->_condition = static_cast<SourceCondition>(index);
SetSettingsSelectionVisible(_entryData->_condition ==
SourceCondition::SETTINGS);
SetWidgetVisibility();
}
void MacroConditionSourceEdit::GetSettingsClicked()
@@ -213,12 +217,23 @@ void MacroConditionSourceEdit::RegexChanged(RegexConfig conf)
updateGeometry();
}
void MacroConditionSourceEdit::SetSettingsSelectionVisible(bool visible)
void MacroConditionSourceEdit::SetWidgetVisibility()
{
_settings->setVisible(visible);
_getSettings->setVisible(visible);
_regex->setVisible(visible);
_settings->setVisible(_entryData->_condition ==
SourceCondition::SETTINGS);
_getSettings->setVisible(_entryData->_condition ==
SourceCondition::SETTINGS);
_regex->setVisible(_entryData->_condition == SourceCondition::SETTINGS);
setToolTip(
(_entryData->_condition == SourceCondition::ACTIVE ||
_entryData->_condition == SourceCondition::SHOWING)
? obs_module_text(
"AdvSceneSwitcher.condition.source.sceneVisibilityHint")
: "");
adjustSize();
updateGeometry();
}
void MacroConditionSourceEdit::UpdateEntryData()
@@ -231,11 +246,7 @@ void MacroConditionSourceEdit::UpdateEntryData()
_conditions->setCurrentIndex(static_cast<int>(_entryData->_condition));
_settings->setPlainText(_entryData->_settings);
_regex->SetRegexConfig(_entryData->_regex);
SetSettingsSelectionVisible(_entryData->_condition ==
SourceCondition::SETTINGS);
adjustSize();
updateGeometry();
SetWidgetVisibility();
}
} // namespace advss

View File

@@ -74,7 +74,7 @@ protected:
std::shared_ptr<MacroConditionSource> _entryData;
private:
void SetSettingsSelectionVisible(bool visible);
void SetWidgetVisibility();
bool _loading = true;
};

View File

@@ -11,17 +11,18 @@ bool MacroConditionStream::_registered = MacroConditionFactory::Register(
{MacroConditionStream::Create, MacroConditionStreamEdit::Create,
"AdvSceneSwitcher.condition.stream"});
static std::map<MacroConditionStream::Condition, std::string> streamStates = {
{MacroConditionStream::Condition::STOP,
"AdvSceneSwitcher.condition.stream.state.stop"},
{MacroConditionStream::Condition::START,
"AdvSceneSwitcher.condition.stream.state.start"},
{MacroConditionStream::Condition::STARTING,
"AdvSceneSwitcher.condition.stream.state.starting"},
{MacroConditionStream::Condition::STOPPING,
"AdvSceneSwitcher.condition.stream.state.stopping"},
{MacroConditionStream::Condition::KEYFRAME_INTERVAL,
"AdvSceneSwitcher.condition.stream.state.keyFrameInterval"},
const static std::map<MacroConditionStream::Condition, std::string>
streamStates = {
{MacroConditionStream::Condition::STOP,
"AdvSceneSwitcher.condition.stream.state.stop"},
{MacroConditionStream::Condition::START,
"AdvSceneSwitcher.condition.stream.state.start"},
{MacroConditionStream::Condition::STARTING,
"AdvSceneSwitcher.condition.stream.state.starting"},
{MacroConditionStream::Condition::STOPPING,
"AdvSceneSwitcher.condition.stream.state.stopping"},
{MacroConditionStream::Condition::KEYFRAME_INTERVAL,
"AdvSceneSwitcher.condition.stream.state.keyFrameInterval"},
};
int MacroConditionStream::GetKeyFrameInterval()

View File

@@ -10,13 +10,14 @@ bool MacroConditionStudioMode::_registered = MacroConditionFactory::Register(
{MacroConditionStudioMode::Create, MacroConditionStudioModeEdit::Create,
"AdvSceneSwitcher.condition.studioMode"});
static std::map<StudioModeCondition, std::string> studioModeConditionTypes = {
{StudioModeCondition::STUDIO_MODE_ACTIVE,
"AdvSceneSwitcher.condition.studioMode.state.active"},
{StudioModeCondition::STUDIO_MODE_NOT_ACTIVE,
"AdvSceneSwitcher.condition.studioMode.state.notActive"},
{StudioModeCondition::PREVIEW_SCENE,
"AdvSceneSwitcher.condition.studioMode.state.previewScene"},
const static std::map<StudioModeCondition, std::string>
studioModeConditionTypes = {
{StudioModeCondition::STUDIO_MODE_ACTIVE,
"AdvSceneSwitcher.condition.studioMode.state.active"},
{StudioModeCondition::STUDIO_MODE_NOT_ACTIVE,
"AdvSceneSwitcher.condition.studioMode.state.notActive"},
{StudioModeCondition::PREVIEW_SCENE,
"AdvSceneSwitcher.condition.studioMode.state.previewScene"},
};
bool MacroConditionStudioMode::CheckCondition()

View File

@@ -10,7 +10,7 @@ bool MacroConditionTimer::_registered = MacroConditionFactory::Register(
{MacroConditionTimer::Create, MacroConditionTimerEdit::Create,
"AdvSceneSwitcher.condition.timer", false});
static std::map<TimerType, std::string> timerTypes = {
const static std::map<TimerType, std::string> timerTypes = {
{TimerType::FIXED, "AdvSceneSwitcher.condition.timer.type.fixed"},
{TimerType::RANDOM, "AdvSceneSwitcher.condition.timer.type.random"},
};
@@ -18,8 +18,10 @@ static std::map<TimerType, std::string> timerTypes = {
bool MacroConditionTimer::CheckCondition()
{
if (_paused) {
SetVariableValue(std::to_string(_remaining));
return _remaining == 0.;
}
SetVariableValue(std::to_string(_duration.TimeRemaining()));
if (_duration.DurationReached()) {
if (!_oneshot) {
_duration.Reset();

View File

@@ -19,7 +19,7 @@ enum class TimerType {
class MacroConditionTimer : public MacroCondition {
public:
MacroConditionTimer(Macro *m) : MacroCondition(m) {}
MacroConditionTimer(Macro *m) : MacroCondition(m, true) {}
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);

View File

@@ -11,7 +11,7 @@ bool MacroConditionTransition::_registered = MacroConditionFactory::Register(
{MacroConditionTransition::Create, MacroConditionTransitionEdit::Create,
"AdvSceneSwitcher.condition.transition"});
static std::map<TransitionCondition, std::string> filterConditionTypes = {
const static std::map<TransitionCondition, std::string> filterConditionTypes = {
{TransitionCondition::CURRENT,
"AdvSceneSwitcher.condition.transition.type.current"},
{TransitionCondition::DURATION,

View File

@@ -10,7 +10,7 @@ bool MacroConditionVCam::_registered = MacroConditionFactory::Register(
{MacroConditionVCam::Create, MacroConditionVCamEdit::Create,
"AdvSceneSwitcher.condition.virtualCamera"});
static std::map<VCamState, std::string> VCamStates = {
const static std::map<VCamState, std::string> VCamStates = {
{VCamState::STOP,
"AdvSceneSwitcher.condition.virtualCamera.state.stop"},
{VCamState::START,

View File

@@ -13,11 +13,12 @@ bool MacroConditionWebsocket::_registered = MacroConditionFactory::Register(
{MacroConditionWebsocket::Create, MacroConditionWebsocketEdit::Create,
"AdvSceneSwitcher.condition.websocket"});
static std::map<MacroConditionWebsocket::Type, std::string> conditionTypes = {
{MacroConditionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.condition.websocket.type.request"},
{MacroConditionWebsocket::Type::EVENT,
"AdvSceneSwitcher.condition.websocket.type.event"},
const static std::map<MacroConditionWebsocket::Type, std::string>
conditionTypes = {
{MacroConditionWebsocket::Type::REQUEST,
"AdvSceneSwitcher.condition.websocket.type.request"},
{MacroConditionWebsocket::Type::EVENT,
"AdvSceneSwitcher.condition.websocket.type.event"},
};
static bool matchRegex(const RegexConfig &conf, const std::string &msg,

View File

@@ -6,20 +6,30 @@
namespace advss {
MacroDock::MacroDock(Macro *m, QWidget *parent, const QString &runButtonText,
const QString &pauseButtonText,
const QString &unpauseButtonText)
MacroDock::MacroDock(Macro *m, QWidget *parent,
const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight)
: OBSDock(parent),
_runButtonText(runButtonText),
_pauseButtonText(pauseButtonText),
_unpauseButtonText(unpauseButtonText),
_run(new QPushButton(runButtonText)),
_conditionsTrueText(conditionsTrueText),
_conditionsFalseText(conditionsFalseText),
_highlight(enableHighlight),
_run(new QPushButton(runButtonText.c_str())),
_pauseToggle(new QPushButton()),
_statusText(new QLabel(conditionsFalseText.c_str())),
_macro(m)
{
if (_macro) {
setWindowTitle(QString::fromStdString(_macro->Name()));
_run->setVisible(_macro->DockHasRunButton());
_pauseToggle->setVisible(_macro->DockHasPauseButton());
_statusText->setVisible(_macro->DockHasStatusLabel());
} else {
setWindowTitle("<deleted macro>");
}
@@ -34,12 +44,12 @@ MacroDock::MacroDock(Macro *m, QWidget *parent, const QString &runButtonText,
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(_run);
layout->addWidget(_pauseToggle);
layout->addWidget(_statusText);
QWidget::connect(&_timer, SIGNAL(timeout()), this,
SLOT(UpdatePauseText()));
_timer.start(1000);
UpdatePauseText();
UpdateText();
QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(UpdateText()));
QWidget::connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
_timer.start(500);
// QFrame wrapper is necessary to avoid dock being partially
// transparent
@@ -63,9 +73,10 @@ void MacroDock::ShowRunButton(bool value)
_run->setVisible(value);
}
void MacroDock::SetRunButtonText(const QString &text)
void MacroDock::SetRunButtonText(const StringVariable &text)
{
_run->setText(text);
_runButtonText = text;
_run->setText(text.c_str());
}
void MacroDock::ShowPauseButton(bool value)
@@ -73,16 +84,38 @@ void MacroDock::ShowPauseButton(bool value)
_pauseToggle->setVisible(value);
}
void MacroDock::SetPauseButtonText(const QString &text)
void MacroDock::SetPauseButtonText(const StringVariable &text)
{
_pauseButtonText = text;
UpdatePauseText();
UpdateText();
}
void MacroDock::SetUnpauseButtonText(const QString &text)
void MacroDock::SetUnpauseButtonText(const StringVariable &text)
{
_unpauseButtonText = text;
UpdatePauseText();
UpdateText();
}
void MacroDock::ShowStatusLabel(bool value)
{
_statusText->setVisible(value);
}
void MacroDock::SetConditionsTrueText(const StringVariable &text)
{
_conditionsTrueText = text;
UpdateText();
}
void MacroDock::SetConditionsFalseText(const StringVariable &text)
{
_conditionsFalseText = text;
UpdateText();
}
void MacroDock::EnableHighlight(bool value)
{
_highlight = value;
}
void MacroDock::RunClicked()
@@ -106,17 +139,33 @@ void MacroDock::PauseToggleClicked()
}
_macro->SetPaused(!_macro->Paused());
UpdatePauseText();
UpdateText();
}
void MacroDock::UpdatePauseText()
void MacroDock::UpdateText()
{
_run->setText(_runButtonText.c_str());
if (!_macro) {
return;
}
_pauseToggle->setText(_macro->Paused() ? _unpauseButtonText
: _pauseButtonText);
_pauseToggle->setText(_macro->Paused() ? _unpauseButtonText.c_str()
: _pauseButtonText.c_str());
_statusText->setText(_macro->Matched() ? _conditionsTrueText.c_str()
: _conditionsFalseText.c_str());
}
void MacroDock::Highlight()
{
if (!_highlight || !_macro) {
return;
}
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
_macro->ExecutedSince(_lastHighlightCheckTime)) {
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
}
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
}
} // namespace advss

View File

@@ -1,9 +1,12 @@
#pragma once
#include "obs-dock.hpp"
#include "variable-string.hpp"
#include <QPushButton>
#include <QTimer>
#include <QLabel>
#include <memory>
#include <chrono>
namespace advss {
@@ -13,28 +16,42 @@ class MacroDock : public OBSDock {
Q_OBJECT
public:
MacroDock(Macro *, QWidget *parent, const QString &runButtonText,
const QString &pauseButtonText,
const QString &unpauseButtonText);
MacroDock(Macro *, QWidget *parent, const StringVariable &runButtonText,
const StringVariable &pauseButtonText,
const StringVariable &unpauseButtonText,
const StringVariable &conditionsTrueText,
const StringVariable &conditionsFalseText,
bool enableHighlight);
void SetName(const QString &);
void ShowRunButton(bool);
void SetRunButtonText(const QString &);
void SetRunButtonText(const StringVariable &);
void ShowPauseButton(bool);
void SetPauseButtonText(const QString &);
void SetUnpauseButtonText(const QString &);
void SetPauseButtonText(const StringVariable &);
void SetUnpauseButtonText(const StringVariable &);
void ShowStatusLabel(bool);
void SetConditionsTrueText(const StringVariable &);
void SetConditionsFalseText(const StringVariable &);
void EnableHighlight(bool);
private slots:
void RunClicked();
void PauseToggleClicked();
void UpdatePauseText();
void UpdateText();
void Highlight();
private:
QString _pauseButtonText;
QString _unpauseButtonText;
StringVariable _runButtonText;
StringVariable _pauseButtonText;
StringVariable _unpauseButtonText;
StringVariable _conditionsTrueText;
StringVariable _conditionsFalseText;
bool _highlight;
QPushButton *_run;
QPushButton *_pauseToggle;
QLabel *_statusText;
QTimer _timer;
std::chrono::high_resolution_clock::time_point _lastHighlightCheckTime{};
Macro *_macro;
};

View File

@@ -30,30 +30,6 @@ void MacroProperties::Load(obs_data_t *obj)
obs_data_release(data);
}
static void setGridLayoutRowVisible(QGridLayout *layout, int row, bool visible)
{
for (int col = 0; col < layout->columnCount(); col++) {
auto item = layout->itemAtPosition(row, col);
if (!item) {
continue;
}
auto rowLayout = item->layout();
if (rowLayout) {
SetLayoutVisible(rowLayout, visible);
}
auto widget = item->widget();
if (widget) {
widget->setVisible(visible);
}
}
if (!visible) {
layout->setRowMinimumHeight(row, 0);
}
}
MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
const MacroProperties &prop,
Macro *macro)
@@ -74,9 +50,15 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
"AdvSceneSwitcher.macroTab.currentDockAddRunButton"))),
_currentMacroDockAddPauseButton(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockAddPauseButton"))),
_runButtonText(new QLineEdit()),
_pauseButtonText(new QLineEdit()),
_unpauseButtonText(new QLineEdit()),
_currentMacroDockAddStatusLabel(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockAddStatusLabel"))),
_currentMacroDockHighlightIfConditionsTrue(new QCheckBox(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockHighlightIfExecuted"))),
_runButtonText(new VariableLineEdit(this)),
_pauseButtonText(new VariableLineEdit(this)),
_unpauseButtonText(new VariableLineEdit(this)),
_conditionsTrueStatusText(new VariableLineEdit(this)),
_conditionsFalseStatusText(new VariableLineEdit(this)),
_dockOptions(new QGroupBox(
obs_module_text("AdvSceneSwitcher.macroTab.dockSettings"))),
_dockLayout(new QGridLayout())
@@ -127,6 +109,26 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
row, 1);
_dockLayout->addWidget(_unpauseButtonText, row, 2);
_unpauseButtonTextRow = row;
row++;
_dockLayout->addWidget(_currentMacroDockAddStatusLabel, row, 1, 1, 2);
row++;
_dockLayout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockStatusText.true")),
row, 1);
_dockLayout->addWidget(_conditionsTrueStatusText, row, 2);
_conditionsTrueTextRow = row;
row++;
_dockLayout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.macroTab.currentDockStatusText.false")),
row, 1);
_dockLayout->addWidget(_conditionsFalseStatusText, row, 2);
_conditionsFalseTextRow = row;
row++;
_dockLayout->addWidget(_currentMacroDockHighlightIfConditionsTrue, row,
1, 1, 2);
_dockOptions->setLayout(_dockLayout);
QDialogButtonBox *buttonbox = new QDialogButtonBox(
@@ -141,6 +143,8 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
&MacroPropertiesDialog::RunButtonEnableChanged);
connect(_currentMacroDockAddPauseButton, &QCheckBox::stateChanged, this,
&MacroPropertiesDialog::PauseButtonEnableChanged);
connect(_currentMacroDockAddStatusLabel, &QCheckBox::stateChanged, this,
&MacroPropertiesDialog::StatusLabelEnableChanged);
auto layout = new QVBoxLayout;
layout->addWidget(highlightOptions);
@@ -164,20 +168,30 @@ MacroPropertiesDialog::MacroPropertiesDialog(QWidget *parent,
_currentMacroDockAddRunButton->setChecked(macro->DockHasRunButton());
_currentMacroDockAddPauseButton->setChecked(
macro->DockHasPauseButton());
_runButtonText->setText(QString::fromStdString(macro->RunButtonText()));
_pauseButtonText->setText(
QString::fromStdString(macro->PauseButtonText()));
_unpauseButtonText->setText(
QString::fromStdString(macro->UnpauseButtonText()));
_currentMacroDockAddStatusLabel->setChecked(
macro->DockHasStatusLabel());
_currentMacroDockHighlightIfConditionsTrue->setChecked(
macro->DockHighlightEnabled());
_runButtonText->setText(macro->RunButtonText());
_pauseButtonText->setText(macro->PauseButtonText());
_unpauseButtonText->setText(macro->UnpauseButtonText());
_conditionsTrueStatusText->setText(macro->ConditionsTrueStatusText());
_conditionsFalseStatusText->setText(macro->ConditionsFalseStatusText());
_currentMacroDockAddRunButton->setVisible(dockEnabled);
_currentMacroDockAddPauseButton->setVisible(dockEnabled);
setGridLayoutRowVisible(_dockLayout, _runButtonTextRow,
_currentMacroDockAddStatusLabel->setVisible(dockEnabled);
_currentMacroDockHighlightIfConditionsTrue->setVisible(dockEnabled);
SetGridLayoutRowVisible(_dockLayout, _runButtonTextRow,
dockEnabled && macro->DockHasRunButton());
setGridLayoutRowVisible(_dockLayout, _pauseButtonTextRow,
SetGridLayoutRowVisible(_dockLayout, _pauseButtonTextRow,
dockEnabled && macro->DockHasPauseButton());
setGridLayoutRowVisible(_dockLayout, _unpauseButtonTextRow,
SetGridLayoutRowVisible(_dockLayout, _unpauseButtonTextRow,
dockEnabled && macro->DockHasPauseButton());
SetGridLayoutRowVisible(_dockLayout, _conditionsTrueTextRow,
dockEnabled && macro->DockHasStatusLabel());
SetGridLayoutRowVisible(_dockLayout, _conditionsFalseTextRow,
dockEnabled && macro->DockHasStatusLabel());
MinimizeSizeOfColumn(_dockLayout, 0);
Resize();
}
@@ -186,22 +200,43 @@ void MacroPropertiesDialog::DockEnableChanged(int enabled)
{
_currentMacroDockAddRunButton->setVisible(enabled);
_currentMacroDockAddPauseButton->setVisible(enabled);
setGridLayoutRowVisible(_dockLayout, _runButtonTextRow, enabled);
setGridLayoutRowVisible(_dockLayout, _pauseButtonTextRow, enabled);
setGridLayoutRowVisible(_dockLayout, _unpauseButtonTextRow, enabled);
_currentMacroDockAddStatusLabel->setVisible(enabled);
_currentMacroDockHighlightIfConditionsTrue->setVisible(enabled);
SetGridLayoutRowVisible(
_dockLayout, _runButtonTextRow,
enabled && _currentMacroDockAddRunButton->isChecked());
SetGridLayoutRowVisible(
_dockLayout, _pauseButtonTextRow,
enabled && _currentMacroDockAddPauseButton->isChecked());
SetGridLayoutRowVisible(
_dockLayout, _unpauseButtonTextRow,
enabled && _currentMacroDockAddPauseButton->isChecked());
SetGridLayoutRowVisible(
_dockLayout, _conditionsTrueTextRow,
enabled && _currentMacroDockAddStatusLabel->isChecked());
SetGridLayoutRowVisible(
_dockLayout, _conditionsFalseTextRow,
enabled && _currentMacroDockAddStatusLabel->isChecked());
Resize();
}
void MacroPropertiesDialog::RunButtonEnableChanged(int enabled)
{
setGridLayoutRowVisible(_dockLayout, _runButtonTextRow, enabled);
SetGridLayoutRowVisible(_dockLayout, _runButtonTextRow, enabled);
Resize();
}
void MacroPropertiesDialog::PauseButtonEnableChanged(int enabled)
{
setGridLayoutRowVisible(_dockLayout, _pauseButtonTextRow, enabled);
setGridLayoutRowVisible(_dockLayout, _unpauseButtonTextRow, enabled);
SetGridLayoutRowVisible(_dockLayout, _pauseButtonTextRow, enabled);
SetGridLayoutRowVisible(_dockLayout, _unpauseButtonTextRow, enabled);
Resize();
}
void MacroPropertiesDialog::StatusLabelEnableChanged(int enabled)
{
SetGridLayoutRowVisible(_dockLayout, _conditionsTrueTextRow, enabled);
SetGridLayoutRowVisible(_dockLayout, _conditionsFalseTextRow, enabled);
Resize();
}
@@ -238,11 +273,19 @@ bool MacroPropertiesDialog::AskForSettings(QWidget *parent,
dialog._currentMacroDockAddRunButton->isChecked());
macro->SetDockHasPauseButton(
dialog._currentMacroDockAddPauseButton->isChecked());
macro->SetDockHasStatusLabel(
dialog._currentMacroDockAddStatusLabel->isChecked());
macro->SetHighlightEnable(
dialog._currentMacroDockHighlightIfConditionsTrue->isChecked());
macro->SetRunButtonText(dialog._runButtonText->text().toStdString());
macro->SetPauseButtonText(
dialog._pauseButtonText->text().toStdString());
macro->SetUnpauseButtonText(
dialog._unpauseButtonText->text().toStdString());
macro->SetConditionsTrueStatusText(
dialog._conditionsTrueStatusText->text().toStdString());
macro->SetConditionsFalseStatusText(
dialog._conditionsFalseStatusText->text().toStdString());
return true;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include "macro.hpp"
#include "variable-line-edit.hpp"
#include <QWidget>
#include <QDialog>
@@ -35,6 +36,7 @@ private slots:
void DockEnableChanged(int);
void RunButtonEnableChanged(int);
void PauseButtonEnableChanged(int);
void StatusLabelEnableChanged(int);
private:
void Resize();
@@ -48,15 +50,21 @@ private:
QCheckBox *_currentMacroRegisterDock;
QCheckBox *_currentMacroDockAddRunButton;
QCheckBox *_currentMacroDockAddPauseButton;
QLineEdit *_runButtonText;
QLineEdit *_pauseButtonText;
QLineEdit *_unpauseButtonText;
QCheckBox *_currentMacroDockAddStatusLabel;
QCheckBox *_currentMacroDockHighlightIfConditionsTrue;
VariableLineEdit *_runButtonText;
VariableLineEdit *_pauseButtonText;
VariableLineEdit *_unpauseButtonText;
VariableLineEdit *_conditionsTrueStatusText;
VariableLineEdit *_conditionsFalseStatusText;
QGroupBox *_dockOptions;
QGridLayout *_dockLayout;
int _runButtonTextRow = -1;
int _pauseButtonTextRow = -1;
int _unpauseButtonTextRow = -1;
int _conditionsTrueTextRow = -1;
int _conditionsFalseTextRow = -1;
};
} // namespace advss

View File

@@ -8,18 +8,10 @@
namespace advss {
MacroSelection::MacroSelection(QWidget *parent) : QComboBox(parent)
MacroSelection::MacroSelection(QWidget *parent)
: FilterComboBox(parent,
obs_module_text("AdvSceneSwitcher.selectMacro"))
{
addItem(obs_module_text("AdvSceneSwitcher.selectMacro"));
QStandardItemModel *model =
qobject_cast<QStandardItemModel *>(this->model());
QModelIndex firstIndex =
model->index(0, modelColumn(), rootModelIndex());
QStandardItem *firstItem = model->itemFromIndex(firstIndex);
firstItem->setSelectable(false);
firstItem->setEnabled(false);
for (const auto &m : switcher->macros) {
if (m->IsGroup()) {
continue;
@@ -46,7 +38,7 @@ void MacroSelection::SetCurrentMacro(const MacroRef &macro)
{
auto m = macro.GetMacro();
if (!m) {
this->setCurrentIndex(0);
this->setCurrentIndex(-1);
} else {
this->setCurrentText(QString::fromStdString(m->Name()));
}
@@ -86,7 +78,7 @@ void MacroSelection::MacroRemove(const QString &name)
return;
}
removeItem(idx);
setCurrentIndex(0);
setCurrentIndex(-1);
}
void MacroSelection::MacroRename(const QString &oldName, const QString &newName)
@@ -139,8 +131,9 @@ bool MacroSelectionDialog::AskForMacro(QWidget *parent, std::string &macroName)
if (dialog.exec() != DialogCode::Accepted) {
return false;
}
macroName = dialog._macroSelection->currentText().toUtf8().constData();
if (macroName == obs_module_text("AdvSceneSwitcher.selectMacro")) {
macroName = dialog._macroSelection->currentText().toStdString();
if (macroName == obs_module_text("AdvSceneSwitcher.selectMacro") ||
macroName.empty()) {
return false;
}

View File

@@ -1,5 +1,6 @@
#pragma once
#include <QComboBox>
#include "filter-combo-box.hpp"
#include <QDialog>
namespace advss {
@@ -7,7 +8,7 @@ namespace advss {
class Macro;
class MacroRef;
class MacroSelection : public QComboBox {
class MacroSelection : public FilterComboBox {
Q_OBJECT
public:

View File

@@ -6,7 +6,9 @@
#include "switcher-data.hpp"
#include "name-dialog.hpp"
#include "macro-properties.hpp"
#include "macro-export-import-dialog.hpp"
#include "utility.hpp"
#include "version.h"
#include <QColor>
#include <QMenu>
@@ -18,7 +20,7 @@ namespace advss {
static QMetaObject::Connection addPulse;
static QTimer onChangeHighlightTimer;
static bool macroNameExists(std::string name)
static bool macroNameExists(const std::string &name)
{
return !!GetMacroByName(name.c_str());
}
@@ -60,12 +62,8 @@ bool AdvSceneSwitcher::AddNewMacro(std::shared_ptr<Macro> &res,
return false;
}
{
auto lock = LockContext();
res = std::make_shared<Macro>(
name,
switcher->macroProperties._newMacroRegisterHotkeys);
}
res = std::make_shared<Macro>(
name, switcher->macroProperties._newMacroRegisterHotkeys);
return true;
}
@@ -77,11 +75,7 @@ void AdvSceneSwitcher::on_macroAdd_clicked()
return;
}
{
auto lock = LockContext();
ui->macros->Add(newMacro);
}
ui->macros->Add(newMacro);
ui->macroAdd->disconnect(addPulse);
emit MacroAdded(QString::fromStdString(name));
}
@@ -101,11 +95,7 @@ void AdvSceneSwitcher::RemoveMacro(std::shared_ptr<Macro> &macro)
}
}
{
auto lock = LockContext();
ui->macros->Remove(macro);
}
ui->macros->Remove(macro);
emit MacroRemoved(name);
}
@@ -152,7 +142,6 @@ void AdvSceneSwitcher::on_macroRemove_clicked()
void AdvSceneSwitcher::on_macroUp_clicked()
{
auto lock = LockContext();
auto macro = GetSelectedMacro();
if (!macro) {
return;
@@ -162,7 +151,6 @@ void AdvSceneSwitcher::on_macroUp_clicked()
void AdvSceneSwitcher::on_macroDown_clicked()
{
auto lock = LockContext();
auto macro = GetSelectedMacro();
if (!macro) {
return;
@@ -202,6 +190,179 @@ void AdvSceneSwitcher::RenameCurrentMacro()
ui->macroName->setText(QString::fromStdString(name));
}
static void addGroupSubitems(std::vector<std::shared_ptr<Macro>> &macros,
const std::shared_ptr<Macro> &group)
{
std::vector<std::shared_ptr<Macro>> subitems;
subitems.reserve(group->GroupSize());
// Find all subitems
for (auto it = switcher->macros.begin(); it < switcher->macros.end();
it++) {
if ((*it)->Name() == group->Name()) {
for (uint32_t i = 1; i <= group->GroupSize(); i++) {
subitems.emplace_back(*std::next(it, i));
}
break;
}
}
// Remove subitems which were already selected to avoid duplicates
for (const auto &subitem : subitems) {
auto it = std::find(macros.begin(), macros.end(), subitem);
if (it == macros.end()) {
continue;
}
macros.erase(it);
}
// Add group subitems
auto it = std::find(macros.begin(), macros.end(), group);
if (it == macros.end()) {
return;
}
it = std::next(it);
macros.insert(it, subitems.begin(), subitems.end());
}
void AdvSceneSwitcher::ExportMacros()
{
auto selectedMacros = GetSelectedMacros();
auto macros = selectedMacros;
for (const auto &macro : selectedMacros) {
if (macro->IsGroup() && macro->GroupSize() > 0) {
addGroupSubitems(macros, macro);
}
}
auto data = obs_data_create();
auto macroArray = obs_data_array_create();
for (const auto &macro : macros) {
obs_data_t *obj = obs_data_create();
macro->Save(obj);
obs_data_array_push_back(macroArray, obj);
obs_data_release(obj);
}
obs_data_set_array(data, "macros", macroArray);
obs_data_array_release(macroArray);
obs_data_set_string(data, "version", g_GIT_TAG);
auto json = obs_data_get_json(data);
QString exportString(json);
obs_data_release(data);
MacroExportImportDialog::ExportMacros(exportString);
}
bool AdvSceneSwitcher::ResolveMacroImportNameConflict(
std::shared_ptr<Macro> &macro)
{
QString errorMesg = obs_module_text(
"AdvSceneSwitcher.macroTab.import.nameConflict");
errorMesg = errorMesg.arg(QString::fromStdString(macro->Name()),
QString::fromStdString(macro->Name()));
bool continueResolve = DisplayMessage(errorMesg, true);
if (!continueResolve) {
return false;
}
QString format = QString::fromStdString(macro->Name()) + " %1";
int i = 2;
QString placeHolderText = format.arg(i);
while ((macroNameExists(placeHolderText.toUtf8().constData()))) {
placeHolderText = format.arg(++i);
}
std::string newName;
bool accepted = AdvSSNameDialog::AskForName(
this, obs_module_text("AdvSceneSwitcher.macroTab.add"),
obs_module_text("AdvSceneSwitcher.macroTab.name"), newName,
placeHolderText);
if (!accepted) {
return false;
}
if (newName.empty()) {
return false;
}
if (macroNameExists(newName)) {
DisplayMessage(
obs_module_text("AdvSceneSwitcher.macroTab.exists"));
return ResolveMacroImportNameConflict(macro);
}
macro->SetName(newName);
return true;
}
void AdvSceneSwitcher::ImportMacros()
{
QString json;
if (!MacroExportImportDialog::ImportMacros(json)) {
return;
}
auto data = obs_data_create_from_json(json.toStdString().c_str());
if (!data) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.macroTab.import.invalid"));
ImportMacros();
return;
}
auto version = obs_data_get_string(data, "version");
if (strcmp(version, g_GIT_TAG) != 0) {
blog(LOG_WARNING,
"importing macros from non matching plugin version \"%s\"",
version);
}
auto array = obs_data_get_array(data, "macros");
size_t count = obs_data_array_count(array);
int groupSize = 0;
std::shared_ptr<Macro> group;
auto lock = LockContext();
for (size_t i = 0; i < count; i++) {
obs_data_t *array_obj = obs_data_array_item(array, i);
auto macro = std::make_shared<Macro>();
macro->Load(array_obj);
macro->PostLoad();
if (macroNameExists(macro->Name()) &&
!ResolveMacroImportNameConflict(macro)) {
obs_data_release(array_obj);
groupSize--;
continue;
}
switcher->macros.emplace_back(macro);
if (groupSize > 0 && !macro->IsGroup()) {
Macro::PrepareMoveToGroup(group, macro);
groupSize--;
}
if (macro->IsGroup()) {
group = macro;
groupSize = macro->GroupSize();
// We are not sure if all elements will be added so we
// have to reset the group size to zero and add elements
// to the group as they come up.
macro->ResetGroupSize();
}
obs_data_release(array_obj);
}
obs_data_array_release(array);
obs_data_release(data);
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
}
void AdvSceneSwitcher::on_macroName_editingFinished()
{
auto macro = GetSelectedMacro();
@@ -372,8 +533,7 @@ std::vector<std::shared_ptr<Macro>> AdvSceneSwitcher::GetSelectedMacros()
return ui->macros->GetCurrentMacros();
}
void AdvSceneSwitcher::MacroSelectionChanged(const QItemSelection &,
const QItemSelection &)
void AdvSceneSwitcher::MacroSelectionChanged()
{
if (loading) {
return;
@@ -445,12 +605,8 @@ void AdvSceneSwitcher::SetupMacroTab()
}
ui->macros->Reset(switcher->macros,
switcher->macroProperties._highlightExecuted);
connect(ui->macros->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection &,
const QItemSelection &)),
this,
SLOT(MacroSelectionChanged(const QItemSelection &,
const QItemSelection &)));
connect(ui->macros, SIGNAL(MacroSelectionChanged()), this,
SLOT(MacroSelectionChanged()));
delete conditionsList;
conditionsList = new MacroSegmentList(this);
@@ -503,6 +659,16 @@ void AdvSceneSwitcher::SetupMacroTab()
ui->macroActionConditionSplitter->setStyleSheet(
"QSplitter::handle {background: transparent;}");
// Set action and condition control icons
const std::string pathPrefix =
GetDataFilePath("res/images/" + GetThemeTypeName());
SetButtonIcon(ui->actionTop, (pathPrefix + "DoubleUp.svg").c_str());
SetButtonIcon(ui->actionBottom,
(pathPrefix + "DoubleDown.svg").c_str());
SetButtonIcon(ui->conditionTop, (pathPrefix + "DoubleUp.svg").c_str());
SetButtonIcon(ui->conditionBottom,
(pathPrefix + "DoubleDown.svg").c_str());
// Reserve more space for macro edit area than for the macro list
ui->macroListMacroEditSplitter->setStretchFactor(0, 1);
ui->macroListMacroEditSplitter->setStretchFactor(1, 4);
@@ -559,6 +725,15 @@ void AdvSceneSwitcher::ShowMacroContextMenu(const QPoint &pos)
obs_module_text("AdvSceneSwitcher.macroTab.remove"), this,
&AdvSceneSwitcher::on_macroRemove_clicked);
remove->setDisabled(ui->macros->SelectionEmpty());
menu.addSeparator();
auto exportAction = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.export"), this,
&AdvSceneSwitcher::ExportMacros);
exportAction->setDisabled(ui->macros->SelectionEmpty());
auto import = menu.addAction(
obs_module_text("AdvSceneSwitcher.macroTab.import"), this,
&AdvSceneSwitcher::ImportMacros);
menu.exec(globalPos);
}
@@ -844,32 +1019,40 @@ void AdvSceneSwitcher::FadeOutActionControls()
{
fade(ui->actionAdd, true);
fade(ui->actionRemove, true);
fade(ui->actionTop, true);
fade(ui->actionUp, true);
fade(ui->actionDown, true);
fade(ui->actionBottom, true);
}
void AdvSceneSwitcher::FadeOutConditionControls()
{
fade(ui->conditionAdd, true);
fade(ui->conditionRemove, true);
fade(ui->conditionTop, true);
fade(ui->conditionUp, true);
fade(ui->conditionDown, true);
fade(ui->conditionBottom, true);
}
void AdvSceneSwitcher::ResetOpacityActionControls()
{
fade(ui->actionAdd, false);
fade(ui->actionRemove, false);
fade(ui->actionTop, false);
fade(ui->actionUp, false);
fade(ui->actionDown, false);
fade(ui->actionBottom, false);
}
void AdvSceneSwitcher::ResetOpacityConditionControls()
{
fade(ui->conditionAdd, false);
fade(ui->conditionRemove, false);
fade(ui->conditionTop, false);
fade(ui->conditionUp, false);
fade(ui->conditionDown, false);
fade(ui->conditionBottom, false);
}
void AdvSceneSwitcher::HighlightControls()

View File

@@ -96,15 +96,15 @@ void MacroTreeItem::UpdatePaused()
void MacroTreeItem::HighlightIfExecuted()
{
if (!_highlight) {
// Run check regardless to reset "_wasExecutedRecently"
(void)_macro->WasExecutedRecently();
if (!_highlight || !_macro) {
return;
}
if (_macro && _macro->WasExecutedRecently()) {
if (_lastHighlightCheckTime.time_since_epoch().count() != 0 &&
_macro->ExecutedSince(_lastHighlightCheckTime)) {
PulseWidget(this, Qt::green, QColor(0, 0, 0, 0), true);
}
_lastHighlightCheckTime = std::chrono::high_resolution_clock::now();
}
void MacroTreeItem::MacroRenamed(const QString &oldName, const QString &newName)
@@ -277,6 +277,7 @@ void MacroTreeModel::MoveItemBefore(const std::shared_ptr<Macro> &item,
blog(LOG_WARNING,
"error while trying to move item \"%s\" before \"%s\"",
item->Name().c_str(), before->Name().c_str());
assert(IsInValidState());
return;
}
@@ -293,6 +294,7 @@ void MacroTreeModel::MoveItemBefore(const std::shared_ptr<Macro> &item,
_macros.erase(it);
_macros.insert(std::next(_macros.begin(), macroTo), tmp);
endMoveRows();
assert(IsInValidState());
return;
}
@@ -309,6 +311,7 @@ void MacroTreeModel::MoveItemBefore(const std::shared_ptr<Macro> &item,
_macros.insert(std::next(_macros.begin(), macroTo + i), tmp);
}
endMoveRows();
assert(IsInValidState());
}
void MacroTreeModel::MoveItemAfter(const std::shared_ptr<Macro> &item,
@@ -318,34 +321,39 @@ void MacroTreeModel::MoveItemAfter(const std::shared_ptr<Macro> &item,
return;
}
int modelFromIdx = 0, modelFromEndIdx = 0, modelTo = 0, macroFrom = 0,
macroTo = 0;
auto moveAfter = after;
int modelFromIdx = 0, modelFromEndIdx = 0, modelTo = 0;
try {
modelFromIdx = GetItemModelIndex(item);
modelFromEndIdx = modelFromIdx;
modelTo = GetItemModelIndex(after);
macroFrom = GetItemMacroIndex(item);
macroTo = GetItemMacroIndex(after);
} catch (const std::out_of_range &) {
blog(LOG_WARNING,
"error while trying to move item \"%s\" after \"%s\"",
item->Name().c_str(), after->Name().c_str());
assert(IsInValidState());
return;
}
if (after->IsGroup()) {
modelTo += after->IsCollapsed() ? 0 : after->GroupSize();
macroTo += after->GroupSize();
moveAfter = FindEndOfGroup(after, false);
}
if (!item->IsGroup()) {
beginMoveRows(QModelIndex(), modelFromIdx, modelFromEndIdx,
QModelIndex(), modelTo + 1);
auto it = std::next(_macros.begin(), macroFrom);
auto it = std::find(_macros.begin(), _macros.end(), item);
std::shared_ptr<Macro> tmp = *it;
_macros.erase(it);
_macros.insert(std::next(_macros.begin(), macroTo), tmp);
it = std::find(_macros.begin(), _macros.end(), moveAfter);
if (it == _macros.end()) {
_macros.insert(it, tmp);
} else {
_macros.insert(std::next(it), tmp);
}
endMoveRows();
assert(IsInValidState());
return;
}
@@ -355,13 +363,22 @@ void MacroTreeModel::MoveItemAfter(const std::shared_ptr<Macro> &item,
beginMoveRows(QModelIndex(), modelFromIdx, modelFromEndIdx,
QModelIndex(), modelTo + 1);
for (uint32_t i = 0; i <= item->GroupSize(); i++) {
auto it = std::next(_macros.begin(), macroFrom);
auto tmp = *it;
auto groupStart = std::find(_macros.begin(), _macros.end(), item);
auto groupEnd = std::next(groupStart, item->GroupSize() + 1);
std::deque<std::shared_ptr<Macro>> elementsToMove(groupStart, groupEnd);
for (const auto &element : elementsToMove) {
auto it = std::find(_macros.begin(), _macros.end(), element);
_macros.erase(it);
_macros.insert(std::next(_macros.begin(), macroTo), tmp);
it = std::find(_macros.begin(), _macros.end(), moveAfter);
if (it == _macros.end()) {
_macros.insert(it, element);
} else {
_macros.insert(std::next(it), element);
}
moveAfter = element;
}
endMoveRows();
assert(IsInValidState());
}
static inline int
@@ -378,6 +395,7 @@ CountItemsVisibleInModel(const std::deque<std::shared_ptr<Macro>> &macros)
void MacroTreeModel::Add(std::shared_ptr<Macro> item)
{
std::lock_guard<std::mutex> lock(switcher->m);
auto idx = CountItemsVisibleInModel(_macros);
beginInsertRows(QModelIndex(), idx, idx);
_macros.emplace_back(item);
@@ -390,6 +408,7 @@ void MacroTreeModel::Add(std::shared_ptr<Macro> item)
void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
{
std::lock_guard<std::mutex> lock(switcher->m);
auto uiStartIdx = GetItemModelIndex(item);
if (uiStartIdx == -1) {
return;
@@ -419,6 +438,7 @@ void MacroTreeModel::Remove(std::shared_ptr<Macro> item)
if (isGroup) {
UpdateGroupState(true);
}
assert(IsInValidState());
}
std::shared_ptr<Macro> MacroTreeModel::Neighbor(const std::shared_ptr<Macro> &m,
@@ -586,6 +606,37 @@ bool MacroTreeModel::IsLastItem(std::shared_ptr<Macro> item) const
return GetItemModelIndex(item) + 1 == (int)_macros.size();
}
bool MacroTreeModel::IsInValidState()
{
// Check for reordering erros
for (int i = 0, j = 0; i < _macros.size(); i++) {
const auto &m = _macros[i];
if (QString::fromStdString(m->Name()) !=
data(index(j, 0), Qt::AccessibleTextRole)) {
return false;
}
if (m->IsGroup() && m->IsCollapsed()) {
i += m->GroupSize();
}
j++;
}
// Check for group errors
for (int i = 0; i < _macros.size(); i++) {
const auto &m = _macros[i];
if (!m->IsGroup()) {
continue;
}
const auto groupSize = m->GroupSize();
assert(i + groupSize < _macros.size());
for (uint32_t j = 1; j <= groupSize; j++) {
assert(_macros[i + j]->IsSubitem());
}
}
return true;
}
void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
{
if (indices.count() == 0) {
@@ -608,14 +659,15 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
itemsToGroup.emplace_back(item);
}
std::shared_ptr<Macro> item =
std::shared_ptr<Macro> group =
Macro::CreateGroup(name.toStdString(), itemsToGroup);
if (!item) {
if (!group) {
return;
}
// A new list entry for group
_macros.insert(_macros.begin() + insertGroupAt, item);
// Add new list entry for group
insertGroupAt = ModelIndexToMacroIndex(insertGroupAt, _macros);
_macros.insert(_macros.begin() + insertGroupAt, group);
// Move all selected items after new group entry
int offset = 1;
@@ -630,6 +682,7 @@ void MacroTreeModel::GroupSelectedItems(QModelIndexList &indices)
_mt->selectionModel()->clear();
Reset(_macros);
assert(IsInValidState());
}
void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
@@ -650,6 +703,7 @@ void MacroTreeModel::UngroupSelectedGroups(QModelIndexList &indices)
_mt->selectionModel()->clear();
Reset(_macros);
assert(IsInValidState());
}
void MacroTreeModel::ExpandGroup(std::shared_ptr<Macro> item)
@@ -664,6 +718,7 @@ void MacroTreeModel::ExpandGroup(std::shared_ptr<Macro> item)
Reset(_macros);
_mt->selectionModel()->clear();
assert(IsInValidState());
}
void MacroTreeModel::CollapseGroup(std::shared_ptr<Macro> item)
@@ -678,6 +733,7 @@ void MacroTreeModel::CollapseGroup(std::shared_ptr<Macro> item)
Reset(_macros);
_mt->selectionModel()->clear();
assert(IsInValidState());
}
void MacroTreeModel::UpdateGroupState(bool update)
@@ -705,6 +761,12 @@ void MacroTree::Reset(std::deque<std::shared_ptr<Macro>> &macros,
MacroTreeModel *mtm = new MacroTreeModel(this, macros);
setModel(mtm);
GetModel()->Reset(macros);
connect(selectionModel(),
SIGNAL(selectionChanged(const QItemSelection &,
const QItemSelection &)),
this,
SLOT(SelectionChangedHelper(const QItemSelection &,
const QItemSelection &)));
}
void MacroTree::Add(std::shared_ptr<Macro> item,
@@ -714,6 +776,7 @@ void MacroTree::Add(std::shared_ptr<Macro> item,
if (after) {
MoveItemAfter(item, after);
}
assert(GetModel()->IsInValidState());
}
std::shared_ptr<Macro> MacroTree::GetCurrentMacro() const
@@ -759,6 +822,7 @@ void MacroTree::ResetWidgets()
}
modelIdx++;
}
assert(GetModel()->IsInValidState());
}
void MacroTree::UpdateWidget(const QModelIndex &idx,
@@ -1043,6 +1107,7 @@ void MacroTree::dropEvent(QDropEvent *event)
event->setDropAction(Qt::CopyAction);
QListView::dropEvent(event);
assert(GetModel()->IsInValidState());
}
bool MacroTree::GroupsSelected() const
@@ -1116,6 +1181,7 @@ void MacroTree::Remove(std::shared_ptr<Macro> item) const
void MacroTree::Up(std::shared_ptr<Macro> item) const
{
std::lock_guard<std::mutex> lock(switcher->m);
auto above = GetModel()->Neighbor(item, true);
if (!above) {
return;
@@ -1141,6 +1207,7 @@ void MacroTree::Up(std::shared_ptr<Macro> item) const
void MacroTree::Down(std::shared_ptr<Macro> item) const
{
std::lock_guard<std::mutex> lock(switcher->m);
auto below = GetModel()->Neighbor(item, false);
if (!below) {
return;
@@ -1180,12 +1247,20 @@ void MacroTree::GroupSelectedItems()
QModelIndexList indices = selectedIndexes();
std::sort(indices.begin(), indices.end());
GetModel()->GroupSelectedItems(indices);
assert(GetModel()->IsInValidState());
}
void MacroTree::UngroupSelectedGroups()
{
QModelIndexList indices = selectedIndexes();
GetModel()->UngroupSelectedGroups(indices);
assert(GetModel()->IsInValidState());
}
void MacroTree::SelectionChangedHelper(const QItemSelection &,
const QItemSelection &)
{
emit MacroSelectionChanged();
}
inline MacroTreeItem *MacroTree::GetItemWidget(int idx) const

View File

@@ -10,6 +10,7 @@
#include <memory>
#include <deque>
#include <chrono>
class QLabel;
class QSpacerItem;
@@ -61,6 +62,7 @@ private:
QLabel *_label = nullptr;
MacroTree *_tree;
bool _highlight;
std::chrono::high_resolution_clock::time_point _lastHighlightCheckTime{};
QTimer _timer;
std::shared_ptr<Macro> _macro;
@@ -105,6 +107,7 @@ private:
int GetItemMacroIndex(const std::shared_ptr<Macro> &item) const;
int GetItemModelIndex(const std::shared_ptr<Macro> &item) const;
bool IsLastItem(std::shared_ptr<Macro> item) const;
bool IsInValidState();
MacroTree *_mt;
std::deque<std::shared_ptr<Macro>> &_macros;
@@ -135,6 +138,11 @@ public:
public slots:
void GroupSelectedItems();
void UngroupSelectedGroups();
void SelectionChangedHelper(const QItemSelection &,
const QItemSelection &);
signals:
void MacroSelectionChanged();
protected:
virtual void dropEvent(QDropEvent *event) override;

View File

@@ -30,7 +30,12 @@ Macro::~Macro()
_die = true;
Stop();
ClearHotkeys();
RemoveDock();
// Keep the dock widgets in case of shutdown so they can be rostored by
// OBS on startup
if (!switcher->obsIsShuttingDown) {
RemoveDock();
}
}
std::shared_ptr<Macro>
@@ -171,21 +176,14 @@ bool Macro::CeckMatch()
}
vblog(LOG_INFO, "Macro %s returned %d", _name.c_str(), _matched);
bool newLastMatched = _matched;
if (_matched && _matchOnChange && _lastMatched == _matched) {
bool matchedBeforeOnChangeCheck = _matched;
if (_matched && _matchOnChange && _lastMatched) {
vblog(LOG_INFO, "ignore match for Macro %s (on change)",
_name.c_str());
_matched = false;
SetOnChangeHighlight();
}
_lastMatched = newLastMatched;
// TODO: Move back to PerformAction() once new scene collection frontend
// events are available - see:
// https://github.com/obsproject/obs-studio/commit/feda1aaa283e8a99f6ba1159cfe6b9c1f2934a61
if (_matched && _runCount != std::numeric_limits<int>::max()) {
_runCount++;
}
_lastMatched = matchedBeforeOnChangeCheck;
_lastCheckTime = std::chrono::high_resolution_clock::now();
return _matched;
}
@@ -208,14 +206,23 @@ bool Macro::PerformActions(bool forceParallel, bool ignorePause)
} else {
RunActions(ret, ignorePause);
}
_wasExecutedRecently = true;
_lastExecutionTime = std::chrono::high_resolution_clock::now();
auto group = _parent.lock();
if (group) {
group->_wasExecutedRecently = true;
group->_lastExecutionTime = _lastExecutionTime;
}
if (_runCount != std::numeric_limits<int>::max()) {
_runCount++;
}
return ret;
}
bool Macro::ExecutedSince(
const std::chrono::high_resolution_clock::time_point &time)
{
return _lastExecutionTime > time;
}
int64_t Macro::MsSinceLastCheck() const
{
if (_lastCheckTime.time_since_epoch().count() == 0) {
@@ -241,19 +248,27 @@ void Macro::ResetTimers()
c->ResetDuration();
}
_lastCheckTime = {};
_lastExecutionTime = {};
}
void Macro::RunActions(bool &retVal, bool ignorePause)
{
bool ret = true;
for (auto &a : _actions) {
a->LogAction();
ret = ret && a->PerformAction();
if (a->Enabled()) {
a->LogAction();
ret = ret && a->PerformAction();
} else {
vblog(LOG_INFO, "skipping disabled action %s",
a->GetId().c_str());
}
if (!ret || (_paused && !ignorePause) || _stop || _die) {
retVal = ret;
break;
}
a->SetHighlight();
if (a->Enabled()) {
a->SetHighlight();
}
}
_done = true;
}
@@ -543,15 +558,6 @@ bool Macro::SwitchesScene() const
return false;
}
bool Macro::WasExecutedRecently()
{
if (_wasExecutedRecently) {
_wasExecutedRecently = false;
return true;
}
return false;
}
bool Macro::OnChangePreventedActionsRecently()
{
if (_onChangeTriggered) {
@@ -601,12 +607,16 @@ void Macro::SaveDockSettings(obs_data_t *obj) const
}
obs_data_set_bool(dockSettings, "hasRunButton", _dockHasRunButton);
obs_data_set_bool(dockSettings, "hasPauseButton", _dockHasPauseButton);
obs_data_set_string(dockSettings, "runButtonText",
_runButtonText.c_str());
obs_data_set_string(dockSettings, "pauseButtonText",
_pauseButtonText.c_str());
obs_data_set_string(dockSettings, "unpauseButtonText",
_unpauseButtonText.c_str());
obs_data_set_bool(dockSettings, "hasStatusLabel", _dockHasStatusLabel);
obs_data_set_bool(dockSettings, "highlightIfConditionsTrue",
_dockHighlight);
_runButtonText.Save(dockSettings, "runButtonText");
_pauseButtonText.Save(dockSettings, "pauseButtonText");
_unpauseButtonText.Save(dockSettings, "unpauseButtonText");
_conditionsTrueStatusText.Save(dockSettings,
"conditionsTrueStatusText");
_conditionsFalseStatusText.Save(dockSettings,
"conditionsFalseStatusText");
if (_dock) {
auto window = static_cast<QMainWindow *>(
obs_frontend_get_main_window());
@@ -648,16 +658,22 @@ void Macro::LoadDockSettings(obs_data_t *obj)
obs_data_set_default_string(
dockSettings, "unpauseButtonText",
obs_module_text("AdvSceneSwitcher.macroDock.unpause"));
_runButtonText = obs_data_get_string(dockSettings, "runButtonText");
_pauseButtonText = obs_data_get_string(dockSettings, "pauseButtonText");
_unpauseButtonText =
obs_data_get_string(dockSettings, "unpauseButtonText");
_runButtonText.Load(dockSettings, "runButtonText");
_pauseButtonText.Load(dockSettings, "pauseButtonText");
_unpauseButtonText.Load(dockSettings, "unpauseButtonText");
_conditionsTrueStatusText.Load(dockSettings,
"conditionsTrueStatusText");
_conditionsFalseStatusText.Load(dockSettings,
"conditionsFalseStatusText");
if (dockEnabled) {
_dockHasRunButton =
obs_data_get_bool(dockSettings, "hasRunButton");
_dockHasPauseButton =
obs_data_get_bool(dockSettings, "hasPauseButton");
_dockHasStatusLabel =
obs_data_get_bool(dockSettings, "hasStatusLabel");
_dockHighlight = obs_data_get_bool(dockSettings,
"highlightIfConditionsTrue");
_dockIsFloating = obs_data_get_bool(dockSettings, "isFloating");
_dockArea = static_cast<Qt::DockWidgetArea>(
obs_data_get_int(dockSettings, "area"));
@@ -692,10 +708,9 @@ void Macro::EnableDock(bool value)
// Create new dock widget
auto window =
static_cast<QMainWindow *>(obs_frontend_get_main_window());
_dock = new MacroDock(this, window,
QString::fromStdString(_runButtonText),
QString::fromStdString(_pauseButtonText),
QString::fromStdString(_unpauseButtonText));
_dock = new MacroDock(this, window, _runButtonText, _pauseButtonText,
_unpauseButtonText, _conditionsTrueStatusText,
_conditionsFalseStatusText, _dockHighlight);
SetDockWidgetName(); // Used by OBS to restore position
// Register new dock
@@ -705,14 +720,16 @@ void Macro::EnableDock(bool value)
// geometry set here.
// The function calls here are only intended to attempt to restore the
// dock status when switching scene collections.
_dock->setVisible(_dockIsVisible);
if (window->dockWidgetArea(_dock) != _dockArea) {
window->addDockWidget(_dockArea, _dock);
if (switcher->startupLoadDone) {
_dock->setVisible(_dockIsVisible);
if (window->dockWidgetArea(_dock) != _dockArea) {
window->addDockWidget(_dockArea, _dock);
}
if (_dock->isFloating() != _dockIsFloating) {
_dock->setFloating(_dockIsFloating);
}
_dock->restoreGeometry(_dockGeo);
}
if (_dock->isFloating() != _dockIsFloating) {
_dock->setFloating(_dockIsFloating);
}
_dock->restoreGeometry(_dockGeo);
_registerDock = value;
}
@@ -734,13 +751,31 @@ void Macro::SetDockHasPauseButton(bool value)
_dock->ShowPauseButton(value);
}
void Macro::SetDockHasStatusLabel(bool value)
{
_dockHasStatusLabel = value;
if (!_dock) {
return;
}
_dock->ShowStatusLabel(value);
}
void Macro::SetHighlightEnable(bool value)
{
_dockHighlight = value;
if (!_dock) {
return;
}
_dock->EnableHighlight(value);
}
void Macro::SetRunButtonText(const std::string &text)
{
_runButtonText = text;
if (!_dock) {
return;
}
_dock->SetRunButtonText(QString::fromStdString(text));
_dock->SetRunButtonText(text);
}
void Macro::SetPauseButtonText(const std::string &text)
@@ -749,7 +784,7 @@ void Macro::SetPauseButtonText(const std::string &text)
if (!_dock) {
return;
}
_dock->SetPauseButtonText(QString::fromStdString(text));
_dock->SetPauseButtonText(text);
}
void Macro::SetUnpauseButtonText(const std::string &text)
@@ -758,7 +793,35 @@ void Macro::SetUnpauseButtonText(const std::string &text)
if (!_dock) {
return;
}
_dock->SetUnpauseButtonText(QString::fromStdString(text));
_dock->SetUnpauseButtonText(text);
}
void Macro::SetConditionsTrueStatusText(const std::string &text)
{
_conditionsTrueStatusText = text;
if (!_dock) {
return;
}
_dock->SetConditionsTrueText(text);
}
StringVariable Macro::ConditionsTrueStatusText() const
{
return _conditionsTrueStatusText;
}
void Macro::SetConditionsFalseStatusText(const std::string &text)
{
_conditionsFalseStatusText = text;
if (!_dock) {
return;
}
_dock->SetConditionsFalseText(text);
}
StringVariable Macro::ConditionsFalseStatusText() const
{
return _conditionsFalseStatusText;
}
void Macro::RemoveDock()

View File

@@ -2,6 +2,7 @@
#include "macro-action.hpp"
#include "macro-condition.hpp"
#include "macro-ref.hpp"
#include "variable-string.hpp"
#include <QString>
#include <QByteArray>
@@ -59,6 +60,7 @@ public:
std::shared_ptr<Macro> item);
bool IsGroup() const { return _isGroup; }
uint32_t GroupSize() const { return _groupSize; }
void ResetGroupSize() { _groupSize = 0; }
bool IsSubitem() const { return !_parent.expired(); }
void SetCollapsed(bool val) { _isCollapsed = val; }
bool IsCollapsed() const { return _isCollapsed; }
@@ -76,7 +78,8 @@ public:
bool SwitchesScene() const;
// UI helpers
bool WasExecutedRecently();
bool
ExecutedSince(const std::chrono::high_resolution_clock::time_point &);
bool OnChangePreventedActionsRecently();
void ResetUIHelpers();
@@ -89,12 +92,20 @@ public:
bool DockHasRunButton() const { return _dockHasRunButton; }
void SetDockHasPauseButton(bool value);
bool DockHasPauseButton() const { return _dockHasPauseButton; }
std::string RunButtonText() const { return _runButtonText; }
void SetDockHasStatusLabel(bool value);
bool DockHasStatusLabel() const { return _dockHasStatusLabel; }
void SetHighlightEnable(bool value);
bool DockHighlightEnabled() const { return _dockHighlight; }
StringVariable RunButtonText() const { return _runButtonText; }
void SetRunButtonText(const std::string &text);
std::string PauseButtonText() const { return _pauseButtonText; }
StringVariable PauseButtonText() const { return _pauseButtonText; }
void SetPauseButtonText(const std::string &text);
std::string UnpauseButtonText() const { return _unpauseButtonText; }
StringVariable UnpauseButtonText() const { return _unpauseButtonText; }
void SetUnpauseButtonText(const std::string &text);
void SetConditionsTrueStatusText(const std::string &text);
StringVariable ConditionsTrueStatusText() const;
void SetConditionsFalseStatusText(const std::string &text);
StringVariable ConditionsFalseStatusText() const;
private:
void SetupHotkeys();
@@ -114,6 +125,7 @@ private:
bool _stop = false;
bool _done = true;
std::chrono::high_resolution_clock::time_point _lastCheckTime{};
std::chrono::high_resolution_clock::time_point _lastExecutionTime{};
std::thread _backgroundThread;
std::vector<std::thread> _helperThreads;
@@ -128,7 +140,7 @@ private:
bool _runInParallel = false;
bool _matched = false;
bool _lastMatched = false;
bool _matchOnChange = false;
bool _matchOnChange = true;
bool _paused = false;
int _runCount = 0;
bool _registerHotkeys = true;
@@ -136,19 +148,23 @@ 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;
bool _registerDock = false;
bool _dockHasRunButton = true;
bool _dockHasPauseButton = true;
std::string _runButtonText =
bool _dockHasStatusLabel = false;
bool _dockHighlight = false;
StringVariable _runButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.run");
std::string _pauseButtonText =
StringVariable _pauseButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.pause");
std::string _unpauseButtonText =
StringVariable _unpauseButtonText =
obs_module_text("AdvSceneSwitcher.macroDock.unpause");
StringVariable _conditionsTrueStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.true");
StringVariable _conditionsFalseStatusText =
obs_module_text("AdvSceneSwitcher.macroDock.statusLabel.false");
bool _dockIsFloating = true;
bool _dockIsVisible = false;
Qt::DockWidgetArea _dockArea;

View File

@@ -17,6 +17,17 @@ install_advss_plugin_dependency(...)
... to install the plugin and its dependencies.
#]]
add_subdirectory(midi)
add_subdirectory(openvr)
add_subdirectory(video)
option(ENABLE_MIDI_PLUGIN "Enable the midi plugin" ON)
if(ENABLE_MIDI_PLUGIN)
add_subdirectory(midi)
endif()
option(ENABLE_OPENVR_PLUGIN "Enable the openvr plugin" ON)
if(ENABLE_OPENVR_PLUGIN)
add_subdirectory(openvr)
endif()
option(ENABLE_VIDEO_PLUGIN "Enable the video plugin" ON)
if(ENABLE_VIDEO_PLUGIN)
add_subdirectory(video)
endif()

View File

@@ -12,6 +12,10 @@ if(NOT EXISTS "${LIBREMIDI_DIR}")
endif()
add_subdirectory("${LIBREMIDI_DIR}" "${LIBREMIDI_DIR}/build" EXCLUDE_FROM_ALL)
if(MSVC)
target_compile_options(libremidi PRIVATE /wd4251 /wd4267 /wd4275)
endif(MSVC)
# --- End of section ---
add_library(${PROJECT_NAME} MODULE)

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,7 @@
#include <screenshot-helper.hpp>
#include <slider-spinbox.hpp>
#include <variable-text-edit.hpp>
#include <variable-line-edit.hpp>
#include <QWidget>
#include <QComboBox>
@@ -24,7 +25,7 @@ class PreviewDialog;
class MacroConditionVideo : public MacroCondition {
public:
MacroConditionVideo(Macro *m) : MacroCondition(m, true){};
MacroConditionVideo(Macro *m);
bool CheckCondition();
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
@@ -42,6 +43,7 @@ public:
void ResetLastMatch() { _lastMatchResult = false; }
double GetCurrentBrightness() const { return _currentBrightness; }
void SetPageSegMode(tesseract::PageSegMode);
bool SetLanguage(const std::string &);
VideoInput _video;
VideoCondition _condition = VideoCondition::MATCH;
@@ -56,8 +58,9 @@ public:
NumberVariable<double> _brightnessThreshold = 0.5;
PatternMatchParameters _patternMatchParameters;
ObjDetectParameters _objMatchParameters;
OCRParameters _ocrParamters;
AreaParamters _areaParameters;
OCRParameters _ocrParameters;
ColorParameters _colorParameters;
AreaParameters _areaParameters;
bool _throttleEnabled = false;
int _throttleCount = 3;
@@ -67,6 +70,7 @@ private:
bool ScreenshotContainsObject();
bool CheckBrightnessThreshold();
bool CheckOCR();
bool CheckColor();
bool Compare();
bool CheckShouldBeSkipped();
@@ -84,6 +88,139 @@ private:
static const std::string id;
};
class BrightnessEdit : public QWidget {
Q_OBJECT
public:
BrightnessEdit(QWidget *parent,
const std::shared_ptr<MacroConditionVideo> &);
private slots:
void BrightnessThresholdChanged(const NumberVariable<double> &);
void UpdateCurrentBrightness();
private:
SliderSpinBox *_threshold;
QLabel *_current;
QTimer _timer;
std::shared_ptr<MacroConditionVideo> _data;
bool _loading = true;
};
class OCREdit : public QWidget {
Q_OBJECT
public:
OCREdit(QWidget *parent, PreviewDialog *,
const std::shared_ptr<MacroConditionVideo> &);
private slots:
void SelectColorClicked();
void ColorThresholdChanged(const NumberVariable<double> &);
void MatchTextChanged();
void RegexChanged(RegexConfig conf);
void PageSegModeChanged(int);
void LanguageChanged();
private:
void SetupColorLabel(const QColor &);
VariableTextEdit *_matchText;
RegexConfigWidget *_regex;
QLabel *_textColor;
QPushButton *_selectColor;
SliderSpinBox *_colorThreshold;
QComboBox *_pageSegMode;
VariableLineEdit *_languageCode;
PreviewDialog *_previewDialog;
std::shared_ptr<MacroConditionVideo> _data;
bool _loading = true;
};
class ObjectDetectEdit : public QWidget {
Q_OBJECT
public:
ObjectDetectEdit(QWidget *parent, PreviewDialog *,
const std::shared_ptr<MacroConditionVideo> &);
private slots:
void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(const NumberVariable<double> &);
void MinNeighborsChanged(int value);
void MinSizeChanged(Size value);
void MaxSizeChanged(Size value);
private:
FileSelection *_modelDataPath;
SliderSpinBox *_objectScaleThreshold;
QSpinBox *_minNeighbors;
QLabel *_minNeighborsDescription;
SizeSelection *_minSize;
SizeSelection *_maxSize;
PreviewDialog *_previewDialog;
std::shared_ptr<MacroConditionVideo> _data;
bool _loading = true;
};
class ColorEdit : public QWidget {
Q_OBJECT
public:
ColorEdit(QWidget *parent,
const std::shared_ptr<MacroConditionVideo> &);
private slots:
void SelectColorClicked();
void MatchThresholdChanged(const NumberVariable<double> &);
void ColorThresholdChanged(const NumberVariable<double> &);
private:
void SetupColorLabel(const QColor &);
SliderSpinBox *_matchThreshold;
SliderSpinBox *_colorThreshold;
QLabel *_color;
QPushButton *_selectColor;
std::shared_ptr<MacroConditionVideo> _data;
bool _loading = true;
};
class AreaEdit : public QWidget {
Q_OBJECT
public:
AreaEdit(QWidget *parent, PreviewDialog *,
const std::shared_ptr<MacroConditionVideo> &);
private slots:
void CheckAreaEnableChanged(int value);
void CheckAreaChanged(Area);
void CheckAreaChanged(QRect area);
void SelectAreaClicked();
signals:
void Resized();
private:
void SetWidgetVisibility();
QCheckBox *_checkAreaEnable;
AreaSelection *_checkArea;
QPushButton *_selectArea;
PreviewDialog *_previewDialog;
std::shared_ptr<MacroConditionVideo> _data;
bool _loading = true;
};
class MacroConditionVideoEdit : public QWidget {
Q_OBJECT
@@ -117,38 +254,20 @@ private slots:
void UseAlphaAsMaskChanged(int value);
void PatternMatchModeChanged(int value);
void BrightnessThresholdChanged(const NumberVariable<double> &);
void ModelPathChanged(const QString &text);
void ObjectScaleThresholdChanged(const NumberVariable<double> &);
void MinNeighborsChanged(int value);
void MinSizeChanged(Size value);
void MaxSizeChanged(Size value);
void SelectColorClicked();
void MatchTextChanged();
void RegexChanged(RegexConfig conf);
void PageSegModeChanged(int);
void CheckAreaEnableChanged(int value);
void CheckAreaChanged(Area);
void CheckAreaChanged(QRect area);
void SelectAreaClicked();
void ThrottleEnableChanged(int value);
void ThrottleCountChanged(int value);
void ShowMatchClicked();
void UpdateCurrentBrightness();
void SetWidgetVisibility();
void Resize();
signals:
void VideoSelectionChanged(const VideoInput &);
void HeaderInfoChanged(const QString &);
private:
void SetWidgetVisibility();
void HandleVideoInputUpdate();
void SetupPreviewDialogParams();
void SetupColorLabel(const QColor &);
QComboBox *_videoInputTypes;
SceneSelectionWidget *_scenes;
@@ -165,40 +284,19 @@ private:
QHBoxLayout *_patternMatchModeLayout;
QComboBox *_patternMatchMode;
SliderSpinBox *_brightnessThreshold;
QLabel *_currentBrightness;
QPushButton *_showMatch;
PreviewDialog _previewDialog;
QVBoxLayout *_ocrLayout;
VariableTextEdit *_matchText;
RegexConfigWidget *_regex;
QLabel *_textColor;
QPushButton *_selectColor;
QComboBox *_pageSegMode;
FileSelection *_modelDataPath;
QHBoxLayout *_modelPathLayout;
SliderSpinBox *_objectScaleThreshold;
QHBoxLayout *_neighborsControlLayout;
QSpinBox *_minNeighbors;
QLabel *_minNeighborsDescription;
QHBoxLayout *_sizeLayout;
SizeSelection *_minSize;
SizeSelection *_maxSize;
QHBoxLayout *_checkAreaControlLayout;
QCheckBox *_checkAreaEnable;
AreaSelection *_checkArea;
QPushButton *_selectArea;
BrightnessEdit *_brightness;
OCREdit *_ocr;
ObjectDetectEdit *_objectDetect;
ColorEdit *_color;
AreaEdit *_area;
QHBoxLayout *_throttleControlLayout;
QCheckBox *_throttleEnable;
QSpinBox *_throttleCount;
QPushButton *_showMatch;
PreviewDialog _previewDialog;
QTimer _updateBrightnessTimer;
std::shared_ptr<MacroConditionVideo> _entryData;
bool _loading = true;
};

View File

@@ -1,8 +1,12 @@
#include "opencv-helpers.hpp"
#include "log-helper.hpp"
#include <opencv2/core/ocl.hpp>
#include <opencv2/core/mat.hpp>
namespace advss {
PatternImageData createPatternData(QImage &pattern)
PatternImageData CreatePatternData(const QImage &pattern)
{
PatternImageData data{};
if (pattern.isNull()) {
@@ -20,18 +24,19 @@ PatternImageData createPatternData(QImage &pattern)
return data;
}
static void invertPatternMatchResult(cv::Mat &mat)
static void invertPatternMatchResult(cv::UMat &umat)
{
auto mat = umat.getMat(cv::ACCESS_RW);
for (int r = 0; r < mat.rows; r++) {
for (int c = 0; c < mat.cols; c++) {
float value = mat.at<float>(r, c) =
1.0 - mat.at<float>(r, c);
mat.at<float>(r, c) = 1.0 - mat.at<float>(r, c);
}
}
umat = mat.getUMat(cv::ACCESS_RW);
}
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result, bool useAlphaAsMask,
void MatchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::UMat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode)
{
if (img.isNull() || patternData.rgbaPattern.empty()) {
@@ -50,13 +55,12 @@ void matchPattern(QImage &img, const PatternImageData &patternData,
// thus should not be used while matching the pattern as well
//
// Input format is Format_RGBA8888 so discard the 4th channel
std::vector<cv::Mat1b> inputChannels;
std::vector<cv::UMat> inputChannels;
cv::split(input, inputChannels);
std::vector<cv::Mat1b> rgbChanlesImage(
std::vector<cv::UMat> rgbChanlesImage(
inputChannels.begin(), inputChannels.begin() + 3);
cv::Mat3b rgbInput;
cv::UMat rgbInput;
cv::merge(rgbChanlesImage, rgbInput);
cv::matchTemplate(rgbInput, patternData.rgbPattern, result,
matchMode, patternData.mask);
} else {
@@ -74,33 +78,34 @@ void matchPattern(QImage &img, const PatternImageData &patternData,
cv::threshold(result, result, threshold, 0.0, cv::THRESH_TOZERO);
}
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask,
void MatchPattern(QImage &img, QImage &pattern, double threshold,
cv::UMat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchColor)
{
auto data = createPatternData(pattern);
matchPattern(img, data, threshold, result, useAlphaAsMask, matchColor);
auto data = CreatePatternData(pattern);
MatchPattern(img, data, threshold, result, useAlphaAsMask, matchColor);
}
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize)
const cv::Size &minSize,
const cv::Size &maxSize)
{
if (img.isNull() || cascade.empty()) {
return {};
}
auto i = QImageToMat(img);
cv::Mat frameGray;
cv::cvtColor(i, frameGray, cv::COLOR_RGBA2GRAY);
equalizeHist(frameGray, frameGray);
auto image = QImageToMat(img);
cv::UMat frameGray;
cv::cvtColor(image, frameGray, cv::COLOR_RGBA2GRAY);
cv::equalizeHist(frameGray, frameGray);
std::vector<cv::Rect> objects;
cascade.detectMultiScale(frameGray, objects, scaleFactor, minNeighbors,
0, minSize, maxSize);
return objects;
}
uchar getAvgBrightness(QImage &img)
uchar GetAvgBrightness(QImage &img)
{
if (img.isNull()) {
return 0;
@@ -119,21 +124,40 @@ uchar getAvgBrightness(QImage &img)
return brightnessSum / (hsvImage.rows * hsvImage.cols);
}
cv::Mat preprocessForOCR(const QImage &image, const QColor &color)
static bool colorIsSimilar(const QColor &color1, const QColor &color2,
int maxDiff)
{
auto mat = QImageToMat(image);
const int diffRed = std::abs(color1.red() - color2.red());
const int diffGreen = std::abs(color1.green() - color2.green());
const int diffBlue = std::abs(color1.blue() - color2.blue());
// Only keep the desired color
cv::cvtColor(mat, mat, cv::COLOR_RGBA2RGB);
cv::cvtColor(mat, mat, cv::COLOR_RGB2HSV);
cv::inRange(mat, cv::Scalar(0, 0, 0),
cv::Scalar(color.red(), color.green(), color.blue()), mat);
return diffRed <= maxDiff && diffGreen <= maxDiff &&
diffBlue <= maxDiff;
}
// Invert to improve ORC detection
cv::bitwise_not(mat, mat);
cv::Mat PreprocessForOCR(const QImage &image, const QColor &textColor,
double colorDiff)
{
auto umat = QImageToMat(image);
auto mat = umat.getMat(cv::ACCESS_RW);
// Scale image up if selected area is too small
// Results will probably still be unsatisfying
// Tesseract works best when matching black text on a white background,
// so everything that matches the text color will be displayed black
// while the rest of the image should be white.
const int diff = colorDiff * 255;
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
if (colorIsSimilar(image.pixelColor(x, y), textColor,
diff)) {
mat.at<cv::Vec4b>(y, x) = {0, 0, 0, 255};
} else {
mat.at<cv::Vec4b>(y, x) = {255, 255, 255, 255};
}
}
}
// Scale image up if selected area is very small.
// Results will probably still be unsatisfying.
if (mat.rows <= 300 || mat.cols <= 300) {
double scale = 0.;
if (mat.rows < mat.cols) {
@@ -141,25 +165,28 @@ cv::Mat preprocessForOCR(const QImage &image, const QColor &color)
} else {
scale = 300. / mat.cols;
}
cv::resize(mat, mat,
cv::Size(mat.cols * scale, mat.rows * scale),
cv::INTER_CUBIC);
}
return mat;
cv::Mat result;
mat.copyTo(result);
return result;
}
std::string runOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
const QColor &color)
std::string RunOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
const QColor &color, double colorDiff)
{
if (image.isNull()) {
return "";
}
#ifdef OCR_SUPPORT
auto mat = preprocessForOCR(image, color);
ocr->SetImage(mat.data, mat.cols, mat.rows, 1, mat.step);
auto mat = PreprocessForOCR(image, color, colorDiff);
cv::Mat gray;
cv::cvtColor(mat, gray, cv::COLOR_RGBA2GRAY);
ocr->SetImage(gray.data, gray.cols, gray.rows, 1, gray.step);
ocr->Recognize(0);
std::unique_ptr<char[]> detectedText(ocr->GetUTF8Text());
@@ -173,15 +200,38 @@ std::string runOCR(tesseract::TessBaseAPI *ocr, const QImage &image,
#endif
}
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
double colorDeviationThreshold,
double totalPixelMatchThreshold)
{
int totalPixels = image.width() * image.height();
int matchingPixels = 0;
int maxColorDiff = static_cast<int>(colorDeviationThreshold * 255.0);
for (int y = 0; y < image.height(); y++) {
for (int x = 0; x < image.width(); x++) {
if (colorIsSimilar(image.pixelColor(x, y), color,
maxColorDiff)) {
matchingPixels++;
}
}
}
double matchPercentage =
static_cast<double>(matchingPixels) / totalPixels;
return matchPercentage >= totalPixelMatchThreshold;
}
// Assumption is that QImage uses Format_RGBA8888.
// Conversion from: https://github.com/dbzhang800/QtOpenCV
cv::Mat QImageToMat(const QImage &img)
cv::UMat QImageToMat(const QImage &img)
{
if (img.isNull()) {
return cv::Mat();
return cv::UMat();
}
return cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
(uchar *)img.bits(), img.bytesPerLine());
auto temp = cv::Mat(img.height(), img.width(), CV_8UC(img.depth() / 8),
(uchar *)img.bits(), img.bytesPerLine());
return temp.getUMat(cv::ACCESS_RW);
}
QImage MatToQImage(const cv::Mat &mat)
@@ -193,4 +243,12 @@ QImage MatToQImage(const cv::Mat &mat)
QImage::Format::Format_RGBA8888);
}
void SetupOpenCL()
{
if (cv::ocl::haveOpenCL() && !cv::ocl::useOpenCL()) {
blog(LOG_INFO, "enabled OpenCL support for OpenCV");
cv::ocl::setUseOpenCL(true);
}
}
} // namespace advss

View File

@@ -42,25 +42,32 @@ constexpr int maxMinNeighbors = 6;
constexpr double defaultScaleFactor = 1.1;
struct PatternImageData {
cv::Mat4b rgbaPattern;
cv::Mat3b rgbPattern;
cv::Mat1b mask;
cv::UMat rgbaPattern;
cv::UMat rgbPattern;
cv::UMat mask;
};
PatternImageData createPatternData(QImage &pattern);
void matchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::Mat &result, bool useAlphaAsMask,
PatternImageData CreatePatternData(const QImage &pattern);
void MatchPattern(QImage &img, const PatternImageData &patternData,
double threshold, cv::UMat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode);
void matchPattern(QImage &img, QImage &pattern, double threshold,
cv::Mat &result, bool useAlphaAsMask,
void MatchPattern(QImage &img, QImage &pattern, double threshold,
cv::UMat &result, bool useAlphaAsMask,
cv::TemplateMatchModes matchMode);
std::vector<cv::Rect> matchObject(QImage &img, cv::CascadeClassifier &cascade,
std::vector<cv::Rect> MatchObject(QImage &img, cv::CascadeClassifier &cascade,
double scaleFactor, int minNeighbors,
cv::Size minSize, cv::Size maxSize);
uchar getAvgBrightness(QImage &img);
cv::Mat preprocessForOCR(const QImage &image, const QColor &color);
std::string runOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &);
cv::Mat QImageToMat(const QImage &img);
const cv::Size &minSize,
const cv::Size &maxSize);
uchar GetAvgBrightness(QImage &img);
cv::Mat PreprocessForOCR(const QImage &image, const QColor &color,
double colorDiff);
std::string RunOCR(tesseract::TessBaseAPI *, const QImage &, const QColor &,
double colorDiff);
bool ContainsPixelsInColorRange(const QImage &image, const QColor &color,
double colorDeviationThreshold,
double totalPixelMatchThreshold);
cv::UMat QImageToMat(const QImage &img);
QImage MatToQImage(const cv::Mat &mat);
void SetupOpenCL();
} // namespace advss

View File

@@ -1,5 +1,7 @@
#include "paramerter-wrappers.hpp"
#include <filesystem>
namespace advss {
bool PatternMatchParameters::Save(obs_data_t *obj) const
@@ -106,7 +108,7 @@ bool ObjDetectParameters::Load(obs_data_t *obj)
return true;
}
bool AreaParamters::Save(obs_data_t *obj) const
bool AreaParameters::Save(obs_data_t *obj) const
{
auto data = obs_data_create();
obs_data_set_bool(data, "enabled", enable);
@@ -116,7 +118,7 @@ bool AreaParamters::Save(obs_data_t *obj) const
return true;
}
bool AreaParamters::Load(obs_data_t *obj)
bool AreaParameters::Load(obs_data_t *obj)
{
// TODO: Remove this fallback in a future version
if (!obs_data_has_user_value(obj, "areaData")) {
@@ -219,6 +221,17 @@ static void SaveColor(obs_data_t *obj, const char *name, const QColor &color)
obs_data_release(data);
}
static QColor LoadColor(obs_data_t *obj, const char *name)
{
QColor color = Qt::black;
auto data = obs_data_get_obj(obj, name);
color.setRed(obs_data_get_int(data, "red"));
color.setGreen(obs_data_get_int(data, "green"));
color.setBlue(obs_data_get_int(data, "blue"));
obs_data_release(data);
return color;
}
OCRParameters::OCRParameters()
{
Setup();
@@ -236,6 +249,7 @@ OCRParameters::OCRParameters(const OCRParameters &other)
: text(other.text),
regex(other.regex),
color(other.color),
colorThreshold(other.colorThreshold),
pageSegMode(other.pageSegMode)
{
Setup();
@@ -249,6 +263,7 @@ OCRParameters &OCRParameters::operator=(const OCRParameters &other)
text = other.text;
regex = other.regex;
color = other.color;
colorThreshold = other.colorThreshold;
pageSegMode = other.pageSegMode;
ocr->SetPageSegMode(pageSegMode);
return *this;
@@ -259,30 +274,27 @@ bool OCRParameters::Save(obs_data_t *obj) const
auto data = obs_data_create();
text.Save(data, "pattern");
regex.Save(data);
languageCode.Save(data, "language");
SaveColor(data, "textColor", color);
colorThreshold.Save(data, "colorThreshold");
obs_data_set_int(data, "pageSegMode", static_cast<int>(pageSegMode));
obs_data_set_int(data, "version", 1);
obs_data_set_obj(obj, "ocrData", data);
obs_data_release(data);
return true;
}
static QColor LoadColor(obs_data_t *obj, const char *name)
{
QColor color = Qt::black;
auto data = obs_data_get_obj(obj, name);
color.setRed(obs_data_get_int(data, "red"));
color.setGreen(obs_data_get_int(data, "green"));
color.setBlue(obs_data_get_int(data, "blue"));
obs_data_release(data);
return color;
}
bool OCRParameters::Load(obs_data_t *obj)
{
auto data = obs_data_get_obj(obj, "ocrData");
text.Load(data, "pattern");
regex.Load(data);
obs_data_set_default_string(data, "language", "eng");
languageCode.Load(data, "language");
color = LoadColor(data, "textColor");
if (obs_data_has_user_value(data, "version")) {
colorThreshold.Load(data, "colorThreshold");
}
pageSegMode = static_cast<tesseract::PageSegMode>(
obs_data_get_int(data, "pageSegMode"));
obs_data_release(data);
@@ -299,6 +311,24 @@ void OCRParameters::SetPageMode(tesseract::PageSegMode mode)
ocr->SetPageSegMode(mode);
}
bool OCRParameters::SetLanguageCode(const std::string &value)
{
std::string dataPath = obs_get_module_data_path(obs_current_module()) +
std::string("/res/ocr") + "/" + value +
".traineddata";
if (!std::filesystem::exists(dataPath)) {
return false;
}
Setup();
ocr->SetPageSegMode(pageSegMode);
return true;
}
std::string OCRParameters::GetLanguageCode() const
{
return languageCode;
}
void OCRParameters::Setup()
{
ocr = std::make_unique<tesseract::TessBaseAPI>();
@@ -308,11 +338,32 @@ void OCRParameters::Setup()
}
std::string dataPath = obs_get_module_data_path(obs_current_module()) +
std::string("/res/ocr");
if (ocr->Init(dataPath.c_str(), "eng") != 0) {
if (ocr->Init(dataPath.c_str(), languageCode.c_str()) != 0) {
initDone = false;
return;
}
initDone = true;
}
bool ColorParameters::Save(obs_data_t *obj) const
{
auto data = obs_data_create();
SaveColor(data, "color", color);
colorThreshold.Save(data, "colorThreshold");
matchThreshold.Save(data, "matchThreshold");
obs_data_set_obj(obj, "colorData", data);
obs_data_release(data);
return true;
}
bool ColorParameters::Load(obs_data_t *obj)
{
auto data = obs_data_get_obj(obj, "colorData");
color = LoadColor(data, "color");
colorThreshold.Load(data, "colorThreshold");
matchThreshold.Load(data, "matchThreshold");
obs_data_release(data);
return true;
}
} // namespace advss

View File

@@ -28,6 +28,7 @@ enum class VideoCondition {
OBJECT,
BRIGHTNESS,
OCR,
COLOR,
};
class VideoInput {
@@ -89,12 +90,16 @@ public:
bool Initialized() const { return initDone; }
void SetPageMode(tesseract::PageSegMode);
bool SetLanguageCode(const std::string &);
std::string GetLanguageCode() const;
tesseract::PageSegMode GetPageMode() const { return pageSegMode; }
tesseract::TessBaseAPI *GetOCR() const { return ocr.get(); }
StringVariable text = obs_module_text("AdvSceneSwitcher.enterText");
RegexConfig regex = RegexConfig::PartialMatchRegexConfig();
QColor color = Qt::black;
DoubleVariable colorThreshold = 0.3;
StringVariable languageCode = "eng";
private:
void Setup();
@@ -104,7 +109,17 @@ private:
bool initDone = false;
};
class AreaParamters {
class ColorParameters {
public:
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);
QColor color = Qt::black;
DoubleVariable colorThreshold = 0.1;
DoubleVariable matchThreshold = 0.8;
};
class AreaParameters {
public:
bool Save(obs_data_t *obj) const;
bool Load(obs_data_t *obj);

View File

@@ -117,21 +117,20 @@ void PreviewDialog::closeEvent(QCloseEvent *event)
Stop();
}
void PreviewDialog::PatternMatchParamtersChanged(
void PreviewDialog::PatternMatchParametersChanged(
const PatternMatchParameters &params)
{
std::unique_lock<std::mutex> lock(_mtx);
_patternMatchParams = params;
_patternImageData = createPatternData(_patternMatchParams.image);
}
void PreviewDialog::ObjDetectParamtersChanged(const ObjDetectParameters &params)
void PreviewDialog::ObjDetectParametersChanged(const ObjDetectParameters &params)
{
std::unique_lock<std::mutex> lock(_mtx);
_objDetectParams = params;
}
void PreviewDialog::OCRParamtersChanged(const OCRParameters &params)
void PreviewDialog::OCRParametersChanged(const OCRParameters &params)
{
std::unique_lock<std::mutex> lock(_mtx);
_ocrParams = params;
@@ -143,7 +142,7 @@ void PreviewDialog::VideoSelectionChanged(const VideoInput &video)
_video = video;
}
void PreviewDialog::AreaParamtersChanged(const AreaParamters &params)
void PreviewDialog::AreaParametersChanged(const AreaParameters &params)
{
std::unique_lock<std::mutex> lock(_mtx);
_areaParams = params;
@@ -170,8 +169,8 @@ void PreviewDialog::UpdateImage(const QPixmap &image)
if (_type == PreviewType::SELECT_AREA && !_selectingArea) {
DrawFrame();
}
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
_objDetectParams, _ocrParams, _areaParams, _condition);
emit NeedImage(_video, _type, _patternMatchParams, _objDetectParams,
_ocrParams, _areaParams, _condition);
}
void PreviewDialog::Start()
@@ -187,7 +186,7 @@ void PreviewDialog::Start()
return;
}
PreviewImage *worker = new PreviewImage();
PreviewImage *worker = new PreviewImage(_mtx);
worker->moveToThread(&_thread);
connect(&_thread, &QThread::finished, worker, &QObject::deleteLater);
connect(worker, &PreviewImage::ImageReady, this,
@@ -198,8 +197,8 @@ void PreviewDialog::Start()
&PreviewImage::CreateImage);
_thread.start();
emit NeedImage(_video, _type, _patternMatchParams, _patternImageData,
_objDetectParams, _ocrParams, _areaParams, _condition);
emit NeedImage(_video, _type, _patternMatchParams, _objDetectParams,
_ocrParams, _areaParams, _condition);
}
void PreviewDialog::DrawFrame()
@@ -217,13 +216,14 @@ void PreviewDialog::DrawFrame()
_rubberBand->show();
}
static void markPatterns(cv::Mat &matchResult, QImage &image,
const cv::Mat &pattern)
static void markPatterns(cv::UMat &matchResult, QImage &image,
const cv::UMat &pattern)
{
auto temp = matchResult.getMat(cv::ACCESS_RW);
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) {
for (int row = 0; row < temp.rows - 1; row++) {
for (int col = 0; col < temp.cols - 1; col++) {
if (temp.at<float>(row, col) != 0.0) {
rectangle(matchImg, {col, row},
cv::Point(col + pattern.cols,
row + pattern.rows),
@@ -231,6 +231,7 @@ static void markPatterns(cv::Mat &matchResult, QImage &image,
}
}
}
matchResult = temp.getUMat(cv::ACCESS_RW);
}
static void markObjects(QImage &image, std::vector<cv::Rect> &objects)
@@ -244,12 +245,13 @@ static void markObjects(QImage &image, std::vector<cv::Rect> &objects)
}
}
PreviewImage::PreviewImage(std::mutex &mtx) : _mtx(mtx) {}
void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
const PatternMatchParameters &patternMatchParams,
const PatternImageData &patternImageData,
ObjDetectParameters objDetectParams,
OCRParameters ocrParams,
const AreaParamters &areaParams,
const AreaParameters &areaParams,
VideoCondition condition)
{
auto source = obs_weak_source_get_source(video.GetVideo());
@@ -271,11 +273,14 @@ void PreviewImage::CreateImage(const VideoInput &video, PreviewType type,
}
if (type == PreviewType::SHOW_MATCH) {
std::unique_lock<std::mutex> lock(_mtx);
if (areaParams.enable) {
screenshot.image = screenshot.image.copy(
areaParams.area.x, areaParams.area.y,
areaParams.area.width, areaParams.area.height);
}
const auto patternImageData =
CreatePatternData(patternMatchParams.image);
// Will emit status label update
MarkMatch(screenshot.image, patternMatchParams,
patternImageData, objDetectParams, ocrParams,
@@ -294,14 +299,17 @@ void PreviewImage::MarkMatch(QImage &screenshot,
VideoCondition condition)
{
if (condition == VideoCondition::PATTERN) {
cv::Mat result;
matchPattern(screenshot, patternImageData,
cv::UMat result;
MatchPattern(screenshot, patternImageData,
patternMatchParams.threshold, result,
patternMatchParams.useAlphaAsMask,
patternMatchParams.matchMode);
if (countNonZero(result) == 0) {
if (result.total() == 0 || countNonZero(result) == 0) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchFail"));
} else if (result.cols == 1 && result.rows == 1) {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccessFullImage"));
} else {
emit StatusUpdate(obs_module_text(
"AdvSceneSwitcher.condition.video.patternMatchSuccess"));
@@ -309,7 +317,7 @@ void PreviewImage::MarkMatch(QImage &screenshot,
patternImageData.rgbaPattern);
}
} else if (condition == VideoCondition::OBJECT) {
auto objects = matchObject(screenshot, objDetectParams.cascade,
auto objects = MatchObject(screenshot, objDetectParams.cascade,
objDetectParams.scaleFactor,
objDetectParams.minNeighbors,
objDetectParams.minSize.CV(),
@@ -323,13 +331,11 @@ void PreviewImage::MarkMatch(QImage &screenshot,
markObjects(screenshot, objects);
}
} else if (condition == VideoCondition::OCR) {
auto text =
runOCR(ocrParams.GetOCR(), screenshot, ocrParams.color);
auto text = RunOCR(ocrParams.GetOCR(), screenshot,
ocrParams.color, ocrParams.colorThreshold);
QString status(obs_module_text(
"AdvSceneSwitcher.condition.video.ocrMatchSuccess"));
emit StatusUpdate(status.arg(QString::fromStdString(text)));
// TODO: show preprocessed image
}
}

View File

@@ -20,11 +20,13 @@ enum class PreviewType {
class PreviewImage : public QObject {
Q_OBJECT
public:
PreviewImage(std::mutex &);
public slots:
void CreateImage(const VideoInput &, PreviewType,
const PatternMatchParameters &,
const PatternImageData &, ObjDetectParameters,
OCRParameters, const AreaParamters &, VideoCondition);
const PatternMatchParameters &, ObjDetectParameters,
OCRParameters, const AreaParameters &, VideoCondition);
signals:
void ImageReady(const QPixmap &);
void StatusUpdate(const QString &);
@@ -33,6 +35,8 @@ private:
void MarkMatch(QImage &screenshot, const PatternMatchParameters &,
const PatternImageData &, ObjDetectParameters &,
const OCRParameters &, VideoCondition);
std::mutex &_mtx;
};
class PreviewDialog : public QDialog {
@@ -47,11 +51,11 @@ public:
void closeEvent(QCloseEvent *event) override;
public slots:
void PatternMatchParamtersChanged(const PatternMatchParameters &);
void ObjDetectParamtersChanged(const ObjDetectParameters &);
void OCRParamtersChanged(const OCRParameters &);
void PatternMatchParametersChanged(const PatternMatchParameters &);
void ObjDetectParametersChanged(const ObjDetectParameters &);
void OCRParametersChanged(const OCRParameters &);
void VideoSelectionChanged(const VideoInput &);
void AreaParamtersChanged(const AreaParamters &);
void AreaParametersChanged(const AreaParameters &);
void ConditionChanged(int cond);
private slots:
void UpdateImage(const QPixmap &);
@@ -59,9 +63,8 @@ private slots:
signals:
void SelectionAreaChanged(QRect area);
void NeedImage(const VideoInput &, PreviewType,
const PatternMatchParameters &, const PatternImageData &,
ObjDetectParameters, OCRParameters,
const AreaParamters &, VideoCondition);
const PatternMatchParameters &, ObjDetectParameters,
OCRParameters, const AreaParameters &, VideoCondition);
private:
void Start();
@@ -73,10 +76,9 @@ private:
VideoInput _video;
PatternMatchParameters _patternMatchParams;
PatternImageData _patternImageData;
ObjDetectParameters _objDetectParams;
OCRParameters _ocrParams;
AreaParamters _areaParams;
AreaParameters _areaParams;
VideoCondition _condition = VideoCondition::PATTERN;
QScrollArea *_scrollArea;

View File

@@ -18,10 +18,8 @@
#include "macro-properties.hpp"
#include "duration-control.hpp"
#include "connection-manager.hpp"
#include "curl-helper.hpp"
#include "priority-helper.hpp"
#include "log-helper.hpp"
#include <condition_variable>
@@ -124,6 +122,7 @@ public:
bool firstInterval = true;
bool firstIntervalAfterStop = true;
int shutdownConditionCount = 0;
bool startupLoadDone = false;
obs_source_t *waitScene = nullptr;
OBSWeakSource currentScene = nullptr;

View File

@@ -6,7 +6,6 @@
#include <algorithm>
#include <QAction>
#include <QMenu>
#include <QLayout>
#include <obs-module.h>
Q_DECLARE_METATYPE(advss::Connection *);
@@ -44,10 +43,13 @@ void SwitcherData::LoadConnections(obs_data_t *obj)
obs_data_array_release(connectionArray);
}
Connection::Connection(std::string name, std::string address, uint64_t port,
Connection::Connection(bool useCustomURI, std::string customURI,
std::string name, std::string address, uint64_t port,
std::string pass, bool connectOnStart, bool reconnect,
int reconnectDelay, bool useOBSWebsocketProtocol)
: Item(name),
_useCustomURI(useCustomURI),
_customURI(customURI),
_address(address),
_port(port),
_password(pass),
@@ -61,6 +63,8 @@ Connection::Connection(std::string name, std::string address, uint64_t port,
Connection::Connection(const Connection &other) : Item(other)
{
_useCustomURI = other._useCustomURI;
_customURI = other._customURI;
_name = other._name;
_address = other._address;
_port = other._port;
@@ -75,6 +79,8 @@ Connection::Connection(const Connection &other) : Item(other)
Connection &Connection::operator=(const Connection &other)
{
if (this != &other) {
_useCustomURI = other._useCustomURI;
_customURI = other._customURI;
_name = other._name;
_address = other._address;
_port = other._port;
@@ -94,14 +100,17 @@ Connection::~Connection()
_client.Disconnect();
}
static std::string GetUri(std::string addr, int port)
static std::string constructUri(std::string addr, int port)
{
return "ws://" + addr + ":" + std::to_string(port);
}
std::string Connection::GetURI()
{
return GetUri(_address, _port);
if (_useCustomURI) {
return _customURI;
}
return constructUri(_address, _port);
}
void Connection::Reconnect()
@@ -127,10 +136,60 @@ void Connection::SendMsg(const std::string &msg)
}
}
void Connection::Load(obs_data_t *obj)
{
Item::Load(obj);
if (obs_data_has_user_value(obj, "version")) {
UseOBSWebsocketProtocol(
obs_data_get_bool(obj, "useOBSWSProtocol"));
} else {
// TODO: Remove this fallback in future version
_useOBSWSProtocol = true;
}
_client.UseOBSWebsocketProtocol(_useOBSWSProtocol);
_useCustomURI = obs_data_get_bool(obj, "useCustomURI");
_customURI = obs_data_get_string(obj, "customURI");
_address = obs_data_get_string(obj, "address");
_port = obs_data_get_int(obj, "port");
_password = obs_data_get_string(obj, "password");
_connectOnStart = obs_data_get_bool(obj, "connectOnStart");
_reconnect = obs_data_get_bool(obj, "reconnect");
_reconnectDelay = obs_data_get_int(obj, "reconnectDelay");
if (_connectOnStart) {
_client.Connect(GetURI(), _password, _reconnect,
_reconnectDelay);
}
}
void Connection::Save(obs_data_t *obj) const
{
Item::Save(obj);
obs_data_set_bool(obj, "useCustomURI", _useCustomURI);
obs_data_set_string(obj, "customURI", _customURI.c_str());
obs_data_set_bool(obj, "useOBSWSProtocol", _useOBSWSProtocol);
obs_data_set_string(obj, "address", _address.c_str());
obs_data_set_int(obj, "port", _port);
obs_data_set_string(obj, "password", _password.c_str());
obs_data_set_bool(obj, "connectOnStart", _connectOnStart);
obs_data_set_bool(obj, "reconnect", _reconnect);
obs_data_set_int(obj, "reconnectDelay", _reconnectDelay);
obs_data_set_int(obj, "version", 1);
}
void Connection::UseOBSWebsocketProtocol(bool useOBSWSProtocol)
{
_useOBSWSProtocol = useOBSWSProtocol;
_client.UseOBSWebsocketProtocol(useOBSWSProtocol);
}
Connection *GetConnectionByName(const QString &name)
{
return GetConnectionByName(name.toStdString());
}
Connection *GetConnectionByName(const std::string &name)
{
for (auto &con : switcher->connections) {
@@ -188,7 +247,8 @@ ConnectionSelection::ConnectionSelection(QWidget *parent)
: ItemSelection(switcher->connections, Connection::Create,
AskForSettingsWrapper,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add", parent)
"AdvSceneSwitcher.connection.add",
"AdvSceneSwitcher.connection.configure", parent)
{
// Connect to slots
QWidget::connect(
@@ -217,7 +277,7 @@ void ConnectionSelection::SetConnection(const std::string &con)
if (!!GetConnectionByName(con)) {
_selection->setCurrentText(QString::fromStdString(con));
} else {
_selection->setCurrentIndex(0);
_selection->setCurrentIndex(-1);
}
}
@@ -229,7 +289,7 @@ void ConnectionSelection::SetConnection(
if (connection) {
SetConnection(connection->Name());
} else {
_selection->setCurrentIndex(0);
_selection->setCurrentIndex(-1);
}
}
@@ -238,17 +298,20 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
: ItemSettingsDialog(settings, switcher->connections,
"AdvSceneSwitcher.connection.select",
"AdvSceneSwitcher.connection.add", parent),
_useCustomURI(new QCheckBox()),
_customUri(new QLineEdit()),
_address(new QLineEdit()),
_port(new QSpinBox()),
_useOBSWSProtocol(new QCheckBox()),
_password(new QLineEdit()),
_showPassword(new QPushButton()),
_connectOnStart(new QCheckBox()),
_reconnect(new QCheckBox()),
_reconnectDelay(new QSpinBox()),
_useOBSWSProtocol(new QCheckBox()),
_test(new QPushButton(
obs_module_text("AdvSceneSwitcher.connection.test"))),
_status(new QLabel())
_status(new QLabel()),
_layout(new QGridLayout())
{
_port->setMaximum(65535);
_showPassword->setMaximumWidth(22);
@@ -258,6 +321,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
_reconnectDelay->setMaximum(9999);
_reconnectDelay->setSuffix("s");
_useCustomURI->setChecked(settings._useCustomURI);
_customUri->setText(QString::fromStdString(settings._customURI));
_address->setText(QString::fromStdString(settings._address));
_port->setValue(settings._port);
_password->setText(QString::fromStdString(settings._password));
@@ -266,6 +331,8 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
_reconnectDelay->setValue(settings._reconnectDelay);
_useOBSWSProtocol->setChecked(settings._useOBSWSProtocol);
QWidget::connect(_useCustomURI, SIGNAL(stateChanged(int)), this,
SLOT(UseCustomURIChanged(int)));
QWidget::connect(_useOBSWSProtocol, SIGNAL(stateChanged(int)), this,
SLOT(ProtocolChanged(int)));
QWidget::connect(_reconnect, SIGNAL(stateChanged(int)), this,
@@ -277,66 +344,90 @@ ConnectionSettingsDialog::ConnectionSettingsDialog(QWidget *parent,
QWidget::connect(_test, SIGNAL(clicked()), this,
SLOT(TestConnection()));
QGridLayout *layout = new QGridLayout;
int row = 0;
layout->addWidget(
_layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.connection.name")),
row, 0);
QHBoxLayout *nameLayout = new QHBoxLayout;
nameLayout->addWidget(_name);
nameLayout->addWidget(_nameHint);
layout->addLayout(nameLayout, row, 1);
_layout->addLayout(nameLayout, row, 1);
++row;
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.address")),
row, 0);
layout->addWidget(_address, row, 1);
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.useCustomURI")),
row, 0);
_layout->addWidget(_useCustomURI, row, 1);
++row;
layout->addWidget(
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.customURI")),
row, 0);
_layout->addWidget(_customUri, row, 1);
_customURIRow = row;
++row;
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.address")),
row, 0);
_layout->addWidget(_address, row, 1);
_addressRow = row;
++row;
_layout->addWidget(
new QLabel(obs_module_text("AdvSceneSwitcher.connection.port")),
row, 0);
layout->addWidget(_port, row, 1);
_layout->addWidget(_port, row, 1);
_portRow = row;
++row;
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.password")),
row, 0);
QHBoxLayout *passLayout = new QHBoxLayout;
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.password")),
row, 0);
auto passLayout = new QHBoxLayout;
passLayout->addWidget(_password);
passLayout->addWidget(_showPassword);
layout->addLayout(passLayout, row, 1);
_layout->addLayout(passLayout, row, 1);
++row;
layout->addWidget(
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.connectOnStart")),
row, 0);
layout->addWidget(_connectOnStart, row, 1);
_layout->addWidget(_connectOnStart, row, 1);
++row;
layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.reconnect")),
row, 0);
layout->addWidget(_reconnect, row, 1);
_layout->addWidget(new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.reconnect")),
row, 0);
_layout->addWidget(_reconnect, row, 1);
++row;
layout->addWidget(
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.reconnectDelay")),
row, 0);
layout->addWidget(_reconnectDelay, row, 1);
_layout->addWidget(_reconnectDelay, row, 1);
++row;
layout->addWidget(
_layout->addWidget(
new QLabel(obs_module_text(
"AdvSceneSwitcher.connection.useOBSWebsocketProtocol")),
row, 0);
layout->addWidget(_useOBSWSProtocol, row, 1);
_layout->addWidget(_useOBSWSProtocol, row, 1);
++row;
layout->addWidget(_test, row, 0);
layout->addWidget(_status, row, 1);
_layout->addWidget(_test, row, 0);
_layout->addWidget(_status, row, 1);
++row;
layout->addWidget(_buttonbox, row, 0, 1, -1);
setLayout(layout);
_layout->addWidget(_buttonbox, row, 0, 1, -1);
setLayout(_layout);
MinimizeSizeOfColumn(_layout, 0);
ReconnectChanged(_reconnect->isChecked());
ProtocolChanged(_useOBSWSProtocol->isChecked());
HidePassword();
UseCustomURIChanged(settings._useCustomURI);
}
void ConnectionSettingsDialog::UseCustomURIChanged(int state)
{
SetGridLayoutRowVisible(_layout, _addressRow, !state);
SetGridLayoutRowVisible(_layout, _portRow, !state);
SetGridLayoutRowVisible(_layout, _customURIRow, state);
adjustSize();
updateGeometry();
}
void ConnectionSettingsDialog::ProtocolChanged(int state)
@@ -390,9 +481,11 @@ void ConnectionSettingsDialog::TestConnection()
{
_testConnection.UseOBSWebsocketProtocol(_useOBSWSProtocol->isChecked());
_testConnection.Disconnect();
_testConnection.Connect(GetUri(_address->text().toStdString(),
_port->value()),
_password->text().toStdString(), false);
std::string uri = _useCustomURI->isChecked()
? _customUri->text().toStdString()
: constructUri(_address->text().toStdString(),
_port->value());
_testConnection.Connect(uri, _password->text().toStdString(), false);
_statusTimer.setInterval(1000);
QWidget::connect(&_statusTimer, &QTimer::timeout, this,
&ConnectionSettingsDialog::SetStatus);
@@ -409,6 +502,8 @@ bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
}
settings._name = dialog._name->text().toStdString();
settings._useCustomURI = dialog._useCustomURI->isChecked();
settings._customURI = dialog._customUri->text().toStdString();
settings._address = dialog._address->text().toStdString();
settings._port = dialog._port->value();
settings._password = dialog._password->text().toStdString();
@@ -420,49 +515,4 @@ bool ConnectionSettingsDialog::AskForSettings(QWidget *parent,
return true;
}
void Connection::Load(obs_data_t *obj)
{
Item::Load(obj);
if (obs_data_has_user_value(obj, "version")) {
UseOBSWebsocketProtocol(
obs_data_get_bool(obj, "useOBSWSProtocol"));
} else {
// TODO: Remove this fallback in future version
_useOBSWSProtocol = true;
}
_client.UseOBSWebsocketProtocol(_useOBSWSProtocol);
_address = obs_data_get_string(obj, "address");
_port = obs_data_get_int(obj, "port");
_password = obs_data_get_string(obj, "password");
_connectOnStart = obs_data_get_bool(obj, "connectOnStart");
_reconnect = obs_data_get_bool(obj, "reconnect");
_reconnectDelay = obs_data_get_int(obj, "reconnectDelay");
if (_connectOnStart) {
_client.Connect(GetURI(), _password, _reconnect,
_reconnectDelay);
}
}
void Connection::Save(obs_data_t *obj) const
{
Item::Save(obj);
obs_data_set_bool(obj, "useOBSWSProtocol", _useOBSWSProtocol);
obs_data_set_string(obj, "address", _address.c_str());
obs_data_set_int(obj, "port", _port);
obs_data_set_string(obj, "password", _password.c_str());
obs_data_set_bool(obj, "connectOnStart", _connectOnStart);
obs_data_set_bool(obj, "reconnect", _reconnect);
obs_data_set_int(obj, "reconnectDelay", _reconnectDelay);
obs_data_set_int(obj, "version", 1);
}
void Connection::UseOBSWebsocketProtocol(bool useOBSWSProtocol)
{
_useOBSWSProtocol = useOBSWSProtocol;
_client.UseOBSWebsocketProtocol(useOBSWSProtocol);
}
} // namespace advss

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