]> git.saurik.com Git - wxWidgets.git/commitdiff
Added the wx.lib.splitter module, which contains the
authorRobin Dunn <robin@alldunn.com>
Sat, 11 Jun 2005 19:35:49 +0000 (19:35 +0000)
committerRobin Dunn <robin@alldunn.com>
Sat, 11 Jun 2005 19:35:49 +0000 (19:35 +0000)
MultiSplitterWindow class.  This class is much like the standard
wx.SplitterWindow class, except it allows more than one split, so it
can manage more than two child windows.

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@34634 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

wxPython/demo/Main.py
wxPython/demo/MultiSplitterWindow.py [new file with mode: 0644]
wxPython/docs/CHANGES.txt
wxPython/wx/lib/splitter.py [new file with mode: 0644]

index c951870a3b3752d80b42d9e53c471cb620041a61..152a858a44646e4edd3231332691fc6c0aeb285a 100644 (file)
@@ -50,6 +50,7 @@ _treeList = [
         'FoldPanelBar',
         'GIFAnimationCtrl',
         'HyperLinkCtrl',
+        'MultiSplitterWindow',
         ]),
 
     # managed windows == things with a (optional) caption you can close
@@ -160,6 +161,7 @@ _treeList = [
         'HyperLinkCtrl',
         'IntCtrl',
         'MediaCtrl',
+        'MultiSplitterWindow',
         'MVCTree',   
         'MaskedEditControls',
         'MaskedNumCtrl',
diff --git a/wxPython/demo/MultiSplitterWindow.py b/wxPython/demo/MultiSplitterWindow.py
new file mode 100644 (file)
index 0000000..e8a3f15
--- /dev/null
@@ -0,0 +1,167 @@
+
+import wx
+from wx.lib.splitter import MultiSplitterWindow
+
+#----------------------------------------------------------------------
+
+class SamplePane(wx.Panel):
+    """
+    Just a simple test window to put into the splitter.
+    """
+    def __init__(self, parent, colour, label):
+        wx.Panel.__init__(self, parent, style=wx.BORDER_SUNKEN)
+        self.SetBackgroundColour(colour)
+        wx.StaticText(self, -1, label, (5,5))
+
+    def SetOtherLabel(self, label):
+        wx.StaticText(self, -1, label, (5, 30))
+
+
+
+class ControlPane(wx.Panel):
+    def __init__(self, parent):
+        wx.Panel.__init__(self, parent)
+
+        hvBox = wx.RadioBox(self, -1, "Orientation",
+                            choices=["Horizontal", "Vertical"],
+                            style=wx.RA_SPECIFY_COLS,
+                            majorDimension=1)
+        hvBox.SetSelection(0)
+        self.Bind(wx.EVT_RADIOBOX, self.OnSetHV, hvBox)
+        
+        luCheck = wx.CheckBox(self, -1, "Live Update")
+        luCheck.SetValue(True)
+        self.Bind(wx.EVT_CHECKBOX, self.OnSetLiveUpdate, luCheck)
+
+        btn = wx.Button(self, -1, "Swap 2 && 4")
+        self.Bind(wx.EVT_BUTTON, self.OnSwapButton, btn)
+
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        sizer.Add(hvBox)
+        sizer.Add(luCheck, 0, wx.TOP, 5)
+        sizer.Add(btn, 0, wx.TOP, 5)
+        border = wx.BoxSizer()
+        border.Add(sizer, 1, wx.EXPAND|wx.ALL, 5)
+        self.SetSizer(border)
+
+
+    def OnSetHV(self, evt):
+        rb = evt.GetEventObject()
+        self.GetParent().SetOrientation(rb.GetSelection())
+        
+
+    def OnSetLiveUpdate(self, evt):
+        check = evt.GetEventObject()
+        self.GetParent().SetLiveUpdate(check.GetValue())
+
+
+    def OnSwapButton(self, evt):
+        self.GetParent().Swap2and4()
+        
+
+
+class TestPanel(wx.Panel):
+    def __init__(self, parent, log):
+        self.log = log
+        wx.Panel.__init__(self, parent, -1)
+
+        cp = ControlPane(self)
+        
+        splitter = MultiSplitterWindow(self, style=wx.SP_LIVE_UPDATE)
+        self.splitter = splitter
+        sizer = wx.BoxSizer(wx.HORIZONTAL)
+        sizer.Add(cp)
+        sizer.Add(splitter, 1, wx.EXPAND)
+        self.SetSizer(sizer)
+
+
+        p1 = SamplePane(splitter, "pink", "Panel One")
+        p1.SetOtherLabel(
+            "There are two sash\n"
+            "drag modes. Try\n"
+            "dragging with and\n"
+            "without the Shift\n"
+            "key held down."
+            )
+        splitter.AppendWindow(p1, 140)
+
+        p2 = SamplePane(splitter, "sky blue", "Panel Two")
+        p2.SetOtherLabel("This window\nhas a\nminsize.")
+        p2.SetMinSize(p2.GetBestSize())
+        splitter.AppendWindow(p2, 150)
+
+        p3 = SamplePane(splitter, "yellow", "Panel Three")
+        splitter.AppendWindow(p3, 125)
+
+        p4 = SamplePane(splitter, "Lime Green", "Panel Four")
+        splitter.AppendWindow(p4)
+
+        self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnChanged)
+        self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, self.OnChanging)
+
+
+    def OnChanging(self, evt):
+        self.log.write( "Changing sash:%d  %s\n" %
+                        (evt.GetSashIdx(), evt.GetSashPosition()))
+        # This is one way to control the sash limits
+        #if evt.GetSashPosition() < 50:
+        #    evt.Veto()
+
+        # Or you can reset the sash position to whatever you want
+        #if evt.GetSashPosition() < 5:
+        #    evt.SetSashPosition(25)
+
+
+    def OnChanged(self, evt):
+        self.log.write( "Changed sash:%d  %s\n" %
+                        (evt.GetSashIdx(), evt.GetSashPosition()))
+
+            
+    def SetOrientation(self, value):
+        if value:
+            self.splitter.SetOrientation(wx.VERTICAL)
+        else:
+            self.splitter.SetOrientation(wx.HORIZONTAL)
+        self.splitter.SizeWindows()
+
+            
+    def SetLiveUpdate(self, enable):
+        if enable:
+            self.splitter.SetWindowStyle(wx.SP_LIVE_UPDATE)
+        else:
+            self.splitter.SetWindowStyle(0)
+            
+
+    def Swap2and4(self):
+        win2 = self.splitter.GetWindow(1)
+        win4 = self.splitter.GetWindow(3)
+        self.splitter.ExchangeWindows(win2, win4)
+
+#----------------------------------------------------------------------
+
+def runTest(frame, nb, log):
+    win = TestPanel(nb, log)
+    return win
+
+#----------------------------------------------------------------------
+
+
+
+overview = """<html><body>
+<h2><center>MultiSplitterWindow</center></h2>
+
+This class is very similar to wx.SplitterWindow except that it
+allows for more than two windows and more than one sash.  Many of
+the same styles, constants, and methods behave the same as in
+wx.SplitterWindow.
+
+</body></html>
+"""
+
+
+
+if __name__ == '__main__':
+    import sys,os
+    import run
+    run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
+
index 96c295af80b2255d7276c0e59cb7326bf8b0f7a4..3a9e0789ceb1e8d91f0061701e945b5a62786432 100644 (file)
@@ -13,6 +13,17 @@ platforms.
 
 Added wx.RendererNative class.
 
