1 # 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) 
   3 # o 2.5 compatability update. 
   4 # o I'm a little nervous about some of it though. 
   6 # 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) 
   8 # o wxTreeModel -> TreeModel 
   9 # o wxMVCTree -> MVCTree 
  10 # o wxMVCTreeEvent -> MVCTreeEvent 
  11 # o wxMVCTreeNotifyEvent -> MVCTreeNotifyEvent 
  15 MVCTree is a control which handles hierarchical data. It is constructed 
  16 in model-view-controller architecture, so the display of that data, and 
  17 the content of the data can be changed greatly without affecting the other parts. 
  19 MVCTree actually is even more configurable than MVC normally implies, because 
  20 almost every aspect of it is pluggable: 
  21     MVCTree - Overall controller, and the window that actually gets placed 
  23         Painter - Paints the control. The 'view' part of MVC. 
  24            NodePainter - Paints just the nodes 
  25            LinePainter - Paints just the lines between the nodes 
  26            TextConverter - Figures out what text to print for each node 
  27         Editor - Edits the contents of a node, if the model is editable. 
  28         LayoutEngine - Determines initial placement of nodes 
  29         Transform - Adjusts positions of nodes for movement or special effects. 
  30         TreeModel - Contains the data which the rest of the control acts 
  31         on. The 'model' part of MVC. 
  33 Author/Maintainer - Bryn Keller <xoltar@starship.python.net> 
  36 NOTE: This module is *not* supported in any way.  Use it however you 
  37       wish, but be warned that dealing with any consequences is 
  42 #------------------------------------------------------------------------ 
  49 #------------------------------------------------------------------------ 
  53 ################################################\ 
  54 # This module is not supported in any way!      | 
  56 # See cource code for wx.lib.mvctree for more   | 
  58 ################################################/ 
  62 warnings
.warn(warningmsg
, DeprecationWarning, stacklevel
=2) 
  63 #------------------------------------------------------------------------ 
  67     Used internally by MVCTree to manage its data. Contains information about 
  68     screen placement, the actual data associated with it, and more. These are 
  69     the nodes passed to all the other helper parts to do their work with. 
  71     def __init__(self
, data
=None, parent 
= None, kids 
= None, x 
= 0, y 
= 0): 
  86     def GetChildren(self
): 
  92     def Remove(self
, node
): 
  94             self
.kids
.remove(node
) 
  98         self
.kids
.append(node
) 
 101     def SetParent(self
, parent
): 
 102         if self
.parent 
and not (self
.parent 
is parent
): 
 103             self
.parent
.Remove(self
) 
 106         return "Node: "  + str(self
.data
) + " (" + str(self
.x
) + ", " + str(self
.y
) + ")" 
 108         return str(self
.data
) 
 109     def GetTreeString(self
, tabs
=0): 
 110         s 
= tabs 
* '\t' + str(self
) + '\n' 
 111         for kid 
in self
.kids
: 
 112             s 
= s 
+ kid
.GetTreeString(tabs 
+ 1) 
 117     def __init__(self
, tree
): 
 119     def Edit(self
, node
): 
 120         raise NotImplementedError 
 121     def EndEdit(self
, node
, commit
): 
 122         raise NotImplementedError 
 123     def CanEdit(self
, node
): 
 124         raise NotImplementedError 
 128     Interface for layout engines. 
 130     def __init__(self
, tree
): 
 132     def Layout(self
, node
): 
 133         raise NotImplementedError 
 134     def GetNodeList(self
): 
 135         raise NotImplementedError 
 141     def __init__(self
, tree
): 
 143     def Transform(self
, node
, offset
, rotation
): 
 145         This method should only change the projx and projy attributes of 
 146         the node. These represent the position of the node as it should 
 147         be drawn on screen. Adjusting the x and y attributes can and 
 150         raise NotImplementedError 
 154         Returns the size of the entire tree as laid out and transformed 
 157         raise NotImplementedError 
 161     This is the interface that MVCTree expects from painters. All painters should 
 162     be Painter subclasses. 
 164     def __init__(self
, tree
): 
 166         self
.textcolor 
= wx
.NamedColour("BLACK") 
 167         self
.bgcolor 
= wx
.NamedColour("WHITE") 
 168         self
.fgcolor 
= wx
.NamedColour("BLUE") 
 169         self
.linecolor 
= wx
.NamedColour("GREY") 
 170         self
.font 
= wx
.Font(9, wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
, False) 
 176     def SetFont(self
, font
): 
 181     def ClearBuffer(self
): 
 183     def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1): 
 184         raise NotImplementedError 
 185     def GetTextColour(self
): 
 186         return self
.textcolor
 
 187     def SetTextColour(self
, color
): 
 188         self
.textcolor 
= color
 
 189         self
.textbrush 
= wx
.Brush(color
) 
 190         self
.textpen 
= wx
.Pen(color
, 1, wx
.SOLID
) 
 191     def GetBackgroundColour(self
): 
 193     def SetBackgroundColour(self
, color
): 
 195         self
.bgbrush 
= wx
.Brush(color
) 
 196         self
.bgpen 
= wx
.Pen(color
, 1, wx
.SOLID
) 
 197     def GetForegroundColour(self
): 
 199     def SetForegroundColour(self
, color
): 
 201         self
.fgbrush 
= wx
.Brush(color
) 
 202         self
.fgpen 
= wx
.Pen(color
, 1, wx
.SOLID
) 
 203     def GetLineColour(self
): 
 204         return self
.linecolor
 
 205     def SetLineColour(self
, color
): 
 206         self
