#----------------------------------------------------------------------
# Name:         multisash
# Purpose:      Multi Sash control
#
# Author:       Gerrit van Dyk
#
# Created:      2002/11/20
# Version:      0.1
# RCS-ID:       $Id$
# License:      wxWindows licensie
#----------------------------------------------------------------------

from wxPython.wx import *

MV_HOR = 0
MV_VER = not MV_HOR

SH_SIZE = 5
CR_SIZE = SH_SIZE * 3

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

class wxMultiSash(wxWindow):
    def __init__(self, *_args,**_kwargs):
        apply(wxWindow.__init__,(self,) + _args,_kwargs)
        self._defChild = EmptyChild
        self.child = wxMultiSplit(self,self,wxPoint(0,0),self.GetSize())
        EVT_SIZE(self,self.OnMultiSize)

    def SetDefaultChildClass(self,childCls):
        self._defChild = childCls
        self.child.DefaultChildChanged()

    def OnMultiSize(self,evt):
        self.child.SetSize(self.GetSize())

    def UnSelect(self):
        self.child.UnSelect()

    def Clear(self):
        old = self.child
        self.child = wxMultiSplit(self,self,wxPoint(0,0),self.GetSize())
        old.Destroy()
        self.child.OnSize(None)

    def GetSaveData(self):
        saveData = {}
        saveData['_defChild'] = str(self._defChild)
        saveData['child'] = self.child.GetSaveData()
        return saveData

    def SetSaveData(self,data):
        dChild = data['_defChild']
        mod = dChild.split('.')[0]
        exec 'import %s' % mod
        self._defChild = eval(dChild)
        old = self.child
        self.child = wxMultiSplit(self,self,wxPoint(0,0),self.GetSize())
        self.child.SetSaveData(data['child'])
        old.Destroy()
        self.OnMultiSize(None)
        self.child.OnSize(None)


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