+wxMSW: Always set flat toolbar style, even under XP with themes: this
+is necessary or separators aren't shown at all.
+
+Fixes for bug #1217872, pydocview.DocService not correctly initialized
+
+Fix for bug #1217874, Error in parameter name in DocManager.CreateView
+
+Added the wx.lib.splitter module, which contains the
+MultiSplitterWindow class.  This class is much like the standard
+wx.SplitterWindow class, except it allows more than one split, so it
+can manage more than two child windows.
 
 
 
diff --git a/wxPython/wx/lib/splitter.py b/wxPython/wx/lib/splitter.py
new file mode 100644 (file)
index 0000000..2930cba
--- /dev/null
@@ -0,0 +1,827 @@
+#----------------------------------------------------------------------
+# Name:        wx.lib.splitter
+# Purpose:     A class similar to wx.SplitterWindow but that allows more
+#              a single split
+#
+# Author:      Robin Dunn
+#
+# Created:     9-June-2005
+# RCS-ID:      $Id$
+# Copyright:   (c) 2005 by Total Control Software
+# Licence:     wxWindows license
+#----------------------------------------------------------------------
+"""
+This module provides the `MultiSplitterWindow` class, which is very
+similar to the standard `wx.SplitterWindow` except it can be split
+more than once.
+"""
+
+import wx
+import sys
+
+_RENDER_VER = (2,6,1,1)
+
+#----------------------------------------------------------------------
+
+class MultiSplitterWindow(wx.PyPanel):
+    """
+    This class is very similar to `wx.SplitterWindow` except that it
+    allows for more than two windows and more than one sash.  Many of
+    the same styles, constants, and methods behave the same as in
+    wx.SplitterWindow.  The key differences are seen in the methods
+    that deal with the child windows manage by the splitter, and also
+    those that deal with the sash positions.  In most cases you will
+    need to pass an index value to tell the class which window or sash
+    you are refering to.
+
+    The concept of the sash position is also different than in
+    wx.SplitterWindow.  Since the wx.Splitterwindow has only one sash
+    you can think of it's position as either relative to the whole
+    splitter window, or as relative to the first window pane managed
+    by the splitter.  Once there are more than one sash then the
+    distinciton between the two concepts needs to be clairified.  I've
+    chosen to use the second definition, and sash positions are the
+    distance (either horizontally or vertically) from the origin of
+    the window just before the sash in the splitter stack.
+
+    NOTE: These things are not yet supported:
+
+        * Using negative sash positions to indicate a position offset
+          from the end.
+          
+        * User controlled unsplitting (with double clicks on the sash
+          or dragging a sash until the pane size is zero.)
+          
+        * Sash gravity
+       
+    """
+    def __init__(self, parent, id=-1,
+                 pos = wx.DefaultPosition, size = wx.DefaultSize,
+                 style = 0, name="multiSplitter"):
+        
+        # always turn on tab traversal
+        style |= wx.TAB_TRAVERSAL
+
+        # and turn off any border styles
+        style &= ~wx.BORDER_MASK
+        style |= wx.BORDER_NONE
+
+        # initialize the base class
+        wx.PyPanel.__init__(self, parent, id, pos, size, style, name)
+        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
+
+        # initialize data members
+        self._windows = []
+        self._sashes = []
+        self._pending = {}
+        self._permitUnsplitAlways = self.HasFlag(wx.SP_PERMIT_UNSPLIT)
+        self._orient = wx.HORIZONTAL
+        self._dragMode = wx.SPLIT_DRAG_NONE
+        self._activeSash = -1
+        self._oldX = 0
+        self._oldY = 0
+        self._checkRequestedSashPosition = False
+        self._minimumPaneSize = 0
+        self._sashCursorWE = wx.StockCursor(wx.CURSOR_SIZEWE)
+        self._sashCursorNS = wx.StockCursor(wx.CURSOR_SIZENS)
+        self._sashTrackerPen = wx.Pen(wx.BLACK, 2, wx.SOLID)
+        self._needUpdating = False
+        self._isHot = False
+
+        # Bind event handlers
+        self.Bind(wx.EVT_PAINT,        self._OnPaint)
+        self.Bind(wx.EVT_IDLE,         self._OnIdle)
+        self.Bind(wx.EVT_SIZE,         self._OnSize)
+        self.Bind(wx.EVT_MOUSE_EVENTS, self._OnMouse)
+
+
+
+    def SetOrientation(self, orient):
+        """
+        Set whether the windows managed by the splitter will be
+        stacked vertically or horizontally.  The default is
+        horizontal.
+        """
+        assert orient in [ wx.VERTICAL, wx.HORIZONTAL ]
+        self._orient = orient
+
+    def GetOrientation(self):
+        """
+        Returns the current orientation of the splitter, either
+        wx.VERTICAL or wx.HORIZONTAL.
+        """
+        return self._orient
+
+
+    def SetMinimumPaneSize(self, minSize):
+        """
+        Set the smallest size that any pane will be allowed to be
+        resized to.
+        """
+        self._minimumPaneSize = minSize
+
+    def GetMinimumPaneSize(self):
+        """
+        Returns the smallest allowed size for a window pane.
+        """
+        return self._minimumPaneSize
+
+    
+
+    def AppendWindow(self, window, sashPos=-1):
+        """
+        Add a new window to the splitter.  If sashPos is given then it is the 
+        """
+        self.InsertWindow(sys.maxint, window, sashPos)
+
+
+    def InsertWindow(self, idx, window, sashPos=-1):
+        """
+        """
+        assert window not in self._windows, "A window can only be in the splitter once!"
+        self._windows.insert(idx, window)
+        self._sashes.insert(idx, -1)
+        if not window.IsShown():
+            window.Show()
+        if sashPos != -1:
+            self._pending[window] = sashPos
+        self._checkRequestedSashPosition = False
+        self._SizeWindows()
+
+
+    def DetachWindow(self, window):
+        """
+        Removes the window from the stack of windows managed by the
+        splitter.  The window will still exist so you should `Hide` or
+        `Destroy` it as needed.
+        """
+        assert window in self._windows, "Unknown window!"
+        idx = self._windows.index(window)
+        del self._windows[idx]
+        del self._sashes[idx]
+        self._SizeWindows()
+
+
+    def ReplaceWindow(self, oldWindow, newWindow):
+        """
+        Replaces oldWindow (which is currently being managed by the
+        splitter) with newWindow.  The oldWindow window will still
+        exist so you should `Hide` or `Destroy` it as needed.
+        """
+        assert oldWindow in self._windows, "Unknown window!"
+        idx = self._windows.index(oldWindow)
+        self._windows[idx] = newWindow
+        if not newWindow.IsShown():
+            newWindow.Show()
+        self._SizeWindows()
+
+
+    def ExchangeWindows(self, window1, window2):
+        """
+        Trade the positions in the splitter of the two windows.
+        """
+        assert window1 in self._windows, "Unknown window!"
+        assert window2 in self._windows, "Unknown window!"
+        idx1 = self._windows.index(window1)
+        idx2 = self._windows.index(window2)
+        self._windows[idx1] = window2
+        self._windows[idx2] = window1
+        self._SizeWindows()
+
+
+    def GetWindow(self, idx):
+        """
+        Returns the idx'th window being managed by the splitter.
+        """
+        assert idx < len(self._windows)
+        return self._windows[idx]
+
+
+    def GetSashPosition(self, idx):
+        """
+        Returns the position of the idx'th sash, measured from the
+        left/top of the window preceding the sash.
+        """
+        assert idx < len(self._sashes)
+        return self._sashes[idx]
+
+
+    def SizeWindows(self):
+        """
+        Reposition and size the windows managed by the splitter.
+        Useful when windows have been added/removed or when styles
+        have been changed.
+        """
+        self._SizeWindows()
+        
+
+    def DoGetBestSize(self):
+        """
+        Overridden base class virtual.  Determines the best size of
+        the control based on the best sizes of the child windows.
+        """
+        best = wx.Size(0,0)
+        if not self._windows:
+            best = wx.Size(10,10)
+
+        sashsize = self._GetSashSize()
+        if self._orient == wx.HORIZONTAL:
+            for win in self._windows:
+                winbest = win.GetAdjustedBestSize()
+                best.width += max(self._minimumPaneSize, winbest.width)
+                best.height = max(best.height, winbest.height)
+            best.width += sashsize * (len(self._windows)-1)
+
+        else:
+            for win in self._windows:
+                winbest = win.GetAdjustedBestSize()
+                best.height += max(self._minimumPaneSize, winbest.height)
+                best.width = max(best.width, winbest.width)
+            best.height += sashsize * (len(self._windows)-1)
+            
+        border = 2 * self._GetBorderSize()
+        best.width += border
+        best.height += border
+        return best
+
+    # -------------------------------------
+    # Event handlers
+    
+    def _OnPaint(self, evt):
+        dc = wx.PaintDC(self)
+        self._DrawSash(dc)
+
+
+    def _OnSize(self, evt):
+        parent = wx.GetTopLevelParent(self)
+        if parent.IsIconized():
+            evt.Skip()
+            return
+        self._SizeWindows()
+
+
+    def _OnIdle(self, evt):
+        evt.Skip()
+        # if this is the first idle time after a sash position has
+        # potentially been set, allow _SizeWindows to check for a
+        # requested size.
+        if not self._checkRequestedSashPosition:
+            self._checkRequestedSashPosition = True
+            self._SizeWindows()
+
+        if self._needUpdating:
+            self._SizeWindows()
+
+            
+
+    def _OnMouse(self, evt):
+        if self.HasFlag(wx.SP_NOSASH):
+            return
+
+        x, y = evt.GetPosition()
+        isLive = self.HasFlag(wx.SP_LIVE_UPDATE)
+        adjustNeighbor = evt.ShiftDown()
+
+        # LeftDown: set things up for dragging the sash
+        if evt.LeftDown() and self._SashHitTest(x, y) != -1:
+            self._activeSash = self._SashHitTest(x, y)
+            self._dragMode = wx.SPLIT_DRAG_DRAGGING
+
+            self.CaptureMouse()
+            self._SetResizeCursor()
+
+            if not isLive:
+                self._pendingPos = (self._sashes[self._activeSash],
+                                    self._sashes[self._activeSash+1])
+                self._DrawSashTracker(x, y)
+
+            self._oldX = x
+            self._oldY = y
+            return
+
+        # LeftUp: Finsish the drag
+        elif evt.LeftUp() and self._dragMode == wx.SPLIT_DRAG_DRAGGING:
+            self._dragMode = wx.SPLIT_DRAG_NONE
+            self.ReleaseMouse()
+            self.SetCursor(wx.STANDARD_CURSOR)
+
+            if not isLive:
+                # erase the old tracker
+                self._DrawSashTracker(self._oldX, self._oldY)
+
+            diff = self._GetMotionDiff(x, y)
+
+            # determine if we can change the position
+            if isLive:
+                oldPos1, oldPos2 = (self._sashes[self._activeSash],
+                                    self._sashes[self._activeSash+1])
+            else:
+                oldPos1, oldPos2 = self._pendingPos
+            newPos1, newPos2 = self._OnSashPositionChanging(self._activeSash,
+                                                            oldPos1 + diff,
+                                                            oldPos2 - diff,
+                                                            adjustNeighbor)
+            if newPos1 == -1:
+                # the change was not allowed
+                return
+
+            # TODO: check for unsplit?
+
+            self._SetSashPositionAndNotify(self._activeSash, newPos1, newPos2, adjustNeighbor)
+            self._activeSash = -1
+            self._pendingPos = (-1, -1)
+            self._SizeWindows()
+
+        # Entering or Leaving a sash: Change the cursor
+        elif (evt.Moving() or evt.Leaving() or evt.Entering()) and self._dragMode == wx.SPLIT_DRAG_NONE:
+            if evt.Leaving() or self._SashHitTest(x, y) == -1:
+                self._OnLeaveSash()
+            else:
+                self._OnEnterSash()
+
+        # Dragging the sash
+        elif evt.Dragging() and self._dragMode == wx.SPLIT_DRAG_DRAGGING:
+            diff = self._GetMotionDiff(x, y)
+            if not diff:
+                return  # mouse didn't move far enough
+
+            # determine if we can change the position
+            if isLive:
+                oldPos1, oldPos2 = (self._sashes[self._activeSash],
+                                    self._sashes[self._activeSash+1])
+            else:
+                oldPos1, oldPos2 = self._pendingPos
+            newPos1, newPos2 = self._OnSashPositionChanging(self._activeSash,
+                                                            oldPos1 + diff,
+                                                            oldPos2 - diff,
+                                                            adjustNeighbor)
+            if newPos1 == -1:
+                # the change was not allowed
+                return
+
+            if newPos1 == self._sashes[self._activeSash]:
+                return  # nothing was changed
+
+            if not isLive:
+                # erase the old tracker
+                self._DrawSashTracker(self._oldX, self._oldY)
+           
+            if self._orient == wx.HORIZONTAL:
+                 x = self._SashToCoord(self._activeSash, newPos1)
+            else:
+                 y = self._SashToCoord(self._activeSash, newPos1)
+
+            # Remember old positions
+            self._oldX = x
+            self._oldY = y
+
+            if not isLive:
+                # draw a new tracker
+                self._pendingPos = (newPos1, newPos2)
+                self._DrawSashTracker(self._oldX, self._oldY)
+            else:
+                self._DoSetSashPosition(self._activeSash, newPos1, newPos2, adjustNeighbor)
+                self._needUpdating = True
+
+
+    # -------------------------------------
+    # Internal helpers
+    
+    def _RedrawIfHotSensitive(self, isHot):
+        if not wx.VERSION >= _RENDER_VER:
+            return
+        if wx.RendererNative.Get().GetSplitterParams(self).isHotSensitive:
+            self._isHot = isHot
+            dc = wx.ClientDC(self)
+            self._DrawSash(dc)
+
+
+    def _OnEnterSash(self):
+        self._SetResizeCursor()
+        self._RedrawIfHotSensitive(True)
+
+
+    def _OnLeaveSash(self):
+        self.SetCursor(wx.STANDARD_CURSOR)
+        self._RedrawIfHotSensitive(False)
+
+
+    def _SetResizeCursor(self):
+        if self._orient == wx.HORIZONTAL:
+            self.SetCursor(self._sashCursorWE)
+        else:
+            self.SetCursor(self._sashCursorNS)
+
+
+    def _OnSashPositionChanging(self, idx, newPos1, newPos2, adjustNeighbor):
+        # TODO: check for possibility of unsplit (pane size becomes zero)
+
+        # make sure that minsizes are honored
+        newPos1, newPos2 = self._AdjustSashPosition(idx, newPos1, newPos2, adjustNeighbor)
+
+        # sanity check
+        if newPos1 <= 0:
+            newPos2 += newPos1
+            newPos1 = 0 
+
+        # send the events
+        evt = MultiSplitterEvent(
+            wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGING, self)
+        evt.SetSashIdx(idx)
+        evt.SetSashPosition(newPos1)
+        if not self._DoSendEvent(evt):
+            # the event handler vetoed the change
+            newPos1 = -1
+        else:
+            # or it might have changed the value
+            newPos1 = evt.GetSashPosition()
+
+        if adjustNeighbor and newPos1 != -1:
+            evt.SetSashIdx(idx+1)
+            evt.SetSashPosition(newPos2)
+            if not self._DoSendEvent(evt):
+                # the event handler vetoed the change
+                newPos2 = -1
+            else:
+                # or it might have changed the value
+                newPos2 = evt.GetSashPosition()
+            if newPos2 == -1:
+                newPos1 = -1
+
+        return (newPos1, newPos2)
+
+
+    def _AdjustSashPosition(self, idx, newPos1, newPos2=-1, adjustNeighbor=False):
+        total = newPos1 + newPos2
+
+        # these are the windows on either side of the sash
+        win1 = self._windows[idx]
+        win2 = self._windows[idx+1]
+
+        # make adjustments for window min sizes
+        minSize = self._GetWindowMin(win1)
+        if minSize == -1 or self._minimumPaneSize > minSize:
+            minSize = self._minimumPaneSize
+        minSize += self._GetBorderSize()
+        if newPos1 < minSize:
+            newPos1 = minSize
+            newPos2 = total - newPos1
+
+        if adjustNeighbor:
+            minSize = self._GetWindowMin(win2)
+            if minSize == -1 or self._minimumPaneSize > minSize:
+                minSize = self._minimumPaneSize
+            minSize += self._GetBorderSize()
+            if newPos2 < minSize:
+                newPos2 = minSize
+                newPos1 = total - newPos2
+        
+        return (newPos1, newPos2)
+
+
+    def _DoSetSashPosition(self, idx, newPos1, newPos2=-1, adjustNeighbor=False):
+        newPos1, newPos2 = self._AdjustSashPosition(idx, newPos1, newPos2, adjustNeighbor)
+        if newPos1 == self._sashes[idx]:
+            return False
+        self._sashes[idx] = newPos1
+        if adjustNeighbor:
+            self._sashes[idx+1] = newPos2
+        return True
+        
+
+    def _SetSashPositionAndNotify(self, idx, newPos1, newPos2=-1, adjustNeighbor=False):
+        # TODO:  what is the thing about _requestedSashPosition for?
+
+        self._DoSetSashPosition(idx, newPos1, newPos2, adjustNeighbor)
+
+        evt = MultiSplitterEvent(
+            wx.wxEVT_COMMAND_SPLITTER_SASH_POS_CHANGED, self)
+        evt.SetSashIdx(idx)
+        evt.SetSashPosition(newPos1)
+        self._DoSendEvent(evt)
+
+        if adjustNeighbor:
+            evt.SetSashIdx(idx+1)
+            evt.SetSashPosition(newPos2)
+            self._DoSendEvent(evt)
+
+
+    def _GetMotionDiff(self, x, y):
+        # find the diff from the old pos
+        if self._orient == wx.HORIZONTAL:
+            diff = x - self._oldX
+        else:
+            diff = y - self._oldY
+        return diff
+
+
+    def _SashToCoord(self, idx, sashPos):
+        coord = 0
+        for i in range(idx):
+            coord += self._sashes[i]
+            coord += self._GetSashSize()
+        coord += sashPos
+        return coord
+
+
+    def _GetWindowMin(self, window):
+        if self._orient == wx.HORIZONTAL:
+            return window.GetMinWidth()
+        else:
+            return window.GetMinHeight()
+        
+            
+    def _GetSashSize(self):
+        if self.HasFlag(wx.SP_NOSASH):
+            return 0
+        if wx.VERSION >= _RENDER_VER:
+            return wx.RendererNative.Get().GetSplitterParams(self).widthSash
+        else:
+            return 5
+
+
+    def _GetBorderSize(self):
+        if wx.VERSION >= _RENDER_VER:
+            return wx.RendererNative.Get().GetSplitterParams(self).border
+        else:
+            return 0
+        
+
+    def _DrawSash(self, dc):
+        if wx.VERSION >= _RENDER_VER:
+            if self.HasFlag(wx.SP_3DBORDER):
+                wx.RendererNative.Get().DrawSplitterBorder(
+                    self, dc, self.GetClientRect())
+
+        # if there are no splits then we're done.
+        if len(self._windows) < 2:
+            return
+
+        # if we are not supposed to use a sash then we're done.
+        if self.HasFlag(wx.SP_NOSASH):
+            return
+
+        # Reverse the sense of the orientation, in this case it refers
+        # to the direction to draw the sash not the direction that
+        # windows are stacked.
+        orient = { wx.HORIZONTAL : wx.VERTICAL,
+                   wx.VERTICAL : wx.HORIZONTAL }[self._orient]
+
+        flag = 0
+        if self._isHot:
+            flag = wx.CONTROL_CURRENT
+
+        pos = 0
+        for sash in self._sashes[:-1]:
+            pos += sash
+            if wx.VERSION >= _RENDER_VER:
+                wx.RendererNative.Get().DrawSplitterSash(self, dc,
+                                                         self.GetClientSize(),
+                                                         pos, orient, flag)
+            else:
+                dc.SetPen(wx.TRANSPARENT_PEN)
+                dc.SetBrush(wx.Brush(self.GetBackgroundColour()))
+                sashsize = self._GetSashSize()
+                if orient == wx.VERTICAL:
+                    x = pos
+                    y = 0
+                    w = sashsize
+                    h = self.GetClientSize().height
+                else:
+                    x = 0
+                    y = pos
+                    w = self.GetClientSize().width
+                    h = sashsize
+                dc.DrawRectangle(x, y, w, h)
+
+            pos += self._GetSashSize()
+
+
+    def _DrawSashTracker(self, x, y):
+        # Draw a line to represent the dragging sash, for when not
+        # doing live updates
+        w, h = self.GetClientSize()
+        dc = wx.ScreenDC()
+
+        if self._orient == wx.HORIZONTAL:
+            x1 = x
+            y1 = 2
+            x2 = x
+            y2 = h-2
+            if x1 > w:
+                x1 = w
+                x2 = w
+            elif x1 < 0:
+                x1 = 0
+                x2 = 0
+        else:
+            x1 = 2
+            y1 = y
+            x2 = w-2
+            y2 = y
+            if y1 > h:
+                y1 = h
+                y2 = h
+            elif y1 < 0:
+                y1 = 0
+                y2 = 0
+
+        x1, y1 = self.ClientToScreenXY(x1, y1)
+        x2, y2 = self.ClientToScreenXY(x2, y2)
+     
+        dc.SetLogicalFunction(wx.INVERT)
+        dc.SetPen(self._sashTrackerPen)
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+        dc.DrawLine(x1, y1, x2, y2)
+        dc.SetLogicalFunction(wx.COPY)
+        
+
+    def _SashHitTest(self, x, y, tolerance=5):
+        # if there are no splits then we're done.
+        if len(self._windows) < 2:
+            return -1
+
+        if self._orient == wx.HORIZONTAL:
+            z = x
+        else:
+            z = y
+
+        pos = 0
+        for idx, sash in enumerate(self._sashes[:-1]):
+            pos += sash
+            hitMin = pos - tolerance
+            hitMax = pos + self._GetSashSize() + tolerance
+            
+            if z >= hitMin and z <= hitMax:
+                return idx
+            
+            pos += self._GetSashSize() 
+
+        return -1
+
+
+    def _SizeWindows(self):
+        # no windows yet?
+        if not self._windows:
+            return
+
+        # are there any pending size settings?
+        for window, spos in self._pending.items():
+            idx = self._windows.index(window)
+            # TODO: this may need adjusted to make sure they all fit
+            # in the current client size
+            self._sashes[idx] = spos
+            del self._pending[window]
+
+        # are there any that still have a -1?
+        for idx, spos in enumerate(self._sashes[:-1]):
+            if spos == -1:
+                # TODO: this should also be adjusted
+                self._sashes[idx] = 100
+        
+        cw, ch = self.GetClientSize()
+        border = self._GetBorderSize()
+        sash   = self._GetSashSize()
+        
+        if len(self._windows) == 1:
+            # there's only one, it's an easy layout
+            self._windows[0].SetDimensions(border, border,
+                                           cw - 2*border, ch - 2*border)
+        else:
+            for win in self._windows:
+                win.Freeze()
+            if self._orient == wx.HORIZONTAL:
+                x = y = border
+                h = ch - 2*border
+                for idx, spos in enumerate(self._sashes[:-1]):
+                    self._windows[idx].SetDimensions(x, y, spos, h)
+                    x += spos + sash
+                # last one takes the rest of the space. TODO make this configurable
+                last = cw - 2*border - x
+                self._windows[idx+1].SetDimensions(x, y, last, h)
+                if last > 0:
+                    self._sashes[idx+1] = last
+            else:
+                x = y = border
+                w = cw - 2*border
+                for idx, spos in enumerate(self._sashes[:-1]):
+                    self._windows[idx].SetDimensions(x, y, w, spos)
+                    y += spos + sash
+                # last one takes the rest of the space. TODO make this configurable
+                last = ch - 2*border - y
+                self._windows[idx+1].SetDimensions(x, y, w, last)
+                if last > 0:
+                    self._sashes[idx+1] = last
+            for win in self._windows:
+                win.Thaw()
+                
+        self._DrawSash(wx.ClientDC(self))
+        self._needUpdating = False
+
+
+    def _DoSendEvent(self, evt):
+        return not self.GetEventHandler().ProcessEvent(evt) or evt.IsAllowed()
+    
+#----------------------------------------------------------------------
+
+class MultiSplitterEvent(wx.PyCommandEvent):
+    """
+    This event class is almost the same as `wx.SplitterEvent` except
+    it adds an accessor for the sash index that is being changed.  The
+    same event type IDs and event binders are used as with
+    `wx.SplitterEvent`.
+    """
+    def __init__(self, type=wx.wxEVT_NULL, splitter=None):
+        wx.PyCommandEvent.__init__(self, type)
+        if splitter:
+            self.SetEventObject(splitter)
+            self.SetId(splitter.GetId())
+        self.sashIdx = -1
+        self.sashPos = -1
+        self.isAllowed = True
+
+    def SetSashIdx(self, idx):
+        self.sashIdx = idx
+
+    def SetSashPosition(self, pos):
+        self.sashPos = pos
+
+    def GetSashIdx(self):
+        return self.sashIdx
+
+    def GetSashPosition(self):
+        return self.sashPos
+
+    # methods from wx.NotifyEvent
+    def Veto(self):
+        self.isAllowed = False
+    def Allow(self):
+        self.isAllowed = True
+    def IsAllowed(self):
+        return self.isAllowed
+        
+
+
+#----------------------------------------------------------------------
+
+
+
+
+if __name__ == "__main__":
+
+    def OnChanged(evt):
+        print "Changed:", evt.GetSashIdx(), evt.GetSashPosition()
+    def OnChanging(evt):
+        print "Changing:", evt.GetSashIdx(), evt.GetSashPosition()
+        
+    
+    app = wx.App(0)
+    frm = wx.Frame(None, title="tester", size=(640,480))
+
+    #frm.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, OnChanged)
+    #frm.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, OnChanging)
+
+    #sty = 0
+    #sty = wx.SP_3DBORDER
+    #sty = wx.SP_NOSASH
+    sty = wx.SP_LIVE_UPDATE
+
+    splitter = MultiSplitterWindow(frm, style=sty)
+    #splitter.SetOrientation(wx.VERTICAL)
+    splitter.SetMinimumPaneSize(25)
+
+    #sty = wx.BORDER_NONE
+    #sty = wx.BORDER_SIMPLE
+    sty = wx.BORDER_SUNKEN
+    
+    p1 = wx.Window(splitter, style=sty)
+    p1.SetBackgroundColour("pink")
+    wx.StaticText(p1, -1, "Panel One", (5,5))
+
+    p2 = wx.Window(splitter, style=sty)
+    p2.SetBackgroundColour("sky blue")
+    wx.StaticText(p2, -1, "Panel Two", (5,5))
+    p2.SetMinSize((50,50))
+    
+    p3 = wx.Window(splitter, style=sty)
+    p3.SetBackgroundColour("yellow")
+    wx.StaticText(p3, -1, "Panel Three", (5,5))
+
+    splitter.AppendWindow(p1, 100)
+    splitter.AppendWindow(p2, 200)
+    splitter.AppendWindow(p3)
+
+    for x in range(3):
+        p = wx.Window(splitter, style=sty)
+        p.SetBackgroundColour("white")
+        wx.StaticText(p, -1, str(4+x), (5,5))
+        splitter.AppendWindow(p, 50)
+
+    #sizer = wx.BoxSizer()
+    #sizer.Add(splitter, 1, wx.EXPAND)
+    #frm.SetSizerAndFit(sizer)
+
+    frm.Show()
+    app.MainLoop()
+
+