mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2025-04-04 12:43:47 +01:00
Compare commits
73 Commits
Author | SHA1 | Date | |
---|---|---|---|
293bccc5ba | |||
1d8ffdc756 | |||
e4d87334cb | |||
ad3020809e | |||
76c6630892 | |||
cc46fc31f8 | |||
8657e8846a | |||
43aad156a0 | |||
5101ff01f2 | |||
c437ae5843 | |||
ae59ba30f9 | |||
a3fa227ac1 | |||
b1b6c66828 | |||
cb00de36f0 | |||
ae200068d0 | |||
b720494c68 | |||
6e6308a17f | |||
848248d02b | |||
e4fc68c1ad | |||
752d1d7dd9 | |||
70c225bda3 | |||
f459cdee44 | |||
198c08003e | |||
6fa9bf7131 | |||
c3f20ea43f | |||
2e868705d5 | |||
df6f215514 | |||
|
0a5987631f | ||
3d3bc71d15 | |||
7b148b2614 | |||
ef558fdde6 | |||
c684ed9981 | |||
1498daf36f | |||
e964c94d07 | |||
9f20225a59 | |||
f63c36c94a | |||
c2db0f2757 | |||
bfb0482c32 | |||
6222ab1e62 | |||
0ad40ab708 | |||
b809bcb28f | |||
a0b9a92a2a | |||
9faf8ae10c | |||
82cf0e914b | |||
3e68488231 | |||
6d46d9a9a5 | |||
e4068277f7 | |||
674999a461 | |||
e5975f0772 | |||
59d2a95ec4 | |||
4bae1e1d15 | |||
1e3751b19f | |||
2ec1c74b7d | |||
0a19e28370 | |||
5ab1fd7102 | |||
d896193ade | |||
8c0c31dc0d | |||
c8b3e9fc33 | |||
d4358bf7d3 | |||
194b95d67b | |||
0f734e87b7 | |||
944ef9ca1c | |||
fc20bb0c1e | |||
5ccc2a6dab | |||
cfc1279f6c | |||
d4df11f62d | |||
|
3b6e1f61a4 | ||
|
3f306ddf62 | ||
|
732368a65b | ||
|
d7df79b798 | ||
|
0906f3343b | ||
|
294dfe7d03 | ||
|
4db7be172b |
6
.gitignore
vendored
6
.gitignore
vendored
@ -131,3 +131,9 @@ dmypy.json
|
|||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
|
# build
|
||||||
|
theme/
|
||||||
|
spec/
|
||||||
|
|
||||||
|
.vscode/
|
67
CHANGELOG.md
67
CHANGELOG.md
@ -7,7 +7,72 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
- [ ] Add support for forest theme (should be coming soon)
|
- [ ]
|
||||||
|
|
||||||
|
## [1.9.8] - 2025-01-22
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- vm-compact config dirs now override _internal/configs (if using build from releases). See [TOML Files](https://github.com/onyx-and-iris/voicemeeter-compact?tab=readme-ov-file#toml-files) section in README.
|
||||||
|
- after disconnecting from a vban connection, vban menus are re-enabled after 500ms.
|
||||||
|
|
||||||
|
## [1.9.5] - 2024-07-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Launching the Voicemeeter Compact app will now launch the x64 bit Voicemeeter GUI (on 64 bit systems) for all kinds.
|
||||||
|
|
||||||
|
## [1.9.0] - 2023-07-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Should the voicemeeter-compact app lose communication with Voicemeeter GUI a popup will show asking to restart the GUI.
|
||||||
|
- If yes is selected the app's mainframe will redraw, there will be a grace period before updates start again due to Voicemeeter engine startup.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- From the menu, Voicemeeter->Shutdown now closes both the compact app and the main Voicemeeter GUI.
|
||||||
|
|
||||||
|
## [1.8.0] - 2023-06-29
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Ability to toggle the navigation frame. This may also be set in app.toml, check example config.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- xpadding added to channel labelframes. This may also be configured through app.toml.
|
||||||
|
- During startup of the app there is now a twelve second grace period before parameter updates begin if the GUI was not previously launched. This is aimed at removing the stutter (due to VM engine startup) on initial launch. Be mindful of this if changing settings on the base Voicemeeter app. After the grace period all updates continue as normal.
|
||||||
|
|
||||||
|
- dependency updates:
|
||||||
|
- sv_ttk updated to v2.5.1.
|
||||||
|
- voicemeeter-api updated to v2.0.2.
|
||||||
|
|
||||||
|
## [1.7.0] - 2023-06-26
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- There are changes to how some parameters must be set in user toml configs.
|
||||||
|
- use `comp.knob` to set a strip comp slider.
|
||||||
|
- use `gate.knob` to set a strip gate slider.
|
||||||
|
- use `eq.on` to set a bus eq.on button.
|
||||||
|
- use `eq.ab` to set a bus eq.ab button.
|
||||||
|
|
||||||
|
Check example configs.
|
||||||
|
|
||||||
|
- `configs` directory may now be located in one of the following locations:
|
||||||
|
- \<current working directory>/configs/
|
||||||
|
- \<user home directory>/.configs/vm-compact/configs/
|
||||||
|
- \<user home directory>/Documents/Voicemeeter/configs/
|
||||||
|
|
||||||
|
- dependency updates:
|
||||||
|
- sv_ttk updated to v2.4.5.
|
||||||
|
- voicemeeter-api updated to v2.0.1.
|
||||||
|
- vban-cmd updated to v2.0.0.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- A number of changes that reduce the amount of api calls being made.
|
||||||
|
|
||||||
## [1.6.0] - 2022-09-29
|
## [1.6.0] - 2022-09-29
|
||||||
|
|
||||||
|
49
README.md
49
README.md
@ -1,6 +1,7 @@
|
|||||||
[](https://badge.fury.io/py/voicemeeter-compact)
|
[](https://badge.fury.io/py/voicemeeter-compact)
|
||||||
[](https://github.com/onyx-and-iris/voicemeeter-compact/blob/main/LICENSE)
|
[](https://github.com/onyx-and-iris/voicemeeter-compact/blob/main/LICENSE)
|
||||||
[](https://github.com/psf/black)
|
[](https://python-poetry.org/)
|
||||||
|
[](https://github.com/astral-sh/ruff)
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
@ -30,20 +31,21 @@ Example `__main__.py` file:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
import voicemeeterlib
|
import voicemeeterlib
|
||||||
|
|
||||||
import vmcompact
|
import vmcompact
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# pass the kind_id and the vm object to the app
|
# choose the kind of Voicemeeter (Local connection)
|
||||||
with voicemeeterlib.api(kind_id) as vm:
|
KIND_ID = 'banana'
|
||||||
app = vmcompact.connect(kind_id, vm)
|
|
||||||
|
# pass the KIND_ID and the vm object to the app
|
||||||
|
with voicemeeterlib.api(KIND_ID) as vm:
|
||||||
|
app = vmcompact.connect(KIND_ID, vm)
|
||||||
app.mainloop()
|
app.mainloop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
# choose the kind of Voicemeeter (Local connection)
|
|
||||||
kind_id = "banana"
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -53,9 +55,9 @@ It's important to know that only labelled strips and buses will appear in the Ch
|
|||||||
|
|
||||||
If the GUI looks like the above when you first load it, then no channels are labelled. From the menu, `Configs->Load config` you may load an example config. Save your current Voicemeeter settings first :).
|
If the GUI looks like the above when you first load it, then no channels are labelled. From the menu, `Configs->Load config` you may load an example config. Save your current Voicemeeter settings first :).
|
||||||
|
|
||||||
### kind_id
|
### KIND_ID
|
||||||
|
|
||||||
Set the kind of Voicemeeter, kind_id may be:
|
Set the kind of Voicemeeter, KIND_ID may be:
|
||||||
|
|
||||||
- `basic`
|
- `basic`
|
||||||
- `banana`
|
- `banana`
|
||||||
@ -63,15 +65,18 @@ Set the kind of Voicemeeter, kind_id may be:
|
|||||||
|
|
||||||
## TOML Files
|
## TOML Files
|
||||||
|
|
||||||
This is how your files should be organised. Wherever your `__main__.py` file is located (after install this can be any location), `configs` should be in the same location.
|
If you've downloaded the binary from [Releases][releases] you can find configs included in the `_internal/configs` directory.
|
||||||
Directly inside of configs directory you may place an app.toml, vban.toml and a directory for each kind.
|
|
||||||
Inside each kind directory you may place as many custom toml configurations as you wish.
|
You may override these configs by placing a directory `vm-compact` in one of the following locations:
|
||||||
|
|
||||||
|
- `user home directory / .config`
|
||||||
|
- `user home directory / Documents / Voicemeeter`
|
||||||
|
|
||||||
|
The contents should match the following directory structure:
|
||||||
|
|
||||||
.
|
.
|
||||||
|
|
||||||
├── `__main__.py`
|
├── vm-compact
|
||||||
|
|
||||||
├── configs
|
|
||||||
|
|
||||||
├── app.toml
|
├── app.toml
|
||||||
|
|
||||||
@ -109,7 +114,7 @@ Configure certain startup states for the app.
|
|||||||
Configure a user config to load on app startup. Don't include the .toml extension in the config name.
|
Configure a user config to load on app startup. Don't include the .toml extension in the config name.
|
||||||
|
|
||||||
- `theme`
|
- `theme`
|
||||||
By default the app loads up the [Sun Valley light or dark theme](https://github.com/rdbende/Sun-Valley-ttk-theme) by @rdbende. You have the option to load up the app without any theme loaded. Simply set `enabled` to false and `mode` will take no effect.
|
By default the app loads up the [Sun Valley light or dark theme][releases] by @rdbende. You have the option to load up the app without any theme loaded. Simply set `enabled` to false and `mode` will take no effect.
|
||||||
|
|
||||||
- `extends`
|
- `extends`
|
||||||
Extending the app will show both strips and buses. In reduced mode only one or the other. This app will extend both horizontally and vertically, simply set `extends_horizontal` true or false accordingly.
|
Extending the app will show both strips and buses. In reduced mode only one or the other. This app will extend both horizontally and vertically, simply set `extends_horizontal` true or false accordingly.
|
||||||
@ -149,7 +154,7 @@ port = 6990
|
|||||||
|
|
||||||
Three example user configs are included with the package, one for each kind of Voicemeeter. Use these to configure parameter startup states. Any parameter supported by the underlying interfaces may be used. Check the 'multiple-parameters' section for more info:
|
Three example user configs are included with the package, one for each kind of Voicemeeter. Use these to configure parameter startup states. Any parameter supported by the underlying interfaces may be used. Check the 'multiple-parameters' section for more info:
|
||||||
|
|
||||||
[Python Interface for Voicemeeter API](https://github.com/onyx-and-iris/voicemeeter-api-python#multiple-parameters)
|
[Python Interface for the Voicemeeter API](https://github.com/onyx-and-iris/voicemeeter-api-python#multiple-parameters)
|
||||||
|
|
||||||
[Python Interface for VBAN CMD](https://github.com/onyx-and-iris/vban-cmd-python#multiple-parameters)
|
[Python Interface for VBAN CMD](https://github.com/onyx-and-iris/vban-cmd-python#multiple-parameters)
|
||||||
|
|
||||||
@ -157,6 +162,10 @@ User configs may be loaded at any time via the menu.
|
|||||||
|
|
||||||
## Special Thanks
|
## Special Thanks
|
||||||
|
|
||||||
[Vincent Burel](https://github.com/vburel2018) for creating Voicemeeter, its SDK, the C Remote API, the RT Packet service and Streamer View app!
|
[Vincent Burel](https://github.com/vburel2018) for creating Voicemeeter and its SDK.
|
||||||
|
|
||||||
[Rdbende](https://github.com/rdbende) for creating the beautiful Sun Valley Tkinter theme and adding it to Pypi!
|
[Rdbende](https://github.com/rdbende) for creating the beautiful [Sun Valley theme][sv-theme].
|
||||||
|
|
||||||
|
|
||||||
|
[sv-theme]: https://github.com/rdbende/Sun-Valley-ttk-theme
|
||||||
|
[releases]: https://github.com/onyx-and-iris/voicemeeter-compact/releases
|
81
Taskfile.yml
Normal file
81
Taskfile.yml
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
vars:
|
||||||
|
SHELL: pwsh
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
default:
|
||||||
|
desc: Prepare artifacts for release
|
||||||
|
cmds:
|
||||||
|
- task: release
|
||||||
|
|
||||||
|
release:
|
||||||
|
desc: Build and compress all artifacts
|
||||||
|
cmds:
|
||||||
|
- task: build
|
||||||
|
- task: compress
|
||||||
|
- echo "Release complete"
|
||||||
|
|
||||||
|
build:
|
||||||
|
desc: Build all artifacts
|
||||||
|
cmds:
|
||||||
|
- task: build-sunvalley
|
||||||
|
- echo "Sunvalley build complete"
|
||||||
|
- task: build-forest
|
||||||
|
- echo "Forest build complete"
|
||||||
|
|
||||||
|
build-sunvalley:
|
||||||
|
desc: Build Sunvalley artifacts
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
KIND: [basic, banana, potato]
|
||||||
|
cmd: poetry run pyinstaller --noconfirm --distpath dist/sunvalley-{{.ITEM.KIND}} spec/sunvalley-{{.ITEM.KIND}}.spec
|
||||||
|
|
||||||
|
build-forest:
|
||||||
|
desc: Build Forest artifacts
|
||||||
|
deps: [rewrite]
|
||||||
|
cmds:
|
||||||
|
- defer: { task: restore }
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
KIND: [basic, banana, potato]
|
||||||
|
THEME: [light, dark]
|
||||||
|
cmd: poetry run pyinstaller --noconfirm --distpath dist/forest-{{.ITEM.KIND}}-{{.ITEM.THEME}} spec/forest-{{.ITEM.KIND}}-{{.ITEM.THEME}}.spec
|
||||||
|
|
||||||
|
rewrite:
|
||||||
|
internal: true
|
||||||
|
desc: Run the source code rewriter
|
||||||
|
cmds:
|
||||||
|
- poetry run python tools/rewriter.py --rewrite
|
||||||
|
|
||||||
|
restore:
|
||||||
|
internal: true
|
||||||
|
desc: Restore the backup files
|
||||||
|
cmds:
|
||||||
|
- poetry run python tools/rewriter.py --restore
|
||||||
|
|
||||||
|
compress:
|
||||||
|
deps: [compress-sunvalley, compress-forest]
|
||||||
|
|
||||||
|
compress-sunvalley:
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
KIND: [basic, banana, potato]
|
||||||
|
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/sunvalley-{{.ITEM.KIND}} -DestinationPath dist/sunvalley-{{.ITEM.KIND}}.zip -Force"'
|
||||||
|
|
||||||
|
compress-forest:
|
||||||
|
cmds:
|
||||||
|
- for:
|
||||||
|
matrix:
|
||||||
|
KIND: [basic, banana, potato]
|
||||||
|
THEME: [light, dark]
|
||||||
|
cmd: '{{.SHELL}} -Command "Compress-Archive -Path dist/forest-{{.ITEM.KIND}}-{{.ITEM.THEME}} -DestinationPath dist/forest-{{.ITEM.KIND}}-{{.ITEM.THEME}}.zip -Force"'
|
||||||
|
|
||||||
|
clean:
|
||||||
|
desc: Clean up build and dist directories
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
{{.SHELL}} -Command "
|
||||||
|
Remove-Item -Path build/forest-*,build/sunvalley-*,dist/forest-*,dist/sunvalley-* -Recurse -Force"
|
10
__main__.py
10
__main__.py
@ -4,12 +4,12 @@ import vmcompact
|
|||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with voicemeeterlib.api(kind_id) as vmr:
|
KIND_ID = 'banana'
|
||||||
app = vmcompact.connect(kind_id, vmr)
|
|
||||||
|
with voicemeeterlib.api(KIND_ID) as vmr:
|
||||||
|
app = vmcompact.connect(KIND_ID, vmr)
|
||||||
app.mainloop()
|
app.mainloop()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == '__main__':
|
||||||
kind_id = "banana"
|
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
@ -3,19 +3,23 @@
|
|||||||
# config="example"
|
# config="example"
|
||||||
# load with themes enabled? set the default mode
|
# load with themes enabled? set the default mode
|
||||||
[theme]
|
[theme]
|
||||||
enabled=true
|
enabled = true
|
||||||
mode="light"
|
mode = "light"
|
||||||
# load in extended mode? if so which orientation
|
# load in extended mode? if so which orientation
|
||||||
[extends]
|
[extends]
|
||||||
extended=true
|
extended = true
|
||||||
extends_horizontal=true
|
extends_horizontal = true
|
||||||
# default dimensions for channel label frames
|
# default dimensions for channel label frames
|
||||||
[channel]
|
[channel]
|
||||||
width=80
|
width = 80
|
||||||
height=130
|
height = 130
|
||||||
|
xpadding = 2
|
||||||
# size of a single mouse wheel scroll step
|
# size of a single mouse wheel scroll step
|
||||||
[mwscroll_step]
|
[mwscroll_step]
|
||||||
size=3
|
size = 3
|
||||||
# default submix bus
|
# default submix bus
|
||||||
[submixes]
|
[submixes]
|
||||||
default=0
|
default = 0
|
||||||
|
# show the navigation frame?
|
||||||
|
[navigation]
|
||||||
|
show = true
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
label = "PhysStrip0"
|
label = "PhysStrip0"
|
||||||
A1 = true
|
A1 = true
|
||||||
gain = -8.8
|
gain = -8.8
|
||||||
comp = 3.2
|
comp.knob = 3.2
|
||||||
|
|
||||||
[strip-1]
|
[strip-1]
|
||||||
label = "PhysStrip1"
|
label = "PhysStrip1"
|
||||||
B1 = true
|
B1 = true
|
||||||
gate = 4.1
|
gate.knob = 4.1
|
||||||
|
|
||||||
[strip-2]
|
[strip-2]
|
||||||
label = "PhysStrip2"
|
label = "PhysStrip2"
|
||||||
@ -34,12 +34,12 @@ mono = true
|
|||||||
|
|
||||||
[bus-2]
|
[bus-2]
|
||||||
label = "PhysBus2"
|
label = "PhysBus2"
|
||||||
eq = true
|
eq.on = true
|
||||||
mode = "composite"
|
mode = "composite"
|
||||||
|
|
||||||
[bus-3]
|
[bus-3]
|
||||||
label = "VirtBus0"
|
label = "VirtBus0"
|
||||||
eq_ab = true
|
eq.ab = true
|
||||||
mode = "upmix61"
|
mode = "upmix61"
|
||||||
|
|
||||||
[bus-4]
|
[bus-4]
|
||||||
|
@ -2,12 +2,12 @@
|
|||||||
label = "PhysStrip0"
|
label = "PhysStrip0"
|
||||||
A1 = true
|
A1 = true
|
||||||
gain = -8.8
|
gain = -8.8
|
||||||
comp = 3.2
|
comp.knob = 3.2
|
||||||
|
|
||||||
[strip-1]
|
[strip-1]
|
||||||
label = "PhysStrip1"
|
label = "PhysStrip1"
|
||||||
B1 = true
|
B1 = true
|
||||||
gate = 4.1
|
gate.knob = 4.1
|
||||||
|
|
||||||
[strip-2]
|
[strip-2]
|
||||||
label = "PhysStrip2"
|
label = "PhysStrip2"
|
||||||
@ -50,7 +50,7 @@ mono = true
|
|||||||
|
|
||||||
[bus-2]
|
[bus-2]
|
||||||
label = "PhysBus2"
|
label = "PhysBus2"
|
||||||
eq = true
|
eq.on = true
|
||||||
|
|
||||||
[bus-3]
|
[bus-3]
|
||||||
label = "PhysBus3"
|
label = "PhysBus3"
|
||||||
@ -62,7 +62,7 @@ mode = "composite"
|
|||||||
|
|
||||||
[bus-5]
|
[bus-5]
|
||||||
label = "VirtBus0"
|
label = "VirtBus0"
|
||||||
eq_ab = true
|
eq.ab = true
|
||||||
|
|
||||||
[bus-6]
|
[bus-6]
|
||||||
label = "VirtBus1"
|
label = "VirtBus1"
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
# kind = 'banana'
|
# kind = 'banana'
|
||||||
# ip = '<ip address 1>'
|
# ip = '<ip address 1>'
|
||||||
# streamname = 'Command1'
|
# streamname = 'Command1'
|
||||||
# port = 6990
|
# port = 6980
|
||||||
|
|
||||||
# [connection-2]
|
# [connection-2]
|
||||||
# kind = 'potato'
|
# kind = 'potato'
|
||||||
# ip = '<ip address 2>'
|
# ip = '<ip address 2>'
|
||||||
# streamname = 'Command1'
|
# streamname = 'Command1'
|
||||||
# port = 6990
|
# port = 6980
|
||||||
|
305
poetry.lock
generated
305
poetry.lock
generated
@ -1,122 +1,257 @@
|
|||||||
[[package]]
|
# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
|
||||||
name = "black"
|
|
||||||
version = "22.8.0"
|
|
||||||
description = "The uncompromising code formatter."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6.2"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
click = ">=8.0.0"
|
|
||||||
mypy-extensions = ">=0.4.3"
|
|
||||||
pathspec = ">=0.9.0"
|
|
||||||
platformdirs = ">=2"
|
|
||||||
tomli = {version = ">=1.1.0", markers = "python_full_version < \"3.11.0a7\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
colorama = ["colorama (>=0.4.3)"]
|
|
||||||
d = ["aiohttp (>=3.7.4)"]
|
|
||||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "altgraph"
|
||||||
version = "8.1.3"
|
version = "0.17.4"
|
||||||
description = "Composable command line interface toolkit"
|
description = "Python graph (network) package"
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorama"
|
|
||||||
version = "0.4.5"
|
|
||||||
description = "Cross-platform colored terminal text."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mypy-extensions"
|
|
||||||
version = "0.4.3"
|
|
||||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
|
||||||
category = "dev"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
|
groups = ["build"]
|
||||||
|
files = [
|
||||||
|
{file = "altgraph-0.17.4-py2.py3-none-any.whl", hash = "sha256:642743b4750de17e655e6711601b077bc6598dbfa3ba5fa2b2a35ce12b508dff"},
|
||||||
|
{file = "altgraph-0.17.4.tar.gz", hash = "sha256:1b5afbb98f6c4dcadb2e2ae6ab9fa994bbb8c1d75f4fa96d340f9437ae454406"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathspec"
|
name = "macholib"
|
||||||
version = "0.10.1"
|
version = "1.16.3"
|
||||||
description = "Utility library for gitignore style pattern matching of file paths."
|
description = "Mach-O header analysis and editing"
|
||||||
category = "dev"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = "*"
|
||||||
|
groups = ["build"]
|
||||||
|
markers = "sys_platform == \"darwin\""
|
||||||
|
files = [
|
||||||
|
{file = "macholib-1.16.3-py2.py3-none-any.whl", hash = "sha256:0e315d7583d38b8c77e815b1ecbdbf504a8258d8b3e17b61165c6feb60d18f2c"},
|
||||||
|
{file = "macholib-1.16.3.tar.gz", hash = "sha256:07ae9e15e8e4cd9a788013d81f5908b3609aa76f9b1421bae9c4d7606ec86a30"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
altgraph = ">=0.17"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "packaging"
|
||||||
version = "2.5.2"
|
version = "24.2"
|
||||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
description = "Core utilities for Python packages"
|
||||||
category = "dev"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["build"]
|
||||||
|
files = [
|
||||||
|
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||||
|
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pefile"
|
||||||
|
version = "2023.2.7"
|
||||||
|
description = "Python PE parsing module"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6.0"
|
||||||
|
groups = ["build"]
|
||||||
|
markers = "sys_platform == \"win32\""
|
||||||
|
files = [
|
||||||
|
{file = "pefile-2023.2.7-py3-none-any.whl", hash = "sha256:da185cd2af68c08a6cd4481f7325ed600a88f6a813bad9dea07ab3ef73d8d8d6"},
|
||||||
|
{file = "pefile-2023.2.7.tar.gz", hash = "sha256:82e6114004b3d6911c77c3953e3838654b04511b8b66e8583db70c65998017dc"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyinstaller"
|
||||||
|
version = "6.11.1"
|
||||||
|
description = "PyInstaller bundles a Python application and all its dependencies into a single package."
|
||||||
|
optional = false
|
||||||
|
python-versions = "<3.14,>=3.8"
|
||||||
|
groups = ["build"]
|
||||||
|
files = [
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-macosx_10_13_universal2.whl", hash = "sha256:44e36172de326af6d4e7663b12f71dbd34e2e3e02233e181e457394423daaf03"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6d12c45a29add78039066a53fb05967afaa09a672426072b13816fe7676abfc4"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_i686.whl", hash = "sha256:ddc0fddd75f07f7e423da1f0822e389a42af011f9589e0269b87e0d89aa48c1f"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:0d6475559c4939f0735122989611d7f739ed3bf02f666ce31022928f7a7e4fda"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:e21c7806e34f40181e7606926a14579f848bfb1dc52cbca7eea66eccccbfe977"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32c742a24fe65d0702958fadf4040f76de85859c26bec0008766e5dbabc5b68f"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:208c0ef6dab0837a0a273ea32d1a3619a208e3d1fe3fec3785eea71a77fd00ce"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:ad84abf465bcda363c1d54eafa76745d77b6a8a713778348377dc98d12a452f7"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-win32.whl", hash = "sha256:2e8365276c5131c9bef98e358fbc305e4022db8bedc9df479629d6414021956a"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-win_amd64.whl", hash = "sha256:7ac83c0dc0e04357dab98c487e74ad2adb30e7eb186b58157a8faf46f1fa796f"},
|
||||||
|
{file = "pyinstaller-6.11.1-py3-none-win_arm64.whl", hash = "sha256:35e6b8077d240600bb309ed68bb0b1453fd2b7ab740b66d000db7abae6244423"},
|
||||||
|
{file = "pyinstaller-6.11.1.tar.gz", hash = "sha256:491dfb4d9d5d1d9650d9507daec1ff6829527a254d8e396badd60a0affcb72ef"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
altgraph = "*"
|
||||||
|
macholib = {version = ">=1.8", markers = "sys_platform == \"darwin\""}
|
||||||
|
packaging = ">=22.0"
|
||||||
|
pefile = {version = ">=2022.5.30,<2024.8.26 || >2024.8.26", markers = "sys_platform == \"win32\""}
|
||||||
|
pyinstaller-hooks-contrib = ">=2024.9"
|
||||||
|
pywin32-ctypes = {version = ">=0.2.1", markers = "sys_platform == \"win32\""}
|
||||||
|
setuptools = ">=42.0.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
completion = ["argcomplete"]
|
||||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
hook-testing = ["execnet (>=1.5.0)", "psutil", "pytest (>=2.7.3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyinstaller-hooks-contrib"
|
||||||
|
version = "2024.11"
|
||||||
|
description = "Community maintained hooks for PyInstaller"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["build"]
|
||||||
|
files = [
|
||||||
|
{file = "pyinstaller_hooks_contrib-2024.11-py3-none-any.whl", hash = "sha256:2781d121a1ee961152ba7287a262c65a1078da30c9ef7621cb8c819326884fd5"},
|
||||||
|
{file = "pyinstaller_hooks_contrib-2024.11.tar.gz", hash = "sha256:84399af6b4b902030958063df25f657abbff249d0f329c5344928355c9833ab4"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
packaging = ">=22.0"
|
||||||
|
setuptools = ">=42.0.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pywin32-ctypes"
|
||||||
|
version = "0.2.3"
|
||||||
|
description = "A (partial) reimplementation of pywin32 using ctypes/cffi"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6"
|
||||||
|
groups = ["build"]
|
||||||
|
markers = "sys_platform == \"win32\""
|
||||||
|
files = [
|
||||||
|
{file = "pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755"},
|
||||||
|
{file = "pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruff"
|
||||||
|
version = "0.9.1"
|
||||||
|
description = "An extremely fast Python linter and code formatter, written in Rust."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["dev"]
|
||||||
|
files = [
|
||||||
|
{file = "ruff-0.9.1-py3-none-linux_armv6l.whl", hash = "sha256:84330dda7abcc270e6055551aca93fdde1b0685fc4fd358f26410f9349cf1743"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3cae39ba5d137054b0e5b472aee3b78a7c884e61591b100aeb544bcd1fc38d4f"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:50c647ff96f4ba288db0ad87048257753733763b409b2faf2ea78b45c8bb7fcb"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0c8b149e9c7353cace7d698e1656ffcf1e36e50f8ea3b5d5f7f87ff9986a7ca"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:beb3298604540c884d8b282fe7625651378e1986c25df51dec5b2f60cafc31ce"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39d0174ccc45c439093971cc06ed3ac4dc545f5e8bdacf9f067adf879544d969"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:69572926c0f0c9912288915214ca9b2809525ea263603370b9e00bed2ba56dbd"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:937267afce0c9170d6d29f01fcd1f4378172dec6760a9f4dface48cdabf9610a"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:186c2313de946f2c22bdf5954b8dd083e124bcfb685732cfb0beae0c47233d9b"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f94942a3bb767675d9a051867c036655fe9f6c8a491539156a6f7e6b5f31831"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:728d791b769cc28c05f12c280f99e8896932e9833fef1dd8756a6af2261fd1ab"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2f312c86fb40c5c02b44a29a750ee3b21002bd813b5233facdaf63a51d9a85e1"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ae017c3a29bee341ba584f3823f805abbe5fe9cd97f87ed07ecbf533c4c88366"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5dc40a378a0e21b4cfe2b8a0f1812a6572fc7b230ef12cd9fac9161aa91d807f"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-win32.whl", hash = "sha256:46ebf5cc106cf7e7378ca3c28ce4293b61b449cd121b98699be727d40b79ba72"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-win_amd64.whl", hash = "sha256:342a824b46ddbcdddd3abfbb332fa7fcaac5488bf18073e841236aadf4ad5c19"},
|
||||||
|
{file = "ruff-0.9.1-py3-none-win_arm64.whl", hash = "sha256:1cd76c7f9c679e6e8f2af8f778367dca82b95009bc7b1a85a47f1521ae524fa7"},
|
||||||
|
{file = "ruff-0.9.1.tar.gz", hash = "sha256:fd2b25ecaf907d6458fa842675382c8597b3c746a2dde6717fe3415425df0c17"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "setuptools"
|
||||||
|
version = "75.8.0"
|
||||||
|
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.9"
|
||||||
|
groups = ["build"]
|
||||||
|
files = [
|
||||||
|
{file = "setuptools-75.8.0-py3-none-any.whl", hash = "sha256:e3982f444617239225d675215d51f6ba05f845d4eec313da4418fdbb56fb27e3"},
|
||||||
|
{file = "setuptools-75.8.0.tar.gz", hash = "sha256:c5afc8f407c626b8313a86e10311dd3f661c6cd9c09d4bf8c15c0e11f9f2b0e6"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)", "ruff (>=0.8.0)"]
|
||||||
|
core = ["importlib_metadata (>=6)", "jaraco.collections", "jaraco.functools (>=4)", "jaraco.text (>=3.7)", "more_itertools", "more_itertools (>=8.8)", "packaging", "packaging (>=24.2)", "platformdirs (>=4.2.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"]
|
||||||
|
cover = ["pytest-cov"]
|
||||||
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
||||||
|
enabler = ["pytest-enabler (>=2.2)"]
|
||||||
|
test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.7.2)", "jaraco.test (>=5.5)", "packaging (>=24.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-home (>=0.5)", "pytest-perf", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel (>=0.44.0)"]
|
||||||
|
type = ["importlib_metadata (>=7.0.2)", "jaraco.develop (>=7.21)", "mypy (==1.14.*)", "pytest-mypy"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sv-ttk"
|
name = "sv-ttk"
|
||||||
version = "2.0"
|
version = "2.6.0"
|
||||||
description = "A gorgeous theme for Tkinter that looks like Windows 11"
|
description = "A gorgeous theme for Tkinter, based on Windows 11's UI"
|
||||||
category = "main"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.4"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "sv_ttk-2.6.0-py3-none-any.whl", hash = "sha256:4319c52edf2e14732fe84bdc9788e26f9e9a1ad79451ec0f89f0120ffc8105d9"},
|
||||||
|
{file = "sv_ttk-2.6.0.tar.gz", hash = "sha256:3fd440396c95e30e88f686fcf28be425480f7320d6bf346f9cea5d6f56702cc2"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.0.1"
|
version = "2.2.1"
|
||||||
description = "A lil' TOML parser"
|
description = "A lil' TOML parser"
|
||||||
category = "main"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version < \"3.11\""
|
||||||
|
files = [
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||||
|
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||||
|
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vban-cmd"
|
name = "vban-cmd"
|
||||||
version = "1.5.2"
|
version = "2.5.0"
|
||||||
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
||||||
category = "main"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = ">=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "vban_cmd-2.5.0-py3-none-any.whl", hash = "sha256:22a19037066487d464a61941a3b85a0331b498a9efb1bcacdc932e9d06c5bf87"},
|
||||||
|
{file = "vban_cmd-2.5.0.tar.gz", hash = "sha256:691a852e5052e50103839b06a0a9d0746b90df3346545c2cf4f10b099d9666e4"},
|
||||||
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=2.0.1,<3.0", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "voicemeeter-api"
|
name = "voicemeeter-api"
|
||||||
version = "0.8.1"
|
version = "2.6.1"
|
||||||
description = "A Python wrapper for the Voiceemeter API"
|
description = "A Python wrapper for the Voiceemeter API"
|
||||||
category = "main"
|
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = "<4.0,>=3.10"
|
||||||
|
groups = ["main"]
|
||||||
|
files = [
|
||||||
|
{file = "voicemeeter_api-2.6.1-py3-none-any.whl", hash = "sha256:8ae3bce0f9ad6bbad78f2f69f522b6fb2e229d314918a075ad83d4009aff7020"},
|
||||||
|
{file = "voicemeeter_api-2.6.1.tar.gz", hash = "sha256:34d8672603ec66197f2d61fd8f038f46d8451759c81fbe222b00e7d3ccccd1f5"},
|
||||||
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
tomli = {version = ">=2.0.1,<3.0", markers = "python_version < \"3.11\""}
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "2.1"
|
||||||
python-versions = "^3.10"
|
python-versions = ">=3.10,<3.14"
|
||||||
content-hash = "0fc1f7b08a87f389504c898142c5a0f2ed20c8f56deabb3028fa815774b7fc98"
|
content-hash = "19c384acd36868a5bfdc3f3173f444858136603694c3f1134c0d30cd17157651"
|
||||||
|
|
||||||
[metadata.files]
|
|
||||||
black = []
|
|
||||||
click = []
|
|
||||||
colorama = []
|
|
||||||
mypy-extensions = []
|
|
||||||
pathspec = []
|
|
||||||
platformdirs = []
|
|
||||||
sv-ttk = []
|
|
||||||
tomli = []
|
|
||||||
vban-cmd = []
|
|
||||||
voicemeeter-api = []
|
|
||||||
|
140
pyproject.toml
140
pyproject.toml
@ -1,27 +1,131 @@
|
|||||||
[tool.poetry]
|
[project]
|
||||||
name = "voicemeeter-compact"
|
name = "voicemeeter-compact"
|
||||||
version = "1.6.0"
|
version = "1.9.8"
|
||||||
description = "A Compact Voicemeeter Remote App"
|
description = "A Compact Voicemeeter Remote App"
|
||||||
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
authors = [
|
||||||
license = "MIT"
|
{name = "Onyx and Iris",email = "code@onyxandiris.online"}
|
||||||
readme = "README.md"
|
|
||||||
repository = "https://github.com/onyx-and-iris/voicemeeter-compact"
|
|
||||||
|
|
||||||
packages = [
|
|
||||||
{ include = "vmcompact" },
|
|
||||||
]
|
]
|
||||||
|
license = {text = "MIT"}
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.10,<3.14"
|
||||||
|
dependencies = [
|
||||||
|
"voicemeeter-api (>=2.6.1,<3.0.0)",
|
||||||
|
"vban-cmd (>=2.5.0,<3.0.0)",
|
||||||
|
"sv-ttk (>=2.6.0,<3.0.0)",
|
||||||
|
"tomli (>=2.0.1,<3.0) ; python_version < '3.11'",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
gui-basic-compact = "vmcompact.gui.basic:run"
|
||||||
|
gui-banana-compact = "vmcompact.gui.banana:run"
|
||||||
|
gui-potato-compact = "vmcompact.gui.potato:run"
|
||||||
|
|
||||||
|
[tool.poetry]
|
||||||
|
packages = [{ include = "vmcompact" }]
|
||||||
include = ["vmcompact/img/cat.ico"]
|
include = ["vmcompact/img/cat.ico"]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.requires-plugins]
|
||||||
python = "^3.10"
|
poethepoet = "^0.32.1"
|
||||||
sv-ttk = "^2.0"
|
|
||||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
|
||||||
voicemeeter-api = "^0.8.1"
|
|
||||||
vban-cmd = "^1.5.2"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = {version = "^22.6.0", allow-prereleases = true}
|
ruff = "^0.9.1"
|
||||||
|
|
||||||
|
[tool.poetry.group.build.dependencies]
|
||||||
|
pyinstaller = "^6.11.1"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.poe.tasks]
|
||||||
|
build-sunvalley = "task build-sunvalley"
|
||||||
|
build-forest = "task build-forest"
|
||||||
|
release = [
|
||||||
|
{ ref = "build-sunvalley" },
|
||||||
|
{ ref = "build-forest" },
|
||||||
|
{ cmd = "task compress-sunvalley" },
|
||||||
|
{ cmd = "task compress-forest" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
exclude = [
|
||||||
|
".bzr",
|
||||||
|
".direnv",
|
||||||
|
".eggs",
|
||||||
|
".git",
|
||||||
|
".git-rewrite",
|
||||||
|
".hg",
|
||||||
|
".mypy_cache",
|
||||||
|
".nox",
|
||||||
|
".pants.d",
|
||||||
|
".pytype",
|
||||||
|
".ruff_cache",
|
||||||
|
".svn",
|
||||||
|
".tox",
|
||||||
|
".venv",
|
||||||
|
"__pypackages__",
|
||||||
|
"_build",
|
||||||
|
"buck-out",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
"node_modules",
|
||||||
|
"venv",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Same as Black.
|
||||||
|
line-length = 88
|
||||||
|
indent-width = 4
|
||||||
|
|
||||||
|
# Assume Python 3.10
|
||||||
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
|
||||||
|
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
|
||||||
|
# McCabe complexity (`C901`) by default.
|
||||||
|
select = ["E4", "E7", "E9", "F"]
|
||||||
|
ignore = []
|
||||||
|
|
||||||
|
# Allow fix for all enabled rules (when `--fix`) is provided.
|
||||||
|
fixable = ["ALL"]
|
||||||
|
unfixable = []
|
||||||
|
|
||||||
|
# Allow unused variables when underscore-prefixed.
|
||||||
|
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
||||||
|
|
||||||
|
|
||||||
|
[tool.ruff.format]
|
||||||
|
# Unlike Black, use single quotes for strings.
|
||||||
|
quote-style = "single"
|
||||||
|
|
||||||
|
# Like Black, indent with spaces, rather than tabs.
|
||||||
|
indent-style = "space"
|
||||||
|
|
||||||
|
# Like Black, respect magic trailing commas.
|
||||||
|
skip-magic-trailing-comma = false
|
||||||
|
|
||||||
|
# Like Black, automatically detect the appropriate line ending.
|
||||||
|
line-ending = "auto"
|
||||||
|
|
||||||
|
# Enable auto-formatting of code examples in docstrings. Markdown,
|
||||||
|
# reStructuredText code/literal blocks and doctests are all supported.
|
||||||
|
#
|
||||||
|
# This is currently disabled by default, but it is planned for this
|
||||||
|
# to be opt-out in the future.
|
||||||
|
docstring-code-format = false
|
||||||
|
|
||||||
|
# Set the line length limit used when formatting code snippets in
|
||||||
|
# docstrings.
|
||||||
|
#
|
||||||
|
# This only has an effect when the `docstring-code-format` setting is
|
||||||
|
# enabled.
|
||||||
|
docstring-code-line-length = "dynamic"
|
||||||
|
|
||||||
|
[tool.ruff.lint.mccabe]
|
||||||
|
max-complexity = 10
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"__init__.py" = [
|
||||||
|
"E402",
|
||||||
|
"F401",
|
||||||
|
]
|
250
tools/rewriter.py
Normal file
250
tools/rewriter.py
Normal file
@ -0,0 +1,250 @@
|
|||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
|
||||||
|
logger = logging.getLogger('vm-compact-rewriter')
|
||||||
|
|
||||||
|
PACKAGE_DIR = Path(__file__).parent.parent / 'vmcompact'
|
||||||
|
|
||||||
|
SRC_DIR = Path(__file__).parent / 'src'
|
||||||
|
|
||||||
|
|
||||||
|
def write_outs(output, outs: tuple):
|
||||||
|
for out in outs:
|
||||||
|
output.write(out)
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_app():
|
||||||
|
app_logger = logger.getChild('app')
|
||||||
|
app_logger.info('rewriting app.py')
|
||||||
|
infile = Path(SRC_DIR) / 'app.bk'
|
||||||
|
outfile = Path(PACKAGE_DIR) / 'app.py'
|
||||||
|
with open(infile, 'r') as input:
|
||||||
|
with open(outfile, 'w') as output:
|
||||||
|
for line in input:
|
||||||
|
match line:
|
||||||
|
# App init()
|
||||||
|
case ' def __init__(self, vmr):\n':
|
||||||
|
output.write(' def __init__(self, vmr, theme):\n')
|
||||||
|
case ' self._vmr = vmr\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' self._vmr = vmr\n',
|
||||||
|
' self._theme = theme\n',
|
||||||
|
' tcldir = Path.cwd() / "theme"\n',
|
||||||
|
' if not tcldir.is_dir():\n',
|
||||||
|
' tcldir = Path.cwd() / "_internal" / "theme"\n',
|
||||||
|
' self.tk.call("source", tcldir.resolve() / f"forest-{self._theme}.tcl")\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# def connect()
|
||||||
|
case 'def connect(kind_id: str, vmr) -> App:\n':
|
||||||
|
output.write(
|
||||||
|
'def connect(kind_id: str, vmr, theme="light") -> App:\n'
|
||||||
|
)
|
||||||
|
case ' return VMMIN_cls(vmr)\n':
|
||||||
|
output.write(' return VMMIN_cls(vmr, theme)\n')
|
||||||
|
case _:
|
||||||
|
output.write(line)
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_builders():
|
||||||
|
builders_logger = logger.getChild('builders')
|
||||||
|
builders_logger.info('rewriting builders.py')
|
||||||
|
infile = Path(SRC_DIR) / 'builders.bk'
|
||||||
|
outfile = Path(PACKAGE_DIR) / 'builders.py'
|
||||||
|
with open(infile, 'r') as input:
|
||||||
|
with open(outfile, 'w') as output:
|
||||||
|
ignore_next_lines = 0
|
||||||
|
|
||||||
|
for line in input:
|
||||||
|
if ignore_next_lines > 0:
|
||||||
|
builders_logger.info(f'ignoring: {line}')
|
||||||
|
ignore_next_lines -= 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
match line:
|
||||||
|
# loading themes
|
||||||
|
case 'import sv_ttk\n':
|
||||||
|
output.write('#import sv_ttk\n')
|
||||||
|
case ' self.app.resizable(False, False)\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' self.app.resizable(False, False)\n'
|
||||||
|
' if _configuration.themes_enabled:\n',
|
||||||
|
' ttk.Style().theme_use(f"forest-{self.app._theme}")\n',
|
||||||
|
' self.logger.info(f"Forest Theme applied")\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ignore_next_lines = 6
|
||||||
|
# setting navframe button widths
|
||||||
|
case ' variable=self.navframe.submix,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.navframe.submix,\n'
|
||||||
|
' width=8,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case ' variable=self.navframe.channel,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.navframe.channel,\n'
|
||||||
|
' width=8,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case ' variable=self.navframe.extend,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.navframe.extend,\n'
|
||||||
|
' width=8,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case ' variable=self.navframe.info,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.navframe.info,\n'
|
||||||
|
' width=8,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# set channelframe button widths
|
||||||
|
case ' variable=self.labelframe.mute,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.labelframe.mute,\n'
|
||||||
|
' width=7,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case ' variable=self.labelframe.conf,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.labelframe.conf,\n'
|
||||||
|
' width=7,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case ' variable=self.labelframe.on,\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.labelframe.on,\n'
|
||||||
|
' width=7,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# set stripconfigframe button widths
|
||||||
|
case ' self.configframe.phys_out_params.index(param)\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' self.configframe.phys_out_params.index(param)\n',
|
||||||
|
' ],\n',
|
||||||
|
' width=6,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ignore_next_lines = 1
|
||||||
|
case ' self.configframe.virt_out_params.index(param)\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' self.configframe.virt_out_params.index(param)\n',
|
||||||
|
' ],\n',
|
||||||
|
' width=6,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
ignore_next_lines = 1
|
||||||
|
# This does both strip and bus param vars buttons
|
||||||
|
case ' variable=self.configframe.param_vars[i],\n':
|
||||||
|
write_outs(
|
||||||
|
output,
|
||||||
|
(
|
||||||
|
' variable=self.configframe.param_vars[i],\n',
|
||||||
|
' width=6,\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case _:
|
||||||
|
if 'Toggle.TButton' in line:
|
||||||
|
output.write(line.replace('Toggle.TButton', 'ToggleButton'))
|
||||||
|
else:
|
||||||
|
output.write(line)
|
||||||
|
|
||||||
|
|
||||||
|
def rewrite_menu():
|
||||||
|
menu_logger = logger.getChild('menu')
|
||||||
|
menu_logger.info('rewriting menu.py')
|
||||||
|
infile = Path(SRC_DIR) / 'menu.bk'
|
||||||
|
outfile = Path(PACKAGE_DIR) / 'menu.py'
|
||||||
|
with open(infile, 'r') as input:
|
||||||
|
with open(outfile, 'w') as output:
|
||||||
|
ignore_next_lines = 0
|
||||||
|
|
||||||
|
for line in input:
|
||||||
|
if ignore_next_lines > 0:
|
||||||
|
menu_logger.info(f'ignoring: {line}')
|
||||||
|
ignore_next_lines -= 1
|
||||||
|
continue
|
||||||
|
match line:
|
||||||
|
case 'import sv_ttk\n':
|
||||||
|
output.write('#import sv_ttk\n')
|
||||||
|
case ' # layout/themes\n':
|
||||||
|
ignore_next_lines = 14
|
||||||
|
case _:
|
||||||
|
output.write(line)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_for_build():
|
||||||
|
################# MOVE FILES FROM PACKAGE DIR INTO SRC DIR #########################
|
||||||
|
for file in (
|
||||||
|
PACKAGE_DIR / 'app.py',
|
||||||
|
PACKAGE_DIR / 'builders.py',
|
||||||
|
PACKAGE_DIR / 'menu.py',
|
||||||
|
):
|
||||||
|
if file.exists():
|
||||||
|
logger.debug(f'moving {str(file)}')
|
||||||
|
file.rename(SRC_DIR / f'{file.stem}.bk')
|
||||||
|
|
||||||
|
###################### RUN THE FILE REWRITER FOR EACH *.BK #########################
|
||||||
|
steps = (
|
||||||
|
rewrite_app,
|
||||||
|
rewrite_builders,
|
||||||
|
rewrite_menu,
|
||||||
|
)
|
||||||
|
[step() for step in steps]
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup():
|
||||||
|
########################## RESTORE *.BK FILES #####################################
|
||||||
|
for file in (
|
||||||
|
PACKAGE_DIR / 'app.py',
|
||||||
|
PACKAGE_DIR / 'builders.py',
|
||||||
|
PACKAGE_DIR / 'menu.py',
|
||||||
|
):
|
||||||
|
file.unlink()
|
||||||
|
|
||||||
|
for file in (
|
||||||
|
SRC_DIR / 'app.bk',
|
||||||
|
SRC_DIR / 'builders.bk',
|
||||||
|
SRC_DIR / 'menu.bk',
|
||||||
|
):
|
||||||
|
file.rename(PACKAGE_DIR / f'{file.stem}.py')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--rewrite', action='store_true')
|
||||||
|
parser.add_argument('--restore', action='store_true')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.rewrite:
|
||||||
|
logger.info('preparing files for build')
|
||||||
|
prepare_for_build()
|
||||||
|
elif args.restore:
|
||||||
|
logger.info('cleaning up files')
|
||||||
|
cleanup()
|
0
tools/src/.gitkeep
Normal file
0
tools/src/.gitkeep
Normal file
@ -1,3 +1,3 @@
|
|||||||
from .app import connect
|
from .app import connect
|
||||||
|
|
||||||
__ALL__ = ["connect"]
|
__ALL__ = ['connect']
|
||||||
|
133
vmcompact/app.py
133
vmcompact/app.py
@ -1,14 +1,21 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from functools import cached_property
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tkinter import ttk
|
from tkinter import messagebox, ttk
|
||||||
from typing import NamedTuple
|
from typing import NamedTuple
|
||||||
|
|
||||||
|
import voicemeeterlib
|
||||||
|
|
||||||
from .builders import MainFrameBuilder
|
from .builders import MainFrameBuilder
|
||||||
from .data import _base_values, _configuration, _kinds_all
|
from .configurations import loader
|
||||||
from .errors import VMCompactErrors
|
from .data import _base_values, _configuration, _kinds_all, get_configuration
|
||||||
|
from .errors import VMCompactError
|
||||||
from .menu import Menus
|
from .menu import Menus
|
||||||
from .subject import Subject
|
from .subject import Subject
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class App(tk.Tk):
|
class App(tk.Tk):
|
||||||
"""App mainframe"""
|
"""App mainframe"""
|
||||||
@ -22,35 +29,46 @@ class App(tk.Tk):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
APP_cls = type(
|
APP_cls = type(
|
||||||
f"Voicemeeter{kind}.Compact",
|
f'Voicemeeter{kind}.Compact',
|
||||||
(cls,),
|
(cls,),
|
||||||
{
|
{
|
||||||
"kind": kind,
|
'kind': kind,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return APP_cls
|
return APP_cls
|
||||||
|
|
||||||
def __init__(self, vmr):
|
def __init__(self, vmr):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self._vmr = vmr
|
self._vmr = vmr
|
||||||
self._vmr.event.add("ldirty")
|
self._vmr.event.add(['pdirty', 'ldirty'])
|
||||||
self._vmr.event.remove("mdirty")
|
|
||||||
self._vmr.event.remove("midi")
|
|
||||||
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
|
|
||||||
if icon_path.is_file():
|
|
||||||
self.iconbitmap(str(icon_path))
|
|
||||||
self.minsize(275, False)
|
|
||||||
self.subject = Subject()
|
self.subject = Subject()
|
||||||
self["menu"] = Menus(self, vmr)
|
self.start_updates()
|
||||||
|
self._vmr.init_thread()
|
||||||
|
for pn in (
|
||||||
|
Path(__file__).parent.resolve() / 'img' / 'cat.ico',
|
||||||
|
Path.cwd() / '_internal' / 'img' / 'cat.ico',
|
||||||
|
):
|
||||||
|
if pn.is_file():
|
||||||
|
self.iconbitmap(str(pn))
|
||||||
|
break
|
||||||
|
self.minsize(275, False)
|
||||||
|
self._configs = None
|
||||||
|
self.protocol('WM_DELETE_WINDOW', self.on_close_window)
|
||||||
|
self.menu = self['menu'] = Menus(self, vmr)
|
||||||
self.styletable = ttk.Style()
|
self.styletable = ttk.Style()
|
||||||
if _configuration.config:
|
if _configuration.config:
|
||||||
vmr.apply_config(_configuration.config)
|
vmr.apply_config(_configuration.config)
|
||||||
|
|
||||||
self.build_app()
|
self.build_app()
|
||||||
|
|
||||||
self.drag_id = ""
|
self.drag_id = ''
|
||||||
self.bind("<Configure>", self.dragging)
|
self.bind('<Configure>', self.dragging)
|
||||||
|
|
||||||
|
self.after(1, self.healthcheck_step)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'{type(self).__name__}App'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
@ -66,8 +84,8 @@ class App(tk.Tk):
|
|||||||
frame
|
frame
|
||||||
for frame in self.winfo_children()
|
for frame in self.winfo_children()
|
||||||
if isinstance(frame, ttk.Frame)
|
if isinstance(frame, ttk.Frame)
|
||||||
and "!stripconfig" in str(frame)
|
and '!stripconfig' in str(frame)
|
||||||
or "!busconfig" in str(frame)
|
or '!busconfig' in str(frame)
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_app(self, kind=None, vban=None):
|
def build_app(self, kind=None, vban=None):
|
||||||
@ -76,29 +94,29 @@ class App(tk.Tk):
|
|||||||
if kind:
|
if kind:
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
|
|
||||||
# register app as observer
|
# register event callbacks
|
||||||
self.target.subject.add(self)
|
self.target.subject.add([self.on_pdirty, self.on_ldirty])
|
||||||
|
|
||||||
self.bus_frame = None
|
self.bus_frame = None
|
||||||
self.submix_frame = None
|
self.submix_frame = None
|
||||||
self.builder = MainFrameBuilder(self)
|
self.builder = MainFrameBuilder(self)
|
||||||
self.builder.setup()
|
self.builder.setup()
|
||||||
self.builder.create_channelframe("strip")
|
self.builder.create_channelframe('strip')
|
||||||
self.builder.create_separator()
|
self.builder.create_separator()
|
||||||
self.builder.create_navframe()
|
self.builder.create_navframe()
|
||||||
if _configuration.extended:
|
if _configuration.extended:
|
||||||
self.nav_frame.extend.set(True)
|
self.nav_frame.extend.set(True)
|
||||||
self.nav_frame.extend_frame()
|
self.nav_frame.extend_frame()
|
||||||
if self.kind.name == "potato":
|
if self.kind.name == 'potato':
|
||||||
self.builder.create_banner()
|
self.builder.create_banner()
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_pdirty(self):
|
||||||
"""called whenever notified of update"""
|
if _base_values.run_update:
|
||||||
|
self.after(1, self.subject.notify, 'pdirty')
|
||||||
|
|
||||||
if subject == "pdirty" and _base_values.run_update:
|
def on_ldirty(self):
|
||||||
self.after(1, self.subject.notify, "pdirty")
|
if not _base_values.dragging:
|
||||||
elif subject == "ldirty" and not _base_values.dragging:
|
self.after(1, self.subject.notify, 'ldirty')
|
||||||
self.after(1, self.subject.notify, "ldirty")
|
|
||||||
|
|
||||||
def _destroy_top_level_frames(self):
|
def _destroy_top_level_frames(self):
|
||||||
"""
|
"""
|
||||||
@ -108,7 +126,7 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
Destroy all top level frames.
|
Destroy all top level frames.
|
||||||
"""
|
"""
|
||||||
self.target.subject.remove(self)
|
self.target.subject.remove([self.on_pdirty, self.on_ldirty])
|
||||||
self.subject.clear()
|
self.subject.clear()
|
||||||
[
|
[
|
||||||
frame.destroy()
|
frame.destroy()
|
||||||
@ -118,16 +136,67 @@ class App(tk.Tk):
|
|||||||
|
|
||||||
def dragging(self, event, *args):
|
def dragging(self, event, *args):
|
||||||
if event.widget is self:
|
if event.widget is self:
|
||||||
if self.drag_id == "":
|
if self.drag_id == '':
|
||||||
_base_values.dragging = True
|
_base_values.dragging = True
|
||||||
else:
|
else:
|
||||||
self.after_cancel(self.drag_id)
|
self.after_cancel(self.drag_id)
|
||||||
self.drag_id = self.after(100, self.stop_drag)
|
self.drag_id = self.after(100, self.stop_drag)
|
||||||
|
|
||||||
def stop_drag(self):
|
def stop_drag(self):
|
||||||
self.drag_id = ""
|
self.drag_id = ''
|
||||||
_base_values.dragging = False
|
_base_values.dragging = False
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def userconfigs(self):
|
||||||
|
self._configs = loader(self.kind.name, self.target)
|
||||||
|
return self._configs
|
||||||
|
|
||||||
|
def start_updates(self):
|
||||||
|
def init():
|
||||||
|
self.logger.debug('updates started')
|
||||||
|
_base_values.run_update = True
|
||||||
|
|
||||||
|
if self._vmr.gui.launched_by_api:
|
||||||
|
self.subject.notify('pdirty')
|
||||||
|
self.after(12000, init)
|
||||||
|
else:
|
||||||
|
init()
|
||||||
|
|
||||||
|
def healthcheck_step(self):
|
||||||
|
if not _base_values.vban_connected:
|
||||||
|
try:
|
||||||
|
self._vmr.version
|
||||||
|
except voicemeeterlib.error.CAPIError:
|
||||||
|
resp = messagebox.askyesno(message='Restart Voicemeeter GUI?')
|
||||||
|
if resp:
|
||||||
|
self.logger.debug(
|
||||||
|
'healthcheck failed, rebuilding the app after GUI restart.'
|
||||||
|
)
|
||||||
|
self._vmr.end_thread()
|
||||||
|
self._vmr.run_voicemeeter(self._vmr.kind.name)
|
||||||
|
_base_values.run_update = False
|
||||||
|
self._vmr.init_thread()
|
||||||
|
self.after(8000, self.start_updates)
|
||||||
|
self._destroy_top_level_frames()
|
||||||
|
self.build_app(self._vmr.kind)
|
||||||
|
vban_config = get_configuration('vban')
|
||||||
|
for i, _ in enumerate(vban_config):
|
||||||
|
target = getattr(self.menu, f'menu_vban_{i + 1}')
|
||||||
|
target.entryconfig(0, state='normal')
|
||||||
|
target.entryconfig(1, state='disabled')
|
||||||
|
[
|
||||||
|
self.menu.menu_vban.entryconfig(j, state='normal')
|
||||||
|
for j, _ in enumerate(self.menu.menu_vban.winfo_children())
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
self.destroy()
|
||||||
|
self.after(250, self.healthcheck_step)
|
||||||
|
|
||||||
|
def on_close_window(self):
|
||||||
|
if _base_values.vban_connected:
|
||||||
|
self._vban.logout()
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
|
|
||||||
_apps = {kind.name: App.make(kind) for kind in _kinds_all}
|
_apps = {kind.name: App.make(kind) for kind in _kinds_all}
|
||||||
|
|
||||||
@ -138,5 +207,5 @@ def connect(kind_id: str, vmr) -> App:
|
|||||||
try:
|
try:
|
||||||
VMMIN_cls = _apps[kind_id]
|
VMMIN_cls = _apps[kind_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise VMCompactErrors(f"Invalid kind: {kind_id}")
|
raise VMCompactError(f'Invalid kind: {kind_id}')
|
||||||
return VMMIN_cls(vmr)
|
return VMMIN_cls(vmr)
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Banner(ttk.Frame):
|
class Banner(ttk.Frame):
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.submix = tk.StringVar()
|
self.parent.subject.add(self)
|
||||||
self.submix.set(self.target.bus[_configuration.submixes].label)
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
self.submix = tk.StringVar(value=self.target.bus[_configuration.submixes].label)
|
||||||
|
|
||||||
self.label = ttk.Label(
|
self.label = ttk.Label(
|
||||||
self,
|
self,
|
||||||
text=f"SUBMIX: {self.submix.get().upper()}",
|
text=f'SUBMIX: {self.submix.get().upper()}',
|
||||||
)
|
)
|
||||||
self.label.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
|
self.label.grid(column=0, row=0, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||||
|
|
||||||
self.upd_submix()
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
"""returns the current interface"""
|
"""returns the current interface"""
|
||||||
|
|
||||||
return self.parent.target
|
return self.parent.target
|
||||||
|
|
||||||
def upd_submix(self):
|
def on_update(self, subject):
|
||||||
self.after(1, self.upd_submix_step)
|
if subject == 'submix':
|
||||||
|
if not _base_values.dragging:
|
||||||
def upd_submix_step(self):
|
self.logger.debug('checking submix for banner')
|
||||||
if not _base_values.dragging:
|
self.submix.set(self.target.bus[_configuration.submixes].label)
|
||||||
self.submix.set(self.target.bus[_configuration.submixes].label)
|
self.label['text'] = f'SUBMIX: {self.submix.get().upper()}'
|
||||||
self.label["text"] = f"SUBMIX: {self.submix.get().upper()}"
|
|
||||||
self.after(100, self.upd_submix_step)
|
|
||||||
|
@ -12,6 +12,8 @@ from .config import BusConfig, StripConfig
|
|||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
from .navigation import Navigation
|
from .navigation import Navigation
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class AbstractBuilder(abc.ABC):
|
class AbstractBuilder(abc.ABC):
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@ -28,11 +30,10 @@ class AbstractBuilder(abc.ABC):
|
|||||||
class MainFrameBuilder(AbstractBuilder):
|
class MainFrameBuilder(AbstractBuilder):
|
||||||
"""Responsible for building the frames that sit directly on the mainframe"""
|
"""Responsible for building the frames that sit directly on the mainframe"""
|
||||||
|
|
||||||
logger = logging.getLogger("builders.mainframebuilder")
|
|
||||||
|
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.kind = app.kind
|
self.kind = app.kind
|
||||||
self.app = app
|
self.app = app
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.app.title(
|
self.app.title(
|
||||||
@ -40,31 +41,31 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
)
|
)
|
||||||
self.app.resizable(False, False)
|
self.app.resizable(False, False)
|
||||||
if _configuration.themes_enabled:
|
if _configuration.themes_enabled:
|
||||||
if sv_ttk.get_theme() not in ("light", "dark"):
|
if sv_ttk.get_theme() not in ('light', 'dark'):
|
||||||
sv_ttk.set_theme(_configuration.theme_mode)
|
sv_ttk.set_theme(_configuration.theme_mode)
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Sunvalley {sv_ttk.get_theme().capitalize()} Theme applied"
|
f'Sunvalley {sv_ttk.get_theme().capitalize()} Theme applied'
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_channelframe(self, type_):
|
def create_channelframe(self, type_):
|
||||||
if type_ == "strip":
|
if type_ == 'strip':
|
||||||
self.app.strip_frame = _make_channelframe(self.app, type_)
|
self.app.strip_frame = _make_channelframe(self.app, type_)
|
||||||
else:
|
else:
|
||||||
self.app.bus_frame = _make_channelframe(self.app, type_)
|
self.app.bus_frame = _make_channelframe(self.app, type_)
|
||||||
self.logger.info(f"Finished building channelframe type {type_}")
|
self.logger.info(f'Finished building channelframe type {type_}')
|
||||||
|
|
||||||
def create_separator(self):
|
def create_separator(self):
|
||||||
self.app.sep = ttk.Separator(self.app, orient="vertical")
|
self.app.sep = ttk.Separator(self.app, orient='vertical')
|
||||||
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
||||||
self.app.columnconfigure(1, minsize=15)
|
self.app.columnconfigure(1, minsize=15)
|
||||||
self.logger.info(f"Finished building separator")
|
self.logger.info('Finished building separator')
|
||||||
|
|
||||||
def create_navframe(self):
|
def create_navframe(self):
|
||||||
self.app.nav_frame = Navigation(self.app)
|
self.app.nav_frame = Navigation(self.app)
|
||||||
self.logger.info(f"Finished building navframe")
|
self.logger.info('Finished building navframe')
|
||||||
|
|
||||||
def create_configframe(self, type_, index, id):
|
def create_configframe(self, type_, index, id):
|
||||||
if type_ == "strip":
|
if type_ == 'strip':
|
||||||
self.app.config_frame = StripConfig(self.app, index, id)
|
self.app.config_frame = StripConfig(self.app, index, id)
|
||||||
if self.app.strip_frame:
|
if self.app.strip_frame:
|
||||||
[
|
[
|
||||||
@ -94,20 +95,20 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
if self.app.strip_frame:
|
if self.app.strip_frame:
|
||||||
[
|
[
|
||||||
frame.styletable.configure(
|
frame.styletable.configure(
|
||||||
f"{frame.identifier}Conf{frame.index}.TButton",
|
f'{frame.identifier}Conf{frame.index}.TButton',
|
||||||
background=f"{'white' if not frame.conf.get() else 'yellow'}",
|
background=f'{"white" if not frame.conf.get() else "yellow"}',
|
||||||
)
|
)
|
||||||
for _, frame in enumerate(self.app.strip_frame.labelframes)
|
for _, frame in enumerate(self.app.strip_frame.labelframes)
|
||||||
]
|
]
|
||||||
if self.app.bus_frame:
|
if self.app.bus_frame:
|
||||||
[
|
[
|
||||||
frame.styletable.configure(
|
frame.styletable.configure(
|
||||||
f"{frame.identifier}Conf{frame.index}.TButton",
|
f'{frame.identifier}Conf{frame.index}.TButton',
|
||||||
background=f"{'white' if not frame.conf.get() else 'yellow'}",
|
background=f'{"white" if not frame.conf.get() else "yellow"}',
|
||||||
)
|
)
|
||||||
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
||||||
]
|
]
|
||||||
self.logger.info(f"Finished building configframe for {type_}[{index}]")
|
self.logger.info(f'Finished building configframe for {type_}[{index}]')
|
||||||
self.app.after(5, self.reset_config_frames)
|
self.app.after(5, self.reset_config_frames)
|
||||||
|
|
||||||
def reset_config_frames(self):
|
def reset_config_frames(self):
|
||||||
@ -120,7 +121,7 @@ class MainFrameBuilder(AbstractBuilder):
|
|||||||
def create_banner(self):
|
def create_banner(self):
|
||||||
self.app.banner = Banner(self.app)
|
self.app.banner = Banner(self.app)
|
||||||
self.app.banner.grid(row=4, column=0, columnspan=3)
|
self.app.banner.grid(row=4, column=0, columnspan=3)
|
||||||
self.logger.info(f"Finished building banner")
|
self.logger.info('Finished building banner')
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
pass
|
pass
|
||||||
@ -139,31 +140,31 @@ class NavigationFrameBuilder(AbstractBuilder):
|
|||||||
self.navframe.info = tk.BooleanVar()
|
self.navframe.info = tk.BooleanVar()
|
||||||
|
|
||||||
self.navframe.channel_text = tk.StringVar(
|
self.navframe.channel_text = tk.StringVar(
|
||||||
value=f"{self.navframe.parent.strip_frame.identifier.upper()}"
|
value=f'{self.navframe.parent.strip_frame.identifier.upper()}'
|
||||||
)
|
)
|
||||||
self.navframe.extend_text = tk.StringVar(
|
self.navframe.extend_text = tk.StringVar(
|
||||||
value=f"{'REDUCE' if self.navframe.extend.get() else 'EXTEND'}"
|
value=f'{"REDUCE" if self.navframe.extend.get() else "EXTEND"}'
|
||||||
)
|
)
|
||||||
self.navframe.info_text = tk.StringVar()
|
self.navframe.info_text = tk.StringVar()
|
||||||
|
|
||||||
def create_submix_button(self):
|
def create_submix_button(self):
|
||||||
self.navframe.submix_button = ttk.Checkbutton(
|
self.navframe.submix_button = ttk.Checkbutton(
|
||||||
self.navframe,
|
self.navframe,
|
||||||
text="SUBMIX",
|
text='SUBMIX',
|
||||||
command=self.navframe.show_submix,
|
command=self.navframe.show_submix,
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Submix.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else "Submix.TButton"}',
|
||||||
variable=self.navframe.submix,
|
variable=self.navframe.submix,
|
||||||
)
|
)
|
||||||
self.navframe.submix_button.grid(column=0, row=0)
|
self.navframe.submix_button.grid(column=0, row=0)
|
||||||
if self.navframe.parent.kind.name != "potato":
|
if self.navframe.parent.kind.name != 'potato':
|
||||||
self.navframe.submix_button["state"] = "disabled"
|
self.navframe.submix_button['state'] = 'disabled'
|
||||||
|
|
||||||
def create_channel_button(self):
|
def create_channel_button(self):
|
||||||
self.navframe.channel_button = ttk.Checkbutton(
|
self.navframe.channel_button = ttk.Checkbutton(
|
||||||
self.navframe,
|
self.navframe,
|
||||||
textvariable=self.navframe.channel_text,
|
textvariable=self.navframe.channel_text,
|
||||||
command=self.navframe.switch_channel,
|
command=self.navframe.switch_channel,
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Channel.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else "Channel.TButton"}',
|
||||||
variable=self.navframe.channel,
|
variable=self.navframe.channel,
|
||||||
)
|
)
|
||||||
self.navframe.channel_button.grid(column=0, row=1, rowspan=1)
|
self.navframe.channel_button.grid(column=0, row=1, rowspan=1)
|
||||||
@ -173,7 +174,7 @@ class NavigationFrameBuilder(AbstractBuilder):
|
|||||||
self.navframe,
|
self.navframe,
|
||||||
textvariable=self.navframe.extend_text,
|
textvariable=self.navframe.extend_text,
|
||||||
command=self.navframe.extend_frame,
|
command=self.navframe.extend_frame,
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Extend.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else "Extend.TButton"}',
|
||||||
variable=self.navframe.extend,
|
variable=self.navframe.extend,
|
||||||
)
|
)
|
||||||
self.navframe.extend_button.grid(column=0, row=2)
|
self.navframe.extend_button.grid(column=0, row=2)
|
||||||
@ -182,7 +183,7 @@ class NavigationFrameBuilder(AbstractBuilder):
|
|||||||
self.navframe.info_button = ttk.Checkbutton(
|
self.navframe.info_button = ttk.Checkbutton(
|
||||||
self.navframe,
|
self.navframe,
|
||||||
textvariable=self.navframe.info_text,
|
textvariable=self.navframe.info_text,
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Rec.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else "Rec.TButton"}',
|
||||||
variable=self.navframe.info,
|
variable=self.navframe.info,
|
||||||
)
|
)
|
||||||
self.navframe.info_button.grid(column=0, row=3)
|
self.navframe.info_button.grid(column=0, row=3)
|
||||||
@ -194,9 +195,9 @@ class NavigationFrameBuilder(AbstractBuilder):
|
|||||||
if isinstance(child, ttk.Checkbutton)
|
if isinstance(child, ttk.Checkbutton)
|
||||||
]
|
]
|
||||||
if _configuration.themes_enabled:
|
if _configuration.themes_enabled:
|
||||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height)
|
self.navframe.rowconfigure(1, minsize=_configuration.channel_height)
|
||||||
else:
|
else:
|
||||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height + 10)
|
self.navframe.rowconfigure(1, minsize=_configuration.channel_height + 10)
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
pass
|
pass
|
||||||
@ -226,9 +227,9 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
"""Adds a progress bar widget to a single label frame"""
|
"""Adds a progress bar widget to a single label frame"""
|
||||||
self.labelframe.pb = ttk.Progressbar(
|
self.labelframe.pb = ttk.Progressbar(
|
||||||
self.labelframe,
|
self.labelframe,
|
||||||
maximum=100,
|
maximum=72,
|
||||||
orient="vertical",
|
orient='vertical',
|
||||||
mode="determinate",
|
mode='determinate',
|
||||||
variable=self.labelframe.level,
|
variable=self.labelframe.level,
|
||||||
)
|
)
|
||||||
self.labelframe.pb.grid(column=0, row=0)
|
self.labelframe.pb.grid(column=0, row=0)
|
||||||
@ -239,16 +240,22 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
self.labelframe,
|
self.labelframe,
|
||||||
from_=12.0,
|
from_=12.0,
|
||||||
to=-60.0,
|
to=-60.0,
|
||||||
orient="vertical",
|
orient='vertical',
|
||||||
variable=self.labelframe.gain,
|
variable=self.labelframe.gain,
|
||||||
command=self.labelframe.scale_callback,
|
command=self.labelframe.scale_callback,
|
||||||
length=_configuration.level_height,
|
length=_configuration.channel_height,
|
||||||
)
|
)
|
||||||
self.scale.grid(column=1, row=0)
|
self.scale.grid(column=1, row=0)
|
||||||
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
self.scale.bind('<Double-Button-1>', self.labelframe.reset_gain)
|
||||||
self.scale.bind("<Button-1>", self.labelframe.scale_press)
|
self.scale.bind('<Button-1>', self.labelframe.scale_press)
|
||||||
self.scale.bind("<ButtonRelease-1>", self.labelframe.scale_release)
|
self.scale.bind('<ButtonRelease-1>', self.labelframe.scale_release)
|
||||||
self.scale.bind("<MouseWheel>", self.labelframe._on_mousewheel)
|
self.scale.bind(
|
||||||
|
'<MouseWheel>',
|
||||||
|
partial(
|
||||||
|
self.labelframe.pause_updates,
|
||||||
|
self.labelframe._on_mousewheel,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def add_gain_label(self):
|
def add_gain_label(self):
|
||||||
self.labelframe.gain_label = ttk.Label(
|
self.labelframe.gain_label = ttk.Label(
|
||||||
@ -261,9 +268,9 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
"""Adds a mute button widget to a single label frame"""
|
"""Adds a mute button widget to a single label frame"""
|
||||||
self.button_mute = ttk.Checkbutton(
|
self.button_mute = ttk.Checkbutton(
|
||||||
self.labelframe,
|
self.labelframe,
|
||||||
text="MUTE",
|
text='MUTE',
|
||||||
command=partial(self.labelframe.toggle_mute, "mute"),
|
command=partial(self.labelframe.pause_updates, self.labelframe.toggle_mute),
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Mute{self.index}.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{self.identifier}Mute{self.index}.TButton"}',
|
||||||
variable=self.labelframe.mute,
|
variable=self.labelframe.mute,
|
||||||
)
|
)
|
||||||
self.button_mute.grid(column=0, row=2, columnspan=2)
|
self.button_mute.grid(column=0, row=2, columnspan=2)
|
||||||
@ -271,9 +278,9 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
def add_conf_button(self):
|
def add_conf_button(self):
|
||||||
self.button_conf = ttk.Checkbutton(
|
self.button_conf = ttk.Checkbutton(
|
||||||
self.labelframe,
|
self.labelframe,
|
||||||
text="CONFIG",
|
text='CONFIG',
|
||||||
command=self.labelframe.open_config,
|
command=self.labelframe.open_config,
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Conf{self.index}.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{self.identifier}Conf{self.index}.TButton"}',
|
||||||
variable=self.labelframe.conf,
|
variable=self.labelframe.conf,
|
||||||
)
|
)
|
||||||
self.button_conf.grid(column=0, row=3, columnspan=2)
|
self.button_conf.grid(column=0, row=3, columnspan=2)
|
||||||
@ -281,9 +288,9 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
|||||||
def add_on_button(self):
|
def add_on_button(self):
|
||||||
self.button_on = ttk.Checkbutton(
|
self.button_on = ttk.Checkbutton(
|
||||||
self.labelframe,
|
self.labelframe,
|
||||||
text="ON",
|
text='ON',
|
||||||
command=self.labelframe.set_on,
|
command=partial(self.labelframe.pause_updates, self.labelframe.set_on),
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}On{self.index}.TButton'}",
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{self.identifier}On{self.index}.TButton"}',
|
||||||
variable=self.labelframe.on,
|
variable=self.labelframe.on,
|
||||||
)
|
)
|
||||||
self.button_on.grid(column=0, row=2, columnspan=2)
|
self.button_on.grid(column=0, row=2, columnspan=2)
|
||||||
@ -323,7 +330,7 @@ class ChannelConfigFrameBuilder(AbstractBuilder):
|
|||||||
]
|
]
|
||||||
self.configframe.grid(sticky=(tk.W))
|
self.configframe.grid(sticky=(tk.W))
|
||||||
[
|
[
|
||||||
self.configframe.columnconfigure(i, minsize=_configuration.level_width)
|
self.configframe.columnconfigure(i, minsize=_configuration.channel_width)
|
||||||
for i in range(self.configframe.phys_out + self.configframe.virt_out)
|
for i in range(self.configframe.phys_out + self.configframe.virt_out)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -332,40 +339,40 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
"""Responsible for building channel configframe widgets"""
|
"""Responsible for building channel configframe widgets"""
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
if self.configframe.parent.kind.name == "basic":
|
if self.configframe.parent.kind.name == 'basic':
|
||||||
self.configframe.slider_params = ("audibility",)
|
self.configframe.slider_params = ('audibility',)
|
||||||
self.configframe.slider_vars = (tk.DoubleVar(),)
|
self.configframe.slider_vars = (tk.DoubleVar(),)
|
||||||
else:
|
else:
|
||||||
self.configframe.slider_params = ("comp", "gate", "limit")
|
self.configframe.slider_params = ('comp.knob', 'gate.knob', 'limit')
|
||||||
self.configframe.slider_vars = [
|
self.configframe.slider_vars = [
|
||||||
tk.DoubleVar() for _ in self.configframe.slider_params
|
tk.DoubleVar() for _ in self.configframe.slider_params
|
||||||
]
|
]
|
||||||
|
|
||||||
self.configframe.phys_out_params = [
|
self.configframe.phys_out_params = [
|
||||||
f"A{i+1}" for i in range(self.configframe.phys_out)
|
f'A{i + 1}' for i in range(self.configframe.phys_out)
|
||||||
]
|
]
|
||||||
self.configframe.phys_out_params_vars = [
|
self.configframe.phys_out_params_vars = [
|
||||||
tk.BooleanVar() for _ in self.configframe.phys_out_params
|
tk.BooleanVar() for _ in self.configframe.phys_out_params
|
||||||
]
|
]
|
||||||
|
|
||||||
self.configframe.virt_out_params = [
|
self.configframe.virt_out_params = [
|
||||||
f"B{i+1}" for i in range(self.configframe.virt_out)
|
f'B{i + 1}' for i in range(self.configframe.virt_out)
|
||||||
]
|
]
|
||||||
self.configframe.virt_out_params_vars = [
|
self.configframe.virt_out_params_vars = [
|
||||||
tk.BooleanVar() for _ in self.configframe.virt_out_params
|
tk.BooleanVar() for _ in self.configframe.virt_out_params
|
||||||
]
|
]
|
||||||
|
|
||||||
self.configframe.params = ("mono", "solo")
|
self.configframe.params = ('mono', 'solo')
|
||||||
self.configframe.param_vars = list(
|
self.configframe.param_vars = list(
|
||||||
tk.BooleanVar() for _ in self.configframe.params
|
tk.BooleanVar() for _ in self.configframe.params
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.configframe.parent.kind.name in ("banana", "potato"):
|
if self.configframe.parent.kind.name in ('banana', 'potato'):
|
||||||
if self.configframe.index == self.configframe.phys_in:
|
if self.configframe.index == self.configframe.phys_in:
|
||||||
self.configframe.params = list(
|
self.configframe.params = list(
|
||||||
map(lambda x: x.replace("mono", "mc"), self.configframe.params)
|
map(lambda x: x.replace('mono', 'mc'), self.configframe.params)
|
||||||
)
|
)
|
||||||
if self.configframe.parent.kind.name == "banana":
|
if self.configframe.parent.kind.name == 'banana':
|
||||||
pass
|
pass
|
||||||
# karaoke modes not in RT Packet yet. May implement in future
|
# karaoke modes not in RT Packet yet. May implement in future
|
||||||
"""
|
"""
|
||||||
@ -381,101 +388,101 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
== self.configframe.phys_in + self.configframe.virt_in - 1
|
== self.configframe.phys_in + self.configframe.virt_in - 1
|
||||||
):
|
):
|
||||||
self.configframe.params = list(
|
self.configframe.params = list(
|
||||||
map(lambda x: x.replace("mono", "mc"), self.configframe.params)
|
map(lambda x: x.replace('mono', 'mc'), self.configframe.params)
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_comp_slider(self):
|
def create_comp_slider(self):
|
||||||
comp_label = ttk.Label(self.configframe, text="Comp")
|
comp_label = ttk.Label(self.configframe, text='Comp')
|
||||||
comp_scale = ttk.Scale(
|
comp_scale = ttk.Scale(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
from_=0.0,
|
from_=0.0,
|
||||||
to=10.0,
|
to=10.0,
|
||||||
orient="horizontal",
|
orient='horizontal',
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("comp")
|
self.configframe.slider_params.index('comp.knob')
|
||||||
],
|
],
|
||||||
command=partial(self.configframe.scale_callback, "comp"),
|
command=partial(self.configframe.scale_callback, 'comp.knob'),
|
||||||
)
|
)
|
||||||
comp_scale.bind(
|
comp_scale.bind(
|
||||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "comp", 0)
|
'<Double-Button-1>', partial(self.configframe.reset_scale, 'comp.knob', 0)
|
||||||
)
|
)
|
||||||
comp_scale.bind("<Button-1>", self.configframe.scale_press)
|
comp_scale.bind('<Button-1>', self.configframe.scale_press)
|
||||||
comp_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
comp_scale.bind('<ButtonRelease-1>', self.configframe.scale_release)
|
||||||
comp_scale.bind("<Enter>", partial(self.configframe.scale_enter, "comp"))
|
comp_scale.bind('<Enter>', partial(self.configframe.scale_enter, 'comp.knob'))
|
||||||
comp_scale.bind("<Leave>", self.configframe.scale_leave)
|
comp_scale.bind('<Leave>', self.configframe.scale_leave)
|
||||||
|
|
||||||
comp_label.grid(column=0, row=0)
|
comp_label.grid(column=0, row=0)
|
||||||
comp_scale.grid(column=1, row=0)
|
comp_scale.grid(column=1, row=0)
|
||||||
|
|
||||||
def create_gate_slider(self):
|
def create_gate_slider(self):
|
||||||
gate_label = ttk.Label(self.configframe, text="Gate")
|
gate_label = ttk.Label(self.configframe, text='Gate')
|
||||||
gate_scale = ttk.Scale(
|
gate_scale = ttk.Scale(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
from_=0.0,
|
from_=0.0,
|
||||||
to=10.0,
|
to=10.0,
|
||||||
orient="horizontal",
|
orient='horizontal',
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("gate")
|
self.configframe.slider_params.index('gate.knob')
|
||||||
],
|
],
|
||||||
command=partial(self.configframe.scale_callback, "gate"),
|
command=partial(self.configframe.scale_callback, 'gate.knob'),
|
||||||
)
|
)
|
||||||
gate_scale.bind(
|
gate_scale.bind(
|
||||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "gate", 0)
|
'<Double-Button-1>', partial(self.configframe.reset_scale, 'gate.knob', 0)
|
||||||
)
|
)
|
||||||
gate_scale.bind("<Button-1>", self.configframe.scale_press)
|
gate_scale.bind('<Button-1>', self.configframe.scale_press)
|
||||||
gate_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
gate_scale.bind('<ButtonRelease-1>', self.configframe.scale_release)
|
||||||
gate_scale.bind("<Enter>", partial(self.configframe.scale_enter, "gate"))
|
gate_scale.bind('<Enter>', partial(self.configframe.scale_enter, 'gate.knob'))
|
||||||
gate_scale.bind("<Leave>", self.configframe.scale_leave)
|
gate_scale.bind('<Leave>', self.configframe.scale_leave)
|
||||||
|
|
||||||
gate_label.grid(column=2, row=0)
|
gate_label.grid(column=2, row=0)
|
||||||
gate_scale.grid(column=3, row=0)
|
gate_scale.grid(column=3, row=0)
|
||||||
|
|
||||||
def create_limit_slider(self):
|
def create_limit_slider(self):
|
||||||
limit_label = ttk.Label(self.configframe, text="Limit")
|
limit_label = ttk.Label(self.configframe, text='Limit')
|
||||||
limit_scale = ttk.Scale(
|
limit_scale = ttk.Scale(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
from_=-40,
|
from_=-40,
|
||||||
to=12,
|
to=12,
|
||||||
orient="horizontal",
|
orient='horizontal',
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("limit")
|
self.configframe.slider_params.index('limit')
|
||||||
],
|
],
|
||||||
command=partial(self.configframe.scale_callback, "limit"),
|
command=partial(self.configframe.scale_callback, 'limit'),
|
||||||
)
|
)
|
||||||
limit_scale.bind(
|
limit_scale.bind(
|
||||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "limit", 12)
|
'<Double-Button-1>', partial(self.configframe.reset_scale, 'limit', 12)
|
||||||
)
|
)
|
||||||
limit_scale.bind("<Button-1>", self.configframe.scale_press)
|
limit_scale.bind('<Button-1>', self.configframe.scale_press)
|
||||||
limit_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
limit_scale.bind('<ButtonRelease-1>', self.configframe.scale_release)
|
||||||
limit_scale.bind("<Enter>", partial(self.configframe.scale_enter, "limit"))
|
limit_scale.bind('<Enter>', partial(self.configframe.scale_enter, 'limit'))
|
||||||
limit_scale.bind("<Leave>", self.configframe.scale_leave)
|
limit_scale.bind('<Leave>', self.configframe.scale_leave)
|
||||||
|
|
||||||
limit_label.grid(column=4, row=0)
|
limit_label.grid(column=4, row=0)
|
||||||
limit_scale.grid(column=5, row=0)
|
limit_scale.grid(column=5, row=0)
|
||||||
|
|
||||||
def create_audibility_slider(self):
|
def create_audibility_slider(self):
|
||||||
aud_label = ttk.Label(self.configframe, text="Audibility")
|
aud_label = ttk.Label(self.configframe, text='Audibility')
|
||||||
aud_scale = ttk.Scale(
|
aud_scale = ttk.Scale(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
from_=0.0,
|
from_=0.0,
|
||||||
to=10.0,
|
to=10.0,
|
||||||
orient="horizontal",
|
orient='horizontal',
|
||||||
length=_configuration.level_width,
|
length=_configuration.channel_width,
|
||||||
variable=self.configframe.slider_vars[
|
variable=self.configframe.slider_vars[
|
||||||
self.configframe.slider_params.index("audibility")
|
self.configframe.slider_params.index('audibility')
|
||||||
],
|
],
|
||||||
command=partial(self.configframe.scale_callback, "audibility"),
|
command=partial(self.configframe.scale_callback, 'audibility'),
|
||||||
)
|
)
|
||||||
aud_scale.bind(
|
aud_scale.bind(
|
||||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "audibility", 0)
|
'<Double-Button-1>', partial(self.configframe.reset_scale, 'audibility', 0)
|
||||||
)
|
)
|
||||||
aud_scale.bind("<Button-1>", self.configframe.scale_press)
|
aud_scale.bind('<Button-1>', self.configframe.scale_press)
|
||||||
aud_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
aud_scale.bind('<ButtonRelease-1>', self.configframe.scale_release)
|
||||||
aud_scale.bind("<Enter>", partial(self.configframe.scale_enter, "audibility"))
|
aud_scale.bind('<Enter>', partial(self.configframe.scale_enter, 'audibility'))
|
||||||
aud_scale.bind("<Leave>", self.configframe.scale_leave)
|
aud_scale.bind('<Leave>', self.configframe.scale_leave)
|
||||||
|
|
||||||
aud_label.grid(column=0, row=0)
|
aud_label.grid(column=0, row=0)
|
||||||
aud_scale.grid(column=1, row=0)
|
aud_scale.grid(column=1, row=0)
|
||||||
@ -485,8 +492,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
ttk.Checkbutton(
|
ttk.Checkbutton(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
text=param,
|
text=param,
|
||||||
command=partial(self.configframe.toggle_a, param),
|
command=partial(
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
self.configframe.pause_updates, self.configframe.toggle_a, param
|
||||||
|
),
|
||||||
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}',
|
||||||
variable=self.configframe.phys_out_params_vars[
|
variable=self.configframe.phys_out_params_vars[
|
||||||
self.configframe.phys_out_params.index(param)
|
self.configframe.phys_out_params.index(param)
|
||||||
],
|
],
|
||||||
@ -506,8 +515,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
ttk.Checkbutton(
|
ttk.Checkbutton(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
text=param,
|
text=param,
|
||||||
command=partial(self.configframe.toggle_b, param),
|
command=partial(
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
self.configframe.pause_updates, self.configframe.toggle_b, param
|
||||||
|
),
|
||||||
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}',
|
||||||
variable=self.configframe.virt_out_params_vars[
|
variable=self.configframe.virt_out_params_vars[
|
||||||
self.configframe.virt_out_params.index(param)
|
self.configframe.virt_out_params.index(param)
|
||||||
],
|
],
|
||||||
@ -527,8 +538,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
ttk.Checkbutton(
|
ttk.Checkbutton(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
text=param,
|
text=param,
|
||||||
command=partial(self.configframe.toggle_p, param),
|
command=partial(
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
self.configframe.pause_updates, self.configframe.toggle_p, param
|
||||||
|
),
|
||||||
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}',
|
||||||
variable=self.configframe.param_vars[i],
|
variable=self.configframe.param_vars[i],
|
||||||
)
|
)
|
||||||
for i, param in enumerate(self.configframe.params)
|
for i, param in enumerate(self.configframe.params)
|
||||||
@ -563,7 +576,7 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
}
|
}
|
||||||
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
||||||
# fmt: on
|
# fmt: on
|
||||||
self.configframe.params = ("mono", "eq", "eq_ab")
|
self.configframe.params = ('mono', 'eq.on', 'eq.ab')
|
||||||
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params]
|
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params]
|
||||||
self.configframe.bus_mode_label_text = tk.StringVar(
|
self.configframe.bus_mode_label_text = tk.StringVar(
|
||||||
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
|
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
|
||||||
@ -577,10 +590,16 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
column=0, row=0, columnspan=2, sticky=(tk.W)
|
column=0, row=0, columnspan=2, sticky=(tk.W)
|
||||||
)
|
)
|
||||||
self.configframe.busmode_button.bind(
|
self.configframe.busmode_button.bind(
|
||||||
"<Button-1>", self.configframe.rotate_bus_modes_right
|
'<Button-1>',
|
||||||
|
partial(
|
||||||
|
self.configframe.pause_updates, self.configframe.rotate_bus_modes_right
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.configframe.busmode_button.bind(
|
self.configframe.busmode_button.bind(
|
||||||
"<Button-3>", self.configframe.rotate_bus_modes_left
|
'<Button-3>',
|
||||||
|
partial(
|
||||||
|
self.configframe.pause_updates, self.configframe.rotate_bus_modes_left
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_param_buttons(self):
|
def create_param_buttons(self):
|
||||||
@ -588,8 +607,10 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
|||||||
ttk.Checkbutton(
|
ttk.Checkbutton(
|
||||||
self.configframe,
|
self.configframe,
|
||||||
text=param,
|
text=param,
|
||||||
command=partial(self.configframe.toggle_p, param),
|
command=partial(
|
||||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
self.configframe.pause_updates, self.configframe.toggle_p, param
|
||||||
|
),
|
||||||
|
style=f'{"Toggle.TButton" if _configuration.themes_enabled else f"{param}.TButton"}',
|
||||||
variable=self.configframe.param_vars[i],
|
variable=self.configframe.param_vars[i],
|
||||||
)
|
)
|
||||||
for i, param in enumerate(self.configframe.params)
|
for i, param in enumerate(self.configframe.params)
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
import logging
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from math import log
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from . import builders
|
from . import builders
|
||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ChannelLabelFrame(ttk.LabelFrame):
|
class ChannelLabelFrame(ttk.LabelFrame):
|
||||||
"""Base class for a single channel"""
|
"""Base class for a single channel"""
|
||||||
@ -14,6 +16,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.styletable = self.parent.parent.styletable
|
self.styletable = self.parent.parent.styletable
|
||||||
|
|
||||||
self.builder = builders.ChannelLabelFrameBuilder(self, index, id)
|
self.builder = builders.ChannelLabelFrameBuilder(self, index, id)
|
||||||
@ -40,29 +43,32 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
return self.parent.target
|
return self.parent.target
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if hasattr(self.target, param):
|
try:
|
||||||
return getattr(self.target, param)
|
return getattr(self.target, param)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger(f'{type(e).__name__}: {e}')
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if hasattr(self.target, param):
|
if param in dir(self.target): # avoid calling getattr (with hasattr)
|
||||||
setattr(self.target, param, value)
|
setattr(self.target, param, value)
|
||||||
|
|
||||||
def scale_callback(self, *args):
|
def scale_callback(self, *args):
|
||||||
"""callback function for scale widget"""
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
self.setter("gain", self.gain.get())
|
val = round(self.gain.get(), 1)
|
||||||
self.gainlabel.set(round(self.gain.get(), 1))
|
self.setter('gain', val)
|
||||||
|
self.gainlabel.set(val)
|
||||||
|
|
||||||
def toggle_mute(self, *args):
|
def toggle_mute(self, *args):
|
||||||
self.target.mute = self.mute.get()
|
self.target.mute = self.mute.get()
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}Mute{self.index}.TButton",
|
f'{self.identifier}Mute{self.index}.TButton',
|
||||||
background=f'{"red" if self.mute.get() else "white"}',
|
background=f'{"red" if self.mute.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def reset_gain(self, *args):
|
def reset_gain(self, *args):
|
||||||
self.setter("gain", 0)
|
self.setter('gain', 0)
|
||||||
self.gain.set(0)
|
self.gain.set(0)
|
||||||
self.gainlabel.set(self.gain.get())
|
self.gainlabel.set(self.gain.get())
|
||||||
|
|
||||||
@ -70,37 +76,47 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
self.after(1, self.remove_events)
|
self.after(1, self.remove_events)
|
||||||
|
|
||||||
def remove_events(self):
|
def remove_events(self):
|
||||||
self.parent.target.event.remove("pdirty")
|
self.parent.target.event.remove('pdirty')
|
||||||
self.parent.target.event.remove("ldirty")
|
self.parent.target.event.remove('ldirty')
|
||||||
|
|
||||||
def scale_release(self, *args):
|
def scale_release(self, *args):
|
||||||
_base_values.run_update = False
|
_base_values.run_update = False
|
||||||
self.after(1, self.add_events)
|
self.after(1, self.add_events)
|
||||||
|
|
||||||
def add_events(self):
|
def add_events(self):
|
||||||
self.parent.target.event.add("pdirty")
|
self.parent.target.event.add('pdirty')
|
||||||
self.parent.target.event.add("ldirty")
|
self.parent.target.event.add('ldirty')
|
||||||
self.after(500, self.resume_updates)
|
self.after(500, self.resume_updates)
|
||||||
|
|
||||||
|
def pause_updates(self, func, *args):
|
||||||
|
"""function wrapper, adds a 50ms delay on updates"""
|
||||||
|
_base_values.run_update = False
|
||||||
|
|
||||||
|
func(*args)
|
||||||
|
|
||||||
|
self.after(50, self.resume_updates)
|
||||||
|
|
||||||
def resume_updates(self):
|
def resume_updates(self):
|
||||||
_base_values.run_update = True
|
_base_values.run_update = True
|
||||||
|
|
||||||
def _on_mousewheel(self, event):
|
def _on_mousewheel(self, event):
|
||||||
_base_values.run_update = False
|
|
||||||
self.gain.set(
|
self.gain.set(
|
||||||
self.gain.get()
|
round(
|
||||||
+ (
|
self.gain.get()
|
||||||
_configuration.mwscroll_step
|
+ (
|
||||||
if event.delta > 0
|
_configuration.mwscroll_step
|
||||||
else -_configuration.mwscroll_step
|
if event.delta > 0
|
||||||
|
else -_configuration.mwscroll_step
|
||||||
|
),
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if self.gain.get() > 12:
|
if self.gain.get() > 12:
|
||||||
self.gain.set(12)
|
self.gain.set(12)
|
||||||
elif self.gain.get() < -60:
|
elif self.gain.get() < -60:
|
||||||
self.gain.set(-60)
|
self.gain.set(-60)
|
||||||
self.setter("gain", self.gain.get())
|
self.setter('gain', self.gain.get())
|
||||||
self.after(1, self.resume_updates)
|
self.gainlabel.set(round(self.gain.get(), 1))
|
||||||
|
|
||||||
def open_config(self):
|
def open_config(self):
|
||||||
if self.conf.get():
|
if self.conf.get():
|
||||||
@ -109,46 +125,47 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
|||||||
self.parent.parent.config_frame.teardown()
|
self.parent.parent.config_frame.teardown()
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}Conf{self.index}.TButton",
|
f'{self.identifier}Conf{self.index}.TButton',
|
||||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_update(self, subject):
|
||||||
if subject == "ldirty":
|
if subject == 'ldirty':
|
||||||
self.upd_levels()
|
self.upd_levels()
|
||||||
elif subject == "pdirty":
|
elif subject == 'pdirty':
|
||||||
self.sync_params()
|
self.sync_params()
|
||||||
elif subject == "labelframe":
|
elif subject == 'labelframe':
|
||||||
self.after(5, self.sync_labels)
|
self.after(5, self.sync_labels)
|
||||||
|
|
||||||
def sync_params(self):
|
def sync_params(self):
|
||||||
"""sync parameter states, update button colours"""
|
"""sync parameter states, update button colours"""
|
||||||
self.gain.set(self.getter("gain"))
|
self.gain.set(self.getter('gain'))
|
||||||
self.gainlabel.set(round(self.gain.get(), 1))
|
self.gainlabel.set(round(self.gain.get(), 1))
|
||||||
self.mute.set(self.getter("mute"))
|
self.mute.set(self.getter('mute'))
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}Mute{self.index}.TButton",
|
f'{self.identifier}Mute{self.index}.TButton',
|
||||||
background=f'{"red" if self.mute.get() else "white"}',
|
background=f'{"red" if self.mute.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def sync_labels(self):
|
def sync_labels(self):
|
||||||
"""sync labelframes according to label text"""
|
"""sync labelframes according to label text"""
|
||||||
retval = self.getter("label")
|
retval = self.getter('label')
|
||||||
self.parent.label_cache[self.id].insert(self.index, retval)
|
if self.parent.label_cache[self.id][self.index] != retval:
|
||||||
if len(retval) > 10:
|
self.parent.label_cache[self.id][self.index] = retval
|
||||||
retval = f"{retval[:8]}.."
|
if len(retval) > 10:
|
||||||
if not retval:
|
retval = f'{retval[:8]}..'
|
||||||
self.parent.columnconfigure(self.index, minsize=0)
|
if not retval:
|
||||||
self.parent.parent.subject.remove(self)
|
self.parent.columnconfigure(self.index, minsize=0)
|
||||||
self.grid_remove()
|
self.parent.parent.subject.remove(self)
|
||||||
else:
|
self.grid_remove()
|
||||||
self.parent.parent.subject.add(self)
|
else:
|
||||||
self.grid()
|
self.parent.parent.subject.add(self)
|
||||||
self.configure(text=retval)
|
self.grid()
|
||||||
|
self.configure(text=retval)
|
||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
self.grid(sticky=(tk.N, tk.S))
|
self.grid(padx=_configuration.channel_xpadding, sticky=(tk.N, tk.S))
|
||||||
[
|
[
|
||||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||||
for child in self.winfo_children()
|
for child in self.winfo_children()
|
||||||
@ -186,7 +203,7 @@ class Strip(ChannelLabelFrame):
|
|||||||
if self.target.levels.is_updated:
|
if self.target.levels.is_updated:
|
||||||
val = max(self.target.levels.prefader)
|
val = max(self.target.levels.prefader)
|
||||||
self.level.set(
|
self.level.set(
|
||||||
(0 if self.mute.get() else 100 + val - 18 + self.gain.get())
|
(0 if self.mute.get() else 72 + val - 12 + self.gain.get())
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -208,19 +225,22 @@ class Bus(ChannelLabelFrame):
|
|||||||
if self.index < self.parent.parent.kind.num_bus:
|
if self.index < self.parent.parent.kind.num_bus:
|
||||||
if self.target.levels.is_updated or self.level.get() != -118:
|
if self.target.levels.is_updated or self.level.get() != -118:
|
||||||
val = max(self.target.levels.all)
|
val = max(self.target.levels.all)
|
||||||
self.level.set((0 if self.mute.get() else 100 + val - 18))
|
self.level.set((0 if self.mute.get() else 72 + val - 12))
|
||||||
|
|
||||||
|
|
||||||
class ChannelFrame(ttk.Frame):
|
class ChannelFrame(ttk.Frame):
|
||||||
label_cache = {"strip": list(), "bus": list()}
|
|
||||||
|
|
||||||
def init(self, parent, id):
|
def init(self, parent, id):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.id = id
|
self.id = id
|
||||||
self.phys_in, self.virt_in = parent.kind.ins
|
self.phys_in, self.virt_in = parent.kind.ins
|
||||||
self.phys_out, self.virt_out = parent.kind.outs
|
self.phys_out, self.virt_out = parent.kind.outs
|
||||||
|
self.label_cache = {
|
||||||
|
'strip': [''] * (self.phys_in + self.virt_in),
|
||||||
|
'bus': [''] * (self.phys_out + self.virt_out),
|
||||||
|
}
|
||||||
self.parent.subject.add(self)
|
self.parent.subject.add(self)
|
||||||
|
self.update_labels()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target(self):
|
def target(self):
|
||||||
@ -242,17 +262,17 @@ class ChannelFrame(ttk.Frame):
|
|||||||
if isinstance(frame, ttk.LabelFrame)
|
if isinstance(frame, ttk.LabelFrame)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def update_labels(self):
|
||||||
|
for labelframe in self.labelframes:
|
||||||
|
labelframe.on_update('labelframe')
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_update(self, subject):
|
||||||
if subject == "pdirty":
|
if subject == 'pdirty':
|
||||||
target = getattr(self.target, self.id)
|
self.update_labels()
|
||||||
num = getattr(self.parent.kind, f"num_{self.id}")
|
|
||||||
if self.label_cache[self.id] != [target[i].label for i in range(num)]:
|
|
||||||
for labelframe in self.labelframes:
|
|
||||||
labelframe.on_update("labelframe")
|
|
||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
[
|
[
|
||||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
self.columnconfigure(i, minsize=_configuration.channel_width)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
[self.rowconfigure(0, minsize=100) for i, _ in enumerate(self.labelframes)]
|
[self.rowconfigure(0, minsize=100) for i, _ in enumerate(self.labelframes)]
|
||||||
@ -261,10 +281,10 @@ class ChannelFrame(ttk.Frame):
|
|||||||
[self.parent.subject.remove(frame) for frame in self.labelframes]
|
[self.parent.subject.remove(frame) for frame in self.labelframes]
|
||||||
self.parent.subject.remove(self)
|
self.parent.subject.remove(self)
|
||||||
self.destroy()
|
self.destroy()
|
||||||
setattr(self.parent, f"{self.identifier}_frame", None)
|
setattr(self.parent, f'{self.identifier}_frame', None)
|
||||||
|
|
||||||
|
|
||||||
def _make_channelframe(parent, id):
|
def _make_channelframe(parent, identifier):
|
||||||
"""
|
"""
|
||||||
Creates a Channel Frame class of type strip or bus
|
Creates a Channel Frame class of type strip or bus
|
||||||
"""
|
"""
|
||||||
@ -272,29 +292,33 @@ def _make_channelframe(parent, id):
|
|||||||
phys_in, virt_in = parent.kind.ins
|
phys_in, virt_in = parent.kind.ins
|
||||||
phys_out, virt_out = parent.kind.outs
|
phys_out, virt_out = parent.kind.outs
|
||||||
|
|
||||||
def init_labels(self, id):
|
def init_labels(self):
|
||||||
"""
|
"""
|
||||||
Grids each labelframe, grid_removes any without a label
|
Grids each labelframe, grid_removes any without a label
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for i, labelframe in enumerate(
|
for i, labelframe in enumerate(
|
||||||
getattr(self, "strips" if id == "strip" else "buses")
|
getattr(self, 'strips' if identifier == 'strip' else 'buses')
|
||||||
):
|
):
|
||||||
labelframe.grid(row=0, column=i)
|
labelframe.grid(row=0, column=i)
|
||||||
if not labelframe.target.label:
|
label = labelframe.target.label
|
||||||
|
if not label:
|
||||||
self.columnconfigure(i, minsize=0)
|
self.columnconfigure(i, minsize=0)
|
||||||
labelframe.grid_remove()
|
labelframe.grid_remove()
|
||||||
|
self.label_cache[identifier][i] = label
|
||||||
|
|
||||||
def init_strip(self, *args, **kwargs):
|
def init_strip(self, *args, **kwargs):
|
||||||
self.init(parent, id)
|
self.init(parent, identifier)
|
||||||
self.strips = tuple(Strip(self, i, id) for i in range(phys_in + virt_in))
|
self.strips = tuple(
|
||||||
|
Strip(self, i, identifier) for i in range(phys_in + virt_in)
|
||||||
|
)
|
||||||
self.grid(row=0, column=0, sticky=(tk.W))
|
self.grid(row=0, column=0, sticky=(tk.W))
|
||||||
self.grid_configure()
|
self.grid_configure()
|
||||||
init_labels(self, id)
|
init_labels(self)
|
||||||
|
|
||||||
def init_bus(self, *args, **kwargs):
|
def init_bus(self, *args, **kwargs):
|
||||||
self.init(parent, id)
|
self.init(parent, identifier)
|
||||||
self.buses = tuple(Bus(self, i, id) for i in range(phys_out + virt_out))
|
self.buses = tuple(Bus(self, i, identifier) for i in range(phys_out + virt_out))
|
||||||
if _configuration.extended:
|
if _configuration.extended:
|
||||||
if _configuration.extends_horizontal:
|
if _configuration.extends_horizontal:
|
||||||
self.grid(row=0, column=2, sticky=(tk.W))
|
self.grid(row=0, column=2, sticky=(tk.W))
|
||||||
@ -303,22 +327,22 @@ def _make_channelframe(parent, id):
|
|||||||
else:
|
else:
|
||||||
self.grid(row=0, column=0)
|
self.grid(row=0, column=0)
|
||||||
self.grid_configure()
|
self.grid_configure()
|
||||||
init_labels(self, id)
|
init_labels(self)
|
||||||
|
|
||||||
if id == "strip":
|
if identifier == 'strip':
|
||||||
CHANNELFRAME_cls = type(
|
CHANNELFRAME_cls = type(
|
||||||
f"ChannelFrame{id.capitalize()}",
|
f'ChannelFrame{identifier.capitalize()}',
|
||||||
(ChannelFrame,),
|
(ChannelFrame,),
|
||||||
{
|
{
|
||||||
"__init__": init_strip,
|
'__init__': init_strip,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
CHANNELFRAME_cls = type(
|
CHANNELFRAME_cls = type(
|
||||||
f"ChannelFrame{id.capitalize()}",
|
f'ChannelFrame{identifier.capitalize()}',
|
||||||
(ChannelFrame,),
|
(ChannelFrame,),
|
||||||
{
|
{
|
||||||
"__init__": init_bus,
|
'__init__': init_bus,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
return CHANNELFRAME_cls(parent)
|
return CHANNELFRAME_cls(parent)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import tkinter as tk
|
import logging
|
||||||
from functools import partial
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from . import builders
|
from . import builders
|
||||||
from .data import _base_values, _configuration
|
from .data import _base_values, _configuration
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Config(ttk.Frame):
|
class Config(ttk.Frame):
|
||||||
def __init__(self, parent, index, _id):
|
def __init__(self, parent, index, _id):
|
||||||
@ -12,6 +13,7 @@ class Config(ttk.Frame):
|
|||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.index = index
|
self.index = index
|
||||||
self.id = _id
|
self.id = _id
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.styletable = parent.styletable
|
self.styletable = parent.styletable
|
||||||
self.phys_in, self.virt_in = parent.kind.ins
|
self.phys_in, self.virt_in = parent.kind.ins
|
||||||
self.phys_out, self.virt_out = parent.kind.outs
|
self.phys_out, self.virt_out = parent.kind.outs
|
||||||
@ -29,29 +31,51 @@ class Config(ttk.Frame):
|
|||||||
return self.parent.target
|
return self.parent.target
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if hasattr(self.target, param):
|
param = param.split('.')
|
||||||
return getattr(self.target, param)
|
try:
|
||||||
|
if len(param) == 2:
|
||||||
|
target = getattr(self.target, param[0])
|
||||||
|
return getattr(target, param[1])
|
||||||
|
else:
|
||||||
|
return getattr(self.target, param[0])
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger.error(f'{type(e).__name__}: {e}')
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if hasattr(self.target, param):
|
param = param.split('.')
|
||||||
setattr(self.target, param, value)
|
try:
|
||||||
|
if len(param) == 2:
|
||||||
|
target = getattr(self.target, param[0])
|
||||||
|
setattr(target, param[1], value)
|
||||||
|
else:
|
||||||
|
setattr(self.target, param[0], value)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger(f'{type(e).__name__}: {e}')
|
||||||
|
|
||||||
def scale_press(self, *args):
|
def scale_press(self, *args):
|
||||||
self.after(1, self.remove_events)
|
self.after(1, self.remove_events)
|
||||||
|
|
||||||
def remove_events(self):
|
def remove_events(self):
|
||||||
self.parent.target.event.remove("pdirty")
|
self.parent.target.event.remove('pdirty')
|
||||||
self.parent.target.event.remove("ldirty")
|
self.parent.target.event.remove('ldirty')
|
||||||
|
|
||||||
def scale_release(self, *args):
|
def scale_release(self, *args):
|
||||||
_base_values.run_update = False
|
_base_values.run_update = False
|
||||||
self.after(1, self.add_events)
|
self.after(1, self.add_events)
|
||||||
|
|
||||||
def add_events(self):
|
def add_events(self):
|
||||||
self.parent.target.event.add("pdirty")
|
self.parent.target.event.add('pdirty')
|
||||||
self.parent.target.event.add("ldirty")
|
self.parent.target.event.add('ldirty')
|
||||||
self.after(350, self.resume_updates)
|
self.after(350, self.resume_updates)
|
||||||
|
|
||||||
|
def pause_updates(self, func, *args):
|
||||||
|
"""function wrapper, adds a 50ms delay on updates"""
|
||||||
|
_base_values.run_update = False
|
||||||
|
|
||||||
|
func(*args)
|
||||||
|
|
||||||
|
self.after(50, self.resume_updates)
|
||||||
|
|
||||||
def resume_updates(self):
|
def resume_updates(self):
|
||||||
_base_values.run_update = True
|
_base_values.run_update = True
|
||||||
|
|
||||||
@ -60,13 +84,13 @@ class Config(ttk.Frame):
|
|||||||
self.parent.nav_frame.info_text.set(round(val, 1))
|
self.parent.nav_frame.info_text.set(round(val, 1))
|
||||||
|
|
||||||
def scale_leave(self, *args):
|
def scale_leave(self, *args):
|
||||||
self.parent.nav_frame.info_text.set("")
|
self.parent.nav_frame.info_text.set('')
|
||||||
|
|
||||||
def scale_callback(self, param, *args):
|
def scale_callback(self, param, *args):
|
||||||
"""callback function for scale widget"""
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
val = self.slider_vars[self.slider_params.index(param)].get()
|
val = self.slider_vars[self.slider_params.index(param)].get()
|
||||||
self.setter(param, val)
|
self.setter(param, round(val, 1))
|
||||||
self.parent.nav_frame.info_text.set(round(val, 1))
|
self.parent.nav_frame.info_text.set(round(val, 1))
|
||||||
|
|
||||||
def reset_scale(self, param, val, *args):
|
def reset_scale(self, param, val, *args):
|
||||||
@ -78,12 +102,12 @@ class Config(ttk.Frame):
|
|||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
f'{param}.TButton', background=f'{"green" if val else "white"}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_update(self, subject):
|
||||||
"""update parameters"""
|
"""update parameters"""
|
||||||
if subject == "pdirty":
|
if subject == 'pdirty':
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
|
|
||||||
@ -98,6 +122,7 @@ class StripConfig(Config):
|
|||||||
self.make_row_2()
|
self.make_row_2()
|
||||||
self.builder.grid_configure()
|
self.builder.grid_configure()
|
||||||
|
|
||||||
|
self.parent.target.clear_dirty()
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -109,7 +134,7 @@ class StripConfig(Config):
|
|||||||
|
|
||||||
def make_row_0(self):
|
def make_row_0(self):
|
||||||
if self.index < self.phys_in:
|
if self.index < self.phys_in:
|
||||||
if self.parent.kind.name == "basic":
|
if self.parent.kind.name == 'basic':
|
||||||
self.builder.create_audibility_slider()
|
self.builder.create_audibility_slider()
|
||||||
else:
|
else:
|
||||||
self.builder.create_comp_slider()
|
self.builder.create_comp_slider()
|
||||||
@ -128,7 +153,7 @@ class StripConfig(Config):
|
|||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
f'{param}.TButton', background=f'{"green" if val else "white"}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def toggle_b(self, param):
|
def toggle_b(self, param):
|
||||||
@ -136,7 +161,7 @@ class StripConfig(Config):
|
|||||||
self.setter(param, val)
|
self.setter(param, val)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
f'{param}.TButton', background=f'{"green" if val else "white"}'
|
||||||
)
|
)
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
@ -155,25 +180,31 @@ class StripConfig(Config):
|
|||||||
self.param_vars[i].set(self.getter(param))
|
self.param_vars[i].set(self.getter(param))
|
||||||
for i, param in enumerate(self.params)
|
for i, param in enumerate(self.params)
|
||||||
]
|
]
|
||||||
|
if not _base_values.vban_connected: # slider vars not defined in RT Packet
|
||||||
|
[
|
||||||
|
self.slider_vars[i].set(self.getter(param))
|
||||||
|
for i, param in enumerate(self.slider_params)
|
||||||
|
if self.index < self.phys_in
|
||||||
|
]
|
||||||
|
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
[
|
[
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f'{param}.TButton',
|
||||||
background=f'{"green" if self.phys_out_params_vars[i].get() else "white"}',
|
background=f'{"green" if self.phys_out_params_vars[i].get() else "white"}',
|
||||||
)
|
)
|
||||||
for i, param in enumerate(self.phys_out_params)
|
for i, param in enumerate(self.phys_out_params)
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f'{param}.TButton',
|
||||||
background=f'{"green" if self.virt_out_params_vars[i].get() else "white"}',
|
background=f'{"green" if self.virt_out_params_vars[i].get() else "white"}',
|
||||||
)
|
)
|
||||||
for i, param in enumerate(self.virt_out_params)
|
for i, param in enumerate(self.virt_out_params)
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f'{param}.TButton',
|
||||||
background=f'{"green" if self.param_vars[i].get() else "white"}',
|
background=f'{"green" if self.param_vars[i].get() else "white"}',
|
||||||
)
|
)
|
||||||
for i, param in enumerate(self.params)
|
for i, param in enumerate(self.params)
|
||||||
@ -193,6 +224,7 @@ class BusConfig(Config):
|
|||||||
self.make_row_1()
|
self.make_row_1()
|
||||||
self.builder.grid_configure()
|
self.builder.grid_configure()
|
||||||
|
|
||||||
|
self.parent.target.clear_dirty()
|
||||||
self.sync()
|
self.sync()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -209,9 +241,7 @@ class BusConfig(Config):
|
|||||||
self.builder.create_param_buttons()
|
self.builder.create_param_buttons()
|
||||||
|
|
||||||
def current_bus_mode(self):
|
def current_bus_mode(self):
|
||||||
for mode in self.bus_modes:
|
return self.target.mode.get()
|
||||||
if getattr(self.target.mode, mode):
|
|
||||||
return mode
|
|
||||||
|
|
||||||
def rotate_bus_modes_right(self, *args):
|
def rotate_bus_modes_right(self, *args):
|
||||||
current_mode = self.current_bus_mode()
|
current_mode = self.current_bus_mode()
|
||||||
@ -225,14 +255,14 @@ class BusConfig(Config):
|
|||||||
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[next]])
|
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[next]])
|
||||||
else:
|
else:
|
||||||
self.target.mode.normal = True
|
self.target.mode.normal = True
|
||||||
self.bus_mode_label_text.set("Normal")
|
self.bus_mode_label_text.set('Normal')
|
||||||
|
|
||||||
def rotate_bus_modes_left(self, *args):
|
def rotate_bus_modes_left(self, *args):
|
||||||
current_mode = self.current_bus_mode()
|
current_mode = self.current_bus_mode()
|
||||||
prev = self.bus_modes.index(current_mode) - 1
|
prev = self.bus_modes.index(current_mode) - 1
|
||||||
if prev < 0:
|
if prev < 0:
|
||||||
self.target.mode.rearonly = True
|
self.target.mode.rearonly = True
|
||||||
self.bus_mode_label_text.set("Rear Only")
|
self.bus_mode_label_text.set('Rear Only')
|
||||||
else:
|
else:
|
||||||
setattr(
|
setattr(
|
||||||
self.target.mode,
|
self.target.mode,
|
||||||
@ -253,7 +283,7 @@ class BusConfig(Config):
|
|||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
[
|
[
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{param}.TButton",
|
f'{param}.TButton',
|
||||||
background=f'{"green" if self.param_vars[i].get() else "white"}',
|
background=f'{"green" if self.param_vars[i].get() else "white"}',
|
||||||
)
|
)
|
||||||
for i, param in enumerate(self.params)
|
for i, param in enumerate(self.params)
|
||||||
|
@ -6,57 +6,93 @@ try:
|
|||||||
except ModuleNotFoundError:
|
except ModuleNotFoundError:
|
||||||
import tomli as tomllib
|
import tomli as tomllib
|
||||||
|
|
||||||
LOGGER = logging.getLogger("configurations")
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
configuration = {}
|
configuration = {}
|
||||||
|
|
||||||
config_path = [Path.cwd() / "configs"]
|
|
||||||
for path in config_path:
|
|
||||||
if path.is_dir():
|
|
||||||
filenames = list(path.glob("*.toml"))
|
|
||||||
configs = {}
|
|
||||||
for filename in filenames:
|
|
||||||
name = filename.with_suffix("").stem
|
|
||||||
try:
|
|
||||||
with open(filename, "rb") as f:
|
|
||||||
configs[name] = tomllib.load(f)
|
|
||||||
except tomllib.TOMLDecodeError:
|
|
||||||
print(f"Invalid TOML config: configs/{filename.stem}")
|
|
||||||
|
|
||||||
for name, cfg in configs.items():
|
def get_configpath():
|
||||||
LOGGER.info(f"Loaded configuration configs/{name}")
|
for pn in (
|
||||||
configuration[name] = cfg
|
Path.home() / '.config' / 'vm-compact',
|
||||||
|
Path.home() / 'Documents' / 'Voicemeeter' / 'vm-compact',
|
||||||
|
Path.cwd() / '_internal' / 'configs',
|
||||||
|
Path.cwd() / 'configs',
|
||||||
|
):
|
||||||
|
if pn.exists():
|
||||||
|
return pn
|
||||||
|
|
||||||
|
|
||||||
|
if configpath := get_configpath():
|
||||||
|
filepaths = list(configpath.glob('*.toml'))
|
||||||
|
if any(f.stem in ('app', 'vban') for f in filepaths):
|
||||||
|
configs = {}
|
||||||
|
for filepath in filepaths:
|
||||||
|
filename = filepath.with_suffix('').stem
|
||||||
|
if filename in ('app', 'vban'):
|
||||||
|
try:
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
configs[filename] = tomllib.load(f)
|
||||||
|
logger.info(f'configuration: {filename} loaded into memory')
|
||||||
|
except tomllib.TOMLDecodeError:
|
||||||
|
logger.error(f'Invalid TOML config: configs/{filename.stem}')
|
||||||
|
configuration |= configs
|
||||||
|
|
||||||
_defaults = {
|
_defaults = {
|
||||||
"configs": {
|
'configs': {
|
||||||
"config": None,
|
'config': None,
|
||||||
},
|
},
|
||||||
"theme": {
|
'theme': {
|
||||||
"enabled": True,
|
'enabled': True,
|
||||||
"mode": "light",
|
'mode': 'light',
|
||||||
},
|
},
|
||||||
"extends": {
|
'extends': {
|
||||||
"extended": True,
|
'extended': True,
|
||||||
"extends_horizontal": True,
|
'extends_horizontal': True,
|
||||||
},
|
},
|
||||||
"channel": {
|
'channel': {
|
||||||
"width": 80,
|
'width': 80,
|
||||||
"height": 130,
|
'height': 130,
|
||||||
|
'xpadding': 3,
|
||||||
},
|
},
|
||||||
"mwscroll_step": {
|
'mwscroll_step': {
|
||||||
"size": 3,
|
'size': 3,
|
||||||
},
|
},
|
||||||
"submixes": {
|
'submixes': {
|
||||||
"default": 0,
|
'default': 0,
|
||||||
},
|
},
|
||||||
|
'navigation': {'show': True},
|
||||||
}
|
}
|
||||||
|
|
||||||
if "app" in configuration:
|
|
||||||
configuration["app"] = _defaults | configuration["app"]
|
if 'app' in configuration:
|
||||||
|
for key in _defaults:
|
||||||
|
if key in configuration['app']:
|
||||||
|
configuration['app'][key] = _defaults[key] | configuration['app'][key]
|
||||||
|
else:
|
||||||
|
configuration['app'][key] = _defaults[key]
|
||||||
else:
|
else:
|
||||||
configuration["app"] = _defaults
|
configuration['app'] = _defaults
|
||||||
|
|
||||||
|
|
||||||
def get_configuration(key):
|
def get_configuration(key):
|
||||||
if key in configuration:
|
if key in configuration:
|
||||||
return configuration[key]
|
return configuration[key]
|
||||||
|
|
||||||
|
|
||||||
|
def loader(kind_id, target):
|
||||||
|
configs = {'reset': target.configs['reset']}
|
||||||
|
if configpath := get_configpath():
|
||||||
|
userconfigpath = configpath / kind_id
|
||||||
|
if userconfigpath.exists():
|
||||||
|
filepaths = list(userconfigpath.glob('*.toml'))
|
||||||
|
for filepath in filepaths:
|
||||||
|
identifier = filepath.with_suffix('').stem
|
||||||
|
try:
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
configs[identifier] = tomllib.load(f)
|
||||||
|
logger.info(f'loader: {identifier} loaded into memory')
|
||||||
|
except tomllib.TOMLDecodeError:
|
||||||
|
logger.error(f'Invalid TOML config: configs/{filename.stem}')
|
||||||
|
|
||||||
|
target.configs = configs
|
||||||
|
return target.configs
|
||||||
|
@ -4,7 +4,7 @@ from voicemeeterlib import kinds
|
|||||||
|
|
||||||
from .configurations import get_configuration
|
from .configurations import get_configuration
|
||||||
|
|
||||||
configuration = get_configuration("app")
|
configuration = get_configuration('app')
|
||||||
|
|
||||||
|
|
||||||
class SingletonMeta(type):
|
class SingletonMeta(type):
|
||||||
@ -20,33 +20,38 @@ class SingletonMeta(type):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class Configurations(metaclass=SingletonMeta):
|
class Configurations(metaclass=SingletonMeta):
|
||||||
# is the gui extended
|
# is the gui extended
|
||||||
extended: bool = configuration["extends"]["extended"]
|
extended: bool = configuration['extends']['extended']
|
||||||
# direction the gui extends
|
# direction the gui extends
|
||||||
extends_horizontal: bool = configuration["extends"]["extends_horizontal"]
|
extends_horizontal: bool = configuration['extends']['extends_horizontal']
|
||||||
# are themes enabled
|
# are themes enabled
|
||||||
themes_enabled: bool = configuration["theme"]["enabled"]
|
themes_enabled: bool = configuration['theme']['enabled']
|
||||||
# light or dark
|
# light or dark
|
||||||
theme_mode: str = configuration["theme"]["mode"]
|
theme_mode: str = configuration['theme']['mode']
|
||||||
# size of mousewheel scroll step
|
# size of mousewheel scroll step
|
||||||
mwscroll_step: int = configuration["mwscroll_step"]["size"]
|
mwscroll_step: int = configuration['mwscroll_step']['size']
|
||||||
# bus assigned as current submix
|
# bus assigned as current submix
|
||||||
submixes: int = configuration["submixes"]["default"]
|
submixes: int = configuration['submixes']['default']
|
||||||
|
|
||||||
# width of a single labelframe
|
# width of a single channel labelframe
|
||||||
level_width: int = configuration["channel"]["width"]
|
channel_width: int = configuration['channel']['width']
|
||||||
# height of a single labelframe
|
# height of a single channel labelframe
|
||||||
level_height: int = configuration["channel"]["height"]
|
channel_height: int = configuration['channel']['height']
|
||||||
|
# xpadding for a single channel labelframe
|
||||||
|
channel_xpadding: int = configuration['channel']['xpadding']
|
||||||
|
|
||||||
|
# do we grid the navigation frame?
|
||||||
|
navigation_show: bool = configuration['navigation']['show']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def config(self):
|
def config(self):
|
||||||
if "configs" in configuration:
|
if 'configs' in configuration:
|
||||||
return configuration["configs"]["config"]
|
return configuration['configs']['config']
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class BaseValues(metaclass=SingletonMeta):
|
class BaseValues(metaclass=SingletonMeta):
|
||||||
# pause updates after releasing scale
|
# pause updates after releasing scale
|
||||||
run_update: bool = True
|
run_update: bool = False
|
||||||
# are we dragging main window with mouse 1
|
# are we dragging main window with mouse 1
|
||||||
dragging: bool = False
|
dragging: bool = False
|
||||||
# a vban connection established
|
# a vban connection established
|
||||||
|
@ -1,4 +1,2 @@
|
|||||||
class VMCompactErrors(Exception):
|
class VMCompactError(Exception):
|
||||||
"""Base classs for VMCompact Errors"""
|
"""Exception raised when general errors occur"""
|
||||||
|
|
||||||
pass
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from math import log
|
|
||||||
from tkinter import ttk
|
from tkinter import ttk
|
||||||
|
|
||||||
from . import builders
|
from . import builders
|
||||||
@ -20,7 +19,7 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
else:
|
else:
|
||||||
self.level_offset = parent.phys_in * 2 + (index - parent.phys_in) * 8
|
self.level_offset = parent.phys_in * 2 + (index - parent.phys_in) * 8
|
||||||
|
|
||||||
self.builder = builders.ChannelLabelFrameBuilder(self, index, id="gainlayer")
|
self.builder = builders.ChannelLabelFrameBuilder(self, index, id='gainlayer')
|
||||||
self.builder.setup()
|
self.builder.setup()
|
||||||
self.builder.add_progressbar()
|
self.builder.add_progressbar()
|
||||||
self.builder.add_scale()
|
self.builder.add_scale()
|
||||||
@ -39,43 +38,54 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def identifier(self):
|
def identifier(self):
|
||||||
return "gainlayer"
|
return 'gainlayer'
|
||||||
|
|
||||||
def getter(self, param):
|
def getter(self, param):
|
||||||
if hasattr(self.target, param):
|
try:
|
||||||
return getattr(self.target, param)
|
return getattr(self.target, param)
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger(f'{type(e).__name__}: {e}')
|
||||||
|
|
||||||
def setter(self, param, value):
|
def setter(self, param, value):
|
||||||
if hasattr(self.target, param):
|
if param in dir(self.target): # avoid calling getattr (with hasattr)
|
||||||
setattr(self.target, param, value)
|
setattr(self.target, param, value)
|
||||||
|
|
||||||
def reset_gain(self, *args):
|
def reset_gain(self, *args):
|
||||||
self.setter("gain", 0)
|
self.setter('gain', 0)
|
||||||
self.gain.set(0)
|
self.gain.set(0)
|
||||||
self.gainlabel.set(self.gain.get())
|
self.gainlabel.set(self.gain.get())
|
||||||
|
|
||||||
def scale_callback(self, *args):
|
def scale_callback(self, *args):
|
||||||
"""callback function for scale widget"""
|
"""callback function for scale widget"""
|
||||||
|
|
||||||
self.setter("gain", self.gain.get())
|
val = round(self.gain.get(), 1)
|
||||||
self.gainlabel.set(round(self.gain.get(), 1))
|
self.setter('gain', val)
|
||||||
|
self.gainlabel.set(val)
|
||||||
|
|
||||||
def scale_press(self, *args):
|
def scale_press(self, *args):
|
||||||
self.after(1, self.remove_events)
|
self.after(1, self.remove_events)
|
||||||
|
|
||||||
def remove_events(self):
|
def remove_events(self):
|
||||||
self.parent.target.event.remove("pdirty")
|
self.parent.target.event.remove('pdirty')
|
||||||
self.parent.target.event.remove("ldirty")
|
self.parent.target.event.remove('ldirty')
|
||||||
|
|
||||||
def scale_release(self, *args):
|
def scale_release(self, *args):
|
||||||
_base_values.run_update = False
|
_base_values.run_update = False
|
||||||
self.after(1, self.add_events)
|
self.after(1, self.add_events)
|
||||||
|
|
||||||
def add_events(self):
|
def add_events(self):
|
||||||
self.parent.target.event.add("pdirty")
|
self.parent.target.event.add('pdirty')
|
||||||
self.parent.target.event.add("ldirty")
|
self.parent.target.event.add('ldirty')
|
||||||
self.after(500, self.resume_updates)
|
self.after(500, self.resume_updates)
|
||||||
|
|
||||||
|
def pause_updates(self, func, *args):
|
||||||
|
"""function wrapper, adds a 50ms delay on updates"""
|
||||||
|
_base_values.run_update = False
|
||||||
|
|
||||||
|
func(*args)
|
||||||
|
|
||||||
|
self.after(50, self.resume_updates)
|
||||||
|
|
||||||
def resume_updates(self):
|
def resume_updates(self):
|
||||||
_base_values.run_update = True
|
_base_values.run_update = True
|
||||||
|
|
||||||
@ -93,7 +103,7 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
self.gain.set(12)
|
self.gain.set(12)
|
||||||
elif self.gain.get() < -60:
|
elif self.gain.get() < -60:
|
||||||
self.gain.set(-60)
|
self.gain.set(-60)
|
||||||
self.setter("gain", self.gain.get())
|
self.setter('gain', self.gain.get())
|
||||||
self.after(1, self.resume_updates)
|
self.after(1, self.resume_updates)
|
||||||
|
|
||||||
def set_on(self):
|
def set_on(self):
|
||||||
@ -106,20 +116,20 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
)
|
)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}On{self.index}.TButton",
|
f'{self.identifier}On{self.index}.TButton',
|
||||||
background=f'{"green" if self.on.get() else "white"}',
|
background=f'{"green" if self.on.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_update(self, subject):
|
||||||
if subject == "ldirty":
|
if subject == 'ldirty':
|
||||||
self.upd_levels()
|
self.upd_levels()
|
||||||
elif subject == "pdirty":
|
elif subject == 'pdirty':
|
||||||
self.sync_params()
|
self.sync_params()
|
||||||
elif subject == "labelframe":
|
elif subject == 'labelframe':
|
||||||
self.after(5, self.sync_labels)
|
self.after(5, self.sync_labels)
|
||||||
|
|
||||||
def sync_params(self):
|
def sync_params(self):
|
||||||
self.gain.set(self.getter("gain"))
|
self.gain.set(self.getter('gain'))
|
||||||
self.gainlabel.set(round(self.gain.get(), 1))
|
self.gainlabel.set(round(self.gain.get(), 1))
|
||||||
self.on.set(
|
self.on.set(
|
||||||
getattr(
|
getattr(
|
||||||
@ -129,7 +139,7 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
)
|
)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"{self.identifier}On{self.index}.TButton",
|
f'{self.identifier}On{self.index}.TButton',
|
||||||
background=f'{"green" if self.on.get() else "white"}',
|
background=f'{"green" if self.on.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -137,7 +147,7 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
"""sync params with voicemeeter"""
|
"""sync params with voicemeeter"""
|
||||||
retval = self.parent.target.strip[self.index].label
|
retval = self.parent.target.strip[self.index].label
|
||||||
if len(retval) > 10:
|
if len(retval) > 10:
|
||||||
retval = f"{retval[:8]}.."
|
retval = f'{retval[:8]}..'
|
||||||
if not retval:
|
if not retval:
|
||||||
self.parent.columnconfigure(self.index, minsize=0)
|
self.parent.columnconfigure(self.index, minsize=0)
|
||||||
self.parent.parent.subject.remove(self)
|
self.parent.parent.subject.remove(self)
|
||||||
@ -157,12 +167,14 @@ class GainLayer(ttk.LabelFrame):
|
|||||||
self.level.set(
|
self.level.set(
|
||||||
(
|
(
|
||||||
0
|
0
|
||||||
if self.parent.target.strip[self.index].mute or not self.on.get()
|
if self.parent.parent.strip_frame.strips[self.index].mute.get()
|
||||||
else 100 + val - 18 + self.gain.get()
|
or not self.on.get()
|
||||||
|
else 72 + val - 12 + self.gain.get()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
|
self.grid(padx=_configuration.channel_xpadding, sticky=(tk.N, tk.S))
|
||||||
[
|
[
|
||||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||||
for child in self.winfo_children()
|
for child in self.winfo_children()
|
||||||
@ -189,8 +201,8 @@ class SubMixFrame(ttk.Frame):
|
|||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.phys_in, self.virt_in = parent.kind.ins
|
self.phys_in, self.virt_in = parent.kind.ins
|
||||||
self.phys_out, self.virt_out = parent.kind.outs
|
self.phys_out, self.virt_out = parent.kind.outs
|
||||||
self.buses = tuple(f"A{i+1}" for i in range(self.phys_out)) + tuple(
|
self.buses = tuple(f'A{i + 1}' for i in range(self.phys_out)) + tuple(
|
||||||
f"B{i+1}" for i in range(self.virt_out)
|
f'B{i + 1}' for i in range(self.virt_out)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.gainlayers = [
|
self.gainlayers = [
|
||||||
@ -209,7 +221,7 @@ class SubMixFrame(ttk.Frame):
|
|||||||
else:
|
else:
|
||||||
if parent.bus_frame and parent.bus_frame.grid_info():
|
if parent.bus_frame and parent.bus_frame.grid_info():
|
||||||
self.grid(
|
self.grid(
|
||||||
row=parent.bus_frame.grid_info()["row"], column=0, sticky=(tk.W)
|
row=parent.bus_frame.grid_info()['row'], column=0, sticky=(tk.W)
|
||||||
)
|
)
|
||||||
parent.bus_frame.grid_remove()
|
parent.bus_frame.grid_remove()
|
||||||
else:
|
else:
|
||||||
@ -244,17 +256,17 @@ class SubMixFrame(ttk.Frame):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_update(self, subject):
|
def on_update(self, subject):
|
||||||
if subject == "pdirty":
|
if subject == 'pdirty':
|
||||||
for labelframe in self.labelframes:
|
for labelframe in self.labelframes:
|
||||||
labelframe.on_update("labelframe")
|
labelframe.on_update('labelframe')
|
||||||
|
|
||||||
def grid_configure(self):
|
def grid_configure(self):
|
||||||
[
|
[
|
||||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
self.columnconfigure(i, minsize=_configuration.channel_width)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
self.rowconfigure(0, minsize=_configuration.level_height)
|
self.rowconfigure(0, minsize=_configuration.channel_height)
|
||||||
for i, _ in enumerate(self.labelframes)
|
for i, _ in enumerate(self.labelframes)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
1
vmcompact/gui/banana/__init__.py
Normal file
1
vmcompact/gui/banana/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .main import run
|
11
vmcompact/gui/banana/main.py
Normal file
11
vmcompact/gui/banana/main.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import voicemeeterlib
|
||||||
|
|
||||||
|
import vmcompact
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
KIND_ID = 'banana'
|
||||||
|
|
||||||
|
with voicemeeterlib.api(KIND_ID) as vmr:
|
||||||
|
app = vmcompact.connect(KIND_ID, vmr)
|
||||||
|
app.mainloop()
|
1
vmcompact/gui/basic/__init__.py
Normal file
1
vmcompact/gui/basic/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .main import run
|
11
vmcompact/gui/basic/main.py
Normal file
11
vmcompact/gui/basic/main.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import voicemeeterlib
|
||||||
|
|
||||||
|
import vmcompact
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
KIND_ID = 'basic'
|
||||||
|
|
||||||
|
with voicemeeterlib.api(KIND_ID) as vmr:
|
||||||
|
app = vmcompact.connect(KIND_ID, vmr)
|
||||||
|
app.mainloop()
|
1
vmcompact/gui/potato/__init__.py
Normal file
1
vmcompact/gui/potato/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .main import run
|
11
vmcompact/gui/potato/main.py
Normal file
11
vmcompact/gui/potato/main.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import voicemeeterlib
|
||||||
|
|
||||||
|
import vmcompact
|
||||||
|
|
||||||
|
|
||||||
|
def run():
|
||||||
|
KIND_ID = 'potato'
|
||||||
|
|
||||||
|
with voicemeeterlib.api(KIND_ID) as vmr:
|
||||||
|
app = vmcompact.connect(KIND_ID, vmr)
|
||||||
|
app.mainloop()
|
@ -2,34 +2,37 @@ import logging
|
|||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from tkinter import messagebox, ttk
|
from tkinter import messagebox
|
||||||
|
|
||||||
import sv_ttk
|
import sv_ttk
|
||||||
import vban_cmd
|
import vban_cmd
|
||||||
from vban_cmd.error import VBANCMDError
|
from vban_cmd.error import VBANCMDConnectionError
|
||||||
|
|
||||||
from .data import _base_values, _configuration, get_configuration, kind_get
|
from .data import _base_values, _configuration, get_configuration, kind_get
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Menus(tk.Menu):
|
class Menus(tk.Menu):
|
||||||
logger = logging.getLogger("menu.menus")
|
|
||||||
|
|
||||||
def __init__(self, parent, vmr):
|
def __init__(self, parent, vmr):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.vmr = vmr
|
self.vmr = vmr
|
||||||
self.vban_config = get_configuration("vban")
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.app_config = get_configuration("app")
|
self.vban_config = get_configuration('vban')
|
||||||
|
self.app_config = get_configuration('app')
|
||||||
self._is_topmost = tk.BooleanVar()
|
self._is_topmost = tk.BooleanVar()
|
||||||
self._lock = tk.BooleanVar()
|
self._lock = tk.BooleanVar()
|
||||||
self._unlock = tk.BooleanVar()
|
self._unlock = tk.BooleanVar()
|
||||||
|
self._navigation_show = tk.BooleanVar(value=_configuration.navigation_show)
|
||||||
|
self._navigation_hide = tk.BooleanVar(value=not _configuration.navigation_show)
|
||||||
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
||||||
|
|
||||||
# voicemeeter menu
|
# voicemeeter menu
|
||||||
self.menu_voicemeeter = tk.Menu(self, tearoff=0)
|
self.menu_voicemeeter = tk.Menu(self, tearoff=0)
|
||||||
self.add_cascade(menu=self.menu_voicemeeter, label="Voicemeeter")
|
self.add_cascade(menu=self.menu_voicemeeter, label='Voicemeeter')
|
||||||
self.menu_voicemeeter.add_checkbutton(
|
self.menu_voicemeeter.add_checkbutton(
|
||||||
label="Always On Top",
|
label='Always On Top',
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
variable=self._is_topmost,
|
variable=self._is_topmost,
|
||||||
@ -37,78 +40,80 @@ class Menus(tk.Menu):
|
|||||||
)
|
)
|
||||||
self.menu_voicemeeter.add_separator()
|
self.menu_voicemeeter.add_separator()
|
||||||
self.menu_voicemeeter.add_command(
|
self.menu_voicemeeter.add_command(
|
||||||
label="Show",
|
label='Show',
|
||||||
underline=0,
|
underline=0,
|
||||||
command=partial(self.action_invoke_voicemeeter, "show"),
|
command=partial(self.action_invoke_voicemeeter, 'show'),
|
||||||
)
|
)
|
||||||
self.menu_voicemeeter.add_command(
|
self.menu_voicemeeter.add_command(
|
||||||
label="Hide",
|
label='Hide',
|
||||||
underline=0,
|
underline=0,
|
||||||
command=partial(self.action_invoke_voicemeeter, "hide"),
|
command=partial(self.action_invoke_voicemeeter, 'hide'),
|
||||||
)
|
)
|
||||||
self.menu_voicemeeter.add_command(
|
self.menu_voicemeeter.add_command(
|
||||||
label="Restart",
|
label='Restart',
|
||||||
underline=0,
|
underline=0,
|
||||||
command=partial(self.action_invoke_voicemeeter, "restart"),
|
command=partial(self.action_invoke_voicemeeter, 'restart'),
|
||||||
)
|
)
|
||||||
self.menu_voicemeeter.add_command(
|
self.menu_voicemeeter.add_command(
|
||||||
label="Shutdown",
|
label='Shutdown',
|
||||||
underline=0,
|
underline=0,
|
||||||
command=partial(self.action_invoke_voicemeeter, "shutdown"),
|
command=partial(self.action_invoke_voicemeeter, 'shutdown'),
|
||||||
)
|
)
|
||||||
self.menu_voicemeeter.add_separator()
|
self.menu_voicemeeter.add_separator()
|
||||||
self.menu_lock = tk.Menu(self.menu_voicemeeter, tearoff=0)
|
self.menu_lock = tk.Menu(self.menu_voicemeeter, tearoff=0)
|
||||||
self.menu_voicemeeter.add_cascade(
|
self.menu_voicemeeter.add_cascade(
|
||||||
menu=self.menu_lock, label="GUI Lock", underline=0
|
menu=self.menu_lock, label='GUI Lock', underline=0
|
||||||
)
|
)
|
||||||
self.menu_lock.add_checkbutton(
|
self.menu_lock.add_checkbutton(
|
||||||
label="Lock",
|
label='Lock',
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
variable=self._lock,
|
variable=self._lock,
|
||||||
command=partial(self.action_set_voicemeeter, "lock"),
|
command=partial(self.action_set_voicemeeter, 'lock'),
|
||||||
)
|
)
|
||||||
self.menu_lock.add_checkbutton(
|
self.menu_lock.add_checkbutton(
|
||||||
label="Unlock",
|
label='Unlock',
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
variable=self._unlock,
|
variable=self._unlock,
|
||||||
command=partial(self.action_set_voicemeeter, "lock", False),
|
command=partial(self.action_set_voicemeeter, 'lock', False),
|
||||||
)
|
)
|
||||||
|
|
||||||
# configs menu
|
# configs menu
|
||||||
self.menu_configs = tk.Menu(self, tearoff=0)
|
self.menu_configs = tk.Menu(self, tearoff=0)
|
||||||
self.add_cascade(menu=self.menu_configs, label="Configs")
|
self.add_cascade(menu=self.menu_configs, label='Configs')
|
||||||
self.menu_configs_load = tk.Menu(self.menu_configs, tearoff=0)
|
self.menu_configs_load = tk.Menu(self.menu_configs, tearoff=0)
|
||||||
self.menu_configs.add_cascade(menu=self.menu_configs_load, label="Load config")
|
self.menu_configs.add_cascade(menu=self.menu_configs_load, label='Load config')
|
||||||
self.config_defaults = {"reset"}
|
self.config_defaults = {'reset'}
|
||||||
if len(self.target.configs) > len(self.config_defaults) and all(
|
if len(self.parent.userconfigs) > len(self.config_defaults) and all(
|
||||||
key in self.target.configs for key in self.config_defaults
|
key in self.parent.userconfigs for key in self.config_defaults
|
||||||
):
|
):
|
||||||
[
|
[
|
||||||
self.menu_configs_load.add_command(
|
self.menu_configs_load.add_command(
|
||||||
label=profile, command=partial(self.load_profile, profile)
|
label=profile, command=partial(self.load_profile, profile)
|
||||||
)
|
)
|
||||||
for profile in self.target.configs.keys()
|
for profile in self.parent.userconfigs.keys()
|
||||||
if profile not in self.config_defaults
|
if profile not in self.config_defaults
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
self.menu_configs.entryconfig(0, state="disabled")
|
self.menu_configs.entryconfig(0, state='disabled')
|
||||||
self.menu_configs.add_command(
|
self.menu_configs.add_command(
|
||||||
label="Reset to defaults", command=self.load_defaults
|
label='Reset to defaults', command=self.load_defaults
|
||||||
)
|
)
|
||||||
|
|
||||||
# layout menu
|
# layout menu
|
||||||
self.menu_layout = tk.Menu(self, tearoff=0)
|
self.menu_layout = tk.Menu(self, tearoff=0)
|
||||||
self.add_cascade(menu=self.menu_layout, label="Layout")
|
self.add_cascade(menu=self.menu_layout, label='Layout')
|
||||||
# layout/submixes
|
# layout/submixes
|
||||||
# here we build menu regardless of kind but disable if not potato
|
# here we build menu regardless of kind but disable if not potato
|
||||||
buses = tuple(f"A{i+1}" for i in range(5)) + tuple(f"B{i+1}" for i in range(3))
|
buses = tuple(f'A{i + 1}' for i in range(5)) + tuple(
|
||||||
|
f'B{i + 1}' for i in range(3)
|
||||||
|
)
|
||||||
self.menu_submixes = tk.Menu(self.menu_layout, tearoff=0)
|
self.menu_submixes = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
self.menu_layout.add_cascade(menu=self.menu_submixes, label="Submixes")
|
self.menu_layout.add_cascade(menu=self.menu_submixes, label='Submixes')
|
||||||
[
|
[
|
||||||
self.menu_submixes.add_checkbutton(
|
self.menu_submixes.add_checkbutton(
|
||||||
label=f"Bus {buses[i]}",
|
label=f'Bus {buses[i]}',
|
||||||
underline=0,
|
underline=0,
|
||||||
onvalue=1,
|
onvalue=1,
|
||||||
offvalue=0,
|
offvalue=0,
|
||||||
@ -118,77 +123,94 @@ class Menus(tk.Menu):
|
|||||||
for i in range(8)
|
for i in range(8)
|
||||||
]
|
]
|
||||||
self._selected_bus[_configuration.submixes].set(True)
|
self._selected_bus[_configuration.submixes].set(True)
|
||||||
if self.parent.kind.name != "potato":
|
if self.parent.kind.name != 'potato':
|
||||||
self.menu_layout.entryconfig(0, state="disabled")
|
self.menu_layout.entryconfig(0, state='disabled')
|
||||||
# layout/extends
|
# layout/extends
|
||||||
self.menu_extends = tk.Menu(self.menu_layout, tearoff=0)
|
self.menu_extends = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
self.menu_layout.add_cascade(
|
self.menu_layout.add_cascade(
|
||||||
menu=self.menu_extends, label="Extends", underline=0
|
menu=self.menu_extends, label='Extends', underline=0
|
||||||
)
|
)
|
||||||
self.menu_extends.add_command(
|
self.menu_extends.add_command(
|
||||||
label="horizontal",
|
label='horizontal',
|
||||||
underline=0,
|
underline=0,
|
||||||
command=partial(self.switch_orientation, extends_horizontal=True),
|
command=partial(self.switch_orientation, extends_horizontal=True),
|
||||||
)
|
)
|
||||||
self.menu_extends.add_command(
|
self.menu_extends.add_command(
|
||||||
label="vertical",
|
label='vertical',
|
||||||
underline=0,
|
underline=0,
|
||||||
command=partial(self.switch_orientation, extends_horizontal=False),
|
command=partial(self.switch_orientation, extends_horizontal=False),
|
||||||
)
|
)
|
||||||
self.menu_extends.entryconfig(
|
self.menu_extends.entryconfig(
|
||||||
0 if _configuration.extends_horizontal else 1, state="disabled"
|
0 if _configuration.extends_horizontal else 1, state='disabled'
|
||||||
)
|
)
|
||||||
# layout/themes
|
# layout/themes
|
||||||
self.menu_themes = tk.Menu(self.menu_layout, tearoff=0)
|
self.menu_themes = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
self.menu_layout.add_cascade(menu=self.menu_themes, label="Themes")
|
self.menu_layout.add_cascade(menu=self.menu_themes, label='Themes')
|
||||||
self.menu_themes.add_command(
|
self.menu_themes.add_command(
|
||||||
label="light", command=partial(self.load_theme, "light")
|
label='light', command=partial(self.load_theme, 'light')
|
||||||
)
|
)
|
||||||
self.menu_themes.add_command(
|
self.menu_themes.add_command(
|
||||||
label="dark", command=partial(self.load_theme, "dark")
|
label='dark', command=partial(self.load_theme, 'dark')
|
||||||
)
|
)
|
||||||
self.menu_themes.entryconfig(
|
self.menu_themes.entryconfig(
|
||||||
0 if self.app_config["theme"]["mode"] == "light" else 1,
|
0 if self.app_config['theme']['mode'] == 'light' else 1,
|
||||||
state="disabled",
|
state='disabled',
|
||||||
)
|
)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.menu_layout.entryconfig(2, state="disabled")
|
self.menu_layout.entryconfig(2, state='disabled')
|
||||||
|
# layout/navigation
|
||||||
|
self.menu_navigation = tk.Menu(self.menu_layout, tearoff=0)
|
||||||
|
self.menu_layout.add_cascade(menu=self.menu_navigation, label='Navigation')
|
||||||
|
self.menu_navigation.add_checkbutton(
|
||||||
|
label='show',
|
||||||
|
onvalue=1,
|
||||||
|
offvalue=0,
|
||||||
|
variable=self._navigation_show,
|
||||||
|
command=partial(self.toggle_navigation, 'show'),
|
||||||
|
)
|
||||||
|
self.menu_navigation.add_checkbutton(
|
||||||
|
label='hide',
|
||||||
|
onvalue=1,
|
||||||
|
offvalue=0,
|
||||||
|
variable=self._navigation_hide,
|
||||||
|
command=partial(self.toggle_navigation, 'hide'),
|
||||||
|
)
|
||||||
|
|
||||||
# vban connect menu
|
# vban connect menu
|
||||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||||
self.add_cascade(menu=self.menu_vban, label="VBAN")
|
self.add_cascade(menu=self.menu_vban, label='VBAN')
|
||||||
if self.vban_config:
|
if self.vban_config:
|
||||||
for i, _ in enumerate(self.vban_config):
|
for i, _ in enumerate(self.vban_config):
|
||||||
setattr(self, f"menu_vban_{i+1}", tk.Menu(self.menu_vban, tearoff=0))
|
setattr(self, f'menu_vban_{i + 1}', tk.Menu(self.menu_vban, tearoff=0))
|
||||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
target_menu = getattr(self, f'menu_vban_{i + 1}')
|
||||||
self.menu_vban.add_cascade(
|
self.menu_vban.add_cascade(
|
||||||
menu=target_menu,
|
menu=target_menu,
|
||||||
label=f"{self.vban_config[f'connection-{i+1}']['streamname']}",
|
label=f'{self.vban_config[f"connection-{i + 1}"]["streamname"]}',
|
||||||
underline=0,
|
underline=0,
|
||||||
)
|
)
|
||||||
target_menu.add_command(
|
target_menu.add_command(
|
||||||
label="Connect", command=partial(self.vban_connect, i)
|
label='Connect', command=partial(self.vban_connect, i)
|
||||||
)
|
)
|
||||||
target_menu.add_command(
|
target_menu.add_command(
|
||||||
label="Disconnect", command=partial(self.vban_disconnect, i)
|
label='Disconnect', command=partial(self.vban_disconnect, i)
|
||||||
)
|
)
|
||||||
target_menu.entryconfig(1, state="disabled")
|
target_menu.entryconfig(1, state='disabled')
|
||||||
else:
|
else:
|
||||||
self.entryconfig(4, state="disabled")
|
self.entryconfig(4, state='disabled')
|
||||||
|
|
||||||
# Help menu
|
# Help menu
|
||||||
self.menu_help = tk.Menu(self, tearoff=0)
|
self.menu_help = tk.Menu(self, tearoff=0)
|
||||||
self.add_cascade(menu=self.menu_help, label="Help")
|
self.add_cascade(menu=self.menu_help, label='Help')
|
||||||
self.menu_help.add_command(
|
self.menu_help.add_command(
|
||||||
label="Voicemeeter Site",
|
label='Voicemeeter Site',
|
||||||
command=self.documentation,
|
command=self.documentation,
|
||||||
)
|
)
|
||||||
self.menu_help.add_command(
|
self.menu_help.add_command(
|
||||||
label="Source Code",
|
label='Source Code',
|
||||||
command=self.github,
|
command=self.github,
|
||||||
)
|
)
|
||||||
self.menu_help.add_command(
|
self.menu_help.add_command(
|
||||||
label="App Creator",
|
label='App Creator',
|
||||||
command=self.onyxandiris,
|
command=self.onyxandiris,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -199,40 +221,56 @@ class Menus(tk.Menu):
|
|||||||
|
|
||||||
def enable_vban_menus(self):
|
def enable_vban_menus(self):
|
||||||
[
|
[
|
||||||
self.menu_vban.entryconfig(j, state="normal")
|
self.menu_vban.entryconfig(j, state='normal')
|
||||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||||
]
|
]
|
||||||
|
|
||||||
def action_invoke_voicemeeter(self, cmd):
|
def action_invoke_voicemeeter(self, cmd):
|
||||||
getattr(self.target.command, cmd)()
|
if fn := getattr(self.target.command, cmd):
|
||||||
|
fn()
|
||||||
|
if cmd == 'shutdown':
|
||||||
|
self.parent.on_close_window()
|
||||||
|
|
||||||
def action_set_voicemeeter(self, cmd, val=True):
|
def action_set_voicemeeter(self, cmd, val=True):
|
||||||
if cmd == "lock":
|
if cmd == 'lock':
|
||||||
self._lock.set(val)
|
self._lock.set(val)
|
||||||
self._unlock.set(not self._lock.get())
|
self._unlock.set(not self._lock.get())
|
||||||
setattr(self.target.command, cmd, val)
|
setattr(self.target.command, cmd, val)
|
||||||
|
|
||||||
|
def load_custom_profile(self, profile):
|
||||||
|
self.logger.info(f'loading user profile {profile}')
|
||||||
|
self.target.apply(profile)
|
||||||
|
if not _base_values.run_update:
|
||||||
|
self.parent.subject.notify('pdirty')
|
||||||
|
|
||||||
def load_profile(self, profile):
|
def load_profile(self, profile):
|
||||||
|
self.logger.info(f'loading user profile {profile}')
|
||||||
self.target.apply_config(profile)
|
self.target.apply_config(profile)
|
||||||
|
if not _base_values.run_update:
|
||||||
|
self.parent.subject.notify('pdirty')
|
||||||
|
|
||||||
def load_defaults(self):
|
def load_defaults(self):
|
||||||
resp = messagebox.askyesno(
|
msg = (
|
||||||
message="Are you sure you want to Reset values to defaults?\nPhysical strips B1, Virtual strips A1\nMono, Solo, Mute, EQ all OFF"
|
'Are you sure you want to Reset values to defaults?',
|
||||||
|
'Physical strips B1, Virtual strips A1',
|
||||||
|
'Mono, Solo, Mute, EQ all OFF',
|
||||||
|
'Gain sliders for Strip/Bus at 0.0',
|
||||||
)
|
)
|
||||||
|
resp = messagebox.askyesno(message='\n'.join(msg))
|
||||||
if resp:
|
if resp:
|
||||||
self.target.apply_config("reset")
|
self.load_profile('reset')
|
||||||
|
|
||||||
def always_on_top(self):
|
def always_on_top(self):
|
||||||
self.parent.attributes("-topmost", self._is_topmost.get())
|
self.parent.attributes('-topmost', self._is_topmost.get())
|
||||||
|
|
||||||
def switch_orientation(self, extends_horizontal: bool = True, *args):
|
def switch_orientation(self, extends_horizontal: bool = True, *args):
|
||||||
_configuration.extends_horizontal = extends_horizontal
|
_configuration.extends_horizontal = extends_horizontal
|
||||||
if extends_horizontal:
|
if extends_horizontal:
|
||||||
self.menu_extends.entryconfig(0, state="disabled")
|
self.menu_extends.entryconfig(0, state='disabled')
|
||||||
self.menu_extends.entryconfig(1, state="normal")
|
self.menu_extends.entryconfig(1, state='normal')
|
||||||
else:
|
else:
|
||||||
self.menu_extends.entryconfig(1, state="disabled")
|
self.menu_extends.entryconfig(1, state='disabled')
|
||||||
self.menu_extends.entryconfig(0, state="normal")
|
self.menu_extends.entryconfig(0, state='normal')
|
||||||
|
|
||||||
def set_submix(self, i):
|
def set_submix(self, i):
|
||||||
if _configuration.submixes != i:
|
if _configuration.submixes != i:
|
||||||
@ -242,86 +280,101 @@ class Menus(tk.Menu):
|
|||||||
self.parent.nav_frame.show_submix()
|
self.parent.nav_frame.show_submix()
|
||||||
for j, var in enumerate(self._selected_bus):
|
for j, var in enumerate(self._selected_bus):
|
||||||
var.set(i == j)
|
var.set(i == j)
|
||||||
|
self.parent.subject.notify('submix')
|
||||||
|
|
||||||
def load_theme(self, theme):
|
def load_theme(self, theme):
|
||||||
sv_ttk.set_theme(theme)
|
sv_ttk.set_theme(theme)
|
||||||
_configuration.theme_mode = theme
|
_configuration.theme_mode = theme
|
||||||
self.menu_themes.entryconfig(
|
self.menu_themes.entryconfig(
|
||||||
0,
|
0,
|
||||||
state=f"{'disabled' if theme == 'light' else 'normal'}",
|
state=f'{"disabled" if theme == "light" else "normal"}',
|
||||||
)
|
)
|
||||||
self.menu_themes.entryconfig(
|
self.menu_themes.entryconfig(
|
||||||
1,
|
1,
|
||||||
state=f"{'disabled' if theme == 'dark' else 'normal'}",
|
state=f'{"disabled" if theme == "dark" else "normal"}',
|
||||||
)
|
)
|
||||||
[
|
[
|
||||||
menu.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
menu.config(bg=f'{"black" if theme == "dark" else "white"}')
|
||||||
for menu in self.winfo_children()
|
for menu in self.winfo_children()
|
||||||
if isinstance(menu, tk.Menu)
|
if isinstance(menu, tk.Menu)
|
||||||
]
|
]
|
||||||
self.menu_lock.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
self.menu_lock.config(bg=f'{"black" if theme == "dark" else "white"}')
|
||||||
self.menu_configs_load.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
self.menu_configs_load.config(bg=f'{"black" if theme == "dark" else "white"}')
|
||||||
[
|
[
|
||||||
menu.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
menu.config(bg=f'{"black" if theme == "dark" else "white"}')
|
||||||
for menu in self.menu_vban.winfo_children()
|
for menu in self.menu_vban.winfo_children()
|
||||||
if isinstance(menu, tk.Menu)
|
if isinstance(menu, tk.Menu)
|
||||||
]
|
]
|
||||||
[
|
[
|
||||||
menu.config(bg=f"{'black' if theme == 'dark' else 'white'}")
|
menu.config(bg=f'{"black" if theme == "dark" else "white"}')
|
||||||
for menu in self.menu_layout.winfo_children()
|
for menu in self.menu_layout.winfo_children()
|
||||||
if isinstance(menu, tk.Menu)
|
if isinstance(menu, tk.Menu)
|
||||||
]
|
]
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Finished loading theme Sunvalley {sv_ttk.get_theme().capitalize()} theme"
|
f'Finished loading theme Sunvalley {sv_ttk.get_theme().capitalize()} theme'
|
||||||
)
|
)
|
||||||
|
|
||||||
def menu_teardown(self, i):
|
def menu_teardown(self, i):
|
||||||
# remove config load menus
|
# remove config load menus
|
||||||
[
|
if len(self.parent.userconfigs) > len(self.config_defaults):
|
||||||
self.menu_configs_load.delete(key)
|
for profile in self.parent.userconfigs:
|
||||||
for key in self.target.configs.keys()
|
if profile not in self.config_defaults:
|
||||||
if key not in self.config_defaults
|
try:
|
||||||
]
|
self.menu_configs_load.delete(profile)
|
||||||
|
except tk._tkinter.tclError as e:
|
||||||
|
self.logger.warning(f'{type(e).__name__}: {e}')
|
||||||
|
|
||||||
[
|
[
|
||||||
self.menu_vban.entryconfig(j, state="disabled")
|
self.menu_vban.entryconfig(j, state='disabled')
|
||||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||||
if j != i
|
if j != i
|
||||||
]
|
]
|
||||||
|
|
||||||
def menu_setup(self):
|
def menu_setup(self):
|
||||||
if len(self.target.configs) > len(self.config_defaults) and all(
|
if len(self.parent.userconfigs) > len(self.config_defaults):
|
||||||
key in self.target.configs for key in self.config_defaults
|
for profile in self.parent.userconfigs:
|
||||||
):
|
if profile not in self.config_defaults:
|
||||||
[
|
self.menu_configs_load.add_command(
|
||||||
self.menu_configs_load.add_command(
|
label=profile, command=partial(self.load_profile, profile)
|
||||||
label=profile, command=partial(self.load_profile, profile)
|
)
|
||||||
)
|
self.menu_configs.entryconfig(0, state='normal')
|
||||||
for profile in self.target.configs.keys()
|
|
||||||
if profile not in self.config_defaults
|
|
||||||
]
|
|
||||||
else:
|
else:
|
||||||
self.menu_configs.entryconfig(0, state="disabled")
|
self.menu_configs.entryconfig(0, state='disabled')
|
||||||
|
|
||||||
|
def toggle_navigation(self, cmd=None):
|
||||||
|
if cmd == 'show':
|
||||||
|
self.logger.debug('show navframe')
|
||||||
|
self.parent.nav_frame.grid()
|
||||||
|
self._navigation_show.set(True)
|
||||||
|
self._navigation_hide.set(not self._navigation_show.get())
|
||||||
|
else:
|
||||||
|
self.logger.debug('hide navframe')
|
||||||
|
self.parent.nav_frame.grid_remove()
|
||||||
|
self._navigation_hide.set(True)
|
||||||
|
self._navigation_show.set(not self._navigation_hide.get())
|
||||||
|
|
||||||
def vban_connect(self, i):
|
def vban_connect(self, i):
|
||||||
opts = {}
|
opts = {}
|
||||||
opts |= self.vban_config[f"connection-{i+1}"]
|
opts |= self.vban_config[f'connection-{i + 1}']
|
||||||
kind_id = opts.pop("kind")
|
kind_id = opts.pop('kind')
|
||||||
self.vban = vban_cmd.api(kind_id, **opts)
|
self.vban = vban_cmd.api(kind_id, **opts)
|
||||||
# login to vban interface
|
# login to vban interface
|
||||||
try:
|
try:
|
||||||
self.logger.info(f"Attempting vban connection to {opts.get('ip')}")
|
self.logger.info(f'Attempting vban connection to {opts.get("ip")}')
|
||||||
self.vban.login()
|
self.vban.login()
|
||||||
except VBANCMDError as e:
|
except VBANCMDConnectionError as e:
|
||||||
self.vban.logout()
|
self.vban.logout()
|
||||||
msg = (str(e), f"Please check your connection settings")
|
msg = (
|
||||||
messagebox.showerror("Connection Error", "\n".join(msg))
|
f'Timeout attempting to establish connection to {opts.get("ip")}',
|
||||||
msg = (str(e), f"resuming local connection")
|
'Please check your connection settings',
|
||||||
self.logger.error(", ".join(msg))
|
)
|
||||||
|
messagebox.showerror('Connection Error', '\n'.join(msg))
|
||||||
|
msg = (str(e), 'resuming local connection')
|
||||||
|
self.logger.error(', '.join(msg))
|
||||||
self.after(1, self.enable_vban_menus)
|
self.after(1, self.enable_vban_menus)
|
||||||
return
|
return
|
||||||
self.menu_teardown(i)
|
self.menu_teardown(i)
|
||||||
self.vban.event.add("ldirty")
|
self.vban.event.add(['pdirty', 'ldirty'])
|
||||||
# destroy the current App frames
|
# destroy the current App frames
|
||||||
self.parent._destroy_top_level_frames()
|
self.parent._destroy_top_level_frames()
|
||||||
_base_values.vban_connected = True
|
_base_values.vban_connected = True
|
||||||
@ -330,12 +383,17 @@ class Menus(tk.Menu):
|
|||||||
# build new app frames according to a kind
|
# build new app frames according to a kind
|
||||||
kind = kind_get(kind_id)
|
kind = kind_get(kind_id)
|
||||||
self.parent.build_app(kind, self.vban)
|
self.parent.build_app(kind, self.vban)
|
||||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
target_menu = getattr(self, f'menu_vban_{i + 1}')
|
||||||
target_menu.entryconfig(0, state="disabled")
|
target_menu.entryconfig(0, state='disabled')
|
||||||
target_menu.entryconfig(1, state="normal")
|
target_menu.entryconfig(1, state='normal')
|
||||||
self.menu_layout.entryconfig(
|
self.menu_layout.entryconfig(
|
||||||
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
|
0, state=f'{"normal" if kind.name == "potato" else "disabled"}'
|
||||||
)
|
)
|
||||||
|
# ensure the configs are reloaded into memory
|
||||||
|
if 'config' in self.parent.target.__dict__:
|
||||||
|
del self.parent.target.__dict__['config']
|
||||||
|
if 'userconfigs' in self.parent.__dict__:
|
||||||
|
del self.parent.__dict__['userconfigs']
|
||||||
self.menu_setup()
|
self.menu_setup()
|
||||||
|
|
||||||
def vban_disconnect(self, i):
|
def vban_disconnect(self, i):
|
||||||
@ -349,22 +407,27 @@ class Menus(tk.Menu):
|
|||||||
self.vban.logout()
|
self.vban.logout()
|
||||||
# build new app frames according to a kind
|
# build new app frames according to a kind
|
||||||
kind = kind_get(self.vmr.type)
|
kind = kind_get(self.vmr.type)
|
||||||
self.parent.build_app(kind, None)
|
self.parent.build_app(kind)
|
||||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
target_menu = getattr(self, f'menu_vban_{i + 1}')
|
||||||
target_menu.entryconfig(0, state="normal")
|
target_menu.entryconfig(0, state='normal')
|
||||||
target_menu.entryconfig(1, state="disabled")
|
target_menu.entryconfig(1, state='disabled')
|
||||||
self.menu_layout.entryconfig(
|
self.menu_layout.entryconfig(
|
||||||
0, state=f"{'normal' if kind.name == 'potato' else 'disabled'}"
|
0, state=f'{"normal" if kind.name == "potato" else "disabled"}'
|
||||||
)
|
)
|
||||||
|
# ensure the configs are reloaded into memory
|
||||||
|
if 'config' in self.parent.target.__dict__:
|
||||||
|
del self.parent.target.__dict__['config']
|
||||||
|
if 'userconfigs' in self.parent.__dict__:
|
||||||
|
del self.parent.__dict__['userconfigs']
|
||||||
self.menu_setup()
|
self.menu_setup()
|
||||||
|
|
||||||
self.after(15000, self.enable_vban_menus)
|
self.after(500, self.enable_vban_menus)
|
||||||
|
|
||||||
def documentation(self):
|
def documentation(self):
|
||||||
webbrowser.open_new(r"https://voicemeeter.com/")
|
webbrowser.open_new(r'https://voicemeeter.com/')
|
||||||
|
|
||||||
def github(self):
|
def github(self):
|
||||||
webbrowser.open_new(r"https://github.com/onyx-and-iris/voicemeeter-compact")
|
webbrowser.open_new(r'https://github.com/onyx-and-iris/voicemeeter-compact')
|
||||||
|
|
||||||
def onyxandiris(self):
|
def onyxandiris(self):
|
||||||
webbrowser.open_new(r"https://onyxandiris.online")
|
webbrowser.open_new(r'https://onyxandiris.online')
|
||||||
|
@ -6,14 +6,17 @@ from . import builders
|
|||||||
from .data import _configuration
|
from .data import _configuration
|
||||||
from .gainlayer import SubMixFrame
|
from .gainlayer import SubMixFrame
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Navigation(ttk.Frame):
|
class Navigation(ttk.Frame):
|
||||||
logger = logging.getLogger("navigation.navigation")
|
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
|
self.logger = logger.getChild(self.__class__.__name__)
|
||||||
self.grid(row=0, column=3, padx=(0, 2), pady=(5, 5), sticky=(tk.W, tk.E))
|
self.grid(row=0, column=3, padx=(0, 2), pady=(5, 5), sticky=(tk.W, tk.E))
|
||||||
|
if not _configuration.navigation_show:
|
||||||
|
self.grid_remove()
|
||||||
self.styletable = self.parent.styletable
|
self.styletable = self.parent.styletable
|
||||||
|
|
||||||
self.builder = builders.NavigationFrameBuilder(self)
|
self.builder = builders.NavigationFrameBuilder(self)
|
||||||
@ -30,7 +33,7 @@ class Navigation(ttk.Frame):
|
|||||||
if self.submix.get():
|
if self.submix.get():
|
||||||
self.parent.submix_frame = SubMixFrame(self.parent)
|
self.parent.submix_frame = SubMixFrame(self.parent)
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Finished building submixframe for submix {_configuration.submixes}"
|
f'Finished building submixframe for submix {_configuration.submixes}'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if _configuration.extends_horizontal:
|
if _configuration.extends_horizontal:
|
||||||
@ -46,51 +49,51 @@ class Navigation(ttk.Frame):
|
|||||||
else:
|
else:
|
||||||
self.parent.rowconfigure(2, weight=0, minsize=0)
|
self.parent.rowconfigure(2, weight=0, minsize=0)
|
||||||
self.logger.info(
|
self.logger.info(
|
||||||
f"Finished tearing down submixframe for submix {_configuration.submixes}"
|
f'Finished tearing down submixframe for submix {_configuration.submixes}'
|
||||||
)
|
)
|
||||||
|
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"Submix.TButton",
|
'Submix.TButton',
|
||||||
background=f'{"purple" if self.submix.get() else "white"}',
|
background=f'{"purple" if self.submix.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
def switch_channel(self):
|
def switch_channel(self):
|
||||||
if self.channel_text.get() == "STRIP":
|
if self.channel_text.get() == 'STRIP':
|
||||||
self.mainframebuilder.create_channelframe("bus")
|
self.mainframebuilder.create_channelframe('bus')
|
||||||
self.parent.strip_frame.teardown()
|
self.parent.strip_frame.teardown()
|
||||||
else:
|
else:
|
||||||
self.mainframebuilder.create_channelframe("strip")
|
self.mainframebuilder.create_channelframe('strip')
|
||||||
self.parent.bus_frame.teardown()
|
self.parent.bus_frame.teardown()
|
||||||
|
|
||||||
self.extend_button["state"] = (
|
self.extend_button['state'] = (
|
||||||
"disabled" if self.channel_text.get() == "STRIP" else "normal"
|
'disabled' if self.channel_text.get() == 'STRIP' else 'normal'
|
||||||
)
|
)
|
||||||
[frame.teardown() for frame in self.parent.configframes]
|
[frame.teardown() for frame in self.parent.configframes]
|
||||||
self.channel_text.set("BUS" if self.channel_text.get() == "STRIP" else "STRIP")
|
self.channel_text.set('BUS' if self.channel_text.get() == 'STRIP' else 'STRIP')
|
||||||
|
|
||||||
def extend_frame(self):
|
def extend_frame(self):
|
||||||
_configuration.extended = self.extend.get()
|
_configuration.extended = self.extend.get()
|
||||||
if self.extend.get():
|
if self.extend.get():
|
||||||
self.channel_button["state"] = "disabled"
|
self.channel_button['state'] = 'disabled'
|
||||||
self.mainframebuilder.create_channelframe("bus")
|
self.mainframebuilder.create_channelframe('bus')
|
||||||
else:
|
else:
|
||||||
[
|
[
|
||||||
frame.teardown()
|
frame.teardown()
|
||||||
for frame in self.parent.configframes
|
for frame in self.parent.configframes
|
||||||
if "!busconfig" in str(frame)
|
if '!busconfig' in str(frame)
|
||||||
]
|
]
|
||||||
self.parent.bus_frame.teardown()
|
self.parent.bus_frame.teardown()
|
||||||
self.parent.bus_frame = None
|
self.parent.bus_frame = None
|
||||||
self.channel_button["state"] = "normal"
|
self.channel_button['state'] = 'normal'
|
||||||
|
|
||||||
if self.parent.submix_frame:
|
if self.parent.submix_frame:
|
||||||
self.parent.submix_frame.teardown()
|
self.parent.submix_frame.teardown()
|
||||||
self.submix.set(False)
|
self.submix.set(False)
|
||||||
if not _configuration.themes_enabled:
|
if not _configuration.themes_enabled:
|
||||||
self.styletable.configure(
|
self.styletable.configure(
|
||||||
f"Submix.TButton",
|
'Submix.TButton',
|
||||||
background=f'{"purple" if self.submix.get() else "white"}',
|
background=f'{"purple" if self.submix.get() else "white"}',
|
||||||
)
|
)
|
||||||
|
|
||||||
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")
|
self.extend_text.set('REDUCE' if self.extend.get() else 'EXTEND')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user