# -*- coding: utf-8 -*-
#
# Licensed under the terms of the BSD 3-Clause
# (see plotpy/LICENSE for details)
from __future__ import annotations
from typing import TYPE_CHECKING
from guidata.utils.misc import assert_interfaces_valid
from qwt import QwtPlotItem
from plotpy.coords import canvas_to_axes
from plotpy.interfaces import IBasePlotItem, IShapeItemType
if TYPE_CHECKING:
from qtpy.QtCore import QPointF # helping out python_qt_documentation
from plotpy.interfaces import IItemType
from plotpy.plot import BasePlot
from plotpy.styles.base import ItemParameters
[docs]
class AbstractShape(QwtPlotItem):
"""Abstract shape class"""
__implements__ = (IBasePlotItem,)
_readonly = False
_private = False
_can_select = True
_can_resize = True
_can_rotate = False # TODO: Implement shape rotation?
_can_move = True
def __init__(self) -> None:
super().__init__()
self.selected = False
[docs]
def plot(self) -> BasePlot | None:
"""Return the plot this item belongs to
Returns:
Plot this item belongs to, or None if not attached to any
"""
return super().plot()
# ------IBasePlotItem API----------------------------------------------------
[docs]
def get_icon_name(self) -> str:
"""Return the icon name
Returns:
Icon name
"""
return self._icon_name
[docs]
def set_icon_name(self, icon_name: str) -> None:
"""Set the icon name
Args:
icon_name: Icon name
"""
self._icon_name = icon_name
[docs]
def set_selectable(self, state: bool) -> None:
"""Set item selectable state
Args:
state: True if item is selectable, False otherwise
"""
self._can_select = state
[docs]
def set_resizable(self, state: bool) -> None:
"""Set item resizable state
(or any action triggered when moving an handle, e.g. rotation)
Args:
state: True if item is resizable, False otherwise
"""
self._can_resize = state
[docs]
def set_movable(self, state: bool) -> None:
"""Set item movable state
Args:
state: True if item is movable, False otherwise
"""
self._can_move = state
[docs]
def set_rotatable(self, state: bool) -> None:
"""Set item rotatable state
Args:
state: True if item is rotatable, False otherwise
"""
self._can_rotate = state
[docs]
def can_select(self) -> bool:
"""
Returns True if this item can be selected
Returns:
bool: True if item can be selected, False otherwise
"""
return self._can_select
[docs]
def can_resize(self) -> bool:
"""
Returns True if this item can be resized
Returns:
bool: True if item can be resized, False otherwise
"""
return self._can_resize
[docs]
def can_rotate(self) -> bool:
"""
Returns True if this item can be rotated
Returns:
bool: True if item can be rotated, False otherwise
"""
return self._can_rotate
[docs]
def can_move(self) -> bool:
"""
Returns True if this item can be moved
Returns:
bool: True if item can be moved, False otherwise
"""
return self._can_move
[docs]
def types(self) -> tuple[type[IItemType], ...]:
"""Returns a group or category for this item.
This should be a tuple of class objects inheriting from IItemType
Returns:
tuple: Tuple of class objects inheriting from IItemType
"""
return (IShapeItemType,)
[docs]
def set_readonly(self, state: bool) -> None:
"""Set object readonly state
Args:
state: True if object is readonly, False otherwise
"""
self._readonly = state
[docs]
def is_readonly(self) -> bool:
"""Return object readonly state
Returns:
bool: True if object is readonly, False otherwise
"""
return self._readonly
[docs]
def set_private(self, state: bool) -> None:
"""Set object as private
Args:
state: True if object is private, False otherwise
"""
self._private = state
[docs]
def is_private(self) -> bool:
"""Return True if object is private
Returns:
bool: True if object is private, False otherwise
"""
return self._private
[docs]
def select(self) -> None:
"""
Select the object and eventually change its appearance to highlight the
fact that it's selected
"""
self.selected = True
self.invalidate_plot()
[docs]
def unselect(self) -> None:
"""
Unselect the object and eventually restore its original appearance to
highlight the fact that it's not selected anymore
"""
self.selected = False
self.invalidate_plot()
[docs]
def hit_test(self, pos: QPointF) -> tuple[float, float, bool, None]:
"""Return a tuple (distance, attach point, inside, other_object)
Args:
pos: Position
Returns:
tuple: Tuple with four elements: (distance, attach point, inside,
other_object).
Description of the returned values:
* distance: distance in pixels (canvas coordinates) to the closest
attach point
* attach point: handle of the attach point
* inside: True if the mouse button has been clicked inside the object
* other_object: if not None, reference of the object which will be
considered as hit instead of self
"""
pass
[docs]
def update_item_parameters(self) -> None:
"""Update item parameters (dataset) from object properties"""
pass
[docs]
def get_item_parameters(self, itemparams: ItemParameters) -> None:
"""
Appends datasets to the list of DataSets describing the parameters
used to customize apearance of this item
Args:
itemparams: Item parameters
"""
[docs]
def set_item_parameters(self, itemparams):
"""
Change the appearance of this item according
to the parameter set provided
params is a list of Datasets of the same types as those returned
by get_item_parameters
"""
pass
[docs]
def move_local_point_to(
self, handle: int, pos: QPointF, ctrl: bool = False
) -> None:
"""Move a handle as returned by hit_test to the new position
Args:
handle: Handle
pos: Position
ctrl: True if <Ctrl> button is being pressed, False otherwise
"""
pt = canvas_to_axes(self, pos)
self.move_point_to(handle, pt, ctrl)
plot: BasePlot = self.plot()
if plot is not None:
plot.SIG_ITEM_RESIZED.emit(self, 0, 0)
plot.SIG_ITEM_HANDLE_MOVED.emit(self)
[docs]
def move_local_shape(self, old_pos: QPointF, new_pos: QPointF) -> None:
"""Translate the shape such that old_pos becomes new_pos in canvas coordinates
Args:
old_pos: Old position
new_pos: New position
"""
old_pt = canvas_to_axes(self, old_pos)
new_pt = canvas_to_axes(self, new_pos)
self.move_shape(old_pt, new_pt)
plot: BasePlot = self.plot()
if plot is not None:
plot.SIG_ITEM_MOVED.emit(self, *(old_pt + new_pt))
[docs]
def move_with_selection(self, delta_x: float, delta_y: float) -> None:
"""Translate the item together with other selected items
Args:
delta_x: Translation in plot coordinates along x-axis
delta_y: Translation in plot coordinates along y-axis
"""
self.move_shape([0, 0], [delta_x, delta_y])
# ------Public API-----------------------------------------------------------
[docs]
def move_point_to(
self, handle: int, pos: tuple[float, float], ctrl: bool = False
) -> None:
"""Move a handle as returned by hit_test to the new position
Args:
handle: Handle
pos: Position
ctrl: True if <Ctrl> button is being pressed, False otherwise
"""
pass
[docs]
def move_shape(self, old_pos: QPointF, new_pos: QPointF) -> None:
"""Translate the shape such that old_pos becomes new_pos in axis coordinates
Args:
old_pos: Old position
new_pos: New position
"""
pass
[docs]
def invalidate_plot(self) -> None:
"""Invalidate the plot to force a redraw"""
plot: BasePlot = self.plot()
if plot is not None:
plot.invalidate()
[docs]
def is_empty(self) -> bool:
"""Return True if the item is empty
Returns:
True if the item is empty, False otherwise
"""
return False
assert_interfaces_valid(AbstractShape)