Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Allow users to rename Zones #197

Merged
merged 11 commits into from
Sep 13, 2021
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
15 changes: 15 additions & 0 deletions brainframe_qt/ui/dialogs/task_configuration/widgets/zone_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 Down Expand Up @@ -47,6 +49,7 @@ def add_zone(self, zone: Zone) -> ZoneListZoneItem:

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)

Expand All @@ -61,6 +64,14 @@ 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]
new_zone_widget = self.add_zone(zone)

self.layout().replaceWidget(old_zone_widget, new_zone_widget)

old_zone_widget.deleteLater()

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

Expand All @@ -75,6 +86,8 @@ def remove_alarm(self, alarm_id: int) -> None:

self.layout().removeWidget(alarm_widget)

alarm_widget.deleteLater()

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

Expand All @@ -87,6 +100,8 @@ def remove_zone(self, zone_id: int) -> None:
# contain alarm widgets inside of them
self.remove_alarm(alarm_widget._alarm.id)

zone_widget.deleteLater()

def _add_alarm_widget(self, alarm_widget: ZoneListAlarmItem, zone_id: int) -> None:
"""Add an alarm widget to the correct zone"""
zone_widget = self.zones[zone_id]
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
43 changes: 33 additions & 10 deletions brainframe_qt/ui/resources/i18n/brainframe_zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1284,22 +1284,22 @@ Please recheck the entered server address.</source>
<context>
<name>TaskConfiguration</name>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="188"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="231"/>
<source>Add points until done, then press &quot;Confirm&quot; button</source>
<translation>添加点直到完成,然后按“确认”按钮</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="224"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="267"/>
<source>Item Name Already Exists</source>
<translation>名称已存在</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="225"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="268"/>
<source>Item {} already exists in Stream</source>
<translation>项目{}已存在于视频流中</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="228"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="271"/>
<source>Please use another name.</source>
<translation>请使用另一个名称。</translation>
</message>
Expand Down Expand Up @@ -1334,35 +1334,50 @@ Please recheck the entered server address.</source>
<translation>已有任务</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.ui" line="217"/>
<location filename="../../dialogs/task_configuration/task_configuration.ui" line="238"/>
<source>Confirm</source>
<translation>确认</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.ui" line="224"/>
<location filename="../../dialogs/task_configuration/task_configuration.ui" line="245"/>
<source>Cancel</source>
<translation>取消</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="101"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="102"/>
<source>New Line</source>
<translation>新检测线段</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="101"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="102"/>
<source>Name for new line:</source>
<translation>新检测线段名称:</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="112"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="113"/>
<source>New Region</source>
<translation>新检测区域</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="112"/>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="113"/>
<source>Name for new region:</source>
<translation>新检测区域名称:</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="188"/>
<source>Unable to rename Zone</source>
<translation>无法重命名区域</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.py" line="191"/>
<source>Attempted to rename Zone {zone_id} to {zone_name} but the Zone no longer exists.</source>
<translation>尝试将区域 {zone_id} 重命名为 {zone_name},但该区域不再存在。</translation>
</message>
<message>
<location filename="../../dialogs/task_configuration/task_configuration.ui" line="167"/>
<source>Double click a zone to rename it</source>
<translation>双击区域以重命名</translation>
</message>
</context>
<context>
<name>TextLicenseEditor</name>
Expand Down Expand Up @@ -1420,6 +1435,14 @@ Please recheck the entered server address.</source>
<translation>这将删除所有此视频流相关的区域、检测结果、警报、报警信息等,并且无法撤消。这个流程会在后台发生&lt;br&gt;&lt;br&gt;可能需要几分钟。</translation>
</message>
</context>
<context>
<name>ZoneListAlarmItem</name>
<message>
<location filename="../../dialogs/task_configuration/widgets/zone_list_item.py" line="122"/>
<source>Editing of alarms is not currently not supported</source>
<translation>目前不支持编辑预警</translation>
</message>
</context>
<context>
<name>ZoneStatusLabelItem</name>
<message>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .drag_and_drop_text_editor import DragAndDropTextEditor
from .editable_label import EditableLabel
from .placeholder_text_edit import PlaceholderTextEdit
Loading