# -*- coding: utf-8 -*-
"""
Plot Item list
^^^^^^^^^^^^^^
The `plot item list` panel is a widget which displays the list of items attached to
the plot.
.. autoclass:: PlotItemList
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from guidata.configtools import get_icon, get_image_layout
from guidata.qthelpers import add_actions, create_action
from guidata.utils.misc import assert_interfaces_valid
from qtpy import QtCore as QC
from qtpy import QtWidgets as QW
from plotpy.config import _
from plotpy.constants import ID_ITEMLIST
from plotpy.interfaces import IPanel
from plotpy.interfaces import items as itf
from plotpy.panels.base import PanelWidget
if TYPE_CHECKING:
from qtpy.QtGui import QContextMenuEvent, QIcon
from qtpy.QtWidgets import QListWidgetItem, QWidget
from plotpy.plot import BasePlot, PlotManager
class ItemListWidget(QW.QListWidget):
"""
PlotItemList
List of items attached to plot
"""
def __init__(self, parent: QWidget) -> None:
super().__init__(parent)
self.manager = None
self.plot = None # the default plot...
self.items = []
self.currentRowChanged.connect(self.current_row_changed)
self.itemChanged.connect(self.item_changed)
self.itemSelectionChanged.connect(self.refresh_actions)
self.itemSelectionChanged.connect(self.selection_changed)
self.setWordWrap(True)
self.setMinimumWidth(140)
self.setSelectionMode(QW.QListWidget.ExtendedSelection)
# Setup context menu
self.menu = QW.QMenu(self)
self.menu_actions = self.setup_actions()
self.refresh_actions()
add_actions(self.menu, self.menu_actions)
def register_panel(self, manager: PlotManager) -> None:
"""Register panel to plot manager
Args:
manager: plot manager to register to
"""
self.manager = manager
for plot in self.manager.get_plots():
plot.SIG_ITEMS_CHANGED.connect(self.items_changed)
plot.SIG_ACTIVE_ITEM_CHANGED.connect(self.active_item_changed)
self.plot = self.manager.get_plot()
def contextMenuEvent(self, event: QContextMenuEvent) -> None:
"""Override Qt method"""
self.refresh_actions()
self.menu.popup(event.globalPos())
def setup_actions(self) -> None:
"""Setup actions"""
self.movedown_ac = create_action(
self,
_("Move to back"),
icon=get_icon("arrow_down.png"),
triggered=lambda: self.move_item("down"),
)
self.moveup_ac = create_action(
self,
_("Move to front"),
icon=get_icon("arrow_up.png"),
triggered=lambda: self.move_item("up"),
)
settings_ac = create_action(
self,
_("Parameters..."),
icon=get_icon("settings.png"),
triggered=self.edit_plot_parameters,
)
self.remove_ac = create_action(
self, _("Remove"), icon=get_icon("trash.png"), triggered=self.remove_item
)
return [self.moveup_ac, self.movedown_ac, None, settings_ac, self.remove_ac]
def edit_plot_parameters(self) -> None:
"""Edit plot parameters"""
self.plot.edit_plot_parameters("item")
def __is_selection_contiguous(self) -> bool:
"""Check if selected items are contiguous"""
indexes = sorted([self.row(lw_item) for lw_item in self.selectedItems()])
return len(indexes) <= 1 or list(range(indexes[0], indexes[-1] + 1)) == indexes
def get_selected_items(self) -> list[itf.IBasePlotItem]:
"""Return selected QwtPlot items
.. warning::
This is not the same as
:py:meth:`.baseplot.BasePlot.get_selected_items`.
Some items could appear in itemlist without being registered in
plot widget items (in particular, some items could be selected in
itemlist without being selected in plot widget)
"""
return [self.items[self.row(lw_item)] for lw_item in self.selectedItems()]
def refresh_actions(self) -> None:
"""Refresh actions"""
is_selection = len(self.selectedItems()) > 0
for action in self.menu_actions:
if action is not None:
action.setEnabled(is_selection)
if is_selection:
remove_state = True
for item in self.get_selected_items():
remove_state = remove_state and not item.is_readonly()
self.remove_ac.setEnabled(remove_state)
for action in [self.moveup_ac, self.movedown_ac]:
action.setEnabled(self.__is_selection_contiguous())
def __get_item_icon(self, item: itf.IBasePlotItem) -> QIcon:
"""Get item icon"""
return get_icon(item.get_icon_name())
def items_changed(self, plot: BasePlot) -> None:
"""Plot items have changed
Args:
plot: plot
"""
active_plot = self.manager.get_active_plot()
if active_plot is not plot:
return
self.plot = plot
_block = self.blockSignals(True)
active = plot.get_active_item()
self.items = plot.get_public_items(z_sorted=True)
self.clear()
for item in self.items:
title = item.title().text()
lw_item = QW.QListWidgetItem(self.__get_item_icon(item), title, self)
lw_item.setCheckState(
QC.Qt.Checked if item.isVisible() else QC.Qt.Unchecked
)
lw_item.setSelected(item.selected)
font = lw_item.font()
if item is active:
font.setItalic(True)
else:
font.setItalic(False)
lw_item.setFont(font)
self.addItem(lw_item)
self.refresh_actions()
self.blockSignals(_block)
def active_item_changed(self, plot: BasePlot) -> None:
"""Plot items have changed
Args:
plot: plot
"""
active_plot = self.manager.get_active_plot()
if active_plot is not plot:
return
self.plot = plot
_block = self.blockSignals(True)
active = plot.get_active_item()
for item in self.items:
title = item.title().text()
lw_item = self.item(self.items.index(item))
lw_item.setText(title)
lw_item.setSelected(item.selected)
font = lw_item.font()
if item is active:
font.setItalic(True)
else:
font.setItalic(False)
lw_item.setFont(font)
self.refresh_actions()
self.blockSignals(_block)
def current_row_changed(self, index: int) -> None:
"""QListWidget current row has changed
Args:
index: index
"""
if index == -1:
return
item = self.items[index]
if not item.can_select():
item = None
if item is None:
self.plot.replot()
def selection_changed(self) -> None:
"""Selection has changed"""
items = [item for item in self.get_selected_items() if item.can_select()]
self.plot.select_some_items(items)
self.plot.replot()
def item_changed(self, listwidgetitem: QListWidgetItem) -> None:
"""QListWidget item has changed
Args:
listwidgetitem: list widget item
"""
item = self.items[self.row(listwidgetitem)]
visible = listwidgetitem.checkState() == QC.Qt.Checked
if visible != item.isVisible():
self.plot.set_item_visible(item, visible)
def move_item(self, direction: str) -> None:
"""Move item to the background/foreground
Works only for contiguous selection
-> 'refresh_actions' method should guarantee that
Args:
direction: direction
"""
items = self.get_selected_items()
if direction == "up":
self.plot.move_up(items)
else:
self.plot.move_down(items)
# Re-select items which can't be selected in plot widget but can be
# selected in ItemListWidget:
for item in items:
lw_item = self.item(self.items.index(item))
if not lw_item.isSelected():
lw_item.setSelected(True)
self.plot.replot()
def remove_item(self) -> None:
"""Remove item"""
if len(self.selectedItems()) == 1:
message = _("Do you really want to remove this item?")
else:
message = _("Do you really want to remove selected items?")
answer = QW.QMessageBox.warning(
self, _("Remove"), message, QW.QMessageBox.Yes | QW.QMessageBox.No
)
if answer == QW.QMessageBox.Yes:
items = self.get_selected_items()
self.plot.del_items(items)
self.plot.replot()
[docs]
class PlotItemList(PanelWidget):
"""Construct the `plot item list panel`"""
__implements__ = (IPanel,)
PANEL_ID = ID_ITEMLIST
PANEL_TITLE = _("Item list")
PANEL_ICON = "item_list.png"
def __init__(self, parent):
super().__init__(parent)
self.manager = None
vlayout = QW.QVBoxLayout()
self.setLayout(vlayout)
style = "<span style='color: #444444'><b>%s</b></span>"
layout, _label = get_image_layout(
self.PANEL_ICON, style % self.PANEL_TITLE, alignment=QC.Qt.AlignCenter
)
vlayout.addLayout(layout)
self.listwidget = ItemListWidget(self)
vlayout.addWidget(self.listwidget)
toolbar = QW.QToolBar(self)
vlayout.addWidget(toolbar)
add_actions(toolbar, self.listwidget.menu_actions)
def register_panel(self, manager: PlotManager) -> None:
"""Register panel to plot manager
Args:
manager: plot manager
"""
self.manager = manager
self.listwidget.register_panel(manager)
def configure_panel(self):
"""Configure panel"""
pass
assert_interfaces_valid(PlotItemList)