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
)
408 fw
= FileWrapper(path
, path
.split(os
.sep
)[-1])
409 self
._Build
(path
, fw
)
411 self
._editable
= True
412 def _Build(self
, path
, fileWrapper
):
413 for name
in os
.listdir(path
):
414 fw
= FileWrapper(path
, name
)
415 self
.AddChild(fileWrapper
, fw
)
416 childName
= path
+ os
.sep
+ name
417 if os
.path
.isdir(childName
):
418 self
._Build
(childName
, fw
)
420 def IsEditable(self
, node
):
421 return self
._editable
423 def SetEditable(self
, node
, bool):
424 self
._editable
= bool
426 class LateFSTreeModel(FSTreeModel
):
428 This treemodel models the filesystem starting from a given path.
429 It retrieves the directory list as requested.
431 def __init__(self
, path
):
432 BasicTreeModel
.__init
__(self
)
433 name
= path
.split(os
.sep
)[-1]
434 pathpart
= path
[:-len(name
)]
435 fw
= FileWrapper(pathpart
, name
)
436 self
._Build
(path
, fw
)
438 self
._editable
= True
441 def _Build(self
, path
, parent
):
442 ppath
= parent
.path
+ os
.sep
+ parent
.fileName
443 if not os
.path
.isdir(ppath
):
445 for name
in os
.listdir(ppath
):
446 fw
= FileWrapper(ppath
, name
)
447 self
.AddChild(parent
, fw
)
448 def GetChildCount(self
, node
):
449 if self
.children
.has_key(node
):
450 return FSTreeModel
.GetChildCount(self
, node
)
452 self
._Build
(node
.path
, node
)
453 return FSTreeModel
.GetChildCount(self
, node
)
455 def IsLeaf(self
, node
):
456 return not os
.path
.isdir(node
.path
+ os
.sep
+ node
.fileName
)
458 class StrTextConverter(TextConverter
):
459 def Convert(self
, node
):
460 return str(node
.data
)
462 class NullTransform(Transform
):
464 return tuple(self
.size
)
466 def Transform(self
, node
, offset
, rotation
):
468 list = self
.tree
.GetLayoutEngine().GetNodeList()
470 node
.projx
= node
.x
+ offset
[0]
471 node
.projy
= node
.y
+ offset
[1]
472 if node
.projx
> self
.size
[0]:
473 self
.size
[0] = node
.projx
474 if node
.projy
> self
.size
[1]:
475 self
.size
[1] = node
.projy
478 def __init__(self
, x
, y
, width
, height
):
483 def __getitem__(self
, index
):
484 return (self
.x
, self
.y
, self
.width
, self
.height
)[index
]
486 def __setitem__(self
, index
, value
):
487 name
= ['x', 'y', 'width', 'height'][index
]
488 setattr(self
, name
, value
)
490 def Contains(self
, other
):
491 if type(other
) == type(()):
492 other
= Rect(other
[0], other
[1], 0, 0)
493 if other
.x
>= self
.x
:
494 if other
.y
>= self
.y
:
495 if other
.width
+ other
.x
<= self
.width
+ self
.x
:
496 if other
.height
+ other
.y
<= self
.height
+ self
.y
:
501 return "Rect: " + str([self
.x
, self
.y
, self
.width
, self
.height
])
503 class TreeLayout(LayoutEngine
):
504 def SetHeight(self
, num
):
505 self
.NODE_HEIGHT
= num
507 def __init__(self
, tree
):
508 LayoutEngine
.__init
__(self
, tree
)
510 self
.NODE_HEIGHT
= 20
513 def Layout(self
, node
):
515 self
.NODE_HEIGHT
= self
.tree
.GetFont().GetPointSize() * 2
516 self
.layoutwalk(node
)
518 def GetNodeList(self
):
521 def layoutwalk(self
, node
):
522 if node
== self
.tree
.currentRoot
:
524 self
.lastY
= (-self
.NODE_HEIGHT
)
525 node
.x
= self
.NODE_STEP
* node
.level
526 node
.y
= self
.lastY
+ self
.NODE_HEIGHT
528 self
.nodelist
.append(node
)
530 for kid
in node
.kids
:
531 kid
.level
= node
.level
+ 1
534 class TreePainter(Painter
):
536 The default painter class. Uses double-buffering, delegates the painting of nodes and
537 lines to helper classes deriving from NodePainter and LinePainter.
539 def __init__(self
, tree
, nodePainter
= None, linePainter
= None, textConverter
= None):
540 Painter
.__init
__(self
, tree
)
542 nodePainter
= TreeNodePainter(self
)
543 self
.nodePainter
= nodePainter
545 linePainter
= TreeLinePainter(self
)
546 self
.linePainter
= linePainter
547 if not textConverter
:
548 textConverter
= StrTextConverter(self
)
549 self
.textConverter
= textConverter
552 def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
553 if not self
.charWidths
:
556 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
)
557 self
.charHeight
= dc
.GetTextExtent("D")[1]
558 self
.textpen
= wxPen(self
.GetTextColour(), 1, wxSOLID
)
559 self
.fgpen
= wxPen(self
.GetForegroundColour(), 1, wxSOLID
)
560 self
.bgpen
= wxPen(self
.GetBackgroundColour(), 1, wxSOLID
)
561 self
.linepen
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
562 self
.dashpen
= wxPen(self
.GetLineColour(), 1, wxDOT
)
563 self
.textbrush
= wxBrush(self
.GetTextColour(), wxSOLID
)
564 self
.fgbrush
= wxBrush(self
.GetForegroundColour(), wxSOLID
)
565 self
.bgbrush
= wxBrush(self
.GetBackgroundColour(), wxSOLID
)
566 self
.linebrush
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
567 treesize
= self
.tree
.GetSize()
568 size
= self
.tree
.transform
.GetSize()
569 size
= (max(treesize
.width
, size
[0]+50), max(treesize
.height
, size
[1]+50))
572 mem_dc
= wxMemoryDC()
573 if not self
.GetBuffer():
576 self
.bmp
= wxEmptyBitmap(size
[0], size
[1])
577 mem_dc
.SelectObject(self
.GetBuffer())
578 mem_dc
.SetPen(self
.GetBackgroundPen())
579 mem_dc
.SetBrush(self
.GetBackgroundBrush())
580 mem_dc
.DrawRectangle(0, 0, size
[0], size
[1])
581 mem_dc
.SetFont(self
.tree
.GetFont())
582 self
.paintWalk(node
, mem_dc
)
584 mem_dc
.SelectObject(self
.GetBuffer())
585 xstart
, ystart
= self
.tree
.CalcUnscrolledPosition(0,0)
586 size
= self
.tree
.GetClientSizeTuple()
587 dc
.Blit(xstart
, ystart
, size
[0], size
[1], mem_dc
, xstart
, ystart
)
589 if node
== self
.tree
.currentRoot
:
592 dc
.SetPen(self
.GetBackgroundPen())
593 dc
.SetBrush(self
.GetBackgroundBrush())
594 dc
.SetFont(self
.tree
.GetFont())
596 dc
.DrawRectangle(0, 0, size
[0], size
[1])
598 #Call with not paintBackground because if we are told not to paint the
599 #whole background, we have to paint in parts to undo selection coloring.
601 self
.paintWalk(node
, dc
, not pb
)
604 def GetDashPen(self
):
607 def SetLinePen(self
, pen
):
608 Painter
.SetLinePen(self
, pen
)
609 self
.dashpen
= wxPen(pen
.GetColour(), 1, wxDOT
)
611 def paintWalk(self
, node
, dc
, paintRects
=0):
612 self
.linePainter
.Paint(node
.parent
, node
, dc
)
613 self
.nodePainter
.Paint(node
, dc
, drawRects
= paintRects
)
615 for kid
in node
.kids
:
616 if not self
.paintWalk(kid
, dc
, paintRects
):
618 for kid
in node
.kids
:
619 px
= (kid
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
620 py
= kid
.projy
+ kid
.height
/2
621 if (not self
.tree
.model
.IsLeaf(kid
.data
)) or ((kid
.expanded
or self
.tree
._assumeChildren
) and len(kid
.kids
)):
622 dc
.SetPen(self
.linepen
)
623 dc
.SetBrush(self
.bgbrush
)
624 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
625 self
.knobs
.append( (kid
, Rect(px
-4, py
-4, 9, 9)) )
626 dc
.SetPen(self
.textpen
)
628 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
629 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
630 if node
== self
.tree
.currentRoot
:
631 px
= (node
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
632 py
= node
.projy
+ node
.height
/2
633 dc
.SetPen(self
.linepen
)
634 dc
.SetBrush(self
.bgbrush
)
635 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
636 self
.knobs
.append( (node
, Rect(px
-4, py
-4, 9, 9)) )
637 dc
.SetPen(self
.textpen
)
638 if not node
.expanded
:
639 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
640 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
643 def OnMouse(self
, evt
):
644 Painter
.OnMouse(self
, evt
)
646 class TreeNodePainter(NodePainter
):
647 def Paint(self
, node
, dc
, location
= None, drawRects
= 0):
648 text
= self
.painter
.textConverter
.Convert(node
)
649 extent
= dc
.GetTextExtent(text
)
650 node
.width
= extent
[0]
651 node
.height
= extent
[1]
653 dc
.SetPen(self
.painter
.GetLinePen())
654 dc
.SetBrush(self
.painter
.GetForegroundBrush())
655 dc
.SetTextForeground(wxNamedColour("WHITE"))
656 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
659 dc
.SetBrush(self
.painter
.GetBackgroundBrush())
660 dc
.SetPen(self
.painter
.GetBackgroundPen())
661 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
662 dc
.SetTextForeground(self
.painter
.GetTextColour())
663 dc
.DrawText(text
, node
.projx
, node
.projy
)
664 self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
)))
666 class TreeLinePainter(LinePainter
):
667 def Paint(self
, parent
, child
, dc
):
668 dc
.SetPen(self
.painter
.GetDashPen())
669 px
= py
= cx
= cy
= 0
670 if parent
is None or child
== self
.painter
.tree
.currentRoot
:
671 px
= (child
.projx
- self
.painter
.tree
.layout
.NODE_STEP
) + 5
672 py
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -2
675 dc
.DrawLine(px
, py
, cx
, cy
)
677 px
= parent
.projx
+ 5
678 py
= parent
.projy
+ parent
.height
680 cy
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -3
681 dc
.DrawLine(px
, py
, px
, cy
)
682 dc
.DrawLine(px
, cy
, cx
, cy
)
685 wxEVT_MVCTREE_BEGIN_EDIT
= 20204 #Start editing. Vetoable.
686 wxEVT_MVCTREE_END_EDIT
= 20205 #Stop editing. Vetoable.
687 wxEVT_MVCTREE_DELETE_ITEM
= 20206 #Item removed from model.
688 wxEVT_MVCTREE_ITEM_EXPANDED
= 20209
689 wxEVT_MVCTREE_ITEM_EXPANDING
= 20210
690 wxEVT_MVCTREE_ITEM_COLLAPSED
= 20211
691 wxEVT_MVCTREE_ITEM_COLLAPSING
= 20212
692 wxEVT_MVCTREE_SEL_CHANGED
= 20213
693 wxEVT_MVCTREE_SEL_CHANGING
= 20214 #Vetoable.
694 wxEVT_MVCTREE_KEY_DOWN
= 20215
695 wxEVT_MVCTREE_ADD_ITEM
= 20216 #Item added to model.
697 def EVT_MVCTREE_SEL_CHANGED(win
, id, func
):
698 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED
, func
)
700 def EVT_MVCTREE_SEL_CHANGING(win
, id, func
):
701 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING
, func
)
703 def EVT_MVCTREE_ITEM_EXPANDED(win
, id, func
):
704 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED
, func
)
706 def EVT_MVCTREE_ITEM_EXPANDING(win
, id, func
):
707 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING
, func
)
709 def EVT_MVCTREE_ITEM_COLLAPSED(win
, id, func
):
710 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED
, func
)
712 def EVT_MVCTREE_ITEM_COLLAPSING(win
, id, func
):
713 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING
, func
)
715 def EVT_MVCTREE_ADD_ITEM(win
, id, func
):
716 win
.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM
, func
)
718 def EVT_MVCTREE_DELETE_ITEM(win
, id, func
):
719 win
.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM
, func
)
721 def EVT_MVCTREE_KEY_DOWN(win
, id, func
):
722 win
.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN
, func
)
725 class wxMVCTreeEvent(wxPyCommandEvent
):
726 def __init__(self
, type, id, node
= None, nodes
= None, keyEvent
= None, **kwargs
):
727 apply(wxPyCommandEvent
.__init
__, (self
, type, id), kwargs
)
730 self
.keyEvent
= keyEvent
735 def getKeyEvent(self
):
738 class wxMVCTreeNotifyEvent(wxMVCTreeEvent
):
739 def __init__(self
, type, id, node
= None, nodes
= None, **kwargs
):
740 apply(wxMVCTreeEvent
.__init
__, (self
, type, id, node
, nodes
), kwargs
)
741 self
.notify
= wxNotifyEvent(type, id)
742 def getNotifyEvent(self
):
745 class wxMVCTree(wxScrolledWindow
):
747 The main mvc tree class.
749 def __init__(self
, parent
, id, model
= None, layout
= None, transform
= None,
750 painter
= None, *args
, **kwargs
):
751 apply(wxScrolledWindow
.__init
__, (self
, parent
, id), kwargs
)
753 self
._multiselect
= False
754 self
._selections
= []
755 self
._assumeChildren
= False
756 self
._scrollx
= False
757 self
._scrolly
= False
758 self
.doubleBuffered
= False
759 self
._lastPhysicalSize
= self
.GetSize()
762 model
= BasicTreeModel()
763 model
.SetRoot("Root")
766 layout
= TreeLayout(self
)
769 transform
= NullTransform(self
)
770 self
.transform
= transform
772 painter
= TreePainter(self
)
773 self
.painter
= painter
774 self
.SetFont(wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, False))
775 EVT_MOUSE_EVENTS(self
, self
.OnMouse
)
776 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
777 self
.doubleBuffered
= True
778 EVT_SIZE(self
, self
.OnSize
)
779 EVT_ERASE_BACKGROUND(self
, self
.OnEraseBackground
)
780 EVT_PAINT(self
, self
.OnPaint
)
784 if self
.doubleBuffered
:
785 self
.painter
.ClearBuffer()
786 wxScrolledWindow
.Refresh(self
, False)
788 def GetPainter(self
):
791 def GetLayoutEngine(self
):
794 def GetTransform(self
):
795 return self
.transform
798 return "<wxMVCTree instance at %s>" % str(hex(id(self
)))
801 return self
.__repr
__()
803 def NodeAdded(self
, parent
, child
):
804 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
805 self
.GetEventHandler().ProcessEvent(e
)
806 self
.painter
.ClearBuffer()
808 def NodeInserted(self
, parent
, child
, index
):
809 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
810 self
.GetEventHandler().ProcessEvent(e
)
811 self
.painter
.ClearBuffer()
813 def NodeRemoved(self
, node
):
814 e
= wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
815 self
.GetEventHandler().ProcessEvent(e
)
816 self
.painter
.ClearBuffer()
818 def OnKeyDown(self
, evt
):
819 e
= wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent
= evt
)
820 self
.GetEventHandler().ProcessEvent(e
)
822 def SetFont(self
, font
):
823 self
.painter
.SetFont(font
)
824 dc
= wxClientDC(self
)
826 self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18)
827 self
.painter
.ClearBuffer()
830 return self
.painter
.GetFont()
832 def AddEditor(self
, editor
):
833 self
._editors
.append(editor
)
835 def RemoveEditor(self
, editor
):
836 self
._editors
.remove(editor
)
838 def OnMouse(self
, evt
):
839 self
.painter
.OnMouse(evt
)
841 def OnNodeClick(self
, node
, mouseEvent
):
842 if node
.selected
and (self
.IsMultiSelect() and mouseEvent
.ControlDown()):
843 self
.RemoveFromSelection(node
.data
)
845 self
.AddToSelection(node
.data
, mouseEvent
.ControlDown(), mouseEvent
.ShiftDown())
847 def OnKnobClick(self
, node
):
848 self
.SetExpanded(node
.data
, not node
.expanded
)
850 def GetDisplayText(self
, node
):
851 treenode
= self
.nodemap
[node
]
852 return self
.painter
.textConverter
.Convert(treenode
)
854 def IsDoubleBuffered(self
):
855 return self
.doubleBuffered
857 def SetDoubleBuffered(self
, bool):
859 By default wxMVCTree is double-buffered.
861 self
.doubleBuffered
= bool
866 def SetModel(self
, model
):
868 Completely change the data to be displayed.
874 self
._selections
= []
875 self
.layoutRoot
= MVCTreeNode()
876 self
.layoutRoot
.data
= self
.model
.GetRoot()
877 self
.layoutRoot
.expanded
= True
878 self
.LoadChildren(self
.layoutRoot
)
879 self
.currentRoot
= self
.layoutRoot
882 self
._scrollset
= None
885 def GetCurrentRoot(self
):
886 return self
.currentRoot
888 def LoadChildren(self
, layoutNode
):
892 self
.nodemap
[layoutNode
.data
]=layoutNode
893 for i
in range(self
.GetModel().GetChildCount(layoutNode
.data
)):
894 p
= MVCTreeNode("RAW", layoutNode
, [])
896 p
.data
= self
.GetModel().GetChildAt(layoutNode
.data
, i
)
897 self
.nodemap
[p
.data
]=p
898 layoutNode
.built
= True
899 if not self
._assumeChildren
:
900 for kid
in layoutNode
.kids
:
901 self
.LoadChildren(kid
)
903 def OnEraseBackground(self
, evt
):
906 def OnSize(self
, evt
):
907 size
= self
.GetSize()
908 self
.center
= (size
.width
/2, size
.height
/2)
909 if self
._lastPhysicalSize
.width
< size
.width
or self
._lastPhysicalSize
.height
< size
.height
:
910 self
.painter
.ClearBuffer()
911 self
._lastPhysicalSize
= size
913 def GetSelection(self
):
914 "Returns a tuple of selected nodes."
915 return tuple(self
._selections
)
917 def SetSelection(self
, nodeTuple
):
918 if type(nodeTuple
) != type(()):
919 nodeTuple
= (nodeTuple
,)
920 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
921 self
.GetEventHandler().ProcessEvent(e
)
922 if not e
.notify
.IsAllowed():
924 for node
in nodeTuple
:
925 treenode
= self
.nodemap
[node
]
926 treenode
.selected
= True
927 for node
in self
._selections
:
928 treenode
= self
.nodemap
[node
]
929 node
.selected
= False
930 self
._selections
= list(nodeTuple
)
931 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
932 self
.GetEventHandler().ProcessEvent(e
)
934 def IsMultiSelect(self
):
935 return self
._multiselect
937 def SetMultiSelect(self
, bool):
938 self
._multiselect
= bool
940 def IsSelected(self
, node
):
941 return self
.nodemap
[node
].selected
943 def Edit(self
, node
):
944 if not self
.model
.IsEditable(node
):
946 for ed
in self
._editors
:
948 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
)
949 self
.GetEventHandler().ProcessEvent(e
)
950 if not e
.notify
.IsAllowed():
953 self
._currentEditor
= ed
957 if self
._currentEditor
:
958 self
._currentEditor
.EndEdit
959 self
._currentEditor
= None
961 def _EditEnding(self
, node
):
962 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
)
963 self
.GetEventHandler().ProcessEvent(e
)
964 if not e
.notify
.IsAllowed():
966 self
._currentEditor
= None
970 def SetExpanded(self
, node
, bool):
971 treenode
= self
.nodemap
[node
]
973 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
)
974 self
.GetEventHandler().ProcessEvent(e
)
975 if not e
.notify
.IsAllowed():
977 if not treenode
.built
:
978 self
.LoadChildren(treenode
)
980 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
)
981 self
.GetEventHandler().ProcessEvent(e
)
982 if not e
.notify
.IsAllowed():
984 treenode
.expanded
= bool
986 if treenode
.expanded
:
987 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
)
989 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
)
990 self
.GetEventHandler().ProcessEvent(e
)
991 self
.layout
.Layout(self
.currentRoot
)
992 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
995 def IsExpanded(self
, node
):
996 return self
.nodemap
[node
].expanded
998 def AddToSelection(self
, nodeOrTuple
, enableMulti
= True, shiftMulti
= False):
999 nodeTuple
= nodeOrTuple
1000 if type(nodeOrTuple
)!= type(()):
1001 nodeTuple
= (nodeOrTuple
,)
1002 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1003 self
.GetEventHandler().ProcessEvent(e
)
1004 if not e
.notify
.IsAllowed():
1007 if not (self
.IsMultiSelect() and (enableMulti
or shiftMulti
)):
1008 for node
in self
._selections
:
1009 treenode
= self
.nodemap
[node
]
1010 treenode
.selected
= False
1011 changeparents
.append(treenode
)
1013 self
._selections
= [node
]
1014 treenode
= self
.nodemap
[node
]
1015 changeparents
.append(treenode
)
1016 treenode
.selected
= True
1019 for node
in nodeTuple
:
1020 treenode
= self
.nodemap
[node
]
1021 oldtreenode
= self
.nodemap
[self
._selections
[0]]
1022 if treenode
.parent
== oldtreenode
.parent
:
1024 for kid
in oldtreenode
.parent
.kids
:
1025 if kid
== treenode
or kid
== oldtreenode
:
1028 self
._selections
.append(kid
.data
)
1029 changeparents
.append(kid
)
1032 self
._selections
.append(kid
.data
)
1033 changeparents
.append(kid
)
1035 for node
in nodeTuple
:
1037 self
._selections
.index(node
)
1039 self
._selections
.append(node
)
1040 treenode
= self
.nodemap
[node
]
1041 treenode
.selected
= True
1042 changeparents
.append(treenode
)
1043 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1044 self
.GetEventHandler().ProcessEvent(e
)
1045 dc
= wxClientDC(self
)
1047 for node
in changeparents
:
1049 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1050 self
.painter
.ClearBuffer()
1052 def RemoveFromSelection(self
, nodeTuple
):
1053 if type(nodeTuple
) != type(()):
1054 nodeTuple
= (nodeTuple
,)
1056 for node
in nodeTuple
:
1057 self
._selections
.remove(node
)
1058 treenode
= self
.nodemap
[node
]
1059 changeparents
.append(treenode
)
1060 treenode
.selected
= False
1061 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes
= nodeTuple
)
1062 self
.GetEventHandler().ProcessEvent(e
)
1063 dc
= wxClientDC(self
)
1065 for node
in changeparents
:
1067 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1068 self
.painter
.ClearBuffer()
1071 def GetBackgroundColour(self
):
1072 if hasattr(self
, 'painter') and self
.painter
:
1073 return self
.painter
.GetBackgroundColour()
1075 return wxWindow
.GetBackgroundColour(self
)
1076 def SetBackgroundColour(self
, color
):
1077 if hasattr(self
, 'painter') and self
.painter
:
1078 self
.painter
.SetBackgroundColour(color
)
1080 wxWindow
.SetBackgroundColour(self
, color
)
1081 def GetForegroundColour(self
):
1082 if hasattr(self
, 'painter') and self
.painter
:
1083 return self
.painter
.GetForegroundColour()
1085 return wxWindow
.GetBackgroundColour(self
)
1086 def SetForegroundColour(self
, color
):
1087 if hasattr(self
, 'painter') and self
.painter
:
1088 self
.painter
.SetForegroundColour(color
)
1090 wxWindow
.SetBackgroundColour(self
, color
)
1092 def SetAssumeChildren(self
, bool):
1093 self
._assumeChildren
= bool
1095 def GetAssumeChildren(self
):
1096 return self
._assumeChildren
1098 def OnPaint(self
, evt
):
1100 Ensures that the tree has been laid out and transformed, then calls the painter
1101 to paint the control.
1104 self
.EnableScrolling(False, False)
1105 if not self
.laidOut
:
1106 self
.layout
.Layout(self
.currentRoot
)
1108 self
.transformed
= False
1109 if not self
.transformed
:
1110 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1111 self
.transformed
= True
1113 tsize
= list(self
.transform
.GetSize())
1114 tsize
[0] = tsize
[0] + 50
1115 tsize
[1] = tsize
[1] + 50
1116 size
= self
.GetSizeTuple()
1117 if tsize
[0] > size
[0] or tsize
[1] > size
[1]:
1118 if not hasattr(self
, '_oldsize') or (tsize
[0] > self
._oldsize
[0] or tsize
[1] > self
._oldsize
[1]):
1119 self
._oldsize
= tsize
1120 oldstart
= self
.ViewStart()
1121 self
._lastPhysicalSize
= self
.GetSize()
1122 self
.SetScrollbars(10, 10, tsize
[0]/10, tsize
[1]/10)
1123 self
.Scroll(oldstart
[0], oldstart
[1])
1124 dc
= wxPaintDC(self
)
1126 dc
.SetFont(self
.GetFont())
1127 self
.painter
.Paint(dc
, self
.currentRoot
, self
.doubleBuffered
)
1129 traceback
.print_exc()