.linecolor 
= color
 
 207         self
.linebrush 
= wx
.Brush(color
) 
 208         self
.linepen 
= wx
.Pen( color
, 1, wx
.SOLID
) 
 209     def GetForegroundPen(self
): 
 211     def GetBackgroundPen(self
): 
 213     def GetTextPen(self
): 
 215     def GetForegroundBrush(self
): 
 217     def GetBackgroundBrush(self
): 
 219     def GetTextBrush(self
): 
 220         return self
.textbrush
 
 221     def GetLinePen(self
): 
 223     def GetLineBrush(self
): 
 224         return self
.linebrush
 
 225     def OnMouse(self
, evt
): 
 227             x
, y 
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY()) 
 228             for item 
in self
.rectangles
: 
 229                 if item
[1].Contains((x
,y
)): 
 230                     self
.tree
.Edit(item
[0].data
) 
 231                     self
.tree
.OnNodeClick(item
[0], evt
) 
 233         elif evt
.ButtonDown(): 
 234             x
, y 
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY()) 
 235             for item 
in self
.rectangles
: 
 236                 if item
[1].Contains((x
, y
)): 
 237                     self
.tree
.OnNodeClick(item
[0], evt
) 
 239             for item 
in self
.knobs
: 
 240                 if item
[1].Contains((x
, y
)): 
 241                     self
.tree
.OnKnobClick(item
[0]) 
 248     Interface for tree models 
 251         raise NotImplementedError 
 252     def SetRoot(self
, root
): 
 253         raise NotImplementedError 
 254     def GetChildCount(self
, node
): 
 255         raise NotImplementedError 
 256     def GetChildAt(self
, node
, index
): 
 257         raise NotImplementedError 
 258     def GetParent(self
, node
): 
 259         raise NotImplementedError 
 260     def AddChild(self
, parent
, child
): 
 261         if hasattr(self
, 'tree') and self
.tree
: 
 262             self
.tree
.NodeAdded(parent
, child
) 
 263     def RemoveNode(self
, child
): 
 264         if hasattr(self
, 'tree') and self
.tree
: 
 265             self
.tree
.NodeRemoved(child
) 
 266     def InsertChild(self
, parent
, child
, index
): 
 267         if hasattr(self
, 'tree') and self
.tree
: 
 268             self
.tree
.NodeInserted(parent
, child
, index
) 
 269     def IsLeaf(self
, node
): 
 270         raise NotImplementedError 
 272     def IsEditable(self
, node
): 
 275     def SetEditable(self
, node
): 
 280     This is the interface expected of a nodepainter. 
 282     def __init__(self
, painter
): 
 283         self
