Repository URL to install this package:
Version:
4.8.0 ▾
|
from __future__ import with_statement
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, on_trait_change, 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(PlotToolbarHover, self).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(PlotToolbar, self).__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
return
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
return
############################################################
# Trait handlers
############################################################
@on_trait_change('components, location')
def _calculate_width(self):
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()
@on_trait_change('components, location')
def _calculate_height(self):
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()
@on_trait_change('hiding')
def _hiding_changed(self):
self._layout_needed = True
self.request_redraw()
@on_trait_change('auto_hide')
def _auto_hide_changed(self):
self.hiding = self.auto_hide
self.request_redraw()