<commit_before>import inspect
import time

from pyglet.event import (EventDispatcher, EVENT_UNHANDLED, EVENT_HANDLED,
    EventException)
from pyglet.window import key

from layout.css import Rule, RuleSet, Selector, SimpleSelector

# partially snarfed from layout.gl.event
# Instead of each stack layer being a dictionary mapping event-name to
# event-function, each layer is a dictionary mapping event-name to 
# RuleSet

'''
Events:

    Passed through if handled:

        on_mouse_motion(self, x, y, dx, dy):
        on_mouse_press(self, x, y, button, modifiers):
        on_mouse_release(self, x, y, button, modifiers):
        on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        on_mouse_scroll(self, x, y, dx, dy):
        on_key_press(self, symbol, modifiers):
        on_text(self, text):
        on_text_motion(self, motion):
        on_text_motion_select(self, motion):
  
    New:

        on_change(element, value)
           the element's "value" changed (eg. text in a text input)

        on_click(element, x, y, buttons, modifiers, click_count)
           the element was clicked. the click_count argument indicates how
           many times the element has been clicked in rapid succession.

        on_drag(element, x, y, dx, dy, buttons, modifiers)
           press on listening element followed by mouse movement

        on_element_enter(element, x, y)
           the mouse is over the element

        on_element_leave(element, x, y)
           the mouse is no longer over the element

        on_drag_enter(element, x, y, dragged_element)
           the dragged_element is being dragged over the stationary element

        on_drag_leave(element, x, y, dragged_element)
           the dragged_element is no longer being dragged over the
           stationary element

        on_drag_complete(element, x, y, buttons, modifiers, ok)
           release after dragging listening element, ok is return code
           from dropped-on element's on_element_drop

        on_drop(element, x, y, button, modifiers, element)
            element drag-n-dropped on listening element

        on_element_enter(element, x, y)
            mouse has entered listening element's rect

        on_element_leave(element, x, y)
            mouse has left listening element's rect

        on_gain_focus(element)
            listening element gains focus

        on_lose_focus(element)
            listening element loses focus
'''

POSITIONAL_EVENTS = set('on_mouse_motion on_mouse_press on_mouse_release on_mouse_drag on_mouse_scroll on_drag on_element_enter on_element_leave on_drag_enter on_drag_leave on_drag_complete on_drop on_enter on_leave'.split())