class wxMultiSplit(wxWindow):
    def __init__(self,multiView,parent,pos,size,view1 = None):
        wxWindow.__init__(self,id = -1,parent = parent,pos = pos,size = size,
                          style = wxCLIP_CHILDREN)
        self.multiView = multiView
        self.view2 = None
        if view1:
            self.view1 = view1
            self.view1.Reparent(self)
            self.view1.MoveXY(0,0)
        else:
            self.view1 = wxMultiViewLeaf(self.multiView,self,
                                         wxPoint(0,0),self.GetSize())
        self.direction = None

        EVT_SIZE(self,self.OnSize)

    def GetSaveData(self):
        saveData = {}
        if self.view1:
            saveData['view1'] = self.view1.GetSaveData()
            if isinstance(self.view1,wxMultiSplit):
                saveData['view1IsSplit'] = 1
        if self.view2:
            saveData['view2'] = self.view2.GetSaveData()
            if isinstance(self.view2,wxMultiSplit):
                saveData['view2IsSplit'] = 1
        saveData['direction'] = self.direction
        v1,v2 = self.GetPositionTuple()
        saveData['x'] = v1
        saveData['y'] = v2
        v1,v2 = self.GetSizeTuple()
        saveData['w'] = v1
        saveData['h'] = v2
        return saveData

    def SetSaveData(self,data):
        self.direction = data['direction']
        self.SetDimensions(data['x'],data['y'],data['w'],data['h'])
        v1Data = data.get('view1',None)
        if v1Data:
            isSplit = data.get('view1IsSplit',None)
            old = self.view1
            if isSplit:
                self.view1 = wxMultiSplit(self.multiView,self,
                                          wxPoint(0,0),self.GetSize())
            else:
                self.view1 = wxMultiViewLeaf(self.multiView,self,
                                             wxPoint(0,0),self.GetSize())
            self.view1.SetSaveData(v1Data)
            if old:
                old.Destroy()
        v2Data = data.get('view2',None)
        if v2Data:
            isSplit = data.get('view2IsSplit',None)
            old = self.view2
            if isSplit:
                self.view2 = wxMultiSplit(self.multiView,self,
                                          wxPoint(0,0),self.GetSize())
            else:
                self.view2 = wxMultiViewLeaf(self.multiView,self,
                                             wxPoint(0,0),self.GetSize())
            self.view2.SetSaveData(v2Data)
            if old:
                old.Destroy()
        if self.view1:
            self.view1.OnSize(None)
        if self.view2:
            self.view2.OnSize(None)

    def UnSelect(self):
        if self.view1:
            self.view1.UnSelect()
        if self.view2:
            self.view2.UnSelect()

    def DefaultChildChanged(self):
        if not self.view2:
            self.view1.DefaultChildChanged()

    def AddLeaf(self,direction,caller,pos):
        if self.view2:
            if caller == self.view1:
                self.view1 = wxMultiSplit(self.multiView,self,
                                          caller.GetPosition(),
                                          caller.GetSize(),
                                          caller)
                self.view1.AddLeaf(direction,caller,pos)
            else:
                self.view2 = wxMultiSplit(self.multiView,self,
                                          caller.GetPosition(),
                                          caller.GetSize(),
                                          caller)
                self.view2.AddLeaf(direction,caller,pos)
        else:
            self.direction = direction
            w,h = self.GetSizeTuple()
            if direction == MV_HOR:
                x,y = (pos,0)
                w1,h1 = (w-pos,h)
                w2,h2 = (pos,h)
            else:
                x,y = (0,pos)
                w1,h1 = (w,h-pos)
                w2,h2 = (w,pos)
            self.view2 = wxMultiViewLeaf(self.multiView,self,
                                         wxPoint(x,y),wxSize(w1,h1))
            self.view1.SetSize(wxSize(w2,h2))
            self.view2.OnSize(None)

    def DestroyLeaf(self,caller):
        if not self.view2:              # We will only have 2 windows if
            return                      # we need to destroy any
        parent = self.GetParent()       # Another splitview
        if parent == self.multiView:    # We'r at the root
            if caller == self.view1:
                old = self.view1
                self.view1 = self.view2
                self.view2 = None
                old.Destroy()
            else:
                self.view2.Destroy()
                self.view2 = None
            self.view1.SetSize(self.GetSize())
            self.view1.Move(self.GetPosition())
        else:
            w,h = self.GetSizeTuple()
            x,y = self.GetPositionTuple()
            if caller == self.view1:
                if self == parent.view1:
                    parent.view1 = self.view2
                else:
                    parent.view2 = self.view2
                self.view2.Reparent(parent)
                self.view2.SetDimensions(x,y,w,h)
            else:
                if self == parent.view1:
                    parent.view1 = self.view1
                else:
                    parent.view2 = self.view1
                self.view1.Reparent(parent)
                self.view1.SetDimensions(x,y,w,h)
            self.view1 = None
            self.view2 = None
            self.Destroy()

    def CanSize(self,side,view):
        if self.SizeTarget(side,view):
            return True
        return False

    def SizeTarget(self,side,view):
        if self.direction == side and self.view2 and view == self.view1:
            return self
        parent = self.GetParent()
        if parent != self.multiView:
            return parent.SizeTarget(side,self)
        return None

    def SizeLeaf(self,leaf,pos,side):
        if self.direction != side:
            return
        if not (self.view1 and self.view2):
            return
        if pos < 10: return
        w,h = self.GetSizeTuple()
        if side == MV_HOR:
            if pos > w - 10: return
        else:
            if pos > h - 10: return
        if side == MV_HOR:
            self.view1.SetDimensions(0,0,pos,h)
            self.view2.SetDimensions(pos,0,w-pos,h)
        else:
            self.view1.SetDimensions(0,0,w,pos)
            self.view2.SetDimensions(0,pos,w,h-pos)

    def OnSize(self,evt):
        if not self.view2:
            self.view1.SetSize(self.GetSize())
            self.view1.OnSize(None)
            return
        v1w,v1h = self.view1.GetSizeTuple()
        v2w,v2h = self.view2.GetSizeTuple()
        v1x,v1y = self.view1.GetPositionTuple()
        v2x,v2y = self.view2.GetPositionTuple()
        w,h = self.GetSizeTuple()

        if v1x != v2x:
            ratio = float(w) / float((v1w + v2w))
            v1w *= ratio
            v2w = w - v1w
            v2x = v1w
        else:
            v1w = v2w = w

        if v1y != v2y:
            ratio = float(h) / float((v1h + v2h))
            v1h *= ratio
            v2h = h - v1h
            v2y = v1h
        else:
            v1h = v2h = h

        self.view1.SetDimensions(v1x,v1y,v1w,v1h)
        self.view2.SetDimensions(v2x,v2y,v2w,v2h)
        self.view1.OnSize(None)
        self.view2.OnSize(None)


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


