Repository URL to install this package:
Version:
5.0.0 ▾
|
# (C) Copyright 2005-2021 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
import numpy
from chaco.abstract_overlay import AbstractOverlay
from chaco.tools.toolbars.toolbar_buttons import (
ToolbarButton,
IndexAxisLogButton,
ValueAxisLogButton,
SaveAsButton,
CopyToClipboardButton,
ZoomResetButton,
ExportDataToClipboardButton,
)
from enable.api import Container
from enable.tools.api import HoverTool
from traits.api import Bool, Float, observe, List, Tuple, Type, Enum
class PlotToolbarHover(HoverTool):
_last_xy = Tuple()
def _is_in(self, x, y):
return self.component.is_in(x, y)
def normal_mouse_move(self, event):
self._last_xy = (event.x, event.y)
super().normal_mouse_move(event)
def on_hover(self):
"""This gets called when all the conditions of the hover action have
been met, and the tool determines that the mouse is, in fact, hovering
over a target region on the component.
By default, this method call self.callback (if one is configured).
"""
for component in self.component.components:
if component.is_in(*self._last_xy):
self.callback(component.label)
return
self.callback("")
class PlotToolbar(Container, AbstractOverlay):
"""A toolbar for embedding buttons in"""
buttons = List(Type(ToolbarButton))
#: Should the toolbar be hidden
hiding = Bool(True)
#: should the toolbar go automatically go back into hiding when the mouse
#: is not hovering over it
auto_hide = Bool(True)
#: the radius used to determine how round to make the toolbar's edges
end_radius = Float(4.0)
#: button spacing is defined as the number of pixels on either side of
#: a button. The gap between 2 buttons will be 2 x the button spacing
button_spacing = Float(5.0)
#: how many pixels to put before and after the set of buttons
horizontal_padding = Float(5.0)
#: how many pixels to put on top and bottom the set of buttons
vertical_padding = Float(5.0)
#: The edge against which the toolbar is placed.
location = Enum("top", "right", "bottom", "left")
#: Should tooltips be shown?
show_tooltips = Bool(False)
############################################################
# PlotToolbar API
############################################################
def __init__(self, component=None, *args, **kw):
super().__init__(*args, **kw)
self.component = component
if component is not None and hasattr(component, "toolbar_location"):
self.location = component.toolbar_location
for buttontype in self.buttons:
self.add_button(buttontype())
hover_tool = PlotToolbarHover(component=self, callback=self.on_hover)
self.tools.append(hover_tool)
if self.location in ["top", "bottom"]:
self._calculate_width()
else:
self._calculate_height()
def _buttons_default(self):
return [
IndexAxisLogButton,
ValueAxisLogButton,
SaveAsButton,
CopyToClipboardButton,
ExportDataToClipboardButton,
ZoomResetButton,
]
def add_button(self, button):
"""adds a button to the toolbar"""
self.add(button)
button.toolbar_overlay = self
self._layout_needed = True
def normal_mouse_move(self, event):
"""handler for normal mouse move"""
self.on_hover("")
if self.hiding:
self.hiding = False
def on_hover(self, tooltip):
if self.show_tooltips:
self.component.window.set_tooltip(tooltip)
def normal_left_down(self, event):
"""handler for a left mouse click"""
if self.hiding:
return
else:
for button in self.components:
if button.is_in(event.x, event.y):
button.perform(event)
event.handled = True
break
############################################################
# AbstractOverlay API
############################################################
def overlay(self, other_component, gc, view_bounds=None, mode="normal"):
"""Draws this component overlaid on another component."""
starting_color = numpy.array([0.0, 1.0, 1.0, 1.0, 0.5])
ending_color = numpy.array([1.0, 0.0, 0.0, 0.0, 0.5])
x = self.x
y = self.y
height = self.height
with gc:
gc.begin_path()
gc.move_to(x + self.end_radius, y)
gc.arc_to(
x + self.width,
y,
x + self.width,
y + self.end_radius,
self.end_radius,
)
gc.arc_to(
x + self.width,
y + height,
x + self.width - self.end_radius,
y + height,
self.end_radius,
)
gc.arc_to(
x, y + height, x, y + height - self.end_radius, self.end_radius
)
gc.arc_to(x, y, x + self.end_radius, y, self.end_radius)
if self.location in ["top", "bottom"]:
gc.linear_gradient(
x,
y,
x,
y + 100,
numpy.array([starting_color, ending_color]),
"pad",
)
else:
gc.linear_gradient(
x,
y,
x + 100,
y,
numpy.array([starting_color, ending_color]),
"pad",
)
gc.draw_path()
if not self.hiding:
for button in self.components:
button.draw(gc)
def is_in(self, x, y):
if (x >= self.x and x <= self.x2) and (y >= self.y and y <= self.y2):
return True
return False
def _do_layout(self, component=None):
if component is None:
component = self.component
if self.location in ["top", "bottom"]:
if self.hiding:
self.height = height = 10
else:
tallest_button = max(
[button.height for button in self.components]
)
self.height = height = (
tallest_button + self.vertical_padding * 2
)
else:
if self.hiding:
self.width = width = 10
else:
widest_button = max(
[button.width for button in self.components]
)
self.width = width = (
widest_button + self.horizontal_padding * 2
)
if component is not None:
# Overlay positions are not relative to the component's position,
# so we have to add in the component's position
cx, cy = component.outer_position
if self.location is "top":
self.x = (
cx
+ (component.width - self.width) / 2
+ component.padding_left
)
self.y = (
cy
+ component.height
+ component.padding_bottom
- height
- 2
)
elif self.location is "bottom":
self.x = (
cx
+ (component.width - self.width) / 2
+ component.padding_left
)
self.y = cy + component.padding_bottom + 2
elif self.location is "left":
self.x = cx + component.padding_left + 2
self.y = (
cy
+ (component.height - self.height) / 2
+ component.padding_bottom
)
else: # 'right'
self.x = (
cx + component.width + component.padding_left - width - 2
)
self.y = (
cy
+ (component.height - self.height) / 2
+ component.padding_bottom
)
if self.location in ["top", "bottom"]:
v_position = self.y + self.vertical_padding * 2
last_button_position = (
self.x + self.horizontal_padding + self.button_spacing
)
for button in self.components:
button.x = last_button_position
button.y = v_position
last_button_position += button.width + self.button_spacing * 2
else:
# location is 'left' or 'right'
h_position = self.x + self.horizontal_padding
last_button_position = (
self.y + self.vertical_padding + self.button_spacing
)
for button in reversed(self.components):
h_offset = (self.width - button.width) / 2
button.y = last_button_position
button.x = h_position + h_offset
last_button_position += button.height + self.button_spacing * 2
def _dispatch_stateful_event(self, event, suffix):
if self.is_in(event.x, event.y):
if suffix == "mouse_move":
self.normal_mouse_move(event)
elif suffix == "left_down":
self.normal_left_down(event)
event.handled = True
else:
if self.auto_hide:
self.hiding = True
############################################################
# Trait handlers
############################################################
@observe("components, location")
def _calculate_width(self, event=None):
if self.location in ["top", "bottom"]:
width = self.horizontal_padding * 2
for button in self.components:
width += button.width + self.button_spacing * 2
self.width = max(10, width)
self._layout_needed = True
self.request_redraw()
@observe("components, location")
def _calculate_height(self, event=None):
if self.location in ["left", "right"]:
height = self.vertical_padding * 2
for button in self.components:
height += button.height + self.button_spacing * 2
self.height = max(10, height)
self._layout_needed = True
self.request_redraw()
@observe("hiding")
def _set_layout_needed_and_redraw(self, event):
self._layout_needed = True
self.request_redraw()
@observe("auto_hide")
def _set_hiding_and_redraw(self, event):
self.hiding = self.auto_hide
self.request_redraw()