.painter 
= painter
 
 284     def Paint(self
, node
, dc
, location 
= None): 
 286         location should be provided only to draw in an unusual position 
 287         (not the node's normal position), otherwise the node's projected x and y 
 288         coordinates will be used. 
 290         raise NotImplementedError 
 294     The linepainter interface. 
 296     def __init__(self
, painter
): 
 297         self
.painter 
= painter
 
 298     def Paint(self
, parent
, child
, dc
): 
 299         raise NotImplementedError 
 303     TextConverter interface. 
 305     def __init__(self
, painter
): 
 306         self
.painter 
= painter
 
 309         Should return a string. The node argument will be an 
 312         raise NotImplementedError 
 315 class BasicTreeModel(TreeModel
): 
 317     A very simple treemodel implementation, but flexible enough for many needs. 
 325     def SetRoot(self
, root
): 
 327     def GetChildCount(self
, node
): 
 328         if self
.children
.has_key(node
): 
 329             return len(self
.children
[node
]) 
 332     def GetChildAt(self
, node
, index
): 
 333         return self
.children
[node
][index
] 
 335     def GetParent(self
, node
): 
 336         return self
.parents
[node
] 
 338     def AddChild(self
, parent
, child
): 
 339         self
.parents
[child
]=parent
 
 340         if not self
.children
.has_key(parent
): 
 341             self
.children
[parent
]=[] 
 342         self
.children
[parent
].append(child
) 
 343         TreeModel
.AddChild(self
, parent
, child
) 
 346     def RemoveNode(self
, node
): 
 347         parent 
= self
.parents
[node
] 
 348         del self
.parents
[node
] 
 349         self
.children
[parent
].remove(node
) 
 350         TreeModel
.RemoveNode(self
, node
) 
 352     def InsertChild(self
, parent
, child
, index
): 
 353         self
.parents
[child
]=parent
 
 354         if not self
.children
.has_key(parent
): 
 355             self
.children
[parent
]=[] 
 356         self
.children
[parent
].insert(child
, index
) 
 357         TreeModel
.InsertChild(self
, parent
, child
, index
) 
 360     def IsLeaf(self
, node
): 
 361         return not self
.children
.has_key(node
) 
 363     def IsEditable(self
, node
): 
 366     def SetEditable(self
, node
, bool): 
 370 class FileEditor(Editor
): 
 371     def Edit(self
, node
): 
 372         treenode 
= self
.tree
.nodemap
[node
] 
 373         self
.editcomp 
= wxTextCtrl(self
.tree
, -1) 
 374         for rect 
in self
.tree
.painter
.rectangles
: 
 375             if rect
[0] == treenode
: 
 376                 self
.editcomp
.SetPosition((rect
[1][0], rect
[1][1])) 
 378         self
.editcomp
.SetValue(node
.fileName
) 
 379         self
.editcomp
.SetSelection(0, len(node
.fileName
)) 
 380         self
.editcomp
.SetFocus() 
 381         self
.treenode 
= treenode
 
 382 #        self.editcomp.Bind(wx.EVT_KEY_DOWN, self._key) 
 383         self
.editcomp
.Bind(wx
.EVT_KEY_UP
, self
._key
) 
 384         self
.editcomp
.Bind(wx
.EVT_LEFT_DOWN
, self
._mdown
) 
 385         self
.editcomp
.CaptureMouse() 
 387     def CanEdit(self
, node
): 
 388         return isinstance(node
, FileWrapper
) 
 390     def EndEdit(self
, commit
): 
 391         if not self
.tree
._EditEnding
(self
.treenode
.data
): 
 394             node 
= self
.treenode
.data
 
 396                 os
.rename(node
.path 
+ os
.sep 
+ node
.fileName
, node
.path 
+ os
.sep 
+ self
.editcomp
.GetValue()) 
 397                 node
.fileName 
= self
.editcomp
.GetValue() 
 399                 traceback
.print_exc() 
 400         self
.editcomp
.ReleaseMouse() 
 401         self
.editcomp
.Destroy() 
 407         if evt
.GetKeyCode() == wx
.WXK_RETURN
: 
 409         elif evt
.GetKeyCode() == wx
.WXK_ESCAPE
: 
 414     def _mdown(self
, evt
): 
 416             x
, y 
= evt
.GetPosition() 
 417             w
, h 
= self
.editcomp
.GetSize() 
 418             if x 
< 0 or y 
< 0 or x 
> w 
or y 
> h
: 
 424     Node class for FSTreeModel. 
 426     def __init__(self
, path
, fileName
): 
 428         self
.fileName 
= fileName
 
 433 class FSTreeModel(BasicTreeModel
): 
 435     This treemodel models the filesystem starting from a given path. 
 437     def __init__(self
, path
): 
 438         BasicTreeModel
.__init
__(self
) 
 439         fw 
= FileWrapper(path
, path
.split(os
.sep
)[-1]) 
 440         self
._Build
(path
, fw
) 
 442         self
._editable 
= True 
 443     def _Build(self
, path
, fileWrapper
): 
 444         for name 
in os
.listdir(path
): 
 445             fw 
= FileWrapper(path
, name
) 
 446             self
.AddChild(fileWrapper
, fw
) 
 447             childName 
= path 
+ os
.sep 
+ name
 
 448             if os
.path
.isdir(childName
): 
 449                 self
._Build
(childName
, fw
) 
 451     def IsEditable(self
, node
): 
 452         return self
._editable
 
 454     def SetEditable(self
, node
, bool): 
 455         self
._editable 
= bool 
 457 class LateFSTreeModel(FSTreeModel
): 
 459     This treemodel models the filesystem starting from a given path. 
 460     It retrieves the directory list as requested. 
 462     def __init__(self
, path
): 
 463         BasicTreeModel
.__init
__(self
) 
 464         name 
= path
.split(os
.sep
)[-1] 
 465         pathpart 
= path
[:-len(name
)] 
 466         fw 
= FileWrapper(pathpart
, name
) 
 467         self
._Build
(path
, fw
) 
 469         self
._editable 
= True 
 472     def _Build(self
, path
, parent
): 
 473         ppath 
= parent
.path 
+ os
.sep 
+ parent
.fileName
 
 474         if not os
.path
.isdir(ppath
): 
 476         for name 
in os
.listdir(ppath
): 
 477             fw 
= FileWrapper(ppath
, name
) 
 478             self
.AddChild(parent
, fw
) 
 479     def GetChildCount(self
, node
): 
 480         if self
.children
.has_key(node
): 
 481             return FSTreeModel
.GetChildCount(self
, node
) 
 483             self
._Build
(node
.path
, node
) 
 484             return FSTreeModel
.GetChildCount(self
, node
) 
 486     def IsLeaf(self
, node
): 
 487         return not os
.path
.isdir(node
.path 
+ os
.sep 
+ node
.fileName
) 
 489 class StrTextConverter(TextConverter
): 
 490     def Convert(self
, node
): 
 491         return str(node
.data
) 
 493 class NullTransform(Transform
): 
 495         return tuple(self
.size
) 
 497     def Transform(self
, node
, offset
, rotation
): 
 499         list = self
.tree
.GetLayoutEngine().GetNodeList() 
 501             node
.projx 
= node
.x 
+ offset
[0] 
 502             node
.projy 
= node
.y 
+ offset
[1] 
 503             if node
.projx 
> self
.size
[0]: 
 504                 self
.size
[0] = node
.projx
 
 505             if node
.projy 
> self
.size
[1]: 
 506                 self
.size
[1] = node
.projy
 
 509     def __init__(self
, x
, y
, width
, height
): 
 514     def __getitem__(self
, index
): 
 515         return (self
.x
, self
.y
, self
.width
, self
.height
)[index
] 
 517     def __setitem__(self
, index
, value
): 
 518         name 
= ['x', 'y', 'width', 'height'][index
] 
 519         setattr(self
, name
, value
) 
 521     def Contains(self
, other
): 
 522         if type(other
) == type(()): 
 523             other 
= Rect(other
[0], other
[1], 0, 0) 
 524         if other
.x 
>= self
.x
: 
 525             if other
.y 
>= self
.y
: 
 526                 if other
.width 
+ other
.x 
<= self
.width 
+ self
.x
: 
 527                     if other
.height 
+ other
.y  
<= self
.height 
+ self
.y
: 
 532         return "Rect: " + str([self
.x
, self
.y
, self
.width
, self
.height
]) 
 534 class TreeLayout(LayoutEngine
): 
 535     def SetHeight(self
, num
): 
 536         self
.NODE_HEIGHT 
= num
 
 538     def __init__(self
, tree
): 
 539         LayoutEngine
.__init
__(self
, tree
) 
 541         self
.NODE_HEIGHT 
= 20 
 544     def Layout(self
, node
): 
 546         self
.NODE_HEIGHT 
= self
.tree
.GetFont().GetPointSize() * 2 
 547         self
.layoutwalk(node
) 
 549     def GetNodeList(self
): 
 552     def layoutwalk(self
, node
): 
 553         if node 
== self
.tree
.currentRoot
: 
 555             self
.lastY 
= (-self
.NODE_HEIGHT
) 
 556         node
.x 
= self
.NODE_STEP 
* node
.level
 
 557         node
.y 
= self
.lastY 
+ self
.NODE_HEIGHT
 
 559         self
.nodelist
.append(node
) 
 561             for kid 
in node
.kids
: 
 562                 kid
.level 
= node
.level 
+ 1 
 565 class TreePainter(Painter
): 
 567     The default painter class. Uses double-buffering, delegates the painting of nodes and 
 568     lines to helper classes deriving from NodePainter and LinePainter. 
 570     def __init__(self
, tree
, nodePainter 
= None, linePainter 
= None, textConverter 
= None): 
 571         Painter
.__init
__(self
, tree
) 
 573             nodePainter 
= TreeNodePainter(self
) 
 574         self
.nodePainter 
= nodePainter
 
 576             linePainter 
= TreeLinePainter(self
) 
 577         self
.linePainter 
= linePainter
 
 578         if not textConverter
: 
 579             textConverter 
= StrTextConverter(self
) 
 580         self
.textConverter 
= textConverter
 
 583     def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1): 
 584         if not self