class wxMultiViewLeaf(wxWindow):
    def __init__(self,multiView,parent,pos,size):
        wxWindow.__init__(self,id = -1,parent = parent,pos = pos,size = size,
                          style = wxCLIP_CHILDREN)
        self.multiView = multiView

        self.sizerHor = MultiSizer(self,MV_HOR)
        self.sizerVer = MultiSizer(self,MV_VER)
        self.creatorHor = MultiCreator(self,MV_HOR)
        self.creatorVer = MultiCreator(self,MV_VER)
        self.detail = MultiClient(self,multiView._defChild)
        self.closer = MultiCloser(self)

        EVT_SIZE(self,self.OnSize)

    def GetSaveData(self):
        saveData = {}
        saveData['detailClass'] = str(self.detail.child.__class__)
        if hasattr(self.detail.child,'GetSaveData'):
            attr = getattr(self.detail.child,'GetSaveData')
            if callable(attr):
                dData = attr()
                if dData:
                    saveData['detail'] = dData
        v1,v2 = self.GetPositionTuple()
        saveData['x'] = v1
        saveData['y'] = v2
        v1,v2 = self.GetSizeTuple()
        saveData['w'] = v1
        saveData['h'] = v2
        return saveData

    def SetSaveData(self,data):
        dChild = data['detailClass']
        mod = dChild.split('.')[0]
        exec 'import %s' % mod
        detClass = eval(dChild)
        self.SetDimensions(data['x'],data['y'],data['w'],data['h'])
        old = self.detail
        self.detail = MultiClient(self,detClass)
        dData = data.get('detail',None)
        if dData:
            if hasattr(self.detail.child,'SetSaveData'):
                attr = getattr(self.detail.child,'SetSaveData')
                if callable(attr):
                    attr(dData)
        old.Destroy()
        self.detail.OnSize(None)

    def UnSelect(self):
        self.detail.UnSelect()

    def DefaultChildChanged(self):
        self.detail.SetNewChildCls(self.multiView._defChild)

    def AddLeaf(self,direction,pos):
        if pos < 10: return
        w,h = self.GetSizeTuple()
        if direction == MV_VER:
            if pos > h - 10: return
        else:
            if pos > w - 10: return
        self.GetParent().AddLeaf(direction,self,pos)

    def DestroyLeaf(self):
        self.GetParent().DestroyLeaf(self)

    def SizeTarget(self,side):
        return self.GetParent().SizeTarget(side,self)

    def CanSize(self,side):
        return self.GetParent().CanSize(side,self)

    def OnSize(self,evt):
        self.sizerHor.OnSize(evt)
        self.sizerVer.OnSize(evt)
        self.creatorHor.OnSize(evt)
        self.creatorVer.OnSize(evt)
        self.detail.OnSize(evt)
        self.closer.OnSize(evt)

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


