#!/usr/bin/env python

"""
$Id$
"""

from __future__ import division # confidence high

import os, matplotlib
import Tkinter as Tki
matplotlib.use('TkAgg') # set backend
import matplotlib.backends.backend_tkagg as tkagg
from Ptkplot import hideTkCursor
from Ptkplot import FullWindowCursor
from wutil import moveCursorTo, WUTIL_USING_X


class MplCanvasAdapter(tkagg.FigureCanvasTkAgg):
    """ Is a FigureCanvasTkAgg, with extra methods to look like Canvas """

    def __init__(self, gkikernel, figure, master=None):
        tkagg.FigureCanvasTkAgg.__init__(self, figure, master,
                                         self.resize_event)
        # members
        self.__theGwidget = self.get_tk_widget() # THE gwidget
        self.__gkiKernel = gkikernel
        self.__doIdleRedraw = 0
        self.__doIdleSWCursor = 0
        self.ignoreNextRedraw = 0 # used externally to this class ...

        # Add a placeholder software cursor attribute. If it is None,
        # that means no software cursor is in effect. If it is not None,
        # then it will be used to render the cursor.
        self.__isSWCursorActive = 0
        self.__SWCursor = None

        # Basic bindings for the virtual trackball.
        # ! Do NOT set these without undergoing a major GkiMpl design
        # check !  Binding Expose to tkExpose forces tkRedraw to be
        # called WAY too many times during a window resize. This uses the
        # draw buffer, which is slow and unnecessary for resizes.
#       self.__theGwidget.bind('<Expose>', self.tkExpose)
#       self.__theGwidget.bind('<Configure>', self.tkExpose) # init draw issue

    def pack(self, **kw):

        """ delegate to the gwidget """
        self.__theGwidget.pack(kw)

    def winfo_id(self):

        """ delegate to the gwidget """
        self.__theGwidget.winfo_id()

    def gwidgetize(self, width, height):

        """ This is a one-stop shopping spot to add all the extra attributes
        to the gwidget object it needs to be seen as a "gwidget" in the GKI
        sense. See requirements in GkiInteractiveTkBase. """

        gw = self.__theGwidget

        # Add attributes to the gwidget
        gw.lastX              = None
        gw.lastY              = None
        gw.width              = width
        gw.height             = height

        # Add our functions to the gwidget
        gw.activateSWCursor   = self.activateSWCursor
        gw.deactivateSWCursor = self.deactivateSWCursor
        gw.isSWCursorActive   = self.isSWCursorActive
        gw.getSWCursor        = self.getSWCursor
        gw.moveCursorTo       = self.moveCursorTo
        gw.tkRedraw           = self.tkRedraw

    def resize_event(self, evt=None):

        # See also get/set_size_inches, figure.get_window_extent().  The latter
        # is Bbox, use .width/.height()
        #
        #  before rsz: w = self.figure.get_window_extent().width # not func .98
        #  before rsz: h = self.figure.get_window_extent().height() # is in .91

        # Note that the 'evt' arg was always part of the signature for
        # versions before MPL v 0.98.5.2, when it became optional.
        # So see if it is there.  Always use it if present, it is accurate.
        if evt != None:
            curW = evt.width
            curH = evt.height
        else:
            dpi = self.figure.get_dpi()
            curW = self.figure.get_figwidth() * dpi
            curH = self.figure.get_figheight() * dpi

        # Need to deactivate cursor before resizing, then re-act. after
        self.wrappedRedrawOrResize(w=curW, h=curH)

        # also update the widget's w/h attrs; we will need this for the cursor
        self.__theGwidget.width  = curW
        self.__theGwidget.height = curH

    def flush(self):

        self.__doIdleRedraw = 0

    # show and draw could be defined to catch events before passing through
