mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2024-11-15 17:40:52 +00:00
gain_label added to chanenl labelframes.
couple of bug fixes for submix frame minor version bump
This commit is contained in:
parent
92aead8754
commit
502110ae0f
172
README.md
172
README.md
@ -1 +1,171 @@
|
||||
# Not Ready Yet
|
||||
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/voicemeeter-compact/blob/main/LICENSE)
|
||||
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
|
||||
![OS: Windows](https://img.shields.io/badge/os-windows-red)
|
||||
|
||||
![Image of app/potato size comparison](./doc_imgs/potatocomparisonsmaller.png)
|
||||
|
||||
# Voicemeeter Compact
|
||||
|
||||
A compact Voicemeeter remote app, works locally and over LAN.
|
||||
|
||||
For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Voicemeeter](https://voicemeeter.com/) (Basic v1.0.8.2), (Banana v2.0.6.2) or (Potato v3.0.2.2)
|
||||
- [Git for Windows](https://gitforwindows.org/)
|
||||
- Python 3.9+
|
||||
|
||||
## Installation
|
||||
|
||||
For a step-by-step guide [click here](INSTALLATION.md)
|
||||
|
||||
```
|
||||
git clone https://github.com/onyx-and-iris/voicemeeter-compact
|
||||
cd voicemeeter-compact
|
||||
pip install .
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Example `__main__.py` file:
|
||||
|
||||
```python
|
||||
import voicemeeter
|
||||
import vmcompact
|
||||
|
||||
|
||||
def main():
|
||||
# pass the kind_id and the vmr object to the app
|
||||
with voicemeeter.remote(kind_id) as vmr:
|
||||
app = vmcompact.connect(kind_id, vmr)
|
||||
app.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# choose the kind of Voicemeeter (Local connection)
|
||||
kind_id = "banana"
|
||||
|
||||
voicemeeter.launch(kind_id, hide=False)
|
||||
|
||||
main()
|
||||
```
|
||||
|
||||
It's important to know that only labelled strips and buses will appear in the Channel frames. Removing a Channels label will cause the GUI to grow/shrink in real time.
|
||||
|
||||
![Image of unlabelled app](./doc_imgs/nolabels.png)
|
||||
|
||||
If the GUI looks like the above when you first load it, then no channels are labelled. From the menu, `Profiles->Load Profile` you may load an example config. Save your current Voicemeeter settings first :).
|
||||
|
||||
### kind_id
|
||||
|
||||
A _kind_id_ specifies a major Voicemeeter version. This may be one of:
|
||||
|
||||
- `basic`
|
||||
- `banana`
|
||||
- `potato`
|
||||
|
||||
## TOML Files
|
||||
|
||||
This is how your files should be organised. Wherever your `__main__.py` file is located (after install this can be any location), `config` and `profiles` directories
|
||||
should be in the same location.
|
||||
Regarding profiles, a directory for each kind should hold the profile files and in each there can be any number of config files. Example, a config for streaming, a config for general listening/movie watching or any other type of config.
|
||||
|
||||
.
|
||||
|
||||
├── `__main__.py`
|
||||
|
||||
├── configs
|
||||
|
||||
├── app.toml
|
||||
|
||||
├── vban.toml
|
||||
|
||||
├── profiles
|
||||
|
||||
├── basic
|
||||
|
||||
├── example.toml
|
||||
|
||||
├── other_config.toml
|
||||
|
||||
├── streaming_config.toml
|
||||
|
||||
├── banana
|
||||
|
||||
├── example.toml
|
||||
|
||||
├── other.toml
|
||||
|
||||
├── ...
|
||||
|
||||
├── potato
|
||||
|
||||
├── example.toml
|
||||
|
||||
├── ...
|
||||
|
||||
## Configs
|
||||
|
||||
### app.toml
|
||||
|
||||
Configure certain startup states for the app.
|
||||
|
||||
- `profiles`
|
||||
Configure a profile to load on app startup. Don't include the .toml extension in the profile name.
|
||||
|
||||
- `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.
|
||||
|
||||
- `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.
|
||||
|
||||
- `channel`
|
||||
For each channel labelframe the width and height may be adjusted which effects the spacing between widgets and the length of the scales and progressbars respectively.
|
||||
|
||||
- `mwscroll_step`
|
||||
Sets the amount (in db) the gain slider moves with a single mousewheel step. Default 3.
|
||||
|
||||
- `submixes`
|
||||
Select the default submix bus when Submix frame is shown. For example, a dedicated bus for OBS.
|
||||
|
||||
### vban.toml
|
||||
|
||||
Configure as many vban connections as you wish. This allows the app to work over a LAN connection as well as with a local Voicemeeter installation.
|
||||
|
||||
For vban connections to work correctly VBAN TEXT incoming stream MUST be configured correctly on the remote machine. Both pcs ought to be connected to a local private network and should be able to ping one another.
|
||||
|
||||
A valid `vban.toml` might look like this:
|
||||
|
||||
```toml
|
||||
[connection-1]
|
||||
kind = 'banana'
|
||||
ip = '192.168.1.2'
|
||||
streamname = 'streampc'
|
||||
port = 6990
|
||||
|
||||
[connection-2]
|
||||
kind = 'potato'
|
||||
ip = '192.168.1.3'
|
||||
streamname = 'worklaptop'
|
||||
port = 6990
|
||||
```
|
||||
|
||||
## Profiles
|
||||
|
||||
Three example profiles 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. For a detailed description of parameter coverage see:
|
||||
|
||||
[Voicemeeter Remote API Python](https://github.com/onyx-and-iris/voicemeeter-api-python)
|
||||
|
||||
[VBAN CMD API Python](https://github.com/onyx-and-iris/vban-cmd-python)
|
||||
|
||||
Profiles may be loaded at any time via the menu.
|
||||
|
||||
## 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!
|
||||
|
||||
[Christian Volkmann](https://github.com/chvolkmann) for the detailed work that went into creating the underlying Remote API Python Interface.
|
||||
Unfortunately, the Remote API Python Interface has `NOT` been open source licensed. I have [raised an issue](https://github.com/chvolkmann/voicemeeter-remote-python/issues/13) and asked directly and politely but so far no response. If a license is added in future I will update this section. Without an open source license there is no guarantee that in future this package may not be pulled down, without any notice.
|
||||
|
||||
[Rdbende](https://github.com/rdbende) for creating the beautiful Sun Valley Tkinter theme and adding it to Pypi!
|
||||
|
2
setup.py
2
setup.py
@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
||||
|
||||
setuptools.setup(
|
||||
name="vmcompact",
|
||||
version="1.1.2",
|
||||
version="1.2.2",
|
||||
author="Onyx and Iris",
|
||||
author_email="code@onyxandiris.online",
|
||||
description="Compact Tkinter Voicemeeter Remote App",
|
||||
|
@ -13,8 +13,6 @@ from .menu import Menus
|
||||
class App(tk.Tk):
|
||||
"""App mainframe"""
|
||||
|
||||
_instances = {}
|
||||
|
||||
@classmethod
|
||||
def make(cls, kind: NamedTuple):
|
||||
"""
|
||||
@ -34,6 +32,7 @@ class App(tk.Tk):
|
||||
|
||||
def __init__(self, vmr):
|
||||
super().__init__()
|
||||
|
||||
self._vmr = vmr
|
||||
icon_path = Path(__file__).parent.resolve() / "img" / "cat.ico"
|
||||
if icon_path.is_file():
|
||||
@ -41,6 +40,8 @@ class App(tk.Tk):
|
||||
self.minsize(275, False)
|
||||
self.subject_pdirty = Subject()
|
||||
self.subject_ldirty = Subject()
|
||||
self.strip_levels = None
|
||||
self.bus_levels = None
|
||||
self["menu"] = Menus(self, vmr)
|
||||
self.styletable = ttk.Style()
|
||||
if _configuration.profile:
|
||||
@ -74,7 +75,8 @@ class App(tk.Tk):
|
||||
self._vban = vban
|
||||
if kind:
|
||||
self.kind = kind
|
||||
# register as observer
|
||||
|
||||
# register app as observer
|
||||
self.target.subject.add(self)
|
||||
|
||||
self.bus_frame = None
|
||||
@ -90,16 +92,20 @@ class App(tk.Tk):
|
||||
if self.kind.name == "Potato":
|
||||
self.builder.create_banner()
|
||||
|
||||
def update(self, subject):
|
||||
"""
|
||||
called whenever notified of update
|
||||
def on_update(self, subject, data):
|
||||
"""called whenever notified of update"""
|
||||
|
||||
after 1 to prevent vmr,vban interface waiting.
|
||||
"""
|
||||
if subject == "pdirty" and not _base_values.in_scale_button_1:
|
||||
self.after(1, self.notify_pdirty)
|
||||
elif subject == "ldirty" and not _base_values.dragging:
|
||||
self.after(1, self.notify_ldirty)
|
||||
if not _base_values.in_scale_button_1:
|
||||
if subject == "pdirty":
|
||||
self.after(1, self.notify_pdirty)
|
||||
elif subject == "ldirty" and not _base_values.dragging:
|
||||
(
|
||||
self.strip_levels,
|
||||
self.strip_comp,
|
||||
self.bus_levels,
|
||||
self.bus_comp,
|
||||
) = data
|
||||
self.after(1, self.notify_ldirty)
|
||||
|
||||
def notify_pdirty(self):
|
||||
self.subject_pdirty.notify()
|
||||
@ -111,13 +117,13 @@ class App(tk.Tk):
|
||||
"""
|
||||
Clear observables.
|
||||
|
||||
Unregister app as observer.
|
||||
Deregister app as observer.
|
||||
|
||||
Destroy all top level frames.
|
||||
"""
|
||||
self.target.subject.remove(self)
|
||||
self.subject_pdirty.clear()
|
||||
self.subject_ldirty.clear()
|
||||
self.target.subject.remove(self)
|
||||
[
|
||||
frame.destroy()
|
||||
for frame in self.winfo_children()
|
||||
|
@ -21,7 +21,8 @@ class Banner(ttk.Frame):
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
"""returns the current interface"""
|
||||
|
||||
return self.parent.target
|
||||
|
||||
def upd_submix(self):
|
||||
|
@ -20,7 +20,7 @@ class AbstractBuilder(abc.ABC):
|
||||
|
||||
@abc.abstractmethod
|
||||
def teardown(self):
|
||||
"""unregister as observable"""
|
||||
"""deregister as observable"""
|
||||
pass
|
||||
|
||||
|
||||
@ -56,11 +56,12 @@ class MainFrameBuilder(AbstractBuilder):
|
||||
def create_configframe(self, type_, index, id):
|
||||
if type_ == "strip":
|
||||
self.app.config_frame = StripConfig(self.app, index, id)
|
||||
[
|
||||
frame.conf.set(False)
|
||||
for i, frame in enumerate(self.app.strip_frame.labelframes)
|
||||
if i != index
|
||||
]
|
||||
if self.app.strip_frame:
|
||||
[
|
||||
frame.conf.set(False)
|
||||
for i, frame in enumerate(self.app.strip_frame.labelframes)
|
||||
if i != index
|
||||
]
|
||||
if self.app.bus_frame:
|
||||
[
|
||||
frame.conf.set(False)
|
||||
@ -68,11 +69,12 @@ class MainFrameBuilder(AbstractBuilder):
|
||||
]
|
||||
else:
|
||||
self.app.config_frame = BusConfig(self.app, index, id)
|
||||
[
|
||||
frame.conf.set(False)
|
||||
for i, frame in enumerate(self.app.bus_frame.labelframes)
|
||||
if i != index
|
||||
]
|
||||
if self.app.bus_frame:
|
||||
[
|
||||
frame.conf.set(False)
|
||||
for i, frame in enumerate(self.app.bus_frame.labelframes)
|
||||
if i != index
|
||||
]
|
||||
if self.app.strip_frame:
|
||||
[
|
||||
frame.conf.set(False)
|
||||
@ -180,7 +182,7 @@ class NavigationFrameBuilder(AbstractBuilder):
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
if _configuration.themes_enabled:
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 25)
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 16)
|
||||
else:
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 5)
|
||||
|
||||
@ -200,9 +202,10 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
def setup(self):
|
||||
"""Create class variables for widgets"""
|
||||
self.labelframe.gain = tk.DoubleVar()
|
||||
self.labelframe.level = tk.DoubleVar()
|
||||
self.labelframe.level = tk.DoubleVar(value=0)
|
||||
self.labelframe.mute = tk.BooleanVar()
|
||||
self.labelframe.conf = tk.BooleanVar()
|
||||
self.labelframe.gainlabel = tk.StringVar()
|
||||
|
||||
"""for gainlayers"""
|
||||
self.labelframe.on = tk.BooleanVar()
|
||||
@ -227,7 +230,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
orient="vertical",
|
||||
variable=self.labelframe.gain,
|
||||
command=self.labelframe.scale_callback,
|
||||
length=100,
|
||||
length=_configuration.level_height,
|
||||
)
|
||||
self.scale.grid(column=1, row=0)
|
||||
self.scale.bind("<Double-Button-1>", self.labelframe.reset_gain)
|
||||
@ -237,6 +240,13 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
self.scale.bind("<Leave>", self.labelframe.scale_leave)
|
||||
self.scale.bind("<MouseWheel>", self.labelframe._on_mousewheel)
|
||||
|
||||
def add_gain_label(self):
|
||||
self.labelframe.gain_label = ttk.Label(
|
||||
self.labelframe,
|
||||
textvariable=self.labelframe.gainlabel,
|
||||
)
|
||||
self.labelframe.gain_label.grid(column=0, row=1, columnspan=2)
|
||||
|
||||
def add_mute_button(self):
|
||||
"""Adds a mute button widget to a single label frame"""
|
||||
self.button_mute = ttk.Checkbutton(
|
||||
@ -246,7 +256,7 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Mute{self.index}.TButton'}",
|
||||
variable=self.labelframe.mute,
|
||||
)
|
||||
self.button_mute.grid(column=0, row=1, columnspan=2)
|
||||
self.button_mute.grid(column=0, row=2, columnspan=2)
|
||||
|
||||
def add_conf_button(self):
|
||||
self.button_conf = ttk.Checkbutton(
|
||||
@ -256,14 +266,14 @@ class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}Conf{self.index}.TButton'}",
|
||||
variable=self.labelframe.conf,
|
||||
)
|
||||
self.button_conf.grid(column=0, row=2, columnspan=2)
|
||||
self.button_conf.grid(column=0, row=3, columnspan=2)
|
||||
|
||||
def add_on_button(self):
|
||||
self.button_on = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
text="ON",
|
||||
command=self.labelframe.set_on,
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else 'On.TButton'}",
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{self.identifier}On{self.index}.TButton'}",
|
||||
variable=self.labelframe.on,
|
||||
)
|
||||
self.button_on.grid(column=0, row=1, columnspan=2)
|
||||
@ -291,7 +301,7 @@ class ChannelConfigFrameBuilder(AbstractBuilder):
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
"""Unregister as observable, then destroy frame"""
|
||||
"""Deregister as observable, then destroy frame"""
|
||||
self.configframe.parent.subject_pdirty.remove(self.configframe)
|
||||
self.configframe.destroy()
|
||||
|
||||
@ -312,7 +322,7 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
"""Responsible for building channel configframe widgets"""
|
||||
|
||||
def setup(self):
|
||||
if self.configframe.parent.kind.ins == "Basic":
|
||||
if self.configframe.parent.kind.name == "Basic":
|
||||
self.configframe.slider_params = ("audibility",)
|
||||
self.configframe.slider_vars = (tk.DoubleVar(),)
|
||||
else:
|
||||
@ -340,6 +350,30 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
tk.BooleanVar() for _ in self.configframe.params
|
||||
)
|
||||
|
||||
if self.configframe.parent.kind.name in ("Banana", "Potato"):
|
||||
if self.configframe.index == self.configframe.phys_in:
|
||||
self.configframe.params = list(
|
||||
map(lambda x: x.replace("mono", "mc"), self.configframe.params)
|
||||
)
|
||||
if self.configframe.parent.kind.name == "Banana":
|
||||
pass
|
||||
# karaoke modes not in RT Packet yet. May implement in future
|
||||
"""
|
||||
if self.index == self.phys_in + 1:
|
||||
self.params = list(
|
||||
map(lambda x: x.replace("mono", "k"), self.params)
|
||||
)
|
||||
self.param_vars[self.params.index("k")] = tk.IntVar
|
||||
"""
|
||||
else:
|
||||
if (
|
||||
self.configframe.index
|
||||
== self.configframe.phys_in + self.configframe.virt_in - 1
|
||||
):
|
||||
self.configframe.params = list(
|
||||
map(lambda x: x.replace("mono", "mc"), self.configframe.params)
|
||||
)
|
||||
|
||||
def create_comp_slider(self):
|
||||
comp_label = ttk.Label(self.configframe, text="Comp")
|
||||
comp_scale = ttk.Scale(
|
||||
@ -356,8 +390,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
comp_scale.bind(
|
||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "comp", 0)
|
||||
)
|
||||
comp_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||
comp_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||
comp_scale.bind("<Button-1>", self.configframe.scale_press)
|
||||
comp_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
||||
comp_scale.bind("<Enter>", partial(self.configframe.scale_enter, "comp"))
|
||||
comp_scale.bind("<Leave>", self.configframe.scale_leave)
|
||||
|
||||
comp_label.grid(column=0, row=0)
|
||||
comp_scale.grid(column=1, row=0)
|
||||
@ -378,8 +414,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
gate_scale.bind(
|
||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "gate", 0)
|
||||
)
|
||||
gate_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||
gate_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||
gate_scale.bind("<Button-1>", self.configframe.scale_press)
|
||||
gate_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
||||
gate_scale.bind("<Enter>", partial(self.configframe.scale_enter, "gate"))
|
||||
gate_scale.bind("<Leave>", self.configframe.scale_leave)
|
||||
|
||||
gate_label.grid(column=2, row=0)
|
||||
gate_scale.grid(column=3, row=0)
|
||||
@ -400,8 +438,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
limit_scale.bind(
|
||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "limit", 12)
|
||||
)
|
||||
limit_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||
limit_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||
limit_scale.bind("<Button-1>", self.configframe.scale_press)
|
||||
limit_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
||||
limit_scale.bind("<Enter>", partial(self.configframe.scale_enter, "limit"))
|
||||
limit_scale.bind("<Leave>", self.configframe.scale_leave)
|
||||
|
||||
limit_label.grid(column=4, row=0)
|
||||
limit_scale.grid(column=5, row=0)
|
||||
@ -409,11 +449,11 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
def create_audibility_slider(self):
|
||||
aud_label = ttk.Label(self.configframe, text="Audibility")
|
||||
aud_scale = ttk.Scale(
|
||||
self,
|
||||
self.configframe,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_values.level_width,
|
||||
length=_configuration.level_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("audibility")
|
||||
],
|
||||
@ -422,8 +462,10 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
aud_scale.bind(
|
||||
"<Double-Button-1>", partial(self.configframe.reset_scale, "audibility", 0)
|
||||
)
|
||||
aud_scale.bind("<Button-1>", self.configframe.scale_enter)
|
||||
aud_scale.bind("<ButtonRelease-1>", self.configframe.scale_leave)
|
||||
aud_scale.bind("<Button-1>", self.configframe.scale_press)
|
||||
aud_scale.bind("<ButtonRelease-1>", self.configframe.scale_release)
|
||||
aud_scale.bind("<Enter>", partial(self.configframe.scale_enter, "audibility"))
|
||||
aud_scale.bind("<Leave>", self.configframe.scale_leave)
|
||||
|
||||
aud_label.grid(column=0, row=0)
|
||||
aud_scale.grid(column=1, row=0)
|
||||
@ -477,11 +519,9 @@ class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_p, param),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||
variable=self.configframe.param_vars[
|
||||
self.configframe.params.index(param)
|
||||
],
|
||||
variable=self.configframe.param_vars[i],
|
||||
)
|
||||
for param in self.configframe.params
|
||||
for i, param in enumerate(self.configframe.params)
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
@ -540,11 +580,9 @@ class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
text=param,
|
||||
command=partial(self.configframe.toggle_p, param),
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'{param}.TButton'}",
|
||||
variable=self.configframe.param_vars[
|
||||
self.configframe.params.index(param)
|
||||
],
|
||||
variable=self.configframe.param_vars[i],
|
||||
)
|
||||
for param in self.configframe.params
|
||||
for i, param in enumerate(self.configframe.params)
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
|
@ -22,6 +22,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
self.builder.add_scale()
|
||||
self.builder.add_mute_button()
|
||||
self.builder.add_conf_button()
|
||||
self.builder.add_gain_label()
|
||||
self.sync()
|
||||
self.grid_configure()
|
||||
|
||||
@ -49,7 +50,7 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
"""callback function for scale widget"""
|
||||
|
||||
self.setter("gain", self.gain.get())
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
self.gainlabel.set(round(self.gain.get(), 1))
|
||||
|
||||
def toggle_mute(self, *args):
|
||||
self.target.mute = self.mute.get()
|
||||
@ -62,13 +63,12 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
def reset_gain(self, *args):
|
||||
self.setter("gain", 0)
|
||||
self.gain.set(0)
|
||||
self.parent.parent.nav_frame.info_text.set(0)
|
||||
|
||||
def scale_enter(self, *args):
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
pass
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self.parent.parent.nav_frame.info_text.set("")
|
||||
pass
|
||||
|
||||
def scale_press(self, *args):
|
||||
_base_values.in_scale_button_1 = True
|
||||
@ -104,7 +104,22 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
)
|
||||
|
||||
def sync(self):
|
||||
"""sync parameters"""
|
||||
self.after(_base_values.pdelay, self.sync_params)
|
||||
self.after(100, self.sync_labels)
|
||||
|
||||
def sync_params(self):
|
||||
"""sync parameter states, update button colours"""
|
||||
self.gain.set(self.getter("gain"))
|
||||
self.gainlabel.set(round(self.gain.get(), 1))
|
||||
self.mute.set(self.getter("mute"))
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{self.identifier}Mute{self.index}.TButton",
|
||||
background=f'{"red" if self.mute.get() else "white"}',
|
||||
)
|
||||
|
||||
def sync_labels(self):
|
||||
"""sync labelframes according to label text"""
|
||||
retval = self.getter("label")
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
@ -116,13 +131,6 @@ class ChannelLabelFrame(ttk.LabelFrame):
|
||||
self.parent.parent.subject_ldirty.add(self)
|
||||
self.grid()
|
||||
self.configure(text=retval)
|
||||
self.gain.set(self.getter("gain"))
|
||||
self.mute.set(self.getter("mute"))
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{self.identifier}Mute{self.index}.TButton",
|
||||
background=f'{"red" if self.mute.get() else "white"}',
|
||||
)
|
||||
|
||||
def convert_level(self, val):
|
||||
if _base_values.vban_connected:
|
||||
@ -160,15 +168,31 @@ class Strip(ChannelLabelFrame):
|
||||
_target = super(Strip, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def update(self):
|
||||
def upd_levels(self):
|
||||
"""
|
||||
Updates level values.
|
||||
|
||||
Checks offset against expected level array size to avoid a race condition
|
||||
"""
|
||||
if self.level_offset + 1 < len(self.parent.parent.strip_levels):
|
||||
if any(
|
||||
self.parent.parent.strip_comp[self.level_offset : self.level_offset + 1]
|
||||
):
|
||||
val = self.convert_level(
|
||||
max(
|
||||
self.parent.parent.strip_levels[
|
||||
self.level_offset : self.level_offset + 1
|
||||
]
|
||||
)
|
||||
)
|
||||
self.level.set(
|
||||
(0 if self.mute.get() else 100 + val - 18 + self.gain.get())
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
"""update levels"""
|
||||
vals = (
|
||||
self.convert_level(self.parent.target.strip_levels[self.level_offset]),
|
||||
self.convert_level(self.parent.target.strip_levels[self.level_offset + 1]),
|
||||
)
|
||||
self.level.set(
|
||||
(0 if self.mute.get() else 100 + (max(vals) - 18) + self.gain.get())
|
||||
)
|
||||
|
||||
self.after(_base_values.ldelay, self.upd_levels)
|
||||
|
||||
|
||||
class Bus(ChannelLabelFrame):
|
||||
@ -185,14 +209,24 @@ class Bus(ChannelLabelFrame):
|
||||
_target = super(Bus, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def update(self):
|
||||
def upd_levels(self):
|
||||
if self.level_offset + 1 < len(self.parent.parent.bus_levels):
|
||||
if any(
|
||||
self.parent.parent.bus_comp[self.level_offset : self.level_offset + 1]
|
||||
):
|
||||
val = self.convert_level(
|
||||
max(
|
||||
self.parent.parent.bus_levels[
|
||||
self.level_offset : self.level_offset + 1
|
||||
]
|
||||
)
|
||||
)
|
||||
self.level.set((0 if self.mute.get() else 100 + val - 18))
|
||||
|
||||
def on_update(self):
|
||||
"""update levels"""
|
||||
|
||||
vals = (
|
||||
self.convert_level(self.parent.target.bus_levels[self.level_offset]),
|
||||
self.convert_level(self.parent.target.bus_levels[self.level_offset + 1]),
|
||||
)
|
||||
self.level.set((0 if self.mute.get() else 100 + (max(vals) - 18)))
|
||||
self.after(_base_values.ldelay, self.upd_levels)
|
||||
|
||||
|
||||
class ChannelFrame(ttk.Frame):
|
||||
@ -231,20 +265,23 @@ class ChannelFrame(ttk.Frame):
|
||||
self.columnconfigure(i, minsize=_configuration.level_width)
|
||||
for i, _ in enumerate(self.labelframes)
|
||||
]
|
||||
[
|
||||
self.rowconfigure(0, minsize=_configuration.level_height)
|
||||
for i, _ in enumerate(self.labelframes)
|
||||
]
|
||||
[self.rowconfigure(0, minsize=100) for i, _ in enumerate(self.labelframes)]
|
||||
|
||||
def upd_labelframe(self, labelframe):
|
||||
labelframe.sync()
|
||||
|
||||
def on_update(self):
|
||||
"""update parameters"""
|
||||
|
||||
def update(self):
|
||||
for labelframe in self.labelframes:
|
||||
labelframe.sync()
|
||||
self.after(1, self.upd_labelframe, labelframe)
|
||||
|
||||
def teardown(self):
|
||||
# deregisters channelframe as pdirty observer
|
||||
|
||||
self.parent.subject_pdirty.remove(self)
|
||||
self.destroy()
|
||||
setattr(self.parent, f"{self.identifier}_frame", None)
|
||||
|
||||
|
||||
def _make_channelframe(parent, id):
|
||||
@ -280,7 +317,7 @@ def _make_channelframe(parent, id):
|
||||
self.buses = tuple(Bus(self, i, id) for i in range(phys_out + virt_out))
|
||||
if _configuration.extended:
|
||||
if _configuration.extends_horizontal:
|
||||
self.grid(row=0, column=2)
|
||||
self.grid(row=0, column=2, sticky=(tk.W))
|
||||
else:
|
||||
self.grid(row=2, column=0, sticky=(tk.W))
|
||||
else:
|
||||
|
@ -29,16 +29,24 @@ class Config(ttk.Frame):
|
||||
return self.parent.target
|
||||
|
||||
def getter(self, param):
|
||||
return getattr(self.target, param)
|
||||
if param in dir(self.target):
|
||||
return getattr(self.target, param)
|
||||
|
||||
def setter(self, param, value):
|
||||
setattr(self.target, param, value)
|
||||
if param in dir(self.target):
|
||||
setattr(self.target, param, value)
|
||||
|
||||
def scale_enter(self, *args):
|
||||
def scale_press(self, *args):
|
||||
_base_values.in_scale_button_1 = True
|
||||
|
||||
def scale_leave(self, *args):
|
||||
def scale_release(self, *args):
|
||||
_base_values.in_scale_button_1 = False
|
||||
|
||||
def scale_enter(self, param, *args):
|
||||
val = self.slider_vars[self.slider_params.index(param)].get()
|
||||
self.parent.nav_frame.info_text.set(round(val, 1))
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self.parent.nav_frame.info_text.set("")
|
||||
|
||||
def scale_callback(self, param, *args):
|
||||
@ -60,14 +68,16 @@ class Config(ttk.Frame):
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def update(self):
|
||||
self.sync()
|
||||
def on_update(self):
|
||||
"""update parameters"""
|
||||
|
||||
self.after(_base_values.pdelay, self.sync)
|
||||
|
||||
|
||||
class StripConfig(Config):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent, index, _id)
|
||||
self.grid(column=0, row=1, columnspan=4)
|
||||
self.grid(column=0, row=1, columnspan=4, padx=(2,))
|
||||
self.builder = builders.StripConfigFrameBuilder(self)
|
||||
self.builder.setup()
|
||||
self.make_row_0()
|
||||
@ -121,43 +131,39 @@ class StripConfig(Config):
|
||||
|
||||
def sync(self):
|
||||
[
|
||||
self.phys_out_params_vars[self.phys_out_params.index(param)].set(
|
||||
self.getter(param)
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
self.phys_out_params_vars[i].set(self.getter(param))
|
||||
for i, param in enumerate(self.phys_out_params)
|
||||
]
|
||||
[
|
||||
self.virt_out_params_vars[self.virt_out_params.index(param)].set(
|
||||
self.getter(param)
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
self.virt_out_params_vars[i].set(self.getter(param))
|
||||
for i, param in enumerate(self.virt_out_params)
|
||||
]
|
||||
[
|
||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||
for param in self.params
|
||||
self.param_vars[i].set(self.getter(param))
|
||||
for i, param in enumerate(self.params)
|
||||
]
|
||||
|
||||
if not _configuration.themes_enabled:
|
||||
[
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.phys_out_params_vars[self.phys_out_params.index(param)].get() else "white"}',
|
||||
background=f'{"green" if self.phys_out_params_vars[i].get() else "white"}',
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
for i, param in enumerate(self.phys_out_params)
|
||||
]
|
||||
[
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.virt_out_params_vars[self.virt_out_params.index(param)].get() else "white"}',
|
||||
background=f'{"green" if self.virt_out_params_vars[i].get() else "white"}',
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
for i, param in enumerate(self.virt_out_params)
|
||||
]
|
||||
[
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||
background=f'{"green" if self.param_vars[i].get() else "white"}',
|
||||
)
|
||||
for param in self.params
|
||||
for i, param in enumerate(self.params)
|
||||
]
|
||||
|
||||
|
||||
@ -165,9 +171,9 @@ class BusConfig(Config):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent, index, _id)
|
||||
if _configuration.extends_horizontal:
|
||||
self.grid(column=0, row=1, columnspan=4)
|
||||
self.grid(column=0, row=1, columnspan=4, padx=(2,))
|
||||
else:
|
||||
self.grid(column=0, row=3, columnspan=4)
|
||||
self.grid(column=0, row=3, columnspan=4, padx=(2,))
|
||||
self.builder = builders.BusConfigFrameBuilder(self)
|
||||
self.builder.setup()
|
||||
self.make_row_0()
|
||||
@ -227,19 +233,15 @@ class BusConfig(Config):
|
||||
|
||||
def sync(self):
|
||||
[
|
||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||
for param in self.params
|
||||
self.param_vars[i].set(self.getter(param))
|
||||
for i, param in enumerate(self.params)
|
||||
]
|
||||
self.bus_mode_label_text.set(self.bus_mode_map[self.current_bus_mode()])
|
||||
if not _configuration.themes_enabled:
|
||||
[
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||
background=f'{"green" if self.param_vars[i].get() else "white"}',
|
||||
)
|
||||
for param in self.params
|
||||
for i, param in enumerate(self.params)
|
||||
]
|
||||
|
||||
|
||||
class Iterator:
|
||||
pass
|
||||
|
@ -19,9 +19,9 @@ class SingletonMeta(type):
|
||||
@dataclass
|
||||
class Configurations(metaclass=SingletonMeta):
|
||||
# width of a single labelframe
|
||||
level_width: int = 75
|
||||
level_width: int = configuration["channel"]["width"]
|
||||
# height of a single labelframe
|
||||
level_height: int = 100
|
||||
level_height: int = configuration["channel"]["height"]
|
||||
|
||||
# is the gui extended
|
||||
extended: bool = configuration["extends"]["extended"]
|
||||
@ -53,11 +53,7 @@ class BaseValues(metaclass=SingletonMeta):
|
||||
# pdirty delay
|
||||
pdelay: int = 5
|
||||
# ldirty delay
|
||||
ldelay: int = 50
|
||||
# size of strip level array for a kind
|
||||
strip_level_array_size: int = None
|
||||
# size of bus level array for a kind
|
||||
bus_level_array_size: int = None
|
||||
ldelay: int = 5
|
||||
|
||||
|
||||
_base_values = BaseValues()
|
||||
|
@ -2,8 +2,8 @@ import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from math import log
|
||||
|
||||
from .data import _base_values, _configuration
|
||||
from . import builders
|
||||
from .data import _base_values, _configuration
|
||||
|
||||
|
||||
class GainLayer(ttk.LabelFrame):
|
||||
@ -35,6 +35,10 @@ class GainLayer(ttk.LabelFrame):
|
||||
_target = self.parent.target
|
||||
return _target.strip[self.index].gainlayer[self.j]
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return "gainlayer"
|
||||
|
||||
def getter(self, param):
|
||||
if param in dir(self.target):
|
||||
return getattr(self.target, param)
|
||||
@ -92,10 +96,78 @@ class GainLayer(ttk.LabelFrame):
|
||||
)
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"On.TButton",
|
||||
f"{self.identifier}On{self.index}.TButton",
|
||||
background=f'{"green" if self.on.get() else "white"}',
|
||||
)
|
||||
|
||||
def sync(self):
|
||||
self.after(_base_values.pdelay, self.sync_params)
|
||||
self.after(100, self.sync_labels)
|
||||
|
||||
def sync_params(self):
|
||||
self.gain.set(self.getter("gain"))
|
||||
self.on.set(
|
||||
getattr(
|
||||
self.parent.target.strip[self.index],
|
||||
self.parent.buses[self.j],
|
||||
)
|
||||
)
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{self.identifier}On{self.index}.TButton",
|
||||
background=f'{"green" if self.on.get() else "white"}',
|
||||
)
|
||||
|
||||
def sync_labels(self):
|
||||
"""sync params with voicemeeter"""
|
||||
retval = self.parent.target.strip[self.index].label
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
if not retval:
|
||||
self.parent.columnconfigure(self.index, minsize=0)
|
||||
self.parent.parent.subject_ldirty.remove(self)
|
||||
self.grid_remove()
|
||||
else:
|
||||
self.parent.parent.subject_ldirty.add(self)
|
||||
self.grid()
|
||||
self.configure(text=retval)
|
||||
|
||||
def convert_level(self, val):
|
||||
if _base_values.vban_connected:
|
||||
return round(-val * 0.01, 1)
|
||||
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
||||
|
||||
def upd_levels(self):
|
||||
"""
|
||||
Updates level values.
|
||||
|
||||
Checks offset against expected level array size to avoid a race condition
|
||||
"""
|
||||
if self.level_offset + 1 < len(self.parent.parent.strip_levels):
|
||||
if any(
|
||||
self.parent.parent.strip_comp[self.level_offset : self.level_offset + 1]
|
||||
):
|
||||
val = self.convert_level(
|
||||
max(
|
||||
self.parent.parent.strip_levels[
|
||||
self.level_offset : self.level_offset + 1
|
||||
]
|
||||
)
|
||||
)
|
||||
self.level.set(
|
||||
(
|
||||
0
|
||||
if self.parent.target.strip[self.index].mute
|
||||
or not self.on.get()
|
||||
else 100 + val - 18 + self.gain.get()
|
||||
)
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
"""update levels"""
|
||||
|
||||
self.after(_base_values.ldelay, self.upd_levels)
|
||||
|
||||
def grid_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
@ -116,47 +188,6 @@ class GainLayer(ttk.LabelFrame):
|
||||
else:
|
||||
self.rowconfigure(1, minsize=55)
|
||||
|
||||
def sync(self):
|
||||
"""sync params with voicemeeter"""
|
||||
retval = self.parent.target.strip[self.index].label
|
||||
if len(retval) > 10:
|
||||
retval = f"{retval[:8]}.."
|
||||
if not retval:
|
||||
self.parent.columnconfigure(self.index, minsize=0)
|
||||
self.parent.parent.subject_ldirty.remove(self)
|
||||
self.grid_remove()
|
||||
else:
|
||||
self.parent.parent.subject_ldirty.add(self)
|
||||
self.grid()
|
||||
self.configure(text=retval)
|
||||
self.gain.set(self.getter("gain"))
|
||||
self.on.set(
|
||||
getattr(
|
||||
self.parent.target.strip[self.index],
|
||||
self.parent.buses[self.j],
|
||||
)
|
||||
)
|
||||
|
||||
def convert_level(self, val):
|
||||
if _base_values.vban_connected:
|
||||
return round(-val * 0.01, 1)
|
||||
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
||||
|
||||
def update(self):
|
||||
"""update levels"""
|
||||
vals = (
|
||||
self.convert_level(self.parent.target.strip_levels[self.level_offset]),
|
||||
self.convert_level(self.parent.target.strip_levels[self.level_offset + 1]),
|
||||
)
|
||||
self.level.set(
|
||||
(
|
||||
0
|
||||
if self.parent.parent.strip_frame.strips[self.index].mute.get()
|
||||
or not self.on.get()
|
||||
else 100 + (max(vals) - 18) + self.gain.get()
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class SubMixFrame(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
@ -182,13 +213,27 @@ class SubMixFrame(ttk.Frame):
|
||||
if parent.bus_frame:
|
||||
parent.bus_frame.grid_remove()
|
||||
else:
|
||||
self._parent.submix_frame.grid(row=2, column=0)
|
||||
if parent.bus_frame:
|
||||
self.grid(
|
||||
row=parent.bus_frame.grid_info()["row"], column=0, sticky=(tk.W)
|
||||
)
|
||||
parent.bus_frame.grid_remove()
|
||||
else:
|
||||
self.grid(row=2, column=0, sticky=(tk.W))
|
||||
|
||||
# registers submixframe as pdirty observer
|
||||
self.parent.subject_pdirty.add(self)
|
||||
|
||||
self.grid_configure()
|
||||
"""
|
||||
Grids each labelframe, grid_removes any without a label
|
||||
"""
|
||||
for i, labelframe in enumerate(self.labelframes):
|
||||
labelframe.grid(row=0, column=i)
|
||||
if not self.target.strip[i].label:
|
||||
self.columnconfigure(i, minsize=0)
|
||||
labelframe.grid_remove()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
@ -215,9 +260,12 @@ class SubMixFrame(ttk.Frame):
|
||||
for i, _ in enumerate(self.labelframes)
|
||||
]
|
||||
|
||||
def update(self):
|
||||
def upd_labelframe(self, labelframe):
|
||||
labelframe.sync()
|
||||
|
||||
def on_update(self):
|
||||
for labelframe in self.labelframes:
|
||||
labelframe.sync()
|
||||
self.after(1, self.upd_labelframe, labelframe)
|
||||
|
||||
def teardown(self):
|
||||
# deregisters submixframe as pdirty observer
|
||||
|
@ -21,6 +21,8 @@ class Menus(tk.Menu):
|
||||
self.vban_config = get_configuration("vban")
|
||||
self.app_config = get_configuration("app")
|
||||
self._is_topmost = tk.BooleanVar()
|
||||
self._lock = tk.BooleanVar()
|
||||
self._unlock = tk.BooleanVar()
|
||||
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
||||
|
||||
# voicemeeter menu
|
||||
@ -59,11 +61,19 @@ class Menus(tk.Menu):
|
||||
self.menu_voicemeeter.add_cascade(
|
||||
menu=self.menu_lock, label="GUI Lock", underline=0
|
||||
)
|
||||
self.menu_lock.add_command(
|
||||
label="Lock", command=partial(self.action_set_voicemeeter, "lock")
|
||||
self.menu_lock.add_checkbutton(
|
||||
label="Lock",
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=self._lock,
|
||||
command=partial(self.action_set_voicemeeter, "lock"),
|
||||
)
|
||||
self.menu_lock.add_command(
|
||||
label="Unlock", command=partial(self.action_set_voicemeeter, "lock", False)
|
||||
self.menu_lock.add_checkbutton(
|
||||
label="Unlock",
|
||||
onvalue=1,
|
||||
offvalue=0,
|
||||
variable=self._unlock,
|
||||
command=partial(self.action_set_voicemeeter, "lock", False),
|
||||
)
|
||||
|
||||
# profiles menu
|
||||
@ -140,7 +150,7 @@ class Menus(tk.Menu):
|
||||
state="disabled",
|
||||
)
|
||||
if not _configuration.themes_enabled:
|
||||
self.entryconfig(6, state="disabled")
|
||||
self.menu_layout.entryconfig(2, state="disabled")
|
||||
|
||||
# vban connect menu
|
||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||
@ -162,7 +172,7 @@ class Menus(tk.Menu):
|
||||
)
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
else:
|
||||
self.entryconfig(3, state="disabled")
|
||||
self.entryconfig(4, state="disabled")
|
||||
|
||||
# Help menu
|
||||
self.menu_help = tk.Menu(self, tearoff=0)
|
||||
@ -195,6 +205,9 @@ class Menus(tk.Menu):
|
||||
getattr(self.target.command, cmd)()
|
||||
|
||||
def action_set_voicemeeter(self, cmd, val=True):
|
||||
if cmd == "lock":
|
||||
self._lock.set(val)
|
||||
self._unlock.set(not self._lock.get())
|
||||
setattr(self.target.command, cmd, val)
|
||||
|
||||
def load_profile(self, profile):
|
||||
@ -226,11 +239,11 @@ class Menus(tk.Menu):
|
||||
self.parent.submix_frame.teardown()
|
||||
self.parent.nav_frame.show_submix()
|
||||
for j, var in enumerate(self._selected_bus):
|
||||
var.set(True if i == j else False)
|
||||
var.set(i == j)
|
||||
|
||||
def load_theme(self, theme):
|
||||
sv_ttk.set_theme(theme)
|
||||
self.app_config["theme"]["mode"] = theme
|
||||
_configuration.theme_mode = theme
|
||||
self.menu_themes.entryconfig(
|
||||
0,
|
||||
state=f"{'disabled' if theme == 'light' else 'normal'}",
|
||||
@ -273,6 +286,7 @@ class Menus(tk.Menu):
|
||||
# destroy the current App frames
|
||||
self.parent._destroy_top_level_frames()
|
||||
_base_values.vban_connected = True
|
||||
self.vmr.end_thread()
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(kind_id)
|
||||
self.parent.build_app(kind, self.vban)
|
||||
@ -288,6 +302,7 @@ class Menus(tk.Menu):
|
||||
self.parent._destroy_top_level_frames()
|
||||
_base_values.vban_connected = False
|
||||
# logout of vban interface
|
||||
self.vmr.init_thread()
|
||||
self.vban.logout()
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(self.vmr.type)
|
||||
|
@ -10,7 +10,7 @@ class Navigation(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self.parent = parent
|
||||
self.grid(row=0, column=3, sticky=(tk.W, tk.E))
|
||||
self.grid(row=0, column=3, padx=(0, 5), pady=(5, 5), sticky=(tk.W, tk.E))
|
||||
self.styletable = self.parent.styletable
|
||||
|
||||
self.builder = builders.NavigationFrameBuilder(self)
|
||||
|
@ -5,10 +5,10 @@ class Subject:
|
||||
self._observables = []
|
||||
|
||||
def notify(self, modifier=None):
|
||||
"""Alert the observers"""
|
||||
"""run callbacks on update"""
|
||||
|
||||
for observer in self._observables:
|
||||
observer.update()
|
||||
observer.on_update()
|
||||
|
||||
def add(self, observer):
|
||||
"""adds an observer to observables"""
|
||||
@ -26,7 +26,10 @@ class Subject:
|
||||
|
||||
def get(self) -> list:
|
||||
"""returns the current observables"""
|
||||
|
||||
return self._observables
|
||||
|
||||
def clear(self):
|
||||
"""clears the observables list"""
|
||||
|
||||
self._observables.clear()
|
||||
|
Loading…
Reference in New Issue
Block a user