class MultiClient(wxWindow):
    def __init__(self,parent,childCls):
        w,h = self.CalcSize(parent)
        wxWindow.__init__(self,id = -1,parent = parent,
                          pos = wxPoint(0,0),
                          size = wxSize(w,h),
                          style = wxCLIP_CHILDREN | wxSUNKEN_BORDER)
        self.child = childCls(self)
        self.child.MoveXY(2,2)
        self.normalColour = self.GetBackgroundColour()
        self.selected = False

        EVT_SET_FOCUS(self,self.OnSetFocus)
        EVT_CHILD_FOCUS(self,self.OnChildFocus)

    def UnSelect(self):
        if self.selected:
            self.selected = False
            self.SetBackgroundColour(self.normalColour)
            self.Refresh()

    def Select(self):
        self.GetParent().multiView.UnSelect()
        self.selected = True
        self.SetBackgroundColour(wxColour(255,255,0)) # Yellow
        self.Refresh()

    def CalcSize(self,parent):
        w,h = parent.GetSizeTuple()
        w -= SH_SIZE
        h -= SH_SIZE
        return (w,h)

    def OnSize(self,evt):
        w,h = self.CalcSize(self.GetParent())
        self.SetDimensions(0,0,w,h)
        w,h = self.GetClientSizeTuple()
        self.child.SetSize(wxSize(w-4,h-4))

    def SetNewChildCls(self,childCls):
        if self.child:
            self.child.Destroy()
            self.child = None
        self.child = childCls(self)
        self.child.MoveXY(2,2)

    def OnSetFocus(self,evt):
        self.Select()

    def OnChildFocus(self,evt):
        self.OnSetFocus(evt)
##        from Funcs import FindFocusedChild
##        child = FindFocusedChild(self)
##        EVT_KILL_FOCUS(child,self.OnChildKillFocus)


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


class MultiSizer(wxWindow):
    def __init__(self,parent,side):
        self.side = side
        x,y,w,h = self.CalcSizePos(parent)
        wxWindow.__init__(self,id = -1,parent = parent,
                          pos = wxPoint(x,y),
                          size = wxSize(w,h),
                          style = wxCLIP_CHILDREN)

        self.px = None                  # Previous X
        self.py = None                  # Previous Y
        self.isDrag = False             # In Dragging
        self.dragTarget = None          # View being sized

        EVT_LEAVE_WINDOW(self,self.OnLeave)
        EVT_ENTER_WINDOW(self,self.OnEnter)
        EVT_MOTION(self,self.OnMouseMove)
        EVT_LEFT_DOWN(self,self.OnPress)
        EVT_LEFT_UP(self,self.OnRelease)

    def CalcSizePos(self,parent):
        pw,ph = parent.GetSizeTuple()
        if self.side == MV_HOR:
            x = CR_SIZE + 2
            y = ph - SH_SIZE
            w = pw - CR_SIZE - SH_SIZE - 2
            h = SH_SIZE
        else:
            x = pw - SH_SIZE
            y = CR_SIZE + 2 + SH_SIZE
            w = SH_SIZE
            h = ph - CR_SIZE - SH_SIZE - 4 - SH_SIZE # For Closer
        return (x,y,w,h)

    def OnSize(self,evt):
        x,y,w,h = self.CalcSizePos(self.GetParent())
        self.SetDimensions(x,y,w,h)

    def OnLeave(self,evt):
        self.SetCursor(wxStockCursor(wxCURSOR_ARROW))

    def OnEnter(self,evt):
        if not self.GetParent().CanSize(not self.side):
            return
        if self.side == MV_HOR:
            self.SetCursor(wxStockCursor(wxCURSOR_SIZENS))
        else:
            self.SetCursor(wxStockCursor(wxCURSOR_SIZEWE))

    def OnMouseMove(self,evt):
        if self.isDrag:
            DrawSash(self.dragTarget,self.px,self.py,self.side)
            self.px,self.py = self.ClientToScreenXY(evt.m_x,evt.m_y)
            self.px,self.py = self.dragTarget.ScreenToClientXY(self.px,self.py)
            DrawSash(self.dragTarget,self.px,self.py,self.side)
        else:
            evt.Skip()

    def OnPress(self,evt):
        self.dragTarget = self.GetParent().SizeTarget(not self.side)
        if self.dragTarget:
            self.isDrag = True
            self.px,self.py = self.ClientToScreenXY(evt.m_x,evt.m_y)
            self.px,self.py = self.dragTarget.ScreenToClientXY(self.px,self.py)
            DrawSash(self.dragTarget,self.px,self.py,self.side)
            self.CaptureMouse()
        else:
            evt.Skip()

    def OnRelease(self,evt):
        if self.isDrag:
            DrawSash(self.dragTarget,self.px,self.py,self.side)
            self.ReleaseMouse()
            self.isDrag = False
            if self.side == MV_HOR:
                self.dragTarget.SizeLeaf(self.GetParent(),
                                         self.py,not self.side)
            else:
                self.dragTarget.SizeLeaf(self.GetParent(),
                                         self.px,not self.side)
            self.dragTarget = None
        else:
            evt.Skip()

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


