#
#   Python GUI - Components - Gtk
#

import gtk
from gtk import gdk
from Geometry import sub_pt
from GComponents import Component as GComponent
from Events import Event, _gtk_key_event_of_interest

_gdk_events_of_interest = (
    #gdk.POINTER_MOTION_MASK |
    gdk.BUTTON_MOTION_MASK |
    gdk.BUTTON_PRESS_MASK |
    gdk.BUTTON_RELEASE_MASK |
    gdk.KEY_PRESS_MASK |
    gdk.KEY_RELEASE_MASK |
    gdk.ENTER_NOTIFY_MASK |
    gdk.LEAVE_NOTIFY_MASK |
    0
)

_gtk_widget_to_component = {}
_gtk_last_keyval_down = None

#------------------------------------------------------------------------------

class Component(GComponent):

    def __init__(self, _gtk_outer, _gtk_inner = None,
            _gtk_focus = None, _gtk_input = None, **kwds):
        self._position = (0, 0)
        self._size = _gtk_outer.size_request()
        _gtk_inner = _gtk_inner or _gtk_outer
        self._gtk_outer_widget = _gtk_outer
        self._gtk_inner_widget = _gtk_inner
        self._gtk_focus_widget = _gtk_focus
        _gtk_widget_to_component[_gtk_outer] = self
        if _gtk_input:
            self._gtk_connect_input_events(_gtk_input)
        if _gtk_focus:
            _gtk_focus.set_property('can-focus', True)
            self._gtk_connect(_gtk_focus, 'focus-in-event', self._gtk_focus_in)
        GComponent.__init__(self, **kwds)
    
    def destroy(self):
        gtk_widget = self._gtk_outer_widget
        if gtk_widget in _gtk_widget_to_component:
            del _gtk_widget_to_component[gtk_widget]
        GComponent.destroy(self)
    
    #
    #		Properties
    #

    def set_width(self, v):
        w, h = self.size
        self.size = (v, h)

    def set_height(self, v):
        w, h = self.size
        self.size = (w, v)

    def get_position(self):
        return self._position

    def set_position(self, v):
        self._position = v
        widget = self._gtk_outer_widget
        parent = widget.get_parent()
        if parent:
            parent.move(widget, *v)
        
    def get_size(self):
        return self._size

    def set_size(self, new_size):
        w, h = new_size
        self._gtk_outer_widget.set_size_request(max(w, 1), max(h, 1))
        self._size = new_size
    
    def get_bounds(self):
        x, y = self._position
        w, h = self.size
        return (x, y, x + w, y + h)

    def set_bounds(self, (l, t, r, b)):
        self.position = (l, t)
        self.size = (r - l, b - t)

    def get_visible(self):
        return self._gtk_outer_widget.get_property('visible')
    
    def set_visible(self, v):
        self._gtk_outer_widget.set_property('visible', v)
    
    #
    #   Message dispatching
    #

    def become_target(self):
        gtk_focus = self._gtk_focus_widget
        if gtk_focus:
            gtk_focus.grab_focus()
        else:
            raise ValueError("%r cannot be targeted" % self)

#	def current_target(self):
#		"""Find the current target object within the Window containing
#		this component. If the component is not contained in a Window,
#		the result is undefined."""
#		target = _gtk_find_component(self._gtk_outer_widget.get_focus())
#		if not target:
#			target = self.window
#		return target

    def is_target(self):
        """Return true if this is the current target within the containing
        Window. If the component is not contained in a Window, the result
        is undefined."""
        gtk_focus = self._gtk_focus_widget
        if gtk_focus:
            return gtk_focus.get_property('has-focus')
        else:
            return False
    
    #
    #   Internal
    #
    
    def _gtk_connect(self, gtk_widget, signal, handler):
        def catch(widget, *args):
            try:
                handler(*args)
            except:
                _gtk_exception_in_signal_handler()
        gtk_widget.connect(signal, lambda widget, *args: handler(*args))

    def _gtk_connect_after(self, gtk_widget, signal, handler):
        def catch(widget, *args):
            try:
                handler(*args)
            except:
                _gtk_exception_in_signal_handler()
        gtk_widget.connect_after(signal, lambda widget, *args: handler(*args))

    def _gtk_focus_in(self, gtk_event):
        window = self.window
        if window:
            old_target = window._target
            window._target = self
            if old_target and old_target is not self:
                old_target._untargeted()
                self._targeted()

    def _targeted(self):
        pass
    
    def _untargeted(self):
        pass
    
    def _gtk_connect_input_events(self, gtk_widget):
        self._last_mouse_down_time = 0
        self._click_count = 0
        gtk_widget.add_events(_gdk_events_of_interest)
        self._gtk_connect(gtk_widget, 'button-press-event',
            self._gtk_button_press_event_signal)
        self._gtk_connect(gtk_widget, 'motion-notify-event',
            self._gtk_motion_notify_event_signal)
        self._gtk_connect(gtk_widget, 'button-release-event',
            self._gtk_button_release_event_signal)
        self._gtk_connect(gtk_widget, 'enter-notify-event',
            self._gtk_enter_leave_event_signal)
        self._gtk_connect(gtk_widget, 'leave-notify-event',
            self._gtk_enter_leave_event_signal)
        self._gtk_connect(gtk_widget, 'key-press-event',
            self._handle_gtk_key_event)
        self._gtk_connect(gtk_widget, 'key-release-event',
            self._handle_gtk_key_event)

    def _gtk_button_press_event_signal(self, gtk_event):
        if gtk_event.type == gdk.BUTTON_PRESS: # don't want 2BUTTON or 3BUTTON
            event = Event._from_gtk_mouse_event(gtk_event)
            last_time = self._last_mouse_down_time
            this_time = event.time
            num_clicks = self._click_count
            if this_time - last_time <= 0.25:
                num_clicks += 1
            else:
                num_clicks = 1
            event.num_clicks = num_clicks
            self._click_count = num_clicks
            self._last_mouse_down_time = this_time
            self.handle_event_here(event)
    
    def _gtk_motion_notify_event_signal(self, gtk_event):
        event = Event._from_gtk_mouse_event(gtk_event)
        self._mouse_event = event
        self.handle_event_here(event)

    def _gtk_button_release_event_signal(self, gtk_event):
        event = Event._from_gtk_mouse_event(gtk_event)
        self._mouse_event = event
        self.handle_event_here(event)

    def _gtk_enter_leave_event_signal(self, gtk_event):
        event = Event._from_gtk_mouse_event(gtk_event)
        self.handle_event_here(event)

    def _handle_gtk_key_event(self, gtk_event):
        """Convert a Gtk key-press or key-release event into an Event
        object and pass it up the message path."""
        global _gtk_last_keyval_down
        if _gtk_key_event_of_interest(gtk_event):
            event = Event._from_gtk_key_event(gtk_event)
            if event.kind == 'key_down':
                this_keyval = gtk_event.keyval
                if _gtk_last_keyval_down == this_keyval:
                    event.auto = 1
                _gtk_last_keyval_down = this_keyval
            else:
                _gtk_last_keyval_down = None
            self.handle_event(event)
        return 1

#------------------------------------------------------------------------------

def _gtk_find_component(gtk_widget):
    while gtk_widget:
        component = _gtk_widget_to_component.get(gtk_widget)
        if component:
            return component
        gtk_widget = gtk_widget.get_parent()
    return None

def _gtk_exception_in_signal_handler():
    print >>sys.stderr, "---------- Exception in gtk signal handler ----------"
    traceback.print_exc()
