# -*- coding: utf-8 -*-
"""
This file contains Qt models for Python data structures.
.. Copyright (c) 2021, the qudi developers. See the AUTHORS.md file at the top-level directory of this
.. distribution and on <https://github.com/Ulm-IQO/qudi-core/>
..
.. This file is part of qudi.
..
.. Qudi is free software: you can redistribute it and/or modify it under the terms of
.. the GNU Lesser General Public License as published by the Free Software Foundation,
.. either version 3 of the License, or (at your option) any later version.
..
.. Qudi is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
.. without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
.. See the GNU Lesser General Public License for more details.
..
.. You should have received a copy of the GNU Lesser General Public License along with qudi.
.. If not, see <https://www.gnu.org/licenses/>.
"""
__all__ = ["DictTableModel", "ListTableModel"]
from PySide2 import QtCore
from typing import Any, Optional, Union, Sequence
from qudi.util.mutex import RecursiveMutex
[docs]
class DictTableModel(QtCore.QAbstractTableModel):
"""Qt model storing a table in dictionaries"""
[docs]
def __init__(self, headers: Union[str, Sequence[str]]):
super().__init__()
self._lock = RecursiveMutex()
if isinstance(headers, str):
self._headers = [headers]
elif not all(isinstance(h, str) for h in headers):
raise TypeError("DictTableModel header entries must be str type.")
else:
self._headers = list(headers)
self._storage = dict()
[docs]
def rowCount(self, parent: Optional[QtCore.QModelIndex] = None) -> int:
"""Returns the number of stored items (rows)"""
with self._lock:
return len(self._storage)
[docs]
def columnCount(self, parent: Optional[QtCore.QModelIndex] = None) -> int:
"""Returns the number of data fields (columns)"""
with self._lock:
return len(self._headers)
[docs]
def flags(self, index: Optional[QtCore.QModelIndex] = None) -> QtCore.Qt.ItemFlags:
"""Determines what can be done with the given indexed cell."""
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
[docs]
def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole) -> Any:
"""Get data from model for a given cell. Data can have a role that affects display.
Re-Implement in subclass in order to support anything else than the 2 default columns.
"""
with self._lock:
if index.isValid() and role == QtCore.Qt.DisplayRole:
key = self.get_key_by_index(index.row())
if index.column() == 0:
return key
elif index.column() == 1:
return self._storage[key]
return None
[docs]
def get_key_by_index(self, n: int) -> Any:
"""Get a dict key by index number"""
with self._lock:
it = iter(self._storage)
try:
for _ in range(n + 1):
key = next(it)
except StopIteration:
raise IndexError(
f"Index {n:d} out of bounds for table model with "
f"{len(self._storage):d} rows"
) from None
return key
[docs]
def get_index_by_key(self, key: Any) -> int:
"""Get row index for dict key.
Warning: Row index for a key changes when keys with lower index are removed.
"""
with self._lock:
for i, storage_key in enumerate(self._storage.keys()):
if key == storage_key:
return i
raise KeyError
def __setitem__(self, key, value):
with self._lock:
if key in self._storage:
row = self.get_index_by_key(key)
self._storage[key] = value
index = self.index(row, 1)
self.dataChanged.emit(index, index)
else:
row = len(self._storage)
self.beginInsertRows(QtCore.QModelIndex(), row, row)
self._storage[key] = value
self.endInsertRows()
def __getitem__(self, item):
with self._lock:
return self._storage.__getitem__(item)
def __repr__(self):
with self._lock:
return repr(self._storage)
def __len__(self):
with self._lock:
return len(self._storage)
def __delitem__(self, key):
with self._lock:
if key not in self._storage:
raise KeyError
self.pop(key)
def __iter__(self):
with self._lock:
return self._storage.__iter__()
def __contains__(self, item):
with self._lock:
return self._storage.__contains__(item)
[docs]
def clear(self):
with self._lock:
self.beginResetModel()
self._storage.clear()
self.endResetModel()
[docs]
def copy(self):
model = DictTableModel(self._headers.copy())
model._storage = self._storage.copy()
return model
[docs]
def update(self, *args, **kwargs):
with self._lock:
update_dict = dict(*args, **kwargs)
for key, value in update_dict.items():
self.__setitem__(key, value)
[docs]
def pop(self, *args):
"""
Remove a key from a dictionary and return its value.
Parameters
----------
*args : tuple
The arguments should include:
- key : The key to remove from the dictionary.
- default : optional
The value to return if the key is not found in the dictionary.
Returns
-------
value
The value associated with the removed key. If the key is not found and a default
value is provided, the default value is returned. Otherwise, a `KeyError` is raised.
"""
with self._lock:
if args[0] in self._storage:
row = self.get_index_by_key(args[0])
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
ret = self._storage.pop(args[0])
self.endRemoveRows()
return ret
elif len(args) > 1:
return args[1]
[docs]
def get(self, *args):
"""
Get the value associated with a key from a dictionary.
Parameters
----------
*args : tuple
The arguments should include:
- key : The key to look up in the dictionary.
- default : optional
The value to return if the key is not found in the dictionary.
Returns
-------
value
The value associated with the key in the dictionary. If the key is not found
and a default value is provided, the default value is returned.
"""
with self._lock:
return self._storage.get(*args)
[docs]
def values(self):
with self._lock:
return self._storage.values()
[docs]
def keys(self):
with self._lock:
return self._storage.keys()
[docs]
def items(self):
with self._lock:
return self._storage.items()
[docs]
class ListTableModel(QtCore.QAbstractTableModel):
"""Qt model storing a table in lists."""
[docs]
def __init__(self, headers: Union[str, Sequence[str]]):
super().__init__()
self._lock = RecursiveMutex()
if isinstance(headers, str):
self._headers = headers
elif not all(isinstance(h, str) for h in headers):
raise TypeError("DictTableModel header entries must be str type.")
else:
self._headers = list(headers)
self._storage = list()
[docs]
def rowCount(self, parent: Optional[QtCore.QModelIndex] = None):
"""Gives the number of stored items (rows)"""
with self._lock:
return len(self._storage)
[docs]
def columnCount(self, parent: Optional[QtCore.QModelIndex] = None):
"""Gives the number of data fields (columns)"""
return len(self._headers)
[docs]
def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
"""Determines what can be done with entry cells in the table view."""
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
[docs]
def data(self, index: QtCore.QModelIndex, role: QtCore.Qt.ItemDataRole) -> Any:
"""Get data from model for a given cell. Data can have a role that affects display."""
with self._lock:
if index.isValid() and role == QtCore.Qt.DisplayRole:
return self._storage[index.row()]
return None
def __setitem__(self, key, value):
with self._lock:
if key < 0:
key = key + len(self._storage)
self._storage[key] = value
self.dataChanged.emit(
self.index(key, 0), self.index(key, len(self._headers) - 1)
)
def __getitem__(self, key):
with self._lock:
return self._storage[key]
def __repr__(self):
with self._lock:
return repr(self._storage)
def __len__(self):
with self._lock:
return len(self._storage)
def __delitem__(self, key):
with self._lock:
if key < 0:
key = key + len(self._storage)
self.pop(key)
def __iter__(self):
with self._lock:
return iter(self._storage)
def __contains__(self, item):
with self._lock:
return item in self._storage
[docs]
def insert(self, n, data):
"""
Insert a row into a table before the nth element.
Parameters
----------
n : int
Index before which the row should be inserted.
data : object
Row to insert into the table.
"""
with self._lock:
if 0 <= n <= len(self._storage):
self.beginInsertRows(QtCore.QModelIndex(), n, n)
self._storage.insert(n, data)
self.endInsertRows()
else:
raise IndexError
[docs]
def append(self, data):
"""
Append a row to a table.
Parameters
----------
data : object
Row to append to the table.
"""
with self._lock:
n = len(self._storage)
self.beginInsertRows(QtCore.QModelIndex(), n, n)
self._storage.append(data)
self.endInsertRows()
[docs]
def pop(self, n):
"""
Remove the nth row from a table.
Parameters
----------
n : int
Index of the row to remove.
Returns
-------
object
Removed row.
"""
with self._lock:
if 0 <= n < len(self._storage):
self.beginRemoveRows(QtCore.QModelIndex(), n, n)
ret = self._storage.pop(n)
self.endRemoveRows()
return ret
else:
raise IndexError
[docs]
def extend(self, seq):
with self._lock:
seq = list(seq)
n = len(self._storage)
m = n + len(seq)
self.beginInsertRows(QtCore.QModelIndex(), n, m)
self._storage.extend(seq)
self.endInsertRows()
[docs]
def remove(self, value):
with self._lock:
row = self._storage.index(value)
self.pop(row)
[docs]
def count(self, value):
with self._lock:
return self._storage.count(value)
[docs]
def copy(self):
model = ListTableModel(self._headers)
model._storage = self._storage.copy()
return model