import typing
from pathlib import Path
from typing import List
import PySide2
from PySide2.QtCore import QAbstractListModel, QModelIndex, Qt, QSortFilterProxyModel
from PySide2.QtWidgets import QWidget
from arthropod_describer.common.local_storage import Storage
from arthropod_describer.common.photo import Photo, UpdateContext
from arthropod_describer.common.state import State
from arthropod_describer.thumbnail_storage import ThumbnailStorage_
ROLE_IMAGE_NAME = Qt.UserRole + 1
ROLE_THUMBNAIL = Qt.UserRole + 3
ROLE_IS_APPROVED = Qt.UserRole + 42
ROLE_APPROVAL_COUNT = Qt.UserRole + 4
ROLE_IS_HIGHLIGHTED = Qt.UserRole + 5
ROLE_HAS_UNSAVED_CHANGES = Qt.UserRole + 6
ROLE_SCALE_MARKER_IMAGE = Qt.UserRole + 7
ROLE_IMAGE_PATH = Qt.UserRole + 8
ROLE_TAGS = Qt.UserRole + 9
[docs]class ImageListModel(QAbstractListModel):
def __init__(self, parent: QWidget = None):
QAbstractListModel.__init__(self, parent)
self.highlighted_indexes: List[int] = []
self.image_paths: List[Path] = []
# self.thumbnails: Optional[ThumbnailStorage] = None
self.dragging = False
self.storage: Storage = None
[docs] def initialize(self, image_paths: List[Path], thumbnail_storage: ThumbnailStorage_, processed_count: int, storage: Storage) -> bool:
self.storage = storage
self.storage.update_photo.connect(self._handle_photo_update)
self.storage.storage_update.connect(self._handle_storage_update)
self.thumbnail_storage = thumbnail_storage
self.thumbnail_storage.update_photo.connect(self._handle_thumbnail_update)
self.highlighted_indexes = []
# if self.thumbnails is not None:
# self.thumbnails.thumbnails_loaded.disconnect(self.handle_thumbnails_loaded)
# self.thumbnails = thumbnail_storage
# self.thumbnails.thumbnails_loaded.connect(self.handle_thumbnails_loaded)
self.beginResetModel()
self.image_paths = image_paths
self.endResetModel()
self.dataChanged.emit(self.index(0, 0), self.index(len(self.image_paths), 0))
[docs] def rowCount(self, parent: QModelIndex = QModelIndex()):
return 0 if self.storage is None else self.storage.image_count
[docs] def columnCount(self, parent: QModelIndex = QModelIndex()):
return 1
[docs] def data(self, index: QModelIndex, role=Qt.DisplayRole):
if not index.isValid():
return None
if role == Qt.DisplayRole:
if index.column() == 0:
return self.storage.image_names[index.row()]
return None
if role == ROLE_IMAGE_NAME:
return self.storage.image_names[index.row()]
if role == ROLE_THUMBNAIL and index.column() == 0:
photo = self.storage.get_photo_by_idx(index.row(), load_image=False)
return photo.thumbnail
if role == ROLE_IS_APPROVED:
return self.storage.is_approved(index.row())
if role == ROLE_APPROVAL_COUNT:
photo = self.storage.get_photo_by_idx(index.row(), load_image=False)
approval = photo.approved['Labels']
mask_names = self.storage.get_label_hierarchy2('Labels').mask_names
approval_count = 0 if approval is None else mask_names.index(approval) + 1
return approval_count, len(mask_names)
if role == ROLE_IS_HIGHLIGHTED:
if len(self.highlighted_indexes) > 0:
return index.row() in self.highlighted_indexes
return True
if role == ROLE_HAS_UNSAVED_CHANGES:
return self.storage.get_photo_by_idx(index.row(), load_image=False).has_unsaved_changes
if role == ROLE_SCALE_MARKER_IMAGE:
photo = self.storage.get_photo_by_idx(index.row(), load_image=False)
if photo.scale_setting is None:
return None
return photo.scale_setting.scale_marker_img
if role == ROLE_IMAGE_PATH:
return self.storage.get_photo_by_idx(index.row(), load_image=False).image_path
if role == ROLE_TAGS:
return self.storage.get_photo_by_idx(index.row(), load_image=False).tags
return None
[docs] def highlight_indexes(self, idxs: List[int]):
self.highlighted_indexes = idxs
self.dataChanged.emit(self.createIndex(0, 0),
self.createIndex(self.rowCount() - 1, 0))
[docs] def handle_slider_pressed(self):
self.dragging = True
[docs] def handle_slider_released(self, first_index: QModelIndex, last_index: QModelIndex):
self.dragging = False
# self.thumbnails.load_thumbnails(first_index.row() - 50, last_index.row() + 50)
[docs] def handle_thumbnails_loaded(self, indices: List[int]):
fidx = self.index(indices[0], 0)
lidx = self.index(indices[-1], 0)
self.dataChanged.emit(fidx, lidx, [ROLE_THUMBNAIL])
[docs] def handle_approval_changed(self, photo: Photo):
idx = self.storage.image_names.index(photo.image_name)
index = self.index(idx, 0)
self.dataChanged.emit(index, index, [ROLE_IS_APPROVED])
def _handle_photo_update(self, img_name: str, ctx: UpdateContext, data: typing.Dict[str, typing.Any]):
index = self.index(self.storage.image_names.index(img_name), 0)
if 'tags' in data:
self.dataChanged.emit(index, index, [ROLE_TAGS])
def _handle_storage_update(self, data: typing.Dict[str, typing.Any]):
if 'photos' in data:
if 'deleted' in data['photos']:
self.beginResetModel()
self.endResetModel()
def _handle_thumbnail_update(self, img_name: str, ctx: UpdateContext, data: typing.Dict[str, typing.Any]):
i = self.storage.image_names.index(img_name)
idx = self.index(i, 0)
self.dataChanged.emit(idx, idx, [ROLE_THUMBNAIL])
[docs]class ImageListSortFilterProxyModel(QSortFilterProxyModel):
def __init__(self, state: State, parent: typing.Optional[PySide2.QtCore.QObject] = None):
super().__init__(parent)
self._state = state
[docs] def filterAcceptsRow(self, source_row: int, source_parent: PySide2.QtCore.QModelIndex) -> bool:
index = self.sourceModel().index(source_row, 0, source_parent)
tags: typing.Set[str] = self.sourceModel().data(index, ROLE_TAGS)
# return self.filterRegExp().pattern() in tags
return set(self._state.active_tags_filter).issubset(tags)