.charWidths
: 
 587                 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
) 
 588             self
.charHeight 
= dc
.GetTextExtent("D")[1] 
 589         self
.textpen 
= wx
.Pen(self
.GetTextColour(), 1, wx
.SOLID
) 
 590         self
.fgpen 
= wx
.Pen(self
.GetForegroundColour(), 1, wx
.SOLID
) 
 591         self
.bgpen 
= wx
.Pen(self
.GetBackgroundColour(), 1, wx
.SOLID
) 
 592         self
.linepen 
= wx
.Pen(self
.GetLineColour(), 1, wx
.SOLID
) 
 593         self
.dashpen 
= wx
.Pen(self
.GetLineColour(), 1, wx
.DOT
) 
 594         self
.textbrush 
= wx
.Brush(self
.GetTextColour(), wx
.SOLID
) 
 595         self
.fgbrush 
= wx
.Brush(self
.GetForegroundColour(), wx
.SOLID
) 
 596         self
.bgbrush 
= wx
.Brush(self
.GetBackgroundColour(), wx
.SOLID
) 
 597         self
.linebrush 
= wx
.Pen(self
.GetLineColour(), 1, wx
.SOLID
) 
 598         treesize 
= self
.tree
.GetSize() 
 599         size 
= self
.tree
.transform
.GetSize() 
 600         size 
= (max(treesize
.width
, size
[0]+50), max(treesize
.height
, size
[1]+50)) 
 603             mem_dc 
= wx
.MemoryDC() 
 604             if not self
.GetBuffer(): 
 607                 self
.bmp 
= wx
.EmptyBitmap(size
[0], size
[1]) 
 608                 mem_dc
.SelectObject(self
.GetBuffer()) 
 609                 mem_dc
.SetPen(self
.GetBackgroundPen()) 
 610                 mem_dc
.SetBrush(self
.GetBackgroundBrush()) 
 611                 mem_dc
.DrawRectangle(0, 0, size
[0], size
[1]) 
 612                 mem_dc
.SetFont(self
.tree
.GetFont()) 
 613                 self
.paintWalk(node
, mem_dc
) 
 615                 mem_dc
.SelectObject(self
.GetBuffer()) 
 616             xstart
, ystart 
= self
.tree
.CalcUnscrolledPosition(0,0) 
 617             size 
= self
.tree
.GetClientSizeTuple() 
 618             dc
.Blit(xstart
, ystart
, size
[0], size
[1], mem_dc
, xstart
, ystart
) 
 620             if node 
== self
.tree
.currentRoot
: 
 623             dc
.SetPen(self
.GetBackgroundPen()) 
 624             dc
.SetBrush(self
.GetBackgroundBrush()) 
 625             dc
.SetFont(self
.tree
.GetFont()) 
 627                 dc
.DrawRectangle(0, 0, size
[0], size
[1]) 
 629                 #Call with not paintBackground because if we are told not to paint the 
 630                 #whole background, we have to paint in parts to undo selection coloring. 
 632                 self
.paintWalk(node
, dc
, not pb
) 
 635     def GetDashPen(self
): 
 638     def SetLinePen(self
, pen
): 
 639         Painter
.SetLinePen(self
, pen
) 
 640         self
.dashpen 
= wx
.Pen(pen
.GetColour(), 1, wx
.DOT
) 
 642     def paintWalk(self
, node
, dc
, paintRects
=0): 
 643         self
.linePainter
.Paint(node
.parent
, node
, dc
) 
 644         self
.nodePainter
.Paint(node
, dc
, drawRects 
= paintRects
) 
 646             for kid 
