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.
8 wxMVCTree is a control which handles hierarchical data. It is constructed
9 in model-view-controller architecture, so the display of that data, and
10 the content of the data can be changed greatly without affecting the other parts.
12 wxMVCTree actually is even more configurable than MVC normally implies, because
13 almost every aspect of it is pluggable:
14 wxMVCTree - Overall controller, and the window that actually gets placed
16 Painter - Paints the control. The 'view' part of MVC.
17 NodePainter - Paints just the nodes
18 LinePainter - Paints just the lines between the nodes
19 TextConverter - Figures out what text to print for each node
20 Editor - Edits the contents of a node, if the model is editable.
21 LayoutEngine - Determines initial placement of nodes
22 Transform - Adjusts positions of nodes for movement or special effects.
23 TreeModel - Contains the data which the rest of the control acts
24 on. The 'model' part of MVC.
26 Author/Maintainer - Bryn Keller <xoltar@starship.python.net>
29 NOTE: This module is *not* supported in any way. Use it however you
30 wish, but be warned that dealing with any consequences is
35 #------------------------------------------------------------------------
42 #------------------------------------------------------------------------
46 ################################################\
47 # This module is not supported in any way! |
49 # See cource code for wx.lib.mvctree for more |
51 ################################################/
55 warnings
.warn(warningmsg
, DeprecationWarning, stacklevel
=2)
56 #------------------------------------------------------------------------
60 Used internally by wxMVCTree to manage its data. Contains information about
61 screen placement, the actual data associated with it, and more. These are
62 the nodes passed to all the other helper parts to do their work with.
64 def __init__(self
, data
=None, parent
= None, kids
= None, x
= 0, y
= 0):
79 def GetChildren(self
):
85 def Remove(self
, node
):
87 self
.kids
.remove(node
)
91 self
.kids
.append(node
)
94 def SetParent(self
, parent
):
95 if self
.parent
and not (self
.parent
is parent
):
96 self
.parent
.Remove(self
)
99 return "Node: " + str(self
.data
) + " (" + str(self
.x
) + ", " + str(self
.y
) + ")"
101 return str(self
.data
)
102 def GetTreeString(self
, tabs
=0):
103 s
= tabs
* '\t' + str(self
) + '\n'
104 for kid
in self
.kids
:
105 s
= s
+ kid
.GetTreeString(tabs
+ 1)
110 def __init__(self
, tree
):
112 def Edit(self
, node
):
113 raise NotImplementedError
114 def EndEdit(self
, node
, commit
):
115 raise NotImplementedError
116 def CanEdit(self
, node
):
117 raise NotImplementedError
121 Interface for layout engines.
123 def __init__(self
, tree
):
125 def Layout(self
, node
):
126 raise NotImplementedError
127 def GetNodeList(self
):
128 raise NotImplementedError
134 def __init__(self
, tree
):
136 def Transform(self
, node
, offset
, rotation
):
138 This method should only change the projx and projy attributes of
139 the node. These represent the position of the node as it should
140 be drawn on screen. Adjusting the x and y attributes can and
143 raise NotImplementedError
147 Returns the size of the entire tree as laid out and transformed
150 raise NotImplementedError
154 This is the interface that wxMVCTree expects from painters. All painters should
155 be Painter subclasses.
157 def __init__(self
, tree
):
159 self
.textcolor
= wx
.NamedColour("BLACK")
160 self
.bgcolor
= wx
.NamedColour("WHITE")
161 self
.fgcolor
= wx
.NamedColour("BLUE")
162 self
.linecolor
= wx
.NamedColour("GREY")
163 self
.font
= wx
.Font(9, wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
, False)
169 def SetFont(self
, font
):
174 def ClearBuffer(self
):
176 def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
177 raise NotImplementedError
178 def GetTextColour(self
):
179 return self
.textcolor
180 def SetTextColour(self
, color
):
181 self
.textcolor
= color
182 self
.textbrush
= wx
.Brush(color
)
183 self
.textpen
= wx
.Pen(color
, 1, wx
.SOLID
)
184 def GetBackgroundColour(self
):
186 def SetBackgroundColour(self
, color
):
188 self
.bgbrush
= wx
.Brush(color
)
189 self
.bgpen
= wx
.Pen(color
, 1, wx
.SOLID
)
190 def GetForegroundColour(self
):
192 def SetForegroundColour(self
, color
):
194 self
.fgbrush
= wx
.Brush(color
)
195 self
.fgpen
= wx
.Pen(color
, 1, wx
.SOLID
)
196 def GetLineColour(self
):
197 return self
.linecolor
198 def SetLineColour(self
, color
):
199 self
.linecolor
= color
200 self
.linebrush
= wx
.Brush(color
)
201 self
.linepen
= wx
.Pen( color
, 1, wx
.SOLID
)
202 def GetForegroundPen(self
):
204 def GetBackgroundPen(self
):
206 def GetTextPen(self
):
208 def GetForegroundBrush(self
):
210 def GetBackgroundBrush(self
):
212 def GetTextBrush(self
):
213 return self
.textbrush
214 def GetLinePen(self
):
216 def GetLineBrush(self
):
217 return self
.linebrush
218 def OnMouse(self
, evt
):
220 x
, y
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY())
221 for item
in self
.rectangles
:
222 if item
[1].Contains((x
,y
)):
223 self
.tree
.Edit(item
[0].data
)
224 self
.tree
.OnNodeClick(item
[0], evt
)
226 elif evt
.ButtonDown():
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
.OnNodeClick(item
[0], evt
)
232 for item
in self
.knobs
:
233 if item
[1].Contains((x
, y
)):
234 self
.tree
.OnKnobClick(item
[0])
241 Interface for tree models
244 raise NotImplementedError
245 def SetRoot(self
, root
):
246 raise NotImplementedError
247 def GetChildCount(self
, node
):
248 raise NotImplementedError
249 def GetChildAt(self
, node
, index
):
250 raise NotImplementedError
251 def GetParent(self
, node
):
252 raise NotImplementedError
253 def AddChild(self
, parent
, child
):
254 if hasattr(self
, 'tree') and self
.tree
:
255 self
.tree
.NodeAdded(parent
, child
)
256 def RemoveNode(self
, child
):
257 if hasattr(self
, 'tree') and self
.tree
:
258 self
.tree
.NodeRemoved(child
)
259 def InsertChild(self
, parent
, child
, index
):
260 if hasattr(self
, 'tree') and self
.tree
:
261 self
.tree
.NodeInserted(parent
, child
, index
)
262 def IsLeaf(self
, node
):
263 raise NotImplementedError
265 def IsEditable(self
, node
):
268 def SetEditable(self
, node
):
273 This is the interface expected of a nodepainter.
275 def __init__(self
, painter
):
276 self
.painter
= painter
277 def Paint(self
, node
, dc
, location
= None):
279 location should be provided only to draw in an unusual position
280 (not the node's normal position), otherwise the node's projected x and y
281 coordinates will be used.
283 raise NotImplementedError
287 The linepainter interface.
289 def __init__(self
, painter
):
290 self
.painter
= painter
291 def Paint(self
, parent
, child
, dc
):
292 raise NotImplementedError
296 TextConverter interface.
298 def __init__(self
, painter
):
299 self
.painter
= painter
302 Should return a string. The node argument will be an
305 raise NotImplementedError
308 class BasicTreeModel(wxTreeModel
):
310 A very simple treemodel implementation, but flexible enough for many needs.
318 def SetRoot(self
, root
):
320 def GetChildCount(self
, node
):
321 if self
.children
.has_key(node
):
322 return len(self
.children
[node
])
325 def GetChildAt(self
, node
, index
):
326 return self
.children
[node
][index
]
328 def GetParent(self
, node
):
329 return self
.parents
[node
]
331 def AddChild(self
, parent
, child
):
332 self
.parents
[child
]=parent
333 if not self
.children
.has_key(parent
):
334 self
.children
[parent
]=[]
335 self
.children
[parent
].append(child
)
336 wxTreeModel
.AddChild(self
, parent
, child
)
339 def RemoveNode(self
, node
):
340 parent
= self
.parents
[node
]
341 del self
.parents
[node
]
342 self
.children
[parent
].remove(node
)
343 wxTreeModel
.RemoveNode(self
, node
)
345 def InsertChild(self
, parent
, child
, index
):
346 self
.parents
[child
]=parent
347 if not self
.children
.has_key(parent
):
348 self
.children
[parent
]=[]
349 self
.children
[parent
].insert(child
, index
)
350 wxTreeModel
.InsertChild(self
, parent
, child
, index
)
353 def IsLeaf(self
, node
):
354 return not self
.children
.has_key(node
)
356 def IsEditable(self
, node
):
359 def SetEditable(self
, node
, bool):
363 class FileEditor(Editor
):
364 def Edit(self
, node
):
365 treenode
= self
.tree
.nodemap
[node
]
366 self
.editcomp
= wxTextCtrl(self
.tree
, -1)
367 for rect
in self
.tree
.painter
.rectangles
:
368 if rect
[0] == treenode
:
369 self
.editcomp
.SetPosition((rect
[1][0], rect
[1][1]))
371 self
.editcomp
.SetValue(node
.fileName
)
372 self
.editcomp
.SetSelection(0, len(node
.fileName
))
373 self
.editcomp
.SetFocus()
374 self
.treenode
= treenode
375 # self.editcomp.Bind(wx.EVT_KEY_DOWN, self._key)
376 self
.editcomp
.Bind(wx
.EVT_KEY_UP
, self
._key
)
377 self
.editcomp
.Bind(wx
.EVT_LEFT_DOWN
, self
._mdown
)
378 self
.editcomp
.CaptureMouse()
380 def CanEdit(self
, node
):
381 return isinstance(node
, FileWrapper
)
383 def EndEdit(self
, commit
):
384 if not self
.tree
._EditEnding
(self
.treenode
.data
):
387 node
= self
.treenode
.data
389 os
.rename(node
.path
+ os
.sep
+ node
.fileName
, node
.path
+ os
.sep
+ self
.editcomp
.GetValue())
390 node
.fileName
= self
.editcomp
.GetValue()
392 traceback
.print_exc()
393 self
.editcomp
.ReleaseMouse()
394 self
.editcomp
.Destroy()
400 if evt
.KeyCode() == wx
.WXK_RETURN
:
402 elif evt
.KeyCode() == wx
.WXK_ESCAPE
:
407 def _mdown(self
, evt
):
409 x
, y
= evt
.GetPosition()
410 w
, h
= self
.editcomp
.GetSize()
411 if x
< 0 or y
< 0 or x
> w
or y
> h
:
417 Node class for FSTreeModel.
419 def __init__(self
, path
, fileName
):
421 self
.fileName
= fileName
426 class FSTreeModel(BasicTreeModel
):
428 This treemodel models the filesystem starting from a given path.
430 def __init__(self
, path
):
431 BasicTreeModel
.__init
__(self
)
432 fw
= FileWrapper(path
, path
.split(os
.sep
)[-1])
433 self
._Build
(path
, fw
)
435 self
._editable
= True
436 def _Build(self
, path
, fileWrapper
):
437 for name
in os
.listdir(path
):
438 fw
= FileWrapper(path
, name
)
439 self
.AddChild(fileWrapper
, fw
)
440 childName
= path
+ os
.sep
+ name
441 if os
.path
.isdir(childName
):
442 self
._Build
(childName
, fw
)
444 def IsEditable(self
, node
):
445 return self
._editable
447 def SetEditable(self
, node
, bool):
448 self
._editable
= bool
450 class LateFSTreeModel(FSTreeModel
):
452 This treemodel models the filesystem starting from a given path.
453 It retrieves the directory list as requested.
455 def __init__(self
, path
):
456 BasicTreeModel
.__init
__(self
)
457 name
= path
.split(os
.sep
)[-1]
458 pathpart
= path
[:-len(name
)]
459 fw
= FileWrapper(pathpart
, name
)
460 self
._Build
(path
, fw
)
462 self
._editable
= True
465 def _Build(self
, path
, parent
):
466 ppath
= parent
.path
+ os
.sep
+ parent
.fileName
467 if not os
.path
.isdir(ppath
):
469 for name
in os
.listdir(ppath
):
470 fw
= FileWrapper(ppath
, name
)
471 self
.AddChild(parent
, fw
)
472 def GetChildCount(self
, node
):
473 if self
.children
.has_key(node
):
474 return FSTreeModel
.GetChildCount(self
, node
)
476 self
._Build
(node
.path
, node
)
477 return FSTreeModel
.GetChildCount(self
, node
)
479 def IsLeaf(self
, node
):
480 return not os
.path
.isdir(node
.path
+ os
.sep
+ node
.fileName
)
482 class StrTextConverter(TextConverter
):
483 def Convert(self
, node
):
484 return str(node
.data
)
486 class NullTransform(Transform
):
488 return tuple(self
.size
)
490 def Transform(self
, node
, offset
, rotation
):
492 list = self
.tree
.GetLayoutEngine().GetNodeList()
494 node
.projx
= node
.x
+ offset
[0]
495 node
.projy
= node
.y
+ offset
[1]
496 if node
.projx
> self
.size
[0]:
497 self
.size
[0] = node
.projx
498 if node
.projy
> self
.size
[1]:
499 self
.size
[1] = node
.projy
502 def __init__(self
, x
, y
, width
, height
):
507 def __getitem__(self
, index
):
508 return (self
.x
, self
.y
, self
.width
, self
.height
)[index
]
510 def __setitem__(self
, index
, value
):
511 name
= ['x', 'y', 'width', 'height'][index
]
512 setattr(self
, name
, value
)
514 def Contains(self
, other
):
515 if type(other
) == type(()):
516 other
= Rect(other
[0], other
[1], 0, 0)
517 if other
.x
>= self
.x
:
518 if other
.y
>= self
.y
:
519 if other
.width
+ other
.x
<= self
.width
+ self
.x
:
520 if other
.height
+ other
.y
<= self
.height
+ self
.y
:
525 return "Rect: " + str([self
.x
, self
.y
, self
.width
, self
.height
])
527 class TreeLayout(LayoutEngine
):
528 def SetHeight(self
, num
):
529 self
.NODE_HEIGHT
= num
531 def __init__(self
, tree
):
532 LayoutEngine
.__init
__(self
, tree
)
534 self
.NODE_HEIGHT
= 20
537 def Layout(self
, node
):
539 self
.NODE_HEIGHT
= self
.tree
.GetFont().GetPointSize() * 2
540 self
.layoutwalk(node
)
542 def GetNodeList(self
):
545 def layoutwalk(self
, node
):
546 if node
== self
.tree
.currentRoot
:
548 self
.lastY
= (-self
.NODE_HEIGHT
)
549 node
.x
= self
.NODE_STEP
* node
.level
550 node
.y
= self
.lastY
+ self
.NODE_HEIGHT
552 self
.nodelist
.append(node
)
554 for kid
in node
.kids
:
555 kid
.level
= node
.level
+ 1
558 class TreePainter(Painter
):
560 The default painter class. Uses double-buffering, delegates the painting of nodes and
561 lines to helper classes deriving from NodePainter and LinePainter.
563 def __init__(self
, tree
, nodePainter
= None, linePainter
= None, textConverter
= None):
564 Painter
.__init
__(self
, tree
)
566 nodePainter
= TreeNodePainter(self
)
567 self
.nodePainter
= nodePainter
569 linePainter
= TreeLinePainter(self
)
570 self
.linePainter
= linePainter
571 if not textConverter
:
572 textConverter
= StrTextConverter(self
)
573 self
.textConverter
= textConverter
576 def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
577 if not self
.charWidths
:
580 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
)
581 self
.charHeight
= dc
.GetTextExtent("D")[1]
582 self
.textpen
= wx
.Pen(self
.GetTextColour(), 1, wx
.SOLID
)
583 self
.fgpen
= wx
.Pen(self
.GetForegroundColour(), 1, wx
.SOLID
)
584 self
.bgpen
= wx
.Pen(self
.GetBackgroundColour(), 1, wx
.SOLID
)
585 self
.linepen
= wx
.Pen(self
.GetLineColour(), 1, wx
.SOLID
)
586 self
.dashpen
= wx
.Pen(self
.GetLineColour(), 1, wx
.DOT
)
587 self
.textbrush
= wx
.Brush(self
.GetTextColour(), wx
.SOLID
)
588 self
.fgbrush
= wx
.Brush(self
.GetForegroundColour(), wx
.SOLID
)
589 self
.bgbrush
= wx
.Brush(self
.GetBackgroundColour(), wx
.SOLID
)
590 self
.linebrush
= wx
.Pen(self
.GetLineColour(), 1, wx
.SOLID
)
591 treesize
= self
.tree
.GetSize()
592 size
= self
.tree
.transform
.GetSize()
593 size
= (max(treesize
.width
, size
[0]+50), max(treesize
.height
, size
[1]+50))
596 mem_dc
= wx
.MemoryDC()
597 if not self
.GetBuffer():
600 self
.bmp
= wx
.EmptyBitmap(size
[0], size
[1])
601 mem_dc
.SelectObject(self
.GetBuffer())
602 mem_dc
.SetPen(self
.GetBackgroundPen())
603 mem_dc
.SetBrush(self
.GetBackgroundBrush())
604 mem_dc
.DrawRectangle((0, 0), (size
[0], size
[1]))
605 mem_dc
.SetFont(self
.tree
.GetFont())
606 self
.paintWalk(node
, mem_dc
)
608 mem_dc
.SelectObject(self
.GetBuffer())
609 xstart
, ystart
= self
.tree
.CalcUnscrolledPosition(0,0)
610 size
= self
.tree
.GetClientSizeTuple()
611 dc
.Blit((xstart
, ystart
), (size
[0], size
[1]), mem_dc
, (xstart
, ystart
))
613 if node
== self
.tree
.currentRoot
:
616 dc
.SetPen(self
.GetBackgroundPen())
617 dc
.SetBrush(self
.GetBackgroundBrush())
618 dc
.SetFont(self
.tree
.GetFont())
620 dc
.DrawRectangle((0, 0), (size
[0], size
[1]))
622 #Call with not paintBackground because if we are told not to paint the
623 #whole background, we have to paint in parts to undo selection coloring.
625 self
.paintWalk(node
, dc
, not pb
)
628 def GetDashPen(self
):
631 def SetLinePen(self
, pen
):
632 Painter
.SetLinePen(self
, pen
)
633 self
.dashpen
= wx
.Pen(pen
.GetColour(), 1, wx
.DOT
)
635 def paintWalk(self
, node
, dc
, paintRects
=0):
636 self
.linePainter
.Paint(node
.parent
, node
, dc
)
637 self
.nodePainter
.Paint(node
, dc
, drawRects
= paintRects
)
639 for kid
in node
.kids
:
640 if not self
.paintWalk(kid
, dc
, paintRects
):
642 for kid
in node
.kids
:
643 px
= (kid
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
644 py
= kid
.projy
+ kid
.height
/2
645 if (not self
.tree
.model
.IsLeaf(kid
.data
)) or ((kid
.expanded
or self
.tree
._assumeChildren
) and len(kid
.kids
)):
646 dc
.SetPen(self
.linepen
)
647 dc
.SetBrush(self
.bgbrush
)
648 dc
.DrawRectangle((px
-4, py
-4), (9, 9))
649 self
.knobs
.append( (kid
, Rect(px
-4, py
-4, 9, 9)) )
650 dc
.SetPen(self
.textpen
)
652 dc
.DrawLine((px
, py
-2), (px
, py
+ 3))
653 dc
.DrawLine((px
-2, py
), (px
+ 3, py
))
654 if node
== self
.tree
.currentRoot
:
655 px
= (node
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
656 py
= node
.projy
+ node
.height
/2
657 dc
.SetPen(self
.linepen
)
658 dc
.SetBrush(self
.bgbrush
)
659 dc
.DrawRectangle((px
-4, py
-4), (9, 9))
660 self
.knobs
.append( (node
, Rect(px
-4, py
-4, 9, 9)) )
661 dc
.SetPen(self
.textpen
)
662 if not node
.expanded
:
663 dc
.DrawLine((px
, py
-2), (px
, py
+ 3))
664 dc
.DrawLine((px
-2, py
), (px
+ 3, py
))
667 def OnMouse(self
, evt
):
668 Painter
.OnMouse(self
, evt
)
670 class TreeNodePainter(NodePainter
):
671 def Paint(self
, node
, dc
, location
= None, drawRects
= 0):
672 text
= self
.painter
.textConverter
.Convert(node
)
673 extent
= dc
.GetTextExtent(text
)
674 node
.width
= extent
[0]
675 node
.height
= extent
[1]
677 dc
.SetPen(self
.painter
.GetLinePen())
678 dc
.SetBrush(self
.painter
.GetForegroundBrush())
679 dc
.SetTextForeground(wx
.NamedColour("WHITE"))
680 dc
.DrawRectangle((node
.projx
-1, node
.projy
-1), (node
.width
+ 3, node
.height
+ 3))
683 dc
.SetBrush(self
.painter
.GetBackgroundBrush())
684 dc
.SetPen(self
.painter
.GetBackgroundPen())
685 dc
.DrawRectangle((node
.projx
-1, node
.projy
-1), (node
.width
+ 3, node
.height
+ 3))
686 dc
.SetTextForeground(self
.painter
.GetTextColour())
687 dc
.DrawText(text
, (node
.projx
, node
.projy
))
688 self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
)))
690 class TreeLinePainter(LinePainter
):
691 def Paint(self
, parent
, child
, dc
):
692 dc
.SetPen(self
.painter
.GetDashPen())
693 px
= py
= cx
= cy
= 0
694 if parent
is None or child
== self
.painter
.tree
.currentRoot
:
695 px
= (child
.projx
- self
.painter
.tree
.layout
.NODE_STEP
) + 5
696 py
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -2
699 dc
.DrawLine((px
, py
), (cx
, cy
))
701 px
= parent
.projx
+ 5
702 py
= parent
.projy
+ parent
.height
704 cy
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -3
705 dc
.DrawLine((px
, py
), (px
, cy
))
706 dc
.DrawLine((px
, cy
), (cx
, cy
))
709 wxEVT_MVCTREE_BEGIN_EDIT
= wx
.NewEventType() #Start editing. Vetoable.
710 wxEVT_MVCTREE_END_EDIT
= wx
.NewEventType() #Stop editing. Vetoable.
711 wxEVT_MVCTREE_DELETE_ITEM
= wx
.NewEventType() #Item removed from model.
712 wxEVT_MVCTREE_ITEM_EXPANDED
= wx
.NewEventType()
713 wxEVT_MVCTREE_ITEM_EXPANDING
= wx
.NewEventType()
714 wxEVT_MVCTREE_ITEM_COLLAPSED
= wx
.NewEventType()
715 wxEVT_MVCTREE_ITEM_COLLAPSING
= wx
.NewEventType()
716 wxEVT_MVCTREE_SEL_CHANGED
= wx
.NewEventType()
717 wxEVT_MVCTREE_SEL_CHANGING
= wx
.NewEventType() #Vetoable.
718 wxEVT_MVCTREE_KEY_DOWN
= wx
.NewEventType()
719 wxEVT_MVCTREE_ADD_ITEM
= wx
.NewEventType() #Item added to model.
721 EVT_MVCTREE_SEL_CHANGED
= wx
.PyEventBinder(wxEVT_MVCTREE_SEL_CHANGED
, 1)
722 EVT_MVCTREE_SEL_CHANGING
= wx
.PyEventBinder(wxEVT_MVCTREE_SEL_CHANGING
, 1)
723 EVT_MVCTREE_ITEM_EXPANDED
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_EXPANDED
, 1)
724 EVT_MVCTREE_ITEM_EXPANDING
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_EXPANDING
, 1)
725 EVT_MVCTREE_ITEM_COLLAPSED
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_COLLAPSED
, 1)
726 EVT_MVCTREE_ITEM_COLLAPSING
= wx
.PyEventBinder(wxEVT_MVCTREE_ITEM_COLLAPSING
, 1)
727 EVT_MVCTREE_ADD_ITEM
= wx
.PyEventBinder(wxEVT_MVCTREE_ADD_ITEM
, 1)
728 EVT_MVCTREE_DELETE_ITEM
= wx
.PyEventBinder(wxEVT_MVCTREE_DELETE_ITEM
, 1)
729 EVT_MVCTREE_KEY_DOWN
= wx
.PyEventBinder(wxEVT_MVCTREE_KEY_DOWN
, 1)
731 class wxMVCTreeEvent(wx
.PyCommandEvent
):
732 def __init__(self
, type, id, node
= None, nodes
= None, keyEvent
= None, **kwargs
):
733 apply(wx
.PyCommandEvent
.__init
__, (self
, type, id), kwargs
)
736 self
.keyEvent
= keyEvent
741 def getKeyEvent(self
):
744 class wxMVCTreeNotifyEvent(wxMVCTreeEvent
):
745 def __init__(self
, type, id, node
= None, nodes
= None, **kwargs
):
746 apply(wxMVCTreeEvent
.__init
__, (self
, type, id, node
, nodes
), kwargs
)
747 self
.notify
= wx
.NotifyEvent(type, id)
748 def getNotifyEvent(self
):
751 class wxMVCTree(wx
.ScrolledWindow
):
753 The main mvc tree class.
755 def __init__(self
, parent
, id, model
= None, layout
= None, transform
= None,
756 painter
= None, *args
, **kwargs
):
757 apply(wx
.ScrolledWindow
.__init
__, (self
, parent
, id), kwargs
)
759 self
._multiselect
= False
760 self
._selections
= []
761 self
._assumeChildren
= False
762 self
._scrollx
= False
763 self
._scrolly
= False
764 self
.doubleBuffered
= False
765 self
._lastPhysicalSize
= self
.GetSize()
768 model
= BasicTreeModel()
769 model
.SetRoot("Root")
772 layout
= TreeLayout(self
)
775 transform
= NullTransform(self
)
776 self
.transform
= transform
778 painter
= TreePainter(self
)
779 self
.painter
= painter
780 self
.SetFont(wx
.Font(9, wx
.DEFAULT
, wx
.NORMAL
, wx
.NORMAL
, False))
781 self
.Bind(wx
.EVT_MOUSE_EVENTS
, self
.OnMouse
)
782 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
783 self
.doubleBuffered
= True
784 self
.Bind(wx
.EVT_SIZE
, self
.OnSize
)
785 self
.Bind(wx
.EVT_ERASE_BACKGROUND
, self
.OnEraseBackground
)
786 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
790 if self
.doubleBuffered
:
791 self
.painter
.ClearBuffer()
792 wx
.ScrolledWindow
.Refresh(self
, False)
794 def GetPainter(self
):
797 def GetLayoutEngine(self
):
800 def GetTransform(self
):
801 return self
.transform
804 return "<wxMVCTree instance at %s>" % str(hex(id(self
)))
807 return self
.__repr
__()
809 def NodeAdded(self
, parent
, child
):
810 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
811 self
.GetEventHandler().ProcessEvent(e
)
812 self
.painter
.ClearBuffer()
814 def NodeInserted(self
, parent
, child
, index
):
815 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
816 self
.GetEventHandler().ProcessEvent(e
)
817 self
.painter
.ClearBuffer()
819 def NodeRemoved(self
, node
):
820 e
= wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
821 self
.GetEventHandler().ProcessEvent(e
)
822 self
.painter
.ClearBuffer()
824 def OnKeyDown(self
, evt
):
825 e
= wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent
= evt
)
826 self
.GetEventHandler().ProcessEvent(e
)
828 def SetFont(self
, font
):
829 self
.painter
.SetFont(font
)
830 dc
= wx
.ClientDC(self
)
832 self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18)
833 self
.painter
.ClearBuffer()
836 return self
.painter
.GetFont()
838 def AddEditor(self
, editor
):
839 self
._editors
.append(editor
)
841 def RemoveEditor(self
, editor
):
842 self
._editors
.remove(editor
)
844 def OnMouse(self
, evt
):
845 self
.painter
.OnMouse(evt
)
847 def OnNodeClick(self
, node
, mouseEvent
):
848 if node
.selected
and (self
.IsMultiSelect() and mouseEvent
.ControlDown()):
849 self
.RemoveFromSelection(node
.data
)
851 self
.AddToSelection(node
.data
, mouseEvent
.ControlDown(), mouseEvent
.ShiftDown())
853 def OnKnobClick(self
, node
):
854 self
.SetExpanded(node
.data
, not node
.expanded
)
856 def GetDisplayText(self
, node
):
857 treenode
= self
.nodemap
[node
]
858 return self
.painter
.textConverter
.Convert(treenode
)
860 def IsDoubleBuffered(self
):
861 return self
.doubleBuffered
863 def SetDoubleBuffered(self
, bool):
865 By default wxMVCTree is double-buffered.
867 self
.doubleBuffered
= bool
872 def SetModel(self
, model
):
874 Completely change the data to be displayed.
880 self
._selections
= []
881 self
.layoutRoot
= MVCTreeNode()
882 self
.layoutRoot
.data
= self
.model
.GetRoot()
883 self
.layoutRoot
.expanded
= True
884 self
.LoadChildren(self
.layoutRoot
)
885 self
.currentRoot
= self
.layoutRoot
888 self
._scrollset
= None
891 def GetCurrentRoot(self
):
892 return self
.currentRoot
894 def LoadChildren(self
, layoutNode
):
898 self
.nodemap
[layoutNode
.data
]=layoutNode
899 for i
in range(self
.GetModel().GetChildCount(layoutNode
.data
)):
900 p
= MVCTreeNode("RAW", layoutNode
, [])
902 p
.data
= self
.GetModel().GetChildAt(layoutNode
.data
, i
)
903 self
.nodemap
[p
.data
]=p
904 layoutNode
.built
= True
905 if not self
._assumeChildren
:
906 for kid
in layoutNode
.kids
:
907 self
.LoadChildren(kid
)
909 def OnEraseBackground(self
, evt
):
912 def OnSize(self
, evt
):
913 size
= self
.GetSize()
914 self
.center
= (size
.width
/2, size
.height
/2)
915 if self
._lastPhysicalSize
.width
< size
.width
or self
._lastPhysicalSize
.height
< size
.height
:
916 self
.painter
.ClearBuffer()
917 self
._lastPhysicalSize
= size
919 def GetSelection(self
):
920 "Returns a tuple of selected nodes."
921 return tuple(self
._selections
)
923 def SetSelection(self
, nodeTuple
):
924 if type(nodeTuple
) != type(()):
925 nodeTuple
= (nodeTuple
,)
926 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
927 self
.GetEventHandler().ProcessEvent(e
)
928 if not e
.notify
.IsAllowed():
930 for node
in nodeTuple
:
931 treenode
= self
.nodemap
[node
]
932 treenode
.selected
= True
933 for node
in self
._selections
:
934 treenode
= self
.nodemap
[node
]
935 node
.selected
= False
936 self
._selections
= list(nodeTuple
)
937 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
938 self
.GetEventHandler().ProcessEvent(e
)
940 def IsMultiSelect(self
):
941 return self
._multiselect
943 def SetMultiSelect(self
, bool):
944 self
._multiselect
= bool
946 def IsSelected(self
, node
):
947 return self
.nodemap
[node
].selected
949 def Edit(self
, node
):
950 if not self
.model
.IsEditable(node
):
952 for ed
in self
._editors
:
954 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
)
955 self
.GetEventHandler().ProcessEvent(e
)
956 if not e
.notify
.IsAllowed():
959 self
._currentEditor
= ed
963 if self
._currentEditor
:
964 self
._currentEditor
.EndEdit
965 self
._currentEditor
= None
967 def _EditEnding(self
, node
):
968 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
)
969 self
.GetEventHandler().ProcessEvent(e
)
970 if not e
.notify
.IsAllowed():
972 self
._currentEditor
= None
976 def SetExpanded(self
, node
, bool):
977 treenode
= self
.nodemap
[node
]
979 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
)
980 self
.GetEventHandler().ProcessEvent(e
)
981 if not e
.notify
.IsAllowed():
983 if not treenode
.built
:
984 self
.LoadChildren(treenode
)
986 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
)
987 self
.GetEventHandler().ProcessEvent(e
)
988 if not e
.notify
.IsAllowed():
990 treenode
.expanded
= bool
992 if treenode
.expanded
:
993 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
)
995 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
)
996 self
.GetEventHandler().ProcessEvent(e
)
997 self
.layout
.Layout(self
.currentRoot
)
998 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1001 def IsExpanded(self
, node
):
1002 return self
.nodemap
[node
].expanded
1004 def AddToSelection(self
, nodeOrTuple
, enableMulti
= True, shiftMulti
= False):
1005 nodeTuple
= nodeOrTuple
1006 if type(nodeOrTuple
)!= type(()):
1007 nodeTuple
= (nodeOrTuple
,)
1008 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1009 self
.GetEventHandler().ProcessEvent(e
)
1010 if not e
.notify
.IsAllowed():
1013 if not (self
.IsMultiSelect() and (enableMulti
or shiftMulti
)):
1014 for node
in self
._selections
:
1015 treenode
= self
.nodemap
[node
]
1016 treenode
.selected
= False
1017 changeparents
.append(treenode
)
1019 self
._selections
= [node
]
1020 treenode
= self
.nodemap
[node
]
1021 changeparents
.append(treenode
)
1022 treenode
.selected
= True
1025 for node
in nodeTuple
:
1026 treenode
= self
.nodemap
[node
]
1027 oldtreenode
= self
.nodemap
[self
._selections
[0]]
1028 if treenode
.parent
== oldtreenode
.parent
:
1030 for kid
in oldtreenode
.parent
.kids
:
1031 if kid
== treenode
or kid
== oldtreenode
:
1034 self
._selections
.append(kid
.data
)
1035 changeparents
.append(kid
)
1038 self
._selections
.append(kid
.data
)
1039 changeparents
.append(kid
)
1041 for node
in nodeTuple
:
1043 self
._selections
.index(node
)
1045 self
._selections
.append(node
)
1046 treenode
= self
.nodemap
[node
]
1047 treenode
.selected
= True
1048 changeparents
.append(treenode
)
1049 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1050 self
.GetEventHandler().ProcessEvent(e
)
1051 dc
= wx
.ClientDC(self
)
1053 for node
in changeparents
:
1055 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1056 self
.painter
.ClearBuffer()
1058 def RemoveFromSelection(self
, nodeTuple
):
1059 if type(nodeTuple
) != type(()):
1060 nodeTuple
= (nodeTuple
,)
1062 for node
in nodeTuple
:
1063 self
._selections
.remove(node
)
1064 treenode
= self
.nodemap
[node
]
1065 changeparents
.append(treenode
)
1066 treenode
.selected
= False
1067 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes
= nodeTuple
)
1068 self
.GetEventHandler().ProcessEvent(e
)
1069 dc
= wx
.ClientDC(self
)
1071 for node
in changeparents
:
1073 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1074 self
.painter
.ClearBuffer()
1077 def GetBackgroundColour(self
):
1078 if hasattr(self
, 'painter') and self
.painter
:
1079 return self
.painter
.GetBackgroundColour()
1081 return wx
.Window
.GetBackgroundColour(self
)
1082 def SetBackgroundColour(self
, color
):
1083 if hasattr(self
, 'painter') and self
.painter
:
1084 self
.painter
.SetBackgroundColour(color
)
1086 wx
.Window
.SetBackgroundColour(self
, color
)
1087 def GetForegroundColour(self
):
1088 if hasattr(self
, 'painter') and self
.painter
:
1089 return self
.painter
.GetForegroundColour()
1091 return wx
.Window
.GetBackgroundColour(self
)
1092 def SetForegroundColour(self
, color
):
1093 if hasattr(self
, 'painter') and self
.painter
:
1094 self
.painter
.SetForegroundColour(color
)
1096 wx
.Window
.SetBackgroundColour(self
, color
)
1098 def SetAssumeChildren(self
, bool):
1099 self
._assumeChildren
= bool
1101 def GetAssumeChildren(self
):
1102 return self
._assumeChildren
1104 def OnPaint(self
, evt
):
1106 Ensures that the tree has been laid out and transformed, then calls the painter
1107 to paint the control.
1110 self
.EnableScrolling(False, False)
1111 if not self
.laidOut
:
1112 self
.layout
.Layout(self
.currentRoot
)
1114 self
.transformed
= False
1115 if not self
.transformed
:
1116 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1117 self
.transformed
= True
1119 tsize
= list(self
.transform
.GetSize())
1120 tsize
[0] = tsize
[0] + 50
1121 tsize
[1] = tsize
[1] + 50
1122 w
, h
= self
.GetSize()
1123 if tsize
[0] > w
or tsize
[1] > h
:
1124 if not hasattr(self
, '_oldsize') or (tsize
[0] > self
._oldsize
[0] or tsize
[1] > self
._oldsize
[1]):
1125 self
._oldsize
= tsize
1126 oldstart
= self
.GetViewStart()
1127 self
._lastPhysicalSize
= self
.GetSize()
1128 self
.SetScrollbars(10, 10, tsize
[0]/10, tsize
[1]/10)
1129 self
.Scroll(oldstart
[0], oldstart
[1])
1130 dc
= wx
.PaintDC(self
)
1132 dc
.SetFont(self
.GetFont())
1133 self
.painter
.Paint(dc
, self
.currentRoot
, self
.doubleBuffered
)
1135 traceback
.print_exc()