Source code for magicclass.widgets.listwidget
"""
ListWidget is a QListWidget wrapper class. This widget can contain any Python objects as list items.
.. code-block:: python
from magicclass.widgets import ListWidget
listwidget = ListWidget()
# You can add any objects
listwidget.add_item("abc")
listwidget.add_item(np.arange(5))
You can dispatch double click callbacks depending on the type of contents.
.. code-block:: python
@listwidget.register_callback(str)
def _(item, i):
# This function will be called when the item "abc" is double-clicked.
print(item)
@listwidget.register_callback(np.ndarray)
def _(item, i):
# This function will be called when the item np.arange(5) is double-clicked.
print(item.tolist())
In a similar way, you can dispatch display method and context menu.
.. code-block:: python
@listwidget.register_delegate(np.ndarray)
def _(item):
# This function should return how ndarray will be displayed.
return f"Array with shape {item.shape}"
@listwidget.register_contextmenu(np.ndarray)
def Plot(item, i):
'''Function documentation will be the tooltip.'''
plt.plot(item)
plt.show()
"""
from __future__ import annotations
from functools import wraps
from typing import Callable, Any
from collections import defaultdict
from _collections_abc import MutableSequence
from qtpy.QtWidgets import QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QMenu, QAction
from qtpy.QtCore import Qt
from .utils import FreeWidget
from ..utils import extract_tooltip
_Callback = Callable[[Any, int], Any]
[docs]class ListWidget(FreeWidget, MutableSequence):
def __init__(self, dragdrop: bool = True, **kwargs):
super().__init__(**kwargs)
self._listwidget = PyListWidget(self.native)
self._listwidget.setParentContainer(self)
self._title = QLabel(self.native)
self._title.setText(self.name)
self._title.setAlignment(Qt.AlignCenter)
self.running = False
self.set_widget(self._title)
self.set_widget(self._listwidget)
self._callbacks: defaultdict[type, list[_Callback]] = defaultdict(list)
self._delegates: dict[type, Callable[[Any], str]] = dict()
self._contextmenu: defaultdict[type, list[_Callback]] = defaultdict(list)
@self._listwidget.itemDoubleClicked.connect
def _(item: PyListWidgetItem):
type_ = type(item.obj)
callbacks = self._callbacks.get(type_, [])
self.running = True
try:
for callback in callbacks:
try:
callback(item.obj, self._listwidget.row(item))
except TypeError:
callback(item.obj)
finally:
self.running = False
if dragdrop:
self._listwidget.setAcceptDrops(True)
self._listwidget.setDragEnabled(True)
self._listwidget.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
def __len__(self) -> int:
return self._listwidget.count()
@property
def value(self) -> list[Any]:
return [self._listwidget.item(i).obj for i in range(len(self))]
@property
def title(self) -> str:
return self._title.text()
@title.setter
def title(self, text: str):
self._title.setText(text)
[docs] def insert(self, i: int, obj: Any):
"""
Insert any item to the list.
"""
name = self._delegates.get(type(obj), str)(obj)
item = PyListWidgetItem(self._listwidget, obj=obj, name=name)
self._listwidget.insertItem(i, item)
[docs] def pop(self, i: int) -> Any:
"""
Pop item at an index.
"""
obj = self[i]
self._listwidget.takeItem(i)
return obj
def __getitem__(self, i: int) -> Any:
"""
Get i-th python object.
"""
return self._listwidget.item(i).obj
def __setitem__(self, i: int, obj: Any):
self._listwidget.takeItem(i)
self.insert(i, obj)
def __delitem__(self, i: int):
self._listwidget.takeItem(i)
[docs] def register_callback(self, type_: type):
"""
Register a double-click callback function for items of certain type.
"""
def wrapper(func: Callable):
self._callbacks[type_].append(func)
return func
return wrapper
[docs] def register_delegate(self, type_: type):
"""
Register a custom display.
"""
def wrapper(func: Callable):
self._delegates[type_] = func
return func
return wrapper
[docs]class PyListWidget(QListWidget):
def __init__(self, parent: None) -> None:
super().__init__(parent=parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.contextMenu)
[docs] def setParentContainer(self, container: ListWidget) -> None:
self._parent = container
return None
[docs] def contextMenu(self, point):
menu = QMenu(self)
menu.setToolTipsVisible(True)
item = self.itemAt(point)
type_ = type(item.obj)
menus = self._parent._contextmenu.get(type_, [])
for f in menus:
text = f.__name__.replace("_", " ")
action = QAction(text, self)
pfunc = partial_event(f, item.obj, self.row(item))
action.triggered.connect(pfunc)
doc = extract_tooltip(f)
if doc:
action.setToolTip(doc)
menu.addAction(action)
menu.exec_(self.mapToGlobal(point))
[docs]class PyListWidgetItem(QListWidgetItem):
def __init__(self, parent:QListWidget=None, obj=None, name=None):
super().__init__(parent)
if obj is not None:
self.obj = obj
if name is None:
self.setText(str(obj))
else:
self.setText(name)