in node
.kids
: 
 647                 if not self
.paintWalk(kid
, dc
, paintRects
): 
 649             for kid 
in node
.kids
: 
 650                 px 
= (kid
.projx 
- self
.tree
.layout
.NODE_STEP
) + 5 
 651                 py 
= kid
.projy 
+ kid
.height
/2 
 652                 if (not self
.tree
.model
.IsLeaf(kid
.data
)) or ((kid
.expanded 
or self
.tree
._assumeChildren
) and len(kid
.kids
)): 
 653                     dc
.SetPen(self
.linepen
) 
 654                     dc
.SetBrush(self
.bgbrush
) 
 655                     dc
.DrawRectangle(px 
-4, py
-4, 9, 9) 
 656                     self
.knobs
.append( (kid
, Rect(px 
-4, py 
-4, 9, 9)) ) 
 657                     dc
.SetPen(self
.textpen
) 
 659                         dc
.DrawLine(px
, py 
-2, px
, py 
+ 3) 
 660                     dc
.DrawLine(px 
-2, py
, px 
+ 3, py
) 
 661         if node 
== self
.tree
.currentRoot
: 
 662             px 
= (node
.projx 
- self
.tree
.layout
.NODE_STEP
) + 5 
 663             py 
= node
.projy 
+ node
.height
/2 
 664             dc
.SetPen(self
.linepen
) 
 665             dc
.SetBrush(self
.bgbrush
) 
 666             dc
.DrawRectangle(px 
-4, py
-4, 9, 9) 
 667             self
.knobs
.append( (node
, Rect(px 
-4, py 
-4, 9, 9)) ) 
 668             dc
.SetPen(self
.textpen
) 
 669             if not node
.expanded
: 
 670                 dc
.DrawLine(px
, py 
-2, px
, py 
+ 3) 
 671             dc
.DrawLine(px 
-2, py
, px 
+ 3, py
) 
 674     def OnMouse(self
, evt
): 
 675         Painter
.OnMouse(self
, evt
) 
 677 class TreeNodePainter(NodePainter
): 
 678     def Paint(self
, node
, dc
, location 
= None, drawRects 
= 0): 
 679         text 
= self
.painter
.textConverter
.Convert(node
) 
 680         extent 
= dc
.GetTextExtent(text
) 
 681         node
.width 
= extent
[0] 
 682         node
.height 
= extent
[1] 
 684             dc
.SetPen(self
.painter
.GetLinePen()) 
 685             dc
.SetBrush(self
.painter
.GetForegroundBrush()) 
 686             dc
.SetTextForeground(wx
.NamedColour("WHITE")) 
 687             dc
.DrawRectangle(node
.projx 
-1, node
.projy 
-1, node
.width 
+ 3, node
.height 
+ 3) 
 690                 dc
.SetBrush(self
.painter
.GetBackgroundBrush()) 
 691                 dc
.SetPen(self
.painter
.GetBackgroundPen()) 
 692                 dc
.DrawRectangle(node
.projx 
-1, node
.projy 
-1, node
.width 
+ 3, node
.height 
+ 3) 
 693             dc
.SetTextForeground(self
.painter
.GetTextColour()) 
 694         dc
.DrawText(text
, node
.projx
, node
.projy
) 
 695         self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
))) 
 697 class TreeLinePainter(LinePainter
): 
 698     def Paint(self
, parent
, child
, dc
): 
 699         dc
.SetPen(self
.painter
.GetDashPen()) 
 700         px 
= py 
= cx 
= cy 
= 0 
 701         if parent 
is None or child 
== self
.painter
.tree
.currentRoot
: 
 702             px 
= (child
.projx 
- self
.painter
.tree
.layout
.NODE_STEP
) + 5 
 703             py 
= child
.projy 
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -2 
 706             dc
.DrawLine(px
, py
, cx
, cy
) 
 708             px 
= parent
.projx 
+ 5 
 709             py 
= parent
.projy 
+ parent
.height
 
 711             cy 
= child
.projy 
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -3 
 712             dc
.DrawLine(px
, py
, px
, cy
) 
 713             dc
.DrawLine(px
, cy
, cx
, cy
) 
 716 wxEVT_MVCTREE_BEGIN_EDIT 
= wx
.NewEventType() #Start editing. Vetoable. 
 717 wxEVT_MVCTREE_END_EDIT 
= wx
.NewEventType() #Stop editing. Vetoable. 
 718 wxEVT_MVCTREE_DELETE_ITEM 
= wx
.NewEventType() #Item removed from model. 
 719 wxEVT_MVCTREE_ITEM_EXPANDED 
= wx
.NewEventType() 
 720 wxEVT_MVCTREE_ITEM_EXPANDING 
= wx
.NewEventType() 
 721 wxEVT_MVCTREE_ITEM_COLLAPSED 
= wx
.NewEventType() 
 722 wxEVT_MVCTREE_ITEM_COLLAPSING 
= wx
.NewEventType() 
 723 wxEVT_MVCTREE_SEL_CHANGED 
= wx
.NewEventType() 
 724 wxEVT_MVCTREE_SEL_CHANGING 
= wx
.NewEventType() #Vetoable. 
 725 wxEVT_MVCTREE_KEY_DOWN 
= wx
.NewEventType() 
 726 wxEVT_MVCTREE_ADD_ITEM 
= wx
.NewEventType() #Item added to model. 
 728 EVT_MVCTREE_SEL_CHANGED 
= wx
.PyEventBinder(wxEVT_MVCTREE_SEL_CHANGED
, 1) 
 729 EVT_MVCTREE_SEL_CHANGING 
