Source code for arthropod_describer.tags_widget
import bisect
import typing
import PySide2
from PySide2.QtCore import Qt, Signal
from PySide2.QtWidgets import QWidget, QHBoxLayout, QCheckBox, QPushButton, QVBoxLayout, QScrollArea, QSizePolicy, \
QDialog, QLineEdit, QLabel, QToolButton, QTableWidget, QTableWidgetItem, QGridLayout, QGroupBox
from arthropod_describer.common.photo import Photo, UpdateContext
from arthropod_describer.common.state import State
[docs]class TagLine(QWidget):
tag_delete_request = Signal(str)
tag_toggled = Signal([str, bool])
tag_assign_global = Signal(str)
def __init__(self, tag: str, enabled: bool = True, parent: typing.Optional[PySide2.QtWidgets.QWidget] = None,
f: PySide2.QtCore.Qt.WindowFlags = Qt.WindowFlags()):
super().__init__(parent, f)
self.tag = tag
self._layout = QHBoxLayout()
self._checkbox = QCheckBox(text=tag)
self._checkbox.setChecked(enabled)
self._checkbox.toggled.connect(lambda b: self.tag_toggled.emit(self.tag, b))
# self._checkbox.toggled.connect(lambda b: self.photo.toggle_tag(self.tag, b))
self._btn_delete = QToolButton(text="x")
self._btn_delete.setToolTip("Delete tag from the project.")
self._btn_delete.clicked.connect(lambda: self.tag_delete_request.emit(self.tag))
self._btn_assign_global = QToolButton(text='all')
self._btn_assign_global.setToolTip("Assign tag to all photos.")
self._btn_assign_global.clicked.connect(lambda: self.tag_assign_global.emit(self.tag))
self._layout.addWidget(self._checkbox)
# self._layout.addWidget(self._btn_assign_global)
# self._layout.addWidget(self._btn_delete)
self.setLayout(self._layout)
[docs]class TagsWidget(QDialog):
widget_left = Signal()
def __init__(self, state: State, title: str = 'Photo tags', parent: typing.Optional[PySide2.QtWidgets.QWidget] = None,
f: PySide2.QtCore.Qt.WindowFlags = Qt.WindowFlags()):
super().__init__(parent, Qt.Window
| Qt.X11BypassWindowManagerHint
| Qt.FramelessWindowHint)
self._state = state
self.storage = self._state.storage
self.setVisible(False)
# self.tags = self.storage.used_tags
self.tag_lines: typing.Dict[str, TagLine] = {}
self.main_layout = QVBoxLayout()
self._tags_grid_layout = QGridLayout()
self._tags_widget = QGroupBox()
self._tags_widget.setTitle(title)
self._tags_widget.setLayout(self._tags_grid_layout)
self._tags_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.tags_sorted: typing.List[str] = []
self.main_layout.addWidget(self._tags_widget)
self.setLayout(self.main_layout)
self.setMaximumHeight(200)
self.is_hovered: bool = False
self._lblNoTags = QLabel(text="no tags")
self._tags_grid_layout.addWidget(self._lblNoTags, 0, 0)
def _add_tag_line(self, tag: str, enabled: bool = True) -> TagLine:
tag_widget = TagLine(tag, enabled)
# tag_widget.tag_toggled.connect(self._state.toggle_filtering_tag)
if len(self.tag_lines) == 0:
self._lblNoTags.hide()
self._tags_grid_layout.removeWidget(self._lblNoTags)
return tag_widget
# def _handle_assign_tag_global(self, tag: str):
# for photo in self.storage.images:
# photo.add_tag(tag)
#
# def _handle_delete_tag_global(self, tag: str):
# for photo in self.storage.images:
# photo.remove_tag(tag)
#
def _connect_tag_line_signals(self, tag_line: TagLine):
tag_line.tag_toggled.connect(self._state.toggle_filtering_tag)
[docs] def clear_tags(self):
for tag in self.tag_lines.values():
self._tags_grid_layout.removeWidget(tag)
tag.deleteLater()
self.tag_lines.clear()
self.tags_sorted.clear()
[docs] def populate(self):
self.clear_tags()
self.tags_sorted = list(sorted(self.storage.used_tags))
if len(self.tags_sorted) == 0:
self._tags_grid_layout.addWidget(self._lblNoTags, 0, 0)
self._lblNoTags.show()
else:
self._lblNoTags.hide()
self._tags_grid_layout.removeWidget(self._lblNoTags)
for row, tag in enumerate(self.tags_sorted):
if tag not in self.tag_lines:
tag_widget = self._add_tag_line(tag, False)
self._connect_tag_line_signals(tag_widget)
self.tag_lines[tag] = tag_widget
self._tags_grid_layout.addWidget(tag_widget, row, 0)
[docs] def update_tag_states(self):
for tag, tag_line in self.tag_lines.items():
tag_line.blockSignals(True)
tag_line._checkbox.setChecked(tag in self._state.active_tags_filter)
tag_line.blockSignals(False)
[docs] def leaveEvent(self, event:PySide2.QtCore.QEvent):
self.is_hovered = False
self.widget_left.emit()
[docs]class PhotoTagsWidget(TagsWidget):
def __init__(self, photo: Photo, state: State, parent: typing.Optional[PySide2.QtWidgets.QWidget] = None,
f: PySide2.QtCore.Qt.WindowFlags = Qt.WindowFlags()):
super().__init__(state, parent=parent, f=f)
self.photo = photo
self.tags = photo.tags
self.populate()
lay = QHBoxLayout()
self.lblNewTag = QLabel(text="New tag: ")
self.txtNewTag = QLineEdit()
self.txtNewTag.setPlaceholderText('enter a new tag and confirm with <Enter>')
self.txtNewTag.returnPressed.connect(self._handle_confirm_new_tag)
lay.addWidget(self.lblNewTag)
lay.addWidget(self.txtNewTag)
self.main_layout.addLayout(lay)
state.storage.update_photo.connect(self.handle_photo_update)
def _handle_confirm_new_tag(self):
if len(self.txtNewTag.text()) == 0 or self.txtNewTag.text().isspace():
return
self.photo.add_tag(self.txtNewTag.text())
self.txtNewTag.clear()
def _add_tag_line(self, tag: str, enabled: bool = True) -> TagLine:
tag_widget = super(PhotoTagsWidget, self)._add_tag_line(tag, enabled)
# tag_widget.tag_toggled.connect(self.photo.toggle_tag)
return tag_widget
[docs] def handle_photo_update(self, image_name: str, ctx: UpdateContext, data: typing.Dict[str, typing.Any]):
if image_name != self.photo.image_name:
return
if 'tags' not in data:
return
for tag in data['tags']['added']:
if tag not in self.tag_lines:
tag_widget = self._add_tag_line(tag)
self._connect_tag_line_signals(tag_widget)
self.tag_lines[tag] = tag_widget
# self._enabled_tags_lay.addWidget(tag_widget)
position = bisect.bisect(self.tags_sorted, tag)
for i in range(position, len(self.tags_sorted)):
tag_to_move = self.tags_sorted[i]
tag_widget_to_move = self.tag_lines[tag_to_move]
self._tags_grid_layout.removeWidget(tag_widget_to_move)
self.tags_sorted.insert(position, tag)
for row in range(position, len(self.tags_sorted)):
tag_to_insert = self.tags_sorted[row]
tag_widget_to_insert = self.tag_lines[tag_to_insert]
self._tags_grid_layout.addWidget(tag_widget_to_insert, row, 0)
else:
self.tag_lines[tag].blockSignals(True)
self.tag_lines[tag]._checkbox.setChecked(True)
self.tag_lines[tag].blockSignals(False)
def _connect_tag_line_signals(self, tag_line: TagLine):
tag_line.tag_toggled.connect(self.photo.toggle_tag)
[docs] def populate(self):
super().populate()
for tag in self.photo.tags:
tag_widget = self.tag_lines[tag]
tag_widget.blockSignals(True)
tag_widget._checkbox.setChecked(True)
tag_widget.blockSignals(False)