Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Gui improvement #73

Merged
merged 10 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions midi_app_controller/actions/_tests/test_actions_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def bound_controller() -> BoundController:
"name": "TestBinds",
"app_name": "TestApp",
"controller_name": "TestController",
"description": None,
"button_binds": [{"button_id": 1, "action_id": "Action1"}],
"knob_binds": [
{
Expand Down
18 changes: 12 additions & 6 deletions midi_app_controller/actions/_tests/test_bound_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,18 +70,24 @@ def bound_controller(binds, controller, actions) -> BoundController:
return BoundController.create(binds=binds, controller=controller, actions=actions)


def validate_actions(data, actions_class):
actions = actions_class(**data)
x = actions.model_dump()

for key in x:
for key2 in x[key]:
if hasattr(data[key], key2):
assert getattr(data[key], key2) == x[key][key2]


def test_button_actions(actions):
data = {"action_press": actions[0]}
actions = ButtonActions(**data)

assert actions.dict() == data
validate_actions(data, ButtonActions)


def test_knob_actions(actions):
data = {"action_increase": actions[0], "action_decrease": actions[1]}
actions = KnobActions(**data)

assert actions.dict() == data
validate_actions(data, KnobActions)


def test_bound_controller(binds, controller, actions):
Expand Down
3 changes: 3 additions & 0 deletions midi_app_controller/controller/connected_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@
event : tuple[list[int], float]
Pair of (MIDI message, delta time).
"""
if self.paused:
return

Check warning on line 130 in midi_app_controller/controller/connected_controller.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/controller/connected_controller.py#L129-L130

Added lines #L129 - L130 were not covered by tests

message, _ = event

command = message[0] & 0xF0
Expand Down
121 changes: 76 additions & 45 deletions midi_app_controller/gui/binds_editor.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# TODO Move style somewhere else in the future to make this class independent from napari.
from typing import Callable, Optional

from qtpy.QtWidgets import QTabWidget, QCheckBox, QSpacerItem, QSizePolicy

Check warning on line 4 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L4

Added line #L4 was not covered by tests
from napari.qt import get_current_stylesheet
from app_model.types import CommandRule
from superqt.utils import ensure_main_thread
Expand All @@ -11,7 +12,6 @@
QPushButton,
QLabel,
QHBoxLayout,
QRadioButton,
QDialog,
QScrollArea,
QGridLayout,
Expand Down Expand Up @@ -72,6 +72,7 @@
button_binds: list[ButtonBind],
actions: list[CommandRule],
connected_controller: Optional[ConnectedController],
show_action_names_checkbox: QCheckBox,
):
"""Creates ButtonBinds widget.

Expand All @@ -85,6 +86,8 @@
List of all actions available to bind.
connected_controller : ConnectedController
Object representing currently connected MIDI controller.
show_action_names_checkbox : QCheckBox
Checkbox that toggles between action names and ids.
"""
super().__init__()

Expand All @@ -96,8 +99,8 @@

# Description row.
description_layout = QHBoxLayout()
description_layout.addWidget(QLabel("Name:"))
description_layout.addWidget(QLabel("Action when clicked:"))
description_layout.addWidget(QLabel("Button:"), 8)
description_layout.addWidget(QLabel("Action when clicked:"), 10)

Check warning on line 103 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L102-L103

Added lines #L102 - L103 were not covered by tests

# All buttons available to bind.
button_list = QWidget()
Expand All @@ -113,6 +116,7 @@

# Layout.
layout = QVBoxLayout()
layout.addWidget(show_action_names_checkbox)

Check warning on line 119 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L119

Added line #L119 was not covered by tests
layout.addLayout(description_layout)
layout.addWidget(scroll)
layout.addStretch()
Expand Down Expand Up @@ -247,6 +251,7 @@
knob_binds: list[KnobBind],
actions: list[CommandRule],
connected_controller: Optional[ConnectedController],
show_action_names_checkbox: QCheckBox,
):
"""Creates KnobBinds widget.

Expand All @@ -260,6 +265,8 @@
List of all actions available to bind.
connected_controller : ConnectedController
Object representing currently connected MIDI controller.
show_action_names_checkbox : QCheckBox
Checkbox that toggles between action names and ids.
"""
super().__init__()

Expand All @@ -272,9 +279,9 @@

# Description row.
description_layout = QHBoxLayout()
description_layout.addWidget(QLabel("Name:"))
description_layout.addWidget(QLabel("Action when increased:"))
description_layout.addWidget(QLabel("Action when decreased:"))
description_layout.addWidget(QLabel("Knob:"), 2)
description_layout.addWidget(QLabel("Action when increased:"), 5)
description_layout.addWidget(QLabel("Action when decreased:"), 5)

Check warning on line 284 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L282-L284

Added lines #L282 - L284 were not covered by tests

# All knobs available to bind.
knob_list = QWidget()
Expand All @@ -290,6 +297,7 @@

# Layout.
layout = QVBoxLayout()
layout.addWidget(show_action_names_checkbox)

Check warning on line 300 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L300

Added line #L300 was not covered by tests
layout.addLayout(description_layout)
layout.addWidget(scroll)
layout.addStretch()
Expand Down Expand Up @@ -422,10 +430,16 @@
Button that allows to switch binds view to knobs.
buttons_radio : QRadioButton
Button that allows to switch binds view to buttons.
tab_widget : QTabWidget
Tab widget for switching between knobs and buttons configurations.
knobs_widget : KnobBinds
Widget with binds editor for knobs.
buttons_widget : ButtonBinds
Widget with binds editor for buttons.
show_action_names_checkbox_button : QCheckBox
Checkbox that toggles between action names and ids for buttons.
show_action_names_checkbox_knob : QCheckBox
Checkbox that toggles between action names and ids for knobs.
"""

def __init__(
Expand All @@ -451,53 +465,71 @@
"""
super().__init__()

self.setWindowTitle("Edit Binds")

Check warning on line 468 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L468

Added line #L468 was not covered by tests
self.binds = binds.copy(deep=True)
self.save_binds = save_binds

self.name_edit = QLineEdit(binds.name)
name_layout = QHBoxLayout()
name_layout.addWidget(QLabel("Name:"))
name_layout.addWidget(self.name_edit)

Check warning on line 475 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L473-L475

Added lines #L473 - L475 were not covered by tests

self.show_action_names_checkbox_button = QCheckBox("Show action names")
self.show_action_names_checkbox_button.setChecked(True)
self.show_action_names_checkbox_button.stateChanged.connect(

Check warning on line 479 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L477-L479

Added lines #L477 - L479 were not covered by tests
self._toggle_names_mode
)

self.show_action_names_checkbox_knob = QCheckBox("Show action names")
self.show_action_names_checkbox_knob.setChecked(True)
self.show_action_names_checkbox_knob.stateChanged.connect(

Check warning on line 485 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L483-L485

Added lines #L483 - L485 were not covered by tests
self._toggle_names_mode
)

# Save/exit buttons.
toggle_names_mode_button = QPushButton("Toggle names mode")
toggle_names_mode_button.clicked.connect(self._toggle_names_mode)
save_and_exit_button = QPushButton("Save and exit")
save_and_exit_button = QPushButton("Save")

Check warning on line 490 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L490

Added line #L490 was not covered by tests
save_and_exit_button.clicked.connect(self._save_and_exit)
exit_button = QPushButton("Exit")
exit_button = QPushButton("Cancel")

Check warning on line 492 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L492

Added line #L492 was not covered by tests
exit_button.clicked.connect(self._exit)
save_and_exit_button_width = save_and_exit_button.sizeHint().width()
exit_button_width = exit_button.sizeHint().width()
button_width = max(save_and_exit_button_width, exit_button_width)

Check warning on line 496 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L494-L496

Added lines #L494 - L496 were not covered by tests

save_and_exit_button.setFixedWidth(button_width)
exit_button.setFixedWidth(button_width)

Check warning on line 499 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L498-L499

Added lines #L498 - L499 were not covered by tests

buttons_layout = QHBoxLayout()
buttons_layout.addWidget(toggle_names_mode_button)
buttons_layout.addWidget(save_and_exit_button)
spacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
buttons_layout.addItem(spacer)

Check warning on line 503 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L502-L503

Added lines #L502 - L503 were not covered by tests
buttons_layout.addWidget(exit_button)
buttons_layout.addWidget(save_and_exit_button)

Check warning on line 505 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L505

Added line #L505 was not covered by tests

# Radio buttons for switching knobs/buttons view.
self.knobs_radio = QRadioButton("Knobs")
self.buttons_radio = QRadioButton("Buttons")
self.knobs_radio.toggled.connect(self._switch_editors)
self.buttons_radio.toggled.connect(self._switch_editors)

radio_layout = QHBoxLayout()
radio_layout.addWidget(self.knobs_radio)
radio_layout.addWidget(self.buttons_radio)
self.tab_widget = QTabWidget()

Check warning on line 507 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L507

Added line #L507 was not covered by tests

# Bind editors.
self.knobs_widget = KnobBinds(
controller.knobs,
binds.knob_binds,
actions,
connected_controller,
self.show_action_names_checkbox_knob,
)
self.buttons_widget = ButtonBinds(
controller.buttons,
binds.button_binds,
actions,
connected_controller,
self.show_action_names_checkbox_button,
)
self.tab_widget.addTab(self.knobs_widget, "Knobs")
self.tab_widget.addTab(self.buttons_widget, "Buttons")
self.tab_widget.setDocumentMode(True)
self.tab_widget.tabBar().setExpanding(True)
self.tab_widget.currentChanged.connect(self.update_action_names_checkboxes)

Check warning on line 527 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L523-L527

Added lines #L523 - L527 were not covered by tests

# Layout.
layout = QVBoxLayout()
layout.addWidget(self.name_edit)
layout.addLayout(radio_layout)
layout.addLayout(buttons_layout)
layout.addLayout(name_layout)
layout.addWidget(self.tab_widget)

Check warning on line 532 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L531-L532

Added lines #L531 - L532 were not covered by tests

if connected_controller is None:
layout.addWidget(
Expand All @@ -514,32 +546,20 @@
)
)

layout.addWidget(self.knobs_widget)
layout.addWidget(self.buttons_widget)

layout.addLayout(buttons_layout)

Check warning on line 549 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L549

Added line #L549 was not covered by tests
self.setLayout(layout)
self.setStyleSheet(get_current_stylesheet())
self.knobs_radio.setChecked(True)
self.setMinimumSize(830, 650)

def _switch_editors(self, checked: bool):
"""Switches binds editor view for knobs/buttons based on checked radio."""
if not checked:
return
if self.knobs_radio.isChecked():
self.buttons_widget.hide()
self.knobs_widget.show()
else:
self.knobs_widget.hide()
self.buttons_widget.show()

def _toggle_names_mode(self):
"""Toggles actions names mode: titles or ids."""
for combo in self.buttons_widget.button_combos.values():
combo.toggle_names_mode()
for combo1, combo2 in self.knobs_widget.knob_combos.values():
combo1.toggle_names_mode()
combo2.toggle_names_mode()
if self.tab_widget.currentIndex() == 1:
for combo in self.buttons_widget.button_combos.values():
combo.toggle_names_mode()

Check warning on line 558 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L556-L558

Added lines #L556 - L558 were not covered by tests
else:
for combo1, combo2 in self.knobs_widget.knob_combos.values():
combo1.toggle_names_mode()
combo2.toggle_names_mode()

Check warning on line 562 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L560-L562

Added lines #L560 - L562 were not covered by tests

def _save_and_exit(self):
"""Saves the binds and closes the widget."""
Expand All @@ -559,6 +579,17 @@
for thread in self.knobs_widget.thread_list:
thread.wait()

def update_action_names_checkboxes(self):

Check warning on line 582 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L582

Added line #L582 was not covered by tests
"""Updates the checkboxes to be in sync."""
if self.tab_widget.currentIndex() == 0:
self.show_action_names_checkbox_knob.setChecked(

Check warning on line 585 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L584-L585

Added lines #L584 - L585 were not covered by tests
self.show_action_names_checkbox_button.isChecked()
)
else:
self.show_action_names_checkbox_button.setChecked(

Check warning on line 589 in midi_app_controller/gui/binds_editor.py

View check run for this annotation

Codecov / codecov/patch

midi_app_controller/gui/binds_editor.py#L589

Added line #L589 was not covered by tests
self.show_action_names_checkbox_knob.isChecked()
)

def _cancel_timers(self):
"""Cancels timers responsible for unhighlighting elements."""
for timer in self.buttons_widget.highlight_timers.values():
Expand Down
6 changes: 3 additions & 3 deletions midi_app_controller/gui/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ def get_selected_action_id(self) -> Optional[str]:

def is_subpath(path: Path, subpath: Path) -> bool:
"""Checks if one path represents a file/directory inside the other directory."""
path_str = str(subpath.resolve().absolute())
subpath_str = str(path.resolve().absolute())
return path_str.startswith(subpath_str)
path_str = str(path.resolve().absolute())
subpath_str = str(subpath.resolve().absolute())
return subpath_str.startswith(path_str)


def reveal_in_explorer(file: Path):
Expand Down
2 changes: 1 addition & 1 deletion midi_app_controller/models/_tests/test_binds.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def binds_data() -> dict:
def test_valid_binds(binds_data):
binds = Binds(**binds_data)

assert binds.dict() == binds_data
assert binds.model_dump() == binds_data


@pytest.mark.parametrize(
Expand Down
2 changes: 1 addition & 1 deletion midi_app_controller/models/_tests/test_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def controller_data() -> dict:
def test_valid_controller(controller_data):
controller = Controller(**controller_data)

assert controller.dict() == controller_data
assert controller.model_dump() == controller_data


@pytest.mark.parametrize(
Expand Down
2 changes: 1 addition & 1 deletion midi_app_controller/models/_tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_load_from(yaml_file, yaml_data):
model = TempYamlModel.load_from(yaml_file)

assert isinstance(model, TempYamlModel)
assert model.dict() == yaml_data
assert model.model_dump() == yaml_data


def test_load_from_when_invalid_data(yaml_file, yaml_data):
Expand Down
8 changes: 4 additions & 4 deletions midi_app_controller/models/binds.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Optional
from pydantic import BaseModel, Field, root_validator
from pydantic import BaseModel, Field, model_validator

from .utils import YamlBaseModel, find_duplicate

Expand Down Expand Up @@ -61,12 +61,12 @@ class Binds(YamlBaseModel):
button_binds: list[ButtonBind]
knob_binds: list[KnobBind]

@root_validator
@model_validator(mode="after")
@classmethod
def check_duplicate_ids(cls, values):
"""Ensures that every element has different id."""
button_ids = [bind.button_id for bind in values.get("button_binds")]
knob_ids = [bind.knob_id for bind in values.get("knob_binds")]
button_ids = [bind.button_id for bind in values.button_binds]
knob_ids = [bind.knob_id for bind in values.knob_binds]

duplicate = find_duplicate(button_ids + knob_ids)
if duplicate is not None:
Expand Down
Loading
Loading