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()