mirror of
https://github.com/onyx-and-iris/nvda-voicemeeter.git
synced 2024-11-22 18:00:50 +00:00
places gain sliders on slider tabs
slider focus in/out events registered.
This commit is contained in:
parent
086fbc1b8c
commit
bc14116cd7
@ -41,19 +41,31 @@ class Builder:
|
|||||||
layout0.append([step()])
|
layout0.append([step()])
|
||||||
|
|
||||||
layout1_1 = []
|
layout1_1 = []
|
||||||
steps = (self.make_tab1_rows,)
|
steps = (self.make_tab1_button_rows,)
|
||||||
for step in steps:
|
for step in steps:
|
||||||
layout1_1.append([step()])
|
layout1_1.append([step()])
|
||||||
|
layout1_2 = []
|
||||||
|
steps = (self.make_tab1_slider_rows,)
|
||||||
|
for step in steps:
|
||||||
|
layout1_2.append([step()])
|
||||||
|
|
||||||
layout2_1 = []
|
layout2_1 = []
|
||||||
steps = (self.make_tab2_rows,)
|
steps = (self.make_tab2_button_rows,)
|
||||||
for step in steps:
|
for step in steps:
|
||||||
layout2_1.append([step()])
|
layout2_1.append([step()])
|
||||||
|
layout2_2 = []
|
||||||
|
steps = (self.make_tab2_slider_rows,)
|
||||||
|
for step in steps:
|
||||||
|
layout2_2.append([step()])
|
||||||
|
|
||||||
layout3_1 = []
|
layout3_1 = []
|
||||||
steps = (self.make_tab3_rows,)
|
steps = (self.make_tab3_button_rows,)
|
||||||
for step in steps:
|
for step in steps:
|
||||||
layout3_1.append([step()])
|
layout3_1.append([step()])
|
||||||
|
layout3_2 = []
|
||||||
|
steps = (self.make_tab3_slider_rows,)
|
||||||
|
for step in steps:
|
||||||
|
layout3_2.append([step()])
|
||||||
|
|
||||||
def _make_inner_tabgroup(layouts, identifier) -> psg.TabGroup:
|
def _make_inner_tabgroup(layouts, identifier) -> psg.TabGroup:
|
||||||
inner_layout = []
|
inner_layout = []
|
||||||
@ -71,11 +83,11 @@ class Builder:
|
|||||||
case "Settings":
|
case "Settings":
|
||||||
return psg.Tab("Settings", layout0, key="tab||Settings")
|
return psg.Tab("Settings", layout0, key="tab||Settings")
|
||||||
case "Physical Strip":
|
case "Physical Strip":
|
||||||
tabgroup = _make_inner_tabgroup((layout1_1, []), identifier)
|
tabgroup = _make_inner_tabgroup((layout1_1, layout1_2), identifier)
|
||||||
case "Virtual Strip":
|
case "Virtual Strip":
|
||||||
tabgroup = _make_inner_tabgroup((layout2_1, []), identifier)
|
tabgroup = _make_inner_tabgroup((layout2_1, layout2_2), identifier)
|
||||||
case "Buses":
|
case "Buses":
|
||||||
tabgroup = _make_inner_tabgroup((layout3_1, []), identifier)
|
tabgroup = _make_inner_tabgroup((layout3_1, layout3_2), identifier)
|
||||||
return psg.Tab(identifier, [[tabgroup]], key=f"tab||{identifier}")
|
return psg.Tab(identifier, [[tabgroup]], key=f"tab||{identifier}")
|
||||||
|
|
||||||
tabs = []
|
tabs = []
|
||||||
@ -118,7 +130,7 @@ class Builder:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
hardware_in = list()
|
hardware_in = []
|
||||||
[step(hardware_in) for step in (add_physical_device_opts,)]
|
[step(hardware_in) for step in (add_physical_device_opts,)]
|
||||||
return psg.Frame("Hardware In", hardware_in)
|
return psg.Frame("Hardware In", hardware_in)
|
||||||
|
|
||||||
@ -142,7 +154,7 @@ class Builder:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
hardware_out = list()
|
hardware_out = []
|
||||||
[step(hardware_out) for step in (add_physical_device_opts,)]
|
[step(hardware_out) for step in (add_physical_device_opts,)]
|
||||||
return psg.Frame("Hardware Out", hardware_out)
|
return psg.Frame("Hardware Out", hardware_out)
|
||||||
|
|
||||||
@ -174,7 +186,7 @@ class Builder:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
inner = list()
|
inner = []
|
||||||
asio_checkboxlists = ([] for _ in range(self.kind.phys_out))
|
asio_checkboxlists = ([] for _ in range(self.kind.phys_out))
|
||||||
for i, checkbox_list in enumerate(asio_checkboxlists):
|
for i, checkbox_list in enumerate(asio_checkboxlists):
|
||||||
[step(checkbox_list, i + 1) for step in (add_asio_checkboxes,)]
|
[step(checkbox_list, i + 1) for step in (add_asio_checkboxes,)]
|
||||||
@ -200,7 +212,7 @@ class Builder:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
hardware_out = list()
|
hardware_out = []
|
||||||
[step(hardware_out) for step in (add_physical_device_opts,)]
|
[step(hardware_out) for step in (add_physical_device_opts,)]
|
||||||
return psg.Frame("PATCH COMPOSITE", hardware_out)
|
return psg.Frame("PATCH COMPOSITE", hardware_out)
|
||||||
|
|
||||||
@ -235,8 +247,8 @@ class Builder:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
asio_checkboxes = list()
|
asio_checkboxes = []
|
||||||
inner = list()
|
inner = []
|
||||||
checkbox_lists = ([] for _ in range(self.kind.num_strip))
|
checkbox_lists = ([] for _ in range(self.kind.num_strip))
|
||||||
for i, checkbox_list in enumerate(checkbox_lists):
|
for i, checkbox_list in enumerate(checkbox_lists):
|
||||||
if i < self.kind.phys_in:
|
if i < self.kind.phys_in:
|
||||||
@ -266,7 +278,7 @@ class Builder:
|
|||||||
key="ADVANCED SETTINGS FRAME",
|
key="ADVANCED SETTINGS FRAME",
|
||||||
)
|
)
|
||||||
|
|
||||||
def make_tab1_row(self, i) -> psg.Frame:
|
def make_tab1_button_row(self, i) -> psg.Frame:
|
||||||
"""tab1 row represents a strip's outputs (A1-A5, B1-B3)"""
|
"""tab1 row represents a strip's outputs (A1-A5, B1-B3)"""
|
||||||
|
|
||||||
def add_strip_outputs(layout):
|
def add_strip_outputs(layout):
|
||||||
@ -290,15 +302,40 @@ class Builder:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
outputs = list()
|
outputs = []
|
||||||
[step(outputs) for step in (add_strip_outputs,)]
|
[step(outputs) for step in (add_strip_outputs,)]
|
||||||
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL")
|
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL")
|
||||||
|
|
||||||
def make_tab1_rows(self) -> psg.Frame:
|
def make_tab1_button_rows(self) -> psg.Frame:
|
||||||
layout = [[self.make_tab1_row(i)] for i in range(self.kind.phys_in)]
|
layout = [[self.make_tab1_button_row(i)] for i in range(self.kind.phys_in)]
|
||||||
return psg.Frame(None, layout, border_width=0)
|
return psg.Frame(None, layout, border_width=0)
|
||||||
|
|
||||||
def make_tab2_row(self, i) -> psg.Frame:
|
def make_tab1_slider_row(self, i) -> psg.Frame:
|
||||||
|
def add_gain_slider(layout):
|
||||||
|
layout.append(
|
||||||
|
[
|
||||||
|
psg.Slider(
|
||||||
|
range=(-60, 12),
|
||||||
|
default_value=self.vm.strip[i].gain,
|
||||||
|
resolution=0.1,
|
||||||
|
disable_number_display=True,
|
||||||
|
expand_x=True,
|
||||||
|
enable_events=True,
|
||||||
|
orientation="horizontal",
|
||||||
|
key=f"STRIP {i}||SLIDER GAIN",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
[step(outputs) for step in (add_gain_slider,)]
|
||||||
|
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL||SLIDER")
|
||||||
|
|
||||||
|
def make_tab1_slider_rows(self) -> psg.Frame:
|
||||||
|
layout = [[self.make_tab1_slider_row(i)] for i in range(self.kind.phys_in)]
|
||||||
|
return psg.Frame(None, layout, border_width=0)
|
||||||
|
|
||||||
|
def make_tab2_button_row(self, i) -> psg.Frame:
|
||||||
"""tab2 row represents a strip's outputs (A1-A5, B1-B3)"""
|
"""tab2 row represents a strip's outputs (A1-A5, B1-B3)"""
|
||||||
|
|
||||||
def add_strip_outputs(layout):
|
def add_strip_outputs(layout):
|
||||||
@ -331,15 +368,44 @@ class Builder:
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
outputs = list()
|
outputs = []
|
||||||
[step(outputs) for step in (add_strip_outputs,)]
|
[step(outputs) for step in (add_strip_outputs,)]
|
||||||
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL")
|
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL")
|
||||||
|
|
||||||
def make_tab2_rows(self) -> psg.Frame:
|
def make_tab2_button_rows(self) -> psg.Frame:
|
||||||
layout = [[self.make_tab2_row(i)] for i in range(self.kind.phys_in, self.kind.phys_in + self.kind.virt_in)]
|
layout = [
|
||||||
|
[self.make_tab2_button_row(i)] for i in range(self.kind.phys_in, self.kind.phys_in + self.kind.virt_in)
|
||||||
|
]
|
||||||
return psg.Frame(None, layout, border_width=0)
|
return psg.Frame(None, layout, border_width=0)
|
||||||
|
|
||||||
def make_tab3_row(self, i) -> psg.Frame:
|
def make_tab2_slider_row(self, i) -> psg.Frame:
|
||||||
|
def add_gain_slider(layout):
|
||||||
|
layout.append(
|
||||||
|
[
|
||||||
|
psg.Slider(
|
||||||
|
range=(-60, 12),
|
||||||
|
default_value=self.vm.strip[i].gain,
|
||||||
|
resolution=0.1,
|
||||||
|
disable_number_display=True,
|
||||||
|
expand_x=True,
|
||||||
|
enable_events=True,
|
||||||
|
orientation="horizontal",
|
||||||
|
key=f"STRIP {i}||SLIDER GAIN",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
[step(outputs) for step in (add_gain_slider,)]
|
||||||
|
return psg.Frame(self.window.cache["labels"][f"STRIP {i}||LABEL"], outputs, key=f"STRIP {i}||LABEL||SLIDER")
|
||||||
|
|
||||||
|
def make_tab2_slider_rows(self) -> psg.Frame:
|
||||||
|
layout = [
|
||||||
|
[self.make_tab2_slider_row(i)] for i in range(self.kind.phys_in, self.kind.phys_in + self.kind.virt_in)
|
||||||
|
]
|
||||||
|
return psg.Frame(None, layout, border_width=0)
|
||||||
|
|
||||||
|
def make_tab3_button_row(self, i) -> psg.Frame:
|
||||||
"""tab3 row represents bus composite toggle"""
|
"""tab3 row represents bus composite toggle"""
|
||||||
|
|
||||||
def add_strip_outputs(layout):
|
def add_strip_outputs(layout):
|
||||||
@ -358,10 +424,35 @@ class Builder:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
outputs = list()
|
outputs = []
|
||||||
[step(outputs) for step in (add_strip_outputs,)]
|
[step(outputs) for step in (add_strip_outputs,)]
|
||||||
return psg.Frame(self.window.cache["labels"][f"BUS {i}||LABEL"], outputs, key=f"BUS {i}||LABEL")
|
return psg.Frame(self.window.cache["labels"][f"BUS {i}||LABEL"], outputs, key=f"BUS {i}||LABEL")
|
||||||
|
|
||||||
def make_tab3_rows(self) -> psg.Frame:
|
def make_tab3_button_rows(self) -> psg.Frame:
|
||||||
layout = [[self.make_tab3_row(i)] for i in range(self.kind.num_bus)]
|
layout = [[self.make_tab3_button_row(i)] for i in range(self.kind.num_bus)]
|
||||||
|
return psg.Frame(None, layout, border_width=0)
|
||||||
|
|
||||||
|
def make_tab3_slider_row(self, i) -> psg.Frame:
|
||||||
|
def add_gain_slider(layout):
|
||||||
|
layout.append(
|
||||||
|
[
|
||||||
|
psg.Slider(
|
||||||
|
range=(-60, 12),
|
||||||
|
default_value=self.vm.bus[i].gain,
|
||||||
|
resolution=0.1,
|
||||||
|
disable_number_display=True,
|
||||||
|
expand_x=True,
|
||||||
|
enable_events=True,
|
||||||
|
orientation="horizontal",
|
||||||
|
key=f"BUS {i}||SLIDER GAIN",
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
outputs = []
|
||||||
|
[step(outputs) for step in (add_gain_slider,)]
|
||||||
|
return psg.Frame(self.window.cache["labels"][f"BUS {i}||LABEL"], outputs, key=f"BUS {i}||LABEL||SLIDER")
|
||||||
|
|
||||||
|
def make_tab3_slider_rows(self) -> psg.Frame:
|
||||||
|
layout = [[self.make_tab3_slider_row(i)] for i in range(self.kind.num_bus)]
|
||||||
return psg.Frame(None, layout, border_width=0)
|
return psg.Frame(None, layout, border_width=0)
|
||||||
|
@ -42,14 +42,14 @@ def _make_param_cache(vm, channel_type) -> dict:
|
|||||||
**{f"STRIP {i}||SOLO": vm.strip[i].solo for i in range(vm.kind.num_strip)},
|
**{f"STRIP {i}||SOLO": vm.strip[i].solo for i in range(vm.kind.num_strip)},
|
||||||
**{f"STRIP {i}||MUTE": vm.strip[i].mute for i in range(vm.kind.num_strip)},
|
**{f"STRIP {i}||MUTE": vm.strip[i].mute for i in range(vm.kind.num_strip)},
|
||||||
}
|
}
|
||||||
return params
|
|
||||||
else:
|
else:
|
||||||
return {
|
params |= {
|
||||||
**{f"BUS {i}||MONO": vm.bus[i].mono for i in range(vm.kind.num_bus)},
|
**{f"BUS {i}||MONO": vm.bus[i].mono for i in range(vm.kind.num_bus)},
|
||||||
**{f"BUS {i}||EQ": vm.bus[i].eq.on for i in range(vm.kind.num_bus)},
|
**{f"BUS {i}||EQ": vm.bus[i].eq.on for i in range(vm.kind.num_bus)},
|
||||||
**{f"BUS {i}||MUTE": vm.bus[i].mute for i in range(vm.kind.num_bus)},
|
**{f"BUS {i}||MUTE": vm.bus[i].mute for i in range(vm.kind.num_bus)},
|
||||||
**{f"BUS {i}||MODE": vm.bus[i].mode.get() for i in range(vm.kind.num_bus)},
|
**{f"BUS {i}||MODE": vm.bus[i].mode.get() for i in range(vm.kind.num_bus)},
|
||||||
}
|
}
|
||||||
|
return params
|
||||||
|
|
||||||
|
|
||||||
def _make_label_cache(vm) -> dict:
|
def _make_label_cache(vm) -> dict:
|
||||||
|
@ -64,6 +64,11 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self[f"HARDWARE OUT||A2"].Widget.config(**buttonmenu_opts)
|
self[f"HARDWARE OUT||A2"].Widget.config(**buttonmenu_opts)
|
||||||
if self.kind.name != "basic":
|
if self.kind.name != "basic":
|
||||||
[self[f"PATCH COMPOSITE||PC{i + 1}"].Widget.config(**buttonmenu_opts) for i in range(self.kind.phys_out)]
|
[self[f"PATCH COMPOSITE||PC{i + 1}"].Widget.config(**buttonmenu_opts) for i in range(self.kind.phys_out)]
|
||||||
|
slider_opts = {"takefocus": 1, "highlightthickness": 1}
|
||||||
|
for i in range(self.kind.num_strip):
|
||||||
|
self[f"STRIP {i}||SLIDER GAIN"].Widget.config(**slider_opts)
|
||||||
|
for i in range(self.kind.num_bus):
|
||||||
|
self[f"BUS {i}||SLIDER GAIN"].Widget.config(**slider_opts)
|
||||||
|
|
||||||
self.register_events()
|
self.register_events()
|
||||||
self["tabgroup"].set_focus()
|
self["tabgroup"].set_focus()
|
||||||
@ -111,6 +116,11 @@ class NVDAVMWindow(psg.Window):
|
|||||||
}
|
}
|
||||||
for key, value in self.cache["labels"].items():
|
for key, value in self.cache["labels"].items():
|
||||||
self[key].update(value=value)
|
self[key].update(value=value)
|
||||||
|
self[f"{key}||SLIDER"].update(value=value)
|
||||||
|
for i in range(self.kind.num_strip):
|
||||||
|
self[f"STRIP {i}||SLIDER GAIN"].update(value=self.vm.strip[i].gain)
|
||||||
|
for i in range(self.kind.num_bus):
|
||||||
|
self[f"BUS {i}||SLIDER GAIN"].update(value=self.vm.bus[i].gain)
|
||||||
if self.kind.name != "basic":
|
if self.kind.name != "basic":
|
||||||
for key, value in self.cache["asio"].items():
|
for key, value in self.cache["asio"].items():
|
||||||
identifier, i = key.split("||")
|
identifier, i = key.split("||")
|
||||||
@ -192,6 +202,11 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self[f"STRIP {i}||{param}"].bind("<FocusIn>", "||FOCUS IN")
|
self[f"STRIP {i}||{param}"].bind("<FocusIn>", "||FOCUS IN")
|
||||||
self[f"STRIP {i}||{param}"].bind("<Return>", "||KEY ENTER")
|
self[f"STRIP {i}||{param}"].bind("<Return>", "||KEY ENTER")
|
||||||
|
|
||||||
|
# Strip Sliders
|
||||||
|
for i in range(self.kind.num_strip):
|
||||||
|
self[f"STRIP {i}||SLIDER GAIN"].bind("<FocusIn>", "||FOCUS IN")
|
||||||
|
self[f"STRIP {i}||SLIDER GAIN"].bind("<FocusOut>", "||FOCUS OUT")
|
||||||
|
|
||||||
# Bus Params
|
# Bus Params
|
||||||
params = ["MONO", "EQ", "MUTE", "MODE"]
|
params = ["MONO", "EQ", "MUTE", "MODE"]
|
||||||
if self.vm.kind.name == "basic":
|
if self.vm.kind.name == "basic":
|
||||||
@ -201,6 +216,11 @@ class NVDAVMWindow(psg.Window):
|
|||||||
self[f"BUS {i}||{param}"].bind("<FocusIn>", "||FOCUS IN")
|
self[f"BUS {i}||{param}"].bind("<FocusIn>", "||FOCUS IN")
|
||||||
self[f"BUS {i}||{param}"].bind("<Return>", "||KEY ENTER")
|
self[f"BUS {i}||{param}"].bind("<Return>", "||KEY ENTER")
|
||||||
|
|
||||||
|
# Bus Sliders
|
||||||
|
for i in range(self.kind.num_bus):
|
||||||
|
self[f"BUS {i}||SLIDER GAIN"].bind("<FocusIn>", "||FOCUS IN")
|
||||||
|
self[f"BUS {i}||SLIDER GAIN"].bind("<FocusOut>", "||FOCUS OUT")
|
||||||
|
|
||||||
def popup_save_as(self, message, title=None, initial_folder=None):
|
def popup_save_as(self, message, title=None, initial_folder=None):
|
||||||
layout = [
|
layout = [
|
||||||
[psg.Text(message)],
|
[psg.Text(message)],
|
||||||
@ -632,6 +652,21 @@ class NVDAVMWindow(psg.Window):
|
|||||||
case [["STRIP", index], [param], ["KEY", "ENTER"]]:
|
case [["STRIP", index], [param], ["KEY", "ENTER"]]:
|
||||||
self.find_element_with_focus().click()
|
self.find_element_with_focus().click()
|
||||||
|
|
||||||
|
# Strip Sliders
|
||||||
|
case [["STRIP", index], ["SLIDER", "GAIN"]]:
|
||||||
|
label = self.cache["labels"][f"STRIP {index}||LABEL"]
|
||||||
|
val = values[event]
|
||||||
|
self.vm.strip[int(index)].gain = val
|
||||||
|
self.nvda.speak(f"{label} gain slider {val}")
|
||||||
|
case [["STRIP", index], ["SLIDER", "GAIN"], ["FOCUS", "IN"]]:
|
||||||
|
if self.find_element_with_focus() is not None:
|
||||||
|
self.vm.event.pdirty = False
|
||||||
|
label = self.cache["labels"][f"STRIP {index}||LABEL"]
|
||||||
|
val = values[f"STRIP {index}||SLIDER GAIN"]
|
||||||
|
self.nvda.speak(f"{label} gain slider {val}")
|
||||||
|
case [["STRIP", index], ["SLIDER", "GAIN"], ["FOCUS", "OUT"]]:
|
||||||
|
self.vm.event.pdirty = True
|
||||||
|
|
||||||
# Bus Params
|
# Bus Params
|
||||||
case [["BUS", index], [param]]:
|
case [["BUS", index], [param]]:
|
||||||
val = self.cache["bus"][event]
|
val = self.cache["bus"][event]
|
||||||
@ -691,6 +726,21 @@ class NVDAVMWindow(psg.Window):
|
|||||||
case [["BUS", index], [param], ["KEY", "ENTER"]]:
|
case [["BUS", index], [param], ["KEY", "ENTER"]]:
|
||||||
self.find_element_with_focus().click()
|
self.find_element_with_focus().click()
|
||||||
|
|
||||||
|
# Bus Sliders
|
||||||
|
case [["BUS", index], ["SLIDER", "GAIN"]]:
|
||||||
|
label = self.cache["labels"][f"BUS {index}||LABEL"]
|
||||||
|
val = values[event]
|
||||||
|
self.vm.bus[int(index)].gain = val
|
||||||
|
self.nvda.speak(f"{label} gain slider {val}")
|
||||||
|
case [["BUS", index], ["SLIDER", "GAIN"], ["FOCUS", "IN"]]:
|
||||||
|
if self.find_element_with_focus() is not None:
|
||||||
|
self.vm.event.pdirty = False
|
||||||
|
label = self.cache["labels"][f"BUS {index}||LABEL"]
|
||||||
|
val = values[f"BUS {index}||SLIDER GAIN"]
|
||||||
|
self.nvda.speak(f"{label} gain slider {val}")
|
||||||
|
case [["BUS", index], ["SLIDER", "GAIN"], ["FOCUS", "OUT"]]:
|
||||||
|
self.vm.event.pdirty = True
|
||||||
|
|
||||||
# Unknown
|
# Unknown
|
||||||
case _:
|
case _:
|
||||||
self.logger.debug(f"Unknown event {event}")
|
self.logger.debug(f"Unknown event {event}")
|
||||||
|
Loading…
Reference in New Issue
Block a user