Skip to content

Commit

Permalink
Allow users to rename Zones (#197)
Browse files Browse the repository at this point in the history
Co-authored-by: Tyler Compton <[email protected]>
  • Loading branch information
BryceBeagle and velovix authored Sep 13, 2021
1 parent d2a44be commit ac0ab59
Show file tree
Hide file tree
Showing 10 changed files with 279 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QInputDialog
from PyQt5.uic import loadUi

from brainframe.api import bf_codecs
from brainframe.api import bf_codecs, bf_errors

from brainframe_qt.api_utils import api
from brainframe_qt.ui.dialogs import AlarmCreationDialog
Expand Down Expand Up @@ -58,6 +58,7 @@ def _init_signals(self) -> None:
self.zone_list.initiate_zone_edit.connect(self._edit_zone_by_id)
self.zone_list.zone_delete.connect(self.delete_zone)
self.zone_list.alarm_delete.connect(self.delete_alarm)
self.zone_list.zone_name_change.connect(self.change_zone_name)

self.dialog_button_box.accepted.connect(self.accept)
self.dialog_button_box.rejected.connect(self.reject)
Expand Down Expand Up @@ -171,6 +172,48 @@ def cancel_zone_edit(self) -> None:
if None in self.zone_list.zones:
self.zone_list.remove_zone(None)

def change_zone_name(self, zone_id: int, zone_name: str) -> None:
def update_zone_name() -> Zone:
zone = api.get_zone(zone_id)
zone.name = zone_name

updated_zone = Zone.from_api_zone(api.set_zone(zone))
return updated_zone

def on_success(updated_zone: Zone) -> None:
self.zone_list.update_zone(updated_zone)

def on_error(error: Exception) -> None:
dialog: Optional[BrainFrameMessage] = None
title = self.tr("Unable to rename Zone")

if isinstance(error, bf_errors.ZoneNotFoundError):
message = self.tr(
f"Attempted to rename Zone {zone_id} to {zone_name} but the Zone "
f"no longer exists."
)
try:
self.zone_list.remove_zone(zone_id)
except KeyError:
# Zone must already be gone
pass

dialog = BrainFrameMessage.warning(
parent=self,
title=title,
warning=message,
)

if dialog is not None:
dialog.exec()

QTAsyncWorker(
self,
update_zone_name,
on_success=on_success,
on_error=on_error,
).start()

def delete_alarm(self, alarm_id: int) -> None:
api.delete_zone_alarm(alarm_id)
self.zone_list.remove_alarm(alarm_id)
Expand Down
21 changes: 21 additions & 0 deletions brainframe_qt/ui/dialogs/task_configuration/task_configuration.ui
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,27 @@
</property>
</spacer>
</item>
<item alignment="Qt::AlignHCenter">
<widget class="QLabel" name="rename_help_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>35</weight>
</font>
</property>
<property name="text">
<string>Double click a zone to rename it</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
Expand Down
30 changes: 24 additions & 6 deletions brainframe_qt/ui/dialogs/task_configuration/widgets/zone_list.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Dict, Callable, List
from typing import Callable, Dict, List, Optional

from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtWidgets import QWidget, QVBoxLayout
Expand All @@ -14,6 +14,8 @@ class ZoneList(QWidget):
zone_delete = pyqtSignal(int)
alarm_delete = pyqtSignal(int)

zone_name_change = pyqtSignal(int, str)

layout: Callable[..., QVBoxLayout]

def __init__(self, parent=None):
Expand All @@ -39,19 +41,22 @@ def _init_style(self) -> None:
self.layout().setContentsMargins(0, 0, 0, 0)
self.layout().setSpacing(0)

def add_zone(self, zone: Zone) -> ZoneListZoneItem:
def add_zone(self, zone: Zone, index: Optional[int] = None) -> ZoneListZoneItem:
"""Creates and returns the new ZoneListItem using the Zone"""
zone_item = ZoneListZoneItem(zone, parent=self)

self.zones[zone.id] = zone_item

zone_item.zone_edit.connect(self.initiate_zone_edit)
zone_item.zone_delete.connect(self.zone_delete)
zone_item.zone_name_change.connect(self.zone_name_change)

self.layout().addWidget(zone_item)
if index is not None:
self.layout().insertWidget(index, zone_item)
else:
self.layout().addWidget(zone_item)

for alarm in zone.alarms:
self.add_alarm(zone, alarm)
self._add_alarm_widgets_for_zone(zone)

return zone_item

Expand All @@ -61,6 +66,13 @@ def confirm_zone(self, zone: Zone) -> None:
self.remove_zone(None)
self.add_zone(zone)

def update_zone(self, zone: Zone) -> None:
old_zone_widget = self.zones[zone.id]
old_index = self.layout().indexOf(old_zone_widget)

self.remove_zone(zone.id)
self.add_zone(zone, old_index)

def add_alarm(self, zone: Zone, alarm: bf_codecs.ZoneAlarm):
alarm_widget = ZoneListAlarmItem(alarm, parent=self)

Expand All @@ -74,18 +86,20 @@ def remove_alarm(self, alarm_id: int) -> None:
alarm_widget = self.alarms.pop(alarm_id)

self.layout().removeWidget(alarm_widget)
alarm_widget.deleteLater()

def remove_zone(self, zone_id: int) -> None:
zone_widget: ZoneListZoneItem = self.zones.pop(zone_id)

alarm_widgets = self._find_alarm_widgets_for_zone(zone_widget)

# Remove zone and all child alarms
self.layout().removeWidget(zone_widget)
for alarm_widget in alarm_widgets:
# Uses private attribute for now, but this is temporary until zone widgets
# contain alarm widgets inside of them
self.remove_alarm(alarm_widget._alarm.id)
self.layout().removeWidget(zone_widget)
zone_widget.deleteLater()

def _add_alarm_widget(self, alarm_widget: ZoneListAlarmItem, zone_id: int) -> None:
"""Add an alarm widget to the correct zone"""
Expand All @@ -103,6 +117,10 @@ def _add_alarm_widget(self, alarm_widget: ZoneListAlarmItem, zone_id: int) -> No

self.layout().insertWidget(insert_index, alarm_widget)

def _add_alarm_widgets_for_zone(self, zone: Zone) -> None:
for alarm in zone.alarms:
self.add_alarm(zone, alarm)

def _find_alarm_widgets_for_zone(
self, zone_widget: ZoneListZoneItem
) -> List[ZoneListAlarmItem]:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from typing import Tuple

from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QValidator
from PyQt5.QtWidgets import QWidget

from brainframe.api import bf_codecs
Expand All @@ -11,6 +14,7 @@ class ZoneListZoneItem(ZoneListItemUI):

zone_delete = pyqtSignal(int)
zone_edit = pyqtSignal(int)
zone_name_change = pyqtSignal(int, str)

def __init__(self, zone: Zone, *, parent: QObject):
super().__init__(parent=parent)
Expand All @@ -20,24 +24,38 @@ def __init__(self, zone: Zone, *, parent: QObject):
self.entry_name = zone.name
self.entry_type = self._get_entry_type(zone)

self._init_validators()
self._init_signals()
self._configure_buttons()

self._handle_full_frame_zone()

def _init_signals(self) -> None:
self.trash_button.clicked.connect(self._on_trash_button_click)
self.edit_button.clicked.connect(self._on_edit_button_click)
self.name_label.text_changed.connect(self._on_zone_name_change)

def _init_validators(self) -> None:
validator = self._ZoneNameValidator()

self.name_label.validator = validator

def _configure_buttons(self) -> None:
def _handle_full_frame_zone(self) -> None:
"""Disable some functionality if the Zone is the full-frame zone"""
if self._zone.name == bf_codecs.Zone.FULL_FRAME_ZONE_NAME:
self.trash_button.setDisabled(True)
self.edit_button.setDisabled(True)

self.name_label.editable = False

def _on_edit_button_click(self, _clicked: bool) -> None:
self.zone_edit.emit(self._zone.id)

def _on_trash_button_click(self, _clicked: bool) -> None:
self.zone_delete.emit(self._zone.id)

def _on_zone_name_change(self, zone_name: str) -> None:
self.zone_name_change.emit(self._zone.id, zone_name)

@staticmethod
def _get_entry_type(zone: Zone) -> ZoneListType:
if isinstance(zone, Line):
Expand All @@ -47,10 +65,20 @@ def _get_entry_type(zone: Zone) -> ZoneListType:
else:
return ZoneListType.UNKNOWN

class _ZoneNameValidator(QValidator):
def validate(self, input_: str, pos: int) -> Tuple[QValidator.State, str, int]:
if input_ == bf_codecs.Zone.FULL_FRAME_ZONE_NAME:
state = QValidator.Intermediate
else:
state = QValidator.Acceptable

return state, input_, pos


class ZoneListAlarmItem(ZoneListItemUI):
"""Temporary until ZoneListZoneItem holds Alarm widgets"""
alarm_delete = pyqtSignal(int)
alarm_edit = pyqtSignal(int)

def __init__(self, alarm: bf_codecs.ZoneAlarm, *, parent: QObject):
super().__init__(parent=parent)
Expand All @@ -64,8 +92,11 @@ def __init__(self, alarm: bf_codecs.ZoneAlarm, *, parent: QObject):

self._init_signals()

self._disable_editing()

def _init_signals(self) -> None:
self.trash_button.clicked.connect(self._on_trash_button_click)
self.edit_button.clicked.connect(self._on_edit_button_click)

def _init_padding_widget(self) -> QWidget:
"""Temporary solution to indent alarm widgets a bit.
Expand All @@ -81,3 +112,15 @@ def _init_padding_widget(self) -> QWidget:

def _on_trash_button_click(self, _clicked: bool) -> None:
self.alarm_delete.emit(self._alarm.id)

def _on_edit_button_click(self, _clicked: bool) -> None:
self.alarm_edit.emit(self._alarm.id)

def _disable_editing(self) -> None:
"""Editing of alarms is not currently supported"""
self.edit_button.setDisabled(True)
self.edit_button.setToolTip(self.tr(
"Editing of alarms is not currently not supported"
))

self.name_label.editable = False
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

from PyQt5.QtCore import QObject, Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QPushButton, QWidget, QHBoxLayout, QLabel
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QLineEdit

from brainframe_qt.ui.resources.ui_elements.buttons import IconButton
from brainframe_qt.ui.resources.ui_elements.widgets import AspectRatioSVGWidget
from brainframe_qt.ui.resources.ui_elements.widgets.text_edit import EditableLabel


class ZoneListType(Enum):
Expand Down Expand Up @@ -48,8 +49,11 @@ def _init_entry_icon(self) -> AspectRatioSVGWidget:

return icon

def _init_name_label(self) -> QLabel:
def _init_name_label(self) -> EditableLabel:
label = QLabel(self._entry_name, parent=self)
line_edit = QLineEdit(parent=self)

label = EditableLabel(label, line_edit, parent=self)

return label

Expand Down Expand Up @@ -93,7 +97,7 @@ def entry_name(self) -> str:
@entry_name.setter
def entry_name(self, entry_name: str) -> None:
self._entry_name = entry_name
self.name_label.setText(entry_name)
self.name_label.text = entry_name

@property
def entry_type(self) -> ZoneListType:
Expand Down
Loading

0 comments on commit ac0ab59

Please sign in to comment.