From 3dc20b12fc2f9b03945a45f0cf2e5700c2c0f493 Mon Sep 17 00:00:00 2001 From: nhielost Date: Sat, 25 Mar 2023 17:04:44 -0400 Subject: [PATCH] Version 2.3 release --- .github/workflows/main.yml | 73 +- CMakeLists.txt | 20 +- HELP.md | 51 +- README.md | 2 + buildspec.json | 4 +- cmake/ObsPluginHelpers.cmake | 5 +- cmake/bundle/windows/resource.rc.in | 32 + src/actions/mmg-action-audio-sources.cpp | 177 +- src/actions/mmg-action-audio-sources.h | 33 +- src/actions/mmg-action-collections.cpp | 54 +- src/actions/mmg-action-collections.h | 22 +- src/actions/mmg-action-filters.cpp | 174 +- src/actions/mmg-action-filters.h | 30 +- src/actions/mmg-action-hotkeys.cpp | 68 +- src/actions/mmg-action-hotkeys.h | 26 +- src/actions/mmg-action-internal.cpp | 331 +- src/actions/mmg-action-internal.h | 108 +- src/actions/mmg-action-media-sources.cpp | 123 +- src/actions/mmg-action-media-sources.h | 26 +- src/actions/mmg-action-midi.cpp | 203 +- src/actions/mmg-action-midi.h | 36 +- src/actions/mmg-action-none.cpp | 22 +- src/actions/mmg-action-none.h | 17 +- src/actions/mmg-action-profiles.cpp | 54 +- src/actions/mmg-action-profiles.h | 22 +- src/actions/mmg-action-record.cpp | 28 +- src/actions/mmg-action-record.h | 15 +- src/actions/mmg-action-replaybuffer.cpp | 28 +- src/actions/mmg-action-replaybuffer.h | 15 +- src/actions/mmg-action-scenes.cpp | 54 +- src/actions/mmg-action-scenes.h | 22 +- src/actions/mmg-action-stream.cpp | 26 +- src/actions/mmg-action-stream.h | 15 +- src/actions/mmg-action-studiomode.cpp | 59 +- src/actions/mmg-action-studiomode.h | 22 +- src/actions/mmg-action-timeout.cpp | 82 - src/actions/mmg-action-timeout.h | 46 - src/actions/mmg-action-transitions.cpp | 248 +- src/actions/mmg-action-transitions.h | 41 +- src/actions/mmg-action-video-sources.cpp | 524 +-- src/actions/mmg-action-video-sources.h | 51 +- src/actions/mmg-action-virtualcam.cpp | 26 +- src/actions/mmg-action-virtualcam.h | 15 +- src/actions/mmg-action.cpp | 19 +- src/actions/mmg-action.h | 70 +- src/mmg-action-include.h | 3 +- src/mmg-binding.cpp | 210 +- src/mmg-binding.h | 51 +- src/mmg-config.cpp | 116 +- src/mmg-config.h | 47 +- src/mmg-device.cpp | 253 +- src/mmg-device.h | 57 +- src/mmg-message.cpp | 51 +- src/mmg-message.h | 22 +- src/mmg-midiin.cpp | 95 + src/mmg-midiin.h | 57 + src/mmg-midiout.cpp | 120 + src/mmg-midiout.h | 54 + src/mmg-utils.cpp | 470 +-- src/mmg-utils.h | 201 +- src/obs-midi-mg.cpp | 22 +- src/obs-midi-mg.h | 13 +- src/ui/mmg-action-display.cpp | 154 + src/ui/mmg-action-display.h | 82 + src/ui/mmg-echo-window.cpp | 695 ++-- src/ui/mmg-echo-window.h | 48 +- src/ui/mmg-echo-window.ui | 4146 +--------------------- src/ui/mmg-fields.cpp | 802 +++-- src/ui/mmg-fields.h | 217 +- src/ui/mmg-lcd-number.cpp | 143 + src/ui/mmg-lcd-number.h | 80 + src/ui/mmg-number-display.cpp | 203 ++ src/ui/mmg-number-display.h | 89 + 73 files changed, 4604 insertions(+), 7016 deletions(-) create mode 100644 cmake/bundle/windows/resource.rc.in delete mode 100644 src/actions/mmg-action-timeout.cpp delete mode 100644 src/actions/mmg-action-timeout.h create mode 100644 src/mmg-midiin.cpp create mode 100644 src/mmg-midiin.h create mode 100644 src/mmg-midiout.cpp create mode 100644 src/mmg-midiout.h create mode 100644 src/ui/mmg-action-display.cpp create mode 100644 src/ui/mmg-action-display.h create mode 100644 src/ui/mmg-lcd-number.cpp create mode 100644 src/ui/mmg-lcd-number.h create mode 100644 src/ui/mmg-number-display.cpp create mode 100644 src/ui/mmg-number-display.h diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b40d1a1..00ab299 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,9 +12,6 @@ on: branches: - master -env: - PLUGIN_NAME: 'obs-midi-mg' - jobs: clang_check: name: 01 - Code Format Check @@ -89,25 +86,27 @@ jobs: if [[ '${{ secrets.MACOS_SIGNING_APPLICATION_IDENTITY }}' != '' && \ '${{ secrets.MACOS_SIGNING_INSTALLER_IDENTITY }}' != '' && \ '${{ secrets.MACOS_SIGNING_CERT }}' != '' ]] { - print '::set-output name=haveCodesignIdent::true' + print 'haveCodesignIdent=true' >> $GITHUB_OUTPUT } else { - print '::set-output name=haveCodesignIdent::false' + print 'haveCodesignIdent=false' >> $GITHUB_OUTPUT } if [[ '${{ secrets.MACOS_NOTARIZATION_USERNAME }}' != '' && \ '${{ secrets.MACOS_NOTARIZATION_PASSWORD }}' != '' ]] { - print '::set-output name=haveNotarizationUser::true' + print 'haveNotarizationUser=true' >> $GITHUB_OUTPUT } else { - print '::set-output name=haveNotarizationUser::false' + print 'haveNotarizationUser=false' >> $GITHUB_OUTPUT } print '::endgroup::' - print "::set-output name=ccacheDate::$(date +"%Y-%m-%d")" - print "::set-output name=commitHash::${"$(git rev-parse HEAD)"[0,9]}" + print "ccacheDate=$(date +"%Y-%m-%d")" >> $GITHUB_OUTPUT + print "commitHash=${"$(git rev-parse HEAD)"[0,9]}" >> $GITHUB_OUTPUT + + print "pluginName=$(jq -r '.name' buildspec.json)" >> $GITHUB_OUTPUT - name: Restore Compilation Cache id: ccache-cache - uses: actions/cache@v2.1.7 + uses: actions/cache@v3 with: path: ${{ github.workspace }}/.ccache key: macos-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }} @@ -119,9 +118,9 @@ jobs: if: ${{ github.event_name == 'pull_request' }} run: | if [[ -n "$(curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')" ]] { - print '::set-output name=found::true' + print 'found=true' >> $GITHUB_OUTPUT } else { - print '::set-output name=found::false' + print 'found=false' >> $GITHUB_OUTPUT } - name: Install Apple Developer Certificate @@ -164,8 +163,8 @@ jobs: if: ${{ success() && (github.event_name != 'pull_request' || steps.seekingTesters.outputs.found == 'true') }} uses: actions/upload-artifact@v3 with: - name: ${{ env.PLUGIN_NAME }}-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} - path: ${{ github.workspace }}/plugin/release/${{ env.PLUGIN_NAME }}-*-macos-${{ matrix.arch }}.pkg + name: ${{ steps.setup.outputs.pluginName }}-macos-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} + path: ${{ github.workspace }}/plugin/release/${{ steps.setup.outputs.pluginName }}-*-macos-${{ matrix.arch }}.pkg linux_build: name: 02 - Linux @@ -201,12 +200,13 @@ jobs: id: setup run: | ## SETUP ENVIRONMENT SCRIPT - echo "::set-output name=ccacheDate::$(date +"%Y-%m-%d")" - echo "::set-output name=commitHash::$(git rev-parse HEAD | cut -c1-9)" + echo "ccacheDate=$(date +"%Y-%m-%d")" >> $GITHUB_OUTPUT + echo "commitHash=$(git rev-parse HEAD | cut -c1-9)" >> $GITHUB_OUTPUT + echo "pluginName=$(jq -r '.name' buildspec.json)" >> $GITHUB_OUTPUT - name: Restore Compilation Cache id: ccache-cache - uses: actions/cache@v2.1.7 + uses: actions/cache@v3 with: path: ${{ github.workspace }}/.ccache key: linux-${{ matrix.arch }}-ccache-plugin-${{ steps.setup.outputs.ccacheDate }} @@ -219,9 +219,9 @@ jobs: run: | ## GITHUB LABEL SCRIPT if [[ -n "$(curl -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -s "${{ github.event.pull_request.url }}" | jq -e '.labels[] | select(.name == "Seeking Testers")')" ]]; then - echo '::set-output name=found::true' + echo 'found=true' >> $GITHUB_OUTPUT else - echo '::set-output name=found::false' + echo 'found=false' >> $GITHUB_OUTPUT fi - name: Build Plugin @@ -242,8 +242,8 @@ jobs: if: ${{ success() && (github.event_name != 'pull_request' || steps.seekingTesters.outputs.found == 'true') }} uses: actions/upload-artifact@v3 with: - name: ${{ env.PLUGIN_NAME }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} - path: ${{ github.workspace }}/plugin/release/${{ env.PLUGIN_NAME }}-*-linux-${{ matrix.arch }}.* + name: ${{ steps.setup.outputs.pluginName }}-linux-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} + path: ${{ github.workspace }}/plugin/release/${{ steps.setup.outputs.pluginName }}-*-linux-${{ matrix.arch }}.* windows_build: name: 02 - Windows @@ -251,7 +251,7 @@ jobs: strategy: fail-fast: true matrix: - arch: [x86, x64] + arch: [x64] if: always() needs: [clang_check] outputs: @@ -280,7 +280,10 @@ jobs: run: | ## SETUP ENVIRONMENT SCRIPT $CommitHash = (git rev-parse HEAD)[0..8] -join '' - Write-Output "::set-output name=commitHash::${CommitHash}" + "commitHash=${CommitHash}" >> $env:GITHUB_OUTPUT + $BuildSpec = Get-Content -Path buildspec.json -Raw | ConvertFrom-Json + $PluginName = $BuildSpec.name + "pluginName=${PluginName}" >> $env:GITHUB_OUTPUT - name: Check for GitHub Labels id: seekingTesters @@ -301,7 +304,7 @@ jobs: $false } - Write-Output "::set-output name=found::$(([string]${LabelFound}).ToLower())" + "found=$(([string]${LabelFound}).ToLower())" >> $env:GITHUB_OUTPUT - name: Build Plugin uses: ./plugin/.github/actions/build-plugin @@ -322,8 +325,8 @@ jobs: if: ${{ success() && (github.event_name != 'pull_request' || steps.seekingTesters.outputs.found == 'true') }} uses: actions/upload-artifact@v3 with: - name: ${{ env.PLUGIN_NAME }}-windows-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} - path: ${{ github.workspace }}/plugin/release/${{ env.PLUGIN_NAME }}-*.zip + name: ${{ steps.setup.outputs.pluginName }}-windows-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }} + path: ${{ github.workspace }}/plugin/release/${{ steps.setup.outputs.pluginName }}-*.zip - name: Package Plugin Installer if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' }} @@ -338,8 +341,8 @@ jobs: if: ${{ startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request' }} uses: actions/upload-artifact@v3 with: - name: ${{ env.PLUGIN_NAME }}-windows-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}-installer - path: ${{ github.workspace }}/plugin/release/${{ env.PLUGIN_NAME }}-*.exe + name: ${{ steps.setup.outputs.pluginName }}-windows-${{ matrix.arch }}-${{ steps.setup.outputs.commitHash }}-installer + path: ${{ github.workspace }}/plugin/release/${{ steps.setup.outputs.pluginName }}-*.exe make-release: name: 03 - Create and upload release @@ -349,12 +352,22 @@ jobs: defaults: run: shell: bash + permissions: + contents: write steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: plugin + submodules: recursive + - name: Get Metadata + working-directory: ${{ github.workspace }}/plugin id: metadata run: | ## METADATA SCRIPT - echo "::set-output name=version::${GITHUB_REF/refs\/tags\//}" + echo "version=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_OUTPUT + echo "pluginName=$(jq -r '.name' buildspec.json)" >> $GITHUB_OUTPUT - name: Download build artifacts uses: actions/download-artifact@v3 @@ -375,7 +388,7 @@ jobs: draft: false prerelease: ${{ contains(steps.metadata.outputs.version, 'rc') || contains(steps.metadata.outputs.version, 'beta') }} tag_name: ${{ steps.metadata.outputs.version }} - name: "${{ env.PLUGIN_NAME }} ${{ steps.metadata.outputs.version }}" + name: "${{ steps.metadata.outputs.pluginName }} ${{ steps.metadata.outputs.version }}" body_path: ${{ github.workspace }}/CHECKSUMS.txt files: | ${{ github.workspace }}/**/*.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index c3b31da..32cccbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.16...3.21) # Change obs-plugintemplate to your plugin's name in a machine-readable format # (e.g.: obs-myawesomeplugin) and set -project(obs-midi-mg VERSION 2.2.0) +project(obs-midi-mg VERSION 2.3.0) add_library(obs-midi-mg MODULE) # Replace `Your Name Here` with the name (yours or your organization's) you want @@ -49,14 +49,18 @@ target_sources( ./src/actions/mmg-action-collections.cpp ./src/actions/mmg-action-midi.cpp ./src/actions/mmg-action-internal.cpp - ./src/actions/mmg-action-timeout.cpp ./src/mmg-binding.cpp ./src/mmg-config.cpp ./src/mmg-device.cpp ./src/mmg-message.cpp + ./src/mmg-midiin.cpp + ./src/mmg-midiout.cpp ./src/mmg-utils.cpp ./src/obs-midi-mg.cpp ./src/ui/mmg-fields.cpp + ./src/ui/mmg-lcd-number.cpp + ./src/ui/mmg-number-display.cpp + ./src/ui/mmg-action-display.cpp ./src/ui/mmg-echo-window.cpp) target_sources( @@ -79,15 +83,19 @@ target_sources( ./src/actions/mmg-action-collections.h ./src/actions/mmg-action-midi.h ./src/actions/mmg-action-internal.h - ./src/actions/mmg-action-timeout.h ./src/mmg-action-include.h ./src/mmg-binding.h ./src/mmg-config.h ./src/mmg-device.h ./src/mmg-message.h + ./src/mmg-midiin.h + ./src/mmg-midiout.h ./src/mmg-utils.h ./src/obs-midi-mg.h ./src/ui/mmg-fields.h + ./src/ui/mmg-lcd-number.h + ./src/ui/mmg-number-display.h + ./src/ui/mmg-action-display.h ./src/ui/mmg-echo-window.h) # Import libobs as main plugin dependency @@ -129,6 +137,10 @@ if(OS_WINDOWS) configure_file(cmake/bundle/windows/installer-Windows.iss.in ${CMAKE_BINARY_DIR}/installer-Windows.generated.iss) + configure_file(cmake/bundle/windows/resource.rc.in + ${CMAKE_BINARY_DIR}/obs-midi-mg.rc) + target_sources(obs-midi-mg PRIVATE ${CMAKE_BINARY_DIR}/obs-midi-mg.rc) + if(MSVC) target_compile_options(obs-midi-mg PRIVATE /W3) endif() @@ -141,7 +153,7 @@ elseif(OS_MACOS) set(MACOSX_PLUGIN_GUI_IDENTIFIER "${MACOS_BUNDLEID}") set(MACOSX_PLUGIN_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION}") - set(MACOSX_PLUGIN_SHORT_VERSION_STRING "2.2.0") + set(MACOSX_PLUGIN_SHORT_VERSION_STRING "2.3.0") target_compile_options(obs-midi-mg PRIVATE -Wall) # --- End of section --- diff --git a/HELP.md b/HELP.md index 399316d..3d7425e 100644 --- a/HELP.md +++ b/HELP.md @@ -68,7 +68,9 @@ If a binding is disabled, the message will not be listened to. This can be chang Messages are composed of a type, channel, and one or two other values. These values can be inputted manually or, by using the *Listen to Message* feature, can be inputted by the input device automatically. Only one message can activate any action (no longer can one use multiple messages to do this). -If the type is set to *Note On / Note Off*, the message will toggle to the other type every time that it is received (i.e. if the message is currently listening for a Note On type, it will switch to Note Off type when it is received, and viceversa). +If the *Toggle Note On / Off Messages* option is checked, the message will toggle to the other type every time that it is received (i.e. if the message is currently listening for a Note On type, it will switch to Note Off type when it is received, and viceversa). + +If the *Toggle Velocity* option is checked, the message will toggle the velocity between 127 and 0 every time that it is received. ### Actions @@ -78,11 +80,11 @@ In addition, some of the fields can use the message value field as their value. When a number field is in *Fixed* mode, the plugin will use the value that is displayed in the number field for that action. -For some actions, an *Ignore* mode and a *127-0* mode are present in number fields. +For some actions, a *Custom* mode and an *Ignore* mode are present in number fields. -In *Ignore* mode, the plugin will ignore the value in that field while setting other values in other fields. This is the most useful when having to set two values (e.g. Move Source or Source Scale), but only one needs to change. +In *Custom* mode, the plugin will use the message value according to the boundary specified in the action. The top number corresponds to a MIDI value of 0, and the bottom number corresponds to a MIDI value of 127. This allows for more control over the range of those actions that support it. -In *127-0* mode, plugin will use the message value according to the action specification, but in reverse. This is most useful with actions like Rotate Source where rotating the opposite direction is desired. +In *Ignore* mode, the plugin will ignore the value in that field while setting other values in other fields. This is the most useful when having to set two values (e.g. Move Source or Source Scale), but only one needs to change. ---------------------------------------------------- @@ -96,7 +98,7 @@ In *127-0* mode, plugin will use the message value according to the action speci ### Streaming -| Subcategory | Description| List Fields | Number Fields | +| Subcategory | Description | List Fields | Number Fields | |---|---|---|---| | Start Streaming | Starts the stream if it is not running. | *N/A* | *N/A* | | Stop Streaming | Stops the stream if it is running. | *N/A* | *N/A* | @@ -170,6 +172,7 @@ All Video Sources actions contain these list fields in addition to the fields li | Align Source Bounding Box | Changes the alignment of the bounding box of the source on the current scene. | **ALIGNMENT [2](#using-the-message-value)**: The alignment state of the bounding box of the source. | *N/A* | | Source Blending Mode | Changes the blending mode of the source on the current scene. | **BLEND MODE [2](#using-the-message-value)**: The blend mode state of the source. | *N/A* | | Take Source Screenshot | Takes a screenshot of the source. | *N/A* | *N/A* | +| Custom Source Settings | Changes custom properties of a source. | *N/A* | *N/A* | ### Audio Sources @@ -213,7 +216,9 @@ All Media Sources actions contain these list fields in addition to the fields li | Change Current Transition | Sets the current transition. | **TRANSITION**: The name of the transition to use. | **DURATION [7](#using-the-message-value)**: The duration of the transition selected. If a value of 0 is used, the plugin will use the current transition duration. | | Set Source Show Transition | Sets the transition that will be used when a source becomes shown. | **SCENE**: The name of the scene containing the video source.
**SOURCE**: The name of the source to set the transition to.
**TRANSITION**: The name of the transition to use. | **DURATION [6](#using-the-message-value)**: The duration of the transition selected. | | Set Source Hide Transition | Sets the transition that will be used when a source becomes hidden. | **SCENE**: The name of the scene containing the video source.
**SOURCE**: The name of the source to set the transition to.
**TRANSITION**: The name of the transition to use. | **DURATION [6](#using-the-message-value)**: The duration of the transition selected. | -| Set Transition Bar (Studio Mode) | Sets the transition bar position.
Fails if not in Studio Mode. | *N/A* | **POSITION [5](#using-the-message-value)**: The percent transitioned by the transition bar. | +| Set Transition Bar Position (Studio Mode) | Sets the transition bar position. This will automatically release the transition bar after 1 second of inactivity.
Fails if not in Studio Mode. | *N/A* | **POSITION [5](#using-the-message-value)**: The amount transitioned by the transition bar. | +| Release Transition Bar (Studio Mode) | Releases the transition bar manually.
Fails if not in Studio Mode. | *N/A* | *N/A* | +| Custom Transition Settings | Changes custom properties of a transition. | *N/A* | *N/A* | ### Filters @@ -258,22 +263,26 @@ All Filters actions contain these list fields in addition to the fields listed i ### Internal -| Subcategory | Description | List Fields | Number Fields | -|---|---|---|---| -| Do 1 Action | Executes one other action in the current device. | **ACTION 1**: The name of the binding containing the action to execute. | *N/A* | -| Do 2 Actions | Executes two other actions in the current device. | **ACTION 1**: The name of the binding containing the first action to execute.
**ACTION 2**: The name of the binding containing the second action to execute. | *N/A* | -| Do 3 Actions | Executes three other actions in the current device. | **ACTION 1**: The name of the binding containing the first action to execute.
**ACTION 2**: The name of the binding containing the second action to execute.
**ACTION 3**: The name of the binding containing the third action to execute. | *N/A* | +An unlimited number of actions can now be executed using the *Internal* action. Add actions using the *Add Action* button, and switch between actions by incrementing the *Action #* indicator. Actions cannot be moved around once they are placed, so ensure that the actions are placed in the correct order - Action #1 being first. To remove any action, use the *Remove Action* button. + +Actions can be configured to execute certain time intervals after a previous action. The *Timing* option has three choices: -Note 1: Actions can only be executed once. Any action that is executed more than once will be skipped.
-Note 2: Actions executed in this manner bypass disabled bindings, meaning that an action in a disabled binding can be executed.
-Note 3: If a selected action is using the message for any of its values, that action will use the message sent to the *Internal* action. +- **Execute as soon as possible** -> The action will execute as soon as either the *Internal* action begins executing or when the previous action finishes executing if it is not Action #1. +- **Wait (ms) before executing** -> The action will execute after some amount of milliseconds have passed. This can be chained, meaning Action #5 may take a while to execute if all the actions have this choice selected. +- **Wait (s) before executing** -> The action will execute after some amount of seconds have passed. This can be chained, meaning Action #5 may take a *long* while to execute if all the actions have this choice selected. + +The timing range is set between 1 and 999 of the specified units. + +**Note 1**: Actions can now be executed without limits. This changes the policy from when an action could only be executed once.
+**Note 2**: Actions executed in this manner bypass disabled bindings, meaning that an action in a disabled binding can be executed.
+**Note 3**: If a selected action is using the message for any of its values, that action will use the message sent to the *Internal* action.
+**Note 4**: Other *Internal* actions cannot be executed in an *Internal* action. (Just make another binding if this behavior is desired.)
+**Note 5**: Only up to 64 *Internal* actions can be running at any one time. Further attempts to run an *Internal* action will fail until one of the 64 running actions finishes.
+**Note 6**: Try not to have 64 *Internal* actions running at once, if possible. Your computer will thank you later. ### Timeout -| Subcategory | Description | List Fields | Number Fields | -|---|---|---|---| -| Wait in Milliseconds | Waits the specified amount of time in milliseconds. | *N/A* | **TIME [7](#using-the-message-value)**: The number of milliseconds to wait. | -| Wait in Seconds | Waits the specified amount of time in seconds. | *N/A* | **TIME [7](#using-the-message-value)**: The number of seconds to wait. | +This category is discontinued as it has moved inside of the *Internal* action. ---------------------------------------------------- @@ -309,6 +318,12 @@ If any fields are marked with a subscript listed below, they are eligible to be If this option is toggled off, it will turn off all communication between the plugin and the active device. +### MIDI Throughput + +When this option is enabled, all messages received by the active device will automatically be routed to the output device specified. + +This may cause a feedback loop if the output device is configured to send messages to the *Active Device*. Feedback loops lead to undesired behavior, and occasionally cause crashes, so be careful when using this feature. + ### Binding Transfer Transferring bindings is useful when a lot of bindings cannot be used since they are on another device that is disconnected or unavailable. All information is provided in the plugin itself. diff --git a/README.md b/README.md index 8fd111f..7247cb8 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Go to the [Releases page](https://github.com/nhielost/obs-midi-mg/releases) and If help is needed, click the *Help* button in the plugin, or click [here](HELP.md). +Some users may find that they want to have multiple programs monitor the same MIDI device (for example, OBS Studio and Ableton). To do this, install a MIDI routing software such as [loopMIDI](https://www.tobias-erichsen.de/software/loopmidi.html) or [BOME MIDI Translator](https://www.bome.com/products/mtclassic). This will allow for virtual devices to be created, and, when configured, multiple programs can connect to these seamlessly. + I would love to hear honestly from you about this plugin. Feel free to share some ideas and don't be afraid to [report an issue](https://github.com/nhielost/obs-midi-mg/issues) or [post on the OBS forum discussion](https://obsproject.com/forum/threads/obs-midi-mg.158407/)! ## Credits diff --git a/buildspec.json b/buildspec.json index c268e49..978d19b 100644 --- a/buildspec.json +++ b/buildspec.json @@ -79,5 +79,7 @@ } }, "name": "obs-midi-mg", - "version": "2.2.0" + "version": "2.3.0", + "author": "nhielost", + "email": "nhielost@gmail.com" } diff --git a/cmake/ObsPluginHelpers.cmake b/cmake/ObsPluginHelpers.cmake index 5e30535..e3144b4 100644 --- a/cmake/ObsPluginHelpers.cmake +++ b/cmake/ObsPluginHelpers.cmake @@ -563,7 +563,7 @@ else() set(OBS_PLUGIN64_DESTINATION "obs-plugins/64bit") set(OBS_DATA_DESTINATION "data") - + #[[ if(MSVC) # Set default Visual Studio CL.exe compile options. # @@ -595,6 +595,7 @@ else() /D_UNICODE /D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_WARNINGS) + # Set default Visual Studio linker options. # # * Enable removal of functions and data that are never used, @@ -617,7 +618,7 @@ else() "$<$:LINKER\:/INCREMENTAL\:NO>" "$<$:LINKER\:/INCREMENTAL\:NO;/OPT\:ICF>") endif() - + #]] endif() # Helper function for plugin targets (Windows and Linux version) diff --git a/cmake/bundle/windows/resource.rc.in b/cmake/bundle/windows/resource.rc.in new file mode 100644 index 0000000..fc4a5b4 --- /dev/null +++ b/cmake/bundle/windows/resource.rc.in @@ -0,0 +1,32 @@ +1 VERSIONINFO + FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0 + PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},0 + FILEFLAGSMASK 0x0L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x0L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "${PLUGIN_AUTHOR}" + VALUE "FileDescription", "${PROJECT_NAME}" + VALUE "FileVersion", "${PROJECT_VERSION}" + VALUE "InternalName", "${PROJECT_NAME}" + VALUE "LegalCopyright", "(C) ${PLUGIN_AUTHOR}" + VALUE "OriginalFilename", "${PROJECT_NAME}" + VALUE "ProductName", "${PROJECT_NAME}" + VALUE "ProductVersion", "${PROJECT_VERSION}" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/actions/mmg-action-audio-sources.cpp b/src/actions/mmg-action-audio-sources.cpp index c7edf90..922d53f 100644 --- a/src/actions/mmg-action-audio-sources.cpp +++ b/src/actions/mmg-action-audio-sources.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,9 @@ with this program. If not, see using namespace MMGUtils; +const QStringList MMGActionAudioSources::audio_monitor_options{ + "Off", "Monitor Only", "Monitor & Output", "Use Message Value"}; + MMGActionAudioSources::MMGActionAudioSources(const QJsonObject &json_obj) : source(json_obj, "source", 1), action(json_obj, "action", 2), num(json_obj, "num", 1) { @@ -35,14 +38,14 @@ void MMGActionAudioSources::blog(int log_status, const QString &message) const void MMGActionAudioSources::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + source.json(json_obj, "source", false); - action.json(json_obj, "action", true); - num.json(json_obj, "num", true); + action.json(json_obj, "action"); + num.json(json_obj, "num"); } -void MMGActionAudioSources::do_action(const MMGMessage *midi) +void MMGActionAudioSources::execute(const MMGMessage *midi) const { OBSSourceAutoRelease obs_source = obs_get_source_by_name(source.mmgtocs()); if (!obs_source) { @@ -54,17 +57,10 @@ void MMGActionAudioSources::do_action(const MMGMessage *midi) return; } - switch (get_sub()) { + switch (sub()) { case MMGActionAudioSources::SOURCE_AUDIO_VOLUME_CHANGETO: - // Value only has range 0-127, meaning full volume cannot be achieved (as it is divided by 128). - // Adding one allows the capability of full volume. - // ADDITION: Removing 1% from the value and changing it to 0% for muting is not as noticable. - if (num.state() == MMGNumber::NUMBERSTATE_MIDI && midi->value() == 0) { - obs_source_set_volume(obs_source, 0); - } else { - obs_source_set_volume(obs_source, - std::pow((num.choose(midi, 0, 100.0, true) / 100.0), 3.0)); - } + // Now divided by 127, no need for adding one + obs_source_set_volume(obs_source, std::pow((num.choose(midi) / 100.0), 3.0)); break; case MMGActionAudioSources::SOURCE_AUDIO_VOLUME_CHANGEBY: if (std::cbrt(obs_source_get_volume(obs_source)) * 100.0 + num >= 100.0) { @@ -86,32 +82,40 @@ void MMGActionAudioSources::do_action(const MMGMessage *midi) obs_source_set_muted(obs_source, !obs_source_muted(obs_source)); break; case MMGActionAudioSources::SOURCE_AUDIO_OFFSET: - // Multiplier is 3200 here to make it so that the sync offset is incremented by 25. - // Hard limit is set at 3175 ms - obs_source_set_sync_offset(obs_source, (num.choose(midi, 0, 3200.0) * 1000000)); + obs_source_set_sync_offset(obs_source, (num.choose(midi) * 1000000)); break; case MMGActionAudioSources::SOURCE_AUDIO_MONITOR: - if (MIDI_NUMBER_IS_NOT_IN_RANGE(num, 3)) { + if (MIDI_STRING_IS_NOT_IN_RANGE(action, 3)) { blog(LOG_INFO, "FAILED: MIDI value exceeds audio monitor option count."); return; } - obs_source_set_monitoring_type(obs_source, (obs_monitoring_type)(num.choose(midi))); - break; - case MMGActionAudioSources::SOURCE_AUDIO_CUSTOM: + obs_source_set_monitoring_type( + obs_source, (obs_monitoring_type)(action.state() ? midi->value() + : audio_monitor_options.indexOf(action))); break; default: break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionAudioSources::deep_copy(MMGAction *dest) const +void MMGActionAudioSources::copy(MMGAction *dest) const +{ + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->source = source.copy(); + casted->action = action.copy(); + casted->num = num.copy(); +} + +void MMGActionAudioSources::setEditable(bool edit) { - dest->set_sub(subcategory); - source.copy(&dest->str1()); - action.copy(&dest->str2()); - num.copy(&dest->num1()); + source.set_edit(edit); + action.set_edit(edit); + num.set_edit(edit); } const QStringList MMGActionAudioSources::enumerate() @@ -131,74 +135,93 @@ const QStringList MMGActionAudioSources::enumerate() return list; } -void MMGActionAudioSources::change_options_sub(MMGActionDisplayParams &val) +void MMGActionAudioSources::createDisplay(QWidget *parent) { - val.list = {"Change Source Volume To", "Change Source Volume By", "Mute Source", - "Unmute Source", "Toggle Source Mute", "Source Audio Offset", - "Source Audio Monitor"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&source); + _display->setStr2Storage(&action); + + MMGNumberDisplay *num_display = new MMGNumberDisplay(_display->numberDisplays()); + num_display->setStorage(&num, true); + _display->numberDisplays()->add(num_display); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); + _display->connect(_display, &MMGActionDisplay::str2Changed, [&]() { setList2Config(); }); } -void MMGActionAudioSources::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionAudioSources::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Audio Source"; - val.list = enumerate(); + sub->addItems({"Change Source Volume To", "Change Source Volume By", "Mute Source", + "Unmute Source", "Toggle Source Mute", "Source Audio Offset", + "Source Audio Monitor"}); } -void MMGActionAudioSources::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionAudioSources::setSubConfig() { - switch ((Actions)subcategory) { - case SOURCE_AUDIO_VOLUME_CHANGETO: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; + _display->setStr1Visible(false); + _display->setStr2Visible(false); + _display->setStr3Visible(false); - val.label_lcds[0] = "Volume (%)"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI | - MMGActionDisplayParams::COMBODISPLAY_MIDI_INVERT; - val.lcds[0]->set_range(0.0, 100.0); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); + _display->setStr1Visible(true); + _display->setStr1Description("Audio Source"); + _display->setStr1Options(enumerate()); +} - break; - case SOURCE_AUDIO_VOLUME_CHANGEBY: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; +void MMGActionAudioSources::setList1Config() +{ + MMGNumberDisplay *num_display = _display->numberDisplays()->fieldAt(0); + num_display->setVisible(false); - val.label_lcds[0] = "Volume (%)"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_FIXED; - val.lcds[0]->set_range(-50.0, 50.0); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); + switch ((Actions)subcategory) { + case SOURCE_AUDIO_VOLUME_CHANGETO: + num_display->setVisible(!source.str().isEmpty()); + num_display->setDescription("Volume (%)"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_CUSTOM); + num_display->setBounds(0.0, 100.0); + num_display->setStep(0.5); + num_display->setDefaultValue(0.0); + break; + case SOURCE_AUDIO_VOLUME_CHANGEBY: + num_display->setVisible(!source.str().isEmpty()); + num_display->setDescription("Volume (%)"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_FIXED_ONLY); + num_display->setBounds(-50.0, 50.0); + num_display->setStep(0.5); + num_display->setDefaultValue(0.0); break; + case SOURCE_AUDIO_VOLUME_MUTE_ON: case SOURCE_AUDIO_VOLUME_MUTE_OFF: case SOURCE_AUDIO_VOLUME_MUTE_TOGGLE_ONOFF: - break; - case SOURCE_AUDIO_OFFSET: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; - - val.label_lcds[0] = "Offset"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI; - val.lcds[0]->set_range(0.0, 20000.0); - val.lcds[0]->set_step(25.0, 250.0); - val.lcds[0]->set_default_value(0.0); + return; + case SOURCE_AUDIO_OFFSET: + num_display->setVisible(!source.str().isEmpty()); + num_display->setDescription("Offset"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_CUSTOM); + num_display->setBounds(0.0, 20000.0); + num_display->setStep(25.0); + num_display->setDefaultValue(0.0); break; - case SOURCE_AUDIO_MONITOR: - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.label_text = "Audio Monitor"; - val.list = {"Off", "Monitor Only", "Monitor & Output", "Use Message Value"}; + case SOURCE_AUDIO_MONITOR: + _display->setStr2Visible(true); + _display->setStr2Description("Audio Monitor"); + _display->setStr2Options(audio_monitor_options); + return; - break; - case SOURCE_AUDIO_CUSTOM: default: - break; + return; } + num_display->reset(); } -void MMGActionAudioSources::change_options_str3(MMGActionDisplayParams &val) + +void MMGActionAudioSources::setList2Config() { - if (subcategory == 6) { - num = val.list.indexOf(action); - num.set_state(action == "Use Message Value" ? MMGNumber::NUMBERSTATE_MIDI - : MMGNumber::NUMBERSTATE_FIXED); + if (subcategory == SOURCE_AUDIO_MONITOR) { + num = audio_monitor_options.indexOf(action); + num.set_state(action.state()); } } -void MMGActionAudioSources::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-audio-sources.h b/src/actions/mmg-action-audio-sources.h index e7094c7..3182037 100644 --- a/src/actions/mmg-action-audio-sources.h +++ b/src/actions/mmg-action-audio-sources.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,29 +30,18 @@ class MMGActionAudioSources : public MMGAction { SOURCE_AUDIO_VOLUME_MUTE_OFF, SOURCE_AUDIO_VOLUME_MUTE_TOGGLE_ONOFF, SOURCE_AUDIO_OFFSET, - SOURCE_AUDIO_MONITOR, - SOURCE_AUDIO_CUSTOM + SOURCE_AUDIO_MONITOR }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_SOURCE_AUDIO; } - - MMGUtils::MMGString &str1() override { return source; }; - const MMGUtils::MMGString &str1() const override { return source; }; - MMGUtils::MMGString &str2() override { return action; }; - const MMGUtils::MMGString &str2() const override { return action; }; - MMGUtils::MMGNumber &num1() override { return num; }; - const MMGUtils::MMGNumber &num1() const override { return num; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_SOURCE_AUDIO; } static const QStringList enumerate(); @@ -60,4 +49,10 @@ class MMGActionAudioSources : public MMGAction { MMGUtils::MMGString source; MMGUtils::MMGString action; MMGUtils::MMGNumber num; + + void setSubConfig() override; + void setList1Config() override; + void setList2Config() override; + + static const QStringList audio_monitor_options; }; diff --git a/src/actions/mmg-action-collections.cpp b/src/actions/mmg-action-collections.cpp index e7a8578..48344be 100644 --- a/src/actions/mmg-action-collections.cpp +++ b/src/actions/mmg-action-collections.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,12 +35,12 @@ void MMGActionCollections::blog(int log_status, const QString &message) const void MMGActionCollections::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); - collection.json(json_obj, "collection", true); + MMGAction::json(json_obj); + + collection.json(json_obj, "collection"); } -void MMGActionCollections::do_action(const MMGMessage *midi) +void MMGActionCollections::execute(const MMGMessage *midi) const { const QStringList collections = MMGActionCollections::enumerate(); @@ -66,7 +66,7 @@ void MMGActionCollections::do_action(const MMGMessage *midi) &name, true); }; - if (get_sub() == 0) { + if (sub() == 0) { set_collection((collection.state() == MMGString::STRINGSTATE_MIDI ? collections[(int)midi->value()] : collection) @@ -74,13 +74,21 @@ void MMGActionCollections::do_action(const MMGMessage *midi) } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionCollections::deep_copy(MMGAction *dest) const +void MMGActionCollections::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - collection.copy(&dest->str1()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->collection = collection.copy(); +} + +void MMGActionCollections::setEditable(bool edit) +{ + collection.set_edit(edit); } const QStringList MMGActionCollections::enumerate() @@ -96,21 +104,23 @@ const QStringList MMGActionCollections::enumerate() return list; } -void MMGActionCollections::change_options_sub(MMGActionDisplayParams &val) +void MMGActionCollections::createDisplay(QWidget *parent) { - val.list = {"Switch Scene Collections"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&collection); } -void MMGActionCollections::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionCollections::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Scene Collection"; - val.list = enumerate(); - val.list.append("Use Message Value"); + sub->addItem("Switch Scene Collections"); } -void MMGActionCollections::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionCollections::setSubConfig() { - collection.set_state(collection == "Use Message Value" ? MMGString::STRINGSTATE_MIDI - : MMGString::STRINGSTATE_FIXED); + _display->setStr1Visible(true); + _display->setStr1Description("Scene Collection"); + QStringList options = enumerate(); + options.append("Use Message Value"); + _display->setStr1Options(options); } -void MMGActionCollections::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionCollections::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-collections.h b/src/actions/mmg-action-collections.h index c2175cd..86b73e2 100644 --- a/src/actions/mmg-action-collections.h +++ b/src/actions/mmg-action-collections.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,23 +26,19 @@ class MMGActionCollections : public MMGAction { enum Actions { COLLECTION_COLLECTION }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_COLLECTION; } - - MMGUtils::MMGString &str1() override { return collection; }; - const MMGUtils::MMGString &str1() const override { return collection; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_COLLECTION; } static const QStringList enumerate(); private: MMGUtils::MMGString collection; + + void setSubConfig() override; }; diff --git a/src/actions/mmg-action-filters.cpp b/src/actions/mmg-action-filters.cpp index ebd7222..3244f4d 100644 --- a/src/actions/mmg-action-filters.cpp +++ b/src/actions/mmg-action-filters.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,8 +24,8 @@ using namespace MMGUtils; MMGActionFilters::MMGActionFilters(const QJsonObject &json_obj) : source(json_obj, "source", 1), filter(json_obj, "filter", 2), - json_str(json_obj, "json", 0), - num(json_obj, "num", 1) + num(json_obj, "num", 1), + json_str(json_obj, "json", 0) { subcategory = json_obj["sub"].toInt(); @@ -39,15 +39,15 @@ void MMGActionFilters::blog(int log_status, const QString &message) const void MMGActionFilters::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + source.json(json_obj, "source", false); filter.json(json_obj, "filter", false); - num.json(json_obj, "num", true); + num.json(json_obj, "num"); json_str.json(json_obj, "json", false); } -void MMGActionFilters::do_action(const MMGMessage *midi) +void MMGActionFilters::execute(const MMGMessage *midi) const { OBSSourceAutoRelease obs_source = obs_get_source_by_name(source.mmgtocs()); OBSSourceAutoRelease obs_filter = obs_source_get_filter_by_name(obs_source, filter.mmgtocs()); @@ -55,12 +55,8 @@ void MMGActionFilters::do_action(const MMGMessage *midi) blog(LOG_INFO, "FAILED: Filter in source does not exist."); return; } - OBSDataAutoRelease filter_data = obs_source_get_settings(obs_filter); - QJsonObject filter_json = json_from_str(obs_data_get_json(filter_data)); - QJsonObject action_json = json_from_str(json_str.mmgtocs()); - QJsonObject final_json; - switch (get_sub()) { + switch (sub()) { case MMGActionFilters::FILTER_SHOW: obs_source_set_enabled(obs_filter, true); break; @@ -71,68 +67,43 @@ void MMGActionFilters::do_action(const MMGMessage *midi) obs_source_set_enabled(obs_filter, !obs_source_enabled(obs_filter)); break; case MMGActionFilters::FILTER_REORDER: - if (num.choose(midi, 0, 128, true) - 1 >= obs_source_filter_count(obs_source)) { + if (num.choose(midi) - 1 >= obs_source_filter_count(obs_source)) { blog(LOG_INFO, "FAILED: MIDI value exceeds filter count in source."); return; } obs_source_filter_set_order(obs_source, obs_filter, OBS_ORDER_MOVE_TOP); - for (int i = 0; i < num.choose(midi, 0, 128, true) - 1; ++i) { + for (int i = 0; i < num.choose(midi) - 1; ++i) { obs_source_filter_set_order(obs_source, obs_filter, OBS_ORDER_MOVE_DOWN); } break; case MMGActionFilters::FILTER_CUSTOM: - for (const QString &key : action_json.keys()) { - if (key.endsWith("_state")) continue; - QString _key = key.endsWith("_state_") ? key.chopped(1) : key; - - vec3 bounds = get_obs_filter_property_bounds(obs_filter, _key); - QHash options = get_obs_filter_property_options(obs_filter, _key); - switch (action_json[key + "_state"].toInt()) { - case 1: // MIDI - switch (action_json[key].type()) { - case QJsonValue::Double: - final_json[_key] = (midi->value() / 127) * (bounds.y - bounds.x) + bounds.x; - break; - case QJsonValue::String: - if (midi->value() >= options.keys().size()) break; - final_json[_key] = options.keys()[(int)midi->value()]; - break; - } - break; - case 2: // INVERSE or TOGGLE - switch (action_json[key].type()) { - case QJsonValue::Double: - final_json[_key] = (1 - (midi->value() / 127)) * (bounds.y - bounds.x) + bounds.x; - break; - case QJsonValue::Bool: - final_json[_key] = !filter_json[_key].toBool(); - break; - } - break; - case 3: // IGNORE - break; - default: // NORMAL - final_json[_key] = action_json[key]; - break; - } - } - obs_source_update(obs_filter, - OBSDataAutoRelease(obs_data_create_from_json(json_to_str(final_json)))); + obs_source_custom_update(obs_filter, json_from_str(json_str.mmgtocs()), midi); break; default: break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionFilters::deep_copy(MMGAction *dest) const +void MMGActionFilters::copy(MMGAction *dest) const +{ + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->source = source.copy(); + casted->filter = filter.copy(); + casted->json_str = json_str.copy(); + casted->num = num.copy(); +} + +void MMGActionFilters::setEditable(bool edit) { - dest->set_sub(subcategory); - source.copy(&dest->str1()); - filter.copy(&dest->str2()); - json_str.copy(&dest->str3()); - num.copy(&dest->num1()); + source.set_edit(edit); + filter.set_edit(edit); + json_str.set_edit(edit); + num.set_edit(edit); } const QStringList MMGActionFilters::enumerate(const QString &source) @@ -160,7 +131,13 @@ const QStringList MMGActionFilters::enumerate_eligible() auto _list = reinterpret_cast(param); if (obs_obj_is_private(source)) return true; - if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) return true; + if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) { + // For Group Sources + if (obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE || + _list->contains(obs_source_get_name(source))) { + return true; + } + } _list->append(obs_source_get_name(source)); return true; @@ -170,45 +147,78 @@ const QStringList MMGActionFilters::enumerate_eligible() return list; } -void MMGActionFilters::change_options_sub(MMGActionDisplayParams &val) +void MMGActionFilters::createDisplay(QWidget *parent) { - val.list = {"Show Filter", "Hide Filter", "Toggle Filter Display", "Reorder Filter Appearance", - "Custom Filter Settings"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&source); + _display->setStr2Storage(&filter); + + MMGNumberDisplay *num_display = new MMGNumberDisplay(_display->numberDisplays()); + num_display->setStorage(&num, true); + _display->numberDisplays()->add(num_display); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); + _display->connect(_display, &MMGActionDisplay::str2Changed, [&]() { setList2Config(); }); } -void MMGActionFilters::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionFilters::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Source"; - val.list = enumerate_eligible(); + sub->addItems({"Show Filter", "Hide Filter", "Toggle Filter Display", "Reorder Filter Appearance", + "Custom Filter Settings"}); } -void MMGActionFilters::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionFilters::setSubConfig() { - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.label_text = "Filter"; - val.list = enumerate(source); + _display->setStr1Visible(false); + _display->setStr2Visible(false); + _display->setStr3Visible(false); + + _display->setStr1Visible(true); + _display->setStr1Description("Source"); + _display->setStr1Options(enumerate_eligible()); } -void MMGActionFilters::change_options_str3(MMGActionDisplayParams &val) + +void MMGActionFilters::setList1Config() { + _display->setStr2Visible(true); + _display->setStr2Description("Filter"); + _display->setStr2Options(enumerate(source)); +} + +void MMGActionFilters::setList2Config() +{ + _display->resetScrollWidget(); + + MMGNumberDisplay *num_display = _display->numberDisplays()->fieldAt(0); + num_display->setVisible(false); + + OBSSourceAutoRelease obs_source; + OBSSourceAutoRelease obs_filter; + switch ((Actions)subcategory) { case FILTER_SHOW: case FILTER_HIDE: case FILTER_TOGGLE_SHOWHIDE: break; - case FILTER_REORDER: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; - - val.label_lcds[0] = "Position"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI; - val.lcds[0]->set_range(1.0, enumerate(source).size()); - val.lcds[0]->set_step(1.0, 5.0); - val.lcds[0]->set_default_value(1.0); + case FILTER_REORDER: + num_display->setVisible(!filter.str().isEmpty()); + num_display->setDescription("Position"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_DEFAULT); + num_display->setBounds(1.0, enumerate(source).size()); + num_display->setStep(1.0); + num_display->setDefaultValue(1.0); + num_display->reset(); break; + case FILTER_CUSTOM: - val.display |= MMGActionDisplayParams::DISPLAY_SEC; + obs_source = obs_get_source_by_name(source.mmgtocs()); + obs_filter = obs_source_get_filter_by_name(obs_source, filter.mmgtocs()); + emit _display->customFieldRequest(obs_filter, &json_str); break; + default: - break; + return; } } -void MMGActionFilters::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-filters.h b/src/actions/mmg-action-filters.h index adaffab..ea98b63 100644 --- a/src/actions/mmg-action-filters.h +++ b/src/actions/mmg-action-filters.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,26 +26,14 @@ class MMGActionFilters : public MMGAction { enum Actions { FILTER_SHOW, FILTER_HIDE, FILTER_TOGGLE_SHOWHIDE, FILTER_REORDER, FILTER_CUSTOM }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_FILTER; } - - MMGUtils::MMGString &str1() override { return source; }; - const MMGUtils::MMGString &str1() const override { return source; }; - MMGUtils::MMGString &str2() override { return filter; }; - const MMGUtils::MMGString &str2() const override { return filter; }; - MMGUtils::MMGNumber &num1() override { return num; }; - const MMGUtils::MMGNumber &num1() const override { return num; }; - MMGUtils::MMGString &str3() override { return json_str; }; - const MMGUtils::MMGString &str3() const override { return json_str; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_FILTER; } static const QStringList enumerate(const QString &source = ""); static const QStringList enumerate_eligible(); @@ -55,4 +43,8 @@ class MMGActionFilters : public MMGAction { MMGUtils::MMGString filter; MMGUtils::MMGNumber num; MMGUtils::MMGString json_str; + + void setSubConfig() override; + void setList1Config() override; + void setList2Config() override; }; diff --git a/src/actions/mmg-action-hotkeys.cpp b/src/actions/mmg-action-hotkeys.cpp index 0b4ce7d..758fb6a 100644 --- a/src/actions/mmg-action-hotkeys.cpp +++ b/src/actions/mmg-action-hotkeys.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -53,13 +53,13 @@ void MMGActionHotkeys::blog(int log_status, const QString &message) const void MMGActionHotkeys::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + hotkey_group.json(json_obj, "hotkey_group", false); hotkey.json(json_obj, "hotkey", false); } -void MMGActionHotkeys::do_action(const MMGMessage *midi) +void MMGActionHotkeys::execute(const MMGMessage *midi) const { Q_UNUSED(midi); struct HotkeyRequestBody { @@ -110,7 +110,7 @@ void MMGActionHotkeys::do_action(const MMGMessage *midi) return; } - switch (get_sub()) { + switch (sub()) { case MMGActionHotkeys::HOTKEY_PREDEF: obs_queue_task( OBS_TASK_UI, @@ -126,15 +126,23 @@ void MMGActionHotkeys::do_action(const MMGMessage *midi) break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionHotkeys::deep_copy(MMGAction *dest) const +void MMGActionHotkeys::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - hotkey_group.copy(&dest->str1()); - hotkey_desc.copy(&dest->str2()); - hotkey.copy(&dest->str3()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->hotkey_group = hotkey_group.copy(); + casted->hotkey = hotkey.copy(); +} + +void MMGActionHotkeys::setEditable(bool edit) +{ + hotkey_group.set_edit(edit); + hotkey.set_edit(edit); } const QStringList MMGActionHotkeys::enumerate_names(const QString &category) @@ -258,21 +266,35 @@ const QStringList MMGActionHotkeys::enumerate_eligible() return list; } -void MMGActionHotkeys::change_options_sub(MMGActionDisplayParams &val) +void MMGActionHotkeys::createDisplay(QWidget *parent) +{ + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&hotkey_group); + _display->setStr2Storage(&hotkey); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); +} + +void MMGActionHotkeys::setSubOptions(QComboBox *sub) { - val.list = {"Activate Predefined Hotkey"}; + sub->addItem("Activate Predefined Hotkey"); } -void MMGActionHotkeys::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionHotkeys::setSubConfig() { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Group"; - val.list = enumerate_eligible(); + _display->setStr1Visible(false); + _display->setStr2Visible(false); + _display->setStr3Visible(false); + + _display->setStr1Visible(true); + _display->setStr1Description("Group"); + _display->setStr1Options(enumerate_eligible()); } -void MMGActionHotkeys::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionHotkeys::setList1Config() { - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.label_text = "Hotkey"; - val.list = enumerate_descriptions(hotkey_group); + _display->setStr2Visible(true); + _display->setStr2Description("Hotkey"); + _display->setStr2Options(enumerate_descriptions(hotkey_group)); } -void MMGActionHotkeys::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionHotkeys::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-hotkeys.h b/src/actions/mmg-action-hotkeys.h index fabb5ad..eb74f15 100644 --- a/src/actions/mmg-action-hotkeys.h +++ b/src/actions/mmg-action-hotkeys.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,22 +26,14 @@ class MMGActionHotkeys : public MMGAction { enum Actions { HOTKEY_PREDEF }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_HOTKEY; } - - MMGUtils::MMGString &str1() override { return hotkey_group; }; - const MMGUtils::MMGString &str1() const override { return hotkey_group; }; - MMGUtils::MMGString &str2() override { return hotkey; }; - const MMGUtils::MMGString &str2() const override { return hotkey; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_HOTKEY; } static const QStringList enumerate_names(const QString &category); static const QStringList enumerate_descriptions(const QString &category); @@ -49,6 +41,8 @@ class MMGActionHotkeys : public MMGAction { private: MMGUtils::MMGString hotkey_group; - MMGUtils::MMGString hotkey_desc; MMGUtils::MMGString hotkey; + + void setSubConfig() override; + void setList1Config() override; }; diff --git a/src/actions/mmg-action-internal.cpp b/src/actions/mmg-action-internal.cpp index 508f6a0..7533221 100644 --- a/src/actions/mmg-action-internal.cpp +++ b/src/actions/mmg-action-internal.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,12 +19,98 @@ with this program. If not, see #include "mmg-action-internal.h" #include "../mmg-config.h" +#include +#include + using namespace MMGUtils; +short MMGActionInternal::thread_count = 0; + +// MMGActionManager +MMGActionManager::MMGActionManager() +{ + _time.set_num(1); + _time.set_min(1); + _time.set_max(999); +} + +MMGActionManager::MMGActionManager(const QJsonObject &json_obj) : MMGActionManager() +{ + binding_name = json_obj["action"].toString(); + before_action = json_obj["pre"].toInt(); + _time = json_obj["time"].toInt(); +} + +void MMGActionManager::json(QJsonObject &json_obj) +{ + json_obj["action"] = binding_name; + json_obj["pre"] = before_action; + json_obj["time"] = (int)_time; +}; + +void MMGActionManager::copy(MMGActionManager *other) const +{ + other->before_action = before_action; + other->binding_name = binding_name; + other->_time = _time.copy(); +} + +void MMGActionManager::execute(const MMGMessage *incoming) const +{ + MMGDevice *device = global()->currentDevice(); + if (!device) return; + + MMGBinding *binding = device->find(binding_name); + if (!binding) return; + + if (binding->action()->category() == MMGAction::MMGACTION_INTERNAL) { + binding->action()->blog(LOG_INFO, "FAILED: Cannot execute another action."); + return; + } + + switch (before_action) { + case 1: // Milliseconds + std::this_thread::sleep_for((std::chrono::milliseconds)_time); + break; + case 2: // Seconds + std::this_thread::sleep_for((std::chrono::seconds)_time); + break; + case 0: // As soon as possible + break; + default: + return; + } + + binding->action()->execute(incoming); +} +// End MMGActionManager + +MMGActionInternal::MMGActionInternal() +{ + blog(LOG_DEBUG, "Empty action created."); +}; + MMGActionInternal::MMGActionInternal(const QJsonObject &json_obj) - : actions{{json_obj, "action1", 1}, {json_obj, "action2", 2}, {json_obj, "action3", 3}} { - subcategory = json_obj["sub"].toInt(); + if (json_obj.contains("str1")) { + for (int i = 0; i < 3; ++i) { + QString name = json_obj[num_to_str(i + 1, "str")].toString(); + if (name.isEmpty()) continue; + actions.append(new MMGActionManager); + actions.last()->setBinding(name); + } + } else if (json_obj.contains("action1")) { + for (int i = 0; i < 3; ++i) { + QString name = json_obj[num_to_str(i + 1, "action")].toString(); + if (name.isEmpty()) continue; + actions.append(new MMGActionManager); + actions.last()->setBinding(name); + } + } else if (json_obj["actions"].isArray()) { + for (const QJsonValue &manager : json_obj["actions"].toArray()) + actions.append(new MMGActionManager(manager.toObject())); + } + subcategory = 0; blog(LOG_DEBUG, " action created."); } @@ -36,68 +122,227 @@ void MMGActionInternal::blog(int log_status, const QString &message) const void MMGActionInternal::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); - for (int i = 0; i < 3; ++i) { - actions[i].json(json_obj, num_to_str(i + 1, "action"), false); + MMGAction::json(json_obj); + + QJsonArray json_array; + for (MMGActionManager *manager : actions) { + QJsonObject json_obj; + manager->json(json_obj); + json_array += json_obj; } + + json_obj["actions"] = json_array; } -void MMGActionInternal::do_action(const MMGMessage *midi) +void MMGActionInternal::execute(const MMGMessage *midi) const { - MMGDevice *device = global()->find_current_device(); - if (get_sub() > 2 || get_sub() < 0) return; + if (sub() != 0) return; - int i = 0; - while (get_sub() >= i) { - MMGBinding *binding = device->find_binding(actions[i]); - if (!binding) return; - binding->get_action()->do_action(midi); - ++i; + if (thread_count >= 64) { + global_blog(LOG_INFO, "Thread count exceeded - the provided function will not execute."); + return; } - blog(LOG_DEBUG, "Successfully executed."); - executed = true; + + QThread *thread = QThread::create( + [&](const MMGMessage &message) { + for (MMGActionManager *manager : actions) + manager->execute(&message); + thread_count--; + }, + *midi); + thread->connect(thread, &QThread::finished, &QObject::deleteLater); + thread_count++; + thread->start(); + + blog(LOG_DEBUG, "Successfully deployed."); } -void MMGActionInternal::deep_copy(MMGAction *dest) const +void MMGActionInternal::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - actions[0].copy(&dest->str1()); - actions[1].copy(&dest->str2()); - actions[2].copy(&dest->str3()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->actions.clear(); + for (MMGActionManager *manager : actions) { + casted->actions.append(new MMGActionManager); + manager->copy(casted->actions.last()); + } +} + +void MMGActionInternal::setEditable(bool edit) +{ + for (MMGActionManager *manager : actions) + manager->setEditable(edit); } -const QStringList MMGActionInternal::enumerate(const QString ¤t_binding) +const QStringList MMGActionInternal::enumerateActions() { QStringList list; - for (MMGBinding *const binding : global()->find_current_device()->get_bindings()) { - if (binding->get_name() != current_binding) list.append(binding->get_name()); + for (MMGBinding *const binding : global()->currentDevice()->bindings()) { + if (binding->action()->category() != MMGACTION_INTERNAL) list.append(binding->name()); } return list; } -void MMGActionInternal::change_options_sub(MMGActionDisplayParams &val) +void MMGActionInternal::createDisplay(QWidget *parent) { - val.list = {"Do 1 Action", "Do 2 Actions", "Do 3 Actions"}; + MMGAction::createDisplay(parent); + + if (actions.size() < 1) actions.append(new MMGActionManager); + + internal_display = new MMGActionInternalDisplay(_display, this); + internal_display->resize(290, 350); + _display->setScrollWidget(internal_display); } -void MMGActionInternal::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionInternal::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.list = enumerate(val.extra_data); - val.label_text = "Action 1"; + sub->addItem("Execute Other Actions"); } -void MMGActionInternal::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionInternal::setSubConfig() { - if (subcategory == 0) return; - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.list = enumerate(val.extra_data); - val.label_text = "Action 2"; + internal_display->setOptions(enumerateActions()); } -void MMGActionInternal::change_options_str3(MMGActionDisplayParams &val) + +// MMGActionInternalDisplay +MMGActionInternalDisplay::MMGActionInternalDisplay(QWidget *parent, MMGActionInternal *storage) + : QWidget(parent) +{ + action = storage; + num_display_storage = 1; + + QWidget *widget = new QWidget(this); + widget->setGeometry(0, 0, 290, 250); + widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + QVBoxLayout *custom_layout = new QVBoxLayout(widget); + custom_layout->setSpacing(10); + custom_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); + custom_layout->setContentsMargins(10, 10, 10, 10); + widget->setLayout(custom_layout); + + binding_options_widget = new QWidget(widget); + binding_options_widget->setFixedSize(270, 70); + + QLabel *binding_options_label = new QLabel(binding_options_widget); + binding_options_label->setGeometry(0, 0, 270, 25); + binding_options_label->setText("Action from Binding"); + + binding_options = new QComboBox(binding_options_widget); + binding_options->setGeometry(0, 25, 270, 40); + connect(binding_options, &QComboBox::currentTextChanged, this, + &MMGActionInternalDisplay::setString); + + widget->layout()->addWidget(binding_options_widget); + + action_options_widget = new QWidget(widget); + action_options_widget->setFixedSize(270, 70); + + QLabel *action_options_label = new QLabel(action_options_widget); + action_options_label->setGeometry(0, 0, 270, 25); + action_options_label->setText("Execution Timing"); + + action_options = new QComboBox(action_options_widget); + action_options->setGeometry(0, 25, 270, 40); + action_options->addItems( + {"Execute as soon as possible", "Wait (ms) before executing", "Wait (s) before executing"}); + connect(action_options, &QComboBox::currentIndexChanged, this, + &MMGActionInternalDisplay::setMode); + + widget->layout()->addWidget(action_options_widget); + + time = new MMGNumberDisplay(widget); + time->setVisible(false); + time->resize(270, 70); + time->setStorage(currentManager()->time(), true); + time->setOptions(MMGNumberDisplay::OPTIONS_FIXED_ONLY); + time->setBounds(1.0, 999.0); + time->setStep(1.0); + time->setDefaultValue(1.0); + widget->layout()->addWidget(time); + + QFrame *separator = new QFrame(this); + separator->setGeometry(10, 250, 270, 1); + separator->setFrameShape(QFrame::HLine); + separator->setLineWidth(1); + + num_display = new MMGNumberDisplay(this); + num_display->setDisplayMode(MMGNumberDisplay::MODE_THIN); + num_display->move(10, 260); + num_display->setDescription("Action #"); + num_display->setStorage(&num_display_storage); + num_display->setBounds(1, action->actions.size()); + connect(num_display, &MMGNumberDisplay::numberChanged, this, + &MMGActionInternalDisplay::setPageIndex); + + add_action = new QPushButton(this); + add_action->setText("Add Action..."); + add_action->setGeometry(10, 300, 130, 40); + connect(add_action, &QPushButton::clicked, this, &MMGActionInternalDisplay::addPage); + + remove_action = new QPushButton(this); + remove_action->setText("Remove Action..."); + remove_action->setGeometry(150, 300, 130, 40); + connect(remove_action, &QPushButton::clicked, this, &MMGActionInternalDisplay::deletePage); + + setPageIndex(); +} + +void MMGActionInternalDisplay::setPageIndex() +{ + if (action->actions.size() < num_display_storage) return; + MMGActionManager *current = currentManager(); + action_options->setCurrentIndex(current->timing()); + time->setStorage(current->time(), true); + binding_options->setCurrentText(current->binding()); + remove_action->setEnabled(action->actions.size() != 1); +} + +void MMGActionInternalDisplay::setMode(int index) +{ + if (index > 2) return; + currentManager()->setTiming(index); + time->setVisible(index != 0); + time->setDescription(index < 2 ? "Milliseconds" : "Seconds"); +} + +void MMGActionInternalDisplay::setOptions(const QStringList &options) { - if (subcategory < 2) return; - val.display = MMGActionDisplayParams::DISPLAY_STR3; - val.list = enumerate(val.extra_data); - val.label_text = "Action 3"; + QString name = currentManager()->binding(); + QSignalBlocker blocker(binding_options); + + binding_options->clear(); + binding_options->addItems(options); + if (options.contains(name)) binding_options->setCurrentText(name); + currentManager()->setBinding(binding_options->currentText()); +} + +void MMGActionInternalDisplay::addPage() +{ + MMGActionManager *manager = new MMGActionManager; + manager->setBinding(binding_options->itemText(0)); + action->actions.insert(num_display_storage, manager); + + num_display_storage = num_display_storage + 1; + num_display->setBounds(1, action->actions.size()); + emit num_display->numberChanged(); +} + +void MMGActionInternalDisplay::setString(const QString &str) +{ + currentManager()->setBinding(str); +} + +void MMGActionInternalDisplay::deletePage() +{ + MMGActionManager *manager = currentManager(); + action->actions.removeAt(num_display_storage - 1); + delete manager; + + num_display->setBounds(1, action->actions.size()); + emit num_display->numberChanged(); } -void MMGActionInternal::change_options_final(MMGActionDisplayParams &val) {} +// End MMGActionInternalDisplay diff --git a/src/actions/mmg-action-internal.h b/src/actions/mmg-action-internal.h index 9d4dce9..625e53b 100644 --- a/src/actions/mmg-action-internal.h +++ b/src/actions/mmg-action-internal.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,34 +19,104 @@ with this program. If not, see #pragma once #include "mmg-action.h" +#include +#include + +#define MMG_ENABLED if (editable) + +class MMGActionManager { + public: + MMGActionManager(); + MMGActionManager(const QJsonObject &json_obj); + + const QString &binding() const { return binding_name; }; + short timing() const { return before_action; }; + MMGUtils::MMGNumber *time() { return &_time; }; + + void setBinding(const QString &name) { MMG_ENABLED binding_name = name; }; + void setTiming(short timing) { MMG_ENABLED before_action = timing; }; + + void json(QJsonObject &json_obj); + void copy(MMGActionManager *other) const; + void setEditable(bool edit) + { + editable = edit; + _time.set_edit(edit); + }; + + void execute(const MMGMessage *incoming) const; + + private: + QString binding_name; + short before_action = 0; + MMGUtils::MMGNumber _time; + + bool editable = true; +}; + +class MMGActionInternalDisplay; + class MMGActionInternal : public MMGAction { public: - explicit MMGActionInternal() { blog(LOG_DEBUG, "Empty action created."); }; + explicit MMGActionInternal(); explicit MMGActionInternal(const QJsonObject &json_obj); - enum Actions { INTERNAL_1, INTERNAL_2, INTERNAL_3 }; + ~MMGActionInternal() { qDeleteAll(actions); }; + enum Actions { INTERNAL_DOACTIONS }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_INTERNAL; } + Category category() const override { return Category::MMGACTION_INTERNAL; } - MMGUtils::MMGString &str1() override { return actions[0]; }; - const MMGUtils::MMGString &str1() const override { return actions[0]; }; - MMGUtils::MMGString &str2() override { return actions[1]; }; - const MMGUtils::MMGString &str2() const override { return actions[1]; }; - MMGUtils::MMGString &str3() override { return actions[2]; }; - const MMGUtils::MMGString &str3() const override { return actions[2]; }; + static const QStringList enumerateActions(); - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + private: + QList actions; + MMGActionInternalDisplay *internal_display; + + void setSubConfig() override; - static const QStringList enumerate(const QString ¤t_binding); + static short thread_count; + + friend class MMGActionInternalDisplay; +}; + +class MMGActionInternalDisplay : public QWidget { + Q_OBJECT + + public: + MMGActionInternalDisplay(QWidget *parent, MMGActionInternal *storage); + + void setOptions(const QStringList &options); + + public slots: + void addPage(); + + private slots: + void setPageIndex(); + void setMode(int); + void setString(const QString &str); + void deletePage(); private: - MMGUtils::MMGString actions[3]; + MMGActionInternal *action; + MMGActionManager *currentManager() const { return action->actions[num_display_storage - 1]; }; + + QWidget *action_options_widget; + QComboBox *action_options; + MMGNumberDisplay *time; + QWidget *binding_options_widget; + QComboBox *binding_options; + + MMGNumberDisplay *num_display; + MMGUtils::MMGNumber num_display_storage; + QPushButton *add_action; + QPushButton *remove_action; }; + +#undef MMG_ENABLED diff --git a/src/actions/mmg-action-media-sources.cpp b/src/actions/mmg-action-media-sources.cpp index da5b852..92801ff 100644 --- a/src/actions/mmg-action-media-sources.cpp +++ b/src/actions/mmg-action-media-sources.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,13 +35,13 @@ void MMGActionMediaSources::blog(int log_status, const QString &message) const void MMGActionMediaSources::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + source.json(json_obj, "source", false); - num.json(json_obj, "num", true); + num.json(json_obj, "num"); } -void MMGActionMediaSources::do_action(const MMGMessage *midi) +void MMGActionMediaSources::execute(const MMGMessage *midi) const { OBSSourceAutoRelease obs_source = obs_get_source_by_name(source.mmgtocs()); if (!obs_source) { @@ -54,7 +54,7 @@ void MMGActionMediaSources::do_action(const MMGMessage *midi) } obs_media_state state = obs_source_media_get_state(obs_source); - switch (get_sub()) { + switch (sub()) { case MMGActionMediaSources::SOURCE_MEDIA_TOGGLE_PLAYPAUSE: switch (state) { case OBS_MEDIA_STATE_PLAYING: @@ -78,8 +78,7 @@ void MMGActionMediaSources::do_action(const MMGMessage *midi) obs_source_media_stop(obs_source); break; case MMGActionMediaSources::SOURCE_MEDIA_TIME: - obs_source_media_set_time(obs_source, - num.choose(midi, 0, get_obs_media_length(source)) * 1000); + obs_source_media_set_time(obs_source, num.choose(midi) * 1000); break; case MMGActionMediaSources::SOURCE_MEDIA_SKIP_FORWARD_TRACK: obs_source_media_next(obs_source); @@ -97,14 +96,23 @@ void MMGActionMediaSources::do_action(const MMGMessage *midi) break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionMediaSources::deep_copy(MMGAction *dest) const +void MMGActionMediaSources::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - source.copy(&dest->str1()); - num.copy(&dest->num1()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->source = source.copy(); + casted->num = num.copy(); +} + +void MMGActionMediaSources::setEditable(bool edit) +{ + source.set_edit(edit); + num.set_edit(edit); } const QStringList MMGActionMediaSources::enumerate() @@ -122,54 +130,69 @@ const QStringList MMGActionMediaSources::enumerate() return list; } -void MMGActionMediaSources::change_options_sub(MMGActionDisplayParams &val) +void MMGActionMediaSources::createDisplay(QWidget *parent) { - val.list = {"Play or Pause", "Restart", "Stop", - "Set Track Time", "Next Track", "Previous Track", - "Skip Forward Time", "Skip Backward Time"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&source); + + MMGNumberDisplay *num_display = new MMGNumberDisplay(_display->numberDisplays()); + num_display->setStorage(&num, true); + num_display->setVisible(false); + _display->numberDisplays()->add(num_display); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); } -void MMGActionMediaSources::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionMediaSources::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Media Source"; - val.list = enumerate(); + sub->addItems({"Play or Pause", "Restart", "Stop", "Set Track Time", "Next Track", + "Previous Track", "Skip Forward Time", "Skip Backward Time"}); } -void MMGActionMediaSources::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionMediaSources::setSubConfig() { - switch ((Actions)subcategory) { - case SOURCE_MEDIA_TOGGLE_PLAYPAUSE: - case SOURCE_MEDIA_RESTART: - case SOURCE_MEDIA_STOP: - case SOURCE_MEDIA_SKIP_FORWARD_TRACK: - case SOURCE_MEDIA_SKIP_BACKWARD_TRACK: - break; - case SOURCE_MEDIA_TIME: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; + _display->setStr1Visible(true); + _display->setStr1Description("Media Source"); + _display->setStr1Options(enumerate()); +} - val.label_lcds[0] = "Time"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI | - MMGActionDisplayParams::COMBODISPLAY_MIDI_INVERT; - val.lcds[0]->set_use_time(true); - val.lcds[0]->set_range(0.0, get_obs_media_length(source)); - val.lcds[0]->set_step(1.0, 10.0); - val.lcds[0]->set_default_value(0.0); +void MMGActionMediaSources::setList1Config() +{ + MMGNumberDisplay *num_display = _display->numberDisplays()->fieldAt(0); + num_display->setVisible(false); + switch ((Actions)subcategory) { + case SOURCE_MEDIA_TIME: + num_display->setVisible(!source.str().isEmpty()); + num_display->setDescription("Time"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_CUSTOM); + num_display->setTimeFormat(true); + num_display->setBounds(0.0, sourceDuration()); + num_display->setStep(1.0); + num_display->setDefaultValue(0.0); break; + case SOURCE_MEDIA_SKIP_FORWARD_TIME: case SOURCE_MEDIA_SKIP_BACKWARD_TIME: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; - - val.label_lcds[0] = "Time Adjust"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_FIXED; - val.lcds[0]->set_use_time(true); - val.lcds[0]->set_range(0.0, get_obs_media_length(source)); - val.lcds[0]->set_step(1.0, 10.0); - val.lcds[0]->set_default_value(0.0); - + num_display->setVisible(!source.str().isEmpty()); + num_display->setDescription("Time Adjust"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_FIXED_ONLY); + num_display->setTimeFormat(true); + num_display->setBounds(0.0, sourceDuration()); + num_display->setStep(1.0); + num_display->setDefaultValue(0.0); break; + default: - break; + return; } + num_display->reset(); +} + +double MMGActionMediaSources::sourceDuration() const +{ + return obs_source_media_get_duration( + OBSSourceAutoRelease(obs_get_source_by_name(source.mmgtocs()))) / + 1000.0; } -void MMGActionMediaSources::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionMediaSources::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-media-sources.h b/src/actions/mmg-action-media-sources.h index 2cd90f0..fcad9c3 100644 --- a/src/actions/mmg-action-media-sources.h +++ b/src/actions/mmg-action-media-sources.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,26 +35,22 @@ class MMGActionMediaSources : public MMGAction { }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_SOURCE_MEDIA; } - - MMGUtils::MMGString &str1() override { return source; }; - const MMGUtils::MMGString &str1() const override { return source; }; - MMGUtils::MMGNumber &num1() override { return num; }; - const MMGUtils::MMGNumber &num1() const override { return num; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_SOURCE_MEDIA; } static const QStringList enumerate(); + double sourceDuration() const; private: MMGUtils::MMGString source; MMGUtils::MMGNumber num; + + void setSubConfig() override; + void setList1Config() override; }; diff --git a/src/actions/mmg-action-midi.cpp b/src/actions/mmg-action-midi.cpp index e1e0d59..13f2ba0 100644 --- a/src/actions/mmg-action-midi.cpp +++ b/src/actions/mmg-action-midi.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,6 +18,7 @@ with this program. If not, see #include "mmg-action-midi.h" #include "../mmg-config.h" +#include "../mmg-midiout.h" using namespace MMGUtils; @@ -40,113 +41,139 @@ void MMGActionMIDI::blog(int log_status, const QString &message) const void MMGActionMIDI::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + device.json(json_obj, "device", false); - type.json(json_obj, "type", true); - channel.json(json_obj, "channel", true); - note.json(json_obj, "note", true); - value.json(json_obj, "value", true); + type.json(json_obj, "type"); + channel.json(json_obj, "channel"); + note.json(json_obj, "note"); + value.json(json_obj, "value"); } -void MMGActionMIDI::do_action(const MMGMessage *midi) +void MMGActionMIDI::execute(const MMGMessage *midi) const { - if (get_sub() == 0) { - MMGDevice *const output = global()->find_device(device); + if (sub() == 0) { + MMGDevice *output = global()->find(device); if (!output) { blog(LOG_INFO, "FAILED: Output device is not connected or does not exist."); return; } - libremidi::message msg; - QString _type = type.state() == MMGString::STRINGSTATE_MIDI ? midi->type() : type; - int _channel = channel.state() == MMGNumber::NUMBERSTATE_MIDI ? midi->channel() : channel; - int _note = note.state() == MMGNumber::NUMBERSTATE_MIDI ? midi->note() : note; - int _value = value.choose(midi); - if (_type == "Note On") { - msg = libremidi::message::note_on(_channel, _note, _value); - } else if (_type == "Note Off") { - msg = libremidi::message::note_off(_channel, _note, _value); - } else if (_type == "Control Change") { - msg = libremidi::message::control_change(_channel, _note, _value); - } else if (_type == "Program Change") { - msg = libremidi::message::program_change(_channel, _value); - } else if (_type == "Pitch Bend") { - msg = libremidi::message::pitch_bend(_channel, (_value <= 64 ? 0 : _value - 64) * 2, _value); - } else { - msg = libremidi::message(); - } - if (!MMGDevice::output_port_open()) MMGDevice::open_output_port(output); - output->output_send(msg); - MMGDevice::close_output_port(); + + MMGMessage msg; + msg.type()->set_str(type.state() == MMGString::STRINGSTATE_MIDI ? midi->type() : type); + msg.channel()->set_num(channel.state() == MMGNumber::NUMBERSTATE_MIDI ? midi->channel() + : channel); + msg.note()->set_num(note.state() == MMGNumber::NUMBERSTATE_MIDI ? midi->note() : note); + msg.value()->set_num(value.state() == MMGNumber::NUMBERSTATE_MIDI ? midi->value() : value); + + if (!output_device()->isOutputPortOpen()) output_device()->openOutputPort(output); + output_device()->sendMessage(&msg); } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionMIDI::deep_copy(MMGAction *dest) const +void MMGActionMIDI::copy(MMGAction *dest) const +{ + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->device = device.copy(); + casted->type = type.copy(); + casted->channel = channel.copy(); + casted->note = note.copy(); + casted->value = value.copy(); +} + +void MMGActionMIDI::setEditable(bool edit) { - dest->set_sub(subcategory); - device.copy(&dest->str1()); - type.copy(&dest->str2()); - channel.copy(&dest->num1()); - note.copy(&dest->num2()); - value.copy(&dest->num3()); + device.set_edit(edit); + type.set_edit(edit); + channel.set_edit(edit); + note.set_edit(edit); + value.set_edit(edit); } -void MMGActionMIDI::change_options_sub(MMGActionDisplayParams &val) +void MMGActionMIDI::createDisplay(QWidget *parent) { - val.list = {"Send Single Message"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&device); + _display->setStr2Storage(&type); + + MMGNumberDisplay *channel_display = new MMGNumberDisplay(_display->numberDisplays()); + channel_display->setStorage(&channel, true); + _display->numberDisplays()->add(channel_display); + MMGNumberDisplay *note_display = new MMGNumberDisplay(_display->numberDisplays()); + note_display->setStorage(¬e, true); + _display->numberDisplays()->add(note_display); + MMGNumberDisplay *value_display = new MMGNumberDisplay(_display->numberDisplays()); + value_display->setStorage(&value, true); + _display->numberDisplays()->add(value_display); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); + _display->connect(_display, &MMGActionDisplay::str2Changed, [&]() { setList2Config(); }); } -void MMGActionMIDI::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionMIDI::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Output Device"; - val.list = MMGDevice::get_output_device_names(); + sub->addItem("Send Single Message"); } -void MMGActionMIDI::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionMIDI::setSubConfig() { - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.label_text = "Message Type"; - val.list = {"Note On", "Note Off", "Control Change", - "Program Change", "Pitch Bend", "Use Message Type"}; + _display->setStr1Visible(false); + _display->setStr2Visible(false); + _display->setStr3Visible(false); + + _display->setStr1Visible(true); + _display->setStr1Description("Output Device"); + _display->setStr1Options(output_device()->outputDeviceNames()); } -void MMGActionMIDI::change_options_str3(MMGActionDisplayParams &val) + +void MMGActionMIDI::setList1Config() { - val.label_lcds[0] = "Channel"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI; - val.lcds[0]->set_range(1.0, 16.0); - val.lcds[0]->set_step(1.0, 5.0); - val.lcds[0]->set_default_value(1.0); - - val.combo_display[1] = MMGActionDisplayParams::COMBODISPLAY_MIDI; - val.lcds[1]->set_range(0.0, 127.0); - val.lcds[1]->set_step(1.0, 10.0); - val.lcds[1]->set_default_value(0.0); - - val.combo_display[2] = MMGActionDisplayParams::COMBODISPLAY_MIDI; - val.lcds[2]->set_range(0.0, 127.0); - val.lcds[2]->set_step(1.0, 10.0); - val.lcds[2]->set_default_value(0.0); - - type.set_state(type == "Use Message Type" ? MMGString::STRINGSTATE_MIDI - : MMGString::STRINGSTATE_FIXED); - - QString actual_type = type.state() > 0 ? val.extra_data : type; - val.display &= ~MMGActionDisplayParams::DISPLAY_NUM3; - if (actual_type.contains("Note")) { - val.display |= MMGActionDisplayParams::DISPLAY_NUM3; - val.label_lcds[1] = "Note #"; - val.label_lcds[2] = "Velocity"; - } else if (actual_type == "Control Change") { - val.display |= MMGActionDisplayParams::DISPLAY_NUM3; - val.label_lcds[1] = "Control #"; - val.label_lcds[2] = "Value"; - } else if (actual_type == "Program Change") { - val.display |= MMGActionDisplayParams::DISPLAY_NUM2; - val.label_lcds[1] = "Program #"; - } else if (actual_type == "Pitch Bend") { - val.display |= MMGActionDisplayParams::DISPLAY_NUM2; - val.label_lcds[1] = "Pitch Adjust"; - } + _display->setStr2Visible(true); + _display->setStr2Description("Message Type"); + _display->setStr2Options( + {"Note On", "Note Off", "Control Change", "Program Change", "Pitch Bend", "Use Message Type"}); +} + +void MMGActionMIDI::setList2Config() +{ + MMGNumberDisplay *channel_display = _display->numberDisplays()->fieldAt(0); + MMGNumberDisplay *note_display = _display->numberDisplays()->fieldAt(1); + MMGNumberDisplay *value_display = _display->numberDisplays()->fieldAt(2); + + channel_display->setDescription("Channel"); + channel_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_DEFAULT); + channel_display->setBounds(1.0, 16.0); + channel_display->setStep(1.0); + channel_display->setDefaultValue(1.0); + + note_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_DEFAULT); + note_display->setBounds(0.0, 127.0); + note_display->setStep(1.0); + note_display->setDefaultValue(0.0); + + value_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_DEFAULT); + value_display->setBounds(0.0, 127.0); + value_display->setStep(1.0); + value_display->setDefaultValue(0.0); + + type.set_state(type == "Use Message Type"); + + setLabels(); +} + +void MMGActionMIDI::setLabels() +{ + if (!_display) return; + + MMGNumberDisplay *note_display = _display->numberDisplays()->fieldAt(1); + MMGNumberDisplay *value_display = _display->numberDisplays()->fieldAt(2); + + set_message_labels(type.state() ? _display->parentBinding()->message()->type() : type, + note_display, value_display); } -void MMGActionMIDI::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-midi.h b/src/actions/mmg-action-midi.h index fb9bc66..92de66c 100644 --- a/src/actions/mmg-action-midi.h +++ b/src/actions/mmg-action-midi.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,28 +26,16 @@ class MMGActionMIDI : public MMGAction { enum Actions { MIDI_SENDONE }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; - - Category get_category() const override { return Category::MMGACTION_MIDI; } - - MMGUtils::MMGString &str1() override { return device; }; - const MMGUtils::MMGString &str1() const override { return device; }; - MMGUtils::MMGString &str2() override { return type; }; - const MMGUtils::MMGString &str2() const override { return type; }; - MMGUtils::MMGNumber &num1() override { return channel; }; - const MMGUtils::MMGNumber &num1() const override { return channel; }; - MMGUtils::MMGNumber &num2() override { return note; }; - const MMGUtils::MMGNumber &num2() const override { return note; }; - MMGUtils::MMGNumber &num3() override { return value; }; - const MMGUtils::MMGNumber &num3() const override { return value; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; + + void setLabels(); + + Category category() const override { return Category::MMGACTION_MIDI; } private: MMGUtils::MMGString device; @@ -55,4 +43,8 @@ class MMGActionMIDI : public MMGAction { MMGUtils::MMGNumber channel; MMGUtils::MMGNumber note; MMGUtils::MMGNumber value; + + void setSubConfig() override; + void setList1Config() override; + void setList2Config() override; }; diff --git a/src/actions/mmg-action-none.cpp b/src/actions/mmg-action-none.cpp index e20aa67..35c2a72 100644 --- a/src/actions/mmg-action-none.cpp +++ b/src/actions/mmg-action-none.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,26 +25,8 @@ void MMGActionNone::blog(int log_status, const QString &message) const global_blog(log_status, " Action -> " + message); } -void MMGActionNone::json(QJsonObject &json_obj) const -{ - json_obj["category"] = 0; - json_obj["sub"] = 0; -} - -void MMGActionNone::do_action(const MMGMessage *midi) +void MMGActionNone::execute(const MMGMessage *midi) const { Q_UNUSED(midi); blog(LOG_DEBUG, "Executed successfully."); - executed = true; -} - -void MMGActionNone::deep_copy(MMGAction *dest) const {} - -void MMGActionNone::change_options_sub(MMGActionDisplayParams &val) -{ - val.list = {"None"}; } -void MMGActionNone::change_options_str1(MMGActionDisplayParams &val) {} -void MMGActionNone::change_options_str2(MMGActionDisplayParams &val) {} -void MMGActionNone::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionNone::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-none.h b/src/actions/mmg-action-none.h index b1021c7..f46ae9b 100644 --- a/src/actions/mmg-action-none.h +++ b/src/actions/mmg-action-none.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,19 +22,12 @@ with this program. If not, see class MMGActionNone : public MMGAction { public: explicit MMGActionNone() { blog(LOG_DEBUG, "Empty action created."); }; - explicit MMGActionNone(const QJsonObject &json_obj) : MMGActionNone(){}; + explicit MMGActionNone(const QJsonObject &json_obj) { Q_UNUSED(json_obj); }; enum Actions { NONE_NONE }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; - void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void execute(const MMGMessage *midi) const override; + void setSubOptions(QComboBox *sub) override { sub->addItem("None"); }; - Category get_category() const override { return Category::MMGACTION_NONE; } - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_NONE; } }; diff --git a/src/actions/mmg-action-profiles.cpp b/src/actions/mmg-action-profiles.cpp index c755f2a..2e9ad16 100644 --- a/src/actions/mmg-action-profiles.cpp +++ b/src/actions/mmg-action-profiles.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,12 +34,12 @@ void MMGActionProfiles::blog(int log_status, const QString &message) const void MMGActionProfiles::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); - profile.json(json_obj, "profile", true); + MMGAction::json(json_obj); + + profile.json(json_obj, "profile"); } -void MMGActionProfiles::do_action(const MMGMessage *midi) +void MMGActionProfiles::execute(const MMGMessage *midi) const { const QStringList profiles = MMGActionProfiles::enumerate(); @@ -65,7 +65,7 @@ void MMGActionProfiles::do_action(const MMGMessage *midi) &name, true); }; - if (get_sub() == 0) { + if (sub() == 0) { if (!(obs_frontend_streaming_active() || obs_frontend_recording_active() || obs_frontend_virtualcam_active())) { set_profile( @@ -75,13 +75,21 @@ void MMGActionProfiles::do_action(const MMGMessage *midi) } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionProfiles::deep_copy(MMGAction *dest) const +void MMGActionProfiles::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - profile.copy(&dest->str1()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->profile = profile.copy(); +} + +void MMGActionProfiles::setEditable(bool edit) +{ + profile.set_edit(edit); } const QStringList MMGActionProfiles::enumerate() @@ -97,21 +105,23 @@ const QStringList MMGActionProfiles::enumerate() return list; } -void MMGActionProfiles::change_options_sub(MMGActionDisplayParams &val) +void MMGActionProfiles::createDisplay(QWidget *parent) { - val.list = {"Switch Profiles"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&profile); } -void MMGActionProfiles::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionProfiles::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Profile"; - val.list = enumerate(); - val.list.append("Use Message Value"); + sub->addItem("Switch Profiles"); } -void MMGActionProfiles::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionProfiles::setSubConfig() { - profile.set_state(profile == "Use Message Value" ? MMGString::STRINGSTATE_MIDI - : MMGString::STRINGSTATE_FIXED); + _display->setStr1Visible(true); + _display->setStr1Description("Profile"); + QStringList options = enumerate(); + options.append("Use Message Value"); + _display->setStr1Options(options); } -void MMGActionProfiles::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionProfiles::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-profiles.h b/src/actions/mmg-action-profiles.h index 2f1bb11..23526b5 100644 --- a/src/actions/mmg-action-profiles.h +++ b/src/actions/mmg-action-profiles.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,23 +26,19 @@ class MMGActionProfiles : public MMGAction { enum Actions { PROFILE_PROFILE }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_PROFILE; } - - MMGUtils::MMGString &str1() override { return profile; }; - const MMGUtils::MMGString &str1() const override { return profile; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_PROFILE; } static const QStringList enumerate(); private: MMGUtils::MMGString profile; + + void setSubConfig() override; }; diff --git a/src/actions/mmg-action-record.cpp b/src/actions/mmg-action-record.cpp index e0980cb..2b52c93 100644 --- a/src/actions/mmg-action-record.cpp +++ b/src/actions/mmg-action-record.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,16 +32,10 @@ void MMGActionRecord::blog(int log_status, const QString &message) const global_blog(log_status, " Action -> " + message); } -void MMGActionRecord::json(QJsonObject &json_obj) const -{ - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); -} - -void MMGActionRecord::do_action(const MMGMessage *midi) +void MMGActionRecord::execute(const MMGMessage *midi) const { Q_UNUSED(midi); - switch (get_sub()) { + switch (sub()) { case MMGActionRecord::RECORD_ON: if (!obs_frontend_recording_active()) obs_frontend_recording_start(); break; @@ -68,20 +62,10 @@ void MMGActionRecord::do_action(const MMGMessage *midi) break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; -} - -void MMGActionRecord::deep_copy(MMGAction *dest) const -{ - dest->set_sub(subcategory); } -void MMGActionRecord::change_options_sub(MMGActionDisplayParams &val) +void MMGActionRecord::setSubOptions(QComboBox *sub) { - val.list = {"Start Recording", "Stop Recording", "Toggle Recording", - "Pause Recording", "Resume Recording", "Toggle Pause Recording"}; + sub->addItems({"Start Recording", "Stop Recording", "Toggle Recording", "Pause Recording", + "Resume Recording", "Toggle Pause Recording"}); } -void MMGActionRecord::change_options_str1(MMGActionDisplayParams &val) {} -void MMGActionRecord::change_options_str2(MMGActionDisplayParams &val) {} -void MMGActionRecord::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionRecord::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-record.h b/src/actions/mmg-action-record.h index 797ebdd..43678c0 100644 --- a/src/actions/mmg-action-record.h +++ b/src/actions/mmg-action-record.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,15 +33,8 @@ class MMGActionRecord : public MMGAction { }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; - void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void execute(const MMGMessage *midi) const override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_RECORD; } - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_RECORD; } }; diff --git a/src/actions/mmg-action-replaybuffer.cpp b/src/actions/mmg-action-replaybuffer.cpp index 26327b7..3f7460b 100644 --- a/src/actions/mmg-action-replaybuffer.cpp +++ b/src/actions/mmg-action-replaybuffer.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,13 +33,7 @@ void MMGActionReplayBuffer::blog(int log_status, const QString &message) const global_blog(log_status, " Action -> " + message); } -void MMGActionReplayBuffer::json(QJsonObject &json_obj) const -{ - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); -} - -void MMGActionReplayBuffer::do_action(const MMGMessage *midi) +void MMGActionReplayBuffer::execute(const MMGMessage *midi) const { config_t *obs_config = obs_frontend_get_profile_config(); if ((QString(config_get_string(obs_config, "Output", "Mode")) == "Simple" && @@ -51,7 +45,7 @@ void MMGActionReplayBuffer::do_action(const MMGMessage *midi) } Q_UNUSED(midi); - switch (get_sub()) { + switch (sub()) { case MMGActionReplayBuffer::REPBUF_ON: if (!obs_frontend_replay_buffer_active()) obs_frontend_replay_buffer_start(); break; @@ -72,20 +66,10 @@ void MMGActionReplayBuffer::do_action(const MMGMessage *midi) break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; -} - -void MMGActionReplayBuffer::deep_copy(MMGAction *dest) const -{ - dest->set_sub(subcategory); } -void MMGActionReplayBuffer::change_options_sub(MMGActionDisplayParams &val) +void MMGActionReplayBuffer::setSubOptions(QComboBox *sub) { - val.list = {"Start Replay Buffer", "Stop Replay Buffer", "Toggle Replay Buffer", - "Save Replay Buffer"}; + sub->addItems( + {"Start Replay Buffer", "Stop Replay Buffer", "Toggle Replay Buffer", "Save Replay Buffer"}); } -void MMGActionReplayBuffer::change_options_str1(MMGActionDisplayParams &val) {} -void MMGActionReplayBuffer::change_options_str2(MMGActionDisplayParams &val) {} -void MMGActionReplayBuffer::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionReplayBuffer::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-replaybuffer.h b/src/actions/mmg-action-replaybuffer.h index 953f82a..5780f9b 100644 --- a/src/actions/mmg-action-replaybuffer.h +++ b/src/actions/mmg-action-replaybuffer.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,15 +26,8 @@ class MMGActionReplayBuffer : public MMGAction { enum Actions { REPBUF_ON, REPBUF_OFF, REPBUF_TOGGLE_ONOFF, REPBUF_SAVE }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; - void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void execute(const MMGMessage *midi) const override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_REPBUF; } - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_REPBUF; } }; diff --git a/src/actions/mmg-action-scenes.cpp b/src/actions/mmg-action-scenes.cpp index ac20e3c..d8bbed2 100644 --- a/src/actions/mmg-action-scenes.cpp +++ b/src/actions/mmg-action-scenes.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,12 +34,12 @@ void MMGActionScenes::blog(int log_status, const QString &message) const void MMGActionScenes::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); - scene.json(json_obj, "scene", true); + MMGAction::json(json_obj); + + scene.json(json_obj, "scene"); } -void MMGActionScenes::do_action(const MMGMessage *midi) +void MMGActionScenes::execute(const MMGMessage *midi) const { const QStringList scenes = MMGActionScenes::enumerate(); @@ -55,7 +55,7 @@ void MMGActionScenes::do_action(const MMGMessage *midi) return; } - switch (get_sub()) { + switch (sub()) { case MMGActionScenes::SCENE_SCENE: obs_frontend_set_current_scene(source_obs_scene); break; @@ -64,13 +64,21 @@ void MMGActionScenes::do_action(const MMGMessage *midi) } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionScenes::deep_copy(MMGAction *dest) const +void MMGActionScenes::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - scene.copy(&dest->str1()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->scene = scene.copy(); +} + +void MMGActionScenes::setEditable(bool edit) +{ + scene.set_edit(edit); } const QStringList MMGActionScenes::enumerate() @@ -104,21 +112,23 @@ const QStringList MMGActionScenes::enumerate_items(const QString &scene) return r.list; } -void MMGActionScenes::change_options_sub(MMGActionDisplayParams &val) +void MMGActionScenes::createDisplay(QWidget *parent) { - val.list = {"Scene Switching"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&scene); } -void MMGActionScenes::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionScenes::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Scene"; - val.list = enumerate(); - val.list.append("Use Message Value"); + sub->addItem("Scene Switching"); } -void MMGActionScenes::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionScenes::setSubConfig() { - scene.set_state(scene == "Use Message Value" ? MMGString::STRINGSTATE_MIDI - : MMGString::STRINGSTATE_FIXED); + _display->setStr1Visible(true); + _display->setStr1Description("Scene"); + QStringList options = enumerate(); + options.append("Use Message Value"); + _display->setStr1Options(options); } -void MMGActionScenes::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionScenes::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-scenes.h b/src/actions/mmg-action-scenes.h index cc830a4..a6f9422 100644 --- a/src/actions/mmg-action-scenes.h +++ b/src/actions/mmg-action-scenes.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,24 +26,20 @@ class MMGActionScenes : public MMGAction { enum Actions { SCENE_SCENE }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_SCENE; } - - MMGUtils::MMGString &str1() override { return scene; }; - const MMGUtils::MMGString &str1() const override { return scene; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_SCENE; } static const QStringList enumerate(); static const QStringList enumerate_items(const QString &scene); private: MMGUtils::MMGString scene; + + void setSubConfig() override; }; diff --git a/src/actions/mmg-action-stream.cpp b/src/actions/mmg-action-stream.cpp index 600051d..49c3a00 100644 --- a/src/actions/mmg-action-stream.cpp +++ b/src/actions/mmg-action-stream.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,16 +32,10 @@ void MMGActionStream::blog(int log_status, const QString &message) const global_blog(log_status, " Action -> " + message); } -void MMGActionStream::json(QJsonObject &json_obj) const -{ - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); -} - -void MMGActionStream::do_action(const MMGMessage *midi) +void MMGActionStream::execute(const MMGMessage *midi) const { Q_UNUSED(midi); - switch (get_sub()) { + switch (sub()) { case MMGActionStream::STREAM_ON: if (!obs_frontend_streaming_active()) obs_frontend_streaming_start(); break; @@ -59,19 +53,9 @@ void MMGActionStream::do_action(const MMGMessage *midi) break; } blog(LOG_DEBUG, "Executed successfully."); - executed = true; -} - -void MMGActionStream::deep_copy(MMGAction *dest) const -{ - dest->set_sub(subcategory); } -void MMGActionStream::change_options_sub(MMGActionDisplayParams &val) +void MMGActionStream::setSubOptions(QComboBox *sub) { - val.list = {"Start Streaming", "Stop Streaming", "Toggle Streaming"}; + sub->addItems({"Start Streaming", "Stop Streaming", "Toggle Streaming"}); } -void MMGActionStream::change_options_str1(MMGActionDisplayParams &val) {} -void MMGActionStream::change_options_str2(MMGActionDisplayParams &val) {} -void MMGActionStream::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionStream::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-stream.h b/src/actions/mmg-action-stream.h index c2dcc21..bc0bfb0 100644 --- a/src/actions/mmg-action-stream.h +++ b/src/actions/mmg-action-stream.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,15 +26,8 @@ class MMGActionStream : public MMGAction { enum Actions { STREAM_ON, STREAM_OFF, STREAM_TOGGLE_ONOFF }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; - void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void execute(const MMGMessage *midi) const override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_STREAM; } - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_STREAM; } }; diff --git a/src/actions/mmg-action-studiomode.cpp b/src/actions/mmg-action-studiomode.cpp index 588b9d7..e33d421 100644 --- a/src/actions/mmg-action-studiomode.cpp +++ b/src/actions/mmg-action-studiomode.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,12 +35,12 @@ void MMGActionStudioMode::blog(int log_status, const QString &message) const void MMGActionStudioMode::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); - scene.json(json_obj, "scene", true); + MMGAction::json(json_obj); + + scene.json(json_obj, "scene"); } -void MMGActionStudioMode::do_action(const MMGMessage *midi) +void MMGActionStudioMode::execute(const MMGMessage *midi) const { const QStringList scenes = MMGActionScenes::enumerate(); @@ -65,7 +65,7 @@ void MMGActionStudioMode::do_action(const MMGMessage *midi) (scene.state() == MMGString::STRINGSTATE_MIDI ? scenes[(int)midi->value()] : scene).qtocs()); OBSSourceAutoRelease obs_preview_scene = obs_frontend_get_current_preview_scene(); - switch (get_sub()) { + switch (sub()) { case MMGActionStudioMode::STUDIOMODE_ON: set_studio_mode(true); break; @@ -90,35 +90,44 @@ void MMGActionStudioMode::do_action(const MMGMessage *midi) } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionStudioMode::deep_copy(MMGAction *dest) const +void MMGActionStudioMode::copy(MMGAction *dest) const { - dest->set_sub(subcategory); - scene.copy(&dest->str1()); + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->scene = scene.copy(); } -void MMGActionStudioMode::change_options_sub(MMGActionDisplayParams &val) +void MMGActionStudioMode::setEditable(bool edit) { - val.list = {"Turn On Studio Mode", "Turn Off Studio Mode", "Toggle Studio Mode", - "Change Preview Scene", "Preview to Program"}; + scene.set_edit(edit); } -void MMGActionStudioMode::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionStudioMode::createDisplay(QWidget *parent) { - if (subcategory == 3) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Preview Scene"; - val.list = MMGActionScenes::enumerate(); - val.list.append("Use Message Value"); - } + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&scene); } -void MMGActionStudioMode::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionStudioMode::setSubOptions(QComboBox *sub) +{ + sub->addItems({"Turn On Studio Mode", "Turn Off Studio Mode", "Toggle Studio Mode", + "Change Preview Scene", "Preview to Program"}); +} + +void MMGActionStudioMode::setSubConfig() { + _display->setStr1Visible(false); if (subcategory == 3) { - scene.set_state(scene == "Use Message Value" ? MMGString::STRINGSTATE_MIDI - : MMGString::STRINGSTATE_FIXED); + _display->setStr1Visible(true); + _display->setStr1Description("Scene"); + QStringList options = MMGActionScenes::enumerate(); + options.append("Use Message Value"); + _display->setStr1Options(options); } } -void MMGActionStudioMode::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionStudioMode::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-studiomode.h b/src/actions/mmg-action-studiomode.h index dcf8f1d..607dabc 100644 --- a/src/actions/mmg-action-studiomode.h +++ b/src/actions/mmg-action-studiomode.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,21 +32,17 @@ class MMGActionStudioMode : public MMGAction { }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_STUDIOMODE; } - - MMGUtils::MMGString &str1() override { return scene; }; - const MMGUtils::MMGString &str1() const override { return scene; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_STUDIOMODE; } private: MMGUtils::MMGString scene; + + void setSubConfig() override; }; diff --git a/src/actions/mmg-action-timeout.cpp b/src/actions/mmg-action-timeout.cpp deleted file mode 100644 index 739ca42..0000000 --- a/src/actions/mmg-action-timeout.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* -obs-midi-mg -Copyright (C) 2022 nhielost - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program. If not, see -*/ - -#include "mmg-action-timeout.h" - -#include - -using namespace MMGUtils; - -MMGActionTimeout::MMGActionTimeout(const QJsonObject &json_obj) : time(json_obj, "time", 1) -{ - subcategory = json_obj["sub"].toInt(); - - blog(LOG_DEBUG, " action created."); -} - -void MMGActionTimeout::blog(int log_status, const QString &message) const -{ - global_blog(log_status, " Action -> " + message); -} - -void MMGActionTimeout::json(QJsonObject &json_obj) const -{ - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); - time.json(json_obj, "time", true); -} - -void MMGActionTimeout::do_action(const MMGMessage *midi) -{ - switch (get_sub()) { - case MMGActionTimeout::TIMEOUT_MS: - std::this_thread::sleep_for(std::chrono::milliseconds((int)time.choose(midi))); - break; - case MMGActionTimeout::TIMEOUT_S: - std::this_thread::sleep_for(std::chrono::seconds((int)time.choose(midi))); - break; - default: - break; - } - blog(LOG_DEBUG, "Successfully executed."); - executed = true; -} - -void MMGActionTimeout::deep_copy(MMGAction *dest) const -{ - dest->set_sub(subcategory); - time.copy(&dest->num1()); -} - -void MMGActionTimeout::change_options_sub(MMGActionDisplayParams &val) -{ - val.list = {"Wait in Milliseconds", "Wait in Seconds"}; -} -void MMGActionTimeout::change_options_str1(MMGActionDisplayParams &val) -{ - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; - - val.label_lcds[0] = "Time"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI; - val.lcds[0]->set_range(1.0, 1000.0); - val.lcds[0]->set_step(1.0, 10.0); - val.lcds[0]->set_default_value(1.0); -} -void MMGActionTimeout::change_options_str2(MMGActionDisplayParams &val) {} -void MMGActionTimeout::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionTimeout::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-timeout.h b/src/actions/mmg-action-timeout.h deleted file mode 100644 index 0238773..0000000 --- a/src/actions/mmg-action-timeout.h +++ /dev/null @@ -1,46 +0,0 @@ -/* -obs-midi-mg -Copyright (C) 2022 nhielost - -This program is free software; you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along -with this program. If not, see -*/ - -#pragma once -#include "mmg-action.h" - -class MMGActionTimeout : public MMGAction { - public: - explicit MMGActionTimeout() { blog(LOG_DEBUG, "Empty action created."); }; - explicit MMGActionTimeout(const QJsonObject &json_obj); - enum Actions { TIMEOUT_MS, TIMEOUT_S }; - - void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; - void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; - - Category get_category() const override { return Category::MMGACTION_TIMEOUT; } - - MMGUtils::MMGNumber &num1() override { return time; }; - const MMGUtils::MMGNumber &num1() const override { return time; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; - - private: - MMGUtils::MMGNumber time; -}; diff --git a/src/actions/mmg-action-transitions.cpp b/src/actions/mmg-action-transitions.cpp index db3122f..39da116 100644 --- a/src/actions/mmg-action-transitions.cpp +++ b/src/actions/mmg-action-transitions.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,10 +21,25 @@ with this program. If not, see using namespace MMGUtils; +static struct MMGTBarTimer { + MMGTBarTimer() + { + timer->connect(timer, &MMGTimer::stopping, [&]() { obs_frontend_release_tbar(); }); + } + ~MMGTBarTimer() { delete timer; }; + MMGTimer *timer = new MMGTimer; +} tbar_timer; + +MMGActionTransitions::MMGActionTransitions() +{ + blog(LOG_DEBUG, "Empty action created."); +}; + MMGActionTransitions::MMGActionTransitions(const QJsonObject &json_obj) : transition(json_obj, "transition", 1), parent_scene(json_obj, "scene", 2), source(json_obj, "source", 3), + json_str(json_obj, "json_str", 0), num(json_obj, "num", 1) { subcategory = json_obj["sub"].toInt(); @@ -39,30 +54,31 @@ void MMGActionTransitions::blog(int log_status, const QString &message) const void MMGActionTransitions::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + transition.json(json_obj, "transition", false); parent_scene.json(json_obj, "scene", false); source.json(json_obj, "source", false); - num.json(json_obj, "num", true); + json_str.json(json_obj, "json_str", false); + num.json(json_obj, "num"); } -void MMGActionTransitions::do_action(const MMGMessage *midi) +void MMGActionTransitions::execute(const MMGMessage *midi) const { - OBSSourceAutoRelease obs_transition = get_obs_transition_by_name(transition); - if (!obs_transition && get_sub() != 3) { + OBSSourceAutoRelease obs_transition = sourceByName(); + if (!obs_transition && !(sub() == 3 || sub() == 4)) { blog(LOG_INFO, "FAILED: Transition does not exist."); return; } - int obs_transition_time = num.choose(midi, obs_frontend_get_transition_duration(), 3200); + int obs_transition_time = num.choose(midi, obs_frontend_get_transition_duration()); bool fixed = obs_transition_fixed(obs_transition); OBSSourceAutoRelease obs_source = obs_get_source_by_name(source.mmgtocs()); OBSSceneAutoRelease obs_scene = obs_get_scene_by_name(parent_scene.mmgtocs()); OBSSceneItemAutoRelease obs_sceneitem = obs_scene_sceneitem_from_source(obs_scene, obs_source); - switch (get_sub()) { + switch (sub()) { case MMGActionTransitions::TRANSITION_CURRENT: obs_frontend_set_current_transition(obs_transition); if (!fixed) obs_frontend_set_transition_duration(obs_transition_time); @@ -83,28 +99,51 @@ void MMGActionTransitions::do_action(const MMGMessage *midi) obs_sceneitem_set_transition(obs_sceneitem, false, obs_transition); if (!fixed) obs_sceneitem_set_transition_duration(obs_sceneitem, false, obs_transition_time); break; - case MMGActionTransitions::TRANSITION_TBAR: + case MMGActionTransitions::TRANSITION_TBAR_ACTIVATE: + if (!obs_frontend_preview_program_mode_active()) { + blog(LOG_INFO, "FAILED: Studio mode is inactive."); + return; + } + obs_frontend_set_tbar_position((int)num.choose(midi)); + tbar_timer.timer->reset(1000); + break; + case MMGActionTransitions::TRANSITION_TBAR_RELEASE: if (!obs_frontend_preview_program_mode_active()) { blog(LOG_INFO, "FAILED: Studio mode is inactive."); return; } - obs_frontend_set_tbar_position((int)num.choose(midi, 0, 1024.0)); - obs_frontend_release_tbar(); + tbar_timer.timer->stopTimer(); + break; + case MMGActionTransitions::TRANSITION_CUSTOM: + obs_source_custom_update(obs_transition, json_from_str(json_str.mmgtocs()), midi); break; default: break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionTransitions::deep_copy(MMGAction *dest) const +void MMGActionTransitions::copy(MMGAction *dest) const +{ + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->transition = transition.copy(); + casted->parent_scene = parent_scene.copy(); + casted->source = source.copy(); + casted->json_str = json_str.copy(); + casted->num = num.copy(); +} + +void MMGActionTransitions::setEditable(bool edit) { - dest->set_sub(subcategory); - transition.copy(&dest->str1()); - parent_scene.copy(&dest->str2()); - source.copy(&dest->str3()); - num.copy(&dest->num1()); + transition.set_edit(edit); + parent_scene.set_edit(edit); + source.set_edit(edit); + json_str.set_edit(edit); + num.set_edit(edit); } const QStringList MMGActionTransitions::enumerate() @@ -119,86 +158,153 @@ const QStringList MMGActionTransitions::enumerate() return list; } -void MMGActionTransitions::change_options_sub(MMGActionDisplayParams &val) +void MMGActionTransitions::createDisplay(QWidget *parent) { - val.list = {"Change Current Transition", "Set Source Show Transition", - "Set Source Hide Transition", "Set Transition Bar Position (Studio Mode)"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&transition); + _display->setStr2Storage(&parent_scene); + _display->setStr3Storage(&source); + + MMGNumberDisplay *num_display = new MMGNumberDisplay(_display->numberDisplays()); + num_display->setStorage(&num, true); + _display->numberDisplays()->add(num_display); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); + _display->connect(_display, &MMGActionDisplay::str2Changed, [&]() { setList2Config(); }); + _display->connect(_display, &MMGActionDisplay::str3Changed, [&]() { setList3Config(); }); +} + +void MMGActionTransitions::setSubOptions(QComboBox *sub) +{ + sub->addItems({"Change Current Transition", "Set Source Show Transition", + "Set Source Hide Transition", "Set Transition Bar Position (Studio Mode)", + "Release Transition Bar (Studio Mode)", "Custom Transition Settings"}); } -void MMGActionTransitions::change_options_str1(MMGActionDisplayParams &val) -{ - if (subcategory == 3) { - val.display = MMGActionDisplayParams::DISPLAY_NUM1; - - val.label_lcds[0] = "Position"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI | - MMGActionDisplayParams::COMBODISPLAY_MIDI_INVERT; - val.lcds[0]->set_range(0.0, 1024.0); - val.lcds[0]->set_step(1.0, 10.0); - val.lcds[0]->set_default_value(0.0); - } else { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Transition"; - val.list = enumerate(); + +void MMGActionTransitions::setSubConfig() +{ + _display->setStr1Visible(false); + _display->setStr2Visible(false); + _display->setStr3Visible(false); + + MMGNumberDisplay *num_display = _display->numberDisplays()->fieldAt(0); + + switch (subcategory) { + case TRANSITION_TBAR_ACTIVATE: + _display->resetScrollWidget(); + + num_display->setVisible(true); + num_display->setDescription("Position"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_CUSTOM); + num_display->setBounds(0.0, 1024.0); + num_display->setStep(1.0); + num_display->setDefaultValue(0.0); + num_display->reset(); + return; + + case TRANSITION_TBAR_RELEASE: + return; + + default: + break; } + + _display->setStr1Visible(true); + _display->setStr1Description("Transition"); + _display->setStr1Options(enumerate()); } -void MMGActionTransitions::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionTransitions::setList1Config() { - switch ((Actions)subcategory) { - case TRANSITION_CURRENT: - val.display &= ~MMGActionDisplayParams::DISPLAY_NUM1; - val.lcds[0]->set_default_value(obs_frontend_get_transition_duration()); - if (get_obs_transition_fixed_length(transition)) break; + _display->resetScrollWidget(); - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; + MMGNumberDisplay *num_display = _display->numberDisplays()->fieldAt(0); + num_display->setVisible(false); - val.label_lcds[0] = "Duration"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI | - MMGActionDisplayParams::COMBODISPLAY_IGNORE; - val.lcds[0]->set_range(25.0, 20000.0); - val.lcds[0]->set_step(25.0, 250.0); + OBSSourceAutoRelease source; + switch ((Actions)subcategory) { + case TRANSITION_CURRENT: + num_display->setVisible(!transitionFixed()); + num_display->setDescription("Duration"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_IGNORE); + num_display->setBounds(25.0, 20000.0); + num_display->setStep(25.0); + num_display->setDefaultValue(obs_frontend_get_transition_duration()); + num_display->reset(); break; + case TRANSITION_SOURCE_SHOW: case TRANSITION_SOURCE_HIDE: - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.label_text = "Scene"; - val.list = MMGActionScenes::enumerate(); + _display->setStr2Visible(true); + _display->setStr2Description("Scene"); + _display->setStr2Options(MMGActionScenes::enumerate()); break; - default: + + case TRANSITION_CUSTOM: + source = sourceByName(); + if (!obs_source_configurable(source)) break; + emit _display->customFieldRequest(source, &json_str); break; + + default: + return; } } -void MMGActionTransitions::change_options_str3(MMGActionDisplayParams &val) + +void MMGActionTransitions::setList2Config() { switch ((Actions)subcategory) { case TRANSITION_SOURCE_SHOW: case TRANSITION_SOURCE_HIDE: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - val.label_text = "Source"; - val.list = MMGActionScenes::enumerate_items(parent_scene); + _display->setStr3Visible(true); + _display->setStr3Description("Source"); + _display->setStr3Options(MMGActionScenes::enumerate_items(parent_scene)); break; + default: - break; + return; } } -void MMGActionTransitions::change_options_final(MMGActionDisplayParams &val) + +void MMGActionTransitions::setList3Config() { + MMGNumberDisplay *num_display = _display->numberDisplays()->fieldAt(0); + switch ((Actions)subcategory) { case TRANSITION_SOURCE_SHOW: case TRANSITION_SOURCE_HIDE: - val.lcds[0]->set_default_value(0.0); - if (get_obs_transition_fixed_length(transition)) break; + num_display->setVisible(!transitionFixed() && !source.str().isEmpty()); + num_display->setDescription("Duration"); + num_display->setOptions(MMGNumberDisplay::OPTIONS_IGNORE); + num_display->setBounds(25.0, 20000.0); + num_display->setStep(25.0); + num_display->setDefaultValue(obs_frontend_get_transition_duration()); + num_display->reset(); + return; - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; - - val.label_lcds[0] = "Duration"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI | - MMGActionDisplayParams::COMBODISPLAY_IGNORE; - val.lcds[0]->set_range(25.0, 20000.0); - val.lcds[0]->set_step(25.0, 250.0); - - break; default: - break; + return; } } + +obs_source_t *MMGActionTransitions::sourceByName() const +{ + obs_frontend_source_list transition_list = {0}; + obs_frontend_get_transitions(&transition_list); + for (size_t i = 0; i < transition_list.sources.num; ++i) { + obs_source_t *ptr = transition_list.sources.array[i]; + if (obs_source_get_name(ptr) == transition.str()) { + obs_frontend_source_list_free(&transition_list); + return obs_source_get_ref(ptr); + } + } + obs_frontend_source_list_free(&transition_list); + return nullptr; +} + +bool MMGActionTransitions::transitionFixed() const +{ + return obs_transition_fixed(OBSSourceAutoRelease(sourceByName())); +} diff --git a/src/actions/mmg-action-transitions.h b/src/actions/mmg-action-transitions.h index b02f45d..028e019 100644 --- a/src/actions/mmg-action-transitions.h +++ b/src/actions/mmg-action-transitions.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,42 +21,41 @@ with this program. If not, see class MMGActionTransitions : public MMGAction { public: - explicit MMGActionTransitions() { blog(LOG_DEBUG, "Empty action created."); }; + explicit MMGActionTransitions(); explicit MMGActionTransitions(const QJsonObject &json_obj); + enum Actions { TRANSITION_CURRENT, TRANSITION_SOURCE_SHOW, TRANSITION_SOURCE_HIDE, - TRANSITION_TBAR + TRANSITION_TBAR_ACTIVATE, + TRANSITION_TBAR_RELEASE, + TRANSITION_CUSTOM }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; - - Category get_category() const override { return Category::MMGACTION_TRANSITION; } + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - MMGUtils::MMGString &str1() override { return transition; }; - const MMGUtils::MMGString &str1() const override { return transition; }; - MMGUtils::MMGString &str2() override { return parent_scene; }; - const MMGUtils::MMGString &str2() const override { return parent_scene; }; - MMGUtils::MMGString &str3() override { return source; }; - const MMGUtils::MMGString &str3() const override { return source; }; - MMGUtils::MMGNumber &num1() override { return num; }; - const MMGUtils::MMGNumber &num1() const override { return num; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_TRANSITION; } static const QStringList enumerate(); + obs_source_t *sourceByName() const; + bool transitionFixed() const; private: MMGUtils::MMGString transition; MMGUtils::MMGString parent_scene; MMGUtils::MMGString source; + MMGUtils::MMGString json_str; MMGUtils::MMGNumber num; + + void setSubConfig() override; + void setList1Config() override; + void setList2Config() override; + void setList3Config() override; }; diff --git a/src/actions/mmg-action-video-sources.cpp b/src/actions/mmg-action-video-sources.cpp index 7d6553b..23f5737 100644 --- a/src/actions/mmg-action-video-sources.cpp +++ b/src/actions/mmg-action-video-sources.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,10 +21,36 @@ with this program. If not, see using namespace MMGUtils; +#define ALIGN_ACTION(value) \ + if ((value) <= 2) align |= OBS_ALIGN_TOP; \ + if ((value) >= 6) align |= OBS_ALIGN_BOTTOM; \ + if ((uint)(value) % 3 == 0) align |= OBS_ALIGN_LEFT; \ + if ((uint)(value) % 3 == 2) align |= OBS_ALIGN_RIGHT + +const QStringList MMGActionVideoSources::alignment_options{ + "Top Left", "Top Center", "Top Right", "Middle Left", "Middle Center", + "Middle Right", "Bottom Left", "Bottom Center", "Bottom Right", "Use Message Value"}; + +const QStringList MMGActionVideoSources::boundingbox_options{"No Bounds", + "Stretch to Bounds", + "Scale to Inner Bounds", + "Scale to Outer Bounds", + "Scale to Width of Bounds", + "Scale to Height of Bounds", + "Maximum Size", + "Use Message Value"}; + +const QStringList MMGActionVideoSources::scalefilter_options{ + "Disable", "Point", "Bicubic", "Bilinear", "Lanczos", "Area", "Use Message Value"}; + +const QStringList MMGActionVideoSources::blendmode_options{ + "Normal", "Additive", "Subtract", "Screen", "Multiply", "Lighten", "Darken", "Use Message Value"}; + MMGActionVideoSources::MMGActionVideoSources(const QJsonObject &json_obj) : parent_scene(json_obj, "scene", 1), source(json_obj, "source", 2), action(json_obj, "action", 3), + json_str(json_obj, "json_str", 0), nums{{json_obj, "num1", 1}, {json_obj, "num2", 2}, {json_obj, "num3", 3}, {json_obj, "num4", 4}} { subcategory = json_obj["sub"].toInt(); @@ -39,17 +65,18 @@ void MMGActionVideoSources::blog(int log_status, const QString &message) const void MMGActionVideoSources::json(QJsonObject &json_obj) const { - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); + MMGAction::json(json_obj); + parent_scene.json(json_obj, "scene", false); source.json(json_obj, "source", false); - action.json(json_obj, "action", true); + action.json(json_obj, "action"); + json_str.json(json_obj, "json_str", false); for (int i = 0; i < 4; ++i) { - nums[i].json(json_obj, num_to_str(i + 1, "num"), true); + nums[i].json(json_obj, num_to_str(i + 1, "num")); } } -void MMGActionVideoSources::do_action(const MMGMessage *midi) +void MMGActionVideoSources::execute(const MMGMessage *midi) const { OBSSourceAutoRelease obs_scene_source = obs_get_source_by_name(parent_scene.mmgtocs()); OBSSourceAutoRelease obs_source = obs_get_source_by_name(source.mmgtocs()); @@ -68,11 +95,11 @@ void MMGActionVideoSources::do_action(const MMGMessage *midi) obs_sceneitem_crop crop; uint32_t align = 0; - switch (get_sub()) { + switch (sub()) { case MMGActionVideoSources::SOURCE_VIDEO_POSITION: obs_sceneitem_get_pos(obs_sceneitem, &get_vec2); - vec2_set(&set_vec2, num1().choose(midi, get_vec2.x / num3(), get_obs_dimensions().x) * num3(), - num2().choose(midi, get_vec2.y / num3(), get_obs_dimensions().y) * num3()); + vec2_set(&set_vec2, num1().choose(midi, get_vec2.x / num3()) * num3(), + num2().choose(midi, get_vec2.y / num3()) * num3()); obs_sceneitem_set_pos(obs_sceneitem, &set_vec2); break; case MMGActionVideoSources::SOURCE_VIDEO_DISPLAY: @@ -91,30 +118,30 @@ void MMGActionVideoSources::do_action(const MMGMessage *midi) break; case MMGActionVideoSources::SOURCE_VIDEO_CROP: obs_sceneitem_get_crop(obs_sceneitem, &crop); - set_vec2 = get_obs_source_dimensions(source); - crop.top = num1().choose(midi, crop.top, set_vec2.y); - crop.right = num2().choose(midi, crop.right, set_vec2.x); - crop.bottom = num3().choose(midi, crop.bottom, set_vec2.y); - crop.left = num4().choose(midi, crop.left, set_vec2.x); + crop.top = num1().choose(midi, crop.top); + crop.right = num2().choose(midi, crop.right); + crop.bottom = num3().choose(midi, crop.bottom); + crop.left = num4().choose(midi, crop.left); obs_sceneitem_set_crop(obs_sceneitem, &crop); break; case MMGActionVideoSources::SOURCE_VIDEO_ALIGNMENT: - if (MIDI_NUMBER_IS_NOT_IN_RANGE(num1(), 9)) { - blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); - return; + if (action.state() != 0) { + if (midi->value() > 8) { + blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); + return; + } + ALIGN_ACTION(midi->value()); + } else { + ALIGN_ACTION(alignment_options.indexOf(action)); } - if (num1().choose(midi) <= 2) align |= OBS_ALIGN_TOP; - if (num1().choose(midi) >= 6) align |= OBS_ALIGN_BOTTOM; - if ((uint)num1().choose(midi) % 3 == 0) align |= OBS_ALIGN_LEFT; - if ((uint)num1().choose(midi) % 3 == 2) align |= OBS_ALIGN_RIGHT; obs_sceneitem_set_alignment(obs_sceneitem, align); break; case MMGActionVideoSources::SOURCE_VIDEO_SCALE: // Multiplier align = 100 / num3(); obs_sceneitem_get_scale(obs_sceneitem, &get_vec2); - vec2_set(&set_vec2, num1().choose(midi, get_vec2.x * align, 100, true) / align, - num2().choose(midi, get_vec2.y * align, 100, true) / align); + vec2_set(&set_vec2, num1().choose(midi, get_vec2.x * align) / align, + num2().choose(midi, get_vec2.y * align) / align); obs_sceneitem_set_scale(obs_sceneitem, &set_vec2); break; case MMGActionVideoSources::SOURCE_VIDEO_SCALEFILTER: @@ -122,34 +149,35 @@ void MMGActionVideoSources::do_action(const MMGMessage *midi) blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); return; } - align = num1().choose(midi); + align = !action.state() ? midi->value() : scalefilter_options.indexOf(action); obs_sceneitem_set_scale_filter(obs_sceneitem, (obs_scale_type)align); break; case MMGActionVideoSources::SOURCE_VIDEO_ROTATION: - obs_sceneitem_set_rot(obs_sceneitem, num1().choose(midi, 0, 360.0)); + obs_sceneitem_set_rot(obs_sceneitem, num1().choose(midi)); break; case MMGActionVideoSources::SOURCE_VIDEO_BOUNDING_BOX_TYPE: if (MIDI_NUMBER_IS_NOT_IN_RANGE(num1(), 7)) { blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); return; } - obs_sceneitem_set_bounds_type(obs_sceneitem, (obs_bounds_type)num1().choose(midi)); + align = !action.state() ? midi->value() : boundingbox_options.indexOf(action); + obs_sceneitem_set_bounds_type(obs_sceneitem, (obs_bounds_type)align); break; case MMGActionVideoSources::SOURCE_VIDEO_BOUNDING_BOX_SIZE: obs_sceneitem_get_bounds(obs_sceneitem, &get_vec2); - vec2_set(&set_vec2, num1().choose(midi, get_vec2.x, get_obs_dimensions().x), - num2().choose(midi, get_vec2.y, get_obs_dimensions().y)); + vec2_set(&set_vec2, num1().choose(midi, get_vec2.x), num2().choose(midi, get_vec2.y)); obs_sceneitem_set_bounds(obs_sceneitem, &set_vec2); break; case MMGActionVideoSources::SOURCE_VIDEO_BOUNDING_BOX_ALIGN: - if (MIDI_NUMBER_IS_NOT_IN_RANGE(num1(), 9)) { - blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); - return; + if (action.state() != 0) { + if (midi->value() > 8) { + blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); + return; + } + ALIGN_ACTION(midi->value()); + } else { + ALIGN_ACTION(alignment_options.indexOf(action)); } - if (num1().choose(midi) <= 2) align |= OBS_ALIGN_TOP; - if (num1().choose(midi) >= 6) align |= OBS_ALIGN_BOTTOM; - if ((uint)num1().choose(midi) % 3 == 0) align |= OBS_ALIGN_LEFT; - if ((uint)num1().choose(midi) % 3 == 2) align |= OBS_ALIGN_RIGHT; obs_sceneitem_set_bounds_alignment(obs_sceneitem, align); break; case MMGActionVideoSources::SOURCE_VIDEO_BLEND_MODE: @@ -157,31 +185,51 @@ void MMGActionVideoSources::do_action(const MMGMessage *midi) blog(LOG_INFO, "FAILED: MIDI value exceeded choice options."); return; } - obs_sceneitem_set_blending_mode(obs_sceneitem, (obs_blending_type)num1().choose(midi)); + align = !action.state() ? midi->value() : blendmode_options.indexOf(action); + obs_sceneitem_set_blending_mode(obs_sceneitem, (obs_blending_type)align); break; case MMGActionVideoSources::SOURCE_VIDEO_SCREENSHOT: obs_frontend_take_source_screenshot(obs_source); break; + case MMGActionVideoSources::SOURCE_VIDEO_CUSTOM: + obs_source_custom_update(obs_source, json_from_str(json_str.mmgtocs()), midi); + break; default: break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; } -void MMGActionVideoSources::deep_copy(MMGAction *dest) const +void MMGActionVideoSources::copy(MMGAction *dest) const +{ + MMGAction::copy(dest); + + auto casted = dynamic_cast(dest); + if (!casted) return; + + casted->parent_scene = parent_scene.copy(); + casted->source = source.copy(); + casted->action = action.copy(); + casted->json_str = json_str.copy(); + casted->nums[0] = num1().copy(); + casted->nums[1] = num2().copy(); + casted->nums[2] = num3().copy(); + casted->nums[3] = num4().copy(); +} + +void MMGActionVideoSources::setEditable(bool edit) { - dest->set_sub(subcategory); - parent_scene.copy(&dest->str1()); - source.copy(&dest->str2()); - action.copy(&dest->str3()); - num1().copy(&dest->num1()); - num2().copy(&dest->num2()); - num3().copy(&dest->num3()); - num4().copy(&dest->num4()); + parent_scene.set_edit(edit); + source.set_edit(edit); + action.set_edit(edit); + json_str.set_edit(edit); + nums[0].set_edit(edit); + nums[1].set_edit(edit); + nums[2].set_edit(edit); + nums[3].set_edit(edit); } -const QStringList MMGActionVideoSources::enumerate(const QString &restriction) +const QStringList MMGActionVideoSources::enumerate() { QStringList list; obs_enum_all_sources( @@ -198,222 +246,254 @@ const QStringList MMGActionVideoSources::enumerate(const QString &restriction) return list; } -void MMGActionVideoSources::change_options_sub(MMGActionDisplayParams &val) +void MMGActionVideoSources::createDisplay(QWidget *parent) { - val.list = {"Move Source", - "Display Source", - "Source Locking", - "Source Crop", - "Align Source", - "Source Scale", - "Source Scale Filtering", - "Rotate Source", - "Source Bounding Box Type", - "Resize Source Bounding Box", - "Align Source Bounding Box", - "Source Blending Mode", - "Take Source Screenshot"}; + MMGAction::createDisplay(parent); + + _display->setStr1Storage(&parent_scene); + _display->setStr2Storage(&source); + _display->setStr3Storage(&action); + + MMGNumberDisplay *num1_display = new MMGNumberDisplay(_display->numberDisplays()); + num1_display->setStorage(&nums[0], true); + _display->numberDisplays()->add(num1_display); + MMGNumberDisplay *num2_display = new MMGNumberDisplay(_display->numberDisplays()); + num2_display->setStorage(&nums[1], true); + _display->numberDisplays()->add(num2_display); + MMGNumberDisplay *num3_display = new MMGNumberDisplay(_display->numberDisplays()); + num3_display->setStorage(&nums[2], true); + _display->numberDisplays()->add(num3_display); + MMGNumberDisplay *num4_display = new MMGNumberDisplay(_display->numberDisplays()); + num4_display->setStorage(&nums[3], true); + _display->numberDisplays()->add(num4_display); + + _display->connect(_display, &MMGActionDisplay::str1Changed, [&]() { setList1Config(); }); + _display->connect(_display, &MMGActionDisplay::str2Changed, [&]() { setList2Config(); }); } -void MMGActionVideoSources::change_options_str1(MMGActionDisplayParams &val) + +void MMGActionVideoSources::setSubOptions(QComboBox *sub) { - val.display = MMGActionDisplayParams::DISPLAY_STR1; - val.label_text = "Scene"; - val.list = MMGActionScenes::enumerate(); + sub->addItems({"Move Source", "Display Source", "Source Locking", "Source Crop", "Align Source", + "Source Scale", "Source Scale Filtering", "Rotate Source", + "Source Bounding Box Type", "Resize Source Bounding Box", + "Align Source Bounding Box", "Source Blending Mode", "Take Source Screenshot", + "Custom Source Settings"}); } -void MMGActionVideoSources::change_options_str2(MMGActionDisplayParams &val) + +void MMGActionVideoSources::setSubConfig() { - val.display = MMGActionDisplayParams::DISPLAY_STR2; - val.label_text = "Source"; - val.list = MMGActionScenes::enumerate_items(parent_scene); + _display->setVisible(true); + _display->setStr1Visible(false); + _display->setStr2Visible(false); + _display->setStr3Visible(false); + + _display->setStr1Visible(true); + _display->setStr1Description("Scene"); + _display->setStr1Options(MMGActionScenes::enumerate()); } -void MMGActionVideoSources::change_options_str3(MMGActionDisplayParams &val) + +void MMGActionVideoSources::setList1Config() { + _display->setStr2Visible(true); + _display->setStr2Description("Source"); + _display->setStr2Options(MMGActionScenes::enumerate_items(parent_scene)); +} + +void MMGActionVideoSources::setList2Config() +{ + _display->resetScrollWidget(); + + MMGNumberDisplay *num1_display = _display->numberDisplays()->fieldAt(0); + MMGNumberDisplay *num2_display = _display->numberDisplays()->fieldAt(1); + MMGNumberDisplay *num3_display = _display->numberDisplays()->fieldAt(2); + MMGNumberDisplay *num4_display = _display->numberDisplays()->fieldAt(3); + + num1_display->setVisible(false); + num2_display->setVisible(false); + num3_display->setVisible(false); + num4_display->setVisible(false); + + bool source_exists = !source.str().isEmpty(); + switch ((Actions)subcategory) { case SOURCE_VIDEO_POSITION: - val.display |= MMGActionDisplayParams::DISPLAY_NUM3; - - val.label_lcds[0] = "Position X"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[0]->set_range(0.0, get_obs_dimensions().x); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); - - val.label_lcds[1] = "Position Y"; - val.combo_display[1] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[1]->set_range(0.0, get_obs_dimensions().y); - val.lcds[1]->set_step(0.5, 5.0); - val.lcds[1]->set_default_value(0.0); - - val.label_lcds[2] = "Magnitude"; - val.combo_display[2] = MMGActionDisplayParams::COMBODISPLAY_FIXED; - val.lcds[2]->set_range(0.5, 100.0); - val.lcds[2]->set_step(0.5, 5.0); - val.lcds[2]->set_default_value(1.0); + num1_display->setVisible(source_exists); + num1_display->setDescription("Position X"); + num1_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num1_display->setBounds(0.0, obsResolution().x, true); + num1_display->setStep(0.5); + num1_display->setDefaultValue(0.0); + + num2_display->setVisible(source_exists); + num2_display->setDescription("Position Y"); + num2_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num2_display->setBounds(0.0, obsResolution().y, true); + num2_display->setStep(0.5); + num2_display->setDefaultValue(0.0); + + num3_display->setVisible(source_exists); + num3_display->setDescription("Magnitude"); + num3_display->setOptions(MMGNumberDisplay::OPTIONS_FIXED_ONLY); + num3_display->setBounds(0.01, 100.0); + num3_display->setStep(0.01); + num3_display->setDefaultValue(1.0); break; - case SOURCE_VIDEO_DISPLAY: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "State"; - val.list = {"Show", "Hide", "Toggle"}; + case SOURCE_VIDEO_DISPLAY: + _display->setStr3Visible(true); + _display->setStr3Description("State"); + _display->setStr3Options({"Show", "Hide", "Toggle"}); break; - case SOURCE_VIDEO_LOCKED: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "State"; - val.list = {"Locked", "Unlocked", "Toggle"}; + case SOURCE_VIDEO_LOCKED: + _display->setStr3Visible(true); + _display->setStr3Description("State"); + _display->setStr3Options({"Locked", "Unlocked", "Toggle"}); break; + case SOURCE_VIDEO_CROP: - val.display |= MMGActionDisplayParams::DISPLAY_NUM4; - - val.label_lcds[0] = "Top"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[0]->set_range(0.0, get_obs_source_dimensions(source).y); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); - - val.label_lcds[1] = "Right"; - val.combo_display[1] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[1]->set_range(0.0, get_obs_source_dimensions(source).x); - val.lcds[1]->set_step(0.5, 5.0); - val.lcds[1]->set_default_value(0.0); - - val.label_lcds[2] = "Bottom"; - val.combo_display[2] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[2]->set_range(0.0, get_obs_source_dimensions(source).y); - val.lcds[2]->set_step(0.5, 5.0); - val.lcds[2]->set_default_value(0.0); - - val.label_lcds[3] = "Left"; - val.combo_display[3] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[3]->set_range(0.0, get_obs_source_dimensions(source).x); - val.lcds[3]->set_step(0.5, 5.0); - val.lcds[3]->set_default_value(0.0); + num1_display->setVisible(source_exists); + num1_display->setDescription("Top"); + num1_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num1_display->setBounds(0.0, sourceResolution().y); + num1_display->setStep(0.5); + num1_display->setDefaultValue(0.0); + + num2_display->setVisible(source_exists); + num2_display->setDescription("Right"); + num2_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num2_display->setBounds(0.0, sourceResolution().x); + num2_display->setStep(0.5); + num2_display->setDefaultValue(0.0); + + num3_display->setVisible(source_exists); + num3_display->setDescription("Bottom"); + num3_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num3_display->setBounds(0.0, sourceResolution().y); + num3_display->setStep(0.5); + num3_display->setDefaultValue(0.0); + + num4_display->setVisible(source_exists); + num4_display->setDescription("Left"); + num4_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num4_display->setBounds(0.0, sourceResolution().x); + num4_display->setStep(0.5); + num4_display->setDefaultValue(0.0); break; - case SOURCE_VIDEO_ALIGNMENT: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "Alignment"; - val.list = {"Top Left", "Top Center", "Top Right", "Middle Left", - "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", - "Bottom Right", "Use Message Value"}; + case SOURCE_VIDEO_ALIGNMENT: + _display->setStr3Visible(true); + _display->setStr3Description("Alignment"); + _display->setStr3Options(alignment_options); break; + case SOURCE_VIDEO_SCALE: - val.display |= MMGActionDisplayParams::DISPLAY_NUM3; - - val.label_lcds[0] = "Scale X"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[0]->set_range(0.0, 100.0); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); - - val.label_lcds[1] = "Scale Y"; - val.combo_display[1] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[1]->set_range(0.0, 100.0); - val.lcds[1]->set_step(0.5, 5.0); - val.lcds[1]->set_default_value(0.0); - - val.label_lcds[2] = "Magnitude"; - val.combo_display[2] = MMGActionDisplayParams::COMBODISPLAY_FIXED; - val.lcds[2]->set_range(0.5, 100.0); - val.lcds[2]->set_step(0.5, 5.0); - val.lcds[2]->set_default_value(1.0); + num1_display->setVisible(source_exists); + num1_display->setDescription("Scale X"); + num1_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num1_display->setBounds(0.0, 100.0); + num1_display->setStep(0.5); + num1_display->setDefaultValue(0.0); + + num2_display->setVisible(source_exists); + num2_display->setDescription("Scale Y"); + num2_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num2_display->setBounds(0.0, 100.0); + num2_display->setStep(0.5); + num2_display->setDefaultValue(0.0); + + num3_display->setVisible(source_exists); + num3_display->setDescription("Magnitude"); + num3_display->setOptions(MMGNumberDisplay::OPTIONS_FIXED_ONLY); + num3_display->setBounds(0.01, 100.0); + num3_display->setStep(0.01); + num3_display->setDefaultValue(1.0); break; - case SOURCE_VIDEO_SCALEFILTER: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "Scale Filtering"; - val.list = {"Disable", "Point", "Bicubic", "Bilinear", - "Lanczos", "Area", "Use Message Value"}; + case SOURCE_VIDEO_SCALEFILTER: + _display->setStr3Visible(true); + _display->setStr3Description("Scale Filtering"); + _display->setStr3Options(scalefilter_options); break; - case SOURCE_VIDEO_ROTATION: - val.display |= MMGActionDisplayParams::DISPLAY_NUM1; - val.label_lcds[0] = "Rotation"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_MIDI | - MMGActionDisplayParams::COMBODISPLAY_MIDI_INVERT; - val.lcds[0]->set_range(0.0, 360.0); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); + case SOURCE_VIDEO_ROTATION: + num1_display->setVisible(source_exists); + num1_display->setDescription("Rotation"); + num1_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_CUSTOM); + num1_display->setBounds(0.0, 360.0); + num1_display->setStep(0.1); break; - case SOURCE_VIDEO_BOUNDING_BOX_TYPE: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "Bounding Box Type"; - val.list = {"No Bounds", - "Stretch to Bounds", - "Scale to Inner Bounds", - "Scale to Outer Bounds", - "Scale to Width of Bounds", - "Scale to Height of Bounds", - "Maximum Size", - "Use Message Value"}; + case SOURCE_VIDEO_BOUNDING_BOX_TYPE: + _display->setStr3Visible(true); + _display->setStr3Description("Bounding Box Type"); + _display->setStr3Options(boundingbox_options); break; - case SOURCE_VIDEO_BOUNDING_BOX_SIZE: - val.display |= MMGActionDisplayParams::DISPLAY_NUM2; - - val.label_lcds[0] = "Bounding Box X"; - val.combo_display[0] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[0]->set_range(0.0, get_obs_source_dimensions(parent_scene).x); - val.lcds[0]->set_step(0.5, 5.0); - val.lcds[0]->set_default_value(0.0); - val.label_lcds[1] = "Bounding Box Y"; - val.combo_display[1] = MMGActionDisplayParams::COMBODISPLAY_ALL; - val.lcds[1]->set_range(0.0, get_obs_source_dimensions(parent_scene).y); - val.lcds[1]->set_step(0.5, 5.0); - val.lcds[1]->set_default_value(0.0); + case SOURCE_VIDEO_BOUNDING_BOX_SIZE: + num1_display->setVisible(source_exists); + num1_display->setDescription("Bounding Box X"); + num1_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num1_display->setBounds(0.0, obsResolution().x, true); + num1_display->setStep(0.5); + num1_display->setDefaultValue(0.0); + + num2_display->setVisible(source_exists); + num2_display->setDescription("Bounding Box Y"); + num2_display->setOptions(MMGNumberDisplay::OPTIONS_ALL); + num2_display->setBounds(0.0, obsResolution().y, true); + num2_display->setStep(0.5); + num2_display->setDefaultValue(0.0); break; - case SOURCE_VIDEO_BOUNDING_BOX_ALIGN: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "Bounding Box Alignment"; - val.list = {"Top Left", "Top Center", "Top Right", "Middle Left", - "Middle Center", "Middle Right", "Bottom Left", "Bottom Center", - "Bottom Right", "Use Message Value"}; + case SOURCE_VIDEO_BOUNDING_BOX_ALIGN: + _display->setStr3Visible(true); + _display->setStr3Description("Bounding Box Alignment"); + _display->setStr3Options(alignment_options); break; - case SOURCE_VIDEO_BLEND_MODE: - val.display = MMGActionDisplayParams::DISPLAY_STR3; - - val.label_text = "Blend Mode"; - val.list = {"Normal", "Additive", "Subtract", "Screen", - "Multiply", "Lighten", "Darken", "Use Message Value"}; + case SOURCE_VIDEO_BLEND_MODE: + _display->setStr3Visible(true); + _display->setStr3Description("Blend Mode"); + _display->setStr3Options(blendmode_options); break; + case SOURCE_VIDEO_SCREENSHOT: + break; + case SOURCE_VIDEO_CUSTOM: - default: + emit _display->customFieldRequest( + OBSSourceAutoRelease(obs_get_source_by_name(source.mmgtocs())), &json_str); break; + + default: + return; } + + num1_display->reset(); + num2_display->reset(); + num3_display->reset(); + num4_display->reset(); } -void MMGActionVideoSources::change_options_final(MMGActionDisplayParams &val) + +const vec2 MMGActionVideoSources::obsResolution() { - switch ((Actions)subcategory) { - case SOURCE_VIDEO_DISPLAY: - case SOURCE_VIDEO_LOCKED: - action.set_state(action == "Toggle" ? MMGString::STRINGSTATE_TOGGLE - : MMGString::STRINGSTATE_FIXED); - break; - case SOURCE_VIDEO_ALIGNMENT: - case SOURCE_VIDEO_BOUNDING_BOX_TYPE: - case SOURCE_VIDEO_BOUNDING_BOX_ALIGN: - case SOURCE_VIDEO_SCALEFILTER: - case SOURCE_VIDEO_BLEND_MODE: - num1() = val.list.indexOf(action); - num1().set_state(action == "Use Message Value" ? MMGNumber::NUMBERSTATE_MIDI - : MMGNumber::NUMBERSTATE_FIXED); - break; - default: - break; - } + obs_video_info video_info; + obs_get_video_info(&video_info); + vec2 xy; + vec2_set(&xy, video_info.base_width, video_info.base_height); + return xy; +} + +const vec2 MMGActionVideoSources::sourceResolution() const +{ + OBSSourceAutoRelease obs_source = obs_get_source_by_name(source.mmgtocs()); + vec2 xy; + vec2_set(&xy, obs_source_get_width(obs_source), obs_source_get_height(obs_source)); + return xy; } diff --git a/src/actions/mmg-action-video-sources.h b/src/actions/mmg-action-video-sources.h index df85d77..0700a2d 100644 --- a/src/actions/mmg-action-video-sources.h +++ b/src/actions/mmg-action-video-sources.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,38 +41,37 @@ class MMGActionVideoSources : public MMGAction { }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; + void execute(const MMGMessage *midi) const override; void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void copy(MMGAction *dest) const override; + void setEditable(bool edit) override; + void createDisplay(QWidget *parent) override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_SOURCE_VIDEO; } + Category category() const override { return Category::MMGACTION_SOURCE_VIDEO; } - MMGUtils::MMGString &str1() override { return parent_scene; }; - const MMGUtils::MMGString &str1() const override { return parent_scene; }; - MMGUtils::MMGString &str2() override { return source; }; - const MMGUtils::MMGString &str2() const override { return source; }; - MMGUtils::MMGString &str3() override { return action; }; - const MMGUtils::MMGString &str3() const override { return action; }; - MMGUtils::MMGNumber &num1() override { return nums[0]; }; - const MMGUtils::MMGNumber &num1() const override { return nums[0]; }; - MMGUtils::MMGNumber &num2() override { return nums[1]; }; - const MMGUtils::MMGNumber &num2() const override { return nums[1]; }; - MMGUtils::MMGNumber &num3() override { return nums[2]; }; - const MMGUtils::MMGNumber &num3() const override { return nums[2]; }; - MMGUtils::MMGNumber &num4() override { return nums[3]; }; - const MMGUtils::MMGNumber &num4() const override { return nums[3]; }; - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; - - static const QStringList enumerate(const QString &restriction = ""); + static const QStringList enumerate(); + static const vec2 obsResolution(); + const vec2 sourceResolution() const; private: MMGUtils::MMGString parent_scene; MMGUtils::MMGString source; MMGUtils::MMGString action; + MMGUtils::MMGString json_str; MMGUtils::MMGNumber nums[4]; + + void setSubConfig() override; + void setList1Config() override; + void setList2Config() override; + + const MMGUtils::MMGNumber &num1() const { return nums[0]; }; + const MMGUtils::MMGNumber &num2() const { return nums[1]; }; + const MMGUtils::MMGNumber &num3() const { return nums[2]; }; + const MMGUtils::MMGNumber &num4() const { return nums[3]; }; + + static const QStringList alignment_options; + static const QStringList boundingbox_options; + static const QStringList scalefilter_options; + static const QStringList blendmode_options; }; diff --git a/src/actions/mmg-action-virtualcam.cpp b/src/actions/mmg-action-virtualcam.cpp index 97373c3..a224420 100644 --- a/src/actions/mmg-action-virtualcam.cpp +++ b/src/actions/mmg-action-virtualcam.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,16 +32,10 @@ void MMGActionVirtualCam::blog(int log_status, const QString &message) const global_blog(log_status, " Action -> " + message); } -void MMGActionVirtualCam::json(QJsonObject &json_obj) const -{ - json_obj["category"] = (int)get_category(); - json_obj["sub"] = (int)get_sub(); -} - -void MMGActionVirtualCam::do_action(const MMGMessage *midi) +void MMGActionVirtualCam::execute(const MMGMessage *midi) const { Q_UNUSED(midi); - switch (get_sub()) { + switch (sub()) { case MMGActionVirtualCam::VIRCAM_ON: if (!obs_frontend_virtualcam_active()) obs_frontend_start_virtualcam(); break; @@ -59,19 +53,9 @@ void MMGActionVirtualCam::do_action(const MMGMessage *midi) break; } blog(LOG_DEBUG, "Successfully executed."); - executed = true; -} - -void MMGActionVirtualCam::deep_copy(MMGAction *dest) const -{ - dest->set_sub(subcategory); } -void MMGActionVirtualCam::change_options_sub(MMGActionDisplayParams &val) +void MMGActionVirtualCam::setSubOptions(QComboBox *sub) { - val.list = {"Start Virtual Camera", "Stop Virtual Camera", "Toggle Virtual Camera"}; + sub->addItems({"Start Virtual Camera", "Stop Virtual Camera", "Toggle Virtual Camera"}); } -void MMGActionVirtualCam::change_options_str1(MMGActionDisplayParams &val) {} -void MMGActionVirtualCam::change_options_str2(MMGActionDisplayParams &val) {} -void MMGActionVirtualCam::change_options_str3(MMGActionDisplayParams &val) {} -void MMGActionVirtualCam::change_options_final(MMGActionDisplayParams &val) {} diff --git a/src/actions/mmg-action-virtualcam.h b/src/actions/mmg-action-virtualcam.h index 54115bb..293fa1d 100644 --- a/src/actions/mmg-action-virtualcam.h +++ b/src/actions/mmg-action-virtualcam.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,15 +26,8 @@ class MMGActionVirtualCam : public MMGAction { enum Actions { VIRCAM_ON, VIRCAM_OFF, VIRCAM_TOGGLE_ONOFF }; void blog(int log_status, const QString &message) const override; - void do_action(const MMGMessage *midi) override; - void json(QJsonObject &json_obj) const override; - void deep_copy(MMGAction *dest) const override; + void execute(const MMGMessage *midi) const override; + void setSubOptions(QComboBox *sub) override; - Category get_category() const override { return Category::MMGACTION_VIRCAM; } - - void change_options_sub(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str1(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str2(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_str3(MMGUtils::MMGActionDisplayParams &val) override; - void change_options_final(MMGUtils::MMGActionDisplayParams &val) override; + Category category() const override { return Category::MMGACTION_VIRCAM; } }; diff --git a/src/actions/mmg-action.cpp b/src/actions/mmg-action.cpp index 92b8fd8..0e6d68a 100644 --- a/src/actions/mmg-action.cpp +++ b/src/actions/mmg-action.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,13 +16,20 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#pragma once #include "mmg-action.h" -MMGUtils::MMGString MMGAction::empty_str{}; -MMGUtils::MMGNumber MMGAction::empty_num{}; - void MMGAction::blog(int log_status, const QString &message) const { global_blog(log_status, "Actions -> " + message); -}; +} + +void MMGAction::json(QJsonObject &json_obj) const +{ + json_obj["category"] = (int)category(); + json_obj["sub"] = subcategory; +} + +void MMGAction::copy(MMGAction *dest) const +{ + dest->setSub(subcategory); +} diff --git a/src/actions/mmg-action.h b/src/actions/mmg-action.h index d7263ae..9256dc1 100644 --- a/src/actions/mmg-action.h +++ b/src/actions/mmg-action.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,8 +16,11 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#pragma once +#ifndef MMG_ACTION_H +#define MMG_ACTION_H + #include "../mmg-message.h" +#include "../ui/mmg-action-display.h" #include @@ -25,7 +28,7 @@ class MMGAction { public: virtual ~MMGAction() = default; - enum class Category { + enum Category { MMGACTION_NONE, MMGACTION_STREAM, MMGACTION_RECORD, @@ -42,60 +45,47 @@ class MMGAction { MMGACTION_PROFILE, MMGACTION_COLLECTION, MMGACTION_MIDI, - MMGACTION_INTERNAL, - MMGACTION_TIMEOUT + MMGACTION_INTERNAL }; - virtual void json(QJsonObject &action_obj) const = 0; - virtual void do_action(const MMGMessage *midi) = 0; - virtual void deep_copy(MMGAction *dest) const = 0; - virtual Category get_category() const = 0; - - short get_sub() const { return subcategory; }; - void set_sub(short val) { subcategory = val; }; - - virtual MMGUtils::MMGString &str1() { return empty_str; }; - virtual const MMGUtils::MMGString &str1() const { return empty_str; }; - virtual MMGUtils::MMGString &str2() { return empty_str; }; - virtual const MMGUtils::MMGString &str2() const { return empty_str; }; - virtual MMGUtils::MMGString &str3() { return empty_str; }; - virtual const MMGUtils::MMGString &str3() const { return empty_str; }; - virtual MMGUtils::MMGNumber &num1() { return empty_num; }; - virtual const MMGUtils::MMGNumber &num1() const { return empty_num; }; - virtual MMGUtils::MMGNumber &num2() { return empty_num; }; - virtual const MMGUtils::MMGNumber &num2() const { return empty_num; }; - virtual MMGUtils::MMGNumber &num3() { return empty_num; }; - virtual const MMGUtils::MMGNumber &num3() const { return empty_num; }; - virtual MMGUtils::MMGNumber &num4() { return empty_num; }; - virtual const MMGUtils::MMGNumber &num4() const { return empty_num; }; - - virtual void change_options_sub(MMGUtils::MMGActionDisplayParams &val) = 0; - virtual void change_options_str1(MMGUtils::MMGActionDisplayParams &val) = 0; - virtual void change_options_str2(MMGUtils::MMGActionDisplayParams &val) = 0; - virtual void change_options_str3(MMGUtils::MMGActionDisplayParams &val) = 0; - virtual void change_options_final(MMGUtils::MMGActionDisplayParams &val) = 0; + virtual void json(QJsonObject &action_obj) const; + virtual void execute(const MMGMessage *midi) const = 0; + virtual void copy(MMGAction *dest) const; + virtual Category category() const = 0; + + virtual void createDisplay(QWidget *parent) { _display = new MMGActionDisplay(parent); }; + MMGActionDisplay *display() { return _display; }; + + short sub() const { return subcategory; }; + void setSub(short val) { subcategory = val; }; + virtual void setSubOptions(QComboBox *sub) = 0; virtual void blog(int log_status, const QString &message) const; - void reset_execution() { executed = false; }; + + virtual void setEditable(bool edit) { Q_UNUSED(edit); }; + + virtual void setSubConfig(){}; protected: int subcategory = 0; - bool executed = false; + MMGActionDisplay *_display = nullptr; - private: - static MMGUtils::MMGString empty_str; - static MMGUtils::MMGNumber empty_num; + virtual void setList1Config(){}; + virtual void setList2Config(){}; + virtual void setList3Config(){}; }; // Macros #define MIDI_NUMBER_IS_NOT_IN_RANGE(mmgnumber, range) \ - mmgnumber.state() == MMGNumber::NUMBERSTATE_MIDI && (uint)midi->value() >= range + mmgnumber.state() == MMGNumber::NUMBERSTATE_MIDI && (uint)(midi->value()) >= range #define MIDI_STRING_IS_NOT_IN_RANGE(mmgstring, range) \ - mmgstring.state() == MMGString::STRINGSTATE_MIDI && (uint)midi->value() >= range + mmgstring.state() == MMGString::STRINGSTATE_MIDI && (uint)(midi->value()) >= range // End Macros struct R { QStringList list; QString str; }; + +#endif // MMG_ACTION_H diff --git a/src/mmg-action-include.h b/src/mmg-action-include.h index 099af27..d9448f9 100644 --- a/src/mmg-action-include.h +++ b/src/mmg-action-include.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,4 +35,3 @@ with this program. If not, see #include "actions/mmg-action-collections.h" #include "actions/mmg-action-midi.h" #include "actions/mmg-action-internal.h" -#include "actions/mmg-action-timeout.h" diff --git a/src/mmg-binding.cpp b/src/mmg-binding.cpp index 554c283..e0a97e1 100644 --- a/src/mmg-binding.cpp +++ b/src/mmg-binding.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,42 +18,43 @@ with this program. If not, see #include "mmg-binding.h" #include "mmg-action-include.h" +#include "mmg-config.h" -qulonglong MMGBinding::next_default = 1; +qulonglong MMGBinding::next_default = 0; MMGBinding::MMGBinding() { - name = get_next_default_name(); - enabled = true; - message = new MMGMessage; - action = new MMGActionNone; + _name = get_next_default_name(); + setEnabled(true); + _message = new MMGMessage; + _action = new MMGActionNone; blog(LOG_DEBUG, "Empty binding created."); } -MMGBinding::MMGBinding(const QJsonObject &obj) : name(obj["name"].toString()) +MMGBinding::MMGBinding(const QJsonObject &obj) : _name(obj["name"].toString()) { - if (name.isEmpty()) name = get_next_default_name(); - enabled = obj["enabled"].toBool(true); - message = new MMGMessage(obj["message"].toObject()); - set_action_type(obj["action"].toObject()); + if (_name.isEmpty()) _name = get_next_default_name(); + setEnabled(obj["enabled"].toBool(true)); + _message = new MMGMessage(obj["message"].toObject()); + setCategory(obj["action"].toObject()); blog(LOG_DEBUG, "Binding created."); } void MMGBinding::json(QJsonObject &binding_obj) const { - binding_obj["name"] = name; - binding_obj["enabled"] = enabled; + binding_obj["name"] = _name; + binding_obj["enabled"] = _enabled; QJsonObject msg; - message->json(msg); + _message->json(msg); binding_obj["message"] = msg; QJsonObject act; - action->json(act); + _action->json(act); binding_obj["action"] = act; } void MMGBinding::blog(int log_status, const QString &message) const { - global_blog(log_status, "Binding {" + name + "} -> " + message); + global_blog(log_status, "Binding {" + _name + "} -> " + message); } QString MMGBinding::get_next_default_name() @@ -61,145 +62,146 @@ QString MMGBinding::get_next_default_name() return QVariant(++MMGBinding::next_default).toString().prepend("Untitled Binding "); } -void MMGBinding::do_action(const MMGSharedMessage &incoming) +void MMGBinding::execute(const MMGSharedMessage &incoming) { - if (!enabled) return; - - if (!message->is_acceptable(incoming.get())) { + if (!_message->acceptable(incoming.get())) { blog(LOG_DEBUG, "Message received is not acceptable."); - return; + } else { + _action->execute(incoming.get()); + _message->toggle(); } +} - action->do_action(incoming.get()); - - message->toggle(); +void MMGBinding::copy(MMGBinding *dest) +{ + if (!_name.contains("Untitled Binding")) dest->setName(_name); + _message->copy(dest->message()); + dest->setCategory((int)_action->category()); + _action->copy(dest->action()); } -void MMGBinding::deep_copy(MMGBinding *dest) +void MMGBinding::setEnabled(bool enabled) { - if (!name.contains("Untitled Binding")) dest->set_name(name); - message->deep_copy(dest->get_message()); - dest->set_action_type((int)action->get_category()); - action->deep_copy(dest->get_action()); + _enabled = enabled; + + if (enabled) { + connect(input_device().get(), &MMGMIDIIn::messageReceived, this, &MMGBinding::execute); + } else { + disconnect(input_device().get(), &MMGMIDIIn::messageReceived, this, &MMGBinding::execute); + } } -void MMGBinding::set_action_type(int index) +void MMGBinding::setCategory(int index) { - delete action; + delete _action; switch ((MMGAction::Category)index) { - case MMGAction::Category::MMGACTION_STREAM: - action = new MMGActionStream(); + case MMGAction::MMGACTION_STREAM: + _action = new MMGActionStream(); break; - case MMGAction::Category::MMGACTION_RECORD: - action = new MMGActionRecord(); + case MMGAction::MMGACTION_RECORD: + _action = new MMGActionRecord(); break; - case MMGAction::Category::MMGACTION_VIRCAM: - action = new MMGActionVirtualCam(); + case MMGAction::MMGACTION_VIRCAM: + _action = new MMGActionVirtualCam(); break; - case MMGAction::Category::MMGACTION_REPBUF: - action = new MMGActionReplayBuffer(); + case MMGAction::MMGACTION_REPBUF: + _action = new MMGActionReplayBuffer(); break; - case MMGAction::Category::MMGACTION_STUDIOMODE: - action = new MMGActionStudioMode(); + case MMGAction::MMGACTION_STUDIOMODE: + _action = new MMGActionStudioMode(); break; - case MMGAction::Category::MMGACTION_SCENE: - action = new MMGActionScenes(); + case MMGAction::MMGACTION_SCENE: + _action = new MMGActionScenes(); break; - case MMGAction::Category::MMGACTION_SOURCE_VIDEO: - action = new MMGActionVideoSources(); + case MMGAction::MMGACTION_SOURCE_VIDEO: + _action = new MMGActionVideoSources(); break; - case MMGAction::Category::MMGACTION_SOURCE_AUDIO: - action = new MMGActionAudioSources(); + case MMGAction::MMGACTION_SOURCE_AUDIO: + _action = new MMGActionAudioSources(); break; - case MMGAction::Category::MMGACTION_SOURCE_MEDIA: - action = new MMGActionMediaSources(); + case MMGAction::MMGACTION_SOURCE_MEDIA: + _action = new MMGActionMediaSources(); break; - case MMGAction::Category::MMGACTION_TRANSITION: - action = new MMGActionTransitions(); + case MMGAction::MMGACTION_TRANSITION: + _action = new MMGActionTransitions(); break; - case MMGAction::Category::MMGACTION_FILTER: - action = new MMGActionFilters(); + case MMGAction::MMGACTION_FILTER: + _action = new MMGActionFilters(); break; - case MMGAction::Category::MMGACTION_HOTKEY: - action = new MMGActionHotkeys(); + case MMGAction::MMGACTION_HOTKEY: + _action = new MMGActionHotkeys(); break; - case MMGAction::Category::MMGACTION_PROFILE: - action = new MMGActionProfiles(); + case MMGAction::MMGACTION_PROFILE: + _action = new MMGActionProfiles(); break; - case MMGAction::Category::MMGACTION_COLLECTION: - action = new MMGActionCollections(); + case MMGAction::MMGACTION_COLLECTION: + _action = new MMGActionCollections(); break; - case MMGAction::Category::MMGACTION_MIDI: - action = new MMGActionMIDI(); + case MMGAction::MMGACTION_MIDI: + _action = new MMGActionMIDI(); break; - case MMGAction::Category::MMGACTION_INTERNAL: - action = new MMGActionInternal(); - break; - case MMGAction::Category::MMGACTION_TIMEOUT: - action = new MMGActionTimeout(); + case MMGAction::MMGACTION_INTERNAL: + _action = new MMGActionInternal(); break; default: - action = new MMGActionNone(); + _action = new MMGActionNone(); break; } } -void MMGBinding::set_action_type(const QJsonObject &json_obj) +void MMGBinding::setCategory(const QJsonObject &json_obj) { switch ((MMGAction::Category)json_obj["category"].toInt()) { - case MMGAction::Category::MMGACTION_STREAM: - action = new MMGActionStream(json_obj); - break; - case MMGAction::Category::MMGACTION_RECORD: - action = new MMGActionRecord(json_obj); + case MMGAction::MMGACTION_STREAM: + _action = new MMGActionStream(json_obj); break; - case MMGAction::Category::MMGACTION_VIRCAM: - action = new MMGActionVirtualCam(json_obj); + case MMGAction::MMGACTION_RECORD: + _action = new MMGActionRecord(json_obj); break; - case MMGAction::Category::MMGACTION_REPBUF: - action = new MMGActionReplayBuffer(json_obj); + case MMGAction::MMGACTION_VIRCAM: + _action = new MMGActionVirtualCam(json_obj); break; - case MMGAction::Category::MMGACTION_STUDIOMODE: - action = new MMGActionStudioMode(json_obj); + case MMGAction::MMGACTION_REPBUF: + _action = new MMGActionReplayBuffer(json_obj); break; - case MMGAction::Category::MMGACTION_SCENE: - action = new MMGActionScenes(json_obj); + case MMGAction::MMGACTION_STUDIOMODE: + _action = new MMGActionStudioMode(json_obj); break; - case MMGAction::Category::MMGACTION_SOURCE_VIDEO: - action = new MMGActionVideoSources(json_obj); + case MMGAction::MMGACTION_SCENE: + _action = new MMGActionScenes(json_obj); break; - case MMGAction::Category::MMGACTION_SOURCE_AUDIO: - action = new MMGActionAudioSources(json_obj); + case MMGAction::MMGACTION_SOURCE_VIDEO: + _action = new MMGActionVideoSources(json_obj); break; - case MMGAction::Category::MMGACTION_SOURCE_MEDIA: - action = new MMGActionMediaSources(json_obj); + case MMGAction::MMGACTION_SOURCE_AUDIO: + _action = new MMGActionAudioSources(json_obj); break; - case MMGAction::Category::MMGACTION_TRANSITION: - action = new MMGActionTransitions(json_obj); + case MMGAction::MMGACTION_SOURCE_MEDIA: + _action = new MMGActionMediaSources(json_obj); break; - case MMGAction::Category::MMGACTION_FILTER: - action = new MMGActionFilters(json_obj); + case MMGAction::MMGACTION_TRANSITION: + _action = new MMGActionTransitions(json_obj); break; - case MMGAction::Category::MMGACTION_HOTKEY: - action = new MMGActionHotkeys(json_obj); + case MMGAction::MMGACTION_FILTER: + _action = new MMGActionFilters(json_obj); break; - case MMGAction::Category::MMGACTION_PROFILE: - action = new MMGActionProfiles(json_obj); + case MMGAction::MMGACTION_HOTKEY: + _action = new MMGActionHotkeys(json_obj); break; - case MMGAction::Category::MMGACTION_COLLECTION: - action = new MMGActionCollections(json_obj); + case MMGAction::MMGACTION_PROFILE: + _action = new MMGActionProfiles(json_obj); break; - case MMGAction::Category::MMGACTION_MIDI: - action = new MMGActionMIDI(json_obj); + case MMGAction::MMGACTION_COLLECTION: + _action = new MMGActionCollections(json_obj); break; - case MMGAction::Category::MMGACTION_INTERNAL: - action = new MMGActionInternal(json_obj); + case MMGAction::MMGACTION_MIDI: + _action = new MMGActionMIDI(json_obj); break; - case MMGAction::Category::MMGACTION_TIMEOUT: - action = new MMGActionTimeout(json_obj); + case MMGAction::MMGACTION_INTERNAL: + _action = new MMGActionInternal(json_obj); break; default: - action = new MMGActionNone(); + _action = new MMGActionNone(); break; } } diff --git a/src/mmg-binding.h b/src/mmg-binding.h index bc69f90..728413f 100644 --- a/src/mmg-binding.h +++ b/src/mmg-binding.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,49 +16,60 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#pragma once +#ifndef MMG_BINDING_H +#define MMG_BINDING_H + #include "mmg-message.h" #include "actions/mmg-action.h" +#include "mmg-midiin.h" +#include "mmg-midiout.h" + +class MMGBinding : public QObject { + Q_OBJECT -class MMGBinding { public: explicit MMGBinding(); explicit MMGBinding(const QJsonObject &obj); ~MMGBinding() { - delete message; - delete action; + delete _message; + delete _action; }; void json(QJsonObject &binding_obj) const; void blog(int log_status, const QString &message) const; - const QString &get_name() const { return name; }; - bool get_enabled() const { return enabled; }; + const QString &name() const { return _name; }; + bool enabled() const { return _enabled; }; - void set_name(const QString &val) { name = val; }; - void set_enabled(bool val) { enabled = val; }; + void setName(const QString &val) { _name = val; }; + void setEnabled(bool val); - MMGMessage *const get_message() const { return message; }; - MMGAction *const get_action() const { return action; }; + MMGMessage *message() { return _message; }; + const MMGMessage *message() const { return _message; }; + MMGAction *action() { return _action; }; + const MMGAction *action() const { return _action; }; - void set_action_type(int index); - void set_action_type(const QJsonObject &json_obj); - void deep_copy(MMGBinding *dest); - void do_action(const MMGSharedMessage &el); - void reset_execution() { action->reset_execution(); }; + void setCategory(int index); + void setCategory(const QJsonObject &json_obj); + void copy(MMGBinding *dest); static qulonglong get_next_default() { return next_default; }; static void set_next_default(qulonglong num) { next_default = num; }; static QString get_next_default_name(); + public slots: + void execute(const MMGSharedMessage &); + private: - QString name; - bool enabled; - MMGMessage *message; - MMGAction *action; + QString _name; + bool _enabled; + MMGMessage *_message; + MMGAction *_action; static qulonglong next_default; }; using MMGBindingList = QList; + +#endif // MMG_BINDING_H diff --git a/src/mmg-config.cpp b/src/mmg-config.cpp index 940c996..c201bbd 100644 --- a/src/mmg-config.cpp +++ b/src/mmg-config.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,33 +25,38 @@ with this program. If not, see #include #include +#include using namespace MMGUtils; // MMGSettings MMGSettings::MMGSettings(const QJsonObject &settings_obj) { - set_active(settings_obj["active"].toBool(true)); + setActive(settings_obj["active"].toBool(true)); + setThruDevice(settings_obj["thru_device"].toString()); } void MMGSettings::json(QJsonObject &settings_obj) const { - settings_obj["active"] = active; + settings_obj["active"] = _active; + settings_obj["thru_device"] = thru_device; } -void MMGSettings::set_active(bool is_active) +void MMGSettings::setActive(bool is_active) { - active = is_active; + _active = is_active; if (!global()) return; - if (!active) - MMGDevice::close_input_port(); + if (!_active) + input_device()->closeInputPort(); else - MMGDevice::open_input_port(global()->find_current_device()); + input_device()->openInputPort(global()->currentDevice()); }; // End MMGSettings // MMGConfig -bool MMGConfig::listening = false; -std::function MMGConfig::cb = 0; +MMGConfig::MMGConfig() +{ + load(); +}; void MMGConfig::blog(int log_status, const QString &message) const { @@ -61,8 +66,8 @@ void MMGConfig::blog(int log_status, const QString &message) const void MMGConfig::check_device_default_names() { for (const MMGDevice *device : devices) { - if (device->get_name().startsWith("Untitled Device ")) { - QString name = device->get_name(); + if (device->name().startsWith("Untitled Device ")) { + QString name = device->name(); qulonglong num = QVariant(name.remove("Untitled Device ")).toULongLong(); if (num > MMGDevice::get_next_default()) MMGDevice::set_next_default(num); } @@ -73,16 +78,12 @@ void MMGConfig::load(const QString &path_str) { blog(LOG_INFO, "Loading configuration..."); - auto default_path = obs_module_config_path(get_filepath().qtocs()); + auto default_path = obs_module_config_path(filepath().qtocs()); QFile file(!path_str.isEmpty() ? qPrintable(path_str) : default_path); file.open(QFile::ReadOnly | QFile::Text); QByteArray config_contents; if (file.exists()) { - QString log_str = "Loading configuration file data from "; - log_str.append(file.fileName()); - log_str.append("..."); - blog(LOG_INFO, log_str); - + blog(LOG_INFO, "Loading configuration file data from " + file.fileName() + "..."); config_contents = file.readAll(); } else { blog(LOG_INFO, "Configuration file not found. Loading new data..."); @@ -102,22 +103,31 @@ void MMGConfig::load(const QString &path_str) if (!devices.isEmpty()) clear(); - if (json_key_exists(doc.object(), "config", QJsonValue::Array)) { + if (doc.object().contains("config")) { // JSON file found, introduce devices from the file (even if nonexistent) - for (QJsonValue obj : doc["config"].toArray()) { - if (json_is_valid(obj, QJsonValue::Object) && !obj.toObject().isEmpty()) { - devices.append(new MMGDevice(obj.toObject())); + for (QJsonValue device : doc["config"].toArray()) { + // If file is old, kill the import process as it is unsupported after v2.3.0 + if (device["bindings"].isArray()) { + if (device["bindings"][0].toObject().contains("actions")) { + blog(LOG_INFO, "Configuration file data is outdated and cannot be accepted."); + blog(LOG_INFO, "Reverting to default extraction..."); + goto finish; + } } + + QJsonObject device_obj = device.toObject(); + if (device_obj.isEmpty()) continue; + devices.append(new MMGDevice(device_obj)); + devices.last()->setParent(this); } // Load active device - if (json_is_valid(doc["active_device"], QJsonValue::String)) { - active_device_name = doc["active_device"].toString(); - } + if (doc["active_device"].isString()) active_device_name = doc["active_device"].toString(); // Load preferences - if (json_is_valid(doc["preferences"], QJsonValue::Object)) - settings = MMGSettings(doc["preferences"].toObject()); + if (doc["preferences"].isObject()) settings = MMGSettings(doc["preferences"].toObject()); } + +finish: blog(LOG_INFO, "Configuration file data extraction complete."); // Check for any unaffiliated devices (i.e. created by obs-midi-mg) @@ -145,7 +155,7 @@ void MMGConfig::save(const QString &path_str) const config_obj["active_device"] = active_device_name; - auto default_path = obs_module_config_path(get_filepath().qtocs()); + auto default_path = obs_module_config_path(filepath().qtocs()); QFile file(!path_str.isEmpty() ? qPrintable(path_str) : default_path); file.open(QFile::WriteOnly | QFile::Text | QFile::Truncate); qlonglong result = file.write(json_to_str(config_obj)); @@ -153,10 +163,7 @@ void MMGConfig::save(const QString &path_str) const blog(LOG_INFO, "Configuration unable to be saved. Reason: "); blog(LOG_INFO, file.errorString()); } else { - QString log_str = "Configuration successfully saved to "; - log_str.append(file.fileName()); - log_str.append("."); - blog(LOG_INFO, log_str); + blog(LOG_INFO, "Configuration successfully saved to " + file.fileName() + "."); } file.close(); bfree(default_path); @@ -168,76 +175,61 @@ void MMGConfig::clear() devices.clear(); } -void MMGConfig::load_new_devices() +void MMGConfig::refresh() { // Check for devices currently connected that are not included // and include them if necessary - for (const QString &name : MMGDevice::get_input_device_names()) { + for (const QString &name : input_device()->inputDeviceNames()) { bool device_included = false; for (MMGDevice *device : devices) { - device_included |= (device->get_name() == name); + device_included |= (device->name() == name); } if (!device_included) { devices.append(new MMGDevice()); - devices.last()->set_name(name); + devices.last()->setName(name); blog(LOG_INFO, "New device detected."); } } - for (const QString &name : MMGDevice::get_output_device_names()) { + for (const QString &name : output_device()->outputDeviceNames()) { bool device_included = false; for (MMGDevice *device : devices) { - device_included |= (device->get_name() == name); + device_included |= (device->name() == name); } if (!device_included) { devices.append(new MMGDevice()); - devices.last()->set_name(name); + devices.last()->setName(name); blog(LOG_INFO, "New device detected."); } } - MMGDevice::open_input_port(find_current_device()); + if (currentDevice() && preferences()->active()) input_device()->openInputPort(currentDevice()); } -void MMGConfig::set_active_device_name(const QString &name) +void MMGConfig::setActiveDeviceName(const QString &name) { if (active_device_name == name) return; active_device_name = name; - if (settings.get_active()) MMGDevice::open_input_port(find_current_device()); + if (settings.active()) input_device()->openInputPort(currentDevice()); } -MMGDevice *MMGConfig::find_device(const QString &name) +MMGDevice *MMGConfig::find(const QString &name) { for (MMGDevice *const device : devices) { - if (device->get_name() == name) return device; + if (device->name() == name) return device; } return nullptr; } -bool MMGConfig::is_listening(MMGMessage *incoming) -{ - if (listening) { - obs_queue_task( - OBS_TASK_UI, - [](void *param) { - auto incoming = static_cast(param); - cb(incoming); - }, - incoming, true); - return true; - } - return false; -} - -const QStringList MMGConfig::get_device_names() const +const QStringList MMGConfig::allDeviceNames() const { QStringList names; for (MMGDevice *const device : devices) { - names.append(device->get_name()); + names.append(device->name()); } return names; } -QString MMGConfig::get_filepath() +QString MMGConfig::filepath() { return "obs-midi-mg-config.json"; } diff --git a/src/mmg-config.h b/src/mmg-config.h index e87eb9c..04e03eb 100644 --- a/src/mmg-config.h +++ b/src/mmg-config.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,6 +16,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ +#ifndef MMG_CONFIG_H +#define MMG_CONFIG_H + #include "mmg-device.h" struct MMGSettings { @@ -24,35 +27,35 @@ struct MMGSettings { explicit MMGSettings(const QJsonObject &settings_obj); void json(QJsonObject &settings_obj) const; - bool get_active() const { return active; }; - void set_active(bool is_active); + bool active() const { return _active; }; + const QString &thruDevice() const { return thru_device; }; + void setActive(bool is_active); + void setThruDevice(const QString &name) { thru_device = name; }; private: - bool active = true; + bool _active = true; + QString thru_device; }; -class MMGConfig { +class MMGConfig : public QObject { + Q_OBJECT + public: - MMGConfig() { load(); }; - ~MMGConfig() { clear(); }; + MMGConfig(); void blog(int log_status, const QString &message) const; void load(const QString &path_str = QString()); void save(const QString &path_str = QString()) const; void clear(); - void load_new_devices(); - const QString &get_active_device_name() { return active_device_name; }; - void set_active_device_name(const QString &name); - MMGDevice *find_device(const QString &name); - MMGDevice *find_current_device() { return find_device(active_device_name); }; - const QStringList get_device_names() const; - MMGSettings &preferences() { return settings; }; - - static QString get_filepath(); - static void set_listening_callback(std::function callback) { cb = callback; }; - static void set_listening(bool value) { listening = value; }; - static bool is_listening(MMGMessage *incoming); + void refresh(); + const QString &activeDeviceName() { return active_device_name; }; + void setActiveDeviceName(const QString &name); + MMGDevice *find(const QString &name); + MMGDevice *currentDevice() { return find(active_device_name); }; + const QStringList allDeviceNames() const; + MMGSettings *preferences() { return &settings; }; + static QString filepath(); private: MMGDevices devices; @@ -60,8 +63,6 @@ class MMGConfig { QString active_device_name = ""; void check_device_default_names(); - - static bool listening; - static std::function cb; }; -Q_DECLARE_METATYPE(MMGConfig); + +#endif // MMG_CONFIG_H diff --git a/src/mmg-device.cpp b/src/mmg-device.cpp index 3444d53..fb2de8a 100644 --- a/src/mmg-device.cpp +++ b/src/mmg-device.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -17,62 +17,24 @@ with this program. If not, see */ #include "mmg-device.h" +#include "mmg-config.h" -bool json_old_bindings(const QJsonObject &json, MMGDevice *parent) -{ - if (json.isEmpty()) return false; - - QList message_list; - QList action_list; - - if (MMGUtils::json_key_exists(json, "messages", QJsonValue::Array)) { - for (QJsonValue val : json["messages"].toArray()) { - message_list.append(new MMGMessage(val.toObject())); - } - } - if (MMGUtils::json_key_exists(json, "actions", QJsonValue::Array)) { - for (QJsonValue val : json["actions"].toArray()) { - json["action"] = val.toObject(); - MMGBinding temp_binding(json); - action_list.append(temp_binding.get_action()); - } - } - - if (action_list.isEmpty()) return false; - - for (qlonglong i = 0; i < action_list.size(); ++i) { - MMGBinding *binding = parent->add(); - if (parent->find_binding(json["name"].toString())) { - binding->set_name(binding->get_next_default_name()); - } else { - binding->set_name(json["name"].toString()); - } - if (message_list.value(i)) { - *(binding->get_message()) = *(message_list[i]); - } else { - *(binding->get_message()) = *(message_list.last()); - } - *(binding->get_action()) = *(action_list[i]); - } - - return true; -} +using namespace MMGUtils; qulonglong MMGDevice::next_default = 0; MMGDevice::MMGDevice(const QJsonObject &data) { - name = data["name"].toString(); - if (name.isEmpty()) name = get_next_default_name(); - if (MMGUtils::json_key_exists(data, "bindings", QJsonValue::Array)) { - QJsonArray arr = data["bindings"].toArray(); - for (QJsonValue json_binding : arr) { - if (json_old_bindings(json_binding.toObject(), this)) continue; - if (MMGUtils::json_is_valid(json_binding, QJsonValue::Object)) { - add(new MMGBinding(json_binding.toObject())); - } + _name = data["name"].toString(); + if (_name.isEmpty()) _name = get_next_default_name(); + if (data.contains("bindings")) { + for (QJsonValue json_binding : data["bindings"].toArray()) { + QJsonObject json_obj = json_binding.toObject(); + if (json_obj.isEmpty()) continue; + add(new MMGBinding(json_binding.toObject())); } } + check_binding_default_names(); blog(LOG_DEBUG, "Device created."); } @@ -80,8 +42,8 @@ MMGDevice::MMGDevice(const QJsonObject &data) void MMGDevice::json(QJsonObject &device_obj) const { QJsonArray json_bindings; - device_obj["name"] = name; - for (const MMGBinding *const binding : bindings) { + device_obj["name"] = _name; + for (const MMGBinding *const binding : _bindings) { QJsonObject json_binding; binding->json(json_binding); json_bindings += json_binding; @@ -91,7 +53,7 @@ void MMGDevice::json(QJsonObject &device_obj) const void MMGDevice::blog(int log_status, const QString &message) const { - global_blog(log_status, "Device {" + name + "} -> " + message); + global_blog(log_status, "Device {" + _name + "} -> " + message); } QString MMGDevice::get_next_default_name() @@ -101,9 +63,9 @@ QString MMGDevice::get_next_default_name() void MMGDevice::check_binding_default_names() { - for (const MMGBinding *binding : bindings) { - if (binding->get_name().contains("Untitled Binding ")) { - QString name = binding->get_name(); + for (const MMGBinding *binding : _bindings) { + if (binding->name().contains("Untitled Binding ")) { + QString name = binding->name(); qulonglong num = QVariant(name.split("Untitled Binding ").last()).toULongLong(); if (num > MMGBinding::get_next_default()) MMGBinding::set_next_default(num); } @@ -112,204 +74,59 @@ void MMGDevice::check_binding_default_names() MMGBinding *MMGDevice::add(MMGBinding *const el) { - bindings.append(el); + _bindings.append(el); + el->setParent(this); return el; } MMGBinding *MMGDevice::copy(MMGBinding *const el) { MMGBinding *new_binding = add(); - el->deep_copy(new_binding); - new_binding->set_name("(Copy of " + el->get_name() + ") " + new_binding->get_name()); + QString old_name = new_binding->name(); + el->copy(new_binding); + new_binding->setName("(Copy of " + el->name() + ") " + old_name); return new_binding; } void MMGDevice::move(int from, int to) { if (from >= size()) return; - to == size() ? bindings.append(bindings.takeAt(from)) : bindings.move(from, to); + to == size() ? _bindings.append(_bindings.takeAt(from)) : _bindings.move(from, to); } void MMGDevice::remove(MMGBinding *const el) { - bindings.removeAt(index_of(el)); + _bindings.removeAt(indexOf(el)); } -int MMGDevice::index_of(MMGBinding *const el) +int MMGDevice::indexOf(MMGBinding *const el) { int i = -1; - for (MMGBinding *const binding : bindings) { + for (MMGBinding *const binding : _bindings) { ++i; if (binding == el) return i; } return -1; } -int MMGDevice::size() const +qint64 MMGDevice::size() const { - return bindings.size(); + return _bindings.size(); } -MMGBinding *MMGDevice::find_binding(const QString &name) +MMGBinding *MMGDevice::find(const QString &name) { - for (MMGBinding *const el : bindings) { - if (el->get_name() == name) return el; + for (MMGBinding *const el : _bindings) { + if (el->name() == name) return el; } return nullptr; } -void MMGDevice::open_input_port(MMGDevice *device) -{ - if (!device) return; - - if (input_port_open()) close_input_port(); - - device->blog(LOG_INFO, "Opening input port..."); - - if (get_input_port_number(device->get_name()) == (uint)-1) { - device->blog(LOG_INFO, "Port opening failed: Device is disconnected or does not exist."); - return; - } - - try { - input_device()->set_callback(MMGUtils::call_midi_callback); - input_device()->open_port(get_input_port_number(device->get_name())); - device->blog(LOG_INFO, "Input port successfully opened."); - } catch (const libremidi::driver_error &err) { - device->blog(LOG_INFO, err.what()); - } catch (const libremidi::invalid_parameter_error &err) { - device->blog(LOG_INFO, err.what()); - } catch (const libremidi::system_error &err) { - device->blog(LOG_INFO, err.what()); - } -} - -void MMGDevice::open_output_port(MMGDevice *device) -{ - if (!device) return; - - if (output_port_open()) close_output_port(); - - device->blog(LOG_INFO, "Opening output port..."); - - if (get_output_port_number(device->get_name()) == (uint)-1) { - device->blog(LOG_INFO, "Port opening failed: Device is disconnected or does not exist."); - return; - } - - try { - output_device()->open_port(get_output_port_number(device->get_name())); - device->blog(LOG_INFO, "Output port successfully opened."); - } catch (const libremidi::driver_error &err) { - device->blog(LOG_INFO, err.what()); - } catch (const libremidi::invalid_parameter_error &err) { - device->blog(LOG_INFO, err.what()); - } catch (const libremidi::system_error &err) { - device->blog(LOG_INFO, err.what()); - } -} - -void MMGDevice::close_input_port() -{ - input_device()->cancel_callback(); - input_device()->close_port(); - global_blog(LOG_INFO, "Main -> Input port closed."); -} - -void MMGDevice::close_output_port() -{ - output_device()->close_port(); - global_blog(LOG_INFO, "Main -> Output port closed."); -} - -bool MMGDevice::input_port_open() +void MMGDevice::copy(MMGDevice *dest) { - return input_device()->is_port_open(); -} - -bool MMGDevice::output_port_open() -{ - return output_device()->is_port_open(); -} - -void MMGDevice::output_send(const libremidi::message &message) -{ - output_device()->send_message(message); -} - -const QString MMGDevice::input_device_status() const -{ - if (get_input_port_number(name) != (uint)-1) { - return "Ready"; - } else if (get_output_port_number(name) != (uint)-1) { - return "Unavailable"; - } else { - return "Not Connected"; - } -} - -const QString MMGDevice::output_device_status() const -{ - if (get_output_port_number(name) != (uint)-1) { - return "Ready"; - } else if (get_input_port_number(name) != (uint)-1) { - return "Unavailable"; - } else { - return "Not Connected"; - } -} - -QStringList MMGDevice::get_input_device_names() -{ - QStringList inputs; - for (uint i = 0; i < input_device()->get_port_count(); ++i) { - inputs.append(QString::fromStdString(input_device()->get_port_name(i))); - } - return inputs; -} -/* - * Returns QStringList of Output Port Names - */ -QStringList MMGDevice::get_output_device_names() -{ - QStringList outputs; - for (uint i = 0; i < output_device()->get_port_count(); ++i) { - outputs.append(QString::fromStdString(output_device()->get_port_name(i))); - } - return outputs; -} - -uint MMGDevice::get_input_port_number(const QString &device_name) -{ - return get_input_device_names().indexOf(device_name); -} - -uint MMGDevice::get_output_port_number(const QString &device_name) -{ - return get_output_device_names().indexOf(device_name); -} - -void MMGDevice::do_all_actions(const MMGSharedMessage &message) -{ - for (MMGBinding *const el : bindings) { - el->do_action(message); - } - for (MMGBinding *const el : bindings) { - el->reset_execution(); - } -} - -void MMGDevice::deep_copy(MMGDevice *dest) -{ - dest->set_name(name); - for (MMGBinding *binding : bindings) { + dest->setName(_name); + for (MMGBinding *binding : _bindings) { MMGBinding *new_binding = dest->add(); - binding->deep_copy(new_binding); + binding->copy(new_binding); } } - -MMGDevice::~MMGDevice() -{ - qDeleteAll(bindings); - clear(); -} diff --git a/src/mmg-device.h b/src/mmg-device.h index 608accc..c407f65 100644 --- a/src/mmg-device.h +++ b/src/mmg-device.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,59 +16,44 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#pragma once +#ifndef MMG_DEVICE_H +#define MMG_DEVICE_H #include "mmg-binding.h" -class MMGDevice { +class MMGDevice : public QObject { + Q_OBJECT + public: MMGDevice() = default; explicit MMGDevice(const QJsonObject &data); - ~MMGDevice(); void json(QJsonObject &device_obj) const; void blog(int log_status, const QString &message) const; - const QString &get_name() const { return name; }; - void set_name(const QString &val) { name = val; }; + const QString &name() const { return _name; }; + void setName(const QString &val) { _name = val; }; - const MMGBindingList &get_bindings() const { return bindings; }; + void copy(MMGDevice *dest); - MMGBinding *add(MMGBinding *const el = new MMGBinding); - MMGBinding *copy(MMGBinding *const el); + MMGBinding *add(MMGBinding *el = new MMGBinding); + MMGBinding *copy(MMGBinding *el); void move(int from, int to); - void remove(MMGBinding *const el); - MMGBinding *find_binding(const QString &name); - int index_of(MMGBinding *const el); - int size() const; - void clear() { bindings.clear(); }; - - void deep_copy(MMGDevice *dest); - - void do_all_actions(const MMGSharedMessage &message); - - static void open_input_port(MMGDevice *device); - static void open_output_port(MMGDevice *device); - static void close_input_port(); - static void close_output_port(); - static bool input_port_open(); - static bool output_port_open(); - static void output_send(const libremidi::message &message); - const QString input_device_status() const; - const QString output_device_status() const; - - static QStringList get_input_device_names(); - static uint get_input_port_number(const QString &deviceName); - static QStringList get_output_device_names(); - static uint get_output_port_number(const QString &deviceName); + void remove(MMGBinding *el); + MMGBinding *find(const QString &name); + int indexOf(MMGBinding *el); + qint64 size() const; + void clear() { _bindings.clear(); }; + + const MMGBindingList &bindings() const { return _bindings; }; static qulonglong get_next_default() { return next_default; }; static void set_next_default(qulonglong num) { next_default = num; }; static QString get_next_default_name(); private: - QString name; - QList bindings; + QString _name; + QList _bindings; static qulonglong next_default; @@ -76,3 +61,5 @@ class MMGDevice { }; using MMGDevices = QList; + +#endif // MMG_DEVICE_H diff --git a/src/mmg-message.cpp b/src/mmg-message.cpp index cb74e27..ae0d3a5 100644 --- a/src/mmg-message.cpp +++ b/src/mmg-message.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -25,6 +25,7 @@ MMGMessage::MMGMessage() : _channel(), _type(), _note(), _value() _channel = 1; _type = "Note On"; _value.set_state(MMGNumber::NUMBERSTATE_MIDI); + _value.set_max(127); blog(LOG_DEBUG, "Empty message created."); } @@ -37,17 +38,18 @@ MMGMessage::MMGMessage(const libremidi::message &message) } MMGMessage::MMGMessage(const QJsonObject &obj) - : _type(obj, "type", 0), - _channel(obj, "channel", 0), + : _channel(obj, "channel", 0), + _type(obj, "type", 0), _note(obj, "note", 0), _value(obj, "value", 0) { if (_channel == 0) _channel = 1; - if ((obj["value_require"].isBool() && !obj["value_require"].toBool()) || obj["value"].toInt() < 0) + if ((obj["value_require"].isBool() && !(obj["value_require"].toBool())) || _value < 0) _value.set_state(MMGNumber::NUMBERSTATE_MIDI); - if (obj["value_toggle"].isBool() && obj["value_toggle"].toBool()) + if ((obj["value_toggle"].isBool() && obj["value_toggle"].toBool()) || + obj["value_state"].toInt() == 2) _value.set_state(MMGNumber::NUMBERSTATE_IGNORE); if (obj["type_toggle"].isBool() && obj["type_toggle"].toBool()) @@ -59,9 +61,9 @@ MMGMessage::MMGMessage(const QJsonObject &obj) void MMGMessage::json(QJsonObject &message_obj) const { _channel.json(message_obj, "channel", false); - _type.json(message_obj, "type", true); + _type.json(message_obj, "type"); _note.json(message_obj, "note", false); - _value.json(message_obj, "value", true); + _value.json(message_obj, "value"); } void MMGMessage::blog(int log_status, const QString &message) const @@ -162,7 +164,7 @@ void MMGMessage::toggle() _type = "Note On"; } } - if (_value.state() == MMGNumber::NUMBERSTATE_MIDI_INVERT) { + if (_value.state() == MMGNumber::NUMBERSTATE_IGNORE) { // TOGGLE SETTING if (_value == 127) { _value = 0; } else if (_value == 0) { @@ -171,20 +173,35 @@ void MMGMessage::toggle() } } -bool MMGMessage::is_acceptable(const MMGMessage *test) const +bool MMGMessage::acceptable(const MMGMessage *test) const { bool is_true = true; is_true &= (_channel == test->channel()); is_true &= (_type == test->type().str()); - if (_type != "Program Change" && _type != "Pitch Bend") is_true &= (_note == test->note().num()); - if (_value.state() != MMGNumber::NUMBERSTATE_MIDI) is_true &= (_value == test->value().num()); - return is_true; + if (_type != "Program Change" && _type != "Pitch Bend") is_true &= (_note == test->note()); + if (_value.state() == 1) return is_true; + if (_value.state() == 2) { + if (_value.min() <= _value.max()) { + return is_true && (test->value() >= _value.min() && test->value() <= _value.max()); + } else { + return is_true && (test->value() < _value.min() && test->value() > _value.max()); + } + } + return is_true && (_value == test->value()); +} + +void MMGMessage::copy(MMGMessage *dest) const +{ + dest->_channel = _channel.copy(); + dest->_type = _type.copy(); + dest->_note = _note.copy(); + dest->_value = _value.copy(); } -void MMGMessage::deep_copy(MMGMessage *dest) +void MMGMessage::setEditable(bool edit) { - _channel.copy(&dest->channel()); - _type.copy(&dest->type()); - _note.copy(&dest->note()); - _value.copy(&dest->value()); + _channel.set_edit(edit); + _type.set_edit(edit); + _note.set_edit(edit); + _value.set_edit(edit); } diff --git a/src/mmg-message.h b/src/mmg-message.h index dd97c53..35b63be 100644 --- a/src/mmg-message.h +++ b/src/mmg-message.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -16,7 +16,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#pragma once +#ifndef MMG_MESSAGE_H +#define MMG_MESSAGE_H + #include "mmg-utils.h" class MMGMessage { @@ -29,18 +31,19 @@ class MMGMessage { void json(QJsonObject &message_obj) const; void blog(int log_status, const QString &message) const; - MMGUtils::MMGString &type() { return _type; }; + MMGUtils::MMGString *type() { return &_type; }; const MMGUtils::MMGString &type() const { return _type; }; - MMGUtils::MMGNumber &channel() { return _channel; }; + MMGUtils::MMGNumber *channel() { return &_channel; }; const MMGUtils::MMGNumber &channel() const { return _channel; }; - MMGUtils::MMGNumber ¬e() { return _note; }; + MMGUtils::MMGNumber *note() { return &_note; }; const MMGUtils::MMGNumber ¬e() const { return _note; }; - MMGUtils::MMGNumber &value() { return _value; }; + MMGUtils::MMGNumber *value() { return &_value; }; const MMGUtils::MMGNumber &value() const { return _value; }; + bool acceptable(const MMGMessage *test) const; + void copy(MMGMessage *dest) const; + void setEditable(bool edit); void toggle(); - bool is_acceptable(const MMGMessage *test) const; - void deep_copy(MMGMessage *dest); static QString get_midi_type(const libremidi::message &mess); static int get_midi_note(const libremidi::message &mess); @@ -54,3 +57,6 @@ class MMGMessage { }; using MMGSharedMessage = QSharedPointer; +Q_DECLARE_METATYPE(MMGSharedMessage); + +#endif // MMG_MESSAGE_H diff --git a/src/mmg-midiin.cpp b/src/mmg-midiin.cpp new file mode 100644 index 0000000..65fb380 --- /dev/null +++ b/src/mmg-midiin.cpp @@ -0,0 +1,95 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#include "mmg-midiin.h" +#include "mmg-config.h" + +using namespace MMGUtils; + +void MMGMIDIIn::blog(int log_status, const QString &message) +{ + global_blog(log_status, "MIDI In -> " + message); +} + +void MMGMIDIIn::openInputPort(MMGDevice *device) +{ + if (isInputPortOpen()) closeInputPort(); + + blog(LOG_INFO, "Opening input port for device <" + device->name() + ">..."); + + if (inputPort(device) == (uint)-1) { + blog(LOG_INFO, "Port opening failed: Device is disconnected or does not exist."); + return; + } + + try { + midi_in.set_callback([&](const libremidi::message &message) { callback(message); }); + midi_in.open_port(inputPort(device)); + blog(LOG_INFO, "Input port successfully opened."); + } catch (const libremidi::driver_error &err) { + blog(LOG_INFO, err.what()); + } catch (const libremidi::invalid_parameter_error &err) { + blog(LOG_INFO, err.what()); + } catch (const libremidi::system_error &err) { + blog(LOG_INFO, err.what()); + } +} + +void MMGMIDIIn::closeInputPort() +{ + if (!isInputPortOpen()) return; + midi_in.cancel_callback(); + midi_in.close_port(); + blog(LOG_INFO, "Input port closed."); +} + +bool MMGMIDIIn::isInputPortOpen() +{ + return midi_in.is_port_open(); +} + +const QStringList MMGMIDIIn::inputDeviceNames() +{ + QStringList inputs; + for (uint i = 0; i < midi_in.get_port_count(); ++i) { + inputs.append(QString::fromStdString(midi_in.get_port_name(i))); + } + return inputs; +} + +uint MMGMIDIIn::inputPort(MMGDevice *device) +{ + return inputDeviceNames().indexOf(device->name()); +} + +void MMGMIDIIn::callback(const libremidi::message &msg) +{ + message.reset(new MMGMessage(msg)); + + if (listening) { + emit messageListened(message); + } else { + emit messageReceived(message); + + if (!global()->preferences()->thruDevice().isEmpty()) { + MMGDevice *device = global()->find(global()->preferences()->thruDevice()); + if (!device) return; + emit sendThru(device); + } + } +} diff --git a/src/mmg-midiin.h b/src/mmg-midiin.h new file mode 100644 index 0000000..563d9b3 --- /dev/null +++ b/src/mmg-midiin.h @@ -0,0 +1,57 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#ifndef MMG_MIDIIN_H +#define MMG_MIDIIN_H + +#include "obs-midi-mg.h" +#include "mmg-message.h" + +class MMGDevice; + +class MMGMIDIIn : public QObject { + Q_OBJECT + + public: + void blog(int log_status, const QString &message); + + void openInputPort(MMGDevice *device); + void closeInputPort(); + bool isInputPortOpen(); + void setListening(bool listen) { listening = listen; }; + + const QStringList inputDeviceNames(); + uint inputPort(MMGDevice *device); + + signals: + void messageListened(const MMGSharedMessage &); // For listen buttons + void messageReceived(const MMGSharedMessage &); // For action execution + void sendThru(MMGDevice *); // For MIDI throughput + + private: + libremidi::midi_in midi_in; + + MMGSharedMessage message; + bool listening = false; + + void callback(const libremidi::message &); + + friend class MMGMIDIOut; +}; + +#endif // MMG_MIDIIN_H diff --git a/src/mmg-midiout.cpp b/src/mmg-midiout.cpp new file mode 100644 index 0000000..19c6324 --- /dev/null +++ b/src/mmg-midiout.cpp @@ -0,0 +1,120 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#include "mmg-midiout.h" +#include "mmg-midiin.h" + +#include "mmg-device.h" + +using namespace MMGUtils; + +MMGMIDIOut::MMGMIDIOut() +{ + thru_timer = new MMGTimer(this); + + connect(input_device().get(), &MMGMIDIIn::sendThru, this, &MMGMIDIOut::sendThru); + connect(thru_timer, &MMGTimer::stopping, this, &MMGMIDIOut::closeOutputPort); +} + +void MMGMIDIOut::blog(int log_status, const QString &message) const +{ + global_blog(log_status, "MIDI Out -> " + message); +} + +void MMGMIDIOut::openOutputPort(MMGDevice *device) +{ + if (isOutputPortOpen()) closeOutputPort(); + + blog(LOG_INFO, "Opening output port for device <" + device->name() + ">..."); + + if (outputPort(device) == (uint)-1) { + blog(LOG_INFO, "Port opening failed: Device is disconnected or does not exist."); + return; + } + + try { + midi_out.open_port(outputPort(device)); + blog(LOG_INFO, "Output port successfully opened."); + } catch (const libremidi::driver_error &err) { + blog(LOG_INFO, err.what()); + } catch (const libremidi::invalid_parameter_error &err) { + blog(LOG_INFO, err.what()); + } catch (const libremidi::system_error &err) { + blog(LOG_INFO, err.what()); + } +} + +void MMGMIDIOut::closeOutputPort() +{ + if (!isOutputPortOpen()) return; + midi_out.close_port(); + blog(LOG_INFO, "Output port closed."); +} + +bool MMGMIDIOut::isOutputPortOpen() +{ + return midi_out.is_port_open(); +} + +void MMGMIDIOut::sendMessage(const MMGMessage *midi) +{ + libremidi::message message; + int channel = midi->channel(); + int note = midi->note(); + int value = midi->value(); + + if (midi->type() == "Note On") { + message = libremidi::message::note_on(channel, note, value); + } else if (midi->type() == "Note Off") { + message = libremidi::message::note_off(channel, note, value); + } else if (midi->type() == "Control Change") { + message = libremidi::message::control_change(channel, note, value); + } else if (midi->type() == "Program Change") { + message = libremidi::message::program_change(channel, value); + } else if (midi->type() == "Pitch Bend") { + message = libremidi::message::pitch_bend(channel, (value <= 64 ? 0 : value - 64) * 2, value); + } + + try { + if (isOutputPortOpen()) midi_out.send_message(message); + } catch (const libremidi::driver_error &err) { + global_blog(LOG_INFO, err.what()); + } +} + +void MMGMIDIOut::sendThru(MMGDevice *device) +{ + if (!output_device()->isOutputPortOpen()) output_device()->openOutputPort(device); + + thru_timer->reset(1000); + sendMessage(input_device()->message.get()); +} + +const QStringList MMGMIDIOut::outputDeviceNames() +{ + QStringList outputs; + for (uint i = 0; i < midi_out.get_port_count(); ++i) { + outputs.append(QString::fromStdString(midi_out.get_port_name(i))); + } + return outputs; +} + +uint MMGMIDIOut::outputPort(MMGDevice *device) +{ + return outputDeviceNames().indexOf(device->name()); +} diff --git a/src/mmg-midiout.h b/src/mmg-midiout.h new file mode 100644 index 0000000..5071025 --- /dev/null +++ b/src/mmg-midiout.h @@ -0,0 +1,54 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#ifndef MMG_MIDIOUT_H +#define MMG_MIDIOUT_H + +#include "obs-midi-mg.h" +#include "mmg-message.h" + +class MMGDevice; + +class MMGMIDIOut : public QObject { + Q_OBJECT + + public: + MMGMIDIOut(); + void blog(int log_status, const QString &message) const; + + bool isOutputPortOpen(); + + const QStringList outputDeviceNames(); + uint outputPort(MMGDevice *device); + + public slots: + void openOutputPort(MMGDevice *device); + void closeOutputPort(); + void sendMessage(const MMGMessage *midi); + + private slots: + void sendThru(MMGDevice *); + + private: + libremidi::midi_out midi_out; + MMGUtils::MMGTimer *thru_timer; + + friend class MMGMIDIIn; +}; + +#endif // MMG_MIDIOUT_H diff --git a/src/mmg-utils.cpp b/src/mmg-utils.cpp index dde6ef7..7e9f0d1 100644 --- a/src/mmg-utils.cpp +++ b/src/mmg-utils.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,11 +18,18 @@ with this program. If not, see #include "mmg-utils.h" #include "mmg-config.h" +#include "ui/mmg-number-display.h" #include "actions/mmg-action.h" +#include "ui/mmg-fields.h" #include #include +#include + +#include + +static std::mutex custom_update; void global_blog(int log_status, const QString &message) { @@ -36,59 +43,81 @@ MMGNumber::MMGNumber(const QJsonObject &json_obj, const QString &preferred, int { double fallback = json_obj[num_to_str(fallback_num, "num")].toDouble(); - if (json_obj[preferred].isDouble()) { - // v2.2.0+ + if (json_obj[preferred].isObject()) { + // v2.3.0+ (with bounds) + QJsonObject number_obj = json_obj[preferred].toObject(); + set_num(number_obj["number"].toDouble()); + set_min(number_obj["lower"].toDouble()); + set_max(number_obj["higher"].toDouble()); + set_state(number_obj["state"].toInt()); + } else if (json_obj[preferred].isDouble()) { + // v2.2.0+ (no bounds) set_num(json_obj[preferred].toDouble()); - num_state = (State)json_obj[preferred + "_state"].toInt(); + short nums_state = json_obj[preferred + "_state"].toInt(); + set_state(nums_state == 2 ? 1 : nums_state); + set_min(number < lower ? number : lower); + set_max(number > higher ? number : higher); } else if (json_obj["nums_state"].isDouble()) { // v2.1.0 - v2.1.1 set_num(fallback); int nums_state = (json_obj["nums_state"].toInt() & (3 << (fallback_num * 2))) >> (fallback_num * 2); - num_state = (State)(nums_state == 2 ? 3 : nums_state); + set_state(nums_state == 2 ? 3 : nums_state); + set_min(number < lower ? number : lower); + set_max(number > higher ? number : higher); } else { // pre v2.1.0 set_num(fallback == -1 ? 0 : fallback); - num_state = (State)(fallback == -1); + set_state(fallback == -1); + set_min(number < lower ? number : lower); + set_max(number > higher ? number : higher); } if ((uint)num_state > 3) num_state = NUMBERSTATE_FIXED; } -void MMGNumber::json(QJsonObject &json_obj, const QString &prefix, bool use_state) const +void MMGNumber::json(QJsonObject &json_obj, const QString &prefix, bool use_bounds) const { - json_obj[prefix] = number; - if (use_state) json_obj[prefix + "_state"] = (int)num_state; + if (!use_bounds) { + json_obj[prefix] = number; + return; + } + QJsonObject number_json_obj; + number_json_obj["number"] = number; + number_json_obj["lower"] = lower; + number_json_obj["higher"] = higher; + number_json_obj["state"] = num_state; + json_obj[prefix] = number_json_obj; } -double MMGNumber::choose(const MMGMessage *midi, double default_value, double mult, bool off_by_one) +double MMGNumber::choose(const MMGMessage *midi, double default_value) const { switch (num_state) { case MMGNumber::NUMBERSTATE_MIDI: - return qRound(((midi->value() + off_by_one) / 128.0) * mult); - case MMGNumber::NUMBERSTATE_MIDI_INVERT: - return qRound(((128 - midi->value() - off_by_one) / 128.0) * mult); + case MMGNumber::NUMBERSTATE_CUSTOM: + return qRound((midi->value() / 127.0) * (higher - lower) + lower); case MMGNumber::NUMBERSTATE_IGNORE: return default_value; default: return number; } } - -void MMGNumber::copy(MMGNumber *dest) const -{ - dest->set_num(number); - dest->set_state(num_state); -} // End MMGNumber // MMGString MMGString::MMGString(const QJsonObject &json_obj, const QString &preferred, int fallback_num) { - if (json_obj[preferred].isString()) { + if (json_obj[preferred].isObject()) { + // v2.3.0+ + QJsonObject string_obj = json_obj[preferred].toObject(); + set_str(string_obj["string"].toString()); + set_state(string_obj["state"].toInt()); + } else if (json_obj[preferred].isString()) { + // v2.1.0+ set_str(json_obj[preferred].toString()); - str_state = (State)json_obj[preferred + "_state"].toInt(); + set_state(json_obj[preferred + "_state"].toInt()); if ((uint)str_state > 2) str_state = STRINGSTATE_FIXED; } else { + // pre v2.1.0 set_str(json_obj[num_to_str(fallback_num, "str")].toString()); if (string == "Use Message Value") { str_state = STRINGSTATE_MIDI; @@ -100,123 +129,30 @@ MMGString::MMGString(const QJsonObject &json_obj, const QString &preferred, int void MMGString::json(QJsonObject &json_obj, const QString &prefix, bool use_state) const { - json_obj[prefix] = string; - if (use_state) json_obj[prefix + "_state"] = (int)str_state; -} - -void MMGString::copy(MMGString *dest) const -{ - dest->set_str(string); - dest->set_state(str_state); -} -// End MMGString - -// LCDData -LCDData::LCDData(QLCDNumber *lcd_ptr) : lcd(lcd_ptr) {} - -void LCDData::set_storage(MMGNumber *number) -{ - storage = number; - set_value(*number); - set_state(number->state()); -} - -void LCDData::set_range(double min, double max) -{ - minimum = min; - maximum = max; -}; -void LCDData::set_step(double minor, double major) -{ - minor_step = minor; - major_step = major; -}; - -void LCDData::down_major() -{ - set_value(*storage - major_step); -} -void LCDData::down_minor() -{ - set_value(*storage - minor_step); -} -void LCDData::up_minor() -{ - set_value(*storage + minor_step); -} -void LCDData::up_major() -{ - set_value(*storage + major_step); -} - -void LCDData::reset() -{ - if (editable) storage->set_num(default_val); - display(); -} -void LCDData::set_value(double val) -{ - if (!editable) return; - if (val > maximum) { - storage->set_num(maximum); - } else if (val < minimum) { - storage->set_num(minimum); - } else if (minor_step < 1 && qAbs(val - qFloor(val)) > 0 && - qAbs(val - qFloor(val)) < major_step) { - storage->set_num(qRound(val / minor_step) * minor_step); - } else { - storage->set_num(val); - } - display(); -} -void LCDData::set_state(int index) -{ - if (editable) storage->set_state((MMGNumber::State)index); - lcd->setEnabled(index == 0); - display(); -} - -void LCDData::display() -{ - if (lcd->isEnabled()) { - if (use_time) { - lcd->display(QTime(*storage / 3600.0, fmod(*storage / 60.0, 60.0), fmod(*storage, 60.0)) - .toString("hh:mm:ss")); - } else { - lcd->display(*storage); - } + if (use_state) { + QJsonObject string_obj; + string_obj["string"] = string; + string_obj["state"] = (int)str_state; + json_obj[prefix] = string_obj; } else { - lcd->display(lcd->digitCount() == 8 ? " --- " : "---"); + json_obj[prefix] = string; } } -// End LCDData +// End MMGString -bool json_key_exists(const QJsonObject &obj, const QString &key, QJsonValue::Type value_type) +// MMGTimer +MMGTimer::MMGTimer(QObject *parent) : QTimer(parent) { - return obj.contains(key) && json_is_valid(obj[key], value_type); + connect(this, &QTimer::timeout, this, &MMGTimer::stopTimer); + connect(this, &MMGTimer::stopping, this, &QTimer::stop); + connect(this, &MMGTimer::resetting, this, QOverload::of(&QTimer::start)); } -bool json_is_valid(const QJsonValue &value, QJsonValue::Type value_type) +void MMGTimer::reset(int time) { - switch (value_type) { - case QJsonValue::Type::Double: - return value.isDouble(); - case QJsonValue::Type::String: - return value.isString(); - case QJsonValue::Type::Bool: - return value.isBool(); - case QJsonValue::Type::Object: - return value.isObject(); - case QJsonValue::Type::Array: - return value.isArray(); - case QJsonValue::Type::Null: - return value.isNull(); - case QJsonValue::Type::Undefined: - return value.isUndefined(); - default: - return false; - } -} + emit resetting(time); +}; +// End MMGTimer const QByteArray json_to_str(const QJsonObject &json_obj) { @@ -228,12 +164,24 @@ const QJsonObject json_from_str(const QByteArray &str) return QJsonDocument::fromJson(str).object(); } -void call_midi_callback(const libremidi::message &message) -{ - MMGSharedMessage incoming(new MMGMessage(message)); - if (global()->is_listening(incoming.get())) return; - if (global()->find_current_device()->input_port_open()) - global()->find_current_device()->do_all_actions(incoming); +void set_message_labels(const QString &type, MMGNumberDisplay *note_display, + MMGNumberDisplay *value_display) +{ + if (type.contains("Note")) { + note_display->setVisible(true); + note_display->setDescription("Note #"); + value_display->setDescription("Velocity"); + } else if (type == "Control Change") { + note_display->setVisible(true); + note_display->setDescription("Control #"); + value_display->setDescription("Value"); + } else if (type == "Program Change") { + note_display->setVisible(false); + value_display->setDescription("Program #"); + } else if (type == "Pitch Bend") { + note_display->setVisible(false); + value_display->setDescription("Pitch Adjust"); + } } bool bool_from_str(const QString &str) @@ -241,6 +189,11 @@ bool bool_from_str(const QString &str) return str == "True" || str == "Yes" || str == "Show" || str == "Locked"; } +double num_from_str(const QString &str) +{ + return str.toDouble(); +} + QString num_to_str(int num, const QString &prefix) { return QString::number(num).prepend(prefix); @@ -248,20 +201,18 @@ QString num_to_str(int num, const QString &prefix) void open_message_box(const QString &title, const QString &text) { - struct C { - QString title; - QString text; - } c{title, text}; + MMGPair c{title, text}; + obs_queue_task( OBS_TASK_UI, [](void *param) { - auto content = reinterpret_cast(param); - QMessageBox::information(nullptr, content->title, content->text); + auto content = reinterpret_cast(param); + QMessageBox::information(nullptr, content->key, content->val.value()); }, &c, true); } -void transfer_bindings(short mode, const QString &source, const QString &dest) +void transfer_bindings(short mode, const QString &dest, const QString &source) { if (mode < 0 || mode > 2) return; @@ -271,28 +222,28 @@ void transfer_bindings(short mode, const QString &source, const QString &dest) return; } - MMGDevice *const source_device = global()->find_device(source); - if (!source_device) { - open_message_box("Transfer Error", "Failed to transfer bindings: Source device is invalid."); - return; - } - - MMGDevice *const dest_device = global()->find_device(dest); + MMGDevice *dest_device = global()->find(dest); if (!dest_device) { open_message_box("Transfer Error", "Failed to transfer bindings: Destination device is invalid."); return; } + MMGDevice *source_device = global()->find(source); + if (!source_device) { + open_message_box("Transfer Error", "Failed to transfer bindings: Source device is invalid."); + return; + } + if (source_device == dest_device) { open_message_box("Transfer Error", "Failed to transfer bindings: Cannot transfer bindings to the same device."); return; } - // Deep copy of bindings MMGDevice *source_copy = new MMGDevice; - source_device->deep_copy(source_copy); + source_device->copy(source_copy); + source_copy->setName(dest); // REMOVE (occurs for both move and replace) if (mode > 0) source_device->clear(); @@ -301,9 +252,7 @@ void transfer_bindings(short mode, const QString &source, const QString &dest) if (mode == 2) dest_device->clear(); // COPY (occurs for all three modes) - for (MMGBinding *const binding : source_copy->get_bindings()) { - dest_device->add(binding); - } + source_copy->copy(dest_device); // Clearing before deleting is important because it allows the // destination device to take control of the pointers. If this @@ -315,111 +264,168 @@ void transfer_bindings(short mode, const QString &source, const QString &dest) open_message_box("Transfer Success", "Bindings successfully transferred."); } -vec2 get_obs_dimensions() -{ - obs_video_info video_info; - obs_get_video_info(&video_info); - vec2 xy; - vec2_set(&xy, video_info.base_width, video_info.base_height); - return xy; -} - -vec2 get_obs_source_dimensions(const QString &name) -{ - OBSSourceAutoRelease source = obs_get_source_by_name(name.qtocs()); - vec2 xy; - vec2_set(&xy, obs_source_get_width(source), obs_source_get_height(source)); - return xy; -} - -double get_obs_media_length(const QString &name) -{ - OBSSourceAutoRelease source = obs_get_source_by_name(name.qtocs()); - return obs_source_media_get_duration(source) / 1000.0; -} - -obs_source_t *get_obs_transition_by_name(const QString &name) -{ - obs_frontend_source_list transition_list = {0}; - obs_frontend_get_transitions(&transition_list); - for (size_t i = 0; i < transition_list.sources.num; ++i) { - obs_source_t *ptr = transition_list.sources.array[i]; - if (obs_source_get_name(ptr) == name) { - obs_frontend_source_list_free(&transition_list); - return obs_source_get_ref(ptr); - } - } - obs_frontend_source_list_free(&transition_list); - return nullptr; -} - -bool get_obs_transition_fixed_length(const QString &name) -{ - OBSSourceAutoRelease transition = obs_get_transition_by_name(name.qtocs()); - return obs_transition_fixed(transition); -} - -const vec3 get_obs_filter_property_bounds(obs_property_t *prop) +const vec3 get_obs_property_bounds(obs_property_t *prop) { vec3 bounds = {0}; - if (obs_property_get_type(prop) != OBS_PROPERTY_INT && - obs_property_get_type(prop) != OBS_PROPERTY_FLOAT) - return bounds; + if (!prop) return bounds; + if (obs_property_get_type(prop) == OBS_PROPERTY_INT) { vec3_set(&bounds, obs_property_int_min(prop), obs_property_int_max(prop), obs_property_int_step(prop)); - } else { + } else if (obs_property_get_type(prop) == OBS_PROPERTY_FLOAT) { vec3_set(&bounds, obs_property_float_min(prop), obs_property_float_max(prop), obs_property_float_step(prop)); } + return bounds; } -const vec3 get_obs_filter_property_bounds(obs_source_t *filter, const QString &field) +const vec3 get_obs_property_bounds(obs_source_t *source, const QString &field) { - if (obs_source_get_type(filter) != OBS_SOURCE_TYPE_FILTER) return vec3(); + if (!source) return vec3(); - obs_properties_t *props = obs_source_properties(filter); + obs_properties_t *props = obs_source_properties(source); + if (!props) { + obs_properties_destroy(props); + return vec3(); + } obs_property_t *prop = obs_properties_get(props, field.qtocs()); - const vec3 bounds = get_obs_filter_property_bounds(prop); + const vec3 bounds = get_obs_property_bounds(prop); obs_properties_destroy(props); return bounds; } -const QHash get_obs_filter_property_options(obs_property_t *prop) +const QList get_obs_property_options(obs_property_t *prop) { - QHash options; - if (obs_property_get_type(prop) != OBS_PROPERTY_LIST) - for (size_t i = 0; i < obs_property_list_item_count(prop); ++i) { - switch (obs_property_list_format(prop)) { - case OBS_COMBO_FORMAT_FLOAT: - options[obs_property_list_item_name(prop, i)] = - QString::number(obs_property_list_item_float(prop, i)); - break; - case OBS_COMBO_FORMAT_INT: - options[obs_property_list_item_name(prop, i)] = - QString::number(obs_property_list_item_int(prop, i)); - break; - case OBS_COMBO_FORMAT_STRING: - options[obs_property_list_item_name(prop, i)] = obs_property_list_item_string(prop, i); - break; - default: - break; - } + QList options; + + for (size_t i = 0; i < obs_property_list_item_count(prop); ++i) { + MMGPair strs; + + strs.key = obs_property_list_item_name(prop, i); + + switch (obs_property_list_format(prop)) { + case OBS_COMBO_FORMAT_FLOAT: + strs.val = obs_property_list_item_float(prop, i); + break; + case OBS_COMBO_FORMAT_INT: + strs.val = obs_property_list_item_int(prop, i); + break; + case OBS_COMBO_FORMAT_STRING: + strs.val = obs_property_list_item_string(prop, i); + break; + default: + break; } + + options.append(strs); + } + return options; } -const QHash get_obs_filter_property_options(obs_source_t *filter, - const QString &field) +const QList get_obs_property_options(obs_source_t *source, const QString &field) { - if (obs_source_get_type(filter) != OBS_SOURCE_TYPE_FILTER) return QHash(); + if (!source) return QList(); - obs_properties_t *props = obs_source_properties(filter); + obs_properties_t *props = obs_source_properties(source); + if (!props) { + obs_properties_destroy(props); + return QList(); + } obs_property_t *prop = obs_properties_get(props, field.qtocs()); - const QHash options = get_obs_filter_property_options(prop); + const QList options = get_obs_property_options(prop); obs_properties_destroy(props); return options; } +void debug_json(const QJsonValue &val) +{ + qDebug("%s", val.toVariant().toString().qtocs()); +} + +uint argb_abgr(uint rgb) +{ + return (rgb & 0xFF000000) | ((rgb & 0xFF0000) >> 16) | (rgb & 0xFF00) | ((rgb & 0xFF) << 16); +} + +void obs_source_custom_update(obs_source_t *source, const QJsonObject &action_json, + const MMGMessage *midi) +{ + if (!source) return; + + std::lock_guard guard(custom_update); + + OBSDataAutoRelease source_data = obs_source_get_settings(source); + QJsonObject source_json = json_from_str(obs_data_get_json(source_data)); + QJsonObject final_json; + + for (const QString &key : action_json.keys()) { + QJsonObject key_obj = action_json[key].toObject(); + switch (key_obj["state"].toInt()) { + case 1: + // MIDI + if (key_obj.contains("number")) { + // Number Field + final_json[key] = + (midi->value() / 127.0) * (key_obj["higher"].toDouble() - key_obj["lower"].toDouble()) + + key_obj["lower"].toDouble(); + } else if (key_obj.contains("value")) { + // String Fields + QList options = get_obs_property_options(source, key); + if (midi->value() >= options.size()) break; + final_json[key] = QJsonValue::fromVariant(options[(int)midi->value()].val); + } else if (key_obj.contains("string")) { + // Text and Path Fields + QString str = action_json[key].toString(); + str.replace("${type}", midi->type()); + str.replace("${channel}", num_to_str(midi->channel())); + str.replace("${note}", num_to_str(midi->note())); + str.replace("${value}", num_to_str(midi->value())); + str.replace("${control}", num_to_str(midi->note())); + final_json[key] = str; + } + break; + + case 2: + // TOGGLE (and CUSTOM) + if (key_obj.contains("number")) { + // Number Field + final_json[key] = + (midi->value() / 127.0) * (key_obj["higher"].toDouble() - key_obj["lower"].toDouble()) + + key_obj["lower"].toDouble(); + } else { + // Boolean Field + final_json[key] = !source_json[key].toBool(); + } + break; + + case 3: + // IGNORE + break; + + default: + // NORMAL + if (key_obj.contains("number")) { + // Number Field + final_json[key] = key_obj["number"]; + } else if (key_obj.contains("value")) { + // String Fields + final_json[key] = key_obj["value"]; + } else if (key_obj.contains("color")) { + // Color Field + final_json[key] = key_obj["color"]; + } else if (key_obj.contains("string")) { + // Text and Path Fields + final_json[key] = key_obj["string"]; + } else { + // Font Field + final_json[key] = key_obj; + } + break; + } + } + obs_source_update(source, OBSDataAutoRelease(obs_data_create_from_json(json_to_str(final_json)))); +} + } diff --git a/src/mmg-utils.h b/src/mmg-utils.h index 115d06b..5578ec3 100644 --- a/src/mmg-utils.h +++ b/src/mmg-utils.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,45 +20,69 @@ with this program. If not, see #include "obs-midi-mg.h" #include -#include #include #include #include #include +#include +#include #include #include #include +#include + +#define MMG_ENABLED if (editable) void global_blog(int log_status, const QString &message); class MMGMessage; -class MMGAction; +class MMGNumberDisplay; namespace MMGUtils { +struct MMGPair { + QString key; + QVariant val; + + bool operator==(const MMGPair &other) const { return key == other.key && val == other.val; }; +}; + struct MMGNumber { MMGNumber(){}; MMGNumber(const QJsonObject &json_obj, const QString &preferred, int fallback_num); - enum State { NUMBERSTATE_FIXED, NUMBERSTATE_MIDI, NUMBERSTATE_MIDI_INVERT, NUMBERSTATE_IGNORE }; + enum State { NUMBERSTATE_FIXED, NUMBERSTATE_MIDI, NUMBERSTATE_CUSTOM, NUMBERSTATE_IGNORE }; double num() const { return number; }; + double min() const { return lower; }; + double max() const { return higher; }; State state() const { return num_state; }; - void set_num(double val) { number = val; }; - void set_state(State state) { num_state = state; } + void set_num(double val) { MMG_ENABLED number = val; }; + void set_min(double val) { MMG_ENABLED lower = val; }; + void set_max(double val) { MMG_ENABLED higher = val; }; + void set_state(State state) { MMG_ENABLED num_state = state; } + void set_state(short state) { MMG_ENABLED num_state = (State)state; } - void json(QJsonObject &json_obj, const QString &prefix, bool use_state) const; - void copy(MMGNumber *dest) const; + void json(QJsonObject &json_obj, const QString &prefix, bool use_bounds = true) const; + MMGNumber copy() const { return *this; }; + void set_edit(bool edit) { editable = edit; }; - double choose(const MMGMessage *midi, double default_val = 0.0, double mult = 128.0, - bool add_one = false); + double choose(const MMGMessage *midi, double default_val = 0.0) const; operator double() const { return number; }; - double operator=(double val) { return number = val; }; + double operator=(double val) + { + set_num(val); + return number; + }; private: double number = 0.0; - State num_state{NUMBERSTATE_FIXED}; + double lower = 0.0; + double higher = 100.0; + bool editable = true; + + State num_state = NUMBERSTATE_FIXED; }; struct MMGString { MMGString(){}; @@ -68,149 +92,68 @@ struct MMGString { const QString &str() const { return string; }; State state() const { return str_state; }; - void set_str(const QString &val) { string = val; }; - void set_state(State state) { str_state = state; } + void set_str(const QString &val) { MMG_ENABLED string = val; }; + void set_state(State state) { MMG_ENABLED str_state = state; } + void set_state(short state) { MMG_ENABLED str_state = (State)state; } - void json(QJsonObject &json_obj, const QString &prefix, bool use_state) const; - void copy(MMGString *dest) const; + void json(QJsonObject &json_obj, const QString &prefix, bool use_state = true) const; + MMGString copy() const { return *this; }; + void set_edit(bool edit) { editable = edit; }; operator const QString &() const { return string; }; - QString &operator=(const QString &val) { return string = val; }; + QString &operator=(const QString &val) + { + set_str(val); + return string; + }; bool operator==(const char *ch) const { return string == ch; }; bool operator!=(const char *ch) const { return string != ch; }; private: QString string; - State str_state{STRINGSTATE_FIXED}; -}; - -class LCDData { - public: - LCDData(){}; - explicit LCDData(QLCDNumber *lcd_ptr); - - void set_storage(MMGNumber *number); - void can_set(bool can_set) { editable = can_set; }; - - double get_minor_step() const { return minor_step; } - double get_major_step() const { return major_step; } - void set_range(double min, double max); - void set_step(double minor, double major); - - void set_value(double val); - void set_default_value(double val) { default_val = val; }; - void reset(); - - void set_state(int index); - - void set_use_time(bool time) { use_time = time; } - - void down_major(); - void down_minor(); - void up_minor(); - void up_major(); - - void display(); - - private: - QLCDNumber *lcd = nullptr; - MMGNumber *storage = nullptr; + State str_state = STRINGSTATE_FIXED; bool editable = true; - - double maximum = 100.0; - double minimum = 0.0; - double minor_step = 1.0; - double major_step = 10.0; - - double default_val = 0.0; - - bool use_time = false; }; -struct MMGActionDisplayParams { - enum FieldDisplay : int { - DISPLAY_NONE = 0, - DISPLAY_STR1 = 1, // STR1 - DISPLAY_STR2 = 3, // STR1 + STR2 - DISPLAY_STR3 = 7, // STR1 + STR2 + STR3 - DISPLAY_NUM1 = 8, // NUM1 - DISPLAY_NUM2 = 24, // NUM1 + NUM2 - DISPLAY_NUM3 = 56, // NUM1 + NUM2 + NUM3 - DISPLAY_NUM4 = 120, // NUM1 + NUM2 + NUM3 + NUM4 - DISPLAY_SEC = 128 - }; - using FieldDisplayFlags = int; - - enum LCDComboDisplay : short { - COMBODISPLAY_FIXED = 0, - COMBODISPLAY_MIDI = 1, - COMBODISPLAY_MIDI_INVERT = 2, - COMBODISPLAY_IGNORE = 4, - COMBODISPLAY_ALL = 7 - }; - using LCDComboDisplayFlags = short; - - void clear() - { - display = DISPLAY_NONE; - - label_text = ""; - list.clear(); - - combo_display[0] = COMBODISPLAY_MIDI; - combo_display[1] = COMBODISPLAY_MIDI; - combo_display[2] = COMBODISPLAY_MIDI; - combo_display[3] = COMBODISPLAY_MIDI; - label_lcds[0] = ""; - label_lcds[1] = ""; - label_lcds[2] = ""; - label_lcds[3] = ""; - }; - - // Window will fill in the LCD - // MMGAction will respond with these values full - - bool initializing = false; - FieldDisplayFlags display = DISPLAY_NONE; +class MMGTimer : public QTimer { + Q_OBJECT - QString label_text; - QStringList list; + public: + MMGTimer(QObject *parent = nullptr); - LCDComboDisplayFlags combo_display[4]{COMBODISPLAY_MIDI, COMBODISPLAY_MIDI, COMBODISPLAY_MIDI, - COMBODISPLAY_MIDI}; - QString label_lcds[4]; - LCDData *lcds[4]; + signals: + void resetting(int); + void stopping(); - QString extra_data; + public slots: + void stopTimer() { emit stopping(); }; + void reset(int time); }; -void call_midi_callback(const libremidi::message &message); +void set_message_labels(const QString &type, MMGNumberDisplay *note_display, + MMGNumberDisplay *value_display); -bool json_key_exists(const QJsonObject &obj, const QString &key, QJsonValue::Type value_type); -bool json_is_valid(const QJsonValue &value, QJsonValue::Type value_type); const QByteArray json_to_str(const QJsonObject &json_obj); const QJsonObject json_from_str(const QByteArray &str); +void debug_json(const QJsonValue &val); bool bool_from_str(const QString &str); -QString num_to_str(int num, const QString &prefix); +double num_from_str(const QString &str); +QString num_to_str(int num, const QString &prefix = ""); void open_message_box(const QString &title, const QString &text); void transfer_bindings(short mode, const QString &source, const QString &dest); -vec2 get_obs_dimensions(); -vec2 get_obs_source_dimensions(const QString &name); - -obs_source_t *get_obs_transition_by_name(const QString &name); -bool get_obs_transition_fixed_length(const QString &name); +const vec3 get_obs_property_bounds(obs_property_t *prop); +const vec3 get_obs_property_bounds(obs_source_t *source, const QString &field); +const QList get_obs_property_options(obs_property_t *prop); +const QList get_obs_property_options(obs_source_t *source, const QString &field); +uint argb_abgr(uint rgb); -double get_obs_media_length(const QString &name); - -const vec3 get_obs_filter_property_bounds(obs_property_t *prop); -const vec3 get_obs_filter_property_bounds(obs_source_t *filter, const QString &field); -const QHash get_obs_filter_property_options(obs_property_t *prop); -const QHash get_obs_filter_property_options(obs_source_t *filter, - const QString &field); +void obs_source_custom_update(obs_source_t *source, const QJsonObject &action_json, + const MMGMessage *midi_value); } -Q_DECLARE_METATYPE(MMGUtils::LCDData); + +#undef MMG_ENABLED diff --git a/src/obs-midi-mg.cpp b/src/obs-midi-mg.cpp index 87b8bab..8300bf8 100644 --- a/src/obs-midi-mg.cpp +++ b/src/obs-midi-mg.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,6 +20,8 @@ with this program. If not, see #include "ui/mmg-echo-window.h" #include "mmg-config.h" +#include "mmg-midiin.h" +#include "mmg-midiout.h" #include #include @@ -35,8 +37,8 @@ OBS_MODULE_USE_DEFAULT_LOCALE("obs-midi-mg", "en-US") static MMGEchoWindow *echo_window; Configuration global_config; -MMGMIDIInputDevice input; -MMGMIDIOutputDevice output; +MMGMIDIInDevice input; +MMGMIDIOutDevice output; void _blog(int log_status, const QString &message) { @@ -54,15 +56,15 @@ bool obs_module_load(void) if (!QDir(config_path).exists()) QDir().mkdir(config_path); bfree(config_path); + // Load the libremidi device objects + input.reset(new MMGMIDIIn); + output.reset(new MMGMIDIOut); + // Load the configuration global_config.reset(new MMGConfig()); - // Load the libremidi device objects - input.reset(new libremidi::midi_in()); - output.reset(new libremidi::midi_out()); - // Load any new devices and open the input port - global_config->load_new_devices(); + global_config->refresh(); // Load the UI Window and the menu button (Tools -> obs-midi-mg Setup) const char *menu_action_text = obs_module_text("obs-midi-mg Setup"); @@ -90,12 +92,12 @@ Configuration global() return global_config; } -MMGMIDIInputDevice input_device() +MMGMIDIInDevice input_device() { return input; } -MMGMIDIOutputDevice output_device() +MMGMIDIOutDevice output_device() { return output; } diff --git a/src/obs-midi-mg.h b/src/obs-midi-mg.h index f36a807..ebaa069 100644 --- a/src/obs-midi-mg.h +++ b/src/obs-midi-mg.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,16 +27,19 @@ with this program. If not, see #include "plugin-macros.generated.h" +class MMGMIDIIn; +class MMGMIDIOut; class MMGConfig; class MMGEchoWindow; using Configuration = QSharedPointer; Configuration global(); -using MMGMIDIInputDevice = QSharedPointer; -using MMGMIDIOutputDevice = QSharedPointer; -MMGMIDIInputDevice input_device(); -MMGMIDIOutputDevice output_device(); +using MMGMIDIInDevice = QSharedPointer; +using MMGMIDIOutDevice = QSharedPointer; + +MMGMIDIInDevice input_device(); +MMGMIDIOutDevice output_device(); #define OBS_MIDIMG_VERSION "v" PLUGIN_VERSION #define qtocs() toUtf8().constData() diff --git a/src/ui/mmg-action-display.cpp b/src/ui/mmg-action-display.cpp new file mode 100644 index 0000000..c2a62cb --- /dev/null +++ b/src/ui/mmg-action-display.cpp @@ -0,0 +1,154 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#include "mmg-action-display.h" +#include "mmg-fields.h" + +using namespace MMGUtils; + +MMGActionDisplay::MMGActionDisplay(QWidget *parent) : QWidget(parent) +{ + label_str1 = new QLabel(this); + label_str2 = new QLabel(this); + label_str3 = new QLabel(this); + editor_str1 = new QComboBox(this); + editor_str2 = new QComboBox(this); + editor_str3 = new QComboBox(this); + scroll_area = new QScrollArea(this); + + label_str1->setGeometry(10, 10, 220, 20); + label_str2->setGeometry(10, 80, 220, 20); + label_str3->setGeometry(10, 150, 220, 20); + editor_str1->setGeometry(10, 30, 220, 40); + editor_str2->setGeometry(10, 100, 220, 40); + editor_str3->setGeometry(10, 170, 220, 40); + scroll_area->setGeometry(240, 0, 290, 350); + + scroll_area->setWidgetResizable(true); + scroll_area->setFrameShape(QFrame::NoFrame); + scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + default_scroll_widget = new MMGNumberDisplayFields(scroll_area); + resetScrollWidget(); + + connect(editor_str1, &QComboBox::currentTextChanged, this, &MMGActionDisplay::setStr1); + connect(editor_str2, &QComboBox::currentTextChanged, this, &MMGActionDisplay::setStr2); + connect(editor_str3, &QComboBox::currentTextChanged, this, &MMGActionDisplay::setStr3); + + //setVisible(true); + setStr1Visible(false); + setStr2Visible(false); + setStr3Visible(false); +} + +void MMGActionDisplay::setStr1(const QString &str) +{ + if (str1) { + *str1 = str; + if (str == "Toggle") + str1->set_state(MMGString::STRINGSTATE_TOGGLE); + else if (str.contains("Use Message")) + str1->set_state(MMGString::STRINGSTATE_MIDI); + else + str1->set_state(MMGString::STRINGSTATE_FIXED); + } + emit str1Changed(); +} + +void MMGActionDisplay::setStr2(const QString &str) +{ + if (str2) { + *str2 = str; + if (str == "Toggle") + str2->set_state(MMGString::STRINGSTATE_TOGGLE); + else if (str.contains("Use Message")) + str2->set_state(MMGString::STRINGSTATE_MIDI); + else + str2->set_state(MMGString::STRINGSTATE_FIXED); + } + emit str2Changed(); +} + +void MMGActionDisplay::setStr3(const QString &str) +{ + if (str3) { + *str3 = str; + if (str == "Toggle") + str3->set_state(MMGString::STRINGSTATE_TOGGLE); + else if (str.contains("Use Message")) + str3->set_state(MMGString::STRINGSTATE_MIDI); + else + str3->set_state(MMGString::STRINGSTATE_FIXED); + } + emit str3Changed(); +} + +void MMGActionDisplay::setStr1Visible(bool visible) +{ + label_str1->setVisible(visible); + editor_str1->setVisible(visible); +} + +void MMGActionDisplay::setStr2Visible(bool visible) +{ + label_str2->setVisible(visible); + editor_str2->setVisible(visible); +} + +void MMGActionDisplay::setStr3Visible(bool visible) +{ + label_str3->setVisible(visible); + editor_str3->setVisible(visible); +} + +void MMGActionDisplay::setStr1Options(const QStringList &options) +{ + editor_str1->blockSignals(true); + editor_str1->clear(); + editor_str1->addItems(options); + if (options.contains(*str1)) editor_str1->setCurrentText(*str1); + setStr1(editor_str1->currentText()); + editor_str1->blockSignals(false); +} + +void MMGActionDisplay::setStr2Options(const QStringList &options) +{ + editor_str2->blockSignals(true); + editor_str2->clear(); + editor_str2->addItems(options); + if (options.contains(*str2)) editor_str2->setCurrentText(*str2); + setStr2(editor_str2->currentText()); + editor_str2->blockSignals(false); +} + +void MMGActionDisplay::setStr3Options(const QStringList &options) +{ + editor_str3->blockSignals(true); + editor_str3->clear(); + editor_str3->addItems(options); + if (options.contains(*str3)) editor_str3->setCurrentText(*str3); + setStr3(editor_str3->currentText()); + editor_str3->blockSignals(false); +} + +void MMGActionDisplay::setScrollWidget(QWidget *widget) +{ + scroll_area->takeWidget(); + current_scroll_widget = widget; + scroll_area->setWidget(widget); +} diff --git a/src/ui/mmg-action-display.h b/src/ui/mmg-action-display.h new file mode 100644 index 0000000..8698ca5 --- /dev/null +++ b/src/ui/mmg-action-display.h @@ -0,0 +1,82 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#pragma once +#include "../mmg-utils.h" +#include "mmg-number-display.h" + +class MMGBinding; +class MMGOBSFields; + +class MMGActionDisplay : public QWidget { + Q_OBJECT + + public: + MMGActionDisplay(QWidget *parent); + + void setStr1Visible(bool visible); + void setStr2Visible(bool visible); + void setStr3Visible(bool visible); + void setStr1Description(const QString &desc) { label_str1->setText(desc); }; + void setStr2Description(const QString &desc) { label_str2->setText(desc); }; + void setStr3Description(const QString &desc) { label_str3->setText(desc); }; + void setStr1Options(const QStringList &options); + void setStr2Options(const QStringList &options); + void setStr3Options(const QStringList &options); + void setStr1Storage(MMGUtils::MMGString *storage) { str1 = storage; }; + void setStr2Storage(MMGUtils::MMGString *storage) { str2 = storage; }; + void setStr3Storage(MMGUtils::MMGString *storage) { str3 = storage; }; + + QWidget *scrollWidget() const { return current_scroll_widget; }; + MMGNumberDisplayFields *numberDisplays() const { return default_scroll_widget; }; + void setScrollWidget(QWidget *widget); + void resetScrollWidget() { setScrollWidget(default_scroll_widget); }; + + const MMGBinding *parentBinding() const { return parent_binding; }; + void setParentBinding(MMGBinding *binding) { parent_binding = binding; }; + + signals: + void str1Changed(); + void str2Changed(); + void str3Changed(); + + void customFieldRequest(void *, MMGUtils::MMGString *); + + private slots: + void setStr1(const QString &); + void setStr2(const QString &); + void setStr3(const QString &); + + protected: + MMGUtils::MMGString *str1 = nullptr; + MMGUtils::MMGString *str2 = nullptr; + MMGUtils::MMGString *str3 = nullptr; + + QLabel *label_str1; + QLabel *label_str2; + QLabel *label_str3; + QComboBox *editor_str1; + QComboBox *editor_str2; + QComboBox *editor_str3; + + QScrollArea *scroll_area; + MMGNumberDisplayFields *default_scroll_widget; + QWidget *current_scroll_widget; + + MMGBinding *parent_binding; +}; diff --git a/src/ui/mmg-echo-window.cpp b/src/ui/mmg-echo-window.cpp index 4c899b1..9d51531 100644 --- a/src/ui/mmg-echo-window.cpp +++ b/src/ui/mmg-echo-window.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -27,84 +27,56 @@ with this program. If not, see #include "mmg-echo-window.h" #include "../mmg-config.h" +#include "../mmg-midiin.h" +#include "../mmg-midiout.h" +#include "../actions/mmg-action-midi.h" using namespace MMGUtils; -#define SET_LCD_STATUS(lcd, kind, status) \ - ui->down_major_##lcd->set##kind(status); \ - ui->down_minor_##lcd->set##kind(status); \ - ui->up_minor_##lcd->set##kind(status); \ - ui->up_major_##lcd->set##kind(status); \ - ui->label_##lcd->set##kind(status); \ - ui->lcd_##lcd->set##kind(status) - -#define CONNECT_LCD(kind) \ - connect(ui->down_major_##kind, &QAbstractButton::clicked, this, \ - [&]() { lcd_##kind.down_major(); }); \ - connect(ui->down_minor_##kind, &QAbstractButton::clicked, this, \ - [&]() { lcd_##kind.down_minor(); }); \ - connect(ui->up_minor_##kind, &QAbstractButton::clicked, this, [&]() { lcd_##kind.up_minor(); }); \ - connect(ui->up_major_##kind, &QAbstractButton::clicked, this, [&]() { lcd_##kind.up_major(); }); \ - connect(ui->editor_##kind, QOverload::of(&QComboBox::currentIndexChanged), this, \ - [&](int index) { \ - SET_LCD_STATUS(kind, Enabled, index == 0); \ - lcd_##kind.set_state(index); \ - }) - -#define INIT_LCD(kind) lcd_##kind = LCDData(ui->lcd_##kind) - #define COMBOBOX_ITEM_STATE(list, index, state) \ qobject_cast(ui->list->model())->item(index)->setEnabled(state); MMGEchoWindow::MMGEchoWindow(QWidget *parent) : QDialog(parent, Qt::Dialog), ui(new Ui::MMGEchoWindow) { - this->setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint); + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); ui->setupUi(this); ui->label_version->setText(OBS_MIDIMG_VERSION); - ui->editor_channel->setVisible(false); - - INIT_LCD(channel); - INIT_LCD(note); - INIT_LCD(value); - INIT_LCD(double1); - INIT_LCD(double2); - INIT_LCD(double3); - INIT_LCD(double4); - - // Channel settings - lcd_channel.set_range(1.0, 16.0); - lcd_channel.set_step(1.0, 5.0); - // Note / Value settings - lcd_note.set_range(0.0, 127.0); - lcd_value.set_range(0.0, 127.0); - // Doubles settings - lcd_double1.set_step(0.1, 1.0); - lcd_double2.set_step(0.1, 1.0); - lcd_double3.set_step(0.1, 1.0); - lcd_double4.set_step(0.1, 1.0); - - action_display_params.lcds[0] = &lcd_double1; - action_display_params.lcds[1] = &lcd_double2; - action_display_params.lcds[2] = &lcd_double3; - action_display_params.lcds[3] = &lcd_double4; + channel_display = new MMGNumberDisplay(ui->message_frame); + channel_display->setDisplayMode(MMGNumberDisplay::MODE_THIN); + channel_display->move(250, 70); + channel_display->setDescription("Channel"); + channel_display->setBounds(1, 16); + + note_display = new MMGNumberDisplay(ui->message_frame); + note_display->setDisplayMode(MMGNumberDisplay::MODE_THIN); + note_display->move(250, 120); + note_display->setDescription("Note"); + note_display->setBounds(0, 127); + + value_display = new MMGNumberDisplay(ui->message_frame); + value_display->setDisplayMode(MMGNumberDisplay::MODE_DEFAULT); + value_display->move(250, 170); + value_display->setDescription("Velocity"); + value_display->setBounds(0, 127); + value_display->setOptions(MMGNumberDisplay::OPTIONS_MIDI_CUSTOM); connect_ui_signals(); } void MMGEchoWindow::show_window() { - global()->load_new_devices(); + global()->refresh(); - QString current_device_name = global()->get_active_device_name(); + QString current_device_name = global()->activeDeviceName(); ui->editor_transfer_source->clear(); ui->editor_transfer_dest->clear(); ui->editor_transfer_mode->setCurrentIndex(0); ui->editor_devices->clear(); on_transfer_mode_change(0); - for (const QString &name : global()->get_device_names()) { + for (const QString &name : global()->allDeviceNames()) { ui->editor_devices->addItem(name); ui->editor_transfer_source->addItem(name); ui->editor_transfer_dest->addItem(name); @@ -122,26 +94,18 @@ void MMGEchoWindow::show_window() void MMGEchoWindow::reject() { - if (global()->is_listening(nullptr)) { - open_message_box( - "Error", - "Cannot close window: The MIDI device is being listened to.\n(A Listen button is on.)"); - return; + if (ui->button_listen_once->isChecked() || ui->button_listen_continuous->isChecked()) { + ui->button_listen_once->setChecked(false); + ui->button_listen_continuous->setChecked(false); } + global()->save(); + QDialog::reject(); } void MMGEchoWindow::connect_ui_signals() { - // LCD Connections - CONNECT_LCD(channel); - CONNECT_LCD(note); - CONNECT_LCD(value); - CONNECT_LCD(double1); - CONNECT_LCD(double2); - CONNECT_LCD(double3); - CONNECT_LCD(double4); // Message Display Connections connect(ui->editor_type, &QComboBox::currentTextChanged, this, &MMGEchoWindow::on_message_type_change); @@ -149,41 +113,41 @@ void MMGEchoWindow::connect_ui_signals() &MMGEchoWindow::on_message_listen_continuous); connect(ui->button_listen_once, &QAbstractButton::toggled, this, &MMGEchoWindow::on_message_listen_once); - connect(ui->editor_value, QOverload::of(&QComboBox::currentIndexChanged), this, - &MMGEchoWindow::on_message_value_button_change); - connect(ui->editor_note, QOverload::of(&QComboBox::currentIndexChanged), this, - &MMGEchoWindow::on_message_value_button_change); + connect(ui->editor_type_toggle, &QCheckBox::toggled, this, + &MMGEchoWindow::on_message_type_toggle); + connect(ui->editor_value_toggle, &QCheckBox::toggled, this, + &MMGEchoWindow::on_message_value_toggle); + // Action Display Connections - connect(ui->editor_cat, QOverload::of(&QComboBox::currentIndexChanged), this, + connect(ui->editor_cat, &QComboBox::currentIndexChanged, this, &MMGEchoWindow::on_action_cat_change); - connect(ui->editor_sub, QOverload::of(&QComboBox::currentIndexChanged), this, + connect(ui->editor_sub, &QComboBox::currentIndexChanged, this, &MMGEchoWindow::on_action_sub_change); - connect(ui->editor_str1, &QComboBox::currentTextChanged, this, - &MMGEchoWindow::change_str1_options); - connect(ui->editor_str2, &QComboBox::currentTextChanged, this, - &MMGEchoWindow::change_str2_options); - connect(ui->editor_str3, &QComboBox::currentTextChanged, this, - &MMGEchoWindow::change_str3_options); + // UI Movement Buttons connect(ui->button_add, &QPushButton::clicked, this, &MMGEchoWindow::on_add_click); connect(ui->button_duplicate, &QPushButton::clicked, this, &MMGEchoWindow::on_copy_click); connect(ui->button_remove, &QPushButton::clicked, this, &MMGEchoWindow::on_remove_click); + // Device Connections connect(ui->editor_devices, &QComboBox::currentTextChanged, this, &MMGEchoWindow::on_device_change); + // Preferences Connections connect(ui->button_preferences, &QAbstractButton::toggled, this, &MMGEchoWindow::on_preferences_click); connect(ui->editor_global_enable, &QCheckBox::toggled, this, &MMGEchoWindow::on_active_change); - connect(ui->editor_interface_style, QOverload::of(&QComboBox::currentIndexChanged), this, - &MMGEchoWindow::on_interface_style_change); + connect(ui->editor_midi_thru_toggle, &QCheckBox::toggled, this, + &MMGEchoWindow::on_midi_thru_change); + connect(ui->editor_midi_thru_device, &QComboBox::currentTextChanged, this, + &MMGEchoWindow::on_midi_thru_device_change); connect(ui->button_export, &QPushButton::clicked, this, &MMGEchoWindow::export_bindings); connect(ui->button_import, &QPushButton::clicked, this, &MMGEchoWindow::import_bindings); connect(ui->button_help_advanced, &QPushButton::clicked, this, &MMGEchoWindow::i_need_help); connect(ui->button_bug_report, &QPushButton::clicked, this, &MMGEchoWindow::report_a_bug); connect(ui->button_update_check, &QPushButton::clicked, this, &MMGEchoWindow::on_update_check); - connect(ui->editor_transfer_mode, QOverload::of(&QComboBox::currentIndexChanged), this, + connect(ui->editor_transfer_mode, &QComboBox::currentIndexChanged, this, &MMGEchoWindow::on_transfer_mode_change); connect(ui->button_binding_transfer, &QPushButton::clicked, this, &MMGEchoWindow::on_transfer_bindings_click); @@ -195,443 +159,213 @@ void MMGEchoWindow::connect_ui_signals() &MMGEchoWindow::on_list_widget_state_change); connect(ui->editor_structure->model(), &QAbstractItemModel::rowsMoved, this, &MMGEchoWindow::on_binding_drag); + + // Listening Buttons + connect(input_device().get(), &MMGMIDIIn::messageListened, this, &MMGEchoWindow::listen_update); } void MMGEchoWindow::set_message_view() { - lcd_channel.set_storage(¤t_message->channel()); - lcd_note.set_storage(¤t_message->note()); - lcd_value.set_storage(¤t_message->value()); + current_message->setEditable(false); - if (current_message->type().state() == MMGString::STRINGSTATE_TOGGLE) { - on_message_type_change("Note On / Note Off"); - } else { - on_message_type_change(current_message->type()); - } + channel_display->setStorage(current_message->channel()); + note_display->setStorage(current_message->note()); + value_display->setStorage(current_message->value(), true); + value_display->setBounds(0, 127); + + ui->editor_type->setCurrentText(*current_message->type()); + on_message_type_change(*current_message->type()); + + ui->editor_type_toggle->setChecked(current_message->type()->state() == + MMGString::STRINGSTATE_TOGGLE); + on_message_type_toggle(current_message->type()->state() == MMGString::STRINGSTATE_TOGGLE); + + ui->editor_value_toggle->setChecked(current_message->value()->state() == + MMGNumber::NUMBERSTATE_IGNORE); + on_message_value_toggle(current_message->value()->state() == MMGNumber::NUMBERSTATE_IGNORE); + + current_message->setEditable(true); } void MMGEchoWindow::set_action_view() { // Preparations (turn off action editing) - action_display_params.initializing = true; - lcd_double1.can_set(false); - lcd_double2.can_set(false); - lcd_double3.can_set(false); - lcd_double4.can_set(false); - - // Set storage locations for LCDData objects to set to - lcd_double1.set_storage(¤t_action->num1()); - lcd_double2.set_storage(¤t_action->num2()); - lcd_double3.set_storage(¤t_action->num3()); - lcd_double4.set_storage(¤t_action->num4()); + current_action->setEditable(false); + ui->editor_cat->blockSignals(true); + ui->editor_sub->blockSignals(true); // Set category (and add sub list) - ui->editor_cat->setCurrentIndex((int)current_action->get_category()); - change_sub_options(); + ui->editor_cat->setCurrentIndex((int)current_action->category()); + on_action_cat_change((int)current_action->category()); // Set subcategory view - ui->editor_sub->setCurrentIndex(current_action->get_sub()); - on_action_sub_change(current_action->get_sub()); - - // Set strings (even if they are invalid) - ui->editor_str1->setCurrentText(current_action->str1()); - change_str1_options(current_action->str1()); - ui->editor_str2->setCurrentText(current_action->str2()); - change_str2_options(current_action->str2()); - ui->editor_str3->setCurrentText(current_action->str3()); - change_str3_options(current_action->str3()); - - // Set LCD ComboBox indices to match their respective states - ui->editor_double1->setCurrentIndex(current_action->num1().state()); - ui->editor_double2->setCurrentIndex(current_action->num2().state()); - ui->editor_double3->setCurrentIndex(current_action->num3().state()); - ui->editor_double4->setCurrentIndex(current_action->num4().state()); + ui->editor_sub->setCurrentIndex(current_action->sub()); + on_action_sub_change(current_action->sub()); - // Turn on action editing so that LCDData's and potential MMGFields can be set - action_display_params.initializing = false; - lcd_double1.can_set(true); - lcd_double2.can_set(true); - lcd_double3.can_set(true); - lcd_double4.can_set(true); - - change_action_secondary(); + // Turn on action editing + current_action->setEditable(true); + ui->editor_cat->blockSignals(false); + ui->editor_sub->blockSignals(false); } void MMGEchoWindow::set_preferences_view() { - ui->editor_global_enable->setChecked(global()->preferences().get_active()); + ui->editor_global_enable->setChecked(global()->preferences()->active()); + + ui->editor_midi_thru_device->blockSignals(true); + ui->editor_midi_thru_toggle->setChecked(!global()->preferences()->thruDevice().isEmpty()); + ui->editor_midi_thru_device->setEnabled(!global()->preferences()->thruDevice().isEmpty()); + ui->editor_midi_thru_device->clear(); + ui->editor_midi_thru_device->addItems(output_device()->outputDeviceNames()); + ui->editor_midi_thru_device->setCurrentText(global()->preferences()->thruDevice()); + ui->editor_midi_thru_device->blockSignals(false); } void MMGEchoWindow::on_device_change(const QString &name) { if (name.isEmpty()) return; - current_device = global()->find_device(name); - global()->set_active_device_name(name); + current_device = global()->find(name); + global()->setActiveDeviceName(name); switch_structure_pane(1); } void MMGEchoWindow::on_message_type_change(const QString &type) { - lcd_note.set_storage(¤t_message->note()); - SET_LCD_STATUS(note, Enabled, true); - SET_LCD_STATUS(value, Visible, true); - SET_LCD_STATUS(value, Enabled, true); - ui->editor_note->setVisible(true); - ui->editor_value->setVisible(true); - COMBOBOX_ITEM_STATE(editor_value, 2, false); - - ui->editor_value->setCurrentIndex(current_message->value().state()); - on_message_value_button_change(current_message->value().state()); + ui->editor_type_toggle->setVisible(false); + ui->editor_value_toggle->setVisible(false); - current_message->type() = type; - ui->editor_type->setCurrentText(type); + current_message->type()->set_str(type); if (current_action) { - if (current_action->get_category() == MMGAction::Category::MMGACTION_MIDI && - current_action->str2().state() != 0) { - MMGString::State state = current_action->str2().state(); - change_str2_options(type); - current_action->str2().set_state(state); + // Slightly hacky, but it works without adding unnecessary functions + if (current_action->category() == MMGAction::MMGACTION_MIDI) { + auto midi_action = dynamic_cast(current_action); + midi_action->setLabels(); } } - if (type == "Note On / Note Off") { - ui->editor_type->setCurrentText("Note On / Note Off"); - current_message->type() = "Note On"; - ui->label_note->setText("Note #"); - ui->label_value->setText("Velocity"); - current_message->type().set_state(MMGString::STRINGSTATE_TOGGLE); - COMBOBOX_ITEM_STATE(editor_value, 2, true); - ui->editor_note->setVisible(false); - lcd_value.display(); - return; - } - - current_message->type().set_state(MMGString::STRINGSTATE_FIXED); + set_message_labels(type, note_display, value_display); if (type == "Note On" || type == "Note Off") { - ui->label_note->setText("Note #"); - ui->label_value->setText("Velocity"); - ui->editor_note->setVisible(false); - COMBOBOX_ITEM_STATE(editor_value, 2, true); - lcd_value.display(); - } else if (type == "Control Change") { - ui->label_note->setText("Control #"); - ui->label_value->setText("Value"); - ui->editor_note->setVisible(false); - lcd_value.display(); - } else if (type == "Program Change") { - ui->label_note->setText("Program #"); - SET_LCD_STATUS(value, Visible, false); - ui->editor_value->setVisible(false); - ui->editor_note->setCurrentIndex(current_message->value().state()); - lcd_note.set_storage(¤t_message->value()); - } else if (type == "Pitch Bend") { - ui->label_note->setText("Pitch Adjust"); - SET_LCD_STATUS(value, Visible, false); - ui->editor_value->setVisible(false); - ui->editor_note->setCurrentIndex(current_message->value().state()); - lcd_note.set_storage(¤t_message->value()); + ui->editor_type_toggle->setVisible(true); + ui->editor_value_toggle->setVisible(true); + return; } + ui->editor_type_toggle->setChecked(false); + ui->editor_value_toggle->setChecked(false); } void MMGEchoWindow::on_message_listen_once(bool toggled) { - global()->set_listening(toggled); ui->button_listen_once->setText(toggled ? "Cancel..." : "Listen Once..."); - if (!toggled) return; - global()->set_listening_callback([this](MMGMessage *incoming) { - if (!incoming) return; - // Check the validity of the message type (whether it is one of the five - // supported types) - if (ui->editor_type->findText(incoming->type()) == -1) return; - global()->set_listening(false); - ui->button_listen_once->setText("Listen Once..."); - ui->button_listen_once->setChecked(false); - incoming->deep_copy(current_message); - set_message_view(); - }); + if (listening_mode == 2) { + ui->button_listen_continuous->blockSignals(true); + ui->button_listen_continuous->setChecked(false); + ui->button_listen_continuous->blockSignals(false); + on_message_listen_continuous(0); + } + input_device()->setListening(toggled); + listening_mode = toggled; } void MMGEchoWindow::on_message_listen_continuous(bool toggled) { - global()->set_listening(toggled); ui->button_listen_continuous->setText(toggled ? "Cancel..." : "Listen Continuous..."); - if (!toggled) return; - global()->set_listening_callback([this](MMGMessage *incoming) { - if (!incoming) return; - // Check the validity of the message type (whether it is one of the five - // supported types) - if (ui->editor_type->findText(incoming->type()) == -1) return; - incoming->deep_copy(current_message); - set_message_view(); - }); -} - -void MMGEchoWindow::on_message_value_button_change(int index) -{ - current_message->value().set_state((MMGNumber::State)index); - - if (ui->editor_type->currentText() == "Program Change" || - ui->editor_type->currentText() == "Pitch Bend") { - if (index == 2) current_message->value().set_state(MMGNumber::NUMBERSTATE_MIDI); - SET_LCD_STATUS(note, Enabled, index == 0); - } else { - if (index == 2) current_message->value() = 127; - SET_LCD_STATUS(value, Enabled, index == 0); + if (listening_mode == 1) { + ui->button_listen_once->blockSignals(true); + ui->button_listen_once->setChecked(false); + ui->button_listen_once->blockSignals(false); + on_message_listen_once(0); } - lcd_note.display(); - lcd_value.display(); + input_device()->setListening(toggled); + listening_mode = toggled ? 2 : 0; } -void MMGEchoWindow::on_action_cat_change(int index) +void MMGEchoWindow::listen_update(const MMGSharedMessage &incoming) { - IF_ACTION_ENABLED current_binding->set_action_type(index); - IF_ACTION_ENABLED current_action = current_binding->get_action(); - lcd_double1.set_storage(¤t_action->num1()); - lcd_double2.set_storage(¤t_action->num2()); - lcd_double3.set_storage(¤t_action->num3()); - lcd_double4.set_storage(¤t_action->num4()); - change_sub_options(); + if (!incoming) return; + if (listening_mode < 1) return; + // Check the validity of the message type (whether it is one of the five + // supported types) + if (ui->editor_type->findText(*incoming->type()) == -1) return; + if (listening_mode == 1) { + ui->button_listen_once->setText("Listen Once..."); + ui->button_listen_once->setChecked(false); + input_device()->setListening(false); + } + incoming->copy(current_message); + set_message_view(); } -void MMGEchoWindow::change_sub_options() +void MMGEchoWindow::on_message_type_toggle(bool toggled) { - ui->editor_sub->clear(); - MMGActionDisplayParams params; - current_action->change_options_sub(params); - ui->editor_sub->addItems(params.list); - ui->editor_sub->setCurrentIndex(0); + current_message->type()->set_state(toggled << 1); } -void MMGEchoWindow::on_action_sub_change(int index) +void MMGEchoWindow::on_message_value_toggle(bool toggled) { - IF_ACTION_ENABLED current_action->set_sub(index); - action_display_params.clear(); - QString current_field_vals[3] = {ui->editor_str1->currentText(), ui->editor_str2->currentText(), - ui->editor_str3->currentText()}; - ui->editor_str1->clear(); - ui->editor_str2->clear(); - ui->editor_str3->clear(); - ui->editor_double1->setCurrentIndex(0); - ui->editor_double2->setCurrentIndex(0); - ui->editor_double3->setCurrentIndex(0); - ui->editor_double4->setCurrentIndex(0); - lcd_double1.set_state(0); - lcd_double2.set_state(0); - lcd_double3.set_state(0); - lcd_double4.set_state(0); - lcd_double1.set_use_time(false); - ui->action_secondary_fields->setCurrentIndex(0); - - switch (current_action->get_category()) { - case MMGAction::Category::MMGACTION_MIDI: - action_display_params.extra_data = current_message->type(); - break; - case MMGAction::Category::MMGACTION_INTERNAL: - action_display_params.extra_data = current_binding->get_name(); - break; - default: - break; - } - current_action->change_options_str1(action_display_params); - display_action_fields(); - ui->label_str1->setText(action_display_params.label_text); - ui->editor_str1->addItems(action_display_params.list); - - lcd_double1.reset(); - lcd_double2.reset(); - lcd_double3.reset(); - lcd_double4.reset(); - if (!current_field_vals[0].isEmpty()) ui->editor_str1->setCurrentText(current_field_vals[0]); - if (!current_field_vals[1].isEmpty()) ui->editor_str2->setCurrentText(current_field_vals[1]); - if (!current_field_vals[2].isEmpty()) ui->editor_str3->setCurrentText(current_field_vals[2]); - change_action_secondary(); -} - -void MMGEchoWindow::change_str1_options(const QString &value) -{ - if (value.isEmpty()) return; - set_str1(value); - ui->editor_str2->clear(); - - switch (current_action->get_category()) { - case MMGAction::Category::MMGACTION_MIDI: - action_display_params.extra_data = current_message->type(); - break; - case MMGAction::Category::MMGACTION_INTERNAL: - action_display_params.extra_data = current_binding->get_name(); - break; - default: - break; - } - current_action->change_options_str2(action_display_params); - display_action_fields(); - ui->label_str2->setText(action_display_params.label_text); - ui->editor_str2->addItems(action_display_params.list); + value_display->setLCDMode( + toggled ? 3 : (current_message->value()->state() == 3 ? 1 : current_message->value()->state())); + current_message->value()->set_num(toggled ? 127 : 0); + value_display->setDisabled(toggled); } -void MMGEchoWindow::change_str2_options(const QString &value) +void MMGEchoWindow::on_action_cat_change(int index) { - ui->action_secondary_fields->setCurrentIndex(0); - if (value.isEmpty()) return; - set_str2(value); - ui->editor_str3->clear(); - - switch (current_action->get_category()) { - case MMGAction::Category::MMGACTION_MIDI: - action_display_params.extra_data = current_message->type(); - break; - case MMGAction::Category::MMGACTION_INTERNAL: - action_display_params.extra_data = current_binding->get_name(); - break; - default: - break; - } - current_action->change_options_str3(action_display_params); - display_action_fields(); - ui->label_str3->setText(action_display_params.label_text); - ui->editor_str3->addItems(action_display_params.list); + if (!ui->editor_cat->signalsBlocked()) current_binding->setCategory(index); + current_action = current_binding->action(); + change_sub_options(); } -void MMGEchoWindow::change_str3_options(const QString &value) +void MMGEchoWindow::change_sub_options() { - if (value.isEmpty()) return; - set_str3(value); - current_action->change_options_final(action_display_params); - display_action_fields(); + ui->editor_sub->clear(); + current_action->setSubOptions(ui->editor_sub); } -void MMGEchoWindow::change_action_secondary() +void MMGEchoWindow::on_action_sub_change(int index) { - if (action_display_params.initializing) return; - - int index = 0; - MMGFields::Kind kind; + current_action->setSub(index); - switch (current_action->get_category()) { - case MMGAction::Category::MMGACTION_FILTER: - if (current_action->get_sub() != 4) return; - kind = MMGFields::MMGFIELDS_FILTER; - break; - case MMGAction::Category::MMGACTION_SOURCE_VIDEO: - if (current_action->get_sub() != 13) return; - kind = MMGFields::MMGFIELDS_SOURCE; - break; - case MMGAction::Category::MMGACTION_SOURCE_AUDIO: - if (current_action->get_sub() != 7) return; - kind = MMGFields::MMGFIELDS_SOURCE; - break; - case MMGAction::Category::MMGACTION_TRANSITION: - if (current_action->get_sub() != 4) return; - kind = MMGFields::MMGFIELDS_TRANSITION; - break; - default: - return; - } + if (current_action->display() == nullptr) { + current_action->createDisplay(ui->action_display_editor); - for (MMGFields *fields : field_groups) { - if (fields->ensure_validity(current_action)) { - index = fields->get_index(); - fields->json(); - break; - } - } + // Custom Fields Request System + connect(current_action->display(), &MMGActionDisplay::customFieldRequest, this, + &MMGEchoWindow::custom_field_request); - if (index == 0) { // Index will never be 0 if the fields exist (it's the index of the doubles) - field_groups.append(new MMGFields(kind, ui->action_secondary_fields, current_action)); - index = field_groups.last()->get_index(); + ui->action_display_editor->addWidget(current_action->display()); } - ui->action_secondary_fields->setCurrentIndex(index); + current_action->display()->setParent(ui->action_display_editor); + current_action->display()->setParentBinding(current_binding); + current_action->setSubConfig(); + ui->action_display_editor->setCurrentWidget(current_action->display()); } -void MMGEchoWindow::display_action_fields() +void MMGEchoWindow::custom_field_request(void *ptr, MMGString *action_json) { - if (action_display_params.display & MMGActionDisplayParams::DISPLAY_SEC) { - change_action_secondary(); - return; - } + obs_source_t *source = static_cast(ptr); + if (source == nullptr) return; - ui->label_str3->setVisible(false); - ui->editor_str3->setVisible(false); - ui->label_str2->setVisible(false); - ui->editor_str2->setVisible(false); - ui->label_str1->setVisible(false); - ui->editor_str1->setVisible(false); - - SET_LCD_STATUS(double4, Visible, false); - ui->editor_double4->setVisible(false); - ui->sep_action_3->setVisible(false); - SET_LCD_STATUS(double3, Visible, false); - ui->editor_double3->setVisible(false); - ui->sep_action_2->setVisible(false); - SET_LCD_STATUS(double2, Visible, false); - ui->editor_double2->setVisible(false); - ui->sep_action_1->setVisible(false); - SET_LCD_STATUS(double1, Visible, false); - ui->editor_double1->setVisible(false); - - switch (action_display_params.display & 0b111) { - case MMGActionDisplayParams::DISPLAY_STR3: - ui->label_str3->setVisible(true); - ui->editor_str3->setVisible(true); - [[fallthrough]]; - case MMGActionDisplayParams::DISPLAY_STR2: - ui->label_str2->setVisible(true); - ui->editor_str2->setVisible(true); - [[fallthrough]]; - case MMGActionDisplayParams::DISPLAY_STR1: - ui->label_str1->setVisible(true); - ui->editor_str1->setVisible(true); - break; - default: - break; - } - - switch (action_display_params.display & 0b1111000) { - case MMGActionDisplayParams::DISPLAY_NUM4: - SET_LCD_STATUS(double4, Visible, true); - ui->editor_double4->setVisible(true); - ui->sep_action_3->setVisible(true); - [[fallthrough]]; - case MMGActionDisplayParams::DISPLAY_NUM3: - SET_LCD_STATUS(double3, Visible, true); - ui->editor_double3->setVisible(true); - ui->sep_action_2->setVisible(true); - [[fallthrough]]; - case MMGActionDisplayParams::DISPLAY_NUM2: - SET_LCD_STATUS(double2, Visible, true); - ui->editor_double2->setVisible(true); - ui->sep_action_1->setVisible(true); - [[fallthrough]]; - case MMGActionDisplayParams::DISPLAY_NUM1: - SET_LCD_STATUS(double1, Visible, true); - ui->editor_double1->setVisible(true); - break; - default: - break; - } + QWidget *parent = current_action->display()->scrollWidget()->parentWidget(); + bool prev_fields_json_match = !current_fields || + current_fields->jsonDestination()->str() != action_json->str(); - ui->label_double1->setText(action_display_params.label_lcds[0]); - ui->label_double2->setText(action_display_params.label_lcds[1]); - ui->label_double3->setText(action_display_params.label_lcds[2]); - ui->label_double4->setText(action_display_params.label_lcds[3]); - - for (int i = 0; i < 3; i++) { - COMBOBOX_ITEM_STATE(editor_double1, i + 1, - action_display_params.combo_display[0] & - (MMGActionDisplayParams::LCDComboDisplay)(1 << i)); - COMBOBOX_ITEM_STATE(editor_double2, i + 1, - action_display_params.combo_display[1] & - (MMGActionDisplayParams::LCDComboDisplay)(1 << i)); - COMBOBOX_ITEM_STATE(editor_double3, i + 1, - action_display_params.combo_display[2] & - (MMGActionDisplayParams::LCDComboDisplay)(1 << i)); - COMBOBOX_ITEM_STATE(editor_double4, i + 1, - action_display_params.combo_display[3] & - (MMGActionDisplayParams::LCDComboDisplay)(1 << i)); + for (MMGOBSFields *fields : custom_fields) { + if (fields->match(source)) { + current_fields = fields; + fields->setParent(parent); + fields->setJsonDestination(action_json, prev_fields_json_match); + current_action->display()->setScrollWidget(fields); + return; + } } + MMGOBSFields *new_fields = new MMGOBSFields(parent, source); + new_fields->setJsonDestination(action_json, prev_fields_json_match); + current_fields = new_fields; + custom_fields.append(new_fields); + current_action->display()->setScrollWidget(new_fields); } void MMGEchoWindow::on_add_click() @@ -648,8 +382,14 @@ void MMGEchoWindow::on_remove_click() { QListWidgetItem *current = ui->editor_structure->currentItem(); if (!current) return; + if (current->text() != current_binding->name()) + current_binding = current_device->find(current->text()); + current_device->remove(current_binding); delete current_binding; + current_binding = nullptr; + current_message = nullptr; + current_action = nullptr; delete current; @@ -667,27 +407,28 @@ void MMGEchoWindow::on_preferences_click(bool toggle) switch_structure_pane(1 + toggle); ui->button_preferences->setText(toggle ? "Return to Bindings..." : "Preferences..."); ui->editor_devices->setDisabled(toggle); + set_preferences_view(); } void MMGEchoWindow::on_list_widget_state_change(QListWidgetItem *widget_item) { - if (!current_binding || !widget_item) return; + if (!widget_item) return; if (ui->editor_structure->currentRow() != ui->editor_structure->row(widget_item)) { ui->editor_structure->setCurrentItem(widget_item); on_list_selection_change(widget_item); } - if (current_binding->get_name() != widget_item->text()) { + if (current_binding->name() != widget_item->text()) { QString name{widget_item->text()}; - if (!!current_device->find_binding(name)) { - ui->editor_structure->currentItem()->setText(current_binding->get_name()); + if (!!current_device->find(name)) { + ui->editor_structure->currentItem()->setText(current_binding->name()); return; } - current_binding->set_name(name); + current_binding->setName(name); } - current_binding->set_enabled((bool)widget_item->checkState()); + current_binding->setEnabled((bool)widget_item->checkState()); } void MMGEchoWindow::on_list_selection_change(QListWidgetItem *widget_item) @@ -704,9 +445,10 @@ void MMGEchoWindow::on_list_selection_change(QListWidgetItem *widget_item) ui->button_duplicate->setEnabled(true); ui->button_remove->setEnabled(true); - current_binding = current_device->find_binding(widget_item->text()); - current_message = current_binding->get_message(); - current_action = current_binding->get_action(); + current_binding = current_device->find(widget_item->text()); + current_message = current_binding->message(); + current_action = current_binding->action(); + set_message_view(); set_action_view(); ui->pages->setCurrentIndex(1); @@ -724,7 +466,7 @@ void MMGEchoWindow::switch_structure_pane(int page) ui->button_add->setEnabled(true); if (!current_device) return; - for (MMGBinding *const binding_el : current_device->get_bindings()) { + for (MMGBinding *const binding_el : current_device->bindings()) { add_widget_item(binding_el); } on_list_selection_change(nullptr); @@ -734,8 +476,8 @@ void MMGEchoWindow::add_widget_item(const MMGBinding *binding) const { QListWidgetItem *new_item = new QListWidgetItem; new_item->setFlags((Qt::ItemFlag)0b110111); - new_item->setCheckState((Qt::CheckState)(binding->get_enabled() ? 2 : 0)); - new_item->setText(binding->get_name()); + new_item->setCheckState((Qt::CheckState)(binding->enabled() ? 2 : 0)); + new_item->setText(binding->name()); ui->editor_structure->addItem(new_item); } @@ -750,13 +492,24 @@ void MMGEchoWindow::on_binding_drag(const QModelIndex &parent, int start, int en void MMGEchoWindow::on_active_change(bool toggle) { - global()->preferences().set_active(toggle); + global()->preferences()->setActive(toggle); +} + +void MMGEchoWindow::on_midi_thru_change(bool toggle) +{ + global()->preferences()->setThruDevice(toggle ? ui->editor_midi_thru_device->currentText() : ""); + ui->editor_midi_thru_device->setEnabled(toggle); +} + +void MMGEchoWindow::on_midi_thru_device_change(const QString &device) +{ + global()->preferences()->setThruDevice(device); } void MMGEchoWindow::export_bindings() { QString filepath = QFileDialog::getSaveFileName(this, tr("Save Bindings..."), - MMGConfig::get_filepath(), "JSON Files (*.json)"); + MMGConfig::filepath(), "JSON Files (*.json)"); if (!filepath.isNull()) global()->save(filepath); } @@ -784,22 +537,21 @@ void MMGEchoWindow::on_transfer_mode_change(short index) { switch (index) { case 0: - ui->text_transfer_mode->setText( - "In this mode, bindings will be copied from the source device " - "to the destination device. " - "The destination device will then contain both device's bindings."); + ui->label_transfer_dest->setText("to"); + ui->label_transfer_source->setText("from"); + ui->button_binding_transfer->setText("Copy..."); break; case 1: - ui->text_transfer_mode->setText( - "In this mode, bindings will be removed from the source device " - "and added to the destination device. " - "The destination device will then contain both device's bindings."); + ui->label_transfer_dest->setText("to"); + ui->label_transfer_source->setText("from"); + ui->button_binding_transfer->setText("Move..."); break; case 2: - ui->text_transfer_mode->setText( - "In this mode, bindings will be removed from the source device " - "and will replace existing bindings in the destination device. " - "NOTE: This will remove all existing bindings in the destination device."); + ui->label_transfer_dest->setText("in"); + ui->label_transfer_source->setText("with"); + ui->button_binding_transfer->setText("Replace..."); + break; + default: break; } } @@ -807,12 +559,8 @@ void MMGEchoWindow::on_transfer_mode_change(short index) void MMGEchoWindow::on_transfer_bindings_click() { transfer_bindings(ui->editor_transfer_mode->currentIndex(), - ui->editor_transfer_source->currentText(), - ui->editor_transfer_dest->currentText()); -} - -void MMGEchoWindow::on_interface_style_change(short index) -{ /* global()->preferences().set_ui_style(index); */ + ui->editor_transfer_dest->currentText(), + ui->editor_transfer_source->currentText()); } void MMGEchoWindow::on_update_check() @@ -823,5 +571,4 @@ void MMGEchoWindow::on_update_check() MMGEchoWindow::~MMGEchoWindow() { delete ui; - qDeleteAll(field_groups); } diff --git a/src/ui/mmg-echo-window.h b/src/ui/mmg-echo-window.h index 91de98c..4e64f9c 100644 --- a/src/ui/mmg-echo-window.h +++ b/src/ui/mmg-echo-window.h @@ -15,11 +15,10 @@ with this program. If not, see #include "ui_mmg-echo-window.h" #include "../mmg-utils.h" +#include "mmg-number-display.h" #include "mmg-fields.h" #include "../mmg-device.h" -#define IF_ACTION_ENABLED if (!action_display_params.initializing) - class MMGEchoWindow : public QDialog { Q_OBJECT @@ -30,22 +29,20 @@ class MMGEchoWindow : public QDialog { private: Ui::MMGEchoWindow *ui; - MMGUtils::LCDData lcd_channel; - MMGUtils::LCDData lcd_note; - MMGUtils::LCDData lcd_value; - MMGUtils::LCDData lcd_double1; - MMGUtils::LCDData lcd_double2; - MMGUtils::LCDData lcd_double3; - MMGUtils::LCDData lcd_double4; - - QList field_groups; - MMGUtils::MMGActionDisplayParams action_display_params; + MMGNumberDisplay *channel_display; + MMGNumberDisplay *note_display; + MMGNumberDisplay *value_display; MMGDevice *current_device = nullptr; MMGBinding *current_binding = nullptr; MMGMessage *current_message = nullptr; MMGAction *current_action = nullptr; + QList custom_fields; + MMGOBSFields *current_fields = nullptr; + + short listening_mode = 0; + void reject() override; void connect_ui_signals(); void switch_structure_pane(int page); @@ -53,24 +50,6 @@ class MMGEchoWindow : public QDialog { void set_action_view(); void set_preferences_view(); void change_sub_options(); - void set_channel(double value) { current_message->channel() = value; } - void set_note(double value) - { - (ui->lcd_value->isVisible() ? current_message->note() : current_message->value()) = value; - }; - void set_value(double value) { current_message->value() = value; }; - void set_str1(const QString &value) { IF_ACTION_ENABLED current_action->str1() = value; }; - void set_str2(const QString &value) { IF_ACTION_ENABLED current_action->str2() = value; }; - void set_str3(const QString &value) { IF_ACTION_ENABLED current_action->str3() = value; }; - void set_double1(double value) { IF_ACTION_ENABLED current_action->num1() = value; }; - void set_double2(double value) { IF_ACTION_ENABLED current_action->num2() = value; }; - void set_double3(double value) { IF_ACTION_ENABLED current_action->num3() = value; }; - void set_double4(double value) { IF_ACTION_ENABLED current_action->num4() = value; }; - void display_action_fields(); - void change_str1_options(const QString &value); - void change_str2_options(const QString &value); - void change_str3_options(const QString &value); - void change_action_secondary(); void add_widget_item(const MMGBinding *binding) const; void export_bindings(); void import_bindings(); @@ -79,19 +58,24 @@ class MMGEchoWindow : public QDialog { public slots: void show_window(); + void listen_update(const MMGSharedMessage &); + void custom_field_request(void *, MMGUtils::MMGString *); + private slots: void on_device_change(const QString &name); void on_message_type_change(const QString &type); void on_message_listen_continuous(bool toggled); void on_message_listen_once(bool toggled); - void on_message_value_button_change(int index); + void on_message_type_toggle(bool toggled); + void on_message_value_toggle(bool toggled); void on_action_cat_change(int index); void on_action_sub_change(int index); void on_active_change(bool toggle); + void on_midi_thru_change(bool toggle); + void on_midi_thru_device_change(const QString &device); void on_preferences_click(bool toggle); void on_transfer_mode_change(short index); void on_transfer_bindings_click(); - void on_interface_style_change(short index); void on_update_check(); void on_add_click(); void on_copy_click(); diff --git a/src/ui/mmg-echo-window.ui b/src/ui/mmg-echo-window.ui index 02cb3d1..e3cdfcb 100644 --- a/src/ui/mmg-echo-window.ui +++ b/src/ui/mmg-echo-window.ui @@ -75,7 +75,7 @@ width: 10px; 0 - 1 + 2 @@ -86,14 +86,14 @@ width: 10px; - + 0 - 300 + 320 531 - 421 + 411 @@ -217,11 +217,6 @@ width: 10px; Internal - - - Timeout - - @@ -245,169 +240,6 @@ width: 10px; - - - - 10 - 70 - 221 - 21 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - List 1 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 10 - 90 - 221 - 41 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - word-wrap: break-word; - - - - - - 10 - 140 - 221 - 21 - - - - - 0 - 0 - - - - - Tahoma - 9 - false - - - - List 2 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 10 - 160 - 221 - 41 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - - - - 10 - 210 - 221 - 21 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - List 3 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 10 - 230 - 221 - 41 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - @@ -427,2186 +259,25 @@ width: 10px; Qt::Horizontal - + - 240 + 1 61 - 290 - 359 + 529 + 349 - - 0 - - - - - - 240 - 320 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ▸▸ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 50 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 120 - 100 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - ArrowCursor - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - - 240 - 230 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ▸▸ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 200 - 320 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 120 - 280 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - ArrowCursor - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - - 200 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 10 - 10 - 111 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - Double 1 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 10 - 320 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 92 - 227 - 106 - 36 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - - Fixed - - - - - 0-127 - - - - - 127-0 - - - - - Ignore - - - - - - - 50 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 10 - 280 - 111 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - Double 4 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 200 - 230 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 200 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 10 - 180 - 270 - 1 - - - - QFrame::Plain - - - 3 - - - Qt::Horizontal - - - - - - 50 - 320 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 120 - 190 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - ArrowCursor - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - - 10 - 270 - 270 - 1 - - - - QFrame::Plain - - - 3 - - - Qt::Horizontal - - - - - - 10 - 100 - 111 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - Double 2 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 10 - 190 - 111 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - Double 3 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - true - - - - - - 10 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 92 - 47 - 106 - 36 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - - Fixed - - - - - 0-127 - - - - - 127-0 - - - - - Ignore - - - - - - - 92 - 137 - 106 - 36 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - - Fixed - - - - - 0-127 - - - - - 127-0 - - - - - Ignore - - - - - - - 50 - 230 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 240 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ▸▸ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 10 - 230 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 120 - 10 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - ArrowCursor - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - - 10 - 90 - 270 - 1 - - - - QFrame::Plain - - - 3 - - - Qt::Horizontal - - - - - - 240 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ▸▸ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 92 - 317 - 106 - 36 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - - Fixed - - - - - 0-127 - - - - - 127-0 - - - - - Ignore - - - - - - - 10 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - + 0 - 10 + 60 531 - 271 + 251 @@ -2630,10 +301,10 @@ width: 10px; - 10 + 250 10 - 101 - 21 + 95 + 41 @@ -2658,9 +329,9 @@ width: 10px; - 10 - 30 - 221 + 345 + 10 + 175 41 @@ -2686,11 +357,6 @@ width: 10px; Note Off - - - Note On / Note Off - - Control Change @@ -2707,45 +373,17 @@ width: 10px; - - - - 250 - 10 - 111 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - Channel - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - + - 250 - 100 - 111 + 10 + 210 + 221 31 - + 0 0 @@ -2756,49 +394,21 @@ width: 10px; 9 - - Note # - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - false - - - - 250 - 190 - 111 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - + + PointingHandCursor - Value + Listen Continuous... - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + true - + 10 - 230 + 170 221 31 @@ -2815,1387 +425,78 @@ width: 10px; 9 - - PointingHandCursor - - - Listen Continuous... - - - true - - - - - - 250 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 290 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 360 - 10 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - - 440 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 480 - 50 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ▸▸ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 360 - 100 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - false - - - - 360 - 190 - 160 - 31 - - - - - 0 - 0 - - - - - - - - - 255 - 255 - 255 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 255 - 255 - 255 - - - - - - - - ArrowCursor - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - true - - - 8 - - - QLCDNumber::Filled - - - 0.000000000000000 - - - 0 - - - - - - 250 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 290 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 440 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - - 480 - 140 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ▸▸ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - false - - - - 250 - 230 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - ◂◂ - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - - false - - - Qt::NoArrow - - - - - false - - - - 290 - 230 - 40 - 30 - - - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - Tahoma - 12 - true - - - - false - - - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly + + PointingHandCursor - - false + + Listen Once... - - Qt::NoArrow + + true - + - false + true - 440 - 230 - 40 - 30 + 10 + 10 + 221 + 41 - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - Tahoma - 12 - true + 10 - - false + + - - - - true - - - 500 - - - 50 - - - QToolButton::InstantPopup + Toggle Note On / Off Messages - - Qt::ToolButtonTextOnly - - + false - - Qt::NoArrow - - + - false + true - 480 - 230 - 40 - 30 + 10 + 50 + 221 + 41 - - - 40 - 30 - - - - - 40 - 30 - - - - - - - - - 72 - 73 - 74 - - - - - - - - - 64 - 65 - 66 - - - - - - - - - 72 - 73 - 74 - - - - - - Tahoma - 12 - true + 10 - - false + + - ▸▸ - - - true + Toggle Velocity - - 500 - - - 50 - - - QToolButton::InstantPopup - - - Qt::ToolButtonTextOnly - - + false - - Qt::NoArrow - 250 - 90 - 271 + 60 + 270 1 @@ -4213,8 +514,8 @@ width: 10px; 250 - 180 - 271 + 110 + 270 1 @@ -4228,129 +529,60 @@ width: 10px; Qt::Horizontal - - - - 10 - 190 - 221 - 31 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - PointingHandCursor - - - Listen Once... - - - true - - - - - - 332 - 137 - 106 - 36 - - - - - 0 - 0 - - - - - Tahoma - 9 - - - - - Fixed - - - - - 0-127 - - - - + - 332 - 227 - 106 - 36 + 250 + 160 + 270 + 1 - - - 0 - 0 - - - - - Tahoma - 9 - - - - - Fixed - - - - - 0-127 - - - - - Toggle - - - - - - - 332 - 47 - 106 - 36 - + + QFrame::Plain - - - 0 - 0 - + + 3 - - - Tahoma - 9 - + + Qt::Horizontal + + + false + + + + 0 + 0 + 531 + 51 + + + + + 0 + 0 + + + + + Tahoma + 9 + + + + PointingHandCursor + + + Switch to Output Binding... + + + false + + @@ -4359,7 +591,7 @@ width: 10px; 10 10 211 - 51 + 41 @@ -4372,7 +604,7 @@ width: 10px; - Enable Device Interaction + Enable MIDI Activity true @@ -4384,7 +616,7 @@ width: 10px; 230 10 291 - 51 + 41 @@ -4394,7 +626,7 @@ width: 10px; - Turning this off disables all actions, and nothing will happen when any message is recevied. + Turning this off will disable the plugin. true @@ -4444,16 +676,16 @@ width: 10px; Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - + - false + true 10 - 110 + 120 201 - 31 + 41 @@ -4468,20 +700,10 @@ width: 10px; 9 - - - Echo - - - - - Legacy - - - + - false + true @@ -4498,9 +720,9 @@ width: 10px; - This changes how this window will appear when opened. Changes will take effect after a restart of OBS Studio. + This allows messages to be sent from the active device input to the specified device output. -This is the Echo interface style. +NOTE: Enabling this feature may lead to a feedback loop or other undesired behavior, which could cause a crash. true @@ -4509,10 +731,10 @@ This is the Echo interface style. - 90 - 190 - 351 - 361 + 130 + 200 + 271 + 321 @@ -4531,8 +753,8 @@ This is the Echo interface style. 10 - 320 - 331 + 280 + 251 31 @@ -4561,9 +783,9 @@ This is the Echo interface style. - 140 - 230 - 201 + 50 + 240 + 211 31 @@ -4584,30 +806,8 @@ This is the Echo interface style. 10 - 270 - 111 - 31 - - - - - Tahoma - 9 - - - - Device Destination - - - true - - - - - - 10 - 110 - 111 + 200 + 31 31 @@ -4618,29 +818,10 @@ This is the Echo interface style. - Transfer Mode - - - true - - - - - - 10 - 140 - 331 - 81 - - - - - Tahoma - 9 - + to - - Transfer Mode Description + + Qt::AlignCenter true @@ -4649,9 +830,9 @@ This is the Echo interface style. - 140 - 110 - 201 + 50 + 160 + 211 31 @@ -4669,17 +850,17 @@ This is the Echo interface style. - Copy + Copy Bindings - Move + Move Bindings - Replace + Replace Bindings @@ -4688,8 +869,8 @@ This is the Echo interface style. 10 30 - 331 - 71 + 251 + 121 @@ -4708,9 +889,9 @@ This is the Echo interface style. - 140 - 270 - 201 + 50 + 200 + 211 31 @@ -4732,7 +913,7 @@ This is the Echo interface style. 10 10 - 331 + 251 21 @@ -4740,6 +921,7 @@ This is the Echo interface style. Tahoma 9 + true @@ -4753,8 +935,8 @@ This is the Echo interface style. 10 - 230 - 111 + 240 + 31 31 @@ -4765,7 +947,10 @@ This is the Echo interface style. - Device Source + from + + + Qt::AlignCenter true @@ -4803,13 +988,38 @@ This is the Echo interface style. false + + + + 10 + 80 + 201 + 31 + + + + + Tahoma + 10 + + + + + + + Enable MIDI Throughput + + + true + + 10 - 480 + 490 441 111 @@ -5081,7 +1291,7 @@ This is the Echo interface style. 10 10 441 - 451 + 471 diff --git a/src/ui/mmg-fields.cpp b/src/ui/mmg-fields.cpp index a38b02d..e32515c 100644 --- a/src/ui/mmg-fields.cpp +++ b/src/ui/mmg-fields.cpp @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,266 +21,254 @@ with this program. If not, see #include #include +#include +#include #include +#include using namespace MMGUtils; -// MMGNumberField -MMGNumberField::MMGNumberField(const MMGFieldInit &init) - : lcd(new QLCDNumber(init.parent)), lcd_data(lcd) +// MMGOBSNumberField +MMGOBSNumberField::MMGOBSNumberField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSField(init.fields, p) { name = obs_property_name(init.prop); - parent = init.group; - - lcd_data.set_storage(&number); - vec3 bounds = get_obs_filter_property_bounds(init.prop); - lcd_data.set_range(bounds.x, bounds.y); - lcd_data.set_step(bounds.z < 0.01 ? 0.01 : bounds.z, bounds.z < 0.01 ? 0.1 : bounds.z * 10); - lcd_data.set_default_value(init.default_json[name].toDouble()); - if (json_is_valid(init.action_json[name], QJsonValue::Double)) { - number = init.action_json[name].toDouble(); - } else if (json_is_valid(init.current_json[name], QJsonValue::Double)) { + + num_display = new MMGNumberDisplay(this); + num_display->setStorage(&number); + + update(init.prop); + if (init.current_json.contains(name)) { number = init.current_json[name].toDouble(); } else { - lcd_data.reset(); + number = init.default_json[name].toDouble(); } - lcd_data.display(); - number.set_state((MMGNumber::State)init.action_json[name + "_state"].toInt()); + num_display->display(); - label = new QLabel(init.parent); - label->setVisible(true); - label->setText(obs_property_description(init.prop)); - label->setGeometry(10, 10, 110, 30); - - lcd->setDigitCount(8); - lcd->setSmallDecimalPoint(true); - QPalette p = init.parent->palette(); - p.setColor(QPalette::Dark, QColor("#ffffff")); - lcd->setPalette(p); - lcd->setSegmentStyle(QLCDNumber::Filled); - lcd->setFrameShape(QFrame::NoFrame); - lcd->setFrameShadow(QFrame::Raised); - lcd->setLineWidth(0); - lcd->setVisible(true); - lcd->setGeometry(120, 10, 160, 30); - - down_major_button = new QToolButton(init.parent); - down_major_button->setVisible(true); - down_major_button->setText("◂◂"); - down_major_button->setAutoRepeat(true); - down_major_button->setAutoRepeatDelay(500); - down_major_button->setAutoRepeatInterval(50); - down_major_button->setGeometry(10, 50, 40, 30); - down_major_button->connect(down_major_button, &QAbstractButton::clicked, down_major_button, - [&]() { - lcd_data.down_major(); - parent->update(this); - }); - - down_minor_button = new QToolButton(init.parent); - down_minor_button->setVisible(true); - down_minor_button->setText("◂"); - down_minor_button->setAutoRepeat(true); - down_minor_button->setAutoRepeatDelay(500); - down_minor_button->setAutoRepeatInterval(50); - down_minor_button->setGeometry(50, 50, 40, 30); - down_minor_button->connect(down_minor_button, &QAbstractButton::clicked, down_minor_button, - [&]() { - lcd_data.down_minor(); - parent->update(this); - }); - - up_minor_button = new QToolButton(init.parent); - up_minor_button->setVisible(true); - up_minor_button->setText("▸"); - up_minor_button->setAutoRepeat(true); - up_minor_button->setAutoRepeatDelay(500); - up_minor_button->setAutoRepeatInterval(50); - up_minor_button->setGeometry(200, 50, 40, 30); - up_minor_button->connect(up_minor_button, &QAbstractButton::clicked, up_minor_button, [&]() { - lcd_data.up_minor(); - parent->update(this); - }); - - up_major_button = new QToolButton(init.parent); - up_major_button->setVisible(true); - up_major_button->setText("▸▸"); - up_major_button->setAutoRepeat(true); - up_major_button->setAutoRepeatDelay(500); - up_major_button->setAutoRepeatInterval(50); - up_major_button->setGeometry(240, 50, 40, 30); - up_major_button->connect(up_major_button, &QAbstractButton::clicked, up_major_button, [&]() { - lcd_data.up_major(); - parent->update(this); - }); - - combo = new QComboBox(init.parent); - combo->setVisible(true); - combo->addItems({"Fixed", "0-127", "127-0", "Ignore"}); - combo->setCurrentIndex(number.state()); - combo->setGeometry(92, 47, 106, 36); - combo->connect(combo, &QComboBox::currentIndexChanged, combo, [&](int index) { - lcd->setEnabled(index == 0); - number.set_state((MMGNumber::State)index); - parent->update(this); - }); + connect(num_display, &MMGNumberDisplay::numberChanged, parent, &MMGOBSFields::mmg_json); } -void MMGNumberField::update(obs_property_t *prop) +void MMGOBSNumberField::apply(const QJsonObject &json_obj) { - bool enabled = obs_property_visible(prop) && obs_property_enabled(prop) && number.state() == 0; - label->setEnabled(enabled); - lcd->setEnabled(enabled); - lcd_data.display(); - down_major_button->setEnabled(enabled); - down_minor_button->setEnabled(enabled); - up_minor_button->setEnabled(enabled); - up_major_button->setEnabled(enabled); - combo->setEnabled(obs_property_visible(prop) && obs_property_enabled(prop)); + num_display->blockSignals(true); + update(parent->property(name)); + number = MMGNumber(json_obj, name, 0); + num_display->display(); + num_display->blockSignals(false); +} + +void MMGOBSNumberField::update(obs_property_t *prop) +{ + bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); + num_display->setEnabled(enabled); + + if (obs_property_get_type(prop) != OBS_PROPERTY_FLOAT && + obs_property_get_type(prop) != OBS_PROPERTY_INT) + return; + + vec3 bounds = get_obs_property_bounds(prop); + num_display->setBounds(bounds.x, bounds.y); + num_display->setStep(bounds.z); + num_display->setDescription(obs_property_description(prop)); } -// End MMGNumber Field +// End MMGOBSNumber Field -// MMGStringField -MMGStringField::MMGStringField(const MMGFieldInit &init) +// MMGOBSStringField +MMGOBSStringField::MMGOBSStringField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSField(init.fields, p) { name = obs_property_name(init.prop); - if (json_is_valid(init.action_json[name], QJsonValue::String)) { - value = init.action_json[name].toString(); - } else if (json_is_valid(init.current_json[name], QJsonValue::String)) { - value = init.current_json[name].toString(); - } else { - value = init.default_json[name].toString(); - } - parent = init.group; - label = new QLabel(init.parent); + label = new QLabel(this); label->setVisible(true); label->setText(obs_property_description(init.prop)); - label->setGeometry(10, 10, 270, 20); + label->setWordWrap(true); + label->setGeometry(0, 0, 270, 30); - combo = new QComboBox(init.parent); + combo = new QComboBox(this); combo->setVisible(true); - QHash options = get_obs_filter_property_options(init.prop); - for (const QString &key : options.keys()) { - combo->addItem(key, options[key]); + combo->setGeometry(0, 30, 270, 40); + combo->setEditable(obs_property_list_type(init.prop) == OBS_COMBO_TYPE_EDITABLE); + + if (init.current_json.contains(name)) { + value = init.current_json[name]; + } else { + value = init.default_json[name]; } - combo->addItem("Use Message Value", "msg_val"); - combo->setGeometry(10, 30, 270, 40); - combo->setCurrentIndex(combo->findData(value.str())); - combo->connect(combo, QOverload::of(&QComboBox::currentIndexChanged), combo, - [&](int index) { callback(combo->itemData(index).value()); }); + update(init.prop); + + combo->connect(combo, &QComboBox::currentIndexChanged, combo, + [&](int index) { callback(combo->itemData(index)); }); } -void MMGStringField::callback(const QString &val) +void MMGOBSStringField::callback(const QVariant &val) { - value = val; - value.set_state(val == "msg_val" ? MMGString::STRINGSTATE_MIDI : MMGString::STRINGSTATE_FIXED); - parent->update(this); + value = val.value(); + state = val == "msg_val" ? MMGString::STRINGSTATE_MIDI : MMGString::STRINGSTATE_FIXED; + parent->obs_json(); + parent->mmg_json(); } -void MMGStringField::update(obs_property_t *prop) +void MMGOBSStringField::apply(const QJsonObject &json_obj) +{ + value = json_obj[name]["value"]; + state = (MMGString::State)json_obj[name]["state"].toInt(); + update(parent->property(name)); +} + +void MMGOBSStringField::update(obs_property_t *prop) { bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); label->setEnabled(enabled); combo->setEnabled(enabled); + + if (obs_property_get_type(prop) != OBS_PROPERTY_LIST) return; + + QList prop_options = get_obs_property_options(prop); + if (options != prop_options) { + options = prop_options; + combo->clear(); + for (const MMGPair &key : prop_options) { + combo->addItem(key.key, key.val); + } + if (!prop_options.isEmpty()) combo->addItem("Use Message Value", "msg_val"); + int index = combo->findData(value.toVariant()); + combo->setCurrentIndex(index == -1 ? 0 : index); + } } -// End MMGStringField +// End MMGOBSStringField -// MMGBooleanField -MMGBooleanField::MMGBooleanField(const MMGFieldInit &init) : MMGStringField(init) +// MMGOBSBooleanField +MMGOBSBooleanField::MMGOBSBooleanField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSStringField(p, init) { + label->setWordWrap(false); + + combo->blockSignals(true); combo->clear(); combo->addItems({"True", "False", "Toggle", "Ignore"}); - combo->setItemData(0, "True"); - - if (json_is_valid(init.action_json[name], QJsonValue::Bool)) { - bool_value = init.action_json[name].toBool(); - } else if (json_is_valid(init.current_json[name], QJsonValue::Bool)) { - bool_value = init.current_json[name].toBool(); + combo->setItemData(0, true); + combo->setItemData(1, false); + combo->blockSignals(false); + + combo->blockSignals(true); + if (init.current_json.contains(name)) { + value = init.current_json[name]; + combo->setCurrentIndex(state != 0 ? state : !value.toBool()); } else { - bool_value = init.default_json[name].toBool(); + value = init.default_json[name]; + combo->setCurrentIndex(state != 0 ? state : !value.toBool()); } - value.set_state((MMGString::State)init.action_json[name + "_state"].toInt()); + combo->blockSignals(false); +} - combo->setCurrentIndex(value.state() != 0 ? value.state() : !bool_value); +void MMGOBSBooleanField::callback(const QVariant &val) +{ + value = (combo->currentIndex() > 1 ? QVariant(true) : val).toJsonValue(); + state = (MMGString::State)(combo->currentIndex() > 1 ? combo->currentIndex() : 0); + parent->obs_json(); + parent->mmg_json(); } -void MMGBooleanField::callback(const QString &val) +void MMGOBSBooleanField::apply(const QJsonObject &json_obj) { - bool_value = combo->currentIndex() > 1 ? true : bool_from_str(val); - value.set_state((MMGString::State)(combo->currentIndex() > 1 ? combo->currentIndex() : 0)); - parent->update(this); + value = json_obj[name]["value"]; + state = (MMGString::State)json_obj[name]["state"].toInt(); + combo->setCurrentIndex(state != 0 ? state : !value.toBool()); } -void MMGBooleanField::update(obs_property_t *prop) +void MMGOBSBooleanField::update(obs_property_t *prop) { bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); label->setEnabled(enabled); combo->setEnabled(enabled); } -// End MMGBooleanField +// End MMGOBSBooleanField + +// MMGOBSButtonField +const QString MMGOBSButtonField::style_sheet = "border: 1px solid #ff0000; border-radius: 10px;"; -// MMGButtonField -MMGButtonField::MMGButtonField(const MMGFieldInit &init) +MMGOBSButtonField::MMGOBSButtonField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSField(init.fields, p) { name = obs_property_name(init.prop); - parent = init.group; - label = new QLabel(init.parent); + label = new QLabel(this); label->setVisible(false); - button = new QPushButton(init.parent); + button = new QPushButton(this); button->setVisible(true); - button->setGeometry(25, 25, 240, 40); + button->setGeometry(15, 15, 240, 40); button->setCursor(QCursor(Qt::PointingHandCursor)); button->setText(obs_property_description(init.prop)); button->connect(button, &QPushButton::clicked, button, [&]() { callback(); }); } -void MMGButtonField::update(obs_property_t *prop) +void MMGOBSButtonField::update(obs_property_t *prop) { button->setEnabled(obs_property_visible(prop) && obs_property_enabled(prop)); } -void MMGButtonField::callback() +void MMGOBSButtonField::callback() { - obs_property_t *prop = parent->get_property(name); - obs_property_button_clicked(prop, nullptr); - parent->update(this); + obs_property_t *prop = parent->property(name); + if (obs_property_button_type(prop) == OBS_BUTTON_URL) { + QUrl url(QString(obs_property_button_url(prop)), QUrl::StrictMode); + if (!url.isValid() || !url.scheme().contains("http")) return; + QDesktopServices::openUrl(url); + } else { + obs_property_button_clicked(prop, nullptr); + } + parent->obs_json(); + parent->mmg_json(); } -// End MMGButtonField - -// MMGColorField -QString MMGColorField::style_sheet = - "border: 1px solid #ff0000; border-radius: 10px; background-color: "; +// End MMGOBSButtonField -MMGColorField::MMGColorField(const MMGFieldInit &init, bool use_alpha) : MMGButtonField(init) +// MMGOBSColorField +MMGOBSColorField::MMGOBSColorField(QWidget *p, const MMGOBSFieldInit &init, bool use_alpha) + : MMGOBSButtonField(p, init) { alpha = use_alpha; - if (json_is_valid(init.action_json[name], QJsonValue::Double)) { - color = QColor::fromRgba((quint32)init.action_json[name].toInteger()); - } else if (json_is_valid(init.current_json[name], QJsonValue::Double)) { - qDebug("%d", init.current_json[name].toInteger()); - color = QColor::fromRgba((quint32)init.current_json[name].toInteger()); - } else { - color = QColor::fromRgba((quint32)init.default_json[name].toInteger(4278190080u)); - } - label->setVisible(true); label->setText(obs_property_description(init.prop)); - label->setGeometry(10, 10, 130, 20); + label->setWordWrap(true); + label->setGeometry(0, 0, 130, 20); - button->setGeometry(10, 40, 130, 40); + button->setGeometry(0, 30, 130, 40); button->setText("Select Color..."); - frame = new QFrame(init.parent); + frame = new QFrame(this); frame->setVisible(true); - frame->setGeometry(150, 10, 130, 70); - frame->setStyleSheet(style_sheet + color.name(alpha ? QColor::HexArgb : QColor::HexRgb)); + frame->setGeometry(140, 0, 130, 70); + + if (init.current_json.contains(name)) { + color = QColor::fromRgba(argb_abgr(init.current_json[name].toInteger(4278190080u))); + } else { + color = QColor::fromRgba(argb_abgr(init.default_json[name].toInteger(4278190080u))); + } + frame->setStyleSheet(style_sheet + " background-color: " + color.name((QColor::NameFormat)alpha)); +} + +void MMGOBSColorField::obs_json(QJsonObject &json_obj) const +{ + json_obj[name] = (double)argb_abgr(color.rgba()); +} + +void MMGOBSColorField::mmg_json(QJsonObject &json_obj) const +{ + QJsonObject color_obj; + color_obj["color"] = (double)argb_abgr(color.rgba()); + json_obj[name] = color_obj; +} + +void MMGOBSColorField::apply(const QJsonObject &json_obj) +{ + color = QColor::fromRgba(argb_abgr(json_obj[name]["color"].toInteger(4278190080u))); + frame->setStyleSheet(style_sheet + " background-color: " + color.name((QColor::NameFormat)alpha)); } -void MMGColorField::update(obs_property_t *prop) +void MMGOBSColorField::update(obs_property_t *prop) { bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); label->setEnabled(enabled); @@ -288,135 +276,425 @@ void MMGColorField::update(obs_property_t *prop) frame->setEnabled(enabled); } -void MMGColorField::callback() +void MMGOBSColorField::callback() { - QColor _color = - alpha ? QColorDialog::getColor(color, nullptr, QString(), QColorDialog::ShowAlphaChannel) - : QColorDialog::getColor(color); + QColor _color = QColorDialog::getColor(color, nullptr, QString(), + alpha ? QColorDialog::ShowAlphaChannel + : (QColorDialog::ColorDialogOption)0); color = _color.isValid() ? _color : color; - frame->setStyleSheet(style_sheet + color.name(alpha ? QColor::HexArgb : QColor::HexRgb)); - parent->update(this); + frame->setStyleSheet(style_sheet + " background-color: " + color.name((QColor::NameFormat)alpha)); + parent->mmg_json(); } -// End MMGColorField +// End MMGOBSColorField -// MMGFields -MMGFields::MMGFields(Kind kind, QStackedWidget *parent, MMGAction *fields_action) +// MMGOBSFontField +MMGOBSFontField::MMGOBSFontField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSButtonField(p, init) { - index = parent->count(); - action = fields_action; - source = action->str1(); - filter = action->str2(); + label->setVisible(true); + label->setText(obs_property_description(init.prop)); + label->setWordWrap(true); + label->setGeometry(0, 0, 130, 20); - QScrollBar *scrollbar = new QScrollBar(parent->window()); + button->setGeometry(0, 30, 130, 40); + button->setText("Select Font..."); - QScrollArea *scroll_area = new QScrollArea(parent); - scroll_area->setGeometry(QRect(0, 0, 291, 360)); - scroll_area->setFrameShape(QFrame::NoFrame); - scroll_area->setVerticalScrollBar(scrollbar); - scroll_area->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); - scroll_area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scroll_area->setWidgetResizable(true); + QFrame *frame = new QFrame(this); + frame->setVisible(true); + frame->setGeometry(140, 0, 130, 70); + frame->setStyleSheet(style_sheet); + + inner_label = new QLabel(frame); + inner_label->setVisible(true); + inner_label->setGeometry(0, 0, 130, 70); + inner_label->setAlignment(Qt::AlignCenter); + inner_label->setText(font.family() + "\n" + QString::number(font.pointSize()) + "pt"); + inner_label->setWordWrap(true); + + if (init.current_json.contains(name)) { + apply(init.current_json); + } else { + apply(init.default_json); + } +} - widget = new QWidget(scroll_area); - widget->setGeometry(QRect(0, 0, 290, 360)); - QVBoxLayout *custom_field_layout = new QVBoxLayout(widget); - custom_field_layout->setSpacing(0); - custom_field_layout->setSizeConstraint(QLayout::SetFixedSize); - custom_field_layout->setContentsMargins(0, 0, 0, 0); - widget->setLayout(custom_field_layout); - scroll_area->setWidget(widget); +void MMGOBSFontField::obs_json(QJsonObject &json_obj) const +{ + QJsonObject font_json; + font_json["face"] = font.family(); + font_json["flags"] = (font.strikeOut() << 3) | (font.underline() << 2) | (font.italic() << 1) | + (font.bold() << 0); + font_json["size"] = font.pointSize(); + font_json["style"] = font.styleName(); + json_obj[name] = font_json; +} - parent->addWidget(scroll_area); +void MMGOBSFontField::apply(const QJsonObject &json_obj) +{ + QJsonObject font_obj = json_obj[name].toObject(); + font.setFamily(font_obj["face"].toString()); + int flags = font_obj["flags"].toInt(); + font.setBold(flags & 0b0001); + font.setItalic(flags & 0b0010); + font.setUnderline(flags & 0b0100); + font.setStrikeOut(flags & 0b1000); + font.setPointSize(font_obj["size"].toInt()); + font.setStyleName(font_obj["style"].toString()); + inner_label->setText(font.family() + "\n" + QString::number(font.pointSize()) + "pt"); +} - switch (kind) { - case Kind::MMGFIELDS_FILTER: - create_filter_fields(); - break; - default: - break; +void MMGOBSFontField::update(obs_property_t *prop) +{ + bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); + label->setEnabled(enabled); + button->setEnabled(enabled); + inner_label->setEnabled(enabled); +} + +void MMGOBSFontField::callback() +{ + bool accept; + QFont _font = QFontDialog::getFont(&accept, font, nullptr, "Select Font...", + QFontDialog::DontUseNativeDialog); + if (accept) font = _font; + inner_label->setText(font.family() + "\n" + QString::number(font.pointSize()) + "pt"); + parent->mmg_json(); +} +// End MMGOBSFontField + +// MMGOBSGroupField +MMGOBSGroupField::MMGOBSGroupField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSField(init.fields, p) +{ + name = obs_property_name(init.prop); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setSpacing(15); + layout->setSizeConstraint(QLayout::SetFixedSize); + layout->setContentsMargins(0, 0, 0, 25); + setLayout(layout); + + if (obs_property_group_type(init.prop) == OBS_GROUP_CHECKABLE) { + boolean_field = new MMGOBSBooleanField(p, init); + boolean_field->setStyleSheet("QLabel {font-weight: bold;}"); + parent->layout()->addWidget(boolean_field); + } else if (obs_property_group_type(init.prop) == OBS_GROUP_NORMAL) { + label = new QLabel(this); + label->setVisible(true); + label->setStyleSheet("QLabel {font-weight: bold;}"); + label->setText(obs_property_description(init.prop)); + layout->addWidget(label); } - update(nullptr); -}; + parent->create(this, obs_property_group_content(init.prop), init); +} -const QString MMGFields::json() +void MMGOBSGroupField::obs_json(QJsonObject &json_obj) const { - QJsonObject json_obj; - for (MMGField *field : fields) { - QString name = field->get_name(); - bool exists = !json_obj[name].isNull(); + if (boolean_field) boolean_field->obs_json(json_obj); +} + +void MMGOBSGroupField::mmg_json(QJsonObject &json_obj) const +{ + if (boolean_field) boolean_field->mmg_json(json_obj); +} - if (exists) field->set_name(name + "_"); - field->json(json_obj); - if (exists) field->set_name(name); +void MMGOBSGroupField::update(obs_property_t *prop) +{ + if (boolean_field) { + boolean_field->update(prop); + boolean_field_state = boolean_field->value.toBool(true); + setEnabled(boolean_field_state); } - action->str3() = json_to_str(json_obj); - return action->str3(); } +// End MMGOBSGroupField -void MMGFields::update(MMGField *from_field) +// MMGOBSTextField +MMGOBSTextField::MMGOBSTextField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSField(init.fields, p) { - QJsonObject json_obj = json_from_str(json().qtocs()); - for (const QString &key : json_obj.keys()) { - if (key.endsWith("_state_")) json_obj[key.chopped(1)] = json_obj[key]; + name = obs_property_name(init.prop); + + obs_text_type prop_type = obs_property_text_type(init.prop); + + label = new QLabel(this); + label->setVisible(true); + label->setText(obs_property_description(init.prop)); + label->setGeometry(0, 0, 270, 20); + + text_edit = new QTextEdit(this); + text_edit->setGeometry(0, 20, 270, 50); + text_edit->setVisible(prop_type == OBS_TEXT_MULTILINE); + + line_edit = new QLineEdit(this); + line_edit->setGeometry(0, 20, 270, 50); + line_edit->setVisible(prop_type != OBS_TEXT_MULTILINE); + line_edit->setEchoMode((QLineEdit::EchoMode)(prop_type == OBS_TEXT_PASSWORD ? 3 : 0)); + + if (init.current_json.contains(name)) { + text = init.current_json[name].toString(); + } else { + text = init.default_json[name].toString(); } - OBSDataAutoRelease data = obs_data_create_from_json(json_to_str(json_obj)); - for (MMGField *field : fields) { - obs_property_t *prop = get_property(field->get_name()); - if (field == from_field) obs_property_modified(prop, data); - field->update(prop); + text.set_state((MMGString::State)checkMIDI()); + update(parent->property(name)); + + text_edit->connect(text_edit, &QTextEdit::textChanged, text_edit, + [&]() { callback(text_edit->toPlainText()); }); + line_edit->connect(line_edit, &QLineEdit::textChanged, line_edit, + [&]() { callback(line_edit->text()); }); +} + +void MMGOBSTextField::callback(const QString &str) +{ + text = str; + text.set_state((MMGString::State)checkMIDI()); + parent->mmg_json(); +} + +void MMGOBSTextField::apply(const QJsonObject &json_obj) +{ + text = json_obj[name]["string"].toString(); + text.set_state((MMGString::State)checkMIDI()); + + update(parent->property(name)); +} + +void MMGOBSTextField::update(obs_property_t *prop) +{ + bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); + label->setEnabled(enabled); + text_edit->setEnabled(enabled); + line_edit->setEnabled(enabled); + + text_edit->setPlainText(text); + line_edit->setText(text); +} + +bool MMGOBSTextField::checkMIDI() const +{ + return text.str().contains("${type}") || text.str().contains("${channel}") || + text.str().contains("${note}") || text.str().contains("${value}") || + text.str().contains("${control}"); +} +// End MMGOBSTextField + +// MMGOBSPathField +MMGOBSPathField::MMGOBSPathField(QWidget *p, const MMGOBSFieldInit &init) + : MMGOBSButtonField(p, init) +{ + label->setVisible(true); + label->setText(obs_property_description(init.prop)); + label->setGeometry(0, 0, 130, 20); + + button->setGeometry(0, 30, 130, 40); + + QFrame *frame = new QFrame(this); + frame->setVisible(true); + frame->setGeometry(140, 0, 130, 70); + frame->setStyleSheet(style_sheet); + + path_display = new QLabel(frame); + path_display->setVisible(true); + path_display->setGeometry(0, 0, 130, 70); + path_display->setAlignment(Qt::AlignBottom); + path_display->setWordWrap(true); + + if (init.current_json.contains(name)) { + path = init.current_json[name].toString(); + } else { + path = init.default_json[name].toString(); + } + update(parent->property(name)); +} + +void MMGOBSPathField::apply(const QJsonObject &json_obj) +{ + path = json_obj[name]["string"].toString(); + update(parent->property(name)); +} + +void MMGOBSPathField::update(obs_property_t *prop) +{ + bool enabled = obs_property_visible(prop) && obs_property_enabled(prop); + label->setEnabled(enabled); + button->setEnabled(enabled); + path_display->setEnabled(enabled); + + if (obs_property_get_type(prop) != OBS_PROPERTY_PATH) return; + + default_path = obs_property_path_default_path(prop); + dialog_type = obs_property_path_type(prop); + filters = obs_property_path_filter(prop); + + path_display->setText(path); + button->setText(dialog_type < 2 ? "Select File..." : "Select Folder..."); +} + +void MMGOBSPathField::callback() +{ + QString new_path; + switch (dialog_type) { + case 0: // FILE READING + new_path = QFileDialog::getOpenFileName(nullptr, "Select File...", default_path, filters, + nullptr, QFileDialog::Option::HideNameFilterDetails); + break; + case 1: // FILE WRITING + new_path = QFileDialog::getSaveFileName(nullptr, "Select File...", default_path, filters); + break; + case 2: // DIRECTORY + new_path = QFileDialog::getExistingDirectory(nullptr, "Select Folder...", default_path); + break; + default: + break; + } + if (!new_path.isNull()) { + path = new_path; + update(parent->property(name)); + parent->mmg_json(); } } +// End MMGOBSPathField -void MMGFields::create_filter_fields() +// MMGOBSFields +MMGOBSFields::MMGOBSFields(QWidget *parent, obs_source_t *source) : QWidget(parent) { - OBSSourceAutoRelease obs_source = obs_get_source_by_name(action->str1().mmgtocs()); - OBSSourceAutoRelease obs_filter = - obs_source_get_filter_by_name(obs_source, action->str2().mmgtocs()); - OBSDataAutoRelease filter_data = obs_source_get_settings(obs_filter); - OBSDataAutoRelease filter_defaults = obs_data_get_defaults(filter_data); - QJsonObject filter_json = json_from_str(obs_data_get_json(filter_data)); - QJsonObject action_json = json_from_str(action->str3().mmgtocs()); - QJsonObject defaults_json = json_from_str(obs_data_get_json(filter_defaults)); + // Qt Setup + setGeometry(0, 0, 290, 350); + QVBoxLayout *custom_field_layout = new QVBoxLayout(this); + custom_field_layout->setSpacing(10); + custom_field_layout->setSizeConstraint(QLayout::SetFixedSize); + custom_field_layout->setContentsMargins(10, 10, 10, 10); + setLayout(custom_field_layout); + + // The Good Stuff + if (!source) { + open_message_box( + "Custom Setup Error", + "The custom fields menu could not be displayed.\n\nThe source is invalid or does not exist."); + return; + }; + + _source = obs_source_get_ref(source); - props = obs_source_properties(obs_filter); + OBSDataAutoRelease source_data = obs_source_get_settings(source); + OBSDataAutoRelease source_defaults = obs_data_get_defaults(source_data); + QJsonObject source_json = json_from_str(obs_data_get_json(source_data)); + QJsonObject defaults_json = json_from_str(obs_data_get_json(source_defaults)); + + props = obs_source_properties(source); + + MMGOBSFieldInit init{this, nullptr, source_json, defaults_json}; + create(this, props, init); + + // Finish + obs_json(); +}; + +void MMGOBSFields::add(QWidget *parent, MMGOBSField *field) +{ + fields.append(field); + parent->layout()->addWidget(field); +} + +void MMGOBSFields::create(QWidget *parent, obs_properties_t *props, + const MMGOBSFieldInit &json_init) +{ + if (!props) return; obs_property_t *prop = obs_properties_first(props); + + MMGOBSFieldInit init{this, prop, json_init.current_json, json_init.default_json}; + do { - QWidget *middle_widget = new QWidget; - middle_widget->setFixedSize(290, 90); - middle_widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); - MMGFieldInit init{this, middle_widget, prop, filter_json, defaults_json, action_json}; + init.prop = prop; + switch (obs_property_get_type(prop)) { case OBS_PROPERTY_FLOAT: case OBS_PROPERTY_INT: - fields.append(new MMGNumberField(init)); + add(parent, new MMGOBSNumberField(parent, init)); break; case OBS_PROPERTY_LIST: - fields.append(new MMGStringField(init)); + add(parent, new MMGOBSStringField(parent, init)); break; case OBS_PROPERTY_BOOL: - fields.append(new MMGBooleanField(init)); + add(parent, new MMGOBSBooleanField(parent, init)); break; case OBS_PROPERTY_BUTTON: - fields.append(new MMGButtonField(init)); + add(parent, new MMGOBSButtonField(parent, init)); break; case OBS_PROPERTY_COLOR: - fields.append(new MMGColorField(init, false)); + add(parent, new MMGOBSColorField(parent, init, false)); break; case OBS_PROPERTY_COLOR_ALPHA: - fields.append(new MMGColorField(init, true)); + add(parent, new MMGOBSColorField(parent, init, true)); break; - case OBS_PROPERTY_EDITABLE_LIST: case OBS_PROPERTY_FONT: + add(parent, new MMGOBSFontField(parent, init)); + break; case OBS_PROPERTY_GROUP: + add(parent, new MMGOBSGroupField(parent, init)); + continue; case OBS_PROPERTY_PATH: + add(parent, new MMGOBSPathField(parent, init)); + break; case OBS_PROPERTY_TEXT: + if (obs_property_text_type(prop) != OBS_TEXT_INFO) { + add(parent, new MMGOBSTextField(parent, init)); + break; + } + [[fallthrough]]; + case OBS_PROPERTY_EDITABLE_LIST: + case OBS_PROPERTY_FRAME_RATE: default: - delete middle_widget; continue; } - widget->layout()->addWidget(middle_widget); } while (obs_property_next(&prop) != 0); } -// End MMGFields + +void MMGOBSFields::obs_json() +{ + QJsonObject json_obj; + for (MMGOBSField *field : fields) { + field->obs_json(json_obj); + } + OBSDataAutoRelease data = obs_data_create_from_json(json_to_str(json_obj)); + obs_properties_apply_settings(props, data); + for (MMGOBSField *field : fields) { + field->update(property(field->get_name())); + } +} + +void MMGOBSFields::mmg_json() +{ + if (!json_des) return; + + QJsonObject json_obj; + for (MMGOBSField *field : fields) { + field->mmg_json(json_obj); + } + json_des->set_str(json_to_str(json_obj)); +} + +void MMGOBSFields::setJsonDestination(MMGString *json, bool force) +{ + if (json_des == json) return; + json_des = json; + + if (force) { + if (!json->str().isEmpty()) apply(); + } else { + mmg_json(); + } +}; + +void MMGOBSFields::apply() +{ + QJsonObject json_obj = json_from_str(json_des->mmgtocs()); + for (MMGOBSField *field : fields) { + field->apply(json_obj); + } +} +// End MMGOBSFields diff --git a/src/ui/mmg-fields.h b/src/ui/mmg-fields.h index a9bae82..e45b5e7 100644 --- a/src/ui/mmg-fields.h +++ b/src/ui/mmg-fields.h @@ -1,6 +1,6 @@ /* obs-midi-mg -Copyright (C) 2022 nhielost +Copyright (C) 2022-2023 nhielost This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -18,7 +18,7 @@ with this program. If not, see #pragma once #include "../mmg-utils.h" -#include "../actions/mmg-action.h" +#include "mmg-number-display.h" #include #include @@ -27,22 +27,31 @@ with this program. If not, see #include #include #include +#include +#include +#include +#include -class MMGFields; +class MMGOBSFields; -struct MMGFieldInit { - MMGFields *group; - QWidget *parent; +struct MMGOBSFieldInit { + MMGOBSFields *fields; obs_property_t *prop; QJsonObject current_json; QJsonObject default_json; - QJsonObject action_json; }; -struct MMGField { +struct MMGOBSField : public QWidget { + Q_OBJECT + + public: + virtual ~MMGOBSField() = default; + const QString &get_name() const { return name; }; void set_name(const QString &val) { name = val; }; - virtual void json(QJsonObject &json_obj) const = 0; + virtual void obs_json(QJsonObject &json_obj) const = 0; + virtual void mmg_json(QJsonObject &json_obj) const = 0; + virtual void apply(const QJsonObject &json_obj) = 0; virtual void update(obs_property_t *prop) = 0; protected: @@ -50,113 +59,185 @@ struct MMGField { QLabel *label; - MMGFields *parent; + MMGOBSFields *parent; + + MMGOBSField(MMGOBSFields *fields, QWidget *widget = nullptr) : QWidget(widget) + { + parent = fields; + setFixedSize(270, 70); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + }; }; +using MMGOBSFieldList = QList; -struct MMGNumberField : public MMGField { - MMGNumberField(const MMGFieldInit &init); +struct MMGOBSNumberField : public MMGOBSField { + MMGOBSNumberField(QWidget *parent, const MMGOBSFieldInit &init); - void json(QJsonObject &json_obj) const override { number.json(json_obj, name, true); }; + void obs_json(QJsonObject &json_obj) const override { json_obj[name] = number.num(); }; + void mmg_json(QJsonObject &json_obj) const override { number.json(json_obj, name); }; + void apply(const QJsonObject &json_obj) override; void update(obs_property_t *prop) override; MMGUtils::MMGNumber number; - QLCDNumber *lcd; - QToolButton *down_major_button; - QToolButton *down_minor_button; - QToolButton *up_minor_button; - QToolButton *up_major_button; - QComboBox *combo; - MMGUtils::LCDData lcd_data; + MMGNumberDisplay *num_display; }; -struct MMGStringField : public MMGField { - MMGStringField(const MMGFieldInit &init); +struct MMGOBSStringField : public MMGOBSField { + MMGOBSStringField(QWidget *parent, const MMGOBSFieldInit &init); - virtual void callback(const QString &val); - void json(QJsonObject &json_obj) const override { value.json(json_obj, name, true); }; + virtual void callback(const QVariant &val); + void obs_json(QJsonObject &json_obj) const override { json_obj[name] = value; }; + void mmg_json(QJsonObject &json_obj) const override + { + QJsonObject value_obj; + value_obj["value"] = value; + value_obj["state"] = state; + json_obj[name] = value_obj; + }; + virtual void apply(const QJsonObject &json_obj) override; void update(obs_property_t *prop) override; - MMGUtils::MMGString value; + QJsonValue value; + MMGUtils::MMGString::State state = MMGUtils::MMGString::STRINGSTATE_FIXED; + + QList options; QComboBox *combo; }; -struct MMGBooleanField : public MMGStringField { - MMGBooleanField(const MMGFieldInit &init); +struct MMGOBSBooleanField : public MMGOBSStringField { + MMGOBSBooleanField(QWidget *parent, const MMGOBSFieldInit &init); - void callback(const QString &val) override; - void json(QJsonObject &json_obj) const override - { - json_obj[name] = bool_value; - json_obj[name + "_state"] = value.state(); - }; + void callback(const QVariant &val) override; + void apply(const QJsonObject &json_obj) override; void update(obs_property_t *prop) override; - - bool bool_value; }; -struct MMGButtonField : public MMGField { - MMGButtonField(const MMGFieldInit &init); +struct MMGOBSButtonField : public MMGOBSField { + MMGOBSButtonField(QWidget *parent, const MMGOBSFieldInit &init); virtual void callback(); - void json(QJsonObject &json_obj) const override{}; + void obs_json(QJsonObject &json_obj) const override { Q_UNUSED(json_obj); }; + void mmg_json(QJsonObject &json_obj) const override { Q_UNUSED(json_obj); }; + virtual void apply(const QJsonObject &json_obj) override { Q_UNUSED(json_obj); }; void update(obs_property_t *prop) override; QPushButton *button; + + static const QString style_sheet; }; -struct MMGColorField : public MMGButtonField { - MMGColorField(const MMGFieldInit &init, bool use_alpha); +struct MMGOBSColorField : public MMGOBSButtonField { + MMGOBSColorField(QWidget *parent, const MMGOBSFieldInit &init, bool use_alpha); void callback() override; - void json(QJsonObject &json_obj) const override { json_obj[name] = (double)(uint)color.rgba(); }; + void obs_json(QJsonObject &json_obj) const override; + void mmg_json(QJsonObject &json_obj) const override; + void apply(const QJsonObject &json_obj) override; void update(obs_property_t *prop) override; QColor color; bool alpha; QFrame *frame; +}; - private: - static QString style_sheet; +struct MMGOBSFontField : public MMGOBSButtonField { + MMGOBSFontField(QWidget *parent, const MMGOBSFieldInit &init); + + void callback() override; + void obs_json(QJsonObject &json_obj) const override; + void mmg_json(QJsonObject &json_obj) const override { obs_json(json_obj); }; + void apply(const QJsonObject &json_obj) override; + void update(obs_property_t *prop) override; + + QFont font; + + QLabel *inner_label; }; -class MMGFields { - public: - enum Kind { MMGFIELDS_FILTER, MMGFIELDS_TRANSITION, MMGFIELDS_SOURCE }; +struct MMGOBSGroupField : public MMGOBSField { + MMGOBSGroupField(QWidget *parent, const MMGOBSFieldInit &init); - MMGFields(Kind kind, QStackedWidget *parent, MMGAction *action); + void obs_json(QJsonObject &json_obj) const override; + void mmg_json(QJsonObject &json_obj) const override; + void apply(const QJsonObject &json_obj) override { Q_UNUSED(json_obj); }; + void update(obs_property_t *prop) override; - int get_index() const { return index; }; - obs_property_t *get_property(const QString &name) - { - return obs_properties_get(props, name.qtocs()); - }; - MMGAction *get_action() const { return action; }; - bool ensure_validity(MMGAction *test) const + MMGOBSBooleanField *boolean_field = nullptr; + bool boolean_field_state; + + ~MMGOBSGroupField() { - return action == test && action->str1() == source && action->str2() == filter; - }; + if (boolean_field) delete boolean_field; + } +}; - const QString json(); - void update(MMGField *from_field); +struct MMGOBSPathField : public MMGOBSButtonField { + MMGOBSPathField(QWidget *parent, const MMGOBSFieldInit &init); + + void callback() override; + void obs_json(QJsonObject &json_obj) const override { json_obj[name] = path.str(); }; + void mmg_json(QJsonObject &json_obj) const override { path.json(json_obj, name); }; + void apply(const QJsonObject &json_obj) override; + void update(obs_property_t *prop) override; - ~MMGFields() + MMGUtils::MMGString path; + + short dialog_type; + QString filters; + QString default_path; + + QLabel *path_display; +}; + +struct MMGOBSTextField : public MMGOBSField { + MMGOBSTextField(QWidget *parent, const MMGOBSFieldInit &init); + + virtual void callback(const QString &str); + void obs_json(QJsonObject &json_obj) const override { json_obj[name] = text.str(); }; + void mmg_json(QJsonObject &json_obj) const override { text.json(json_obj, name); }; + void apply(const QJsonObject &json_obj) override; + void update(obs_property_t *prop) override; + + bool checkMIDI() const; + + MMGUtils::MMGString text; + + QTextEdit *text_edit; + QLineEdit *line_edit; +}; + +class MMGOBSFields : public QWidget { + Q_OBJECT + + public: + MMGOBSFields(QWidget *parent, obs_source_t *source); + + bool match(obs_source_t *source) const { return _source == source; }; + MMGUtils::MMGString *jsonDestination() const { return json_des; }; + void setJsonDestination(MMGUtils::MMGString *json, bool force); + + obs_property_t *property(const QString &name) { return obs_properties_get(props, name.qtocs()); }; + void create(QWidget *parent, obs_properties_t *props, const MMGOBSFieldInit &json_init); + void add(QWidget *parent, MMGOBSField *field); + + ~MMGOBSFields() { - qDeleteAll(fields); + obs_source_release(_source); obs_properties_destroy(props); } - private: - QString source; - QString filter; + public slots: + void obs_json(); + void mmg_json(); - QWidget *widget; - int index; - MMGAction *action; + private: + obs_source_t *_source; + MMGUtils::MMGString *json_des = nullptr; obs_properties_t *props = nullptr; - QList fields; + MMGOBSFieldList fields; - void create_filter_fields(); + void apply(); }; diff --git a/src/ui/mmg-lcd-number.cpp b/src/ui/mmg-lcd-number.cpp new file mode 100644 index 0000000..adc0f60 --- /dev/null +++ b/src/ui/mmg-lcd-number.cpp @@ -0,0 +1,143 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#include "mmg-lcd-number.h" +#include "../mmg-utils.h" + +#include + +using namespace MMGUtils; + +MMGLCDNumber::MMGLCDNumber(QWidget *parent) : QWidget(parent) +{ + lcd = new QLCDNumber(this); + inc_button = new QToolButton(this); + dec_button = new QToolButton(this); + auto_repeat_timer = new QTimer(this); + repeat_delay_timer = new QTimer(this); + repeat_delay_timer->setSingleShot(true); + + connect(auto_repeat_timer, &QTimer::timeout, this, &MMGLCDNumber::autoRepeat); + connect(repeat_delay_timer, &QTimer::timeout, this, &MMGLCDNumber::initAutoRepeat); + + lcd->setDigitCount(8); + lcd->setSmallDecimalPoint(true); + QPalette p = parent->palette(); + p.setColor(QPalette::Dark, QColor("#ffffff")); + lcd->setPalette(p); + lcd->setSegmentStyle(QLCDNumber::Filled); + lcd->setFrameShape(QFrame::NoFrame); + lcd->setFrameShadow(QFrame::Raised); + lcd->setLineWidth(0); + lcd->setVisible(true); + lcd->setGeometry(0, 0, 160, 30); + + inc_button->setVisible(true); + inc_button->setGeometry(160, 0, 15, 15); + inc_button->setStyleSheet("padding: 0; border-radius: 0;"); + inc_button->setText("▴"); + connect(inc_button, &QAbstractButton::pressed, this, &MMGLCDNumber::delayAutoRepeatInc); + connect(inc_button, &QAbstractButton::released, this, &MMGLCDNumber::autoRepeatCancel); + + dec_button->setVisible(true); + dec_button->setGeometry(160, 15, 15, 15); + dec_button->setStyleSheet("padding: 0; border-radius: 0;"); + dec_button->setText("▾"); + connect(dec_button, &QAbstractButton::pressed, this, &MMGLCDNumber::delayAutoRepeatDec); + connect(dec_button, &QAbstractButton::released, this, &MMGLCDNumber::autoRepeatCancel); +} + +void MMGLCDNumber::delayAutoRepeatInc() +{ + timer_kind = true; + repeat_delay_timer->start(500); + inc(); +} + +void MMGLCDNumber::delayAutoRepeatDec() +{ + timer_kind = false; + repeat_delay_timer->start(500); + dec(); +} + +void MMGLCDNumber::autoRepeat() +{ + if (++timer_iterator == 20 && step_mult < 100) { + step_mult *= 10; + timer_iterator = 0; + } + timer_kind ? inc() : dec(); +} + +void MMGLCDNumber::autoRepeatCancel() +{ + repeat_delay_timer->stop(); + auto_repeat_timer->stop(); + step_mult = 1; + timer_iterator = 0; +} + +void MMGLCDNumber::setBounds(double lower, double upper) +{ + _min = lower; + _max = upper; +} + +void MMGLCDNumber::setStep(double step) +{ + _step = step; +} + +void MMGLCDNumber::setValue(double val) +{ + if (!modifiable) return; + if (val > _max) { + _value = _max; + } else if (val < _min) { + _value = _min; + } else if (_step < 1 && qAbs(val - qFloor(val)) > 0 && qAbs(val - qFloor(val)) < adj_step()) { + _value = qRound(val / _step) * _step; + } else { + _value = val; + } + emit numberChanged(_value); + display(LCDNUMBER_ACTIVE); +} + +void MMGLCDNumber::display(State state) +{ + switch (state) { + case LCDNUMBER_ACTIVE: + if (_time_format) { + lcd->display(QTime(_value / 3600.0, fmod(_value / 60.0, 60.0), fmod(_value, 60.0)) + .toString("hh:mm:ss")); + } else { + lcd->display(_value); + } + break; + case LCDNUMBER_0_127: + lcd->display(" 0-127 "); + break; + case LCDNUMBER_INACTIVE: + lcd->display(" ----- "); + break; + default: + break; + } +} diff --git a/src/ui/mmg-lcd-number.h b/src/ui/mmg-lcd-number.h new file mode 100644 index 0000000..e7d4e22 --- /dev/null +++ b/src/ui/mmg-lcd-number.h @@ -0,0 +1,80 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#pragma once +#include +#include +#include + +class MMGLCDNumber : public QWidget { + Q_OBJECT + + public: + MMGLCDNumber(QWidget *parent = nullptr); + ~MMGLCDNumber() = default; + + enum State { LCDNUMBER_ACTIVE, LCDNUMBER_0_127, LCDNUMBER_INACTIVE }; + + double value() const { return _value; }; + double min() const { return _min; }; + double max() const { return _max; }; + double step() const { return _step; }; + + void setModifiable(bool editable) { modifiable = editable; }; + void setBounds(double lower, double upper); + void setStep(double step); + void setTimeFormat(bool time_format) { _time_format = time_format; }; + + void setValue(double val); + void display(State state); + + signals: + void numberChanged(double); + + private: + QLCDNumber *lcd; + QToolButton *inc_button; + QToolButton *dec_button; + + double _value = 0.0; + bool modifiable = true; + + double _min = 0.0; + double _max = 100.0; + double _step = 1.0; + + QTimer *repeat_delay_timer; + QTimer *auto_repeat_timer; + short timer_iterator = 0; + bool timer_kind = false; + double step_mult = 1; + + bool _time_format = false; + + double adj_step() const { return _step * step_mult; }; + + private slots: + void inc() { setValue(_value + adj_step()); }; + void dec() { setValue(_value - adj_step()); }; + + void delayAutoRepeatInc(); + void delayAutoRepeatDec(); + void initAutoRepeat() { auto_repeat_timer->start(50); } + void autoRepeat(); + void autoRepeatCancel(); +}; diff --git a/src/ui/mmg-number-display.cpp b/src/ui/mmg-number-display.cpp new file mode 100644 index 0000000..6b7df8d --- /dev/null +++ b/src/ui/mmg-number-display.cpp @@ -0,0 +1,203 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#include "mmg-number-display.h" + +#include + +using namespace MMGUtils; + +// MMGNumberDisplay +MMGNumberDisplay::MMGNumberDisplay(QWidget *parent) : QWidget(parent) +{ + lcd_number = new MMGLCDNumber(this); + lcd_min = new MMGLCDNumber(this); + lcd_max = new MMGLCDNumber(this); + + setVisible(true); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + lcd_number->setVisible(true); + lcd_min->setVisible(false); + lcd_max->setVisible(false); + + connect(lcd_number, &MMGLCDNumber::numberChanged, this, [&](double val) { + number->set_num(val); + emit numberChanged(); + }); + connect(lcd_min, &MMGLCDNumber::numberChanged, this, [&](double val) { + number->set_min(val); + emit numberChanged(); + }); + connect(lcd_max, &MMGLCDNumber::numberChanged, this, [&](double val) { + number->set_max(val); + emit numberChanged(); + }); + + label = new QLabel(this); + label->setVisible(true); + label->setWordWrap(true); + label->setGeometry(0, 0, 95, 30); + + lcd_min->setToolTip("Lower Bound (corresponds to MIDI value of 0)"); + lcd_min->setGeometry(95, 0, 175, 30); + lcd_max->setToolTip("Upper Bound (corresponds to MIDI value of 127)"); + lcd_max->setGeometry(95, 40, 175, 30); + + combo = new QComboBox(this); + combo->setGeometry(0, 30, 95, 40); + combo->addItems({"Fixed", "0-127", "Custom", "Ignore"}); + combo->connect(combo, &QComboBox::currentIndexChanged, this, &MMGNumberDisplay::setLCDMode); + + setDisplayMode(MODE_DEFAULT); +} + +void MMGNumberDisplay::setStorage(MMGNumber *storage, bool force_values) +{ + number = storage; + if (force_values) { + setBounds(number->min(), number->max()); + } else { + number->set_min(lcd_min->value()); + number->set_max(lcd_max->value()); + } + display(); +} + +void MMGNumberDisplay::setDescription(const QString &desc) +{ + label->setText(desc); +} + +void MMGNumberDisplay::setBounds(double lower, double upper, bool extend_bounds) +{ + double range = upper - lower; + if (extend_bounds) { + lcd_number->setBounds(lower - range, upper + range); + lcd_min->setBounds(lower - range, upper + range); + lcd_max->setBounds(lower - range, upper + range); + } else { + lcd_number->setBounds(lower, upper); + lcd_min->setBounds(lower, upper); + lcd_max->setBounds(lower, upper); + } + if (number) { + number->set_min(lower); + number->set_max(upper); + display(); + } + default_bounds_min = lower; + default_bounds_max = upper; +} + +void MMGNumberDisplay::setOptions(ComboOptions options) +{ + QStandardItemModel *model = qobject_cast(combo->model()); + model->item(1)->setEnabled(options != 0 && options != 3); + model->item(2)->setEnabled(options == 2 || options == 4); + model->item(3)->setEnabled(options > 2); + if (options == 0 || options == 3) setLCDMode(0); +} + +void MMGNumberDisplay::setStep(double inc) +{ + lcd_number->setStep(inc < 0.01 ? 0.01 : inc); + lcd_min->setStep(inc < 0.01 ? 0.01 : inc); + lcd_max->setStep(inc < 0.01 ? 0.01 : inc); +} + +void MMGNumberDisplay::setTimeFormat(bool format) +{ + lcd_number->setTimeFormat(format); + lcd_min->setTimeFormat(format); + lcd_max->setTimeFormat(format); +} + +void MMGNumberDisplay::setLCDMode(int mode) +{ + if (mode > 3) return; + if (combo->currentIndex() != mode) combo->setCurrentIndex(mode); + + lcd_number->setVisible(mode != 2); + lcd_number->setEnabled(mode == 0); + lcd_number->display((MMGLCDNumber::State)(mode == 3 ? 2 : mode)); + lcd_min->setVisible(mode == 2); + lcd_max->setVisible(mode == 2); + if (number) number->set_state((MMGNumber::State)mode); + if (mode == 1) { + // numberChanged will be called twice here + lcd_min->blockSignals(true); + lcd_max->blockSignals(true); + lcd_min->setValue(default_bounds_min); + lcd_max->setValue(default_bounds_max); + lcd_min->blockSignals(false); + lcd_max->blockSignals(false); + } + emit numberChanged(); +} + +void MMGNumberDisplay::setDisplayMode(Mode mode) +{ + switch (mode) { + case MODE_DEFAULT: + setFixedSize(270, 70); + lcd_number->setGeometry(95, 20, 175, 30); + combo->setVisible(true); + break; + case MODE_THIN: + setFixedSize(270, 30); + lcd_number->setGeometry(95, 0, 175, 30); + setLCDMode(0); + combo->setVisible(false); + break; + } +} + +void MMGNumberDisplay::display() +{ + lcd_number->setValue(number->num()); + lcd_min->setValue(number->min()); + lcd_max->setValue(number->max()); + combo->setCurrentIndex(number->state()); + setLCDMode(number->state()); +} + +void MMGNumberDisplay::reset() +{ + if (number) *number = default_val; + display(); +} +// End MMGNumberDisplay + +// MMGNumberDisplayFields +MMGNumberDisplayFields::MMGNumberDisplayFields(QWidget *parent) : QWidget(parent) +{ + setGeometry(0, 0, 290, 350); + QVBoxLayout *default_field_layout = new QVBoxLayout(this); + default_field_layout->setSpacing(10); + default_field_layout->setSizeConstraint(QLayout::SetFixedSize); + default_field_layout->setContentsMargins(10, 10, 10, 10); + setLayout(default_field_layout); +} + +void MMGNumberDisplayFields::add(MMGNumberDisplay *field) +{ + fields.append(field); + layout()->addWidget(field); +} +// End MMGNumberDisplayFields diff --git a/src/ui/mmg-number-display.h b/src/ui/mmg-number-display.h new file mode 100644 index 0000000..7a18fd8 --- /dev/null +++ b/src/ui/mmg-number-display.h @@ -0,0 +1,89 @@ +/* +obs-midi-mg +Copyright (C) 2022-2023 nhielost + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see +*/ + +#ifndef MMG_NUMBER_DISPLAY_H +#define MMG_NUMBER_DISPLAY_H + +#include "../mmg-utils.h" +#include "mmg-lcd-number.h" + +class MMGNumberDisplay : public QWidget { + Q_OBJECT + + public: + MMGNumberDisplay(QWidget *parent); + + enum ComboOptions : int { + OPTIONS_FIXED_ONLY, + OPTIONS_MIDI_DEFAULT, + OPTIONS_MIDI_CUSTOM, + OPTIONS_IGNORE, + OPTIONS_ALL + }; + enum Mode { + MODE_DEFAULT, + MODE_THIN, + }; + + int state() const { return combo->currentIndex() == 2 ? 1 : combo->currentIndex(); }; + + void setStorage(MMGUtils::MMGNumber *storage, bool force_values = false); + void setDescription(const QString &desc); + void setOptions(ComboOptions options); + void setBounds(double lower, double upper, bool extend_bounds = false); + void setStep(double inc); + void setDefaultValue(double val) { default_val = val; }; + void setTimeFormat(bool format); + void setDisplayMode(Mode mode); + void display(); + void reset(); + + signals: + void numberChanged(); + + public slots: + void setLCDMode(int labels); + + protected: + MMGUtils::MMGNumber *number = nullptr; + + double default_val = 0.0; + double default_bounds_min = 0.0; + double default_bounds_max = 100.0; + + QLabel *label; + MMGLCDNumber *lcd_number; + MMGLCDNumber *lcd_min; + MMGLCDNumber *lcd_max; + QComboBox *combo; +}; + +class MMGNumberDisplayFields : public QWidget { + Q_OBJECT + + public: + MMGNumberDisplayFields(QWidget *parent); + + void add(MMGNumberDisplay *field); + MMGNumberDisplay *fieldAt(int index) { return fields.at(index); }; + + private: + QList fields; +}; + +#endif // MMG_NUMBER_DISPLAY_H