= wx
.PyEventBinder(wxEVT_MVCTREE_SEL_CHANGING
, 1) 
 730 EVT_MVCTREE_ITEM_EXPANDED 
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_EXPANDED
, 1) 
 731 EVT_MVCTREE_ITEM_EXPANDING 
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_EXPANDING
, 1) 
 732 EVT_MVCTREE_ITEM_COLLAPSED 
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_COLLAPSED
, 1) 
 733 EVT_MVCTREE_ITEM_COLLAPSING 
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_COLLAPSING
, 1) 
 734 EVT_MVCTREE_ADD_ITEM 
= wx
.PyEventBinder(wxEVT_MVCTREE_ADD_ITEM
, 1) 
 735 EVT_MVCTREE_DELETE_ITEM 
= wx
.PyEventBinder(wxEVT_MVCTREE_DELETE_ITEM
, 1) 
 736 EVT_MVCTREE_KEY_DOWN 
= wx
.PyEventBinder(wxEVT_MVCTREE_KEY_DOWN
, 1) 
 738 class MVCTreeEvent(wx
.PyCommandEvent
): 
 739     def __init__(self
, type, id, node 
= None, nodes 
= None, keyEvent 
= None, **kwargs
): 
 740         apply(wx
.PyCommandEvent
.__init
__, (self
, type, id), kwargs
) 
 743         self
.keyEvent 
= keyEvent
 
 748     def getKeyEvent(self
): 
 751 class MVCTreeNotifyEvent(MVCTreeEvent
): 
 752     def __init__(self
, type, id, node 
= None, nodes 
= None, **kwargs
): 
 753         apply(MVCTreeEvent
.__init
__, (self
, type, id, node
, nodes
), kwargs
) 
 754         self
.notify 
= wx
.NotifyEvent(type, id) 
 755     def getNotifyEvent(self
): 
 758 class MVCTree(wx
.ScrolledWindow
): 
 760     The main mvc tree class. 
 762     def __init__(self
, parent
, id, model 
= None, layout 
= None, transform 
= None, 
 763                  painter 
= None, *args
, **kwargs
): 
 764         apply(wx
.ScrolledWindow
.__init
__, (self
, parent
, id), kwargs
) 
 766         self
._multiselect 
= False 
 767         self
._selections 
= [] 
 768         self
._assumeChildren 
= False 
 769         self
._scrollx 
= False 
 770         self
._scrolly 
= False 
 771         self
.doubleBuffered 
= False 
 772         self
._lastPhysicalSize 
= self
.GetSize() 
 775             model 
= BasicTreeModel() 
 776             model
.SetRoot("Root") 
 779             layout 
= TreeLayout(self
) 
 782            transform 
= NullTransform(self
) 
 783         self
.transform 
= transform
 
 785             painter 
= TreePainter(self
) 
 786         self
.painter 
= painter
 
 787         self
.SetFont(wx
.Font(9, wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
, False)) 
 788         self
.Bind(wx
.EVT_MOUSE_EVENTS
, self
.OnMouse
) 
 789         self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
) 
 790         self
.doubleBuffered 
= True 
 791         self
.Bind(wx
.EVT_SIZE
, self
.OnSize
) 
 792         self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
) 
 793         self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
) 
 797         if self
.doubleBuffered
: 
 798             self
.painter
.ClearBuffer() 
 799         wx
.ScrolledWindow
.Refresh(self
, False) 
 801     def GetPainter(self
): 
 804     def GetLayoutEngine(self
): 
 807     def GetTransform(self
): 
 808         return self
.transform
 
 811         return "<MVCTree instance at %s>" % str(hex(id(self
))) 
 814         return self
.__repr
__() 
 816     def NodeAdded(self
, parent
, child
): 
 817         e 
= MVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node 
= child
, nodes 
= [parent
, child
]) 
 818         self
.GetEventHandler().ProcessEvent(e
) 
 819         self
.painter
.ClearBuffer() 
 821     def NodeInserted(self
, parent
, child
, index
): 
 822         e 
= MVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node 
= child
, nodes 
= [parent
, child
]) 
 823         self
.GetEventHandler().ProcessEvent(e
) 
 824         self
.painter
.ClearBuffer() 
 826     def NodeRemoved(self
, node
): 
 827         e 
= MVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node 
= child
, nodes 
= [parent
, child
]) 
 828         self
.GetEventHandler().ProcessEvent(e
) 
 829         self
.painter
.ClearBuffer() 
 831     def OnKeyDown(self
, evt
): 
 832         e 
= MVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent 
= evt
) 
 833         self
.GetEventHandler().ProcessEvent(e
) 
 835     def SetFont(self
, font
): 
 836         self
.painter
.SetFont(font
) 
 837         dc 
= wx
.ClientDC(self
) 
 839         self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18) 
 840         self
.painter
.ClearBuffer() 
 843         return self
.painter
.GetFont() 
 845     def AddEditor(self
, editor
): 
 846         self
._editors
.append(editor
) 
 848     def RemoveEditor(self
, editor
): 
 849         self
._editors
.remove(editor
) 
 851     def OnMouse(self
, evt
): 
 852         self
.painter
.OnMouse(evt
) 
 854     def OnNodeClick(self
, node
, mouseEvent
): 
 855         if node
.selected 
and (self
.IsMultiSelect() and mouseEvent
.ControlDown()): 
 856             self
.RemoveFromSelection(node
.data
) 
 858             self