class GUIEventDispatcher(EventDispatcher):

    default_event_handlers = {}

    def __init__(self):
        EventDispatcher.__init__(self)
        assert isinstance(self._event_stack, tuple)
        self._event_stack = [self.default_event_handlers]

        # list of elements that have responded to an on_element_enter event
        self.entered_elements = []

    @classmethod
    def set_default_handler(cls, name, selector, handler):
        '''Inspect handler for a selector and apply to the primary-set.
        If the handler has no selector, it is assumed to have a universal
        selector.
        '''
        if name not in cls.default_event_handlers:
            cls.default_event_handlers[name] = RuleSet()
        ruleset = cls.default_event_handlers[name]
        ruleset.add_rule(Rule(selector, handler))

    def select(self, rule, event_name=None):
        # XXX assume passed an element with an id to select on
        if not isinstance(rule, str):
            rule = '#' + rule.id

        def decorate(func):
            func.selectors = [Selector.from_string(r.strip())
                for r in rule.split(',')]
            if event_name is not None:
                func.event_name = event_name
            self.push_handlers(func)
            return func
        return decorate

    def set_handlers(self, *args, **kwargs):
        '''Attach one or more event handlers to the top level of the handler
        stack.
        
        See `push_handlers` for the accepted argument types.
        '''
        # Create event stack if necessary
        if type(self._event_stack) is tuple:
            self._event_stack = [{}]

        for object in args:
            if inspect.isroutine(object):
                # Single magically named function
                name = getattr(object, 'event_name', object.__name__)
                if name not in self.event_types:
                    raise EventException('Unknown event "%s"' % name)
                self.set_handler(name, object)
            else:
                # Single instance with magically named methods
                for name, handler in inspect.getmembers(object):
                    name = getattr(handler, 'event_name', name)
                    if name in self.event_types:
                        self.set_handler(name, handler)
        for name, handler in kwargs.items():
            # Function for handling given event (no magic)
            if name not in self.event_types:
                raise EventException('Unknown event "%s"' % name)
            self.set_handler(name, handler)

    def set_handler(self, name, handler):
        '''Inspect handler for a selector and apply to the primary-set.
        If the handler has no selector, it is assumed to have a universal
        selector.
        '''
        if name not in self._event_stack[0]:
            self._event_stack[0][name] = RuleSet()
        ruleset = self._event_stack[0][name]
        #if not hasattr(handler, 'selector'):
            #handler.selector = universal_selector
        for selector in handler.selectors:
            ruleset.add_rule(Rule(selector, handler))

    def dispatch_event(self, element, event_type, *args, **kw):
        update_active=kw.get('update_active', True)
        propogate=kw.get('propogate', True)
        if element.isEnabled():
            for frame in self._event_stack:
                ruleset = frame.get(event_type, None)
                if ruleset:
                    rules = ruleset.get_matching_rules(element)
                    for rule in rules:
                        handler = rule.declaration_set
                        try:
                            ret = handler(element, *args)
                        except TypeError, message:
                            print 'ERROR CALLING  %r (%r, *%r)]'%(handler,
                                element, args)
                            raise
                        if ret != EVENT_UNHANDLED:
                            # update the currently-active element
                            if update_active:
                                self.active_element = element
                            return True

        # not handled, so pass the event up to parent element
        if propogate and element.parent is not None:
            return self.dispatch_event(element.parent, event_type, *args, **kw)

    # NOW THE EVENT HANDLERS
    active_element = None
    is_dragging_element = False
    mouse_press_element = None
    drag_over_element = None
    focused_element = None

    _rects = None
    def setDirty(self):
        self._rects = None

    def setFocus(self, element):
        # gain focus first so some elements are able to detect whether their
        # child has been focused
        if element is not None and self.focused_element is not element:
            ae = self.active_element
            self.dispatch_event(element, 'on_gain_focus')
            if self.active_element is not ae:
                # focus was caught by some parent element
                element = self.active_element
            else:
                # focus was uncaught - stay with the element targetted
                self.active_element = element

        if (self.focused_element is not None and
                self.focused_element is not element):
            self.dispatch_event(self.focused_element, 'on_lose_focus',
                update_active=False)

        self.focused_element = element

    def getRects(self, exclude=None):
        if self._rects is not None and exclude is not None:
            return self._rects

        rects = []
        clip = self.rect
        for element in self.children:
            if element is exclude: continue
            rects.extend(element.getRects(clip, exclude=exclude))
        rects.reverse()
        if exclude:
            self._rects = rects
        return rects

    def determineHit(self, x, y, exclude=None):
        rects = self.getRects(exclude)

        # and now who did we hit?
        for o, (ox, oy, oz, w, h) in rects:
            if x < ox: continue
            if y < oy: continue
            if x > ox + w: continue
            if y > oy + h: continue
            return o
        return None

    # XXX need list of entered elements which 

    def on_mouse_motion(self, x, y, dx, dy):
        element = self.determineHit(x, y)

        if self.debug_display is not None:
            self.debug_display.setText(repr(element))

        # see which elements (starting with the one under the mouse and
        # moving up the parentage) care about an on_element_enter event
        over = []
        e = element
        while e:
            if not e.isEnabled():
                e = e.parent
                continue
            if e in self.entered_elements:
                over.append(e)
                e = e.parent
                continue
            if self.dispatch_event(e, 'on_element_enter', x, y,
                    propogate=False):
                over.append(e)
            e = e.parent

        # right, now "leave" any elements that aren't in "over" any
        # more
        for e in self.entered_elements:
            if e not in over:
                self.dispatch_event(e, 'on_element_leave', x, y)

        #if mouse stable (not moving)? and 1 second has passed
        #    element.on_element_hover(x, y)

        self.entered_elements = over

        if element is not None:
            self.dispatch_event(element, 'on_mouse_motion', x, y, dx, dy)
        return EVENT_HANDLED

    def on_mouse_enter(self, x, y):
        return self.on_mouse_motion(x, y, 0, 0)

    def on_mouse_leave(self, x, y):
        for e in self.entered_elements:
            self.dispatch_event(e, 'on_element_leave', x, y)
        self.entered_elements = []
        return EVENT_HANDLED

    def on_mouse_press(self, x, y, button, modifiers):
        element = self.determineHit(x, y)
        if element is None: return EVENT_UNHANDLED
        if not element.is_enabled: return EVENT_UNHANDLED

        # remember the pressed element so that we can:
        # 1. pass it mouse drag events even if the mouse moves off its hit area
        # 2. TODO pass it a "lost mouse focus" event?
        self.active_element = element
        self.mouse_press_element = element
        self.is_dragging_element = False

        # switch focus
        self.setFocus(element)

        return self.dispatch_event(element, 'on_mouse_press', x, y, button,
            modifiers)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        if self.active_element is not None:
            # see if the previously-pressed element wants...
