#!/usr/bin/env python
"""

Module that holds the GUI modes used by FloatCanvas


Note that this can only be imported after a wx.App() has been created.

This approach was inpired by Christian Blouin, who also wrote the initial
version of the code.

"""

import wx
## fixme: events should live in their own module, so all of FloatCanvas
##        wouldn't have to be imported here.
import FloatCanvas, Resources
import numpy as N

## create all the Cursors, so they don't need to be created each time.
if "wxMac" in wx.PlatformInfo: # use 16X16 cursors for wxMac
    HandCursor = wx.CursorFromImage(Resources.getHand16Image())
    GrabHandCursor = wx.CursorFromImage(Resources.getGrabHand16Image())

    img = Resources.getMagPlus16Image()
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6)
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6)
    MagPlusCursor = wx.CursorFromImage(img)

    img = Resources.getMagMinus16Image()
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6)
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6)
    MagMinusCursor = wx.CursorFromImage(img)
else: # use 24X24 cursors for GTK and Windows
    HandCursor = wx.CursorFromImage(Resources.getHandImage())
    GrabHandCursor = wx.CursorFromImage(Resources.getGrabHandImage())

    img = Resources.getMagPlusImage()
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9)
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9)
    MagPlusCursor = wx.CursorFromImage(img)

    img = Resources.getMagMinusImage()
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9)
    img.SetOptionInt(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9)
    MagMinusCursor = wx.CursorFromImage(img)


class GUIBase:
    """
    Basic Mouse mode and baseclass for other GUImode.

    This one does nothing with any event

    """
    def __init__(self, parent):
        self.parent = parent
    
    Cursor = wx.NullCursor

    # Handlers
    def OnLeftDown(self, event):
        pass
    def OnLeftUp(self, event):
        pass
    def OnLeftDouble(self, event):
        pass
    def OnRightDown(self, event):
        pass
    def OnRightUp(self, event):
        pass
    def OnRightDouble(self, event):
        pass
    def OnMiddleDown(self, event):
        pass
    def OnMiddleUp(self, event):
        pass
    def OnMiddleDouble(self, event):
        pass
    def OnWheel(self, event):
        pass
    def OnMove(self, event):
        pass
            
    def UpdateScreen(self):
        """
        Update gets called if the screen has been repainted in the middle of a zoom in
        so the Rubber Band Box can get updated
        """
        pass

class GUIMouse(GUIBase):
    """

    Mouse mode checks for a hit test, and if nothing is hit,
    raises a FloatCanvas mouse event for each event.

    """

    Cursor = wx.NullCursor

    # Handlers
    def OnLeftDown(self, event):
        EventType = FloatCanvas.EVT_FC_LEFT_DOWN
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnLeftUp(self, event):
        EventType = FloatCanvas.EVT_FC_LEFT_UP
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnLeftDouble(self, event):
        EventType = FloatCanvas.EVT_FC_LEFT_DCLICK
        if not self.parent.HitTest(event, EventType):
                self.parent._RaiseMouseEvent(event, EventType)

    def OnMiddleDown(self, event):
        EventType = FloatCanvas.EVT_FC_MIDDLE_DOWN
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnMiddleUp(self, event):
        EventType = FloatCanvas.EVT_FC_MIDDLE_UP
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnMiddleDouble(self, event):
        EventType = FloatCanvas.EVT_FC_MIDDLE_DCLICK
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnRightDown(self, event):
        EventType = FloatCanvas.EVT_FC_RIGHT_DOWN
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnRightUp(self, event):
        EventType = FloatCanvas.EVT_FC_RIGHT_UP
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnRightDouble(self, event):
        EventType = FloatCanvas.EVT_FC_RIGHT_DCLICK
        if not self.parent.HitTest(event, EventType):
            self.parent._RaiseMouseEvent(event, EventType)

    def OnWheel(self, event):
        EventType = FloatCanvas.EVT_FC_MOUSEWHEEL
        self.parent._RaiseMouseEvent(event, EventType)

    def OnMove(self, event):
        ## The Move event always gets raised, even if there is a hit-test
        self.parent.MouseOverTest(event)
        self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)