.AddToSelection(node
.data
, mouseEvent
.ControlDown(), mouseEvent
.ShiftDown()) 
 860     def OnKnobClick(self
, node
): 
 861         self
.SetExpanded(node
.data
, not node
.expanded
) 
 863     def GetDisplayText(self
, node
): 
 864         treenode 
= self
.nodemap
[node
] 
 865         return self
.painter
.textConverter
.Convert(treenode
) 
 867     def IsDoubleBuffered(self
): 
 868         return self
.doubleBuffered
 
 870     def SetDoubleBuffered(self
, bool): 
 872         By default MVCTree is double-buffered. 
 874         self
.doubleBuffered 
= bool 
 879     def SetModel(self
, model
): 
 881         Completely change the data to be displayed. 
 887         self
._selections 
= [] 
 888         self
.layoutRoot 
= MVCTreeNode() 
 889         self
.layoutRoot
.data 
= self
.model
.GetRoot() 
 890         self
.layoutRoot
.expanded 
= True 
 891         self
.LoadChildren(self
.layoutRoot
) 
 892         self
.currentRoot 
= self
.layoutRoot
 
 895         self
._scrollset 
= None 
 898     def GetCurrentRoot(self
): 
 899         return self
.currentRoot
 
 901     def LoadChildren(self
, layoutNode
): 
 905             self
.nodemap
[layoutNode
.data
]=layoutNode
 
 906             for i 
in range(self
.GetModel().GetChildCount(layoutNode
.data
)): 
 907                 p 
= MVCTreeNode("RAW", layoutNode
, []) 
 909                 p
.data 
= self
.GetModel().GetChildAt(layoutNode
.data
, i
) 
 910                 self
.nodemap
[p
.data
]=p
 
 911             layoutNode
.built 
= True 
 912             if not self
._assumeChildren
: 
 913                 for kid 
in layoutNode
.kids
: 
 914                     self
.LoadChildren(kid
) 
 916     def OnEraseBackground(self
, evt
): 
 919     def OnSize(self
, evt
): 
 920         size 
= self
.GetSize() 
 921         self
.center 
= (size
.width
/2, size
.height
/2) 
 922         if self
._lastPhysicalSize
.width 
< size
.width 
or self
._lastPhysicalSize
.height 
< size
.height
: 
 923             self
.painter
.ClearBuffer() 
 924         self
._lastPhysicalSize 
= size
 
 926     def GetSelection(self
): 
 927         "Returns a tuple of selected nodes." 
 928         return tuple(self
._selections
) 
 930     def SetSelection(self
, nodeTuple
): 
 931         if type(nodeTuple
) != type(()): 
 932             nodeTuple 
= (nodeTuple
,) 
 933         e 
= MVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes 
= nodeTuple
) 
 934         self
.GetEventHandler().ProcessEvent(e
) 
 935         if not e
.notify
.IsAllowed(): 
 937         for node 
in nodeTuple
: 
 938             treenode 
= self
.nodemap
[node
] 
 939             treenode
.selected 
= True 
 940         for node 
in self
._selections
: 
 941             treenode 
= self
.nodemap
[node
] 
 942             node
.selected 
= False 
 943         self
._selections 
= list(nodeTuple
) 
 944         e 
= MVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes 
= nodeTuple
) 
 945         self
.GetEventHandler().ProcessEvent(e
) 
 947     def IsMultiSelect(self
): 
 948         return self
._multiselect
 
 950     def SetMultiSelect(self
, bool): 
 951         self
._multiselect 
= bool 
 953     def IsSelected(self
, node
): 
 954         return self
.nodemap
[node
].selected
 
 956     def Edit(self
, node
): 
 957         if not self
.model
.IsEditable(node
): 
 959         for ed 
in self
._editors
: 
 961                 e 
= MVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
) 
 962                 self
.GetEventHandler().ProcessEvent(e
) 
 963                 if not e
.notify
.IsAllowed(): 
 966                 self
._currentEditor 
= ed
 
 970         if self
._currentEditor
: 
 971             self
._currentEditor
.EndEdit
 
 972             self
._currentEditor 
= None 
 974     def _EditEnding(self
, node
): 
 975         e 
= MVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
) 
 976         self
.GetEventHandler().ProcessEvent(e
) 
 977         if not e
.notify
.IsAllowed(): 
 979         self
._currentEditor 
= None 
 983     def SetExpanded(self
, node
, bool): 
 984         treenode 
= self
.nodemap
[node
] 
 986             e 
= MVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
) 
 987             self
.GetEventHandler().ProcessEvent(e
) 
 988             if not e
.notify
.IsAllowed(): 
 990             if not treenode
.built
: 
 991                 self
.LoadChildren(treenode
) 
 993             e 
= MVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
) 
 994             self
.GetEventHandler().ProcessEvent(e
) 
 995             if not e
.notify
.IsAllowed(): 
 997         treenode
.expanded 
= bool 
 999         if treenode
.expanded
: 
1000             e 
= MVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
) 
1002             e 
= MVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
) 
1003         self
.GetEventHandler().ProcessEvent(e
) 
1004         self
.layout
.Layout(self
.currentRoot
) 
1005         self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
) 
1008     def IsExpanded(self
, node
): 
1009         return self
.nodemap
[node
].expanded
 
1011     def AddToSelection(self
, nodeOrTuple
, enableMulti 
= True, shiftMulti 
= False): 
1012         nodeTuple 
= nodeOrTuple
 
