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 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
|
# Major library imports
from numpy import dot
# Enthought library imports
from enthought.traits.api import Any, Bool, Event, HasTraits, Instance, \
Property, ReadOnly, Trait, Tuple, List
# Local relative imports
from base import bounds_to_coordinates, does_disjoint_intersect_coordinates, \
union_bounds
from component import Component
from interactor import Interactor
from container import Container
from colors import ColorTrait
def Alias(name):
return Property(lambda obj: getattr(obj, name),
lambda obj, val: setattr(obj, name, val))
class AbstractWindow(HasTraits):
# The top-level component that this window houses
component = Instance(Component)
# A reference to the nested component that has focus. This is part of the
# manual mechanism for determining keyboard focus.
focus_owner = Instance(Interactor)
# If set, this is the component to which all mouse events are passed,
# bypassing the normal event propagation mechanism.
mouse_owner = Instance(Interactor)
# The transform to apply to mouse event positions to put them into the
# relative coordinates of the mouse_owner component.
mouse_owner_transform = Any()
# When a component captures the mouse, it can optionally store a
# dispatch order for events (until it releases the mouse).
mouse_owner_dispatch_history = Trait(None, None, List)
# The background window of the window. The entire window first gets
# painted with this color before the component gets to draw.
bgcolor = ColorTrait("sys_window")
# Unfortunately, for a while, there was a naming inconsistency and the
# background color trait named "bg_color". This is still provided for
# backwards compatibility but should not be used in new code.
bg_color = Alias("bgcolor")
window = ReadOnly
alt_pressed = Bool(False)
ctrl_pressed = Bool(False)
shift_pressed = Bool(False)
# A container that gets drawn after & on top of the main component, and
# which receives events first.
overlay = Instance(Container)
# When the underlying toolkit control gets resized, this event gets set
# to the new size of the window, expressed as a tuple (dx, dy).
resized = Event
# Whether to enable damaged region handling
use_damaged_region = Bool(False)
# The previous component that handled an event. Used to generate
# mouse_enter and mouse_leave events. Right now this can only be
# None, self.component, or self.overlay.
_prev_event_handler = Instance(Component)
# (dx, dy) integer size of the Window.
_size = Trait(None, Tuple)
# The regions to update upon redraw
_update_region = Any
#---------------------------------------------------------------------------
# Abstract methods that must be implemented by concrete subclasses
#---------------------------------------------------------------------------
def set_drag_result(self, result):
""" Sets the result that should be returned to the system from the
handling of the current drag operation. Valid result values are:
"error", "none", "copy", "move", "link", "cancel". These have the
meanings associated with their WX equivalents.
"""
raise NotImplementedError
def _capture_mouse(self):
"Capture all future mouse events"
raise NotImplementedError
def _release_mouse(self):
"Release the mouse capture"
raise NotImplementedError
def _create_mouse_event(self, event):
"Convert a GUI toolkit mouse event into a MouseEvent"
raise NotImplementedError
def _redraw(self, coordinates=None):
""" Request a redraw of the window, within just the (x,y,w,h) coordinates
(if provided), or over the entire window if coordinates is None.
"""
raise NotImplementedError
def _get_control_size(self):
"Get the size of the underlying toolkit control"
raise NotImplementedError
def _create_gc(self, size, pix_format = "bgr24"):
""" Create a Kiva graphics context of a specified size. This method
only gets called when the size of the window itself has changed. To
perform pre-draw initialization every time in the paint loop, use
_init_gc().
"""
raise NotImplementedError
def _init_gc(self):
""" Gives a GC a chance to initialize itself before components perform
layout and draw. This is called every time through the paint loop.
"""
gc = self._gc
if self._update_region == [] or not self.use_damaged_region:
self._update_region = None
if self._update_region is None:
gc.clear(self.bgcolor_)
else:
# Fixme: should use clip_to_rects
update_union = reduce(union_bounds, self._update_region)
gc.clip_to_rect(*update_union)
return
def _window_paint(self, event):
"Do a GUI toolkit specific screen update"
raise NotImplementedError
def set_pointer(self, pointer):
"Sets the current cursor shape"
raise NotImplementedError
def set_timer_interval(self, component, interval):
"Set up or cancel a timer for a specified component"
raise NotImplementedError
def _set_focus(self):
"Sets this window to have keyboard focus"
raise NotImplementedError
def screen_to_window(self, x, y):
"Returns local window coordinates for given global screen coordinates"
raise NotImplementedError
#------------------------------------------------------------------------
# Public methods
#------------------------------------------------------------------------
def __init__(self, **traits):
self.window = self
self._scroll_origin = (0.0, 0.0)
self._update_region = None
self._gc = None
self._pointer_owner = None
HasTraits.__init__(self, **traits)
# Create a default component (if necessary):
if self.component is None:
self.component = Container()
return
def _component_changed(self, old, new):
if old is not None:
old.on_trait_change(self.component_bounds_changed, 'bounds', remove=True)
old.window = None
if new is None:
self.component = Container()
return
new.window = self
# If possible, size the new component according to the size of the
# toolkit control
size = self._get_control_size()
if (size is not None) and hasattr(self.component, "bounds"):
new.on_trait_change(self.component_bounds_changed, 'bounds')
if getattr(self.component, "fit_window", False):
self.component.outer_position = [0,0]
self.component.outer_bounds = list(size)
elif hasattr(self.component, "resizable"):
if "h" in self.component.resizable:
self.component.outer_x = 0
self.component.outer_width = size[0]
if "v" in self.component.resizable:
self.component.outer_y = 0
self.component.outer_height = size[1]
self._update_region = None
self.redraw()
return
def component_bounds_changed(self, bounds):
"""
Dynamic trait listener that handles our component changing its size;
bounds is a length-2 list of [width, height].
"""
self.invalidate_draw()
pass
def set_mouse_owner(self, mouse_owner, transform=None, history=None):
"Handle the 'mouse_owner' being changed"
if mouse_owner is None:
self._release_mouse()
self.mouse_owner = None
self.mouse_owner_transform = None
self.mouse_owner_dispatch_history = None
else:
self._capture_mouse()
self.mouse_owner = mouse_owner
self.mouse_owner_transform = transform
self.mouse_owner_dispatch_history = history
return
def invalidate_draw(self, damaged_regions=None, self_relative=False):
if damaged_regions is not None and self._update_region is not None:
self._update_region += damaged_regions
else:
self._update_region = None
# print damaged_regions
#---------------------------------------------------------------------------
# Generic mouse event handler:
#---------------------------------------------------------------------------
def _handle_mouse_event(self, event_name, event, set_focus=False):
""" **event** should be a toolkit-specific opaque object that will
be passed in to the backend's _create_mouse_event() method. It can
be None if the the toolkit lacks a native "mouse event" object.
Returns True if the event has been handled within the Enable object
hierarchy, or False otherwise.
"""
if self._size is None:
# PZW: Hack!
# We need to handle the cases when the window hasn't been painted yet, but
# it's gotten a mouse event. In such a case, we just ignore the mouse event.
# If the window has been painted, then _size will have some sensible value.
return False
mouse_event = self._create_mouse_event(event)
mouse_owner = self.mouse_owner
if mouse_owner is not None:
# A mouse_owner has grabbed the mouse. Check to see if we need to
# compose a net transform by querying each of the objects in the
# dispatch history in turn, or if we can just apply a saved top-level
# transform.
history = self.mouse_owner_dispatch_history
if history is not None and len(history) > 0:
# Assemble all the transforms
transforms = [c.get_event_transform() for c in history]
total_transform = reduce(dot, transforms[::-1])
mouse_event.push_transform(total_transform)
elif self.mouse_owner_transform is not None:
mouse_event.push_transform(self.mouse_owner_transform)
mouse_owner.dispatch(mouse_event, event_name)
self._pointer_owner = mouse_owner
else:
# Normal event handling loop
if self.overlay is not None:
# TODO: implement this...
pass
if (not mouse_event.handled) and (self.component is not None):
# Test to see if we need to generate a mouse_leave event
if self._prev_event_handler:
if not self._prev_event_handler.is_in(mouse_event.x, mouse_event.y):
self._prev_event_handler.dispatch(mouse_event, "pre_mouse_leave")
mouse_event.handled = False
self._prev_event_handler.dispatch(mouse_event, "mouse_leave")
self._prev_event_handler = None
if self.component.is_in(mouse_event.x, mouse_event.y):
# Test to see if we need to generate a mouse_enter event
if self._prev_event_handler != self.component:
self._prev_event_handler = self.component
self.component.dispatch(mouse_event, "pre_mouse_enter")
mouse_event.handled = False
self.component.dispatch(mouse_event, "mouse_enter")
# Fire the actual event
self.component.dispatch(mouse_event, "pre_" + event_name)
mouse_event.handled = False
self.component.dispatch(mouse_event, event_name)
# If this event requires setting the keyboard focus, set the first
# component under the mouse pointer that accepts focus as the new focus
# owner (otherwise, nobody owns the focus):
if set_focus:
# If the mouse event was a click, then we set the toolkit's
# focus to ourselves
if mouse_event.left_down or mouse_event.middle_down or \
mouse_event.right_down or mouse_event.mouse_wheel != 0:
self._set_focus()
if (self.component is not None) and (self.component.accepts_focus):
if self.focus_owner is None:
self.focus_owner = self.component
else:
pass
return mouse_event.handled
def set_tooltip(self, components):
"Set the window's tooltip (if necessary)"
raise NotImplementedError
def redraw(self):
""" Requests that the window be redrawn. """
self._redraw()
return
def _needs_redraw(self, bounds):
"Determine if a specified region intersects the update region"
return does_disjoint_intersect_coordinates( self._update_region,
bounds_to_coordinates( bounds ) )
def _paint(self, event=None):
""" This method is called directly by the UI toolkit's callback
mechanism on the paint event.
"""
# Create a new GC if necessary
size = self._get_control_size()
if (self._size != tuple(size)) or (self._gc is None):
self._size = tuple(size)
self._gc = self._create_gc(size)
# Always give the GC a chance to initialize
self._init_gc()
# Layout components and draw
if hasattr(self.component, "do_layout"):
self.component.do_layout()
gc = self._gc
self.component.draw(gc, view_bounds=(0, 0, size[0], size[1]))
# damaged_regions = draw_result['damaged_regions']
# FIXME: consolidate damaged regions if necessary
if not self.use_damaged_region:
self._update_region = None
# Perform a paint of the GC to the window (only necessary on backends
# that render to an off-screen buffer)
self._window_paint(event)
self._update_region = []
return
def __getstate__(self):
attribs = ("component", "bgcolor", "overlay", "_scroll_origin")
state = {}
for attrib in attribs:
state[attrib] = getattr(self, attrib)
return state
#---------------------------------------------------------------------------
# Wire up the mouse event handlers
#---------------------------------------------------------------------------
def _on_left_down ( self, event ):
self._handle_mouse_event( 'left_down', event, set_focus = True )
def _on_left_up ( self, event ):
self._handle_mouse_event( 'left_up', event )
def _on_left_dclick ( self, event ):
self._handle_mouse_event( 'left_dclick', event )
def _on_right_down ( self, event ):
self._handle_mouse_event( 'right_down', event, set_focus = True )
def _on_right_up ( self, event ):
self._handle_mouse_event( 'right_up', event )
def _on_right_dclick ( self, event ):
self._handle_mouse_event( 'right_dclick', event )
def _on_middle_down ( self, event ):
self._handle_mouse_event( 'middle_down', event )
def _on_middle_up ( self, event ):
self._handle_mouse_event( 'middle_up', event )
def _on_middle_dclick ( self, event ):
self._handle_mouse_event( 'middle_dclick', event )
def _on_mouse_move ( self, event ):
self._handle_mouse_event( 'mouse_move', event, 1 )
def _on_mouse_wheel ( self, event ):
self._handle_mouse_event( 'mouse_wheel', event )
def _on_mouse_enter ( self, event ):
self._handle_mouse_event( 'mouse_enter', event )
def _on_mouse_leave ( self, event ):
self._handle_mouse_event( 'mouse_leave', event, -1 )
# Additional event handlers that are not part of normal Interactors
def _on_window_enter(self, event):
# TODO: implement this to generate a mouse_leave on self.component
pass
def _on_window_leave(self, event):
if self._prev_event_handler:
mouse_event = self._create_mouse_event(event)
self._prev_event_handler.dispatch(mouse_event, "mouse_leave")
self._prev_event_handler = None
return
# EOF
|