class GUIMove(GUIBase):

    Cursor = HandCursor
    GrabCursor = GrabHandCursor
    def __init__(self, parent):
        GUIBase.__init__(self, parent)
        self.StartMove = None
        self.PrevMoveXY = None

    def OnLeftDown(self, event):
        self.parent.SetCursor(self.GrabCursor)
        self.parent.CaptureMouse()
        self.StartMove = N.array( event.GetPosition() )
        self.PrevMoveXY = (0,0)

    def OnLeftUp(self, event):
        if self.StartMove is not None:
            StartMove = self.StartMove
            EndMove = N.array(event.GetPosition())
            DiffMove = StartMove-EndMove
            if N.sum(DiffMove**2) > 16:
                self.parent.MoveImage(DiffMove, 'Pixel')
            self.StartMove = None
        self.parent.SetCursor(self.Cursor)

    def OnMove(self, event):
        # Allways raise the Move event.
        self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)
        if event.Dragging() and event.LeftIsDown() and not self.StartMove is None:
            xy1 = N.array( event.GetPosition() )
            wh = self.parent.PanelSize
            xy_tl = xy1 - self.StartMove
            dc = wx.ClientDC(self.parent)
            dc.BeginDrawing()
            x1,y1 = self.PrevMoveXY
            x2,y2 = xy_tl
            w,h = self.parent.PanelSize
            ##fixme: This sure could be cleaner!
            if x2 > x1 and y2 > y1:
                xa = xb = x1
                ya = yb = y1
                wa = w
                ha = y2 - y1
                wb = x2-  x1
                hb = h
            elif x2 > x1 and y2 <= y1:
                xa = x1
                ya = y1
                wa = x2 - x1
                ha = h
                xb = x1
                yb = y2 + h
                wb = w
                hb = y1 - y2
            elif x2 <= x1 and y2 > y1:
                xa = x1
                ya = y1
                wa = w
                ha = y2 - y1
                xb = x2 + w
                yb = y1
                wb = x1 - x2
                hb = h - y2 + y1
            elif x2 <= x1 and y2 <= y1:
                xa = x2 + w
                ya = y1
                wa = x1 - x2
                ha = h
                xb = x1
                yb = y2 + h
                wb = w
                hb = y1 - y2

            dc.SetPen(wx.TRANSPARENT_PEN)
            dc.SetBrush(self.parent.BackgroundBrush)
            dc.DrawRectangle(xa, ya, wa, ha)
            dc.DrawRectangle(xb, yb, wb, hb)
            self.PrevMoveXY = xy_tl
            if self.parent._ForeDrawList:
                dc.DrawBitmapPoint(self.parent._ForegroundBuffer,xy_tl)
            else:
                dc.DrawBitmapPoint(self.parent._Buffer,xy_tl)
            dc.EndDrawing()

    def OnWheel(self, event):
        """
           By default, zoom in/out by a 0.1 factor per Wheel event.
        """
        if event.GetWheelRotation() < 0:
            self.parent.Zoom(0.9)
        else:
            self.parent.Zoom(1.1)

class GUIMouseAndMove(GUIMouse, GUIMove):
    def __init__(self, parent):
        GUIMouse.__init__(self, parent)
        GUIMove.__init__(self, parent)

    def OnKeyUp(self, event):
        # FIXME: Check these on non-qwerty keyboards
        ZoomOut = (61, 388)
        ZoomIn  = (45, 390)

        pos = event.GetPosition()
        size = self.parent.GetSize()
        center = size[0]/2, size[1]/2

        move = center[0]-pos[0], center[1]-pos[1]
        if event.UnicodeKey in ZoomIn:
            self.parent.Zoom(0.90, event.GetPosition(), 'Pixel', False) 
            self.parent.MoveImage(move, 'Pixel')
            return True
        elif event.UnicodeKey in ZoomOut:
            self.parent.Zoom(1.10, event.GetPosition(), 'Pixel', False)
            self.parent.MoveImage(move, 'Pixel')
            return True

        Up    = (315, 87, 332)
        Left  = (314, 328, 65)
        Down  = (317, 326, 83)
        Right = (316, 330, 68)
        if event.UnicodeKey in Left:
            self.parent.MoveImage((-40, 0), 'Pixel')
        elif event.UnicodeKey in Right:
            self.parent.MoveImage((40, 0), 'Pixel')
        elif event.UnicodeKey in Up:
            self.parent.MoveImage((0, -40), 'Pixel')
        elif event.UnicodeKey in Down:
            self.parent.MoveImage((0, 40), 'Pixel')
        return True

    def OnLeftDown(self, event):
        EventType = FloatCanvas.EVT_FC_LEFT_DOWN
        if not self.parent.HitTest(event, EventType):
            GUIMove.OnLeftDown(self, event) 

    def OnLeftUp(self, event):
        if self.StartMove is None:
            GUIMouse.OnLeftUp(self, event)
        else:
            GUIMove.OnLeftUp(self, event)

    def OnLeftDouble(self, event):
        EventType = FloatCanvas.EVT_FC_LEFT_DCLICK
        if not self.parent.HitTest(event, EventType):
            self.parent.Zoom(1.0,event.GetPosition(),"Pixel")