1013         if type(nodeOrTuple
)!= type(()): 
1014             nodeTuple 
= (nodeOrTuple
,) 
1015         e 
= MVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes 
= nodeTuple
) 
1016         self
.GetEventHandler().ProcessEvent(e
) 
1017         if not e
.notify
.IsAllowed(): 
1020         if not (self
.IsMultiSelect() and (enableMulti 
or shiftMulti
)): 
1021             for node 
in self
._selections
: 
1022                 treenode 
= self
.nodemap
[node
] 
1023                 treenode
.selected 
= False 
1024                 changeparents
.append(treenode
) 
1026             self
._selections 
= [node
] 
1027             treenode 
= self
.nodemap
[node
] 
1028             changeparents
.append(treenode
) 
1029             treenode
.selected 
= True 
1032                 for node 
in nodeTuple
: 
1033                     treenode 
= self
.nodemap
[node
] 
1034                     oldtreenode 
= self
.nodemap
[self
._selections
[0]] 
1035                     if treenode
.parent 
== oldtreenode
.parent
: 
1037                         for kid 
in oldtreenode
.parent
.kids
: 
1038                             if kid 
== treenode 
or kid 
== oldtreenode
: 
1041                                 self
._selections
.append(kid
.data
) 
1042                                 changeparents
.append(kid
) 
1045                                 self
._selections
.append(kid
.data
) 
1046                                 changeparents
.append(kid
) 
1048                 for node 
in nodeTuple
: 
1050                         self
._selections
.index(node
) 
1052                         self
._selections
.append(node
) 
1053                         treenode 
= self
.nodemap
[node
] 
1054                         treenode
.selected 
= True 
1055                         changeparents
.append(treenode
) 
1056         e 
= MVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes 
= nodeTuple
) 
1057         self
.GetEventHandler().ProcessEvent(e
) 
1058         dc 
= wx
.ClientDC(self
) 
1060         for node 
in changeparents
: 
1062                 self
.painter
.Paint(dc
, node
, doubleBuffered 
= 0, paintBackground 
= 0) 
1063         self
.painter
.ClearBuffer() 
1065     def RemoveFromSelection(self
, nodeTuple
): 
1066         if type(nodeTuple
) != type(()): 
1067             nodeTuple 
= (nodeTuple
,) 
1069         for node 
in nodeTuple
: 
1070             self
._selections
.remove(node
) 
1071             treenode 
= self
.nodemap
[node
] 
1072             changeparents
.append(treenode
) 
1073             treenode
.selected 
= False 
1074         e 
= MVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes 
= nodeTuple
) 
1075         self
.GetEventHandler().ProcessEvent(e
) 
1076         dc 
= wx
.ClientDC(self
) 
1078         for node 
in changeparents
: 
1080                 self
.painter
.Paint(dc
, node
, doubleBuffered 
= 0, paintBackground 
= 0) 
1081         self
.painter
.ClearBuffer() 
1084     def GetBackgroundColour(self
): 
1085         if hasattr(self
, 'painter') and self
.painter
: 
1086             return self
.painter
.GetBackgroundColour() 
1088             return wx
.Window
.GetBackgroundColour(self
) 
1089     def SetBackgroundColour(self
, color
): 
1090         if hasattr(self
, 'painter') and self
.painter
: 
1091             self
.painter
.SetBackgroundColour(color
) 
1093             wx
.Window
.SetBackgroundColour(self
, color
) 
1094     def GetForegroundColour(self
): 
1095         if hasattr(self
, 'painter') and self
.painter
: 
1096             return self
.painter
.GetForegroundColour() 
1098             return wx
.Window
.GetBackgroundColour(self
) 
1099     def SetForegroundColour(self
, color
): 
1100         if hasattr(self
, 'painter') and self
.painter
: 
1101             self
.painter
.SetForegroundColour(color
) 
1103             wx
.Window
.SetBackgroundColour(self
, color
) 
1105     def SetAssumeChildren(self
, bool): 
1106         self
._assumeChildren 
= bool 
1108     def GetAssumeChildren(self
): 
1109         return self
._assumeChildren
 
1111     def OnPaint(self
, evt
): 
1113         Ensures that the tree has been laid out and transformed, then calls the painter 
1114         to paint the control. 
1117             self
.EnableScrolling(False, False) 
1118             if not self
.laidOut
: 
1119                 self
.layout
.Layout(self
.currentRoot
) 
1121                 self
.transformed 
= False 
1122             if not self
.transformed
: 
1123                 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
) 
1124                 self
.transformed 
= True 
1126             tsize 
= list(self
.transform
.GetSize()) 
1127             tsize
[0] = tsize
[0] + 50 
1128             tsize
[1] = tsize
[1] + 50 
1129             w
, h 
= self
.GetSize() 
1130             if tsize
[0] > w 
or tsize
[1] > h
: 
1131                 if not hasattr(self
, '_oldsize') or (tsize
[0] > self
._oldsize
[0] or tsize
[1] > self
._oldsize
[1]): 
1132                     self
._oldsize 
= tsize
 
1133                     oldstart 
= self
.GetViewStart() 
1134                     self
._lastPhysicalSize 
= self
.GetSize() 
1135                     self
.SetScrollbars(10, 10, tsize
[0]/10, tsize
[1]/10) 
1136                     self
.Scroll(oldstart
[0], oldstart
[1]) 
1137             dc 
= wx
.PaintDC(self
) 
1139             dc
.SetFont(self
.GetFont()) 
1140             self
.painter
.Paint(dc
, self
.currentRoot
, self
.doubleBuffered
) 
1142             traceback
.print_exc()