]> git.saurik.com Git - wxWidgets.git/blobdiff - utils/wxPython/lib/mvctree.py
patches for XPM/TIFF compilation with BC++ from Ricky Gonzales
[wxWidgets.git] / utils / wxPython / lib / mvctree.py
index 49eb4b7980c0a9d778cbd0c2761a38bc7c2c6b9f..05ff67ab9602b7e5443fcabd74843ef217fec1d1 100644 (file)
@@ -2,10 +2,8 @@
 wxMVCTree is a control which handles hierarchical data. It is constructed
 in model-view-controller architecture, so the display of that data, and
 the content of the data can be changed greatly without affecting the other parts.
-This module contains the wxMVCTree class (the 'controller' of the MVC trio)
-and PathfinderNode, which it uses internally to manage its info.
 
-Pathfinder actually is even more configurable than MVC normally implies, because
+wxMVCTree actually is even more configurable than MVC normally implies, because
 almost every aspect of it is pluggable:
     wxMVCTree - Overall controller, and the window that actually gets placed
     in the GUI.
@@ -24,7 +22,7 @@ Author/Maintainer - Bryn Keller <xoltar@starship.python.net>
 
 #------------------------------------------------------------------------
 from wxPython.wx import *
-import os, sys
+import os, sys, traceback
 #------------------------------------------------------------------------
 
 class MVCTreeNode:
@@ -92,7 +90,9 @@ class LayoutEngine:
     """
     def __init__(self, tree):
         self.tree = tree
-    def layout(self, node):
+    def Layout(self, node):
+        raise NotImplementedError
+    def GetNodeList(self):
         raise NotImplementedError
 
 class Transform:
@@ -101,7 +101,7 @@ class Transform:
     """
     def __init__(self, tree):
         self.tree = tree
-    def transform(self, node, offset, rotation):
+    def Transform(self, node, offset, rotation):
         """
         This method should only change the projx and projy attributes of
         the node. These represent the position of the node as it should
@@ -110,6 +110,13 @@ class Transform:
         """
         raise NotImplementedError
 
+    def GetSize(self):
+        """
+        Returns the size of the entire tree as laid out and transformed
+        as a tuple
+        """
+        raise NotImplementedError
+
 class Painter:
     """
     This is the interface that wxMVCTree expects from painters. All painters should
@@ -122,9 +129,7 @@ class Painter:
         self.fgcolor = wxNamedColour("BLUE")
         self.linecolor = wxNamedColour("GREY")
         self.font = wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, false)
-        self.knobs = []
-        self.rectangles = []
-        self.minx = self.maxx = self.miny = self.maxy = 0
+        self.bmp = None
 
     def GetFont(self):
         return self.font
@@ -132,8 +137,11 @@ class Painter:
     def SetFont(self, font):
         self.font = font
         self.tree.Refresh()
-
-    def paint(self, dc, node):
+    def GetBuffer(self):
+        return self.bmp
+    def ClearBuffer(self):
+        self.bmp = None
+    def Paint(self, dc, node, doubleBuffered=1, paintBackground=1):
         raise NotImplementedError
     def GetTextColour(self):
         return self.textcolor
@@ -177,19 +185,20 @@ class Painter:
         return self.linebrush
     def OnMouse(self, evt):
         if evt.LeftDClick():
+            x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
             for item in self.rectangles:
-                if item[1].contains((evt.GetX(), evt.GetY())):
+                if item[1].Contains((x,y)):
                     self.tree.Edit(item[0].data)
                     self.tree.OnNodeClick(item[0], evt)
                     return
         elif evt.ButtonDown():
-            #self.oldpos = (evt.GetX(), evt.GetY())
+            x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
             for item in self.rectangles:
-                if item[1].contains((evt.GetX(), evt.GetY())):
+                if item[1].Contains((x, y)):
                     self.tree.OnNodeClick(item[0], evt)
                     return
             for item in self.knobs:
-                if item[1].contains((evt.GetX(), evt.GetY())):
+                if item[1].Contains((x, y)):
                     self.tree.OnKnobClick(item[0])
                     return
         evt.Skip()
@@ -233,7 +242,7 @@ class NodePainter:
     """
     def __init__(self, painter):
         self.painter = painter