class MultiCreator(wxWindow):
    def __init__(self,parent,side):
        self.side = side
        x,y,w,h = self.CalcSizePos(parent)
        wxWindow.__init__(self,id = -1,parent = parent,
                          pos = wxPoint(x,y),
                          size = wxSize(w,h),
                          style = wxCLIP_CHILDREN)

        self.px = None                  # Previous X
        self.py = None                  # Previous Y
        self.isDrag = False           # In Dragging

        EVT_LEAVE_WINDOW(self,self.OnLeave)
        EVT_ENTER_WINDOW(self,self.OnEnter)
        EVT_MOTION(self,self.OnMouseMove)
        EVT_LEFT_DOWN(self,self.OnPress)
        EVT_LEFT_UP(self,self.OnRelease)
        EVT_PAINT(self,self.OnPaint)

    def CalcSizePos(self,parent):
        pw,ph = parent.GetSizeTuple()
        if self.side == MV_HOR:
            x = 2
            y = ph - SH_SIZE
            w = CR_SIZE
            h = SH_SIZE
        else:
            x = pw - SH_SIZE
            y = 4 + SH_SIZE             # Make provision for closer
            w = SH_SIZE
            h = CR_SIZE
        return (x,y,w,h)

    def OnSize(self,evt):
        x,y,w,h = self.CalcSizePos(self.GetParent())
        self.SetDimensions(x,y,w,h)

    def OnLeave(self,evt):
        self.SetCursor(wxStockCursor(wxCURSOR_ARROW))

    def OnEnter(self,evt):
        if self.side == MV_HOR:
            self.SetCursor(wxStockCursor(wxCURSOR_HAND))
        else:
            self.SetCursor(wxStockCursor(wxCURSOR_POINT_LEFT))

    def OnMouseMove(self,evt):
        if self.isDrag:
            parent = self.GetParent()
            DrawSash(parent,self.px,self.py,self.side)
            self.px,self.py = self.ClientToScreenXY(evt.m_x,evt.m_y)
            self.px,self.py = parent.ScreenToClientXY(self.px,self.py)
            DrawSash(parent,self.px,self.py,self.side)
        else:
            evt.Skip()

    def OnPress(self,evt):
        self.isDrag = True
        parent = self.GetParent()
        self.px,self.py = self.ClientToScreenXY(evt.m_x,evt.m_y)
        self.px,self.py = parent.ScreenToClientXY(self.px,self.py)
        DrawSash(parent,self.px,self.py,self.side)
        self.CaptureMouse()

    def OnRelease(self,evt):
        if self.isDrag:
            parent = self.GetParent()
            DrawSash(parent,self.px,self.py,self.side)
            self.ReleaseMouse()
            self.isDrag = False

            if self.side == MV_HOR:
                parent.AddLeaf(MV_VER,self.py)
            else:
                parent.AddLeaf(MV_HOR,self.px)
        else:
            evt.Skip()

    def OnPaint(self,evt):
        dc = wxPaintDC(self)
        dc.SetBackground(wxBrush(self.GetBackgroundColour(),wxSOLID))
        dc.Clear()

        highlight = wxPen(wxSystemSettings_GetColour(wxSYS_COLOUR_BTNHIGHLIGHT), 1, wxSOLID)
        shadow = wxPen(wxSystemSettings_GetColour(wxSYS_COLOUR_BTNSHADOW), 1, wxSOLID)
        black = wxPen(wxBLACK,1,wxSOLID)
        w,h = self.GetSizeTuple()
        w -= 1
        h -= 1

        # Draw outline
        dc.SetPen(highlight)
        dc.DrawLine((0,0), (0,h))
        dc.DrawLine((0,0), (w,0))
        dc.SetPen(black)
        dc.DrawLine((0,h), (w+1,h))
        dc.DrawLine((w,0), (w,h))
        dc.SetPen(shadow)
        dc.DrawLine((w-1,2), (w-1,h))

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