#            self.parent._RaiseMouseEvent(event, EventType)

    def OnMove(self, event):
        if self.StartMove is None:
            GUIMouse.OnMove(self, event)
        else:
            GUIMove.OnMove(self, event)

class GUIZoomIn(GUIBase):
 
    Cursor = MagPlusCursor
 
    def __init__(self, parent):
        GUIBase.__init__(self, parent)
        self.StartRBBox = None
        self.PrevRBBox = None

    def OnLeftDown(self, event):
        self.StartRBBox = N.array( event.GetPosition() )
        self.PrevRBBox = None
        self.parent.CaptureMouse()

    def OnLeftUp(self, event):
        #if self.parent.HasCapture():
        #        self.parent.ReleaseMouse()
        if event.LeftUp() and not self.StartRBBox is None:
            self.PrevRBBox = None
            EndRBBox = event.GetPosition()
            StartRBBox = self.StartRBBox
            # if mouse has moved less that ten pixels, don't use the box.
            if ( abs(StartRBBox[0] - EndRBBox[0]) > 10
                    and abs(StartRBBox[1] - EndRBBox[1]) > 10 ):
                EndRBBox = self.parent.PixelToWorld(EndRBBox)
                StartRBBox = self.parent.PixelToWorld(StartRBBox)
                BB = N.array(((min(EndRBBox[0],StartRBBox[0]),
                                min(EndRBBox[1],StartRBBox[1])),
                            (max(EndRBBox[0],StartRBBox[0]),
                                max(EndRBBox[1],StartRBBox[1]))),N.float_)
                self.parent.ZoomToBB(BB)
            else:
                Center = self.parent.PixelToWorld(StartRBBox)
                self.parent.Zoom(1.5,Center)
            self.StartRBBox = None

    def OnMove(self, event):
        # Allways raise the Move event.
        self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)
        if event.Dragging() and event.LeftIsDown() and not (self.StartRBBox is None):
            xy0 = self.StartRBBox
            xy1 = N.array( event.GetPosition() )
            wh  = abs(xy1 - xy0)
            wh[0] = max(wh[0], int(wh[1]*self.parent.AspectRatio))
            wh[1] = int(wh[0] / self.parent.AspectRatio)
            xy_c = (xy0 + xy1) / 2
            dc = wx.ClientDC(self.parent)
            dc.BeginDrawing()
            dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            dc.SetLogicalFunction(wx.XOR)
            if self.PrevRBBox:
                dc.DrawRectanglePointSize(*self.PrevRBBox)
            self.PrevRBBox = ( xy_c - wh/2, wh )
            dc.DrawRectanglePointSize( *self.PrevRBBox )
            dc.EndDrawing()
            
    def UpdateScreen(self):
        """
        Update gets called if the screen has been repainted in the middle of a zoom in
        so the Rubber Band Box can get updated
        """
        if self.PrevRBBox is not None:
            dc = wx.ClientDC(self.parent)
            dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH))
            dc.SetBrush(wx.TRANSPARENT_BRUSH)
            dc.SetLogicalFunction(wx.XOR)
            dc.DrawRectanglePointSize(*self.PrevRBBox)

    def OnRightDown(self, event):
        self.parent.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel")

    def OnWheel(self, event):
        if event.GetWheelRotation() < 0:
            self.parent.Zoom(0.9)
        else:
            self.parent.Zoom(1.1)

class GUIZoomOut(GUIBase):

    Cursor = MagMinusCursor

    def OnLeftDown(self, event):
        self.parent.Zoom(1/1.5, event.GetPosition(), centerCoords="Pixel")

    def OnRightDown(self, event):
        self.parent.Zoom(1.5, event.GetPosition(), centerCoords="Pixel")

    def OnWheel(self, event):
        if event.GetWheelRotation() < 0:
            self.parent.Zoom(0.9)
        else:
            self.parent.Zoom(1.1)

    def OnMove(self, event):
        # Allways raise the Move event.
        self.parent._RaiseMouseEvent(event,FloatCanvas.EVT_FC_MOTION)