-    def paint(self, node, dc, location = None):
+    def Paint(self, node, dc, location = None):
         """
         location should be provided only to draw in an unusual position
         (not the node's normal position), otherwise the node's projected x and y
@@ -247,7 +256,7 @@ class LinePainter:
     """
     def __init__(self, painter):
         self.painter = painter
-    def paint(self, parent, child, dc):
+    def Paint(self, parent, child, dc):
         raise NotImplementedError
 
 class TextConverter:
@@ -256,7 +265,7 @@ class TextConverter:
     """
     def __init__(self, painter):
         self.painter = painter
-    def convert(node):
+    def Convert(node):
         """
         Should return a string. The node argument will be an
         MVCTreeNode.
@@ -331,7 +340,8 @@ class FileEditor(Editor):
         self.editcomp.SetSelection(0, len(node.fileName))
         self.editcomp.SetFocus()
         self.treenode = treenode
-        EVT_KEY_DOWN(self.editcomp, self._key)
+#        EVT_KEY_DOWN(self.editcomp, self._key)
+        EVT_KEY_UP(self.editcomp, self._key)
         EVT_LEFT_DOWN(self.editcomp, self._mdown)
         self.editcomp.CaptureMouse()
 
@@ -347,10 +357,11 @@ class FileEditor(Editor):
                 os.rename(node.path + os.sep + node.fileName, node.path + os.sep + self.editcomp.GetValue())
                 node.fileName = self.editcomp.GetValue()
             except:
-                import traceback;traceback.print_exc()
+                traceback.print_exc()
         self.editcomp.ReleaseMouse()
         self.editcomp.Destroy()
         del self.editcomp
+        self.tree.Refresh()
 
 
     def _key(self, evt):
@@ -364,7 +375,6 @@ class FileEditor(Editor):
     def _mdown(self, evt):
         if evt.IsButton():
             pos = evt.GetPosition()
-            print pos.x, pos.y
             edsize = self.editcomp.GetSize()
             if pos.x < 0 or pos.y < 0 or pos.x > edsize.width or pos.y > edsize.height:
                 self.EndEdit(false)
@@ -416,7 +426,6 @@ class LateFSTreeModel(FSTreeModel):
         import string
         name = string.split(path, os.sep)[-1]
         pathpart = path[:-len(name)]
-        print pathpart
         fw = FileWrapper(pathpart, name)
         self._Build(path, fw)
         self.SetRoot(fw)
@@ -441,15 +450,23 @@ class LateFSTreeModel(FSTreeModel):
         return not os.path.isdir(node.path + os.sep + node.fileName)
 
 class StrTextConverter(TextConverter):
-    def convert(self, node):
+    def Convert(self, node):
         return str(node.data)
 
 class NullTransform(Transform):
-    def transform(self, node, offset, rotation):
-        node.projx = node.x + offset[0]
-        node.projy = node.y + offset[1]
-        for kid in node.kids:
-            self.transform(kid, offset, rotation)
+    def GetSize(self):
+        return tuple(self.size)
+
+    def Transform(self, node, offset, rotation):
+        self.size = [0,0]
+        list = self.tree.GetLayoutEngine().GetNodeList()
+        for node in list:
+            node.projx = node.x + offset[0]
+            node.projy = node.y + offset[1]
+            if node.projx > self.size[0]:
+                self.size[0] = node.projx
+            if node.projy > self.size[1]:
+                self.size[1] = node.projy
 
 class Rect:
     def __init__(self, x, y, width, height):
@@ -464,7 +481,7 @@ class Rect:
         name = ['x', 'y', 'width', 'height'][index]
         setattr(self, name, value)
 
-    def contains(self, other):
+    def Contains(self, other):
         if type(other) == type(()):
             other = Rect(other[0], other[1], 0, 0)
         if other.x >= self.x:
