2 wxMVCTree is a control which handles hierarchical data. It is constructed
3 in model-view-controller architecture, so the display of that data, and
4 the content of the data can be changed greatly without affecting the other parts.
6 wxMVCTree actually is even more configurable than MVC normally implies, because
7 almost every aspect of it is pluggable:
8 wxMVCTree - Overall controller, and the window that actually gets placed
10 Painter - Paints the control. The 'view' part of MVC.
11 NodePainter - Paints just the nodes
12 LinePainter - Paints just the lines between the nodes
13 TextConverter - Figures out what text to print for each node
14 Editor - Edits the contents of a node, if the model is editable.
15 LayoutEngine - Determines initial placement of nodes
16 Transform - Adjusts positions of nodes for movement or special effects.
17 TreeModel - Contains the data which the rest of the control acts
18 on. The 'model' part of MVC.
20 Author/Maintainer - Bryn Keller <xoltar@starship.python.net>
23 NOTE: This module is *not* supported in any way. Use it however you
24 wish, but be warned that dealing with any consequences is
29 #------------------------------------------------------------------------
30 from wxPython
.wx
import *
31 import os
, sys
, traceback
32 #------------------------------------------------------------------------
36 Used internally by wxMVCTree to manage its data. Contains information about
37 screen placement, the actual data associated with it, and more. These are
38 the nodes passed to all the other helper parts to do their work with.
40 def __init__(self
, data
=None, parent
= None, kids
= None, x
= 0, y
= 0):
55 def GetChildren(self
):
61 def Remove(self
, node
):
63 self
.kids
.remove(node
)
67 self
.kids
.append(node
)
70 def SetParent(self
, parent
):
71 if self
.parent
and not (self
.parent
is parent
):
72 self
.parent
.Remove(self
)
75 return "Node: " + str(self
.data
) + " (" + str(self
.x
) + ", " + str(self
.y
) + ")"
78 def GetTreeString(self
, tabs
=0):
79 s
= tabs
* '\t' + str(self
) + '\n'
81 s
= s
+ kid
.GetTreeString(tabs
+ 1)
86 def __init__(self
, tree
):
89 raise NotImplementedError
90 def EndEdit(self
, node
, commit
):
91 raise NotImplementedError
92 def CanEdit(self
, node
):
93 raise NotImplementedError
97 Interface for layout engines.
99 def __init__(self
, tree
):
101 def Layout(self
, node
):
102 raise NotImplementedError
103 def GetNodeList(self
):
104 raise NotImplementedError
110 def __init__(self
, tree
):
112 def Transform(self
, node
, offset
, rotation
):
114 This method should only change the projx and projy attributes of
115 the node. These represent the position of the node as it should
116 be drawn on screen. Adjusting the x and y attributes can and
119 raise NotImplementedError
123 Returns the size of the entire tree as laid out and transformed
126 raise NotImplementedError
130 This is the interface that wxMVCTree expects from painters. All painters should
131 be Painter subclasses.
133 def __init__(self
, tree
):
135 self
.textcolor
= wxNamedColour("BLACK")
136 self
.bgcolor
= wxNamedColour("WHITE")
137 self
.fgcolor
= wxNamedColour("BLUE")
138 self
.linecolor
= wxNamedColour("GREY")
139 self
.font
= wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
)
145 def SetFont(self
, font
):
150 def ClearBuffer(self
):
152 def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
153 raise NotImplementedError
154 def GetTextColour(self
):
155 return self
.textcolor
156 def SetTextColour(self
, color
):
157 self
.textcolor
= color
158 self
.textbrush
= wxBrush(color
)
159 self
.textpen
= wxPen(color
, 1, wxSOLID
)
160 def GetBackgroundColour(self
):
162 def SetBackgroundColour(self
, color
):
164 self
.bgbrush
= wxBrush(color
)
165 self
.bgpen
= wxPen(color
, 1, wxSOLID
)
166 def GetForegroundColour(self
):
168 def SetForegroundColour(self
, color
):
170 self
.fgbrush
= wxBrush(color
)
171 self
.fgpen
= wxPen(color
, 1, wxSOLID
)
172 def GetLineColour(self
):
173 return self
.linecolor
174 def SetLineColour(self
, color
):
175 self
.linecolor
= color
176 self
.linebrush
= wxBrush(color
)
177 self
.linepen
= wxPen( color
, 1, wxSOLID
)
178 def GetForegroundPen(self
):
180 def GetBackgroundPen(self
):
182 def GetTextPen(self
):
184 def GetForegroundBrush(self
):
186 def GetBackgroundBrush(self
):
188 def GetTextBrush(self
):
189 return self
.textbrush
190 def GetLinePen(self
):
192 def GetLineBrush(self
):
193 return self
.linebrush
194 def OnMouse(self
, evt
):
196 x
, y
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY())
197 for item
in self
.rectangles
:
198 if item
[1].Contains((x
,y
)):
199 self
.tree
.Edit(item
[0].data
)
200 self
.tree
.OnNodeClick(item
[0], evt
)
202 elif evt
.ButtonDown():
203 x
, y
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY())
204 for item
in self
.rectangles
:
205 if item
[1].Contains((x
, y
)):
206 self
.tree
.OnNodeClick(item
[0], evt
)
208 for item
in self
.knobs
:
209 if item
[1].Contains((x
, y
)):
210 self
.tree
.OnKnobClick(item
[0])
217 Interface for tree models
220 raise NotImplementedError
221 def SetRoot(self
, root
):
222 raise NotImplementedError
223 def GetChildCount(self
, node
):
224 raise NotImplementedError
225 def GetChildAt(self
, node
, index
):
226 raise NotImplementedError
227 def GetParent(self
, node
):
228 raise NotImplementedError
229 def AddChild(self
, parent
, child
):
230 if hasattr(self
, 'tree') and self
.tree
:
231 self
.tree
.NodeAdded(parent
, child
)
232 def RemoveNode(self
, child
):
233 if hasattr(self
, 'tree') and self
.tree
:
234 self
.tree
.NodeRemoved(child
)
235 def InsertChild(self
, parent
, child
, index
):
236 if hasattr(self
, 'tree') and self
.tree
:
237 self
.tree
.NodeInserted(parent
, child
, index
)
238 def IsLeaf(self
, node
):
239 raise NotImplementedError
241 def IsEditable(self
, node
):
244 def SetEditable(self
, node
):
249 This is the interface expected of a nodepainter.
251 def __init__(self
, painter
):
252 self
.painter
= painter
253 def Paint(self
, node
, dc
, location
= None):
255 location should be provided only to draw in an unusual position
256 (not the node's normal position), otherwise the node's projected x and y
257 coordinates will be used.
259 raise NotImplementedError
263 The linepainter interface.
265 def __init__(self
, painter
):
266 self
.painter
= painter
267 def Paint(self
, parent
, child
, dc
):
268 raise NotImplementedError
272 TextConverter interface.
274 def __init__(self
, painter
):
275 self
.painter
= painter
278 Should return a string. The node argument will be an
281 raise NotImplementedError
284 class BasicTreeModel(wxTreeModel
):
286 A very simple treemodel implementation, but flexible enough for many needs.
294 def SetRoot(self
, root
):
296 def GetChildCount(self
, node
):
297 if self
.children
.has_key(node
):
298 return len(self
.children
[node
])
301 def GetChildAt(self
, node
, index
):
302 return self
.children
[node
][index
]
304 def GetParent(self
, node
):
305 return self
.parents
[node
]
307 def AddChild(self
, parent
, child
):
308 self
.parents
[child
]=parent
309 if not self
.children
.has_key(parent
):
310 self
.children
[parent
]=[]
311 self
.children
[parent
].append(child
)
312 wxTreeModel
.AddChild(self
, parent
, child
)
315 def RemoveNode(self
, node
):
316 parent
= self
.parents
[node
]
317 del self
.parents
[node
]
318 self
.children
[parent
].remove(node
)
319 wxTreeModel
.RemoveNode(self
, node
)
321 def InsertChild(self
, parent
, child
, index
):
322 self
.parents
[child
]=parent
323 if not self
.children
.has_key(parent
):
324 self
.children
[parent
]=[]
325 self
.children
[parent
].insert(child
, index
)
326 wxTreeModel
.InsertChild(self
, parent
, child
, index
)
329 def IsLeaf(self
, node
):
330 return not self
.children
.has_key(node
)
332 def IsEditable(self
, node
):
335 def SetEditable(self
, node
, bool):
339 class FileEditor(Editor
):
340 def Edit(self
, node
):
341 treenode
= self
.tree
.nodemap
[node
]
342 self
.editcomp
= wxTextCtrl(self
.tree
, -1)
343 for rect
in self
.tree
.painter
.rectangles
:
344 if rect
[0] == treenode
:
345 self
.editcomp
.SetPosition((rect
[1][0], rect
[1][1]))
347 self
.editcomp
.SetValue(node
.fileName
)
348 self
.editcomp
.SetSelection(0, len(node
.fileName
))
349 self
.editcomp
.SetFocus()
350 self
.treenode
= treenode
351 # EVT_KEY_DOWN(self.editcomp, self._key)
352 EVT_KEY_UP(self
.editcomp
, self
._key
)
353 EVT_LEFT_DOWN(self
.editcomp
, self
._mdown
)
354 self
.editcomp
.CaptureMouse()
356 def CanEdit(self
, node
):
357 return isinstance(node
, FileWrapper
)
359 def EndEdit(self
, commit
):
360 if not self
.tree
._EditEnding
(self
.treenode
.data
):
363 node
= self
.treenode
.data
365 os
.rename(node
.path
+ os
.sep
+ node
.fileName
, node
.path
+ os
.sep
+ self
.editcomp
.GetValue())
366 node
.fileName
= self
.editcomp
.GetValue()
368 traceback
.print_exc()
369 self
.editcomp
.ReleaseMouse()
370 self
.editcomp
.Destroy()
376 if evt
.KeyCode() == WXK_RETURN
:
378 elif evt
.KeyCode() == WXK_ESCAPE
:
383 def _mdown(self
, evt
):
385 pos
= evt
.GetPosition()
386 edsize
= self
.editcomp
.GetSize()
387 if pos
.x
< 0 or pos
.y
< 0 or pos
.x
> edsize
.width
or pos
.y
> edsize
.height
:
393 Node class for FSTreeModel.
395 def __init__(self
, path
, fileName
):
397 self
.fileName
= fileName
402 class FSTreeModel(BasicTreeModel
):
404 This treemodel models the filesystem starting from a given path.
406 def __init__(self
, path
):
407 BasicTreeModel
.__init
__(self
)
409 fw
= FileWrapper(path
, string
.split(path
, os
.sep
)[-1])
410 self
._Build
(path
, fw
)
412 self
._editable
= true
413 def _Build(self
, path
, fileWrapper
):
414 for name
in os
.listdir(path
):
415 fw
= FileWrapper(path
, name
)
416 self
.AddChild(fileWrapper
, fw
)
417 childName
= path
+ os
.sep
+ name
418 if os
.path
.isdir(childName
):
419 self
._Build
(childName
, fw
)
421 def IsEditable(self
, node
):
422 return self
._editable
424 def SetEditable(self
, node
, bool):
425 self
._editable
= bool
427 class LateFSTreeModel(FSTreeModel
):
429 This treemodel models the filesystem starting from a given path.
430 It retrieves the directory list as requested.
432 def __init__(self
, path
):
433 BasicTreeModel
.__init
__(self
)
435 name
= string
.split(path
, os
.sep
)[-1]
436 pathpart
= path
[:-len(name
)]
437 fw
= FileWrapper(pathpart
, name
)
438 self
._Build
(path
, fw
)
440 self
._editable
= true
443 def _Build(self
, path
, parent
):
444 ppath
= parent
.path
+ os
.sep
+ parent
.fileName
445 if not os
.path
.isdir(ppath
):
447 for name
in os
.listdir(ppath
):
448 fw
= FileWrapper(ppath
, name
)
449 self
.AddChild(parent
, fw
)
450 def GetChildCount(self
, node
):
451 if self
.children
.has_key(node
):
452 return FSTreeModel
.GetChildCount(self
, node
)
454 self
._Build
(node
.path
, node
)
455 return FSTreeModel
.GetChildCount(self
, node
)
457 def IsLeaf(self
, node
):
458 return not os
.path
.isdir(node
.path
+ os
.sep
+ node
.fileName
)
460 class StrTextConverter(TextConverter
):
461 def Convert(self
, node
):
462 return str(node
.data
)
464 class NullTransform(Transform
):
466 return tuple(self
.size
)
468 def Transform(self
, node
, offset
, rotation
):
470 list = self
.tree
.GetLayoutEngine().GetNodeList()
472 node
.projx
= node
.x
+ offset
[0]
473 node
.projy
= node
.y
+ offset
[1]
474 if node
.projx
> self
.size
[0]:
475 self
.size
[0] = node
.projx
476 if node
.projy
> self
.size
[1]:
477 self
.size
[1] = node
.projy
480 def __init__(self
, x
, y
, width
, height
):
485 def __getitem__(self
, index
):
486 return (self
.x
, self
.y
, self
.width
, self
.height
)[index
]
488 def __setitem__(self
, index
, value
):
489 name
= ['x', 'y', 'width', 'height'][index
]
490 setattr(self
, name
, value
)
492 def Contains(self
, other
):
493 if type(other
) == type(()):
494 other
= Rect(other
[0], other
[1], 0, 0)
495 if other
.x
>= self
.x
:
496 if other
.y
>= self
.y
:
497 if other
.width
+ other
.x
<= self
.width
+ self
.x
:
498 if other
.height
+ other
.y
<= self
.height
+ self
.y
:
503 return "Rect: " + str([self
.x
, self
.y
, self
.width
, self
.height
])
505 class TreeLayout(LayoutEngine
):
506 def SetHeight(self
, num
):
507 self
.NODE_HEIGHT
= num
509 def __init__(self
, tree
):
510 LayoutEngine
.__init
__(self
, tree
)
512 self
.NODE_HEIGHT
= 20
515 def Layout(self
, node
):
517 self
.NODE_HEIGHT
= self
.tree
.GetFont().GetPointSize() * 2
518 self
.layoutwalk(node
)
520 def GetNodeList(self
):
523 def layoutwalk(self
, node
):
524 if node
== self
.tree
.currentRoot
:
526 self
.lastY
= (-self
.NODE_HEIGHT
)
527 node
.x
= self
.NODE_STEP
* node
.level
528 node
.y
= self
.lastY
+ self
.NODE_HEIGHT
530 self
.nodelist
.append(node
)
532 for kid
in node
.kids
:
533 kid
.level
= node
.level
+ 1
536 class TreePainter(Painter
):
538 The default painter class. Uses double-buffering, delegates the painting of nodes and
539 lines to helper classes deriving from NodePainter and LinePainter.
541 def __init__(self
, tree
, nodePainter
= None, linePainter
= None, textConverter
= None):
542 Painter
.__init
__(self
, tree
)
544 nodePainter
= TreeNodePainter(self
)
545 self
.nodePainter
= nodePainter
547 linePainter
= TreeLinePainter(self
)
548 self
.linePainter
= linePainter
549 if not textConverter
:
550 textConverter
= StrTextConverter(self
)
551 self
.textConverter
= textConverter
554 def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
555 if not self
.charWidths
:
558 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
)
559 self
.charHeight
= dc
.GetTextExtent("D")[1]
560 self
.textpen
= wxPen(self
.GetTextColour(), 1, wxSOLID
)
561 self
.fgpen
= wxPen(self
.GetForegroundColour(), 1, wxSOLID
)
562 self
.bgpen
= wxPen(self
.GetBackgroundColour(), 1, wxSOLID
)
563 self
.linepen
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
564 self
.dashpen
= wxPen(self
.GetLineColour(), 1, wxDOT
)
565 self
.textbrush
= wxBrush(self
.GetTextColour(), wxSOLID
)
566 self
.fgbrush
= wxBrush(self
.GetForegroundColour(), wxSOLID
)
567 self
.bgbrush
= wxBrush(self
.GetBackgroundColour(), wxSOLID
)
568 self
.linebrush
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
569 treesize
= self
.tree
.GetSize()
570 size
= self
.tree
.transform
.GetSize()
571 size
= (max(treesize
.width
, size
[0]+50), max(treesize
.height
, size
[1]+50))
574 mem_dc
= wxMemoryDC()
575 if not self
.GetBuffer():
578 self
.bmp
= wxEmptyBitmap(size
[0], size
[1])
579 mem_dc
.SelectObject(self
.GetBuffer())
580 mem_dc
.SetPen(self
.GetBackgroundPen())
581 mem_dc
.SetBrush(self
.GetBackgroundBrush())
582 mem_dc
.DrawRectangle(0, 0, size
[0], size
[1])
583 mem_dc
.SetFont(self
.tree
.GetFont())
584 self
.paintWalk(node
, mem_dc
)
586 mem_dc
.SelectObject(self
.GetBuffer())
587 xstart
, ystart
= self
.tree
.CalcUnscrolledPosition(0,0)
588 size
= self
.tree
.GetClientSizeTuple()
589 dc
.Blit(xstart
, ystart
, size
[0], size
[1], mem_dc
, xstart
, ystart
)
591 if node
== self
.tree
.currentRoot
:
594 dc
.SetPen(self
.GetBackgroundPen())
595 dc
.SetBrush(self
.GetBackgroundBrush())
596 dc
.SetFont(self
.tree
.GetFont())
598 dc
.DrawRectangle(0, 0, size
[0], size
[1])
600 #Call with not paintBackground because if we are told not to paint the
601 #whole background, we have to paint in parts to undo selection coloring.
603 self
.paintWalk(node
, dc
, not pb
)
606 def GetDashPen(self
):
609 def SetLinePen(self
, pen
):
610 Painter
.SetLinePen(self
, pen
)
611 self
.dashpen
= wxPen(pen
.GetColour(), 1, wxDOT
)
613 def paintWalk(self
, node
, dc
, paintRects
=0):
614 self
.linePainter
.Paint(node
.parent
, node
, dc
)
615 self
.nodePainter
.Paint(node
, dc
, drawRects
= paintRects
)
617 for kid
in node
.kids
:
618 if not self
.paintWalk(kid
, dc
, paintRects
):
620 for kid
in node
.kids
:
621 px
= (kid
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
622 py
= kid
.projy
+ kid
.height
/2
623 if (not self
.tree
.model
.IsLeaf(kid
.data
)) or ((kid
.expanded
or self
.tree
._assumeChildren
) and len(kid
.kids
)):
624 dc
.SetPen(self
.linepen
)
625 dc
.SetBrush(self
.bgbrush
)
626 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
627 self
.knobs
.append( (kid
, Rect(px
-4, py
-4, 9, 9)) )
628 dc
.SetPen(self
.textpen
)
630 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
631 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
632 if node
== self
.tree
.currentRoot
:
633 px
= (node
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
634 py
= node
.projy
+ node
.height
/2
635 dc
.SetPen(self
.linepen
)
636 dc
.SetBrush(self
.bgbrush
)
637 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
638 self
.knobs
.append( (node
, Rect(px
-4, py
-4, 9, 9)) )
639 dc
.SetPen(self
.textpen
)
640 if not node
.expanded
:
641 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
642 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
645 def OnMouse(self
, evt
):
646 Painter
.OnMouse(self
, evt
)
648 class TreeNodePainter(NodePainter
):
649 def Paint(self
, node
, dc
, location
= None, drawRects
= 0):
650 text
= self
.painter
.textConverter
.Convert(node
)
651 extent
= dc
.GetTextExtent(text
)
652 node
.width
= extent
[0]
653 node
.height
= extent
[1]
655 dc
.SetPen(self
.painter
.GetLinePen())
656 dc
.SetBrush(self
.painter
.GetForegroundBrush())
657 dc
.SetTextForeground(wxNamedColour("WHITE"))
658 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
661 dc
.SetBrush(self
.painter
.GetBackgroundBrush())
662 dc
.SetPen(self
.painter
.GetBackgroundPen())
663 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
664 dc
.SetTextForeground(self
.painter
.GetTextColour())
665 dc
.DrawText(text
, node
.projx
, node
.projy
)
666 self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
)))
668 class TreeLinePainter(LinePainter
):
669 def Paint(self
, parent
, child
, dc
):
670 dc
.SetPen(self
.painter
.GetDashPen())
671 px
= py
= cx
= cy
= 0
672 if parent
is None or child
== self
.painter
.tree
.currentRoot
:
673 px
= (child
.projx
- self
.painter
.tree
.layout
.NODE_STEP
) + 5
674 py
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -2
677 dc
.DrawLine(px
, py
, cx
, cy
)
679 px
= parent
.projx
+ 5
680 py
= parent
.projy
+ parent
.height
682 cy
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -3
683 dc
.DrawLine(px
, py
, px
, cy
)
684 dc
.DrawLine(px
, cy
, cx
, cy
)
687 wxEVT_MVCTREE_BEGIN_EDIT
= 20204 #Start editing. Vetoable.
688 wxEVT_MVCTREE_END_EDIT
= 20205 #Stop editing. Vetoable.
689 wxEVT_MVCTREE_DELETE_ITEM
= 20206 #Item removed from model.
690 wxEVT_MVCTREE_ITEM_EXPANDED
= 20209
691 wxEVT_MVCTREE_ITEM_EXPANDING
= 20210
692 wxEVT_MVCTREE_ITEM_COLLAPSED
= 20211
693 wxEVT_MVCTREE_ITEM_COLLAPSING
= 20212
694 wxEVT_MVCTREE_SEL_CHANGED
= 20213
695 wxEVT_MVCTREE_SEL_CHANGING
= 20214 #Vetoable.
696 wxEVT_MVCTREE_KEY_DOWN
= 20215
697 wxEVT_MVCTREE_ADD_ITEM
= 20216 #Item added to model.
699 def EVT_MVCTREE_SEL_CHANGED(win
, id, func
):
700 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED
, func
)
702 def EVT_MVCTREE_SEL_CHANGING(win
, id, func
):
703 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING
, func
)
705 def EVT_MVCTREE_ITEM_EXPANDED(win
, id, func
):
706 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED
, func
)
708 def EVT_MVCTREE_ITEM_EXPANDING(win
, id, func
):
709 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING
, func
)
711 def EVT_MVCTREE_ITEM_COLLAPSED(win
, id, func
):
712 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED
, func
)
714 def EVT_MVCTREE_ITEM_COLLAPSING(win
, id, func
):
715 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING
, func
)
717 def EVT_MVCTREE_ADD_ITEM(win
, id, func
):
718 win
.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM
, func
)
720 def EVT_MVCTREE_DELETE_ITEM(win
, id, func
):
721 win
.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM
, func
)
723 def EVT_MVCTREE_KEY_DOWN(win
, id, func
):
724 win
.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN
, func
)
727 class wxMVCTreeEvent(wxPyCommandEvent
):
728 def __init__(self
, type, id, node
= None, nodes
= None, keyEvent
= None, **kwargs
):
729 apply(wxPyCommandEvent
.__init
__, (self
, type, id), kwargs
)
732 self
.keyEvent
= keyEvent
737 def getKeyEvent(self
):
740 class wxMVCTreeNotifyEvent(wxMVCTreeEvent
):
741 def __init__(self
, type, id, node
= None, nodes
= None, **kwargs
):
742 apply(wxMVCTreeEvent
.__init
__, (self
, type, id, node
, nodes
), kwargs
)
743 self
.notify
= wxNotifyEvent(type, id)
744 def getNotifyEvent(self
):
747 class wxMVCTree(wxScrolledWindow
):
749 The main mvc tree class.
751 def __init__(self
, parent
, id, model
= None, layout
= None, transform
= None,
752 painter
= None, *args
, **kwargs
):
753 apply(wxScrolledWindow
.__init
__, (self
, parent
, id), kwargs
)
755 self
._multiselect
= false
756 self
._selections
= []
757 self
._assumeChildren
= false
758 self
._scrollx
= false
759 self
._scrolly
= false
760 self
.doubleBuffered
= false
761 self
._lastPhysicalSize
= self
.GetSize()
764 model
= BasicTreeModel()
765 model
.SetRoot("Root")
768 layout
= TreeLayout(self
)
771 transform
= NullTransform(self
)
772 self
.transform
= transform
774 painter
= TreePainter(self
)
775 self
.painter
= painter
776 self
.SetFont(wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
))
777 EVT_MOUSE_EVENTS(self
, self
.OnMouse
)
778 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
779 self
.doubleBuffered
= true
780 EVT_SIZE(self
, self
.OnSize
)
781 EVT_ERASE_BACKGROUND(self
, self
.OnEraseBackground
)
782 EVT_PAINT(self
, self
.OnPaint
)
786 if self
.doubleBuffered
:
787 self
.painter
.ClearBuffer()
788 wxScrolledWindow
.Refresh(self
, false
)
790 def GetPainter(self
):
793 def GetLayoutEngine(self
):
796 def GetTransform(self
):
797 return self
.transform
800 return "<wxMVCTree instance at %s>" % str(hex(id(self
)))
803 return self
.__repr
__()
805 def NodeAdded(self
, parent
, child
):
806 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
807 self
.GetEventHandler().ProcessEvent(e
)
808 self
.painter
.ClearBuffer()
810 def NodeInserted(self
, parent
, child
, index
):
811 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
812 self
.GetEventHandler().ProcessEvent(e
)
813 self
.painter
.ClearBuffer()
815 def NodeRemoved(self
, node
):
816 e
= wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
817 self
.GetEventHandler().ProcessEvent(e
)
818 self
.painter
.ClearBuffer()
820 def OnKeyDown(self
, evt
):
821 e
= wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent
= evt
)
822 self
.GetEventHandler().ProcessEvent(e
)
824 def SetFont(self
, font
):
825 self
.painter
.SetFont(font
)
826 dc
= wxClientDC(self
)
828 self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18)
829 self
.painter
.ClearBuffer()
832 return self
.painter
.GetFont()
834 def AddEditor(self
, editor
):
835 self
._editors
.append(editor
)
837 def RemoveEditor(self
, editor
):
838 self
._editors
.remove(editor
)
840 def OnMouse(self
, evt
):
841 self
.painter
.OnMouse(evt
)
843 def OnNodeClick(self
, node
, mouseEvent
):
844 if node
.selected
and (self
.IsMultiSelect() and mouseEvent
.ControlDown()):
845 self
.RemoveFromSelection(node
.data
)
847 self
.AddToSelection(node
.data
, mouseEvent
.ControlDown(), mouseEvent
.ShiftDown())
849 def OnKnobClick(self
, node
):
850 self
.SetExpanded(node
.data
, not node
.expanded
)
852 def GetDisplayText(self
, node
):
853 treenode
= self
.nodemap
[node
]
854 return self
.painter
.textConverter
.Convert(treenode
)
856 def IsDoubleBuffered(self
):
857 return self
.doubleBuffered
859 def SetDoubleBuffered(self
, bool):
861 By default wxMVCTree is double-buffered.
863 self
.doubleBuffered
= bool
868 def SetModel(self
, model
):
870 Completely change the data to be displayed.
876 self
._selections
= []
877 self
.layoutRoot
= MVCTreeNode()
878 self
.layoutRoot
.data
= self
.model
.GetRoot()
879 self
.layoutRoot
.expanded
= true
880 self
.LoadChildren(self
.layoutRoot
)
881 self
.currentRoot
= self
.layoutRoot
884 self
._scrollset
= None
887 def GetCurrentRoot(self
):
888 return self
.currentRoot
890 def LoadChildren(self
, layoutNode
):
894 self
.nodemap
[layoutNode
.data
]=layoutNode
895 for i
in range(self
.GetModel().GetChildCount(layoutNode
.data
)):
896 p
= MVCTreeNode("RAW", layoutNode
, [])
898 p
.data
= self
.GetModel().GetChildAt(layoutNode
.data
, i
)
899 self
.nodemap
[p
.data
]=p
900 layoutNode
.built
= true
901 if not self
._assumeChildren
:
902 for kid
in layoutNode
.kids
:
903 self
.LoadChildren(kid
)
905 def OnEraseBackground(self
, evt
):
908 def OnSize(self
, evt
):
909 size
= self
.GetSize()
910 self
.center
= (size
.width
/2, size
.height
/2)
911 if self
._lastPhysicalSize
.width
< size
.width
or self
._lastPhysicalSize
.height
< size
.height
:
912 self
.painter
.ClearBuffer()
913 self
._lastPhysicalSize
= size
915 def GetSelection(self
):
916 "Returns a tuple of selected nodes."
917 return tuple(self
._selections
)
919 def SetSelection(self
, nodeTuple
):
920 if type(nodeTuple
) != type(()):
921 nodeTuple
= (nodeTuple
,)
922 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
923 self
.GetEventHandler().ProcessEvent(e
)
924 if not e
.notify
.IsAllowed():
926 for node
in nodeTuple
:
927 treenode
= self
.nodemap
[node
]
928 treenode
.selected
= true
929 for node
in self
._selections
:
930 treenode
= self
.nodemap
[node
]
931 node
.selected
= false
932 self
._selections
= list(nodeTuple
)
933 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
934 self
.GetEventHandler().ProcessEvent(e
)
936 def IsMultiSelect(self
):
937 return self
._multiselect
939 def SetMultiSelect(self
, bool):
940 self
._multiselect
= bool
942 def IsSelected(self
, node
):
943 return self
.nodemap
[node
].selected
945 def Edit(self
, node
):
946 if not self
.model
.IsEditable(node
):
948 for ed
in self
._editors
:
950 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
)
951 self
.GetEventHandler().ProcessEvent(e
)
952 if not e
.notify
.IsAllowed():
955 self
._currentEditor
= ed
959 if self
._currentEditor
:
960 self
._currentEditor
.EndEdit
961 self
._currentEditor
= None
963 def _EditEnding(self
, node
):
964 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
)
965 self
.GetEventHandler().ProcessEvent(e
)
966 if not e
.notify
.IsAllowed():
968 self
._currentEditor
= None
972 def SetExpanded(self
, node
, bool):
973 treenode
= self
.nodemap
[node
]
975 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
)
976 self
.GetEventHandler().ProcessEvent(e
)
977 if not e
.notify
.IsAllowed():
979 if not treenode
.built
:
980 self
.LoadChildren(treenode
)
982 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
)
983 self
.GetEventHandler().ProcessEvent(e
)
984 if not e
.notify
.IsAllowed():
986 treenode
.expanded
= bool
988 if treenode
.expanded
:
989 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
)
991 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
)
992 self
.GetEventHandler().ProcessEvent(e
)
993 self
.layout
.Layout(self
.currentRoot
)
994 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
997 def IsExpanded(self
, node
):
998 return self
.nodemap
[node
].expanded
1000 def AddToSelection(self
, nodeOrTuple
, enableMulti
= true
, shiftMulti
= false
):
1001 nodeTuple
= nodeOrTuple
1002 if type(nodeOrTuple
)!= type(()):
1003 nodeTuple
= (nodeOrTuple
,)
1004 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1005 self
.GetEventHandler().ProcessEvent(e
)
1006 if not e
.notify
.IsAllowed():
1009 if not (self
.IsMultiSelect() and (enableMulti
or shiftMulti
)):
1010 for node
in self
._selections
:
1011 treenode
= self
.nodemap
[node
]
1012 treenode
.selected
= false
1013 changeparents
.append(treenode
)
1015 self
._selections
= [node
]
1016 treenode
= self
.nodemap
[node
]
1017 changeparents
.append(treenode
)
1018 treenode
.selected
= true
1021 for node
in nodeTuple
:
1022 treenode
= self
.nodemap
[node
]
1023 oldtreenode
= self
.nodemap
[self
._selections
[0]]
1024 if treenode
.parent
== oldtreenode
.parent
:
1026 for kid
in oldtreenode
.parent
.kids
:
1027 if kid
== treenode
or kid
== oldtreenode
:
1030 self
._selections
.append(kid
.data
)
1031 changeparents
.append(kid
)
1034 self
._selections
.append(kid
.data
)
1035 changeparents
.append(kid
)
1037 for node
in nodeTuple
:
1039 self
._selections
.index(node
)
1041 self
._selections
.append(node
)
1042 treenode
= self
.nodemap
[node
]
1043 treenode
.selected
= true
1044 changeparents
.append(treenode
)
1045 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1046 self
.GetEventHandler().ProcessEvent(e
)
1047 dc
= wxClientDC(self
)
1049 for node
in changeparents
:
1051 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1052 self
.painter
.ClearBuffer()
1054 def RemoveFromSelection(self
, nodeTuple
):
1055 if type(nodeTuple
) != type(()):
1056 nodeTuple
= (nodeTuple
,)
1058 for node
in nodeTuple
:
1059 self
._selections
.remove(node
)
1060 treenode
= self
.nodemap
[node
]
1061 changeparents
.append(treenode
)
1062 treenode
.selected
= false
1063 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes
= nodeTuple
)
1064 self
.GetEventHandler().ProcessEvent(e
)
1065 dc
= wxClientDC(self
)
1067 for node
in changeparents
:
1069 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1070 self
.painter
.ClearBuffer()
1073 def GetBackgroundColour(self
):
1074 if hasattr(self
, 'painter') and self
.painter
:
1075 return self
.painter
.GetBackgroundColour()
1077 return wxWindow
.GetBackgroundColour(self
)
1078 def SetBackgroundColour(self
, color
):
1079 if hasattr(self
, 'painter') and self
.painter
:
1080 self
.painter
.SetBackgroundColour(color
)
1082 wxWindow
.SetBackgroundColour(self
, color
)
1083 def GetForegroundColour(self
):
1084 if hasattr(self
, 'painter') and self
.painter
:
1085 return self
.painter
.GetForegroundColour()
1087 return wxWindow
.GetBackgroundColour(self
)
1088 def SetForegroundColour(self
, color
):
1089 if hasattr(self
, 'painter') and self
.painter
:
1090 self
.painter
.SetForegroundColour(color
)
1092 wxWindow
.SetBackgroundColour(self
, color
)
1094 def SetAssumeChildren(self
, bool):
1095 self
._assumeChildren
= bool
1097 def GetAssumeChildren(self
):
1098 return self
._assumeChildren
1100 def OnPaint(self
, evt
):
1102 Ensures that the tree has been laid out and transformed, then calls the painter
1103 to paint the control.
1106 self
.EnableScrolling(false
, false
)
1107 if not self
.laidOut
:
1108 self
.layout
.Layout(self
.currentRoot
)
1110 self
.transformed
= false
1111 if not self
.transformed
:
1112 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1113 self
.transformed
= true
1115 tsize
= list(self
.transform
.GetSize())
1116 tsize
[0] = tsize
[0] + 50
1117 tsize
[1] = tsize
[1] + 50
1118 size
= self
.GetSizeTuple()
1119 if tsize
[0] > size
[0] or tsize
[1] > size
[1]:
1120 if not hasattr(self
, '_oldsize') or (tsize
[0] > self
._oldsize
[0] or tsize
[1] > self
._oldsize
[1]):
1121 self
._oldsize
= tsize
1122 oldstart
= self
.ViewStart()
1123 self
._lastPhysicalSize
= self
.GetSize()
1124 self
.SetScrollbars(10, 10, tsize
[0]/10, tsize
[1]/10)
1125 self
.Scroll(oldstart
[0], oldstart
[1])
1126 dc
= wxPaintDC(self
)
1128 dc
.SetFont(self
.GetFont())
1129 self
.painter
.Paint(dc
, self
.currentRoot
, self
.doubleBuffered
)
1131 traceback
.print_exc()