<commit_msg>Buncha documentation.<commit_after>'''Implement event handling for wydget GUIs.

The `GUIEventDispatcher` class is automatically mixed into the `wydget.GUI`
class and is activated by pushing the gui onto a window's event handlers
stack::

    gui = GUI(window)
    window.push_handlers(gui)

Standard pyglet events are passed through if handled. The first argument is
always the active element (see below `determining the active element`_):

- `on_mouse_motion(element, x, y, dx, dy)`
- `on_mouse_press(element, x, y, button, modifiers)`
- `on_mouse_release(element, x, y, button, modifiers)`
- `on_mouse_drag(element, x, y, dx, dy, buttons, modifiers)`
- `on_mouse_scroll(element, x, y, dx, dy)`
- `on_key_press(element, symbol, modifiers)`
- `on_text(element, text)`
- `on_text_motion(element, motion)`
- `on_text_motion_select(element, motion)`
  
New events generated by wydget:

`on_change(element, value)`
   The element's "value" changed (eg. text in a TextInput, selection
   choice for a Selection)

`on_click(element, x, y, buttons, modifiers, click_count)`
   The element was clicked. the click_count argument indicates how
   many times the element has been clicked in rapid succession.

`on_element_enter(element, x, y)`
   The mouse is over the element. Note that this event will be
   automatically propogated to all parents of the element directly under
   the mouse.
   
   If an element implements the `on_element_enter` handler but does not
   wish to receive an `on_element_leave` event when the mouse
   departs it should return `EVENT_UNHANDLED`. Returning `EVENT_HANDLED`
   implies that `on_element_leave` be generated once the mouse leaves the
   element.

`on_element_leave(element, x, y)`
   The mouse is no longer over the element.

`on_drag(element, x, y, dx, dy, buttons, modifiers)`
   Press on listening element followed by mouse movement. If the handler
   returns `EVENT_UNHANDLED` then the element is not considered to be being
   dragged, and thus no further `on_drag_*` events will be generated, nor
   an `on_drop`.

`on_drag_enter(element, x, y, dragged_element)`
   The dragged_element is being dragged over the stationary element.

`on_drag_leave(element, x, y, dragged_element)`
   The dragged_element is no longer being dragged over the
   stationary element.

`on_drag_complete(element, x, y, buttons, modifiers, ok)`
   Release after dragging listening element, ok is return code
   from dropped-on element's `on_drop`.

`on_drop(element, x, y, button, modifiers, element)`
   Element has been drag-n-dropped on listening element.

`on_gain_focus(element)`
   Listening element gains focus.

`on_lose_focus(element)`
   Listening element loses focus.


Determining the Active Element
------------------------------

The active element - the one which is passed into the event handlers above
- is usually the element directly under the mouse. There are some
situations where this is not the case:

- ``element.is_transparent == True``
- ``element.isEnabled() == False``
- `on_text`, `on_text_motion` and `on_text_motion_select` are passed to the
  *currently focused element* regardless of the mouse position
- in all other cases, the event may be propogated (see `event propogation`_
  below)


Event Propogation
-----------------

Events are automatically propogated up to element parents if an event
handler either does not exist or the handler returns `EVENT_UNHANDLED`.

'''

import inspect
import time

from pyglet.event import (EventDispatcher, EVENT_UNHANDLED, EVENT_HANDLED,
    EventException)
from pyglet.window import key

from layout.css import Rule, RuleSet, Selector, SimpleSelector

# partially snarfed from layout.gl.event
# Instead of each stack layer being a dictionary mapping event-name to
# event-function, each layer is a dictionary mapping event-name to 
# RuleSet

POSITIONAL_EVENTS = set('on_mouse_motion on_mouse_press on_mouse_release on_mouse_drag on_mouse_scroll on_drag on_element_enter on_element_leave on_drag_enter on_drag_leave on_drag_complete on_drop on_enter on_leave'.split())