@@ -485,17 +502,28 @@ class TreeLayout(LayoutEngine):
         LayoutEngine.__init__(self, tree)
         self.NODE_STEP = 20
         self.NODE_HEIGHT = 20
-    def layout(self, node):
+        self.nodelist = []
+
+    def Layout(self, node):
+        self.nodelist = []
+        self.NODE_HEIGHT = self.tree.GetFont().GetPointSize() * 2
+        self.layoutwalk(node)
+
+    def GetNodeList(self):
+        return self.nodelist
+
+    def layoutwalk(self, node):
         if node == self.tree.currentRoot:
             node.level = 1
             self.lastY = (-self.NODE_HEIGHT)
         node.x = self.NODE_STEP * node.level
         node.y = self.lastY + self.NODE_HEIGHT
         self.lastY = node.y
+        self.nodelist.append(node)
         if node.expanded:
             for kid in node.kids:
                 kid.level = node.level + 1
-                self.layout(kid)
+                self.layoutwalk(kid)
 
 class TreePainter(Painter):
     """
@@ -515,7 +543,7 @@ class TreePainter(Painter):
         self.textConverter = textConverter
         self.charWidths = []
 
-    def paint(self, dc, node):
+    def Paint(self, dc, node, doubleBuffered=1, paintBackground=1):
         if not self.charWidths:
             self.charWidths = []
             for i in range(25):
@@ -530,15 +558,41 @@ class TreePainter(Painter):
         self.fgbrush = wxBrush(self.GetForegroundColour(), wxSOLID)
         self.bgbrush = wxBrush(self.GetBackgroundColour(), wxSOLID)
         self.linebrush = wxPen(self.GetLineColour(), 1, wxSOLID)
-        self.rectangles = []
-        self.knobs = []
+        treesize = self.tree.GetSize()
+        size = self.tree.transform.GetSize()
+        size = (max(treesize.width, size[0]+50), max(treesize.height, size[1]+50))
         dc.BeginDrawing()
-        dc.SetPen(self.GetBackgroundPen())
-        dc.SetBrush(self.GetBackgroundBrush())
-        size = self.tree.GetSize()
-        dc.DrawRectangle(0, 0, size.width, size.height)
-        if node:
-            self.paintWalk(node, dc)
+        if doubleBuffered:
+            mem_dc = wxMemoryDC()
+            if not self.GetBuffer():
+                self.knobs = []
+                self.rectangles = []
+                self.bmp = wxEmptyBitmap(size[0], size[1])
+                mem_dc.SelectObject(self.GetBuffer())
+                mem_dc.SetPen(self.GetBackgroundPen())
+                mem_dc.SetBrush(self.GetBackgroundBrush())
+                mem_dc.DrawRectangle(0, 0, size[0], size[1])
+                mem_dc.SetFont(self.tree.GetFont())
+                self.paintWalk(node, mem_dc)
+            else:
+                mem_dc.SelectObject(self.GetBuffer())
+            xstart, ystart = self.tree.CalcUnscrolledPosition(0,0)
+            size = self.tree.GetClientSizeTuple()
+            dc.Blit(xstart, ystart, size[0], size[1], mem_dc, xstart, ystart)
+        else:
+            if node == self.tree.currentRoot:
+                self.knobs = []
+                self.rectangles = []
+            dc.SetPen(self.GetBackgroundPen())
+            dc.SetBrush(self.GetBackgroundBrush())
+            dc.SetFont(self.tree.GetFont())
+            if paintBackground:
+                dc.DrawRectangle(0, 0, size[0], size[1])
+            if node:
+                #Call with not paintBackground because if we are told not to paint the
+                #whole background, we have to paint in parts to undo selection coloring.
+                pb = paintBackground
+                self.paintWalk(node, dc, not pb)
         dc.EndDrawing()
 
     def GetDashPen(self):
@@ -548,71 +602,68 @@ class TreePainter(Painter):
         Painter.SetLinePen(self, pen)
         self.dashpen = wxPen(pen.GetColour(), 1, wxDOT)
 
-    def drawBox(self, px, py, node, dc):
-        if self.tree.model.IsLeaf(node.data) or ((node.expanded or not self.tree._assumeChildren) and not len(node.kids)):
-            return
-        dc.SetPen(self.linepen)
-        dc.SetBrush(self.bgbrush)
-        dc.DrawRectangle(px -4, py-4, 9, 9)
-        self.knobs.append(node, Rect(px -4, py -4, 9, 9))
-        dc.SetPen(self.textpen)
-        if not node.expanded:
-            dc.DrawLine(px, py -2, px, py + 3)
-        dc.DrawLine(px -2, py, px + 3, py)
-
-    def paintWalk(self, node, dc):
-        self.linePainter.paint(node.parent, node, dc)
-        self.nodePainter.paint(node, dc)
+    def paintWalk(self, node, dc, paintRects=0):
+        self.linePainter.Paint(node.parent, node, dc)
+        self.nodePainter.Paint(node, dc, drawRects = paintRects)
         if node.expanded:
             for kid in node.kids:
-                if not self.paintWalk(kid, dc):
+                if not self.paintWalk(kid, dc, paintRects):
                     return false
             for kid in node.kids:
                 px = (kid.projx - self.tree.layout.NODE_STEP) + 5
                 py = kid.projy + kid.height/2
-                self.drawBox(px, py, kid, dc)
+                if (not self.tree.model.IsLeaf(kid.data)) or ((kid.expanded or self.tree._assumeChildren) and len(kid.kids)):
+                    dc.SetPen(self.linepen)
+                    dc.SetBrush(self.bgbrush)
+                    dc.DrawRectangle(px -4, py-4, 9, 9)
+                    self.knobs.append(kid, Rect(px -4, py -4, 9, 9))
+                    dc.SetPen(self.textpen)
+                    if not kid.expanded:
+                        dc.DrawLine(px, py -2, px, py + 3)
+                    dc.DrawLine(px -2, py, px + 3, py)
         if node == self.tree.currentRoot:
             px = (node.projx - self.tree.layout.NODE_STEP) + 5
             py = node.projy + node.height/2
-            self.drawBox(px, py, node, dc)
+            dc.SetPen(self.linepen)
+            dc.SetBrush(self.bgbrush)
+            dc.DrawRectangle(px -4, py-4, 9, 9)
+            self.knobs.append(node, Rect(px -4, py -4, 9, 9))
+            dc.SetPen(self.textpen)
+            if not node.expanded:
+                dc.DrawLine(px, py -2, px, py + 3)
+            dc.DrawLine(px -2, py, px + 3, py)
         return true
 
     def OnMouse(self, evt):
         Painter.OnMouse(self, evt)
 
 class TreeNodePainter(NodePainter):
-    def paint(self, node, dc, location = None):
-        text = self.painter.textConverter.convert(node)
+    def Paint(self, node, dc, location = None, drawRects = 0):
+        text = self.painter.textConverter.Convert(node)
         extent = dc.GetTextExtent(text)
         node.width = extent[0]
         node.height = extent[1]
-        if node == self.painter.tree.currentRoot:
-            self.painter.minx = self.painter.maxx = self.painter.miny = self.painter.maxy = 0
-        if node.projx < self.painter.minx:
-            self.painter.minx = node.projx
-        elif node.projx + node.width > self.painter.maxx:
-            self.painter.maxx = node.projx + node.width
-        if node.projy < self.painter.miny:
-            self.painter.miny = node.projy
-        elif node.projy + node.height > self.painter.maxy:
-            self.painter.maxy = node.projy + node.height
         if node.selected:
             dc.SetPen(self.painter.GetLinePen())
             dc.SetBrush(self.painter.GetForegroundBrush())
             dc.SetTextForeground(wxNamedColour("WHITE"))
             dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3)
         else:
+            if drawRects:
+                dc.SetBrush(self.painter.GetBackgroundBrush())
+                dc.SetPen(self.painter.GetBackgroundPen())
+                dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3)
             dc.SetTextForeground(self.painter.GetTextColour())
         dc.DrawText(text, node.projx, node.projy)
         self.painter.rectangles.append((node, Rect(node.projx, node.projy, node.width, node.height)))
 
 class TreeLinePainter(LinePainter):
-    def paint(self, parent, child, dc):
+    def Paint(self, parent, child, dc):
         dc.SetPen(self.painter.GetDashPen())
         px = py = cx = cy = 0
         if parent is None or child == self.painter.tree.currentRoot:
             px = (child.projx - self.painter.tree.layout.NODE_STEP) + 5
-            py = child.projy + self.painter.tree.layout.NODE_HEIGHT/2
+            py = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -2
             cx = child.projx
             cy = py
             dc.DrawLine(px, py, cx, cy)
@@ -620,7 +671,7 @@ class TreeLinePainter(LinePainter):
             px = parent.projx + 5
             py = parent.projy + parent.height
             cx = child.projx -5
-            cy = child.projy + self.painter.tree.layout.NODE_HEIGHT/2
+            cy = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -3
             dc.DrawLine(px, py, px, cy)
             dc.DrawLine(px, cy, cx, cy)
 
@@ -671,27 +722,35 @@ class wxMVCTreeEvent(wxPyCommandEvent):
         self.node = node
         self.nodes = nodes
         self.keyEvent = keyEvent
-
+    def GetNode(self):
+        return self.node
+    def GetNodes(self):
+        return self.nodes
+    def getKeyEvent(self):
+        return self.keyEvent
 
 class wxMVCTreeNotifyEvent(wxMVCTreeEvent):
     def __init__(self, type, id, node = None, nodes = None, **kwargs):
         apply(wxMVCTreeEvent.__init__, (self, type, id), kwargs)
         self.notify = wxNotifyEvent(type, id)
+    def getNotifyEvent(self):
+        return self.notify
 
-class wxMVCTree(wxWindow):
+class wxMVCTree(wxScrolledWindow):
     """
