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 qtpy.QtWidgets import QLabel, QListWidget, QListWidgetItem, QAbstractItemView, QMenu, QAction
from qtpy.QtCore import Qt
from .utils import FrozenContainer
from ..utils import extract_tooltip
_Callback = Callable[[Any, int], Any]
[docs]class ListWidget(FrozenContainer):
def __init__(self, dragdrop: bool = True, **kwargs):
super().__init__(labels=False, **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.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_, [])
for callback in callbacks:
try:
callback(item.obj, self._listwidget.row(item))
except TypeError:
callback(item.obj)
if dragdrop:
self._listwidget.setAcceptDrops(True)
self._listwidget.setDragEnabled(True)
self._listwidget.setDragDropMode(QAbstractItemView.DragDropMode.InternalMove)
@property
def nitems(self) -> int:
return self._listwidget.count()
@property
def value(self) -> list[Any]:
return [self._listwidget.item(i).obj for i in range(self.nitems)]
@property
def title(self) -> str:
return self._title.text()
@title.setter
def title(self, text: str):
self._title.setText(text)
[docs] def add_item(self, obj: Any):
"""
Append any item to the list.
"""
self.insert_item(self.nitems, obj)
[docs] def insert_item(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_item(self, i: int) -> Any:
"""
Pop item at an index.
"""
self._listwidget.takeItem(i)
return None
[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)