#   def show(self):  tkagg.FigureCanvasTkAgg.show(self)
#   def draw(self):  tkagg.FigureCanvasTkAgg.draw(self)

    def wrappedRedrawOrResize(self, w=None, h=None):

        """Wrap the redraw (or resize) with a deactivate and then re-activate
        of the cursor.  If w or h are provided then we are only resizing."""

        # need to indicate cursor is not visible before this, since
        # cursor sleeps are now issued by redraw. The presumption is that
        # redraw/resize will affect cursor visibility, so we set it first
        resizing = w != None
        cursorActivate = 0

        if self.__isSWCursorActive:
            # deactivate cursor for duration of redraw
            # otherwise it slows the redraw to a glacial pace
            cursorActivate = 1
            x = self.__SWCursor.lastx
            y = self.__SWCursor.lasty
            self.deactivateSWCursor()

        if resizing:
            self.__gkiKernel.resizeGraphics(w, h)
        else:
            # should document how this line gets to GkiMplKernel.redraw()
            self.__theGwidget.redraw(self.__theGwidget)

        if cursorActivate:
            # Need to activate it, but don't draw it if only resizing, there is
            # a bug where the previous crosshair cursor is drawn too.
            self.activateSWCursor(x, y, drawToo=(not resizing))

    # tkRedraw() is used as if it belonged to the gwidget's class
    def tkRedraw(self, *dummy):

        """ delegate to the gwidget """
        gw = self.__theGwidget
        self.__doIdleRedraw = 1
        gw.after_idle(self.idleRedraw)

    def idleRedraw(self):

        """Do a redraw, then set buffer so no more happen on this idle cycle"""
        if self.__doIdleRedraw:
            self.__doIdleRedraw = 0
            self.wrappedRedrawOrResize()

    # isSWCursorActive() is used as if it belonged to the gwidget's class
    def isSWCursorActive(self):

        """ getter """
        return self.__isSWCursorActive

    # activateSWCursor() is used as if it belonged to the gwidget's class
    def activateSWCursor(self, x=None, y=None, type=None, drawToo=True):

        gw = self.__theGwidget
        hideTkCursor(gw) # from Ptkplot
        # ignore type for now since only one type of software cursor
        # is implemented
        gw.update_idletasks()
        if not self.__isSWCursorActive:
            if not self.__SWCursor:
                self.__SWCursor = FullWindowCursor(0.5, 0.5, gw)
            self.__isSWCursorActive = 1
            gw.bind("<Motion>",self.moveCursor)
        if drawToo and not self.__SWCursor.isVisible():
            self.__SWCursor.draw()

    # deactivateSWCursor() is used as if it belonged to the gwidget's class
    def deactivateSWCursor(self):

        gw = self.__theGwidget
        gw.update_idletasks()
        if self.__isSWCursorActive:
            self.__SWCursor.erase()
            gw.unbind("<Motion>")
            self.__SWCursor.isLastSWmove = 1
            self.__isSWCursorActive = 0
            gw['cursor'] = 'arrow' # set back to normal

    # getSWCursor() is used as if it belonged to the gwidget's class
    def getSWCursor(self):

        """ getter """
        return self.__SWCursor

    def SWCursorWake(self):

        """ Wake cursor only after idle """
        self.__doIdleSWCursor = 1
        self.after_idle(self.idleSWCursorWake)

    def idleSWCursorWake(self):

        """Do cursor redraw, then reset so no more happen on this idle cycle"""
        if self.__doIdleSWCursor:
            self.__doIdleSWCursor = 0
            self.SWCursorImmediateWake()

    def SWCursorImmediateWake(self):

        if self.__isSWCursorActive:
            self.__SWCursor.draw()

    def moveCursor(self, event):

        """Call back for mouse motion events"""
        # Kludge to handle the fact that MacOS X (X11) doesn't remember
        # software driven moves, the first move will just move nothing
        # but will properly update the coordinates.  Do not do this under Aqua.
        gw = self.__theGwidget
        if WUTIL_USING_X and self.__SWCursor.isLastSWmove:
            x = self.__SWCursor.lastx
            y = self.__SWCursor.lasty
            # call the wutil version
            moveCursorTo(gw.winfo_id(),
                         gw.winfo_rootx(),
                         gw.winfo_rooty(),
                         int(x*gw.winfo_width()),
                         int((1.-y)*gw.winfo_height()))
        else:
            x = (event.x+0.5)/gw.winfo_width()
            y = 1.-(event.y+0.5)/gw.winfo_height()
        self.__SWCursor.moveTo(x,y,SWmove=0)

    # moveCursorTo() is used as if it belonged to the gwidget's class
    def moveCursorTo(self, x, y, SWmove=0):

        self.__SWCursor.moveTo(float(x)/self.__theGwidget.width,
                              float(y)/self.__theGwidget.height,
                              SWmove)

    def activate(self):

        """Not really needed for Tkplot widgets"""
        pass

    def tkExpose(self, *dummy):

        """Redraw the widget upon the tkExpose event.
        Make it active, update tk events, call redraw procedure."""
        if self.ignoreNextRedraw:
            self.ignoreNextRedraw = 0
        else:
            self.tkRedraw()