-    The main mvcTree class.
+    The main mvc tree class.
     """
     def __init__(self, parent, id, model = None, layout = None, transform = None,
                  painter = None, *args, **kwargs):
-        apply(wxWindow.__init__, (self, parent, id), kwargs)
+        apply(wxScrolledWindow.__init__, (self, parent, id), kwargs)
         self.nodemap = {}
         self._multiselect = false
         self._selections = []
         self._assumeChildren = false
         self._scrollx = false
         self._scrolly = false
-        self.doubleBuffered = true
+        self.doubleBuffered = false
+        self._lastPhysicalSize = self.GetSize()
         self._editors = []
         if not model:
             model = BasicTreeModel()
@@ -708,8 +767,22 @@ class wxMVCTree(wxWindow):
         self.painter = painter
         self.SetFont(wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, false))
         EVT_MOUSE_EVENTS(self, self.OnMouse)
-        EVT_SCROLLWIN(self, self.OnScroll)
         EVT_KEY_DOWN(self, self.OnKeyDown)
+        self.doubleBuffered = true
+
+    def Refresh(self):
+        if self.doubleBuffered:
+            self.painter.ClearBuffer()
+        wxScrolledWindow.Refresh(self, false)
+
+    def GetPainter(self):
+        return self.painter
+
+    def GetLayoutEngine(self):
+        return self.layout
+
+    def GetTransform(self):
+        return self.transform
 
     def __repr__(self):
         return "<wxMVCTree instance at %s>" % str(hex(id(self)))
@@ -720,15 +793,16 @@ class wxMVCTree(wxWindow):
     def NodeAdded(self, parent, child):
         e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child])
         self.GetEventHandler().ProcessEvent(e)
+        self.painter.ClearBuffer()
 
     def NodeInserted(self, parent, child, index):
         e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child])
         self.GetEventHandler().ProcessEvent(e)
-
+        self.painter.ClearBuffer()
     def NodeRemoved(self, node):
         e = wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM, self.GetId(), node = child, nodes = [parent, child])
         self.GetEventHandler().ProcessEvent(e)
-
+        self.painter.ClearBuffer()
     def OnKeyDown(self, evt):
         e = wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN, self.GetId(), keyEvent = evt)
         self.GetEventHandler().ProcessEvent(e)
@@ -738,7 +812,7 @@ class wxMVCTree(wxWindow):
         dc = wxClientDC(self)
         dc.SetFont(font)
         self.layout.SetHeight(dc.GetTextExtent("")[1] + 18)
-
+        self.painter.ClearBuffer()
     def GetFont(self):
         return self.painter.GetFont()
 
@@ -752,18 +826,17 @@ class wxMVCTree(wxWindow):
         self.painter.OnMouse(evt)
 
     def OnNodeClick(self, node, mouseEvent):
-        if node.selected:
+        if node.selected and (self.IsMultiSelect() and mouseEvent.ControlDown()):
             self.RemoveFromSelection(node.data)
         else:
-            self.AddToSelection(node.data, mouseEvent.ControlDown())
-        self.Refresh()
+            self.AddToSelection(node.data, mouseEvent.ControlDown(), mouseEvent.ShiftDown())
 
     def OnKnobClick(self, node):
         self.SetExpanded(node.data, not node.expanded)
 
     def GetDisplayText(self, node):
         treenode = self.nodemap[node]
-        return self.painter.textConverter.convert(treenode)
+        return self.painter.textConverter.Convert(treenode)
 
     def IsDoubleBuffered(self):
         return self.doubleBuffered
@@ -793,6 +866,7 @@ class wxMVCTree(wxWindow):
         self.currentRoot = self.layoutRoot
         self.offset = [0,0]
         self.rotation = 0
+        self._scrollset = None
         self.Refresh()
 
     def GetCurrentRoot(self):
@@ -817,12 +891,11 @@ class wxMVCTree(wxWindow):
         pass
 
     def OnSize(self, evt):
-        try:
-            size = self.GetSizeTuple()
-            self.center = (size[0]/2, size[1]/2)
-            del self.bmp
-        except:
-            pass
+        size = self.GetSize()
+        self.center = (size.width/2, size.height/2)
+        if self._lastPhysicalSize.width < size.width or self._lastPhysicalSize.height < size.height:
+            self.painter.ClearBuffer()
+        self._lastPhysicalSize = size
 
     def GetSelection(self):
         "Returns a tuple of selected nodes."
@@ -902,14 +975,14 @@ class wxMVCTree(wxWindow):
         else:
             e = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED, self.GetId(), node)
         self.GetEventHandler().ProcessEvent(e)
-        self.layout.layout(self.currentRoot)
-        self.transform.transform(self.currentRoot, self.offset, self.rotation)
+        self.layout.Layout(self.currentRoot)
+        self.transform.Transform(self.currentRoot, self.offset, self.rotation)
         self.Refresh()
 
     def IsExpanded(self, node):
         return self.nodemap[node].expanded
 
-    def AddToSelection(self, nodeOrTuple, enableMulti = true):
+    def AddToSelection(self, nodeOrTuple, enableMulti = true, shiftMulti = false):
         nodeTuple = nodeOrTuple
         if type(nodeOrTuple)!= type(()):
             nodeTuple = (nodeOrTuple,)
@@ -917,38 +990,69 @@ class wxMVCTree(wxWindow):
         self.GetEventHandler().ProcessEvent(e)
         if not e.notify.IsAllowed():
             return
-        if not self.IsMultiSelect() or not enableMulti:
+        changeparents = []
+        if not (self.IsMultiSelect() and (enableMulti or shiftMulti)):
             for node in self._selections:
                 treenode = self.nodemap[node]
                 treenode.selected = false
+                changeparents.append(treenode)
             node = nodeTuple[0]
             self._selections = [node]
             treenode = self.nodemap[node]
+            changeparents.append(treenode)
             treenode.selected = true
         else:
-            for node in nodeTuple:
-                try:
-                    self._selections.index(node)
-                except ValueError:
-                    self._selections.append(node)
+            if shiftMulti:
+                for node in nodeTuple:
                     treenode = self.nodemap[node]
-                    treenode.selected = true
+                    oldtreenode = self.nodemap[self._selections[0]]
+                    if treenode.parent == oldtreenode.parent:
+                        found = 0
+                        for kid in oldtreenode.parent.kids:
+                            if kid == treenode or kid == oldtreenode:
+                                found = not found
+                                kid.selected = true
+                                self._selections.append(kid.data)
+                                changeparents.append(kid)
+                            elif found:
+                                kid.selected = true
+                                self._selections.append(kid.data)
+                                changeparents.append(kid)
+            else:
+                for node in nodeTuple:
+                    try:
+                        self._selections.index(node)
+                    except ValueError:
+                        self._selections.append(node)
+                        treenode = self.nodemap[node]
+                        treenode.selected = true
+                        changeparents.append(treenode)
         e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple)
         self.GetEventHandler().ProcessEvent(e)
+        dc = wxClientDC(self)
+        self.PrepareDC(dc)
+        for node in changeparents:
+            if node:
+                self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0)
+        self.painter.ClearBuffer()
 
     def RemoveFromSelection(self, nodeTuple):
         if type(nodeTuple) != type(()):
             nodeTuple = (nodeTuple,)
+        changeparents = []
         for node in nodeTuple:
-            try:
-                self._selections.index(node)
-            except IndexError:
-                self._selections.remove(node)
-                treenode = self.nodemap[node]
-                node.selected = false
+            self._selections.remove(node)
+            treenode = self.nodemap[node]
+            changeparents.append(treenode)
+            treenode.selected = false
         e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), node, nodes = nodeTuple)
         self.GetEventHandler().ProcessEvent(e)
-
+        dc = wxClientDC(self)
+        self.PrepareDC(dc)
+        for node in changeparents:
+            if node:
+                self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0)
+        self.painter.ClearBuffer()
 
 
     def GetBackgroundColour(self):
@@ -978,126 +1082,39 @@ class wxMVCTree(wxWindow):
     def GetAssumeChildren(self):
         return self._assumeChildren
 
-    def OnScroll(self, evt):
-        type = evt.GetEventType()
-        field = [self.painter.maxx - self.painter.minx, self.painter.maxy - self.painter.miny]
-        size = self.GetSizeTuple()
-        index = 1
-        if evt.GetOrientation() == wxHORIZONTAL:
-            index = 0
-            self._scrollx = true
-        else:
-            self._scrolly = true
-            index = 1
-        if type ==  wxEVT_SCROLLWIN_TOP:
-            self.offset[index] = 0
-        elif type == wxEVT_SCROLLWIN_LINEUP:
-            self.offset[index] = self.offset[index] + 1
-        elif type == wxEVT_SCROLLWIN_LINEDOWN:
-            self.offset[index] = self.offset[index] - 1
-        elif type == wxEVT_SCROLLWIN_PAGEUP:
-            self.offset[index] = self.offset[index] + int(20 * float(field[index])/float(size[index]))
-        elif type == wxEVT_SCROLLWIN_PAGEDOWN:
-            self.offset[index] = self.offset[index] - int(20 * float(field[index])/float(size[index]))
-        elif type == wxEVT_SCROLLWIN_THUMBTRACK:
-            self.offset[index] = -(evt.GetPosition())
-        elif type == wxEVT_SCROLLWIN_BOTTOM:
-            self.offset[index] = field[index]
-        self.transformed = false
-        self.Refresh()
-
     def OnPaint(self, evt):
         """
         Ensures that the tree has been laid out and transformed, then calls the painter
         to paint the control.
         """
         try:
+            self.EnableScrolling(false, false)
             if not self.laidOut:
-                self.layout.layout(self.currentRoot)
+                self.layout.Layout(self.currentRoot)
                 self.laidOut = true
+                self.transformed = false
             if not self.transformed:
-                self.transform.transform(self.currentRoot, self.offset, self.rotation)
+                self.transform.Transform(self.currentRoot, self.offset, self.rotation)
                 self.transformed = true
+            tsize = None
+            tsize = list(self.transform.GetSize())
+            tsize[0] = tsize[0] + 50
+            tsize[1] = tsize[1] + 50
+            size = self.GetSizeTuple()
+            if tsize[0] > size[0] or tsize[1] > size[1]:
+                if not hasattr(self, '_oldsize') or (tsize[0] > self._oldsize[0] or tsize[1] > self._oldsize[1]):
+                    self._oldsize = tsize
+                    oldstart = self.ViewStart()
+                    self._lastPhysicalSize = self.GetSize()
+                    self.SetScrollbars(10, 10, tsize[0]/10, tsize[1]/10)
+                    self.Scroll(oldstart[0], oldstart[1])
             dc = wxPaintDC(self)
+            self.PrepareDC(dc)
             dc.SetFont(self.GetFont())
-            if self.doubleBuffered:
-                size = self.GetSize()
-                if not hasattr(self, 'bmp'):
-                    self.bmp = bmp =wxEmptyBitmap(size.width, size.height)
-                else:
-                    bmp = self.bmp
-                mem_dc = wxMemoryDC()
-                mem_dc.SetFont(self.GetFont())
-                mem_dc.SelectObject(bmp)
-                self.painter.paint(mem_dc, self.currentRoot)
-                dc.Blit(0, 0, size.width, size.height, mem_dc, 0, 0);
-            else:
-                self.painter.paint(dc, self.currentRoot)
-            size = self.GetSizeTuple()
-            if self._scrollx or self.painter.minx < 0 or self.painter.maxx > size[0]:
-                field = self.painter.maxx - self.painter.minx
-                self.SetScrollbar(wxHORIZONTAL, -self.offset[0], size[0]/field, field, true)
-                self._scrollx = false
-            if self._scrolly or self.painter.miny < 0 or self.painter.maxy > size[1]:
-                field = self.painter.maxy - self.painter.miny
-                self.SetScrollbar(wxVERTICAL, -self.offset[1], size[1]/field, field, true)
-                self._scrolly = false
+            self.painter.Paint(dc, self.currentRoot, self.doubleBuffered)
         except:
-            import traceback;traceback.print_exc()
-
-if __name__ == '__main__':
-    def exit(evt):
-        import sys;sys.exit()
-
-    block = 0
-
-    def selchanging(evt):
-        print "SelChanging!"
-        print evt.node
-        global block
-        if block:
-            evt.notify.Veto()
-        block = not block
-
-    def selchanged(evt):
-        print "SelChange!"
-        print evt.node
-    def expanded(evt):
-        print "Expanded!"
-    def closed(evt):
-        print "Closed!"
-    def key(evt):
-        print "Key"
-    def add(evt):
-        print "Add"
-    def delitem(evt):
-        print "Delete"
-
-    class MyApp(wxApp):
-        def OnInit(self):
-            f = wxFrame(NULL, -1, "wxMVCTree")
-            p = None
-            p = wxMVCTree(f, -1)
-            p.SetAssumeChildren(true)
-            if len(sys.argv) > 1:
-                p.SetModel(LateFSTreeModel(sys.argv[1]))
-                p.AddEditor(FileEditor(p))
-            p.SetMultiSelect(true)
-            f.Show(true)
-            EVT_CLOSE(f, exit)
-            EVT_MVCTREE_SEL_CHANGED(p, p.GetId(), selchanged)
-            EVT_MVCTREE_SEL_CHANGING(p, p.GetId(), selchanging)
-            EVT_MVCTREE_ITEM_EXPANDED(p, p.GetId(), expanded)
-            EVT_MVCTREE_ITEM_COLLAPSED(p, p.GetId(), closed)
-            EVT_MVCTREE_ADD_ITEM(p, p.GetId(), add)
-            EVT_MVCTREE_DELETE_ITEM(p, p.GetId(), delitem)
-            EVT_MVCTREE_KEY_DOWN(p, p.GetId(), key)
-            p.SetForegroundColour(wxNamedColour("GREEN"))
-            self.SetTopWindow(f)
-            return true
-
-    app = MyApp(false)
-    app.MainLoop()
+            traceback.print_exc()
+