mirror of
https://github.com/onyx-and-iris/voicemeeter-compact.git
synced 2026-04-13 11:03:31 +00:00
gui rewritten with builder classes and observables
gui rewritten with builder classes and observables
This commit is contained in:
222
vmcompact/app.py
222
vmcompact/app.py
@@ -2,27 +2,27 @@ import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from typing import NamedTuple
|
||||
from pathlib import Path
|
||||
import sv_ttk
|
||||
|
||||
from .errors import VMCompactErrors
|
||||
from .data import _base_vals, _kinds_all
|
||||
from .channels import ChannelFrame
|
||||
from .navigation import Navigation
|
||||
from .data import _kinds_all, _configuration, _base_values
|
||||
from .subject import Subject
|
||||
from .builders import MainFrameBuilder
|
||||
from .menu import Menus
|
||||
from .banner import Banner
|
||||
from .configurations import configuration
|
||||
|
||||
|
||||
class App(tk.Tk):
|
||||
"""Topmost Level of App"""
|
||||
"""App mainframe"""
|
||||
|
||||
_instances = {}
|
||||
|
||||
@classmethod
|
||||
def make(cls, kind: NamedTuple):
|
||||
"""
|
||||
Factory function for App
|
||||
Factory function for App.
|
||||
|
||||
Returns an App class of a kind
|
||||
Returns an App class of a kind.
|
||||
"""
|
||||
|
||||
APP_cls = type(
|
||||
f"Voicemeeter{kind.name}.Compact",
|
||||
(cls,),
|
||||
@@ -34,106 +34,34 @@ class App(tk.Tk):
|
||||
|
||||
def __init__(self, vmr):
|
||||
super().__init__()
|
||||
defaults = {
|
||||
"profiles": {
|
||||
"profile": None,
|
||||
},
|
||||
"theme": {
|
||||
"enabled": True,
|
||||
"mode": "light",
|
||||
},
|
||||
"extends": {
|
||||
"extended": True,
|
||||
"extends_horizontal": True,
|
||||
},
|
||||
"channel": {
|
||||
"width": 80,
|
||||
"height": 130,
|
||||
},
|
||||
"mwscroll_step": {
|
||||
"size": 3,
|
||||
},
|
||||
"submixes": {
|
||||
"default": 0,
|
||||
},
|
||||
}
|
||||
if configuration:
|
||||
self.configuration = defaults | self.configuration
|
||||
else:
|
||||
configuration["app"] = defaults
|
||||
_base_vals.themes_enabled = self.configuration["theme"]["enabled"]
|
||||
_base_vals.extends_horizontal = self.configuration["extends"][
|
||||
"extends_horizontal"
|
||||
]
|
||||
_base_vals.submixes = self.configuration["submixes"]["default"]
|
||||
_base_vals.mwscroll_step = self.configuration["mwscroll_step"]["size"]
|
||||
self.bus_modes_cache = {
|
||||
"vmr": list(tk.StringVar(value="normal") for _ in range(8)),
|
||||
"vban": list(tk.StringVar(value="normal") for _ in range(8)),
|
||||
}
|
||||
|
||||
if (
|
||||
"profiles" in self.configuration
|
||||
and self.configuration["profiles"]["profile"]
|
||||
):
|
||||
vmr.apply_profile(self.configuration["profiles"]["profile"])
|
||||
|
||||
# create menus
|
||||
self._vmr = vmr
|
||||
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_pdirty = Subject()
|
||||
self.subject_ldirty = Subject()
|
||||
self["menu"] = Menus(self, vmr)
|
||||
self.styletable = ttk.Style()
|
||||
self._vmr = vmr
|
||||
if _configuration.profile:
|
||||
vmr.apply_profile(_configuration.profile)
|
||||
|
||||
# start watchers, initialize level arrays
|
||||
self.upd_pdirty()
|
||||
self.strip_levels = self.target.strip_levels
|
||||
self.bus_levels = self.target.bus_levels
|
||||
self.watch_levels()
|
||||
|
||||
self.resizable(False, False)
|
||||
if _base_vals.themes_enabled:
|
||||
self.apply_theme()
|
||||
self._make_app(self.kind)
|
||||
self.build_app()
|
||||
|
||||
self.drag_id = ""
|
||||
self.bind("<Configure>", self.dragging)
|
||||
|
||||
self.iconbitmap(Path(__file__).parent.resolve() / "img" / "cat.ico")
|
||||
|
||||
self.minsize(275, False)
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._vban if _base_vals.vban_connected else self._vmr
|
||||
|
||||
@property
|
||||
def pdirty(self):
|
||||
return self._pdirty
|
||||
|
||||
@pdirty.setter
|
||||
def pdirty(self, val):
|
||||
self._pdirty = val
|
||||
|
||||
@property
|
||||
def ldirty(self):
|
||||
return self._ldirty
|
||||
|
||||
@ldirty.setter
|
||||
def ldirty(self, val):
|
||||
self._ldirty = val
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
return configuration["app"]
|
||||
|
||||
@configuration.setter
|
||||
def configuration(self, val):
|
||||
self.configuration["app"] = val
|
||||
return self._vban if _base_values.vban_connected else self._vmr
|
||||
|
||||
@property
|
||||
def configframes(self):
|
||||
"""returns a tuple of current config frame addresses"""
|
||||
return tuple(
|
||||
"""returns the current configframes"""
|
||||
|
||||
return (
|
||||
frame
|
||||
for frame in self.winfo_children()
|
||||
if isinstance(frame, ttk.Frame)
|
||||
@@ -141,86 +69,73 @@ class App(tk.Tk):
|
||||
or "!busconfig" in str(frame)
|
||||
)
|
||||
|
||||
def apply_theme(self):
|
||||
_base_vals.using_theme = True
|
||||
sv_ttk.set_theme(self.configuration["theme"]["mode"])
|
||||
|
||||
def _make_app(self, kind, vban=None):
|
||||
self.title(
|
||||
f'Voicemeeter{kind.name}.Compact [{"Local" if not vban else "Network"} Connection]'
|
||||
)
|
||||
def build_app(self, kind=None, vban=None):
|
||||
"""builds the app frames according to a kind"""
|
||||
self._vban = vban
|
||||
self.kind = kind
|
||||
self.strip_levels = self.target.strip_levels
|
||||
self.bus_levels = self.target.bus_levels
|
||||
if kind:
|
||||
self.kind = kind
|
||||
# register as observer
|
||||
self.target.subject.add(self)
|
||||
|
||||
self._make_top_level_frames()
|
||||
|
||||
def _make_top_level_frames(self):
|
||||
# initialize bus frame variable
|
||||
self.bus_frame = None
|
||||
# channel_frame, left aligned
|
||||
self.channel_frame = ChannelFrame.make_strips(self)
|
||||
self.channel_frame.grid(row=0, column=0, sticky=(tk.W))
|
||||
# separator
|
||||
self.sep = ttk.Separator(self, orient="vertical")
|
||||
self.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
||||
self.columnconfigure(1, minsize=15)
|
||||
|
||||
# navigation frame
|
||||
self.nav_frame = Navigation(self)
|
||||
self.nav_frame.grid(row=0, column=3, sticky=(tk.E))
|
||||
|
||||
if self.configuration["extends"]["extended"]:
|
||||
self.submix_frame = None
|
||||
self.builder = MainFrameBuilder(self)
|
||||
self.builder.setup()
|
||||
self.builder.create_channelframe("strip")
|
||||
self.builder.create_separator()
|
||||
self.builder.create_navframe()
|
||||
if _configuration.extended:
|
||||
self.nav_frame.extend.set(True)
|
||||
self.nav_frame.extend_frame()
|
||||
|
||||
if self.kind.name == "Potato":
|
||||
self.banner = Banner(self)
|
||||
self.banner.grid(row=4, column=0, columnspan=3)
|
||||
self.builder.create_banner()
|
||||
|
||||
def update(self, subject):
|
||||
"""
|
||||
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)
|
||||
|
||||
def notify_pdirty(self):
|
||||
self.subject_pdirty.notify()
|
||||
|
||||
def notify_ldirty(self):
|
||||
self.subject_ldirty.notify()
|
||||
|
||||
def _destroy_top_level_frames(self):
|
||||
"""
|
||||
Clear observables.
|
||||
|
||||
Unregister app as observer.
|
||||
|
||||
Destroy all top level frames.
|
||||
"""
|
||||
self.subject_pdirty.clear()
|
||||
self.subject_ldirty.clear()
|
||||
self.target.subject.remove(self)
|
||||
[
|
||||
frame.destroy()
|
||||
for frame in self.winfo_children()
|
||||
if isinstance(frame, ttk.Frame)
|
||||
]
|
||||
|
||||
def upd_pdirty(self):
|
||||
self.after(1, self.upd_pdirty_step)
|
||||
|
||||
def upd_pdirty_step(self):
|
||||
self.pdirty = self.target.pdirty
|
||||
self.after(_base_vals.pdelay, self.upd_pdirty_step)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
|
||||
def watch_levels_step(self):
|
||||
"""Continuously fetch level arrays, only update if ldirty"""
|
||||
_strip_levels = self.target.strip_levels
|
||||
_bus_levels = self.target.bus_levels
|
||||
self.comp_strip = [not a == b for a, b in zip(self.strip_levels, _strip_levels)]
|
||||
self.comp_bus = [not a == b for a, b in zip(self.bus_levels, _bus_levels)]
|
||||
|
||||
self.ldirty = any(any(l) for l in (self.comp_strip, self.comp_bus))
|
||||
if self.ldirty:
|
||||
self.strip_levels = _strip_levels
|
||||
self.bus_levels = _bus_levels
|
||||
self.after(_base_vals.ldelay, self.watch_levels_step)
|
||||
|
||||
def dragging(self, event, *args):
|
||||
if event.widget is self:
|
||||
if self.drag_id == "":
|
||||
_base_vals.in_scale_button_1 = True
|
||||
_base_vals.dragging = True
|
||||
_base_values.in_scale_button_1 = True
|
||||
_base_values.dragging = True
|
||||
else:
|
||||
self.after_cancel(self.drag_id)
|
||||
self.drag_id = self.after(100, self.stop_drag)
|
||||
|
||||
def stop_drag(self):
|
||||
_base_vals.dragging = False
|
||||
_base_vals.in_scale_button_1 = False
|
||||
_base_values.dragging = False
|
||||
_base_values.in_scale_button_1 = False
|
||||
self.drag_id = ""
|
||||
|
||||
|
||||
@@ -229,6 +144,7 @@ _apps = {kind.id: App.make(kind) for kind in _kinds_all}
|
||||
|
||||
def connect(kind_id: str, vmr) -> App:
|
||||
"""return App of the kind requested"""
|
||||
|
||||
try:
|
||||
VMMIN_cls = _apps[kind_id]
|
||||
return VMMIN_cls(vmr)
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from .data import _base_vals
|
||||
from .data import _base_values
|
||||
|
||||
|
||||
class Banner(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
super().__init__()
|
||||
self._parent = parent
|
||||
self.parent = parent
|
||||
self.submix = tk.StringVar()
|
||||
self.submix.set(self.target.bus[_base_vals.submixes].label)
|
||||
self.submix.set(self.target.bus[_base_values.submixes].label)
|
||||
|
||||
self.label = ttk.Label(
|
||||
self,
|
||||
@@ -22,13 +22,13 @@ class Banner(ttk.Frame):
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
return self._parent.target
|
||||
return self.parent.target
|
||||
|
||||
def upd_submix(self):
|
||||
self.after(1, self.upd_submix_step)
|
||||
|
||||
def upd_submix_step(self):
|
||||
if not _base_vals.dragging:
|
||||
self.submix.set(self.target.bus[_base_vals.submixes].label)
|
||||
if not _base_values.dragging:
|
||||
self.submix.set(self.target.bus[_base_values.submixes].label)
|
||||
self.label["text"] = f"SUBMIX: {self.submix.get().upper()}"
|
||||
self.after(100, self.upd_submix_step)
|
||||
|
||||
555
vmcompact/builders.py
Normal file
555
vmcompact/builders.py
Normal file
@@ -0,0 +1,555 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from functools import partial
|
||||
import abc
|
||||
import sv_ttk
|
||||
|
||||
|
||||
from .data import _base_values, _configuration
|
||||
from .channels import _make_channelframe
|
||||
from .navigation import Navigation
|
||||
from .config import StripConfig, BusConfig
|
||||
from .banner import Banner
|
||||
|
||||
|
||||
class AbstractBuilder(abc.ABC):
|
||||
@abc.abstractmethod
|
||||
def setup(self):
|
||||
"""register as observable"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def teardown(self):
|
||||
"""unregister as observable"""
|
||||
pass
|
||||
|
||||
|
||||
class MainFrameBuilder(AbstractBuilder):
|
||||
"""Responsible for building the frames that sit directly on the mainframe"""
|
||||
|
||||
def __init__(self, app):
|
||||
self.kind = app.kind
|
||||
self.app = app
|
||||
|
||||
def setup(self):
|
||||
self.app.title(
|
||||
f'Voicemeeter{self.kind.name}.Compact [{"Local" if not _base_values.vban_connected else "Network"} Connection]'
|
||||
)
|
||||
self.app.resizable(False, False)
|
||||
if _configuration.themes_enabled:
|
||||
sv_ttk.set_theme(_configuration.theme_mode)
|
||||
|
||||
def create_channelframe(self, type_):
|
||||
if type_ == "strip":
|
||||
self.app.strip_frame = _make_channelframe(self.app, type_)
|
||||
else:
|
||||
self.app.bus_frame = _make_channelframe(self.app, type_)
|
||||
|
||||
def create_separator(self):
|
||||
self.app.sep = ttk.Separator(self.app, orient="vertical")
|
||||
self.app.sep.grid(row=0, column=1, sticky=(tk.N, tk.S))
|
||||
self.app.columnconfigure(1, minsize=15)
|
||||
|
||||
def create_navframe(self):
|
||||
self.app.nav_frame = Navigation(self.app)
|
||||
|
||||
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.bus_frame:
|
||||
[
|
||||
frame.conf.set(False)
|
||||
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
||||
]
|
||||
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.strip_frame:
|
||||
[
|
||||
frame.conf.set(False)
|
||||
for _, frame in enumerate(self.app.strip_frame.labelframes)
|
||||
]
|
||||
if not _configuration.themes_enabled:
|
||||
if self.app.strip_frame:
|
||||
[
|
||||
frame.styletable.configure(
|
||||
f"{frame.identifier}Conf{frame.index}.TButton",
|
||||
background=f"{'white' if not frame.conf.get() else 'yellow'}",
|
||||
)
|
||||
for _, frame in enumerate(self.app.strip_frame.labelframes)
|
||||
]
|
||||
if self.app.bus_frame:
|
||||
[
|
||||
frame.styletable.configure(
|
||||
f"{frame.identifier}Conf{frame.index}.TButton",
|
||||
background=f"{'white' if not frame.conf.get() else 'yellow'}",
|
||||
)
|
||||
for _, frame in enumerate(self.app.bus_frame.labelframes)
|
||||
]
|
||||
self.app.after(5, self.reset_config_frames)
|
||||
|
||||
def reset_config_frames(self):
|
||||
[
|
||||
frame.teardown()
|
||||
for frame in self.app.configframes
|
||||
if frame != self.app.config_frame
|
||||
]
|
||||
|
||||
def create_banner(self):
|
||||
self.app.banner = Banner(self.app)
|
||||
self.app.banner.grid(row=4, column=0, columnspan=3)
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
|
||||
class NavigationFrameBuilder(AbstractBuilder):
|
||||
"""Responsible for building navigation frame widgets"""
|
||||
|
||||
def __init__(self, navframe):
|
||||
self.navframe = navframe
|
||||
|
||||
def setup(self):
|
||||
self.navframe.submix = tk.BooleanVar()
|
||||
self.navframe.channel = tk.BooleanVar()
|
||||
self.navframe.extend = tk.BooleanVar(value=_configuration.extended)
|
||||
self.navframe.info = tk.BooleanVar()
|
||||
|
||||
self.navframe.channel_text = tk.StringVar(
|
||||
value=f"{self.navframe.parent.strip_frame.identifier.upper()}"
|
||||
)
|
||||
self.navframe.extend_text = tk.StringVar(
|
||||
value=f"{'REDUCE' if self.navframe.extend.get() else 'EXTEND'}"
|
||||
)
|
||||
self.navframe.info_text = tk.StringVar()
|
||||
|
||||
def create_submix_button(self):
|
||||
self.navframe.submix_button = ttk.Checkbutton(
|
||||
self.navframe,
|
||||
text="SUBMIX",
|
||||
command=self.navframe.show_submix,
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Submix.TButton'}",
|
||||
variable=self.navframe.submix,
|
||||
)
|
||||
self.navframe.submix_button.grid(column=0, row=0)
|
||||
if self.navframe.parent.kind.name != "Potato":
|
||||
self.navframe.submix_button["state"] = "disabled"
|
||||
|
||||
def create_channel_button(self):
|
||||
self.navframe.channel_button = ttk.Checkbutton(
|
||||
self.navframe,
|
||||
textvariable=self.navframe.channel_text,
|
||||
command=self.navframe.switch_channel,
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Channel.TButton'}",
|
||||
variable=self.navframe.channel,
|
||||
)
|
||||
self.navframe.channel_button.grid(column=0, row=1, rowspan=1)
|
||||
|
||||
def create_extend_button(self):
|
||||
self.navframe.extend_button = ttk.Checkbutton(
|
||||
self.navframe,
|
||||
textvariable=self.navframe.extend_text,
|
||||
command=self.navframe.extend_frame,
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Extend.TButton'}",
|
||||
variable=self.navframe.extend,
|
||||
)
|
||||
self.navframe.extend_button.grid(column=0, row=2)
|
||||
|
||||
def create_info_button(self):
|
||||
self.navframe.info_button = ttk.Checkbutton(
|
||||
self.navframe,
|
||||
textvariable=self.navframe.info_text,
|
||||
style=f"{'Toggle.TButton' if _configuration.themes_enabled else f'Rec.TButton'}",
|
||||
variable=self.navframe.info,
|
||||
)
|
||||
self.navframe.info_button.grid(column=0, row=3)
|
||||
|
||||
def grid_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
for child in self.navframe.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
if _configuration.themes_enabled:
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 25)
|
||||
else:
|
||||
self.navframe.rowconfigure(1, minsize=_configuration.level_height - 5)
|
||||
|
||||
def teardown(self):
|
||||
pass
|
||||
|
||||
|
||||
class ChannelLabelFrameBuilder(AbstractBuilder):
|
||||
"""Responsible for building channel labelframe widgets"""
|
||||
|
||||
def __init__(self, labelframe, index, id):
|
||||
self.labelframe = labelframe
|
||||
self.index = index
|
||||
self.identifier = id
|
||||
self.using_theme = False
|
||||
|
||||
def setup(self):
|
||||
"""Create class variables for widgets"""
|
||||
self.labelframe.gain = tk.DoubleVar()
|
||||
self.labelframe.level = tk.DoubleVar()
|
||||
self.labelframe.mute = tk.BooleanVar()
|
||||
self.labelframe.conf = tk.BooleanVar()
|
||||
|
||||
"""for gainlayers"""
|
||||
self.labelframe.on = tk.BooleanVar()
|
||||
|
||||
def add_progressbar(self):
|
||||
"""Adds a progress bar widget to a single label frame"""
|
||||
self.labelframe.pb = ttk.Progressbar(
|
||||
self.labelframe,
|
||||
maximum=100,
|
||||
orient="vertical",
|
||||
mode="determinate",
|
||||
variable=self.labelframe.level,
|
||||
)
|
||||
self.labelframe.pb.grid(column=0, row=0)
|
||||
|
||||
def add_scale(self):
|
||||
"""Adds a scale widget to a single label frame"""
|
||||
self.scale = ttk.Scale(
|
||||
self.labelframe,
|
||||
from_=12.0,
|
||||
to=-60.0,
|
||||
orient="vertical",
|
||||
variable=self.labelframe.gain,
|
||||
command=self.labelframe.scale_callback,
|
||||
length=100,
|
||||
)
|
||||
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("<Enter>", self.labelframe.scale_enter)
|
||||
self.scale.bind("<ButtonRelease-1>", self.labelframe.scale_release)
|
||||
self.scale.bind("<Leave>", self.labelframe.scale_leave)
|
||||
self.scale.bind("<MouseWheel>", self.labelframe._on_mousewheel)
|
||||
|
||||
def add_mute_button(self):
|
||||
"""Adds a mute button widget to a single label frame"""
|
||||
self.button_mute = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
text="MUTE",
|
||||
command=partial(self.labelframe.toggle_mute, "mute"),
|
||||
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)
|
||||
|
||||
def add_conf_button(self):
|
||||
self.button_conf = ttk.Checkbutton(
|
||||
self.labelframe,
|
||||
text="CONFIG",
|
||||
command=self.labelframe.open_config,
|
||||
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)
|
||||
|
||||
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'}",
|
||||
variable=self.labelframe.on,
|
||||
)
|
||||
self.button_on.grid(column=0, row=1, columnspan=2)
|
||||
|
||||
def teardown(self):
|
||||
self.labelframe.grid_remove()
|
||||
|
||||
|
||||
class ChannelConfigFrameBuilder(AbstractBuilder):
|
||||
"""Responsible for building channel configframe widgets"""
|
||||
|
||||
def __init__(self, configframe):
|
||||
self.configframe = configframe
|
||||
(
|
||||
self.configframe.phys_in,
|
||||
self.configframe.virt_in,
|
||||
) = self.configframe.parent.kind.ins
|
||||
(
|
||||
self.configframe.phys_out,
|
||||
self.configframe.virt_out,
|
||||
) = self.configframe.parent.kind.outs
|
||||
|
||||
def setup(self):
|
||||
"register configframe as observable"
|
||||
pass
|
||||
|
||||
def teardown(self):
|
||||
"""Unregister as observable, then destroy frame"""
|
||||
self.configframe.parent.subject_pdirty.remove(self.configframe)
|
||||
self.configframe.destroy()
|
||||
|
||||
def grid_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||
for child in self.configframe.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
self.configframe.grid(sticky=(tk.W))
|
||||
[
|
||||
self.configframe.columnconfigure(i, minsize=_configuration.level_width)
|
||||
for i in range(self.configframe.phys_out + self.configframe.virt_out)
|
||||
]
|
||||
|
||||
|
||||
class StripConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
"""Responsible for building channel configframe widgets"""
|
||||
|
||||
def setup(self):
|
||||
if self.configframe.parent.kind.ins == "Basic":
|
||||
self.configframe.slider_params = ("audibility",)
|
||||
self.configframe.slider_vars = (tk.DoubleVar(),)
|
||||
else:
|
||||
self.configframe.slider_params = ("comp", "gate", "limit")
|
||||
self.configframe.slider_vars = [
|
||||
tk.DoubleVar() for _ in self.configframe.slider_params
|
||||
]
|
||||
|
||||
self.configframe.phys_out_params = [
|
||||
f"A{i+1}" for i in range(self.configframe.phys_out)
|
||||
]
|
||||
self.configframe.phys_out_params_vars = [
|
||||
tk.BooleanVar() for _ in self.configframe.phys_out_params
|
||||
]
|
||||
|
||||
self.configframe.virt_out_params = [
|
||||
f"B{i+1}" for i in range(self.configframe.virt_out)
|
||||
]
|
||||
self.configframe.virt_out_params_vars = [
|
||||
tk.BooleanVar() for _ in self.configframe.virt_out_params
|
||||
]
|
||||
|
||||
self.configframe.params = ("mono", "solo")
|
||||
self.configframe.param_vars = list(
|
||||
tk.BooleanVar() for _ in self.configframe.params
|
||||
)
|
||||
|
||||
def create_comp_slider(self):
|
||||
comp_label = ttk.Label(self.configframe, text="Comp")
|
||||
comp_scale = ttk.Scale(
|
||||
self.configframe,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("comp")
|
||||
],
|
||||
command=partial(self.configframe.scale_callback, "comp"),
|
||||
)
|
||||
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_label.grid(column=0, row=0)
|
||||
comp_scale.grid(column=1, row=0)
|
||||
|
||||
def create_gate_slider(self):
|
||||
gate_label = ttk.Label(self.configframe, text="Gate")
|
||||
gate_scale = ttk.Scale(
|
||||
self.configframe,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("gate")
|
||||
],
|
||||
command=partial(self.configframe.scale_callback, "gate"),
|
||||
)
|
||||
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_label.grid(column=2, row=0)
|
||||
gate_scale.grid(column=3, row=0)
|
||||
|
||||
def create_limit_slider(self):
|
||||
limit_label = ttk.Label(self.configframe, text="Limit")
|
||||
limit_scale = ttk.Scale(
|
||||
self.configframe,
|
||||
from_=-40,
|
||||
to=12,
|
||||
orient="horizontal",
|
||||
length=_configuration.level_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("limit")
|
||||
],
|
||||
command=partial(self.configframe.scale_callback, "limit"),
|
||||
)
|
||||
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_label.grid(column=4, row=0)
|
||||
limit_scale.grid(column=5, row=0)
|
||||
|
||||
def create_audibility_slider(self):
|
||||
aud_label = ttk.Label(self.configframe, text="Audibility")
|
||||
aud_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_values.level_width,
|
||||
variable=self.configframe.slider_vars[
|
||||
self.configframe.slider_params.index("audibility")
|
||||
],
|
||||
command=partial(self.configframe.scale_callback, "audibility"),
|
||||
)
|
||||
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_label.grid(column=0, row=0)
|
||||
aud_scale.grid(column=1, row=0)
|
||||
|
||||
def create_a_buttons(self):
|
||||
self.configframe.a_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(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)
|
||||
],
|
||||
)
|
||||
for param in self.configframe.phys_out_params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=i,
|
||||
row=1,
|
||||
)
|
||||
for i, button in enumerate(self.configframe.a_buttons)
|
||||
]
|
||||
|
||||
def create_b_buttons(self):
|
||||
self.configframe.b_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
text=param,
|
||||
command=partial(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)
|
||||
],
|
||||
)
|
||||
for param in self.configframe.virt_out_params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=len(self.configframe.a_buttons) + i,
|
||||
row=1,
|
||||
)
|
||||
for i, button in enumerate(self.configframe.b_buttons)
|
||||
]
|
||||
|
||||
def create_param_buttons(self):
|
||||
param_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
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)
|
||||
],
|
||||
)
|
||||
for param in self.configframe.params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=i,
|
||||
row=2,
|
||||
)
|
||||
for i, button in enumerate(param_buttons)
|
||||
]
|
||||
|
||||
|
||||
class BusConfigFrameBuilder(ChannelConfigFrameBuilder):
|
||||
"""Responsible for building channel configframe widgets"""
|
||||
|
||||
def setup(self):
|
||||
# fmt: off
|
||||
self.configframe.bus_mode_map = {
|
||||
"normal": "Normal",
|
||||
"amix": "Mix Down A",
|
||||
"bmix": "Mix Down B",
|
||||
"repeat": "Stereo Repeat",
|
||||
"composite": "Composite",
|
||||
"tvmix": "Up Mix TV",
|
||||
"upmix21": "Up Mix 2.1",
|
||||
"upmix41": "Up Mix 4.1",
|
||||
"upmix61": "Up Mix 6.1",
|
||||
"centeronly": "Center Only",
|
||||
"lfeonly": "LFE Only",
|
||||
"rearonly": "Rear Only",
|
||||
}
|
||||
self.configframe.bus_modes = list(self.configframe.bus_mode_map.keys())
|
||||
# fmt: on
|
||||
self.configframe.params = ("mono", "eq", "eq_ab")
|
||||
self.configframe.param_vars = [tk.BooleanVar() for _ in self.configframe.params]
|
||||
self.configframe.bus_mode_label_text = tk.StringVar(
|
||||
value=self.configframe.bus_mode_map[self.configframe.current_bus_mode()]
|
||||
)
|
||||
|
||||
def create_bus_mode_button(self):
|
||||
self.configframe.busmode_button = ttk.Button(
|
||||
self.configframe, textvariable=self.configframe.bus_mode_label_text
|
||||
)
|
||||
self.configframe.busmode_button.grid(
|
||||
column=0, row=0, columnspan=2, sticky=(tk.W)
|
||||
)
|
||||
self.configframe.busmode_button.bind(
|
||||
"<Button-1>", self.configframe.rotate_bus_modes_right
|
||||
)
|
||||
self.configframe.busmode_button.bind(
|
||||
"<Button-3>", self.configframe.rotate_bus_modes_left
|
||||
)
|
||||
|
||||
def create_param_buttons(self):
|
||||
param_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self.configframe,
|
||||
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)
|
||||
],
|
||||
)
|
||||
for param in self.configframe.params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=i,
|
||||
row=1,
|
||||
)
|
||||
for i, button in enumerate(param_buttons)
|
||||
]
|
||||
@@ -1,34 +1,31 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from functools import partial
|
||||
from math import log
|
||||
|
||||
from .data import _base_vals
|
||||
from .config import StripConfig, BusConfig
|
||||
from . import builders
|
||||
from .data import _base_values, _configuration
|
||||
|
||||
|
||||
class Channel(ttk.LabelFrame):
|
||||
class ChannelLabelFrame(ttk.LabelFrame):
|
||||
"""Base class for a single channel"""
|
||||
|
||||
def __init__(self, parent, index, id):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.parent = parent
|
||||
self.index = index
|
||||
self.id = id
|
||||
self.s = self._parent._parent.styletable
|
||||
self.config_frame = None
|
||||
|
||||
self.gain = tk.DoubleVar()
|
||||
self.level = tk.DoubleVar()
|
||||
self.mute = tk.BooleanVar()
|
||||
self.conf = tk.BooleanVar()
|
||||
self.styletable = self.parent.parent.styletable
|
||||
|
||||
self.builder = builders.ChannelLabelFrameBuilder(self, index, id)
|
||||
self.builder.setup()
|
||||
self.builder.add_progressbar()
|
||||
self.builder.add_scale()
|
||||
self.builder.add_mute_button()
|
||||
self.builder.add_conf_button()
|
||||
self.sync()
|
||||
self._make_widgets()
|
||||
self.grid_configure()
|
||||
|
||||
self.col_row_configure()
|
||||
self.watch_pdirty()
|
||||
self.watch_levels()
|
||||
self.configbuilder = builders.MainFrameBuilder(self.parent.parent)
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
@@ -36,8 +33,9 @@ class Channel(ttk.LabelFrame):
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
return self._parent.target
|
||||
"""returns the current interface"""
|
||||
|
||||
return self.parent.target
|
||||
|
||||
def getter(self, param):
|
||||
if param in dir(self.target):
|
||||
@@ -47,10 +45,16 @@ class Channel(ttk.LabelFrame):
|
||||
if param in dir(self.target):
|
||||
setattr(self.target, param, value)
|
||||
|
||||
def scale_callback(self, *args):
|
||||
"""callback function for scale widget"""
|
||||
|
||||
self.setter("gain", self.gain.get())
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def toggle_mute(self, *args):
|
||||
self.target.mute = self.mute.get()
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{self.identifier}Mute{self.index}.TButton",
|
||||
background=f'{"red" if self.mute.get() else "white"}',
|
||||
)
|
||||
@@ -58,27 +62,27 @@ class Channel(ttk.LabelFrame):
|
||||
def reset_gain(self, *args):
|
||||
self.setter("gain", 0)
|
||||
self.gain.set(0)
|
||||
self._parent._parent.nav_frame.info_text.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))
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self._parent._parent.nav_frame.info_text.set("")
|
||||
self.parent.parent.nav_frame.info_text.set("")
|
||||
|
||||
def scale_press(self, *args):
|
||||
_base_vals.in_scale_button_1 = True
|
||||
_base_values.in_scale_button_1 = True
|
||||
|
||||
def scale_release(self, *args):
|
||||
_base_vals.in_scale_button_1 = False
|
||||
_base_values.in_scale_button_1 = False
|
||||
|
||||
def _on_mousewheel(self, event):
|
||||
self.gain.set(
|
||||
self.gain.get()
|
||||
+ (
|
||||
_base_vals.mwscroll_step
|
||||
_configuration.mwscroll_step
|
||||
if event.delta > 0
|
||||
else -_base_vals.mwscroll_step
|
||||
else -_configuration.mwscroll_step
|
||||
)
|
||||
)
|
||||
if self.gain.get() > 12:
|
||||
@@ -86,94 +90,46 @@ class Channel(ttk.LabelFrame):
|
||||
elif self.gain.get() < -60:
|
||||
self.gain.set(-60)
|
||||
self.setter("gain", self.gain.get())
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_callback(self, *args):
|
||||
"""callback function for scale widget"""
|
||||
self.setter("gain", self.gain.get())
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def convert_level(self, val):
|
||||
if _base_vals.vban_connected:
|
||||
return round(-val * 0.01, 1)
|
||||
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
||||
|
||||
def _make_widgets(self):
|
||||
"""Creates a progressbar, scale, mute button and config button for a single channel"""
|
||||
# Progress bar
|
||||
self.pb = ttk.Progressbar(
|
||||
self,
|
||||
maximum=100,
|
||||
orient="vertical",
|
||||
mode="determinate",
|
||||
variable=self.level,
|
||||
)
|
||||
self.pb.grid(column=0, row=0)
|
||||
|
||||
# Scale
|
||||
self.scale = ttk.Scale(
|
||||
self,
|
||||
from_=12.0,
|
||||
to=-60.0,
|
||||
orient="vertical",
|
||||
variable=self.gain,
|
||||
command=self.scale_callback,
|
||||
length=self._parent.height,
|
||||
)
|
||||
self.scale.grid(column=1, row=0)
|
||||
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
||||
self.scale.bind("<Button-1>", self.scale_press)
|
||||
self.scale.bind("<Enter>", self.scale_enter)
|
||||
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
||||
self.scale.bind("<Leave>", self.scale_leave)
|
||||
self.scale.bind("<MouseWheel>", self._on_mousewheel)
|
||||
|
||||
# Mute button
|
||||
self.button_mute = ttk.Checkbutton(
|
||||
self,
|
||||
text="MUTE",
|
||||
command=partial(self.toggle_mute, "mute"),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{self.identifier}Mute{self.index}.TButton'}",
|
||||
variable=self.mute,
|
||||
)
|
||||
self.button_mute.grid(column=0, row=1, columnspan=2)
|
||||
|
||||
self.button_conf = ttk.Checkbutton(
|
||||
self,
|
||||
text="CONFIG",
|
||||
command=self.open_config,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{self.identifier}Conf{self.index}.TButton'}",
|
||||
variable=self.conf,
|
||||
)
|
||||
self.button_conf.grid(column=0, row=2, columnspan=2)
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
"""keeps params synced but ensures sliders are responsive"""
|
||||
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
||||
self.sync()
|
||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
self.configbuilder.create_configframe(self.identifier, self.index, self.id)
|
||||
else:
|
||||
self.parent.parent.config_frame.teardown()
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton",
|
||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||
)
|
||||
|
||||
def sync(self):
|
||||
"""sync params with voicemeeter"""
|
||||
"""sync parameters"""
|
||||
retval = self.getter("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.mute.set(self.getter("mute"))
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{self.identifier}Mute{self.index}.TButton",
|
||||
background=f'{"red" if self.mute.get() else "white"}',
|
||||
)
|
||||
self.s.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton", background="white"
|
||||
)
|
||||
|
||||
def col_row_configure(self):
|
||||
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 grid_configure(self):
|
||||
self.grid(sticky=(tk.N, tk.S))
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||
@@ -187,8 +143,8 @@ class Channel(ttk.LabelFrame):
|
||||
]
|
||||
|
||||
|
||||
class Strip(Channel):
|
||||
"""Concrete class representing a single"""
|
||||
class Strip(ChannelLabelFrame):
|
||||
"""Concrete class representing a single strip"""
|
||||
|
||||
def __init__(self, parent, index, id):
|
||||
super().__init__(parent, index, id)
|
||||
@@ -199,251 +155,153 @@ class Strip(Channel):
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
"""returns the strip class for this labelframe in the current interface"""
|
||||
|
||||
_target = super(Strip, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
self.config_frame = StripConfig(
|
||||
self._parent._parent,
|
||||
self.index,
|
||||
self.identifier,
|
||||
)
|
||||
self.config_frame.grid(column=0, row=1, columnspan=4)
|
||||
self._parent._parent.channel_frame.reset_config_buttons(self)
|
||||
if self._parent._parent.bus_frame is not None:
|
||||
self._parent._parent.bus_frame.reset_config_buttons(self)
|
||||
else:
|
||||
self.config_frame.destroy()
|
||||
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton",
|
||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||
)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
|
||||
def watch_levels_step(self):
|
||||
if not _base_vals.dragging:
|
||||
if (
|
||||
self._parent._parent.ldirty
|
||||
and any(
|
||||
self._parent._parent.comp_strip[
|
||||
self.level_offset : self.level_offset + 1
|
||||
]
|
||||
)
|
||||
and _base_vals.strip_level_array_size
|
||||
== len(self._parent._parent.comp_strip)
|
||||
):
|
||||
vals = (
|
||||
self.convert_level(
|
||||
self._parent._parent.strip_levels[self.level_offset]
|
||||
),
|
||||
self.convert_level(
|
||||
self._parent._parent.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_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
||||
self.watch_levels_step,
|
||||
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.mute.get() else 100 + (max(vals) - 18) + self.gain.get())
|
||||
)
|
||||
|
||||
|
||||
class Bus(Channel):
|
||||
class Bus(ChannelLabelFrame):
|
||||
"""Concrete bus class representing a single bus"""
|
||||
|
||||
def __init__(self, parent, index, id):
|
||||
super().__init__(parent, index, id)
|
||||
self.level_offset = self.index * 8
|
||||
self.level_offset = index * 8
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
"""returns the bus class for this labelframe in the current interface"""
|
||||
|
||||
_target = super(Bus, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def open_config(self):
|
||||
if self.conf.get():
|
||||
self.config_frame = BusConfig(
|
||||
self._parent._parent,
|
||||
self.index,
|
||||
self.identifier,
|
||||
)
|
||||
if _base_vals.extends_horizontal:
|
||||
self.config_frame.grid(column=0, row=1, columnspan=4)
|
||||
else:
|
||||
self.config_frame.grid(column=0, row=3, columnspan=4)
|
||||
self._parent._parent.channel_frame.reset_config_buttons(self)
|
||||
self._parent._parent.bus_frame.update_bus_modes()
|
||||
self._parent._parent.bus_frame.reset_config_buttons(self)
|
||||
else:
|
||||
self._parent._parent.bus_modes_cache[
|
||||
"vban" if _base_vals.vban_connected else "vmr"
|
||||
][self.index].set(self.config_frame.bus_mode)
|
||||
self.config_frame.destroy()
|
||||
def update(self):
|
||||
"""update levels"""
|
||||
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{self.identifier}Conf{self.index}.TButton",
|
||||
background=f'{"yellow" if self.conf.get() else "white"}',
|
||||
)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
|
||||
def watch_levels_step(self):
|
||||
if not _base_vals.dragging:
|
||||
if (
|
||||
self._parent._parent.ldirty
|
||||
and any(
|
||||
self._parent._parent.comp_bus[
|
||||
self.level_offset : self.level_offset + 1
|
||||
]
|
||||
)
|
||||
and _base_vals.bus_level_array_size
|
||||
== len(self._parent._parent.comp_bus)
|
||||
):
|
||||
vals = (
|
||||
self.convert_level(
|
||||
self._parent._parent.bus_levels[self.level_offset]
|
||||
),
|
||||
self.convert_level(
|
||||
self._parent._parent.bus_levels[self.level_offset + 1]
|
||||
),
|
||||
)
|
||||
self.level.set((0 if self.mute.get() else 100 + (max(vals) - 18)))
|
||||
self.after(
|
||||
_base_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
||||
self.watch_levels_step,
|
||||
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)))
|
||||
|
||||
|
||||
class ChannelFrame(ttk.Frame):
|
||||
@classmethod
|
||||
def make_strips(cls, parent):
|
||||
return cls(parent, is_strip=True)
|
||||
|
||||
@classmethod
|
||||
def make_buses(cls, parent):
|
||||
return cls(parent, is_strip=False)
|
||||
|
||||
def __init__(self, parent, is_strip: bool = True):
|
||||
def init(self, parent, id):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self._is_strip = is_strip
|
||||
self.parent = parent
|
||||
self.id = id
|
||||
self.phys_in, self.virt_in = parent.kind.ins
|
||||
self.phys_out, self.virt_out = parent.kind.outs
|
||||
_base_vals.strip_level_array_size = 2 * self.phys_in + 8 * self.virt_in
|
||||
_base_vals.bus_level_array_size = 8 * (self.phys_out + self.virt_out)
|
||||
|
||||
defaults = {
|
||||
"width": 80,
|
||||
"height": 150,
|
||||
}
|
||||
self.configuration = defaults | self.configuration
|
||||
self.width = self.configuration["width"]
|
||||
self.height = self.configuration["height"]
|
||||
|
||||
# create labelframes
|
||||
if is_strip:
|
||||
self.strips = tuple(
|
||||
Strip(self, i, self.identifier)
|
||||
for i in range(self.phys_in + self.virt_in)
|
||||
)
|
||||
else:
|
||||
self.buses = tuple(
|
||||
Bus(self, i, self.identifier)
|
||||
for i in range(self.phys_out + self.virt_out)
|
||||
)
|
||||
|
||||
# position label frames. destroy any without label text
|
||||
self.labelframes = self.strips if is_strip else self.buses
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
for i, labelframe in enumerate(self.labelframes):
|
||||
labelframe.grid(row=0, column=i)
|
||||
if not labelframe.cget("text"):
|
||||
self.columnconfigure(i, minsize=0)
|
||||
labelframe.grid_remove()
|
||||
|
||||
self.watch_pdirty()
|
||||
# registers channelframe as pdirty observer
|
||||
self.parent.subject_pdirty.add(self)
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._parent.target
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
return self._parent.configuration["channel"]
|
||||
|
||||
@configuration.setter
|
||||
def configuration(self, val):
|
||||
self._parent.configuration["channel"] = val
|
||||
return self.parent.target
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return "strip" if self._is_strip else "bus"
|
||||
return self.id
|
||||
|
||||
def update_bus_modes(self):
|
||||
[
|
||||
self._parent.bus_modes_cache[
|
||||
"vban" if _base_vals.vban_connected else "vmr"
|
||||
][i].set(labelframe.config_frame.bus_mode)
|
||||
for i, labelframe in enumerate(self.labelframes)
|
||||
if labelframe is not None and labelframe.config_frame
|
||||
]
|
||||
@property
|
||||
def labelframes(self):
|
||||
"""returns a tuple of current channel labelframe addresses"""
|
||||
|
||||
def reset_config_buttons(self, current):
|
||||
if not _base_vals.using_theme:
|
||||
[
|
||||
labelframe.s.configure(
|
||||
f"{labelframe.identifier}Conf{labelframe.index}.TButton",
|
||||
background="white",
|
||||
)
|
||||
for labelframe in self.labelframes
|
||||
if labelframe is not None
|
||||
]
|
||||
[
|
||||
labelframe.conf.set(False)
|
||||
for labelframe in self.labelframes
|
||||
if labelframe is not None and labelframe != current
|
||||
]
|
||||
[
|
||||
labelframe.config_frame.destroy()
|
||||
for labelframe in self.labelframes
|
||||
if labelframe is not None
|
||||
and labelframe.config_frame
|
||||
and labelframe != current
|
||||
]
|
||||
return tuple(
|
||||
frame
|
||||
for frame in self.winfo_children()
|
||||
if isinstance(frame, ttk.LabelFrame)
|
||||
)
|
||||
|
||||
def col_row_configure(self):
|
||||
def grid_configure(self):
|
||||
[
|
||||
self.columnconfigure(i, minsize=self.width)
|
||||
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=130) for i, _ in enumerate(self.labelframes)]
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
def update(self):
|
||||
for labelframe in self.labelframes:
|
||||
labelframe.sync()
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
if self._parent.pdirty:
|
||||
self.watch_labels()
|
||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
||||
def teardown(self):
|
||||
# deregisters channelframe as pdirty observer
|
||||
|
||||
def watch_labels(self):
|
||||
for i, labelframe in enumerate(self.labelframes):
|
||||
if not labelframe.getter("label"):
|
||||
self.parent.subject_pdirty.remove(self)
|
||||
self.destroy()
|
||||
|
||||
|
||||
def _make_channelframe(parent, id):
|
||||
"""
|
||||
Creates a Channel Frame class of type strip or bus
|
||||
"""
|
||||
|
||||
phys_in, virt_in = parent.kind.ins
|
||||
phys_out, virt_out = parent.kind.outs
|
||||
|
||||
def init_labels(self, id):
|
||||
"""
|
||||
Grids each labelframe, grid_removes any without a label
|
||||
"""
|
||||
|
||||
for i, labelframe in enumerate(
|
||||
getattr(self, "strips" if id == "strip" else "buses")
|
||||
):
|
||||
labelframe.grid(row=0, column=i)
|
||||
if not labelframe.target.label:
|
||||
self.columnconfigure(i, minsize=0)
|
||||
labelframe.grid_remove()
|
||||
|
||||
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.grid(row=0, column=0, sticky=(tk.W))
|
||||
self.grid_configure()
|
||||
init_labels(self, id)
|
||||
|
||||
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))
|
||||
if _configuration.extended:
|
||||
if _configuration.extends_horizontal:
|
||||
self.grid(row=0, column=2)
|
||||
else:
|
||||
self.columnconfigure(i, minsize=self.width)
|
||||
labelframe.grid()
|
||||
self.grid(row=2, column=0, sticky=(tk.W))
|
||||
else:
|
||||
self.grid(row=0, column=0)
|
||||
self.grid_configure()
|
||||
init_labels(self, id)
|
||||
|
||||
if id == "strip":
|
||||
CHANNELFRAME_cls = type(
|
||||
f"ChannelFrame{id.capitalize}",
|
||||
(ChannelFrame,),
|
||||
{
|
||||
"__init__": init_strip,
|
||||
},
|
||||
)
|
||||
else:
|
||||
CHANNELFRAME_cls = type(
|
||||
f"ChannelFrame{id.capitalize}",
|
||||
(ChannelFrame,),
|
||||
{
|
||||
"__init__": init_bus,
|
||||
},
|
||||
)
|
||||
return CHANNELFRAME_cls(parent)
|
||||
|
||||
@@ -2,21 +2,21 @@ import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from functools import partial
|
||||
|
||||
from .data import _base_vals
|
||||
from . import builders
|
||||
from .data import _configuration, _base_values
|
||||
|
||||
|
||||
class Config(ttk.Frame):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.parent = parent
|
||||
self.index = index
|
||||
self.id = _id
|
||||
self.s = parent.styletable
|
||||
|
||||
self.styletable = parent.styletable
|
||||
self.phys_in, self.virt_in = parent.kind.ins
|
||||
self.phys_out, self.virt_out = parent.kind.outs
|
||||
|
||||
self.watch_pdirty()
|
||||
self.parent.subject_pdirty.add(self)
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
@@ -25,277 +25,99 @@ class Config(ttk.Frame):
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._parent.target
|
||||
|
||||
return self.parent.target
|
||||
|
||||
def getter(self, param):
|
||||
if param in dir(self.target):
|
||||
return getattr(self.target, param)
|
||||
return getattr(self.target, param)
|
||||
|
||||
def setter(self, param, value):
|
||||
if param in dir(self.target):
|
||||
setattr(self.target, param, value)
|
||||
setattr(self.target, param, value)
|
||||
|
||||
def scale_enter(self, *args):
|
||||
_base_vals.in_scale_button_1 = True
|
||||
_base_values.in_scale_button_1 = True
|
||||
|
||||
def scale_leave(self, *args):
|
||||
_base_vals.in_scale_button_1 = False
|
||||
self._parent.nav_frame.info_text.set("")
|
||||
_base_values.in_scale_button_1 = False
|
||||
self.parent.nav_frame.info_text.set("")
|
||||
|
||||
def scale_callback(self, param, *args):
|
||||
"""callback function for scale widget"""
|
||||
|
||||
val = self.slider_vars[self.slider_params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
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):
|
||||
self.setter(param, val)
|
||||
self.slider_vars[self.slider_params.index(param)].set(val)
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
self.grid(sticky=(tk.W))
|
||||
def toggle_p(self, param):
|
||||
val = self.param_vars[self.params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
"""keeps params synced but ensures sliders are responsive"""
|
||||
if self._parent.pdirty and not _base_vals.in_scale_button_1:
|
||||
self.sync()
|
||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
||||
def update(self):
|
||||
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.builder = builders.StripConfigFrameBuilder(self)
|
||||
self.builder.setup()
|
||||
self.make_row_0()
|
||||
self.make_row_1()
|
||||
self.make_row_2()
|
||||
self.builder.grid_configure()
|
||||
|
||||
# create parameter variables
|
||||
if self._parent.kind.name == "Basic":
|
||||
self.slider_params = ("audibility",)
|
||||
self.slider_vars = (tk.DoubleVar(),)
|
||||
else:
|
||||
self.slider_params = ("comp", "gate", "limit")
|
||||
self.slider_vars = [
|
||||
tk.DoubleVar() for i, _ in enumerate(self.slider_params)
|
||||
]
|
||||
|
||||
self.phys_out_params = [f"A{i+1}" for i in range(self.phys_out)]
|
||||
self.phys_out_params_vars = [
|
||||
tk.BooleanVar() for i, _ in enumerate(self.phys_out_params)
|
||||
]
|
||||
|
||||
self.virt_out_params = [f"B{i+1}" for i in range(self.virt_out)]
|
||||
self.virt_out_params_vars = [
|
||||
tk.BooleanVar() for i, _ in enumerate(self.virt_out_params)
|
||||
]
|
||||
|
||||
self.params = ("mono", "solo")
|
||||
self.param_vars = list(tk.BooleanVar() for i, _ in enumerate(self.params))
|
||||
|
||||
self.make_row0()
|
||||
self.make_row1()
|
||||
self.make_row2()
|
||||
|
||||
# sync all parameters
|
||||
self.sync()
|
||||
self.sync_sliders()
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
"""returns the strip class for this configframe in the current interface"""
|
||||
|
||||
_target = super(StripConfig, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
def make_row0(self):
|
||||
# Create sliders
|
||||
def make_row_0(self):
|
||||
if self.index < self.phys_in:
|
||||
if self._parent.kind.name == "Basic":
|
||||
# audibility
|
||||
aud_label = ttk.Label(self, text="Audibility")
|
||||
aud_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("audibility")],
|
||||
command=partial(self.scale_callback, "audibility"),
|
||||
)
|
||||
aud_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "audibility", 0)
|
||||
)
|
||||
aud_scale.bind("<Button-1>", self.scale_enter)
|
||||
aud_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
|
||||
aud_label.grid(column=0, row=0)
|
||||
aud_scale.grid(column=1, row=0)
|
||||
if self.parent.kind.name == "Basic":
|
||||
self.builder.create_audibility_slider()
|
||||
else:
|
||||
# comp
|
||||
comp_label = ttk.Label(self, text="Comp")
|
||||
comp_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("comp")],
|
||||
command=partial(self.scale_callback, "comp"),
|
||||
)
|
||||
comp_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "comp", 0)
|
||||
)
|
||||
comp_scale.bind("<Button-1>", self.scale_enter)
|
||||
comp_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
self.builder.create_comp_slider()
|
||||
self.builder.create_gate_slider()
|
||||
self.builder.create_limit_slider()
|
||||
|
||||
# gate
|
||||
gate_label = ttk.Label(self, text="Gate")
|
||||
gate_scale = ttk.Scale(
|
||||
self,
|
||||
from_=0.0,
|
||||
to=10.0,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("gate")],
|
||||
command=partial(self.scale_callback, "gate"),
|
||||
)
|
||||
gate_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "gate", 0)
|
||||
)
|
||||
gate_scale.bind("<Button-1>", self.scale_enter)
|
||||
gate_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
def make_row_1(self):
|
||||
self.builder.create_a_buttons()
|
||||
self.builder.create_b_buttons()
|
||||
|
||||
# limit
|
||||
limit_label = ttk.Label(self, text="Limit")
|
||||
limit_scale = ttk.Scale(
|
||||
self,
|
||||
from_=-40,
|
||||
to=12,
|
||||
orient="horizontal",
|
||||
length=_base_vals.level_width,
|
||||
variable=self.slider_vars[self.slider_params.index("limit")],
|
||||
command=partial(self.scale_callback, "limit"),
|
||||
)
|
||||
limit_scale.bind(
|
||||
"<Double-Button-1>", partial(self.reset_scale, "limit", 12)
|
||||
)
|
||||
limit_scale.bind("<Button-1>", self.scale_enter)
|
||||
limit_scale.bind("<ButtonRelease-1>", self.scale_leave)
|
||||
|
||||
# Position sliders
|
||||
comp_label.grid(column=0, row=0)
|
||||
comp_scale.grid(column=1, row=0)
|
||||
gate_label.grid(column=2, row=0)
|
||||
gate_scale.grid(column=3, row=0)
|
||||
limit_label.grid(column=4, row=0)
|
||||
limit_scale.grid(column=5, row=0)
|
||||
|
||||
def make_row1(self):
|
||||
# create buttons
|
||||
self.a_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_a, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.phys_out_params_vars[self.phys_out_params.index(param)],
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
]
|
||||
self.b_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_b, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.virt_out_params_vars[self.virt_out_params.index(param)],
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
]
|
||||
|
||||
# set button positions
|
||||
[
|
||||
button.grid(
|
||||
column=self.a_buttons.index(button),
|
||||
row=1,
|
||||
)
|
||||
for button in self.a_buttons
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=len(self.a_buttons) + self.b_buttons.index(button),
|
||||
row=1,
|
||||
)
|
||||
for button in self.b_buttons
|
||||
]
|
||||
def make_row_2(self):
|
||||
self.builder.create_param_buttons()
|
||||
|
||||
def toggle_a(self, param):
|
||||
val = self.phys_out_params_vars[self.phys_out_params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def toggle_b(self, param):
|
||||
val = self.virt_out_params_vars[self.virt_out_params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def make_row2(self):
|
||||
if self._parent.kind.name in ("Banana", "Potato"):
|
||||
if self.index == self.phys_in:
|
||||
self.params = list(map(lambda x: x.replace("mono", "mc"), self.params))
|
||||
if self._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.index == self.phys_in + self.virt_in - 1:
|
||||
self.params = list(
|
||||
map(lambda x: x.replace("mono", "mc"), self.params)
|
||||
)
|
||||
|
||||
param_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_p, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.param_vars[self.params.index(param)],
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=param_buttons.index(button),
|
||||
row=2,
|
||||
)
|
||||
for button in param_buttons
|
||||
]
|
||||
|
||||
def toggle_p(self, param):
|
||||
val = self.param_vars[self.params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
def teardown(self):
|
||||
self.builder.teardown()
|
||||
|
||||
def sync(self):
|
||||
[
|
||||
@@ -314,154 +136,110 @@ class StripConfig(Config):
|
||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||
for param in self.params
|
||||
]
|
||||
if not _base_vals.using_theme:
|
||||
|
||||
if not _configuration.themes_enabled:
|
||||
[
|
||||
self.s.configure(
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.phys_out_params_vars[self.phys_out_params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.phys_out_params
|
||||
]
|
||||
[
|
||||
self.s.configure(
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.virt_out_params_vars[self.virt_out_params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.virt_out_params
|
||||
]
|
||||
[
|
||||
self.s.configure(
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
|
||||
def sync_sliders(self):
|
||||
[
|
||||
self.slider_vars[self.slider_params.index(param)].set(self.getter(param))
|
||||
for param in self.slider_params
|
||||
]
|
||||
|
||||
def col_row_configure(self):
|
||||
super(StripConfig, self).col_row_configure()
|
||||
[
|
||||
self.columnconfigure(i, minsize=80)
|
||||
for i in range(self.phys_out + self.virt_out)
|
||||
]
|
||||
|
||||
|
||||
class BusConfig(Config):
|
||||
def __init__(self, parent, index, _id):
|
||||
super().__init__(parent, index, _id)
|
||||
# fmt: off
|
||||
# create parameter variables
|
||||
self.bus_modes = (
|
||||
"normal", "Amix", "Bmix", "Repeat", "Composite", "TVMix", "UpMix21",
|
||||
"UpMix41", "UpMix61", "CenterOnly", "LFEOnly", "RearOnly",
|
||||
)
|
||||
# fmt: on
|
||||
self.params = ("mono", "eq", "eq_ab")
|
||||
self.param_vars = [tk.BooleanVar() for i, _ in enumerate(self.params)]
|
||||
if _configuration.extends_horizontal:
|
||||
self.grid(column=0, row=1, columnspan=4)
|
||||
else:
|
||||
self.grid(column=0, row=3, columnspan=4)
|
||||
self.builder = builders.BusConfigFrameBuilder(self)
|
||||
self.builder.setup()
|
||||
self.make_row_0()
|
||||
self.make_row_1()
|
||||
self.builder.grid_configure()
|
||||
|
||||
# sync all parameters
|
||||
self.sync()
|
||||
|
||||
self.make_row0()
|
||||
self.make_row1()
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
"""returns the bus class for this configframe in the current interface"""
|
||||
|
||||
_target = super(BusConfig, self).target
|
||||
return getattr(_target, self.identifier)[self.index]
|
||||
|
||||
@property
|
||||
def bus_mode(self):
|
||||
return self._parent.bus_modes_cache[
|
||||
"vban" if _base_vals.vban_connected else "vmr"
|
||||
][self.index].get()
|
||||
def make_row_0(self):
|
||||
self.builder.create_bus_mode_button()
|
||||
|
||||
@bus_mode.setter
|
||||
def bus_mode(self, val):
|
||||
self._parent.bus_modes_cache["vban" if _base_vals.vban_connected else "vmr"][
|
||||
self.index
|
||||
].set(val)
|
||||
def make_row_1(self):
|
||||
self.builder.create_param_buttons()
|
||||
|
||||
def make_row0(self):
|
||||
self.bus_mode_label_text = tk.StringVar(value=f"Bus Mode: {self.bus_mode}")
|
||||
self.busmode_button = ttk.Button(self, textvariable=self.bus_mode_label_text)
|
||||
self.busmode_button.grid(column=0, row=0, columnspan=2, sticky=(tk.W))
|
||||
self.busmode_button.bind("<Button-1>", self.rotate_bus_modes_right)
|
||||
self.busmode_button.bind("<Button-3>", self.rotate_bus_modes_left)
|
||||
def current_bus_mode(self):
|
||||
for mode in self.bus_modes:
|
||||
if getattr(self.target.mode, mode):
|
||||
return mode
|
||||
|
||||
def rotate_bus_modes_right(self, *args):
|
||||
current_index = self.bus_modes.index(self.bus_mode)
|
||||
if current_index + 1 < len(self.bus_modes):
|
||||
self.bus_mode = self.bus_modes[current_index + 1]
|
||||
current_mode = self.current_bus_mode()
|
||||
next = self.bus_modes.index(current_mode) + 1
|
||||
if next < len(self.bus_modes):
|
||||
setattr(
|
||||
self.target.mode,
|
||||
self.bus_modes[next],
|
||||
True,
|
||||
)
|
||||
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[next]])
|
||||
else:
|
||||
self.bus_mode = self.bus_modes[0]
|
||||
setattr(self.target.mode, self.bus_mode.lower(), True)
|
||||
self.bus_mode_label_text.set(f"Bus Mode: {self.bus_mode}")
|
||||
self.target.mode.normal = True
|
||||
self.bus_mode_label_text.set("Normal")
|
||||
|
||||
def rotate_bus_modes_left(self, *args):
|
||||
current_index = self.bus_modes.index(self.bus_mode)
|
||||
if current_index == 0:
|
||||
self.bus_mode = self.bus_modes[-1]
|
||||
current_mode = self.current_bus_mode()
|
||||
prev = self.bus_modes.index(current_mode) - 1
|
||||
if prev < 0:
|
||||
self.target.mode.rearonly = True
|
||||
self.bus_mode_label_text.set("Rear Only")
|
||||
else:
|
||||
self.bus_mode = self.bus_modes[current_index - 1]
|
||||
setattr(self.target.mode, self.bus_mode.lower(), True)
|
||||
self.bus_mode_label_text.set(f"Bus Mode: {self.bus_mode}")
|
||||
|
||||
def make_row1(self):
|
||||
param_buttons = [
|
||||
ttk.Checkbutton(
|
||||
self,
|
||||
text=param,
|
||||
command=partial(self.toggle_p, param),
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'{param}.TButton'}",
|
||||
variable=self.param_vars[self.params.index(param)],
|
||||
setattr(
|
||||
self.target.mode,
|
||||
self.bus_modes[prev],
|
||||
True,
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
[
|
||||
button.grid(
|
||||
column=param_buttons.index(button),
|
||||
row=1,
|
||||
)
|
||||
for button in param_buttons
|
||||
]
|
||||
self.bus_mode_label_text.set(self.bus_mode_map[self.bus_modes[prev]])
|
||||
|
||||
def toggle_p(self, param):
|
||||
val = self.param_vars[self.params.index(param)].get()
|
||||
self.setter(param, val)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
f"{param}.TButton", background=f'{"green" if val else "white"}'
|
||||
)
|
||||
|
||||
def col_row_configure(self):
|
||||
super(BusConfig, self).col_row_configure()
|
||||
[
|
||||
self.columnconfigure(i, minsize=80)
|
||||
for i in range(self.phys_out + self.virt_out)
|
||||
]
|
||||
def teardown(self):
|
||||
self.builder.teardown()
|
||||
|
||||
def sync(self):
|
||||
for i, mode in enumerate(self.bus_modes):
|
||||
if getattr(self.target.mode, mode.lower()):
|
||||
self.bus_mode = self.bus_modes[i]
|
||||
[
|
||||
self.param_vars[self.params.index(param)].set(self.getter(param))
|
||||
for param in self.params
|
||||
]
|
||||
if not _base_vals.using_theme:
|
||||
self.bus_mode_label_text.set(self.bus_mode_map[self.current_bus_mode()])
|
||||
if not _configuration.themes_enabled:
|
||||
[
|
||||
self.s.configure(
|
||||
self.styletable.configure(
|
||||
f"{param}.TButton",
|
||||
background=f'{"green" if self.param_vars[self.params.index(param)].get() else "white"}',
|
||||
)
|
||||
for param in self.params
|
||||
]
|
||||
|
||||
|
||||
class Iterator:
|
||||
pass
|
||||
|
||||
@@ -16,5 +16,39 @@ for path in config_path:
|
||||
print(f"Invalid TOML profile: configs/{filename.stem}")
|
||||
|
||||
for name, cfg in configs.items():
|
||||
print(f"Loaded profile configs/{name}")
|
||||
print(f"Loaded configuration configs/{name}")
|
||||
configuration[name] = cfg
|
||||
|
||||
_defaults = {
|
||||
"profiles": {
|
||||
"profile": None,
|
||||
},
|
||||
"theme": {
|
||||
"enabled": True,
|
||||
"mode": "light",
|
||||
},
|
||||
"extends": {
|
||||
"extended": True,
|
||||
"extends_horizontal": True,
|
||||
},
|
||||
"channel": {
|
||||
"width": 80,
|
||||
"height": 130,
|
||||
},
|
||||
"mwscroll_step": {
|
||||
"size": 3,
|
||||
},
|
||||
"submixes": {
|
||||
"default": 0,
|
||||
},
|
||||
}
|
||||
|
||||
if "app" in configuration:
|
||||
configuration["app"] = _defaults | configuration["app"]
|
||||
else:
|
||||
configuration["app"] = _defaults
|
||||
|
||||
|
||||
def get_configuration(key):
|
||||
if key in configuration:
|
||||
return configuration[key]
|
||||
|
||||
@@ -1,24 +1,53 @@
|
||||
from dataclasses import dataclass
|
||||
from voicemeeter import kinds
|
||||
|
||||
from .configurations import get_configuration
|
||||
|
||||
configuration = get_configuration("app")
|
||||
|
||||
|
||||
class SingletonMeta(type):
|
||||
_instances = {}
|
||||
|
||||
def __call__(cls, *args, **kwargs):
|
||||
if cls not in cls._instances:
|
||||
instance = super().__call__(*args, **kwargs)
|
||||
cls._instances[cls] = instance
|
||||
return cls._instances[cls]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseValues:
|
||||
class Configurations(metaclass=SingletonMeta):
|
||||
# width of a single labelframe
|
||||
level_width: int = 75
|
||||
# height of a single labelframe
|
||||
level_height: int = 100
|
||||
level_width: int = 80
|
||||
|
||||
# is the gui extended
|
||||
extended: bool = configuration["extends"]["extended"]
|
||||
# direction the gui extends
|
||||
extends_horizontal: bool = configuration["extends"]["extends_horizontal"]
|
||||
# are themes enabled
|
||||
themes_enabled: bool = configuration["theme"]["enabled"]
|
||||
# light or dark
|
||||
theme_mode: str = configuration["theme"]["mode"]
|
||||
# size of mousewheel scroll step
|
||||
mwscroll_step: int = configuration["mwscroll_step"]["size"]
|
||||
|
||||
@property
|
||||
def profile(self):
|
||||
if "profiles" in configuration:
|
||||
return configuration["profiles"]["profile"]
|
||||
|
||||
|
||||
@dataclass
|
||||
class BaseValues(metaclass=SingletonMeta):
|
||||
# are we dragging a scale with mouse 1
|
||||
in_scale_button_1: bool = False
|
||||
# are we dragging main window with mouse 1
|
||||
dragging: bool = False
|
||||
# direction the gui extends
|
||||
extends_horizontal: bool = True
|
||||
# a vban connection established
|
||||
vban_connected: bool = False
|
||||
# are themes enabled
|
||||
themes_enabled: bool = True
|
||||
# are we using a theme
|
||||
using_theme: bool = False
|
||||
# bus assigned as current submix
|
||||
submixes: int = 0
|
||||
# pdirty delay
|
||||
@@ -29,12 +58,10 @@ class BaseValues:
|
||||
strip_level_array_size: int = None
|
||||
# size of bus level array for a kind
|
||||
bus_level_array_size: int = None
|
||||
# size of mousewheel scroll step
|
||||
mwscroll_step: int = 3
|
||||
|
||||
|
||||
_base_vals = BaseValues()
|
||||
|
||||
_base_values = BaseValues()
|
||||
_configuration = Configurations()
|
||||
|
||||
_kinds = {kind.id: kind for kind in kinds.all}
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from math import log
|
||||
|
||||
from .data import _base_vals
|
||||
from .data import _base_values, _configuration
|
||||
from . import builders
|
||||
|
||||
|
||||
class GainLayer(ttk.LabelFrame):
|
||||
@@ -10,29 +11,28 @@ class GainLayer(ttk.LabelFrame):
|
||||
|
||||
def __init__(self, parent, index, j):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.parent = parent
|
||||
self.index = index
|
||||
self.j = j
|
||||
self.gain = tk.DoubleVar()
|
||||
self.level = tk.DoubleVar()
|
||||
self.on = tk.BooleanVar()
|
||||
self.s = self._parent._parent.styletable
|
||||
self.styletable = self.parent.parent.styletable
|
||||
if index <= parent.phys_in:
|
||||
self.level_offset = index * 2
|
||||
else:
|
||||
self.level_offset = parent.phys_in * 2 + (index - parent.phys_in) * 8
|
||||
|
||||
self.builder = builders.ChannelLabelFrameBuilder(self, index, id="gainlayer")
|
||||
self.builder.setup()
|
||||
self.builder.add_progressbar()
|
||||
self.builder.add_scale()
|
||||
self.builder.add_on_button()
|
||||
self.sync()
|
||||
self._make_widgets()
|
||||
|
||||
self.col_row_configure()
|
||||
self.watch_pdirty()
|
||||
self.watch_levels()
|
||||
self.grid_configure()
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
_target = self._parent.target
|
||||
"""returns the strip[i].gainlayer class in the current interface"""
|
||||
|
||||
_target = self.parent.target
|
||||
return _target.strip[self.index].gainlayer[self.j]
|
||||
|
||||
def getter(self, param):
|
||||
@@ -46,27 +46,33 @@ class GainLayer(ttk.LabelFrame):
|
||||
def reset_gain(self, *args):
|
||||
self.setter("gain", 0)
|
||||
self.gain.set(0)
|
||||
self._parent._parent.nav_frame.info_text.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))
|
||||
def scale_callback(self, *args):
|
||||
"""callback function for scale widget"""
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self._parent._parent.nav_frame.info_text.set("")
|
||||
self.setter("gain", self.gain.get())
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_press(self, *args):
|
||||
_base_vals.in_scale_button_1 = True
|
||||
_base_values.in_scale_button_1 = True
|
||||
|
||||
def scale_release(self, *args):
|
||||
_base_vals.in_scale_button_1 = False
|
||||
_base_values.in_scale_button_1 = False
|
||||
|
||||
def scale_enter(self, *args):
|
||||
self.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_leave(self, *args):
|
||||
self.parent.parent.nav_frame.info_text.set("")
|
||||
|
||||
def _on_mousewheel(self, event):
|
||||
self.gain.set(
|
||||
self.gain.get()
|
||||
+ (
|
||||
_base_vals.mwscroll_step
|
||||
_base_values.mwscroll_step
|
||||
if event.delta > 0
|
||||
else -_base_vals.mwscroll_step
|
||||
else -_base_values.mwscroll_step
|
||||
)
|
||||
)
|
||||
if self.gain.get() > 12:
|
||||
@@ -74,72 +80,23 @@ class GainLayer(ttk.LabelFrame):
|
||||
elif self.gain.get() < -60:
|
||||
self.gain.set(-60)
|
||||
self.setter("gain", self.gain.get())
|
||||
self._parent._parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def scale_callback(self, *args):
|
||||
"""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.parent.parent.nav_frame.info_text.set(round(self.gain.get(), 1))
|
||||
|
||||
def set_on(self):
|
||||
"""enables a gainlayer. sets its button colour"""
|
||||
|
||||
setattr(
|
||||
self._parent.target.strip[self.index],
|
||||
self._parent.buses[self.j],
|
||||
self.parent.target.strip[self.index],
|
||||
self.parent.buses[self.j],
|
||||
self.on.get(),
|
||||
)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"On.TButton",
|
||||
background=f'{"green" if self.on.get() else "white"}',
|
||||
)
|
||||
|
||||
def convert_level(self, val):
|
||||
if _base_vals.vban_connected:
|
||||
return round(-val * 0.01, 1)
|
||||
return round(20 * log(val, 10), 1) if val > 0 else -200.0
|
||||
|
||||
def _make_widgets(self):
|
||||
"""Creates a progressbar, scale, on button and config button for a single channel"""
|
||||
# Progress bar
|
||||
self.pb = ttk.Progressbar(
|
||||
self,
|
||||
maximum=100,
|
||||
orient="vertical",
|
||||
mode="determinate",
|
||||
variable=self.level,
|
||||
)
|
||||
self.pb.grid(column=0, row=0)
|
||||
|
||||
# Scale
|
||||
self.scale = ttk.Scale(
|
||||
self,
|
||||
from_=12.0,
|
||||
to=-60.0,
|
||||
orient="vertical",
|
||||
variable=self.gain,
|
||||
command=self.scale_callback,
|
||||
length=self._parent.height,
|
||||
)
|
||||
self.scale.grid(column=1, row=0)
|
||||
self.scale.bind("<Double-Button-1>", self.reset_gain)
|
||||
self.scale.bind("<Button-1>", self.scale_press)
|
||||
self.scale.bind("<Enter>", self.scale_enter)
|
||||
self.scale.bind("<ButtonRelease-1>", self.scale_release)
|
||||
self.scale.bind("<Leave>", self.scale_leave)
|
||||
self.scale.bind("<MouseWheel>", self._on_mousewheel)
|
||||
|
||||
# On button
|
||||
self.button_on = ttk.Checkbutton(
|
||||
self,
|
||||
text="ON",
|
||||
command=self.set_on,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else 'On.TButton'}",
|
||||
variable=self.on,
|
||||
)
|
||||
self.button_on.grid(column=0, row=1, columnspan=2)
|
||||
|
||||
def col_row_configure(self):
|
||||
def grid_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
@@ -150,141 +107,119 @@ class GainLayer(ttk.LabelFrame):
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Progressbar) or isinstance(child, ttk.Scale)
|
||||
]
|
||||
# pb and scale
|
||||
self.columnconfigure(0, minsize=36)
|
||||
self.columnconfigure(1, minsize=36)
|
||||
self.rowconfigure(1, minsize=70)
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
"""keeps params synced but ensures sliders are responsive"""
|
||||
if self._parent._parent.pdirty and not _base_vals.in_scale_button_1:
|
||||
self.sync()
|
||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
||||
# on button
|
||||
if _configuration.themes_enabled:
|
||||
self.rowconfigure(1, minsize=70)
|
||||
else:
|
||||
self.rowconfigure(1, minsize=55)
|
||||
|
||||
def sync(self):
|
||||
"""sync params with voicemeeter"""
|
||||
retval = self._parent.target.strip[self.index].label
|
||||
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],
|
||||
self.parent.target.strip[self.index],
|
||||
self.parent.buses[self.j],
|
||||
)
|
||||
)
|
||||
|
||||
def watch_levels(self):
|
||||
self.after(1, self.watch_levels_step)
|
||||
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 watch_levels_step(self):
|
||||
if not _base_vals.dragging:
|
||||
if (
|
||||
self._parent._parent.ldirty
|
||||
and any(
|
||||
self._parent._parent.comp_strip[
|
||||
self.level_offset : self.level_offset + 1
|
||||
]
|
||||
)
|
||||
and _base_vals.strip_level_array_size
|
||||
== len(self._parent._parent.comp_strip)
|
||||
):
|
||||
vals = (
|
||||
self.convert_level(
|
||||
self._parent._parent.strip_levels[self.level_offset]
|
||||
),
|
||||
self.convert_level(
|
||||
self._parent._parent.strip_levels[self.level_offset + 1]
|
||||
),
|
||||
)
|
||||
self.level.set(
|
||||
(
|
||||
0
|
||||
if self._parent._parent.channel_frame.strips[
|
||||
self.index
|
||||
].mute.get()
|
||||
or not self.on.get()
|
||||
else 100 + (max(vals) - 18) + self.gain.get()
|
||||
)
|
||||
)
|
||||
self.after(
|
||||
_base_vals.ldelay if not _base_vals.in_scale_button_1 else 100,
|
||||
self.watch_levels_step,
|
||||
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):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.parent = parent
|
||||
self.phys_in, self.virt_in = parent.kind.ins
|
||||
self.phys_out, self.virt_out = parent.kind.outs
|
||||
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)
|
||||
)
|
||||
defaults = {
|
||||
"width": 80,
|
||||
"height": 150,
|
||||
}
|
||||
self.configuration = defaults | self.configuration
|
||||
self.width = self.configuration["width"]
|
||||
self.height = self.configuration["height"]
|
||||
|
||||
self.gainlayers = [
|
||||
GainLayer(self, index, _base_vals.submixes) for index in range(8)
|
||||
GainLayer(self, index, _base_values.submixes) for index in range(8)
|
||||
]
|
||||
[
|
||||
gainlayer.grid(row=0, column=self.gainlayers.index(gainlayer))
|
||||
for gainlayer in self.gainlayers
|
||||
]
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
# destroy any without label text
|
||||
for i, gainlayer in enumerate(self.gainlayers):
|
||||
gainlayer.grid(row=0, column=i)
|
||||
if not gainlayer.cget("text"):
|
||||
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)
|
||||
gainlayer.grid_remove()
|
||||
labelframe.grid_remove()
|
||||
|
||||
self.watch_pdirty()
|
||||
if _configuration.extends_horizontal:
|
||||
self.grid(row=0, column=2)
|
||||
if parent.bus_frame:
|
||||
parent.bus_frame.grid_remove()
|
||||
else:
|
||||
self._parent.submix_frame.grid(row=2, column=0)
|
||||
if parent.bus_frame:
|
||||
parent.bus_frame.grid_remove()
|
||||
|
||||
# registers submixframe as pdirty observer
|
||||
self.parent.subject_pdirty.add(self)
|
||||
|
||||
@property
|
||||
def target(self):
|
||||
"""returns the current interface"""
|
||||
return self._parent.target
|
||||
|
||||
return self.parent.target
|
||||
|
||||
@property
|
||||
def configuration(self):
|
||||
return self._parent.configuration["channel"]
|
||||
def labelframes(self):
|
||||
"""returns a tuple of current gainlayer labelframe addresses"""
|
||||
|
||||
@configuration.setter
|
||||
def configuration(self, val):
|
||||
self._parent.configuration["channel"] = val
|
||||
return tuple(
|
||||
frame
|
||||
for frame in self.winfo_children()
|
||||
if isinstance(frame, ttk.LabelFrame)
|
||||
)
|
||||
|
||||
def col_row_configure(self):
|
||||
def grid_configure(self):
|
||||
[
|
||||
self.columnconfigure(i, minsize=self.width)
|
||||
for i, _ in enumerate(self.gainlayers)
|
||||
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=130) for i, _ in enumerate(self.gainlayers)]
|
||||
|
||||
def watch_pdirty(self):
|
||||
self.after(1, self.watch_pdirty_step)
|
||||
def update(self):
|
||||
for labelframe in self.labelframes:
|
||||
labelframe.sync()
|
||||
|
||||
def watch_pdirty_step(self):
|
||||
if self._parent.pdirty:
|
||||
self.watch_labels()
|
||||
self.after(_base_vals.pdelay, self.watch_pdirty_step)
|
||||
|
||||
def watch_labels(self):
|
||||
for i, gainlayer in enumerate(self.gainlayers):
|
||||
if not self.target.strip[gainlayer.index].label:
|
||||
self.columnconfigure(i, minsize=0)
|
||||
gainlayer.grid_remove()
|
||||
else:
|
||||
self.columnconfigure(i, minsize=80)
|
||||
gainlayer.grid()
|
||||
def teardown(self):
|
||||
# deregisters submixframe as pdirty observer
|
||||
self.parent.subject_pdirty.remove(self)
|
||||
self.destroy()
|
||||
|
||||
@@ -3,20 +3,23 @@ from tkinter import ttk, messagebox
|
||||
from functools import partial
|
||||
import webbrowser
|
||||
import sv_ttk
|
||||
|
||||
import vbancmd
|
||||
|
||||
from .configurations import configuration
|
||||
from .data import _base_vals, kind_get
|
||||
from .data import (
|
||||
get_configuration,
|
||||
_base_values,
|
||||
_configuration,
|
||||
kind_get,
|
||||
)
|
||||
|
||||
|
||||
class Menus(tk.Menu):
|
||||
def __init__(self, parent, vmr):
|
||||
super().__init__()
|
||||
self._parent = parent
|
||||
self._vmr = vmr
|
||||
if self.configuration_vban is not None:
|
||||
self.vban_conns = [None for i, _ in enumerate(self.configuration_vban)]
|
||||
self.parent = parent
|
||||
self.vmr = vmr
|
||||
self.vban_config = get_configuration("vban")
|
||||
self.app_config = get_configuration("app")
|
||||
self._is_topmost = tk.BooleanVar()
|
||||
self._selected_bus = list(tk.BooleanVar() for _ in range(8))
|
||||
|
||||
@@ -69,76 +72,23 @@ class Menus(tk.Menu):
|
||||
self.menu_profiles_load = tk.Menu(menu_profiles, tearoff=0)
|
||||
menu_profiles.add_cascade(menu=self.menu_profiles_load, label="Load profile")
|
||||
defaults = {"base", "blank"}
|
||||
if len(vmr.profiles) > len(defaults) and all(
|
||||
key in vmr.profiles for key in defaults
|
||||
if len(self.target.profiles) > len(defaults) and all(
|
||||
key in self.target.profiles for key in defaults
|
||||
):
|
||||
[
|
||||
self.menu_profiles_load.add_command(
|
||||
label=profile, command=partial(self.load_profile, profile)
|
||||
)
|
||||
for profile in vmr.profiles.keys()
|
||||
for profile in self.target.profiles.keys()
|
||||
if profile not in defaults
|
||||
]
|
||||
else:
|
||||
menu_profiles.entryconfig(0, state="disabled")
|
||||
menu_profiles.add_command(label="Reset to defaults", command=self.load_defaults)
|
||||
|
||||
# vban connect menu
|
||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||
self.add_cascade(menu=self.menu_vban, label="VBAN")
|
||||
if self.configuration_vban:
|
||||
for i, _ in enumerate(self.configuration_vban):
|
||||
setattr(self, f"menu_vban_{i+1}", tk.Menu(self.menu_vban, tearoff=0))
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
self.menu_vban.add_cascade(
|
||||
menu=target_menu, label=f"VBAN Connect #{i+1}", underline=0
|
||||
)
|
||||
target_menu.add_command(
|
||||
label="Connect", command=partial(self.vban_connect, i)
|
||||
)
|
||||
target_menu.add_command(
|
||||
label="Disconnect", command=partial(self.vban_disconnect, i)
|
||||
)
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
else:
|
||||
self.entryconfig(3, state="disabled")
|
||||
|
||||
# layout menu
|
||||
self.menu_layout = tk.Menu(self, tearoff=0)
|
||||
self.add_cascade(menu=self.menu_layout, label="Layout")
|
||||
# layout/extends
|
||||
self.menu_extends = tk.Menu(self.menu_layout, tearoff=0)
|
||||
self.menu_layout.add_cascade(
|
||||
menu=self.menu_extends, label="Extends", underline=0
|
||||
)
|
||||
self.menu_extends.add_command(
|
||||
label="horizontal",
|
||||
underline=0,
|
||||
command=partial(self.switch_orientation, extends_horizontal=True),
|
||||
)
|
||||
self.menu_extends.add_command(
|
||||
label="vertical",
|
||||
underline=0,
|
||||
command=partial(self.switch_orientation, extends_horizontal=False),
|
||||
)
|
||||
self.menu_extends.entryconfig(
|
||||
0 if _base_vals.extends_horizontal else 1, state="disabled"
|
||||
)
|
||||
# layout/themes
|
||||
self.menu_themes = tk.Menu(self.menu_layout, tearoff=0)
|
||||
self.menu_layout.add_cascade(menu=self.menu_themes, label="Themes")
|
||||
self.menu_themes.add_command(
|
||||
label="light", command=partial(self.load_theme, "light")
|
||||
)
|
||||
self.menu_themes.add_command(
|
||||
label="dark", command=partial(self.load_theme, "dark")
|
||||
)
|
||||
self.menu_themes.entryconfig(
|
||||
0 if self.configuration_app["theme"]["mode"] == "light" else 1,
|
||||
state="disabled",
|
||||
)
|
||||
if not _base_vals.themes_enabled:
|
||||
self.entryconfig(6, state="disabled")
|
||||
# layout/submixes
|
||||
# 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))
|
||||
@@ -155,9 +105,64 @@ class Menus(tk.Menu):
|
||||
)
|
||||
for i in range(8)
|
||||
]
|
||||
self._selected_bus[_base_vals.submixes].set(True)
|
||||
if self._parent.kind.name != "Potato":
|
||||
self.menu_layout.entryconfig(2, state="disabled")
|
||||
self._selected_bus[_base_values.submixes].set(True)
|
||||
if self.parent.kind.name != "Potato":
|
||||
self.menu_layout.entryconfig(0, state="disabled")
|
||||
# layout/extends
|
||||
self.menu_extends = tk.Menu(self.menu_layout, tearoff=0)
|
||||
self.menu_layout.add_cascade(
|
||||
menu=self.menu_extends, label="Extends", underline=0
|
||||
)
|
||||
self.menu_extends.add_command(
|
||||
label="horizontal",
|
||||
underline=0,
|
||||
command=partial(self.switch_orientation, extends_horizontal=True),
|
||||
)
|
||||
self.menu_extends.add_command(
|
||||
label="vertical",
|
||||
underline=0,
|
||||
command=partial(self.switch_orientation, extends_horizontal=False),
|
||||
)
|
||||
self.menu_extends.entryconfig(
|
||||
0 if _configuration.extends_horizontal else 1, state="disabled"
|
||||
)
|
||||
# layout/themes
|
||||
self.menu_themes = tk.Menu(self.menu_layout, tearoff=0)
|
||||
self.menu_layout.add_cascade(menu=self.menu_themes, label="Themes")
|
||||
self.menu_themes.add_command(
|
||||
label="light", command=partial(self.load_theme, "light")
|
||||
)
|
||||
self.menu_themes.add_command(
|
||||
label="dark", command=partial(self.load_theme, "dark")
|
||||
)
|
||||
self.menu_themes.entryconfig(
|
||||
0 if self.app_config["theme"]["mode"] == "light" else 1,
|
||||
state="disabled",
|
||||
)
|
||||
if not _configuration.themes_enabled:
|
||||
self.entryconfig(6, state="disabled")
|
||||
|
||||
# vban connect menu
|
||||
self.menu_vban = tk.Menu(self, tearoff=0)
|
||||
self.add_cascade(menu=self.menu_vban, label="VBAN")
|
||||
if self.vban_config:
|
||||
for i, _ in enumerate(self.vban_config):
|
||||
setattr(self, f"menu_vban_{i+1}", tk.Menu(self.menu_vban, tearoff=0))
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
self.menu_vban.add_cascade(
|
||||
menu=target_menu,
|
||||
label=f"{self.vban_config[f'connection-{i+1}']['streamname']}",
|
||||
underline=0,
|
||||
)
|
||||
target_menu.add_command(
|
||||
label="Connect", command=partial(self.vban_connect, i)
|
||||
)
|
||||
target_menu.add_command(
|
||||
label="Disconnect", command=partial(self.vban_disconnect, i)
|
||||
)
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
else:
|
||||
self.entryconfig(3, state="disabled")
|
||||
|
||||
# Help menu
|
||||
self.menu_help = tk.Menu(self, tearoff=0)
|
||||
@@ -178,20 +183,13 @@ class Menus(tk.Menu):
|
||||
@property
|
||||
def target(self):
|
||||
"""use the correct interface"""
|
||||
return self._parent.target
|
||||
return self.parent.target
|
||||
|
||||
@property
|
||||
def configuration_app(self):
|
||||
return configuration["app"]
|
||||
|
||||
@configuration_app.setter
|
||||
def configuration_app(self, val):
|
||||
self.configuration_app = val
|
||||
|
||||
@property
|
||||
def configuration_vban(self):
|
||||
if "vban" in configuration:
|
||||
return configuration["vban"]
|
||||
def enable_vban_menus(self):
|
||||
[
|
||||
self.menu_vban.entryconfig(j, state="normal")
|
||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||
]
|
||||
|
||||
def action_invoke_voicemeeter(self, cmd):
|
||||
getattr(self.target.command, cmd)()
|
||||
@@ -210,11 +208,10 @@ class Menus(tk.Menu):
|
||||
self.target.apply_profile("base")
|
||||
|
||||
def always_on_top(self):
|
||||
self._parent.attributes("-topmost", self._is_topmost.get())
|
||||
self._parent.update()
|
||||
self.parent.attributes("-topmost", self._is_topmost.get())
|
||||
|
||||
def switch_orientation(self, extends_horizontal: bool = True, *args):
|
||||
_base_vals.extends_horizontal = extends_horizontal
|
||||
_configuration.extends_horizontal = extends_horizontal
|
||||
if extends_horizontal:
|
||||
self.menu_extends.entryconfig(0, state="disabled")
|
||||
self.menu_extends.entryconfig(1, state="normal")
|
||||
@@ -223,17 +220,17 @@ class Menus(tk.Menu):
|
||||
self.menu_extends.entryconfig(0, state="normal")
|
||||
|
||||
def set_submix(self, i):
|
||||
if _base_vals.submixes != i:
|
||||
_base_vals.submixes = i
|
||||
if self._parent.submix_frame is not None:
|
||||
self._parent.submix_frame.destroy()
|
||||
self._parent.nav_frame.show_submix()
|
||||
if _base_values.submixes != i:
|
||||
_base_values.submixes = i
|
||||
if self.parent.submix_frame is not None:
|
||||
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)
|
||||
|
||||
def load_theme(self, theme):
|
||||
sv_ttk.set_theme(theme)
|
||||
self.configuration_app["theme"]["mode"] = theme
|
||||
self.app_config["theme"]["mode"] = theme
|
||||
self.menu_themes.entryconfig(
|
||||
0,
|
||||
state=f"{'disabled' if theme == 'light' else 'normal'}",
|
||||
@@ -268,50 +265,42 @@ class Menus(tk.Menu):
|
||||
]
|
||||
|
||||
opts = {}
|
||||
opts |= self.configuration_vban[f"connection-{i+1}"]
|
||||
opts |= self.vban_config[f"connection-{i+1}"]
|
||||
kind_id = opts.pop("kind")
|
||||
self.vban_conns[i] = vbancmd.connect(kind_id, **opts)
|
||||
self.vban = vbancmd.connect(kind_id, **opts)
|
||||
# login to vban interface
|
||||
self.vban_conns[i].login()
|
||||
self.vban.login()
|
||||
# destroy the current App frames
|
||||
self._parent._destroy_top_level_frames()
|
||||
_base_vals.vban_connected = True
|
||||
self.parent._destroy_top_level_frames()
|
||||
_base_values.vban_connected = True
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(kind_id)
|
||||
self._parent._make_app(kind, self.vban_conns[i])
|
||||
self.parent.build_app(kind, self.vban)
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
target_menu.entryconfig(0, state="disabled")
|
||||
target_menu.entryconfig(1, state="normal")
|
||||
self.menu_layout.entryconfig(
|
||||
2, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||
0, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||
)
|
||||
|
||||
def vban_disconnect(self, i):
|
||||
# destroy the current App frames
|
||||
self._parent._destroy_top_level_frames()
|
||||
_base_vals.vban_connected = False
|
||||
self.parent._destroy_top_level_frames()
|
||||
_base_values.vban_connected = False
|
||||
# logout of vban interface
|
||||
i_to_close = self.vban_conns[i]
|
||||
self.vban_conns[i] = None
|
||||
i_to_close.logout()
|
||||
self.vban.logout()
|
||||
# build new app frames according to a kind
|
||||
kind = kind_get(self._vmr.type)
|
||||
self._parent._make_app(kind, None)
|
||||
kind = kind_get(self.vmr.type)
|
||||
self.parent.build_app(kind, None)
|
||||
target_menu = getattr(self, f"menu_vban_{i+1}")
|
||||
target_menu.entryconfig(0, state="normal")
|
||||
target_menu.entryconfig(1, state="disabled")
|
||||
self.menu_layout.entryconfig(
|
||||
2, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||
0, state=f"{'normal' if kind.name == 'Potato' else 'disabled'}"
|
||||
)
|
||||
|
||||
self.after(15000, self.enable_vban_menus)
|
||||
|
||||
def enable_vban_menus(self):
|
||||
[
|
||||
self.menu_vban.entryconfig(j, state="normal")
|
||||
for j, _ in enumerate(self.menu_vban.winfo_children())
|
||||
]
|
||||
|
||||
def documentation(self):
|
||||
webbrowser.open_new(r"https://voicemeeter.com/")
|
||||
|
||||
|
||||
@@ -1,156 +1,87 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
from .channels import ChannelFrame
|
||||
from . import builders
|
||||
from .data import _configuration
|
||||
from .gainlayer import SubMixFrame
|
||||
from .data import _base_vals
|
||||
|
||||
|
||||
class Navigation(ttk.Frame):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
self._parent = parent
|
||||
self.s = parent.styletable
|
||||
self.parent = parent
|
||||
self.grid(row=0, column=3, sticky=(tk.W, tk.E))
|
||||
self.styletable = self.parent.styletable
|
||||
|
||||
self.submix = tk.BooleanVar()
|
||||
self.channel = tk.BooleanVar()
|
||||
self.extend = tk.BooleanVar()
|
||||
self.info = tk.BooleanVar()
|
||||
self.builder = builders.NavigationFrameBuilder(self)
|
||||
self.builder.setup()
|
||||
self.builder.create_submix_button()
|
||||
self.builder.create_channel_button()
|
||||
self.builder.create_extend_button()
|
||||
self.builder.create_info_button()
|
||||
self.builder.grid_configure()
|
||||
|
||||
self.channel_text = tk.StringVar()
|
||||
self.channel_text.set(parent.channel_frame.identifier.upper())
|
||||
self.extend_text = tk.StringVar()
|
||||
self.extend_text.set("EXTEND")
|
||||
self.info_text = tk.StringVar()
|
||||
self._parent.submix_frame = None
|
||||
|
||||
self._make_widgets()
|
||||
|
||||
self.col_row_configure()
|
||||
|
||||
def _make_widgets(self):
|
||||
"""Creates the navigation buttons"""
|
||||
self.submix_button = ttk.Checkbutton(
|
||||
self,
|
||||
text="SUBMIX",
|
||||
command=self.show_submix,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Submix.TButton'}",
|
||||
variable=self.submix,
|
||||
)
|
||||
self.channel_button = ttk.Checkbutton(
|
||||
self,
|
||||
textvariable=self.channel_text,
|
||||
command=self.switch_channel,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Channel.TButton'}",
|
||||
variable=self.channel,
|
||||
)
|
||||
self.extend_button = ttk.Checkbutton(
|
||||
self,
|
||||
textvariable=self.extend_text,
|
||||
command=self.extend_frame,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Extend.TButton'}",
|
||||
variable=self.extend,
|
||||
)
|
||||
self.info_button = ttk.Checkbutton(
|
||||
self,
|
||||
textvariable=self.info_text,
|
||||
style=f"{'Toggle.TButton' if _base_vals.using_theme else f'Rec.TButton'}",
|
||||
variable=self.info,
|
||||
)
|
||||
self.info_button["state"] = "active"
|
||||
|
||||
""" Position navigation buttons """
|
||||
self.submix_button.grid(column=0, row=0)
|
||||
self.channel_button.grid(column=0, row=1, rowspan=1)
|
||||
self.extend_button.grid(column=0, row=2)
|
||||
self.info_button.grid(column=0, row=3)
|
||||
|
||||
if self._parent.kind.name != "Potato":
|
||||
self.submix_button["state"] = "disabled"
|
||||
self.mainframebuilder = builders.MainFrameBuilder(self.parent)
|
||||
|
||||
def show_submix(self):
|
||||
if self.submix.get():
|
||||
if _base_vals.extends_horizontal:
|
||||
self._parent.submix_frame = SubMixFrame(self._parent)
|
||||
self._parent.submix_frame.grid(row=0, column=2)
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid_remove()
|
||||
else:
|
||||
self._parent.submix_frame = SubMixFrame(self._parent)
|
||||
self._parent.submix_frame.grid(row=2, column=0, sticky=(tk.W))
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid_remove()
|
||||
self.parent.submix_frame = SubMixFrame(self.parent)
|
||||
else:
|
||||
if _base_vals.extends_horizontal:
|
||||
self._parent.submix_frame.destroy()
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid()
|
||||
if _configuration.extends_horizontal:
|
||||
self.parent.submix_frame.teardown()
|
||||
if self.parent.bus_frame:
|
||||
self.parent.bus_frame.grid()
|
||||
else:
|
||||
self._parent.columnconfigure(1, weight=0)
|
||||
self.parent.columnconfigure(1, weight=0)
|
||||
else:
|
||||
self._parent.submix_frame.destroy()
|
||||
if self._parent.bus_frame:
|
||||
self._parent.bus_frame.grid()
|
||||
self.parent.submix_frame.teardown()
|
||||
if self.parent.bus_frame:
|
||||
self.parent.bus_frame.grid()
|
||||
else:
|
||||
self._parent.rowconfigure(2, weight=0, minsize=0)
|
||||
self.parent.rowconfigure(2, weight=0, minsize=0)
|
||||
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"Submix.TButton",
|
||||
background=f'{"purple" if self.submix.get() else "white"}',
|
||||
)
|
||||
|
||||
def switch_channel(self):
|
||||
if self.channel_text.get() == "STRIP":
|
||||
self._parent.bus_frame = ChannelFrame.make_buses(self._parent)
|
||||
self._parent.bus_frame.grid(row=0, column=0)
|
||||
self._parent.channel_frame.destroy()
|
||||
self.mainframebuilder.create_channelframe("bus")
|
||||
self.parent.strip_frame.teardown()
|
||||
else:
|
||||
self._parent.channel_frame = ChannelFrame.make_strips(self._parent)
|
||||
self._parent.channel_frame.grid(row=0, column=0)
|
||||
self._parent.bus_frame.destroy()
|
||||
self.mainframebuilder.create_channelframe("strip")
|
||||
self.parent.bus_frame.teardown()
|
||||
|
||||
self.extend_button["state"] = (
|
||||
"disabled" if self.channel_text.get() == "STRIP" else "normal"
|
||||
)
|
||||
[frame.destroy() 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")
|
||||
|
||||
def extend_frame(self):
|
||||
_configuration.extended = self.extend.get()
|
||||
if self.extend.get():
|
||||
self.channel_button["state"] = "disabled"
|
||||
self._parent.bus_frame = ChannelFrame.make_buses(self._parent)
|
||||
if _base_vals.extends_horizontal:
|
||||
self._parent.bus_frame.grid(row=0, column=2)
|
||||
else:
|
||||
self._parent.bus_frame.grid(row=2, column=0, sticky=(tk.W))
|
||||
self.mainframebuilder.create_channelframe("bus")
|
||||
else:
|
||||
[
|
||||
frame.destroy()
|
||||
for frame in self._parent.configframes
|
||||
frame.teardown()
|
||||
for frame in self.parent.configframes
|
||||
if "!busconfig" in str(frame)
|
||||
]
|
||||
self._parent.bus_frame.destroy()
|
||||
self._parent.bus_frame = None
|
||||
self.parent.bus_frame.teardown()
|
||||
self.parent.bus_frame = None
|
||||
self.channel_button["state"] = "normal"
|
||||
|
||||
if self._parent.submix_frame:
|
||||
self._parent.submix_frame.destroy()
|
||||
if self.parent.submix_frame:
|
||||
self.parent.submix_frame.teardown()
|
||||
self.submix.set(False)
|
||||
if not _base_vals.using_theme:
|
||||
self.s.configure(
|
||||
if not _configuration.themes_enabled:
|
||||
self.styletable.configure(
|
||||
f"Submix.TButton",
|
||||
background=f'{"purple" if self.submix.get() else "white"}',
|
||||
)
|
||||
|
||||
self.extend_text.set("REDUCE" if self.extend.get() else "EXTEND")
|
||||
|
||||
def col_row_configure(self):
|
||||
[
|
||||
child.grid_configure(padx=1, pady=1, sticky=(tk.N, tk.S, tk.W, tk.E))
|
||||
for child in self.winfo_children()
|
||||
if isinstance(child, ttk.Checkbutton)
|
||||
]
|
||||
|
||||
self.rowconfigure(1, minsize=self._parent.channel_frame.height - 18)
|
||||
self.grid(sticky=(tk.N))
|
||||
|
||||
32
vmcompact/subject.py
Normal file
32
vmcompact/subject.py
Normal file
@@ -0,0 +1,32 @@
|
||||
class Subject:
|
||||
def __init__(self):
|
||||
"""list of current observers"""
|
||||
|
||||
self._observables = []
|
||||
|
||||
def notify(self, modifier=None):
|
||||
"""Alert the observers"""
|
||||
|
||||
for observer in self._observables:
|
||||
observer.update()
|
||||
|
||||
def add(self, observer):
|
||||
"""adds an observer to observables"""
|
||||
|
||||
if observer not in self._observables:
|
||||
self._observables.append(observer)
|
||||
|
||||
def remove(self, observer):
|
||||
"""removes an observer from observables"""
|
||||
|
||||
try:
|
||||
self._observables.remove(observer)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def get(self) -> list:
|
||||
"""returns the current observables"""
|
||||
return self._observables
|
||||
|
||||
def clear(self):
|
||||
self._observables.clear()
|
||||
Reference in New Issue
Block a user