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 #------------------------------------------------------------------------
24 from wxPython
.wx
import *
25 import os
, sys
, traceback
26 #------------------------------------------------------------------------
30 Used internally by wxMVCTree to manage its data. Contains information about
31 screen placement, the actual data associated with it, and more. These are
32 the nodes passed to all the other helper parts to do their work with.
34 def __init__(self
, data
=None, parent
= None, kids
= [], x
= 0, y
= 0):
47 def GetChildren(self
):
53 def Remove(self
, node
):
55 self
.kids
.remove(node
)
59 self
.kids
.append(node
)
62 def SetParent(self
, parent
):
63 if self
.parent
and not (self
.parent
is parent
):
64 self
.parent
.Remove(self
)
67 return "Node: " + str(self
.data
) + " (" + str(self
.x
) + ", " + str(self
.y
) + ")"
70 def GetTreeString(self
, tabs
=0):
71 s
= tabs
* '\t' + str(self
) + '\n'
73 s
= s
+ kid
.GetTreeString(tabs
+ 1)
78 def __init__(self
, tree
):
81 raise NotImplementedError
82 def EndEdit(self
, node
, commit
):
83 raise NotImplementedError
84 def CanEdit(self
, node
):
85 raise NotImplementedError
89 Interface for layout engines.
91 def __init__(self
, tree
):
93 def layout(self
, node
):
94 raise NotImplementedError
95 def GetNodeList(self
):
96 raise NotImplementedError
102 def __init__(self
, tree
):
104 def transform(self
, node
, offset
, rotation
):
106 This method should only change the projx and projy attributes of
107 the node. These represent the position of the node as it should
108 be drawn on screen. Adjusting the x and y attributes can and
111 raise NotImplementedError
115 Returns the size of the entire tree as laid out and transformed
118 raise NotImplementedError
122 This is the interface that wxMVCTree expects from painters. All painters should
123 be Painter subclasses.
125 def __init__(self
, tree
):
127 self
.textcolor
= wxNamedColour("BLACK")
128 self
.bgcolor
= wxNamedColour("WHITE")
129 self
.fgcolor
= wxNamedColour("BLUE")
130 self
.linecolor
= wxNamedColour("GREY")
131 self
.font
= wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
)
137 def SetFont(self
, font
):
142 def ClearBuffer(self
):
144 def paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
145 raise NotImplementedError
146 def GetTextColour(self
):
147 return self
.textcolor
148 def SetTextColour(self
, color
):
149 self
.textcolor
= color
150 self
.textbrush
= wxBrush(color
)
151 self
.textpen
= wxPen(color
, 1, wxSOLID
)
152 def GetBackgroundColour(self
):
154 def SetBackgroundColour(self
, color
):
156 self
.bgbrush
= wxBrush(color
)
157 self
.bgpen
= wxPen(color
, 1, wxSOLID
)
158 def GetForegroundColour(self
):
160 def SetForegroundColour(self
, color
):
162 self
.fgbrush
= wxBrush(color
)
163 self
.fgpen
= wxPen(color
, 1, wxSOLID
)
164 def GetLineColour(self
):
165 return self
.linecolor
166 def SetLineColour(self
, color
):
167 self
.linecolor
= color
168 self
.linebrush
= wxBrush(color
)
169 self
.linepen
= wxPen( color
, 1, wxSOLID
)
170 def GetForegroundPen(self
):
172 def GetBackgroundPen(self
):
174 def GetTextPen(self
):
176 def GetForegroundBrush(self
):
178 def GetBackgroundBrush(self
):
180 def GetTextBrush(self
):
181 return self
.textbrush
182 def GetLinePen(self
):
184 def GetLineBrush(self
):
185 return self
.linebrush
186 def OnMouse(self
, evt
):
188 x
, y
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY())
189 for item
in self
.rectangles
:
190 if item
[1].contains((x
,y
)):
191 self
.tree
.Edit(item
[0].data
)
192 self
.tree
.OnNodeClick(item
[0], evt
)
194 elif evt
.ButtonDown():
195 x
, y
= self
.tree
.CalcUnscrolledPosition(evt
.GetX(), evt
.GetY())
196 for item
in self
.rectangles
:
197 if item
[1].contains((x
, y
)):
198 self
.tree
.OnNodeClick(item
[0], evt
)
200 for item
in self
.knobs
:
201 if item
[1].contains((x
, y
)):
202 self
.tree
.OnKnobClick(item
[0])
209 Interface for tree models
212 raise NotImplementedError
213 def SetRoot(self
, root
):
214 raise NotImplementedError
215 def GetChildCount(self
, node
):
216 raise NotImplementedError
217 def GetChildAt(self
, node
, index
):
218 raise NotImplementedError
219 def GetParent(self
, node
):
220 raise NotImplementedError
221 def AddChild(self
, parent
, child
):
222 if hasattr(self
, 'tree') and self
.tree
:
223 self
.tree
.NodeAdded(parent
, child
)
224 def RemoveNode(self
, child
):
225 if hasattr(self
, 'tree') and self
.tree
:
226 self
.tree
.NodeRemoved(child
)
227 def InsertChild(self
, parent
, child
, index
):
228 if hasattr(self
, 'tree') and self
.tree
:
229 self
.tree
.NodeInserted(parent
, child
, index
)
230 def IsLeaf(self
, node
):
231 raise NotImplementedError
233 def IsEditable(self
, node
):
236 def SetEditable(self
, node
):
241 This is the interface expected of a nodepainter.
243 def __init__(self
, painter
):
244 self
.painter
= painter
245 def paint(self
, node
, dc
, location
= None):
247 location should be provided only to draw in an unusual position
248 (not the node's normal position), otherwise the node's projected x and y
249 coordinates will be used.
251 raise NotImplementedError
255 The linepainter interface.
257 def __init__(self
, painter
):
258 self
.painter
= painter
259 def paint(self
, parent
, child
, dc
):
260 raise NotImplementedError
264 TextConverter interface.
266 def __init__(self
, painter
):
267 self
.painter
= painter
270 Should return a string. The node argument will be an
273 raise NotImplementedError
276 class BasicTreeModel(wxTreeModel
):
278 A very simple treemodel implementation, but flexible enough for many needs.
286 def SetRoot(self
, root
):
288 def GetChildCount(self
, node
):
289 if self
.children
.has_key(node
):
290 return len(self
.children
[node
])
293 def GetChildAt(self
, node
, index
):
294 return self
.children
[node
][index
]
296 def GetParent(self
, node
):
297 return self
.parents
[node
]
299 def AddChild(self
, parent
, child
):
300 self
.parents
[child
]=parent
301 if not self
.children
.has_key(parent
):
302 self
.children
[parent
]=[]
303 self
.children
[parent
].append(child
)
304 wxTreeModel
.AddChild(self
, parent
, child
)
307 def RemoveNode(self
, node
):
308 parent
= self
.parents
[node
]
309 del self
.parents
[node
]
310 self
.children
[parent
].remove(node
)
311 wxTreeModel
.RemoveNode(self
, node
)
313 def InsertChild(self
, parent
, child
, index
):
314 self
.parents
[child
]=parent
315 if not self
.children
.has_key(parent
):
316 self
.children
[parent
]=[]
317 self
.children
[parent
].insert(child
, index
)
318 wxTreeModel
.InsertChild(self
, parent
, child
, index
)
321 def IsLeaf(self
, node
):
322 return not self
.children
.has_key(node
)
324 def IsEditable(self
, node
):
327 def SetEditable(self
, node
, bool):
331 class FileEditor(Editor
):
332 def Edit(self
, node
):
333 treenode
= self
.tree
.nodemap
[node
]
334 self
.editcomp
= wxTextCtrl(self
.tree
, -1)
335 for rect
in self
.tree
.painter
.rectangles
:
336 if rect
[0] == treenode
:
337 self
.editcomp
.SetPosition((rect
[1][0], rect
[1][1]))
339 self
.editcomp
.SetValue(node
.fileName
)
340 self
.editcomp
.SetSelection(0, len(node
.fileName
))
341 self
.editcomp
.SetFocus()
342 self
.treenode
= treenode
343 # EVT_KEY_DOWN(self.editcomp, self._key)
344 EVT_KEY_UP(self
.editcomp
, self
._key
)
345 EVT_LEFT_DOWN(self
.editcomp
, self
._mdown
)
346 self
.editcomp
.CaptureMouse()
348 def CanEdit(self
, node
):
349 return isinstance(node
, FileWrapper
)
351 def EndEdit(self
, commit
):
352 if not self
.tree
._EditEnding
(self
.treenode
.data
):
355 node
= self
.treenode
.data
357 os
.rename(node
.path
+ os
.sep
+ node
.fileName
, node
.path
+ os
.sep
+ self
.editcomp
.GetValue())
358 node
.fileName
= self
.editcomp
.GetValue()
360 traceback
.print_exc()
361 self
.editcomp
.ReleaseMouse()
362 self
.editcomp
.Destroy()
368 if evt
.KeyCode() == WXK_RETURN
:
370 elif evt
.KeyCode() == WXK_ESCAPE
:
375 def _mdown(self
, evt
):
377 pos
= evt
.GetPosition()
378 edsize
= self
.editcomp
.GetSize()
379 if pos
.x
< 0 or pos
.y
< 0 or pos
.x
> edsize
.width
or pos
.y
> edsize
.height
:
385 Node class for FSTreeModel.
387 def __init__(self
, path
, fileName
):
389 self
.fileName
= fileName
394 class FSTreeModel(BasicTreeModel
):
396 This treemodel models the filesystem starting from a given path.
398 def __init__(self
, path
):
399 BasicTreeModel
.__init
__(self
)
401 fw
= FileWrapper(path
, string
.split(path
, os
.sep
)[-1])
402 self
._Build
(path
, fw
)
404 self
._editable
= true
405 def _Build(self
, path
, fileWrapper
):
406 for name
in os
.listdir(path
):
407 fw
= FileWrapper(path
, name
)
408 self
.AddChild(fileWrapper
, fw
)
409 childName
= path
+ os
.sep
+ name
410 if os
.path
.isdir(childName
):
411 self
._Build
(childName
, fw
)
413 def IsEditable(self
, node
):
414 return self
._editable
416 def SetEditable(self
, node
, bool):
417 self
._editable
= bool
419 class LateFSTreeModel(FSTreeModel
):
421 This treemodel models the filesystem starting from a given path.
422 It retrieves the directory list as requested.
424 def __init__(self
, path
):
425 BasicTreeModel
.__init
__(self
)
427 name
= string
.split(path
, os
.sep
)[-1]
428 pathpart
= path
[:-len(name
)]
429 fw
= FileWrapper(pathpart
, name
)
430 self
._Build
(path
, fw
)
432 self
._editable
= true
435 def _Build(self
, path
, parent
):
436 ppath
= parent
.path
+ os
.sep
+ parent
.fileName
437 if not os
.path
.isdir(ppath
):
439 for name
in os
.listdir(ppath
):
440 fw
= FileWrapper(ppath
, name
)
441 self
.AddChild(parent
, fw
)
442 def GetChildCount(self
, node
):
443 if self
.children
.has_key(node
):
444 return FSTreeModel
.GetChildCount(self
, node
)
446 self
._Build
(node
.path
, node
)
447 return FSTreeModel
.GetChildCount(self
, node
)
449 def IsLeaf(self
, node
):
450 return not os
.path
.isdir(node
.path
+ os
.sep
+ node
.fileName
)
452 class StrTextConverter(TextConverter
):
453 def convert(self
, node
):
454 return str(node
.data
)
456 class NullTransform(Transform
):
458 return tuple(self
.size
)
460 def transform(self
, node
, offset
, rotation
):
462 list = self
.tree
.GetLayoutEngine().GetNodeList()
464 node
.projx
= node
.x
+ offset
[0]
465 node
.projy
= node
.y
+ offset
[1]
466 if node
.projx
> self
.size
[0]:
467 self
.size
[0] = node
.projx
468 if node
.projy
> self
.size
[1]:
469 self
.size
[1] = node
.projy
472 def __init__(self
, x
, y
, width
, height
):
477 def __getitem__(self
, index
):
478 return (self
.x
, self
.y
, self
.width
, self
.height
)[index
]
480 def __setitem__(self
, index
, value
):
481 name
= ['x', 'y', 'width', 'height'][index
]
482 setattr(self
, name
, value
)
484 def contains(self
, other
):
485 if type(other
) == type(()):
486 other
= Rect(other
[0], other
[1], 0, 0)
487 if other
.x
>= self
.x
:
488 if other
.y
>= self
.y
:
489 if other
.width
+ other
.x
<= self
.width
+ self
.x
:
490 if other
.height
+ other
.y
<= self
.height
+ self
.y
:
495 return "Rect: " + str([self
.x
, self
.y
, self
.width
, self
.height
])
497 class TreeLayout(LayoutEngine
):
498 def SetHeight(self
, num
):
499 self
.NODE_HEIGHT
= num
501 def __init__(self
, tree
):
502 LayoutEngine
.__init
__(self
, tree
)
504 self
.NODE_HEIGHT
= 20
507 def layout(self
, node
):
509 self
.layoutwalk(node
)
511 def GetNodeList(self
):
514 def layoutwalk(self
, node
):
515 if node
== self
.tree
.currentRoot
:
517 self
.lastY
= (-self
.NODE_HEIGHT
)
518 node
.x
= self
.NODE_STEP
* node
.level
519 node
.y
= self
.lastY
+ self
.NODE_HEIGHT
521 self
.nodelist
.append(node
)
523 for kid
in node
.kids
:
524 kid
.level
= node
.level
+ 1
527 class TreePainter(Painter
):
529 The default painter class. Uses double-buffering, delegates the painting of nodes and
530 lines to helper classes deriving from NodePainter and LinePainter.
532 def __init__(self
, tree
, nodePainter
= None, linePainter
= None, textConverter
= None):
533 Painter
.__init
__(self
, tree
)
535 nodePainter
= TreeNodePainter(self
)
536 self
.nodePainter
= nodePainter
538 linePainter
= TreeLinePainter(self
)
539 self
.linePainter
= linePainter
540 if not textConverter
:
541 textConverter
= StrTextConverter(self
)
542 self
.textConverter
= textConverter
545 def paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
546 if not self
.charWidths
:
549 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
)
550 self
.charHeight
= dc
.GetTextExtent("D")[1]
551 self
.textpen
= wxPen(self
.GetTextColour(), 1, wxSOLID
)
552 self
.fgpen
= wxPen(self
.GetForegroundColour(), 1, wxSOLID
)
553 self
.bgpen
= wxPen(self
.GetBackgroundColour(), 1, wxSOLID
)
554 self
.linepen
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
555 self
.dashpen
= wxPen(self
.GetLineColour(), 1, wxDOT
)
556 self
.textbrush
= wxBrush(self
.GetTextColour(), wxSOLID
)
557 self
.fgbrush
= wxBrush(self
.GetForegroundColour(), wxSOLID
)
558 self
.bgbrush
= wxBrush(self
.GetBackgroundColour(), wxSOLID
)
559 self
.linebrush
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
560 treesize
= self
.tree
.GetSize()
561 size
= self
.tree
.transform
.GetSize()
562 size
= (max(treesize
.width
, size
[0]+50), max(treesize
.height
, size
[1]+50))
565 mem_dc
= wxMemoryDC()
566 if not self
.GetBuffer():
569 self
.bmp
= wxEmptyBitmap(size
[0], size
[1])
570 mem_dc
.SelectObject(self
.GetBuffer())
571 mem_dc
.SetPen(self
.GetBackgroundPen())
572 mem_dc
.SetBrush(self
.GetBackgroundBrush())
573 mem_dc
.DrawRectangle(0, 0, size
[0], size
[1])
574 mem_dc
.SetFont(self
.tree
.GetFont())
575 self
.paintWalk(node
, mem_dc
)
577 mem_dc
.SelectObject(self
.GetBuffer())
578 xstart
, ystart
= self
.tree
.CalcUnscrolledPosition(0,0)
579 size
= self
.tree
.GetClientSizeTuple()
580 dc
.Blit(xstart
, ystart
, size
[0], size
[1], mem_dc
, xstart
, ystart
)
582 if node
== self
.tree
.currentRoot
:
585 dc
.SetPen(self
.GetBackgroundPen())
586 dc
.SetBrush(self
.GetBackgroundBrush())
587 dc
.SetFont(self
.tree
.GetFont())
589 dc
.DrawRectangle(0, 0, size
[0], size
[1])
591 #Call with not paintBackground because if we are told not to paint the
592 #whole background, we have to paint in parts to undo selection coloring.
594 self
.paintWalk(node
, dc
, not pb
)
597 def GetDashPen(self
):
600 def SetLinePen(self
, pen
):
601 Painter
.SetLinePen(self
, pen
)
602 self
.dashpen
= wxPen(pen
.GetColour(), 1, wxDOT
)
604 def paintWalk(self
, node
, dc
, paintRects
=0):
605 self
.linePainter
.paint(node
.parent
, node
, dc
)
606 self
.nodePainter
.paint(node
, dc
, drawRects
= paintRects
)
608 for kid
in node
.kids
:
609 if not self
.paintWalk(kid
, dc
, paintRects
):
611 for kid
in node
.kids
:
612 px
= (kid
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
613 py
= kid
.projy
+ kid
.height
/2
614 if (not self
.tree
.model
.IsLeaf(kid
.data
)) or ((kid
.expanded
or self
.tree
._assumeChildren
) and len(kid
.kids
)):
615 dc
.SetPen(self
.linepen
)
616 dc
.SetBrush(self
.bgbrush
)
617 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
618 self
.knobs
.append(kid
, Rect(px
-4, py
-4, 9, 9))
619 dc
.SetPen(self
.textpen
)
621 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
622 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
623 if node
== self
.tree
.currentRoot
:
624 px
= (node
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
625 py
= node
.projy
+ node
.height
/2
626 dc
.SetPen(self
.linepen
)
627 dc
.SetBrush(self
.bgbrush
)
628 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
629 self
.knobs
.append(node
, Rect(px
-4, py
-4, 9, 9))
630 dc
.SetPen(self
.textpen
)
631 if not node
.expanded
:
632 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
633 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
636 def OnMouse(self
, evt
):
637 Painter
.OnMouse(self
, evt
)
639 class TreeNodePainter(NodePainter
):
640 def paint(self
, node
, dc
, location
= None, drawRects
= 0):
641 text
= self
.painter
.textConverter
.convert(node
)
642 extent
= dc
.GetTextExtent(text
)
643 node
.width
= extent
[0]
644 node
.height
= extent
[1]
646 dc
.SetPen(self
.painter
.GetLinePen())
647 dc
.SetBrush(self
.painter
.GetForegroundBrush())
648 dc
.SetTextForeground(wxNamedColour("WHITE"))
649 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
652 dc
.SetBrush(self
.painter
.GetBackgroundBrush())
653 dc
.SetPen(self
.painter
.GetBackgroundPen())
654 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
655 dc
.SetTextForeground(self
.painter
.GetTextColour())
656 dc
.DrawText(text
, node
.projx
, node
.projy
)
657 self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
)))
659 class TreeLinePainter(LinePainter
):
660 def paint(self
, parent
, child
, dc
):
661 dc
.SetPen(self
.painter
.GetDashPen())
662 px
= py
= cx
= cy
= 0
663 if parent
is None or child
== self
.painter
.tree
.currentRoot
:
664 px
= (child
.projx
- self
.painter
.tree
.layout
.NODE_STEP
) + 5
665 py
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -2
668 dc
.DrawLine(px
, py
, cx
, cy
)
670 px
= parent
.projx
+ 5
671 py
= parent
.projy
+ parent
.height
673 cy
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -3
674 dc
.DrawLine(px
, py
, px
, cy
)
675 dc
.DrawLine(px
, cy
, cx
, cy
)
678 wxEVT_MVCTREE_BEGIN_EDIT
= 20204 #Start editing. Vetoable.
679 wxEVT_MVCTREE_END_EDIT
= 20205 #Stop editing. Vetoable.
680 wxEVT_MVCTREE_DELETE_ITEM
= 20206 #Item removed from model.
681 wxEVT_MVCTREE_ITEM_EXPANDED
= 20209
682 wxEVT_MVCTREE_ITEM_EXPANDING
= 20210
683 wxEVT_MVCTREE_ITEM_COLLAPSED
= 20211
684 wxEVT_MVCTREE_ITEM_COLLAPSING
= 20212
685 wxEVT_MVCTREE_SEL_CHANGED
= 20213
686 wxEVT_MVCTREE_SEL_CHANGING
= 20214 #Vetoable.
687 wxEVT_MVCTREE_KEY_DOWN
= 20215
688 wxEVT_MVCTREE_ADD_ITEM
= 20216 #Item added to model.
690 def EVT_MVCTREE_SEL_CHANGED(win
, id, func
):
691 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED
, func
)
693 def EVT_MVCTREE_SEL_CHANGING(win
, id, func
):
694 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING
, func
)
696 def EVT_MVCTREE_ITEM_EXPANDED(win
, id, func
):
697 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED
, func
)
699 def EVT_MVCTREE_ITEM_EXPANDING(win
, id, func
):
700 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING
, func
)
702 def EVT_MVCTREE_ITEM_COLLAPSED(win
, id, func
):
703 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED
, func
)
705 def EVT_MVCTREE_ITEM_COLLAPSING(win
, id, func
):
706 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING
, func
)
708 def EVT_MVCTREE_ADD_ITEM(win
, id, func
):
709 win
.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM
, func
)
711 def EVT_MVCTREE_DELETE_ITEM(win
, id, func
):
712 win
.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM
, func
)
714 def EVT_MVCTREE_KEY_DOWN(win
, id, func
):
715 win
.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN
, func
)
718 class wxMVCTreeEvent(wxPyCommandEvent
):
719 def __init__(self
, type, id, node
= None, nodes
= None, keyEvent
= None, **kwargs
):
720 apply(wxPyCommandEvent
.__init
__, (self
, type, id), kwargs
)
723 self
.keyEvent
= keyEvent
728 def getKeyEvent(self
):
731 class wxMVCTreeNotifyEvent(wxMVCTreeEvent
):
732 def __init__(self
, type, id, node
= None, nodes
= None, **kwargs
):
733 apply(wxMVCTreeEvent
.__init
__, (self
, type, id), kwargs
)
734 self
.notify
= wxNotifyEvent(type, id)
735 def getNotifyEvent(self
):
738 class wxMVCTree(wxScrolledWindow
):
740 The main mvc tree class.
742 def __init__(self
, parent
, id, model
= None, layout
= None, transform
= None,
743 painter
= None, *args
, **kwargs
):
744 apply(wxScrolledWindow
.__init
__, (self
, parent
, id), kwargs
)
746 self
._multiselect
= false
747 self
._selections
= []
748 self
._assumeChildren
= false
749 self
._scrollx
= false
750 self
._scrolly
= false
751 self
.doubleBuffered
= false
752 self
._lastPhysicalSize
= self
.GetSize()
755 model
= BasicTreeModel()
756 model
.SetRoot("Root")
759 layout
= TreeLayout(self
)
762 transform
= NullTransform(self
)
763 self
.transform
= transform
765 painter
= TreePainter(self
)
766 self
.painter
= painter
767 self
.SetFont(wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
))
768 EVT_MOUSE_EVENTS(self
, self
.OnMouse
)
769 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
770 self
.doubleBuffered
= true
773 if self
.doubleBuffered
:
774 self
.painter
.ClearBuffer()
775 wxScrolledWindow
.Refresh(self
)
777 def GetPainter(self
):
780 def GetLayoutEngine(self
):
783 def GetTransform(self
):
784 return self
.transform
787 return "<wxMVCTree instance at %s>" % str(hex(id(self
)))
790 return self
.__repr
__()
792 def NodeAdded(self
, parent
, child
):
793 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
794 self
.GetEventHandler().ProcessEvent(e
)
795 self
.painter
.ClearBuffer()
797 def NodeInserted(self
, parent
, child
, index
):
798 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
799 self
.GetEventHandler().ProcessEvent(e
)
800 self
.painter
.ClearBuffer()
801 def NodeRemoved(self
, node
):
802 e
= wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
803 self
.GetEventHandler().ProcessEvent(e
)
804 self
.painter
.ClearBuffer()
805 def OnKeyDown(self
, evt
):
806 e
= wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent
= evt
)
807 self
.GetEventHandler().ProcessEvent(e
)
809 def SetFont(self
, font
):
810 self
.painter
.SetFont(font
)
811 dc
= wxClientDC(self
)
813 self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18)
814 self
.painter
.ClearBuffer()
816 return self
.painter
.GetFont()
818 def AddEditor(self
, editor
):
819 self
._editors
.append(editor
)
821 def RemoveEditor(self
, editor
):
822 self
._editors
.remove(editor
)
824 def OnMouse(self
, evt
):
825 self
.painter
.OnMouse(evt
)
827 def OnNodeClick(self
, node
, mouseEvent
):
828 if node
.selected
and (self
.IsMultiSelect() and mouseEvent
.ControlDown()):
829 self
.RemoveFromSelection(node
.data
)
831 self
.AddToSelection(node
.data
, mouseEvent
.ControlDown(), mouseEvent
.ShiftDown())
833 def OnKnobClick(self
, node
):
834 self
.SetExpanded(node
.data
, not node
.expanded
)
836 def GetDisplayText(self
, node
):
837 treenode
= self
.nodemap
[node
]
838 return self
.painter
.textConverter
.convert(treenode
)
840 def IsDoubleBuffered(self
):
841 return self
.doubleBuffered
843 def SetDoubleBuffered(self
, bool):
845 By default wxMVCTree is double-buffered.
847 self
.doubleBuffered
= bool
852 def SetModel(self
, model
):
854 Completely change the data to be displayed.
860 self
._selections
= []
861 self
.layoutRoot
= MVCTreeNode()
862 self
.layoutRoot
.data
= self
.model
.GetRoot()
863 self
.layoutRoot
.expanded
= true
864 self
.LoadChildren(self
.layoutRoot
)
865 self
.currentRoot
= self
.layoutRoot
868 self
._scrollset
= None
871 def GetCurrentRoot(self
):
872 return self
.currentRoot
874 def LoadChildren(self
, layoutNode
):
878 self
.nodemap
[layoutNode
.data
]=layoutNode
879 for i
in range(self
.GetModel().GetChildCount(layoutNode
.data
)):
880 p
= MVCTreeNode("RAW", layoutNode
, [])
882 p
.data
= self
.GetModel().GetChildAt(layoutNode
.data
, i
)
883 self
.nodemap
[p
.data
]=p
884 layoutNode
.built
= true
885 if not self
._assumeChildren
:
886 for kid
in layoutNode
.kids
:
887 self
.LoadChildren(kid
)
889 def OnEraseBackground(self
, evt
):
892 def OnSize(self
, evt
):
893 size
= self
.GetSize()
894 self
.center
= (size
.width
/2, size
.height
/2)
895 if self
._lastPhysicalSize
.width
< size
.width
or self
._lastPhysicalSize
.height
< size
.height
:
896 self
.painter
.ClearBuffer()
897 self
._lastPhysicalSize
= size
899 def GetSelection(self
):
900 "Returns a tuple of selected nodes."
901 return tuple(self
._selections
)
903 def SetSelection(self
, nodeTuple
):
904 if type(nodeTuple
) != type(()):
905 nodeTuple
= (nodeTuple
,)
906 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
907 self
.GetEventHandler().ProcessEvent(e
)
908 if not e
.notify
.IsAllowed():
910 for node
in nodeTuple
:
911 treenode
= self
.nodemap
[node
]
912 treenode
.selected
= true
913 for node
in self
._selections
:
914 treenode
= self
.nodemap
[node
]
915 node
.selected
= false
916 self
._selections
= list(nodeTuple
)
917 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
918 self
.GetEventHandler().ProcessEvent(e
)
920 def IsMultiSelect(self
):
921 return self
._multiselect
923 def SetMultiSelect(self
, bool):
924 self
._multiselect
= bool
926 def IsSelected(self
, node
):
927 return self
.nodemap
[node
].selected
929 def Edit(self
, node
):
930 if not self
.model
.IsEditable(node
):
932 for ed
in self
._editors
:
934 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
)
935 self
.GetEventHandler().ProcessEvent(e
)
936 if not e
.notify
.IsAllowed():
939 self
._currentEditor
= ed
943 if self
._currentEditor
:
944 self
._currentEditor
.EndEdit
945 self
._currentEditor
= None
947 def _EditEnding(self
, node
):
948 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
)
949 self
.GetEventHandler().ProcessEvent(e
)
950 if not e
.notify
.IsAllowed():
952 self
._currentEditor
= None
956 def SetExpanded(self
, node
, bool):
957 treenode
= self
.nodemap
[node
]
959 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
)
960 self
.GetEventHandler().ProcessEvent(e
)
961 if not e
.notify
.IsAllowed():
963 if not treenode
.built
:
964 self
.LoadChildren(treenode
)
966 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
)
967 self
.GetEventHandler().ProcessEvent(e
)
968 if not e
.notify
.IsAllowed():
970 treenode
.expanded
= bool
972 if treenode
.expanded
:
973 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
)
975 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
)
976 self
.GetEventHandler().ProcessEvent(e
)
977 self
.layout
.layout(self
.currentRoot
)
978 self
.transform
.transform(self
.currentRoot
, self
.offset
, self
.rotation
)
981 def IsExpanded(self
, node
):
982 return self
.nodemap
[node
].expanded
984 def AddToSelection(self
, nodeOrTuple
, enableMulti
= true
, shiftMulti
= false
):
985 nodeTuple
= nodeOrTuple
986 if type(nodeOrTuple
)!= type(()):
987 nodeTuple
= (nodeOrTuple
,)
988 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
989 self
.GetEventHandler().ProcessEvent(e
)
990 if not e
.notify
.IsAllowed():
993 if not (self
.IsMultiSelect() and (enableMulti
or shiftMulti
)):
994 for node
in self
._selections
:
995 treenode
= self
.nodemap
[node
]
996 treenode
.selected
= false
997 changeparents
.append(treenode
)
999 self
._selections
= [node
]
1000 treenode
= self
.nodemap
[node
]
1001 changeparents
.append(treenode
)
1002 treenode
.selected
= true
1005 for node
in nodeTuple
:
1006 treenode
= self
.nodemap
[node
]
1007 oldtreenode
= self
.nodemap
[self
._selections
[0]]
1008 if treenode
.parent
== oldtreenode
.parent
:
1010 for kid
in oldtreenode
.parent
.kids
:
1011 if kid
== treenode
or kid
== oldtreenode
:
1014 self
._selections
.append(kid
.data
)
1015 changeparents
.append(kid
)
1018 self
._selections
.append(kid
.data
)
1019 changeparents
.append(kid
)
1021 for node
in nodeTuple
:
1023 self
._selections
.index(node
)
1025 self
._selections
.append(node
)
1026 treenode
= self
.nodemap
[node
]
1027 treenode
.selected
= true
1028 changeparents
.append(treenode
)
1029 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1030 self
.GetEventHandler().ProcessEvent(e
)
1031 dc
= wxClientDC(self
)
1033 for node
in changeparents
:
1035 self
.painter
.paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1036 self
.painter
.ClearBuffer()
1037 def RemoveFromSelection(self
, nodeTuple
):
1038 if type(nodeTuple
) != type(()):
1039 nodeTuple
= (nodeTuple
,)
1041 for node
in nodeTuple
:
1042 self
._selections
.remove(node
)
1043 treenode
= self
.nodemap
[node
]
1044 changeparents
.append(treenode
)
1045 treenode
.selected
= false
1046 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes
= nodeTuple
)
1047 self
.GetEventHandler().ProcessEvent(e
)
1048 dc
= wxClientDC(self
)
1050 for node
in changeparents
:
1052 self
.painter
.paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1053 self
.painter
.ClearBuffer()
1056 def GetBackgroundColour(self
):
1057 if hasattr(self
, 'painter') and self
.painter
:
1058 return self
.painter
.GetBackgroundColour()
1060 return wxWindow
.GetBackgroundColour(self
)
1061 def SetBackgroundColour(self
, color
):
1062 if hasattr(self
, 'painter') and self
.painter
:
1063 self
.painter
.SetBackgroundColour(color
)
1065 wxWindow
.SetBackgroundColour(self
, color
)
1066 def GetForegroundColour(self
):
1067 if hasattr(self
, 'painter') and self
.painter
:
1068 return self
.painter
.GetForegroundColour()
1070 return wxWindow
.GetBackgroundColour(self
)
1071 def SetForegroundColour(self
, color
):
1072 if hasattr(self
, 'painter') and self
.painter
:
1073 self
.painter
.SetForegroundColour(color
)
1075 wxWindow
.SetBackgroundColour(self
, color
)
1077 def SetAssumeChildren(self
, bool):
1078 self
._assumeChildren
= bool
1080 def GetAssumeChildren(self
):
1081 return self
._assumeChildren
1083 def OnPaint(self
, evt
):
1085 Ensures that the tree has been laid out and transformed, then calls the painter
1086 to paint the control.
1089 self
.EnableScrolling(false
, false
)
1090 if not self
.laidOut
:
1091 self
.layout
.layout(self
.currentRoot
)
1093 self
.transformed
= false
1094 if not self
.transformed
:
1095 self
.transform
.transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1096 self
.transformed
= true
1098 tsize
= list(self
.transform
.GetSize())
1099 tsize
[0] = tsize
[0] + 50
1100 tsize
[1] = tsize
[1] + 50
1101 size
= self
.GetSizeTuple()
1102 if tsize
[0] > size
[0] or tsize
[1] > size
[1]:
1103 if not hasattr(self
, '_oldsize') or (tsize
[0] > self
._oldsize
[0] or tsize
[1] > self
._oldsize
[1]):
1104 self
._oldsize
= tsize
1105 oldstart
= self
.ViewStart()
1106 self
._lastPhysicalSize
= self
.GetSize()
1107 self
.SetScrollbars(10, 10, tsize
[0]/10, tsize
[1]/10)
1108 self
.Scroll(oldstart
[0], oldstart
[1])
1109 dc
= wxPaintDC(self
)
1111 dc
.SetFont(self
.GetFont())
1112 self
.painter
.paint(dc
, self
.currentRoot
, self
.doubleBuffered
)
1114 traceback
.print_exc()