1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
|
#
# (C) Copyright 2012 Enthought, Inc., Austin, TX
# All right reserved.
#
# This file is open source software distributed according to the terms in
# LICENSE.txt
#
"""
Value Drag Tools
================
This module contains tools to handle simple dragging interactions where the
drag operation has a direct effect on some underlying value. This can
potentially be used as the basis for many different interactions,
"""
from traits.api import Str, Float, Set, Enum, Bool, Tuple, Dict, Event, Any, Either
from enable.tools.api import DragTool
keys = set(['shift', 'alt', 'control'])
class IdentityMapper(object):
def map_data(self, screen):
return screen
def map_screen(self, data):
return data
identity_mapper = IdentityMapper()
class ValueDragTool(DragTool):
""" Abstract tool for modifying a value as the mouse is dragged
The tool allows the use of an x_mapper and y_mapper to map between
screen coordinates and more abstract data coordinates. These mappers must
be objects with a map_data() method that maps a component-space coordinate
to a data-space coordinate. Chaco mappers satisfy the required API, and the
tool will look for 'x_mapper' and 'y_mapper' attributes on the component
to use as the defaults, facilitating interoperability with Chaco plots.
Failing that, a simple identity mapper is provided which does nothing.
Coordinates are given relative to the component.
Subclasses of this tool need to supply get_value() and set_delta() methods.
The get_value method returns the current underlying value, while the
set_delta method takes the current mapped x and y deltas from the original
position, and sets the underlying value appropriately. The object stores
the original value at the start of the operation as the original_value
attribute.
"""
#: set of modifier keys that must be down to invoke the tool
modifier_keys = Set(Enum(*keys))
#: mapper that maps from horizontal screen coordinate to data coordinate
x_mapper = Any
#: mapper that maps from vertical screen coordinate to data coordinate
y_mapper = Any
#: start point of the drag in component coordinates
original_screen_point = Tuple(Float, Float)
#: start point of the drag in data coordinates
original_data_point = Tuple(Any, Any)
#: initial underlying value
original_value = Any
#: new_value event for inspector overlay
new_value = Event(Dict)
#: visibility for inspector overlay
visible = Bool(False)
def get_value(self):
""" Return the current value that is being modified
"""
pass
def set_delta(self, value, delta_x, delta_y):
""" Set the value that is being modified
This function should modify the underlying value based on the provided
delta_x and delta_y in data coordinates. These deltas are total
displacement from the original location, not incremental. The value
parameter is the original value at the point where the drag started.
"""
pass
# Drag tool API
def drag_start(self, event):
self.original_screen_point = (event.x, event.y)
data_x = self.x_mapper.map_data(event.x)
data_y = self.y_mapper.map_data(event.y)
self.original_data_point = (data_x, data_y)
self.original_value = self.get_value()
self.visible = True
return True
def dragging(self, event):
position = event.window.get_pointer_position()
delta_x = self.x_mapper.map_data(position[0]) - self.original_data_point[0]
delta_y = self.y_mapper.map_data(position[1]) - self.original_data_point[1]
self.set_delta(self.original_value, delta_x, delta_y)
return True
def drag_end(self, event):
event.window.set_pointer("arrow")
self.visible = False
return True
def _drag_button_down(self, event):
# override button down to handle modifier keys correctly
if not event.handled and self._drag_state == "nondrag":
key_states = dict((key, key in self.modifier_keys) for key in keys)
if not all(getattr(event, key+'_down') == state for key, state in key_states.items()):
return False
self.mouse_down_position = (event.x, event.y)
if not self.is_draggable(*self.mouse_down_position):
self._mouse_down_recieved = False
return False
self._mouse_down_received = True
return True
return False
# traits default handlers
def _x_mapper_default(self):
# if the component has an x_mapper, try to sue it by default
return getattr(self.component, 'x_mapper', identity_mapper)
def _y_mapper_default(self):
# if the component has an x_mapper, try to sue it by default
return getattr(self.component, 'y_mapper', identity_mapper)
class AttributeDragTool(ValueDragTool):
""" Tool which modifies a model's attributes as it drags
This is designed to cover the simplest of drag cases where the drag is
modifying one or two numerical attributes on an underlying model. To use,
simply provide the model object and the attributes that you want to be
changed by the drag. If only one attribute is required, the other can be
left as an empty string.
"""
#: the model object which has the attributes we are modifying
model = Any
#: the name of the attributes that is modified by horizontal motion
x_attr = Str
#: the name of the attributes that is modified by vertical motion
y_attr = Str
#: max and min values for x value
x_bounds = Tuple(Either(Float, Str, None), Either(Float, Str, None))
#: max and min values for y value
y_bounds = Tuple(Either(Float,Str, None), Either(Float, Str, None))
x_name = Str
y_name = Str
# ValueDragTool API
def get_value(self):
""" Get the current value of the attributes
Returns a 2-tuple of (x, y) values. If either x_attr or y_attr is
the empty string, then the corresponding component of the tuple is
None.
"""
x_value = None
y_value = None
if self.x_attr:
x_value = getattr(self.model, self.x_attr)
if self.y_attr:
y_value = getattr(self.model, self.y_attr)
return (x_value, y_value)
def set_delta(self, value, delta_x, delta_y):
""" Set the current value of the attributes
Set the underlying attribute values based upon the starting value and
the provided deltas. The values are simply set to the sum of the
appropriate coordinate and the delta. If either x_attr or y_attr is
the empty string, then the corresponding component of is ignored.
Note that setting x and y are two separate operations, and so will fire
two trait notification events.
"""
inspector_value = {}
if self.x_attr:
x_value = value[0] + delta_x
if self.x_bounds[0] is not None:
if isinstance(self.x_bounds[0], basestring):
m = getattr(self.model, self.x_bounds[0])
else:
m = self.x_bounds[0]
x_value = max(x_value, m)
if self.x_bounds[1] is not None:
if isinstance(self.x_bounds[1], basestring):
M = getattr(self.model, self.x_bounds[1])
else:
M = self.x_bounds[1]
x_value = min(x_value, M)
setattr(self.model, self.x_attr, x_value)
inspector_value[self.x_name] = x_value
if self.y_attr:
y_value = value[1] + delta_y
if self.y_bounds[0] is not None:
if isinstance(self.y_bounds[0], basestring):
m = getattr(self.model, self.y_bounds[0])
else:
m = self.y_bounds[0]
y_value = max(y_value, m)
if self.y_bounds[1] is not None:
if isinstance(self.y_bounds[1], basestring):
M = getattr(self.model, self.y_bounds[1])
else:
M = self.y_bounds[1]
y_value = min(y_value, M)
setattr(self.model, self.y_attr, y_value)
inspector_value[self.y_name] = y_value
self.new_value = inspector_value
def _x_name_default(self):
return self.x_attr.replace('_', ' ').capitalize()
def _y_name_default(self):
return self.y_attr.replace('_', ' ').capitalize()
|