mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2026-04-08 17:03:32 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2db0f2757 | |||
| bfb0482c32 | |||
| 6222ab1e62 | |||
| 0ad40ab708 | |||
| b809bcb28f | |||
| a0b9a92a2a | |||
| 9faf8ae10c | |||
| 82cf0e914b | |||
| 3e68488231 | |||
| 6d46d9a9a5 | |||
| e4068277f7 | |||
| 674999a461 | |||
| e5975f0772 | |||
| 59d2a95ec4 | |||
| 4bae1e1d15 | |||
| 1e3751b19f | |||
| 2ec1c74b7d | |||
| 0a19e28370 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -9,6 +9,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- [ ] Add support for forest theme (if rbende adds it to pypi)
|
||||
|
||||
## [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
|
||||
|
||||
16
README.md
16
README.md
@@ -34,16 +34,16 @@ import vmcompact
|
||||
|
||||
|
||||
def main():
|
||||
# pass the kind_id and the vm object to the app
|
||||
with voicemeeterlib.api(kind_id) as vm:
|
||||
app = vmcompact.connect(kind_id, vm)
|
||||
# choose the kind of Voicemeeter (Local connection)
|
||||
KIND_ID = "banana"
|
||||
|
||||
# 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()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# choose the kind of Voicemeeter (Local connection)
|
||||
kind_id = "banana"
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
@@ -53,9 +53,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 :).
|
||||
|
||||
### kind_id
|
||||
### KIND_ID
|
||||
|
||||
Set the kind of Voicemeeter, kind_id may be:
|
||||
Set the kind of Voicemeeter, KIND_ID may be:
|
||||
|
||||
- `basic`
|
||||
- `banana`
|
||||
|
||||
@@ -4,12 +4,12 @@ import vmcompact
|
||||
|
||||
|
||||
def main():
|
||||
with voicemeeterlib.api(kind_id) as vmr:
|
||||
app = vmcompact.connect(kind_id, vmr)
|
||||
KIND_ID = "banana"
|
||||
|
||||
with voicemeeterlib.api(KIND_ID) as vmr:
|
||||
app = vmcompact.connect(KIND_ID, vmr)
|
||||
app.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
kind_id = "banana"
|
||||
|
||||
main()
|
||||
|
||||
@@ -3,19 +3,23 @@
|
||||
# config="example"
|
||||
# load with themes enabled? set the default mode
|
||||
[theme]
|
||||
enabled=true
|
||||
mode="light"
|
||||
enabled = true
|
||||
mode = "light"
|
||||
# load in extended mode? if so which orientation
|
||||
[extends]
|
||||
extended=true
|
||||
extends_horizontal=true
|
||||
extended = true
|
||||
extends_horizontal = true
|
||||
# default dimensions for channel label frames
|
||||
[channel]
|
||||
width=80
|
||||
height=130
|
||||
width = 80
|
||||
height = 130
|
||||
xpadding = 2
|
||||
# size of a single mouse wheel scroll step
|
||||
[mwscroll_step]
|
||||
size=3
|
||||
size = 3
|
||||
# default submix bus
|
||||
[submixes]
|
||||
default=0
|
||||
default = 0
|
||||
# show the navigation frame?
|
||||
[navigation]
|
||||
show = true
|
||||
|
||||
118
poetry.lock
generated
118
poetry.lock
generated
@@ -1,10 +1,25 @@
|
||||
# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "black"
|
||||
version = "22.10.0"
|
||||
version = "22.12.0"
|
||||
description = "The uncompromising code formatter."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "black-22.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d"},
|
||||
{file = "black-22.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351"},
|
||||
{file = "black-22.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"},
|
||||
{file = "black-22.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4"},
|
||||
{file = "black-22.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2"},
|
||||
{file = "black-22.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350"},
|
||||
{file = "black-22.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d"},
|
||||
{file = "black-22.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc"},
|
||||
{file = "black-22.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320"},
|
||||
{file = "black-22.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148"},
|
||||
{file = "black-22.12.0-py3-none-any.whl", hash = "sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf"},
|
||||
{file = "black-22.12.0.tar.gz", hash = "sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
@@ -21,117 +36,134 @@ uvloop = ["uvloop (>=0.15.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.1.3"
|
||||
version = "8.1.4"
|
||||
description = "Composable command line interface toolkit"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "click-8.1.4-py3-none-any.whl", hash = "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3"},
|
||||
{file = "click-8.1.4.tar.gz", hash = "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
files = [
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
files = [
|
||||
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3)"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "dev"
|
||||
version = "1.0.0"
|
||||
description = "Type system extensions for programs checked with the mypy type checker."
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
python-versions = ">=3.5"
|
||||
files = [
|
||||
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
|
||||
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.10.1"
|
||||
version = "0.11.1"
|
||||
description = "Utility library for gitignore style pattern matching of file paths."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "pathspec-0.11.1-py3-none-any.whl", hash = "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"},
|
||||
{file = "pathspec-0.11.1.tar.gz", hash = "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
version = "3.8.1"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "platformdirs-3.8.1-py3-none-any.whl", hash = "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c"},
|
||||
{file = "platformdirs-3.8.1.tar.gz", hash = "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
|
||||
docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "sv-ttk"
|
||||
version = "2.4.5"
|
||||
version = "2.5.5"
|
||||
description = "A gorgeous theme for Tkinter, based on Windows 11's UI"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.4"
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "sv_ttk-2.5.5-py3-none-any.whl", hash = "sha256:49d1cd03c032728c183d1fe2318f88cdb658ef3e87157e1ca3fcf6661054965b"},
|
||||
{file = "sv_ttk-2.5.5.tar.gz", hash = "sha256:9bbfe2aba6cc6f9fdf70d79331046543c9666fcccc78bad5ff648a9987e3cedb"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
files = [
|
||||
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
|
||||
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vban-cmd"
|
||||
version = "2.0.0"
|
||||
version = "2.4.4"
|
||||
description = "Python interface for the VBAN RT Packet Service (Sendtext)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.10,<4.0"
|
||||
files = [
|
||||
{file = "vban_cmd-2.4.4-py3-none-any.whl", hash = "sha256:f439219a2dc6a45123bb70fc94d2aabfc643f26dffc9206da2228e2520f83e01"},
|
||||
{file = "vban_cmd-2.4.4.tar.gz", hash = "sha256:31dbf6abb681d57772c9082722b024d0798eff99b4c622bfbb539179852a935d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[[package]]
|
||||
name = "voicemeeter-api"
|
||||
version = "2.0.1"
|
||||
version = "2.4.4"
|
||||
description = "A Python wrapper for the Voiceemeter API"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.10,<4.0"
|
||||
files = [
|
||||
{file = "voicemeeter_api-2.4.4-py3-none-any.whl", hash = "sha256:2e2f0b475de7cfc0d1c397838498162f0b405ab82a6f6ca0095434b43385b43e"},
|
||||
{file = "voicemeeter_api-2.4.4.tar.gz", hash = "sha256:a4f8ecaa7f5d6b9e9a8545dcf047754711af9a120628b3a98821466c7d65c1e7"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""}
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
lock-version = "2.0"
|
||||
python-versions = "^3.10"
|
||||
content-hash = "ffb9af7ef7aa87ac08a09293de5e99487155faaa459cd49964ac95589deb69fa"
|
||||
|
||||
[metadata.files]
|
||||
black = []
|
||||
click = []
|
||||
colorama = []
|
||||
isort = []
|
||||
mypy-extensions = []
|
||||
pathspec = []
|
||||
platformdirs = []
|
||||
sv-ttk = []
|
||||
tomli = []
|
||||
vban-cmd = []
|
||||
voicemeeter-api = []
|
||||
content-hash = "cde74de8c9de0895555068efea354edf6b51f2332c253f02f6c8f26fe6b4e795"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "voicemeeter-compact"
|
||||
version = "1.7.1"
|
||||
version = "1.9.2"
|
||||
description = "A Compact Voicemeeter Remote App"
|
||||
authors = ["onyx-and-iris <code@onyxandiris.online>"]
|
||||
license = "MIT"
|
||||
@@ -12,10 +12,10 @@ include = ["vmcompact/img/cat.ico"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
sv-ttk = "^2.4.5"
|
||||
sv-ttk = "^2.5.5"
|
||||
tomli = { version = "^2.0.1", python = "<3.11" }
|
||||
voicemeeter-api = "^2.0.1"
|
||||
vban-cmd = "^2.0.0"
|
||||
voicemeeter-api = "^2.4.4"
|
||||
vban-cmd = "^2.4.4"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
black = { version = "^22.6.0", allow-prereleases = true }
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from tkinter import ttk
|
||||
from tkinter import messagebox, ttk
|
||||
from typing import NamedTuple
|
||||
|
||||
import voicemeeterlib
|
||||
|
||||
from .builders import MainFrameBuilder
|
||||
from .configurations import loader
|
||||
from .data import _base_values, _configuration, _kinds_all
|
||||
from .data import _base_values, _configuration, _kinds_all, get_configuration
|
||||
from .errors import VMCompactError
|
||||
from .menu import Menus
|
||||
from .subject import Subject
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class App(tk.Tk):
|
||||
"""App mainframe"""
|
||||
@@ -34,9 +39,10 @@ class App(tk.Tk):
|
||||
|
||||
def __init__(self, vmr):
|
||||
super().__init__()
|
||||
|
||||
self.logger = logger.getChild(self.__class__.__name__)
|
||||
self._vmr = vmr
|
||||
self._vmr.event.add(["pdirty", "ldirty"])
|
||||
self.after(12000 if self._vmr.gui.launched_by_api else 1, self.start_updates)
|
||||
self._vmr.init_thread()
|
||||
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
|
||||
if icon_path.is_file():
|
||||
@@ -44,7 +50,8 @@ class App(tk.Tk):
|
||||
self.minsize(275, False)
|
||||
self.subject = Subject()
|
||||
self._configs = None
|
||||
self["menu"] = Menus(self, vmr)
|
||||
self.protocol("WM_DELETE_WINDOW", self.on_close_window)
|
||||
self.menu = self["menu"] = Menus(self, vmr)
|
||||
self.styletable = ttk.Style()
|
||||
if _configuration.config:
|
||||
vmr.apply_config(_configuration.config)
|
||||
@@ -54,6 +61,8 @@ class App(tk.Tk):
|
||||
self.drag_id = ""
|
||||
self.bind("<Configure>", self.dragging)
|
||||
|
||||
self.after(1, self.healthcheck_step)
|
||||
|
||||
def __str__(self):
|
||||
return f"{type(self).__name__}App"
|
||||
|
||||
@@ -113,7 +122,7 @@ class App(tk.Tk):
|
||||
|
||||
Destroy all top level frames.
|
||||
"""
|
||||
self.target.subject.remove(self)
|
||||
self.target.subject.remove([self.on_pdirty, self.on_ldirty])
|
||||
self.subject.clear()
|
||||
[
|
||||
frame.destroy()
|
||||
@@ -135,9 +144,50 @@ class App(tk.Tk):
|
||||
|
||||
@cached_property
|
||||
def userconfigs(self):
|
||||
self._configs = loader(self.kind.name)
|
||||
self._configs = loader(self.kind.name, self.target)
|
||||
return self._configs
|
||||
|
||||
def start_updates(self):
|
||||
self.logger.debug("updates started")
|
||||
_base_values.run_update = True
|
||||
if self._vmr.gui.launched_by_api:
|
||||
self.on_pdirty()
|
||||
|
||||
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}
|
||||
|
||||
|
||||
@@ -195,9 +195,9 @@ class NavigationFrameBuilder(AbstractBuilder):
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
if _configuration.themes_enabled:
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height)
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.channel_height)
|
||||
else:
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height + 10)
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.channel_height + 10)
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
@@ -243,13 +243,19 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
orient="vertical",
|
||||
variable=self.labelframe.gain,
|
||||
command=self.labelframe.scale_callback,
|
||||
length=_configuration.level_height,
|
||||
length=_configuration.channel_height,
|
||||
)
|
||||
self.scale.grid(column=1, row=0)
|
||||
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
||||
self.scale.bind("<Button-1>", self.labelframe.scale_press)
|
||||
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):
|
||||
self.labelframe.gain_label = ttk.Label(
|
||||
@@ -263,7 +269,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
self.button_mute = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
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'}",
|
||||
variable=self.labelframe.mute,
|
||||
)
|
||||
@@ -283,7 +289,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
self.button_on = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
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'}",
|
||||
variable=self.labelframe.on,
|
||||
)
|
||||
@@ -324,7 +330,7 @@ class ChannelConfigFrameBuilder(AbstractBuilder):
|
||||
]
|
||||
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)
|
||||
]
|
||||
|
||||
@@ -392,7 +398,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
length=_configuration.channel_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("comp.knob")
|
||||
],
|
||||
@@ -416,7 +422,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
length=_configuration.channel_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("gate.knob")
|
||||
],
|
||||
@@ -440,7 +446,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
from_=-40,
|
||||
to=12,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
length=_configuration.channel_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("limit")
|
||||
],
|
||||
@@ -464,7 +470,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
length=_configuration.channel_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("audibility")
|
||||
],
|
||||
@@ -486,7 +492,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_a, param),
|
||||
command=partial(
|
||||
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[
|
||||
self.configframe.phys_out_params.index(param)
|
||||
@@ -507,7 +515,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_b, param),
|
||||
command=partial(
|
||||
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[
|
||||
self.configframe.virt_out_params.index(param)
|
||||
@@ -528,7 +538,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_p, param),
|
||||
command=partial(
|
||||
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],
|
||||
)
|
||||
@@ -578,10 +590,16 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
column=0, row=0, columnspan=2, sticky=(tk.W)
|
||||
)
|
||||
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(
|
||||
"<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):
|
||||
@@ -589,7 +607,9 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_p, param),
|
||||
command=partial(
|
||||
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],
|
||||
)
|
||||
|
||||
@@ -88,17 +88,27 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
self.parent.target.event.add("ldirty")
|
||||
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):
|
||||
_base_values.run_update = True
|
||||
|
||||
def _on_mousewheel(self, event):
|
||||
_base_values.run_update = False
|
||||
self.gain.set(
|
||||
self.gain.get()
|
||||
+ (
|
||||
_configuration.mwscroll_step
|
||||
if event.delta > 0
|
||||
else -_configuration.mwscroll_step
|
||||
round(
|
||||
self.gain.get()
|
||||
+ (
|
||||
_configuration.mwscroll_step
|
||||
if event.delta > 0
|
||||
else -_configuration.mwscroll_step
|
||||
),
|
||||
1,
|
||||
)
|
||||
)
|
||||
if self.gain.get() > 12:
|
||||
@@ -106,7 +116,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
elif self.gain.get() < -60:
|
||||
self.gain.set(-60)
|
||||
self.setter("gain", self.gain.get())
|
||||
self.after(1, self.resume_updates)
|
||||
self.gainlabel.set(round(self.gain.get(), 1))
|
||||
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
@@ -141,20 +151,21 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
def sync_labels(self):
|
||||
"""sync labelframes according to label text"""
|
||||
retval = self.getter("label")
|
||||
self.parent.label_cache[self.id].insert(self.index, retval)
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
if not retval:
|
||||
self.parent.columnconfigure(self.index, minsize=0)
|
||||
self.parent.parent.subject.remove(self)
|
||||
self.grid_remove()
|
||||
else:
|
||||
self.parent.parent.subject.add(self)
|
||||
self.grid()
|
||||
self.configure(text=retval)
|
||||
if self.parent.label_cache[self.id][self.index] != retval:
|
||||
self.parent.label_cache[self.id][self.index] = retval
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
if not retval:
|
||||
self.parent.columnconfigure(self.index, minsize=0)
|
||||
self.parent.parent.subject.remove(self)
|
||||
self.grid_remove()
|
||||
else:
|
||||
self.parent.parent.subject.add(self)
|
||||
self.grid()
|
||||
self.configure(text=retval)
|
||||
|
||||
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))
|
||||
for child in self.winfo_children()
|
||||
@@ -218,15 +229,18 @@ class Bus(ChannelLabelFrame):
|
||||
|
||||
|
||||
class ChannelFrame(ttk.Frame):
|
||||
label_cache = {"strip": list(), "bus": list()}
|
||||
|
||||
def init(self, parent, id):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
self.id = id
|
||||
self.phys_in, self.virt_in = parent.kind.ins
|
||||
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.update_labels()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
@@ -248,17 +262,17 @@ class ChannelFrame(ttk.Frame):
|
||||
if isinstance(frame, ttk.LabelFrame)
|
||||
)
|
||||
|
||||
def update_labels(self):
|
||||
for labelframe in self.labelframes:
|
||||
labelframe.on_update("labelframe")
|
||||
|
||||
def on_update(self, subject):
|
||||
if subject == "pdirty":
|
||||
target = getattr(self.target, self.id)
|
||||
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")
|
||||
self.update_labels()
|
||||
|
||||
def grid_configure(self):
|
||||
[
|
||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
||||
self.columnconfigure(i, minsize=_configuration.channel_width)
|
||||
for i, _ in enumerate(self.labelframes)
|
||||
]
|
||||
[self.rowconfigure(0, minsize=100) for i, _ in enumerate(self.labelframes)]
|
||||
@@ -270,7 +284,7 @@ class ChannelFrame(ttk.Frame):
|
||||
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
|
||||
"""
|
||||
@@ -278,29 +292,33 @@ def _make_channelframe(parent, id):
|
||||
phys_in, virt_in = parent.kind.ins
|
||||
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
|
||||
"""
|
||||
|
||||
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)
|
||||
if not labelframe.target.label:
|
||||
label = labelframe.target.label
|
||||
if not label:
|
||||
self.columnconfigure(i, minsize=0)
|
||||
labelframe.grid_remove()
|
||||
self.label_cache[identifier][i] = label
|
||||
|
||||
def init_strip(self, *args, **kwargs):
|
||||
self.init(parent, id)
|
||||
self.strips = tuple(Strip(self, i, id) for i in range(phys_in + virt_in))
|
||||
self.init(parent, identifier)
|
||||
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_configure()
|
||||
init_labels(self, id)
|
||||
init_labels(self)
|
||||
|
||||
def init_bus(self, *args, **kwargs):
|
||||
self.init(parent, id)
|
||||
self.buses = tuple(Bus(self, i, id) for i in range(phys_out + virt_out))
|
||||
self.init(parent, identifier)
|
||||
self.buses = tuple(Bus(self, i, identifier) for i in range(phys_out + virt_out))
|
||||
if _configuration.extended:
|
||||
if _configuration.extends_horizontal:
|
||||
self.grid(row=0, column=2, sticky=(tk.W))
|
||||
@@ -309,11 +327,11 @@ def _make_channelframe(parent, id):
|
||||
else:
|
||||
self.grid(row=0, column=0)
|
||||
self.grid_configure()
|
||||
init_labels(self, id)
|
||||
init_labels(self)
|
||||
|
||||
if id == "strip":
|
||||
if identifier == "strip":
|
||||
CHANNELFRAME_cls = type(
|
||||
f"ChannelFrame{id.capitalize()}",
|
||||
f"ChannelFrame{identifier.capitalize()}",
|
||||
(ChannelFrame,),
|
||||
{
|
||||
"__init__": init_strip,
|
||||
@@ -321,7 +339,7 @@ def _make_channelframe(parent, id):
|
||||
)
|
||||
else:
|
||||
CHANNELFRAME_cls = type(
|
||||
f"ChannelFrame{id.capitalize()}",
|
||||
f"ChannelFrame{identifier.capitalize()}",
|
||||
(ChannelFrame,),
|
||||
{
|
||||
"__init__": init_bus,
|
||||
|
||||
@@ -68,6 +68,14 @@ class Config(ttk.Frame):
|
||||
self.parent.target.event.add("ldirty")
|
||||
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):
|
||||
_base_values.run_update = True
|
||||
|
||||
|
||||
@@ -10,28 +10,32 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
configuration = {}
|
||||
|
||||
configpaths = [
|
||||
Path.cwd() / "configs",
|
||||
Path.home() / ".config" / "vm-compact" / "configs",
|
||||
Path.home() / "Documents" / "Voicemeeter" / "configs",
|
||||
]
|
||||
for configpath in configpaths:
|
||||
if configpath.is_dir():
|
||||
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
|
||||
break
|
||||
def get_configpath():
|
||||
configpaths = [
|
||||
Path.cwd() / "configs",
|
||||
Path.home() / ".config" / "vm-compact" / "configs",
|
||||
Path.home() / "Documents" / "Voicemeeter" / "configs",
|
||||
]
|
||||
for configpath in configpaths:
|
||||
if configpath.exists():
|
||||
return configpath
|
||||
|
||||
|
||||
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 = {
|
||||
"configs": {
|
||||
@@ -48,6 +52,7 @@ _defaults = {
|
||||
"channel": {
|
||||
"width": 80,
|
||||
"height": 130,
|
||||
"xpadding": 3,
|
||||
},
|
||||
"mwscroll_step": {
|
||||
"size": 3,
|
||||
@@ -55,10 +60,16 @@ _defaults = {
|
||||
"submixes": {
|
||||
"default": 0,
|
||||
},
|
||||
"navigation": {"show": True},
|
||||
}
|
||||
|
||||
|
||||
if "app" in configuration:
|
||||
configuration["app"] = _defaults | configuration["app"]
|
||||
for key in _defaults:
|
||||
if key in configuration["app"]:
|
||||
configuration["app"][key] = _defaults[key] | configuration["app"][key]
|
||||
else:
|
||||
configuration["app"][key] = _defaults[key]
|
||||
else:
|
||||
configuration["app"] = _defaults
|
||||
|
||||
@@ -68,17 +79,20 @@ def get_configuration(key):
|
||||
return configuration[key]
|
||||
|
||||
|
||||
def loader(kind_id):
|
||||
configs = {}
|
||||
userconfigpath = Path.home() / ".config" / "vm-compact" / "configs" / 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}")
|
||||
return configs
|
||||
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
|
||||
|
||||
@@ -32,10 +32,15 @@ class Configurations(metaclass=SingletonMeta):
|
||||
# bus assigned as current submix
|
||||
submixes: int = configuration["submixes"]["default"]
|
||||
|
||||
# width of a single labelframe
|
||||
level_width: int = configuration["channel"]["width"]
|
||||
# height of a single labelframe
|
||||
level_height: int = configuration["channel"]["height"]
|
||||
# width of a single channel labelframe
|
||||
channel_width: int = configuration["channel"]["width"]
|
||||
# height of a single channel labelframe
|
||||
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
|
||||
def config(self):
|
||||
@@ -46,7 +51,7 @@ class Configurations(metaclass=SingletonMeta):
|
||||
@dataclass
|
||||
class BaseValues(metaclass=SingletonMeta):
|
||||
# pause updates after releasing scale
|
||||
run_update: bool = True
|
||||
run_update: bool = False
|
||||
# are we dragging main window with mouse 1
|
||||
dragging: bool = False
|
||||
# a vban connection established
|
||||
|
||||
@@ -78,6 +78,14 @@ class GainLayer(ttk.LabelFrame):
|
||||
self.parent.target.event.add("ldirty")
|
||||
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):
|
||||
_base_values.run_update = True
|
||||
|
||||
@@ -166,6 +174,7 @@ class GainLayer(ttk.LabelFrame):
|
||||
)
|
||||
|
||||
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))
|
||||
for child in self.winfo_children()
|
||||
@@ -253,11 +262,11 @@ class SubMixFrame(ttk.Frame):
|
||||
|
||||
def grid_configure(self):
|
||||
[
|
||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
||||
self.columnconfigure(i, minsize=_configuration.channel_width)
|
||||
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)
|
||||
]
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ class Menus(tk.Menu):
|
||||
self._is_topmost = tk.BooleanVar()
|
||||
self._lock = 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))
|
||||
|
||||
# voicemeeter menu
|
||||
@@ -83,24 +85,16 @@ class Menus(tk.Menu):
|
||||
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.config_defaults = {"reset"}
|
||||
if len(self.target.configs) > len(self.config_defaults) and all(
|
||||
key in self.target.configs for key in self.config_defaults
|
||||
if len(self.parent.userconfigs) > len(self.config_defaults) and all(
|
||||
key in self.parent.userconfigs for key in self.config_defaults
|
||||
):
|
||||
[
|
||||
self.menu_configs_load.add_command(
|
||||
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
|
||||
]
|
||||
elif self.parent.userconfigs:
|
||||
[
|
||||
self.menu_configs_load.add_command(
|
||||
label=name, command=partial(self.load_custom_profile, data)
|
||||
)
|
||||
for name, data in self.parent.userconfigs.items()
|
||||
if name not in self.config_defaults
|
||||
]
|
||||
else:
|
||||
self.menu_configs.entryconfig(0, state="disabled")
|
||||
self.menu_configs.add_command(
|
||||
@@ -162,6 +156,23 @@ class Menus(tk.Menu):
|
||||
)
|
||||
if not _configuration.themes_enabled:
|
||||
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
|
||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||
@@ -213,7 +224,10 @@ class Menus(tk.Menu):
|
||||
]
|
||||
|
||||
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):
|
||||
if cmd == "lock":
|
||||
@@ -296,16 +310,13 @@ class Menus(tk.Menu):
|
||||
|
||||
def menu_teardown(self, i):
|
||||
# remove config load menus
|
||||
[
|
||||
self.menu_configs_load.delete(key)
|
||||
for key in self.target.configs.keys()
|
||||
if key not in self.config_defaults
|
||||
]
|
||||
[
|
||||
self.menu_configs_load.delete(key)
|
||||
for key in self.parent.userconfigs.keys()
|
||||
if key not in self.config_defaults
|
||||
]
|
||||
if len(self.parent.userconfigs) > len(self.config_defaults):
|
||||
for profile in self.parent.userconfigs:
|
||||
if profile 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")
|
||||
@@ -314,27 +325,28 @@ class Menus(tk.Menu):
|
||||
]
|
||||
|
||||
def menu_setup(self):
|
||||
if len(self.target.configs) > len(self.config_defaults) and all(
|
||||
key in self.target.configs for key in self.config_defaults
|
||||
):
|
||||
[
|
||||
self.menu_configs_load.add_command(
|
||||
label=profile, command=partial(self.load_profile, profile)
|
||||
)
|
||||
for profile in self.target.configs.keys()
|
||||
if profile not in self.config_defaults
|
||||
]
|
||||
elif self.parent.userconfigs:
|
||||
[
|
||||
self.menu_configs_load.add_command(
|
||||
label=name, command=partial(self.load_custom_profile, data)
|
||||
)
|
||||
for name, data in self.parent.userconfigs.items()
|
||||
if name not in self.config_defaults
|
||||
]
|
||||
if len(self.parent.userconfigs) > len(self.config_defaults):
|
||||
for profile in self.parent.userconfigs:
|
||||
if profile not in self.config_defaults:
|
||||
self.menu_configs_load.add_command(
|
||||
label=profile, command=partial(self.load_profile, profile)
|
||||
)
|
||||
self.menu_configs.entryconfig(0, state="normal")
|
||||
else:
|
||||
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):
|
||||
opts = {}
|
||||
opts |= self.vban_config[f"connection-{i+1}"]
|
||||
@@ -389,7 +401,7 @@ class Menus(tk.Menu):
|
||||
self.vban.logout()
|
||||
# build new app frames according to a kind
|
||||
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.entryconfig(0, state="normal")
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
|
||||
@@ -15,6 +15,8 @@ class Navigation(ttk.Frame):
|
||||
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))
|
||||
if not _configuration.navigation_show:
|
||||
self.grid_remove()
|
||||
self.styletable = self.parent.styletable
|
||||
|
||||
self.builder = builders.NavigationFrameBuilder(self)
|
||||
|
||||
Reference in New Issue
Block a user