class MultiCloser(wxWindow):
    def __init__(self,parent):
        x,y,w,h = self.CalcSizePos(parent)
        wxWindow.__init__(self,id = -1,parent = parent,
                          pos = wxPoint(x,y),
                          size = wxSize(w,h),
                          style = wxCLIP_CHILDREN)

        self.down = False
        self.entered = False

        EVT_LEFT_DOWN(self,self.OnPress)
        EVT_LEFT_UP(self,self.OnRelease)
        EVT_PAINT(self,self.OnPaint)
        EVT_LEAVE_WINDOW(self,self.OnLeave)
        EVT_ENTER_WINDOW(self,self.OnEnter)

    def OnLeave(self,evt):
        self.SetCursor(wxStockCursor(wxCURSOR_ARROW))
        self.entered = False

    def OnEnter(self,evt):
        self.SetCursor(wxStockCursor(wxCURSOR_BULLSEYE))
        self.entered = True

    def OnPress(self,evt):
        self.down = True
        evt.Skip()

    def OnRelease(self,evt):
        if self.down and self.entered:
            self.GetParent().DestroyLeaf()
        else:
            evt.Skip()
        self.down = False

    def OnPaint(self,evt):
        dc = wxPaintDC(self)
        dc.SetBackground(wxBrush(wxRED,wxSOLID))
        dc.Clear()

    def CalcSizePos(self,parent):
        pw,ph = parent.GetSizeTuple()
        x = pw - SH_SIZE
        w = SH_SIZE
        h = SH_SIZE + 2
        y = 1
        return (x,y,w,h)

    def OnSize(self,evt):
        x,y,w,h = self.CalcSizePos(self.GetParent())
        self.SetDimensions(x,y,w,h)


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


class EmptyChild(wxWindow):
    def __init__(self,parent):
        wxWindow.__init__(self,parent,-1, style = wxCLIP_CHILDREN)


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


def DrawSash(win,x,y,direction):
    dc = wxScreenDC()
    dc.StartDrawingOnTopWin(win)
    bmp = wxEmptyBitmap(8,8)
    bdc = wxMemoryDC()
    bdc.SelectObject(bmp)
    bdc.DrawRectangle((-1,-1), (10,10))
    for i in range(8):
        for j in range(8):
            if ((i + j) & 1):
                bdc.DrawPoint((i,j))

    brush = wxBrush(wxColour(0,0,0))
    brush.SetStipple(bmp)

    dc.SetBrush(brush)
    dc.SetLogicalFunction(wxXOR)

    body_w,body_h = win.GetClientSizeTuple()

    if y < 0:
        y = 0
    if y > body_h:
        y = body_h
    if x < 0:
        x = 0
    if x > body_w:
        x = body_w

    if direction == MV_HOR:
        x = 0
    else:
        y = 0

    x,y = win.ClientToScreenXY(x,y)

    w = body_w
    h = body_h

    if direction == MV_HOR:
        dc.DrawRectangle((x,y-2), (w,4))
    else:
        dc.DrawRectangle((x-2,y), (4,h))

    dc.EndDrawingOnTop()