class GUIEventDispatcher(EventDispatcher):

    default_event_handlers = {}

    def __init__(self):
        EventDispatcher.__init__(self)
        assert isinstance(self._event_stack, tuple)
        self._event_stack = [self.default_event_handlers]

        # list of elements that have responded to an on_element_enter event
        self.entered_elements = []

    @classmethod
    def set_default_handler(cls, name, selector, handler):
        '''Inspect handler for a selector and apply to the primary-set.
        If the handler has no selector, it is assumed to have a universal
        selector.
        '''
        if name not in cls.default_event_handlers:
            cls.default_event_handlers[name] = RuleSet()
        ruleset = cls.default_event_handlers[name]
        ruleset.add_rule(Rule(selector, handler))

    def select(self, rule, event_name=None):
        # XXX assume passed an element with an id to select on
        if not isinstance(rule, str):
            rule = '#' + rule.id

        def decorate(func):
            func.selectors = [Selector.from_string(r.strip())
                for r in rule.split(',')]
            if event_name is not None:
                func.event_name = event_name
            self.push_handlers(func)
            return func
        return decorate

    def set_handlers(self, *args, **kwargs):
        '''Attach one or more event handlers to the top level of the handler
        stack.
        
        See `push_handlers` for the accepted argument types.
        '''
        # Create event stack if necessary
        if type(self._event_stack) is tuple:
            self._event_stack = [{}]

        for object in args:
            if inspect.isroutine(object):
                # Single magically named function
                name = getattr(object, 'event_name', object.__name__)
                if name not in self.event_types:
                    raise EventException('Unknown event "%s"' % name)
                self.set_handler(name, object)
            else:
                # Single instance with magically named methods
                for name, handler in inspect.getmembers(object):
                    name = getattr(handler, 'event_name', name)
                    if name in self.event_types:
                        self.set_handler(name, handler)
        for name, handler in kwargs.items():
            # Function for handling given event (no magic)
            if name not in self.event_types:
                raise EventException('Unknown event "%s"' % name)
            self.set_handler(name, handler)

    def set_handler(self, name, handler):
        '''Inspect handler for a selector and apply to the primary-set.
        If the handler has no selector, it is assumed to have a universal
        selector.
        '''
        if name not in self._event_stack[0]:
            self._event_stack[0][name] = RuleSet()
        ruleset = self._event_stack[0][name]
        #if not hasattr(handler, 'selector'):
            #handler.selector = universal_selector
        for selector in handler.selectors:
            ruleset.add_rule(Rule(selector, handler))

    def dispatch_event(self, element, event_type, *args, **kw):
        update_active=kw.get('update_active', True)
        propogate=kw.get('propogate', True)
        if element.isEnabled():
            for frame in self._event_stack:
                ruleset = frame.get(event_type, None)
                if ruleset:
                    rules = ruleset.get_matching_rules(element)
                    for rule in rules:
                        handler = rule.declaration_set
                        try:
                            ret = handler(element, *args)
                        except TypeError, message:
                            print 'ERROR CALLING  %r (%r, *%r)]'%(handler,
                                element, args)
                            raise
                        if ret != EVENT_UNHANDLED:
                            # update the currently-active element
                            if update_active:
                                self.active_element = element
                            return True

        # not handled, so pass the event up to parent element
        if propogate and element.parent is not None:
            return self.dispatch_event(element.parent, event_type, *args, **kw)

    # NOW THE EVENT HANDLERS
    active_element = None
    is_dragging_element = False
    mouse_press_element = None
    drag_over_element = None
    focused_element = None

    _rects = None
    def setDirty(self):
        '''Indicate that one or more of the gui's children have changed
        size and a new set of collision rects is needed.
        '''
        self._rects = None

    def setFocus(self, element):
        '''Set the indicated element to be the focus of keyboard input.

        All future `on_text`, `on_text_motion` and `on_text_motion_select`
        events will be passed to the element.
        '''
        # gain focus first so some elements are able to detect whether their
        # child has been focused
        if element is not None and self.focused_element is not element:
            ae = self.active_element
            self.dispatch_event(element, 'on_gain_focus')
            if self.active_element is not ae:
                # focus was caught by some parent element
                element = self.active_element
            else:
                # focus was uncaught - stay with the element targetted
                self.active_element = element

        if (self.focused_element is not None and
                self.focused_element is not element):
            self.dispatch_event(self.focused_element, 'on_lose_focus',
                update_active=False)

        self.focused_element = element

    def getRects(self, exclude=None):
        '''Get the collision rects.

        Prune the tree at "exclude" if provided.
        '''
        if self._rects is not None and exclude is not None:
            return self._rects

        rects = []
        clip = self.rect
        for element in self.children:
            if element is exclude: continue
            rects.extend(element.getRects(clip, exclude))
        rects.reverse()
        if exclude is not None:
            self._rects = rects
        return rects

    def determineHit(self, x, y, exclude=None):
        '''Determine which element is at the absolute (x, y) position.

        "exclude" allows us to ignore a single element (eg. an element
        under the cursor being dragged - we wish to know which element is
        under *that)
        '''
        for o, (ox, oy, oz, w, h) in self.getRects(exclude):
            if x < ox: continue
            if y < oy: continue
            if x > ox + w: continue
            if y > oy + h: continue
            return o
        return None


    def on_mouse_motion(self, x, y, dx, dy):
        '''Determine what element(s) the mouse is positioned over and
        generate on_element_enter and on_element_leave events. Additionally
        generate a new on_mouse_motion event for the element under the
        mouse.
        '''
        element = self.determineHit(x, y)

        if self.debug_display is not None:
            self.debug_display.setText(repr(element))

        # see which elements (starting with the one under the mouse and
        # moving up the parentage) care about an on_element_enter event
        over = []
        e = element
        active_element = self.active_element
        while e:
            if not e.isEnabled():
                e = e.parent
                continue
            if e in self.entered_elements:
                over.append(e)
                e = e.parent
                continue
            if self.dispatch_event(e, 'on_element_enter', x, y,
                    propogate=False):
                over.append(e)
            e = e.parent

        # right, now "leave" any elements that aren't in "over" any
        # more
        for e in self.entered_elements:
            if e not in over:
                self.dispatch_event(e, 'on_element_leave', x, y)

        #if mouse stable (not moving)? and 1 second has passed
        #    element.on_element_hover(x, y)

        self.entered_elements = over

        if element is not None:
            self.dispatch_event(element, 'on_mouse_motion', x, y, dx, dy)

        # restore old active element
        self.active_element = active_element
        return EVENT_HANDLED

    def on_mouse_enter(self, x, y):
        '''Translate this into an on_mouse_motion event.
        '''
        return self.on_mouse_motion(x, y, 0, 0)

    def on_mouse_leave(self, x, y):
        '''Translate this into an on_element_leave for all
        on_element_enter'ed elements.
        '''
        for e in self.entered_elements:
            self.dispatch_event(e, 'on_element_leave', x, y)
        self.entered_elements = []
        return EVENT_HANDLED

    def on_mouse_press(self, x, y, button, modifiers):
        '''Pass this event on to the element underneath the mouse.
        Additionally, switch keyboard focus to this element through
        `self.setFocus(element)`

        The element will be registered as potentially interesting for
        generating future `on_click` and `on_drag` events.
        '''
        element = self.determineHit(x, y)
        if element is None: return EVENT_UNHANDLED
        if not element.is_enabled: return EVENT_UNHANDLED

        # remember the pressed element so that we can:
        # 1. pass it mouse drag events even if the mouse moves off its hit area
        # 2. TODO pass it a "lost focus" event?
        self.active_element = element
        self.mouse_press_element = element
        self.is_dragging_element = False

        # switch focus
        self.setFocus(element)

        return self.dispatch_event(element, 'on_mouse_press', x, y, button,
            modifiers)

    def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
        '''Translate this event into a number of events:
        
        If the mouse button was pressed while over an element we attempt to
        pass an `on_mouse_drag` event to that element. If that event is
        handled and the handler returns EVENT_HANDLED then we additionally
        generate an `on_mouse_release` event (once per drag) and this
        handler returns.
        
        If the mouse button was pressed while over an element we attempt to
        pass an `on_drag` event to that element. If that event is
        handled and the handler returns EVENT_HANDLED then we additionally
        generate an `on_mouse_release` event (once per drag). We then
        attempt to pass on_drag_enter and on_drag_leave events on the
        element *under* the element being dragged.

        If no event handling was invoked above then we generate an
        `on_mouse_drag` event for the element underneath the mouse pointer.
        '''
        if self.active_element is not None:
            # see if the previously-pressed element wants...

{'fraction_non_alphanumeric': 0.07291745336454951, 'fraction_numerical': 0.0006041839740200891, 'mean_word_length': 2.155743565300286, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3284858', 'n_tokens_mistral': 7265, 'n_tokens_neox': 6991, 'n_words': 2519}