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.
5 This module contains the wxMVCTree class (the 'controller' of the MVC trio)
6 and PathfinderNode, which it uses internally to manage its info.
8 Pathfinder actually is even more configurable than MVC normally implies, because
9 almost every aspect of it is pluggable:
10 wxMVCTree - Overall controller, and the window that actually gets placed
12 Painter - Paints the control. The 'view' part of MVC.
13 NodePainter - Paints just the nodes
14 LinePainter - Paints just the lines between the nodes
15 TextConverter - Figures out what text to print for each node
16 Editor - Edits the contents of a node, if the model is editable.
17 LayoutEngine - Determines initial placement of nodes
18 Transform - Adjusts positions of nodes for movement or special effects.
19 TreeModel - Contains the data which the rest of the control acts
20 on. The 'model' part of MVC.
22 Author/Maintainer - Bryn Keller <xoltar@starship.python.net>
25 #------------------------------------------------------------------------
26 from wxPython
.wx
import *
28 #------------------------------------------------------------------------
32 Used internally by wxMVCTree to manage its data. Contains information about
33 screen placement, the actual data associated with it, and more. These are
34 the nodes passed to all the other helper parts to do their work with.
36 def __init__(self
, data
=None, parent
= None, kids
= [], x
= 0, y
= 0):
49 def GetChildren(self
):
55 def Remove(self
, node
):
57 self
.kids
.remove(node
)
61 self
.kids
.append(node
)
64 def SetParent(self
, parent
):
65 if self
.parent
and not (self
.parent
is parent
):
66 self
.parent
.Remove(self
)
69 return "Node: " + str(self
.data
) + " (" + str(self
.x
) + ", " + str(self
.y
) + ")"
72 def GetTreeString(self
, tabs
=0):
73 s
= tabs
* '\t' + str(self
) + '\n'
75 s
= s
+ kid
.GetTreeString(tabs
+ 1)
80 def __init__(self
, tree
):
83 raise NotImplementedError
84 def EndEdit(self
, node
, commit
):
85 raise NotImplementedError
86 def CanEdit(self
, node
):
87 raise NotImplementedError
91 Interface for layout engines.
93 def __init__(self
, tree
):
95 def layout(self
, node
):
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 This is the interface that wxMVCTree expects from painters. All painters should
116 be Painter subclasses.
118 def __init__(self
, tree
):
120 self
.textcolor
= wxNamedColour("BLACK")
121 self
.bgcolor
= wxNamedColour("WHITE")
122 self
.fgcolor
= wxNamedColour("BLUE")
123 self
.linecolor
= wxNamedColour("GREY")
124 self
.font
= wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
)
127 self
.minx
= self
.maxx
= self
.miny
= self
.maxy
= 0
132 def SetFont(self
, font
):
136 def paint(self
, dc
, node
):
137 raise NotImplementedError
138 def GetTextColour(self
):
139 return self
.textcolor
140 def SetTextColour(self
, color
):
141 self
.textcolor
= color
142 self
.textbrush
= wxBrush(color
)
143 self
.textpen
= wxPen(color
, 1, wxSOLID
)
144 def GetBackgroundColour(self
):
146 def SetBackgroundColour(self
, color
):
148 self
.bgbrush
= wxBrush(color
)
149 self
.bgpen
= wxPen(color
, 1, wxSOLID
)
150 def GetForegroundColour(self
):
152 def SetForegroundColour(self
, color
):
154 self
.fgbrush
= wxBrush(color
)
155 self
.fgpen
= wxPen(color
, 1, wxSOLID
)
156 def GetLineColour(self
):
157 return self
.linecolor
158 def SetLineColour(self
, color
):
159 self
.linecolor
= color
160 self
.linebrush
= wxBrush(color
)
161 self
.linepen
= wxPen( color
, 1, wxSOLID
)
162 def GetForegroundPen(self
):
164 def GetBackgroundPen(self
):
166 def GetTextPen(self
):
168 def GetForegroundBrush(self
):
170 def GetBackgroundBrush(self
):
172 def GetTextBrush(self
):
173 return self
.textbrush
174 def GetLinePen(self
):
176 def GetLineBrush(self
):
177 return self
.linebrush
178 def OnMouse(self
, evt
):
180 for item
in self
.rectangles
:
181 if item
[1].contains((evt
.GetX(), evt
.GetY())):
182 self
.tree
.Edit(item
[0].data
)
183 self
.tree
.OnNodeClick(item
[0], evt
)
185 elif evt
.ButtonDown():
186 #self.oldpos = (evt.GetX(), evt.GetY())
187 for item
in self
.rectangles
:
188 if item
[1].contains((evt
.GetX(), evt
.GetY())):
189 self
.tree
.OnNodeClick(item
[0], evt
)
191 for item
in self
.knobs
:
192 if item
[1].contains((evt
.GetX(), evt
.GetY())):
193 self
.tree
.OnKnobClick(item
[0])
200 Interface for tree models
203 raise NotImplementedError
204 def SetRoot(self
, root
):
205 raise NotImplementedError
206 def GetChildCount(self
, node
):
207 raise NotImplementedError
208 def GetChildAt(self
, node
, index
):
209 raise NotImplementedError
210 def GetParent(self
, node
):
211 raise NotImplementedError
212 def AddChild(self
, parent
, child
):
213 if hasattr(self
, 'tree') and self
.tree
:
214 self
.tree
.NodeAdded(parent
, child
)
215 def RemoveNode(self
, child
):
216 if hasattr(self
, 'tree') and self
.tree
:
217 self
.tree
.NodeRemoved(child
)
218 def InsertChild(self
, parent
, child
, index
):
219 if hasattr(self
, 'tree') and self
.tree
:
220 self
.tree
.NodeInserted(parent
, child
, index
)
221 def IsLeaf(self
, node
):
222 raise NotImplementedError
224 def IsEditable(self
, node
):
227 def SetEditable(self
, node
):
232 This is the interface expected of a nodepainter.
234 def __init__(self
, painter
):
235 self
.painter
= painter
236 def paint(self
, node
, dc
, location
= None):
238 location should be provided only to draw in an unusual position
239 (not the node's normal position), otherwise the node's projected x and y
240 coordinates will be used.
242 raise NotImplementedError
246 The linepainter interface.
248 def __init__(self
, painter
):
249 self
.painter
= painter
250 def paint(self
, parent
, child
, dc
):
251 raise NotImplementedError
255 TextConverter interface.
257 def __init__(self
, painter
):
258 self
.painter
= painter
261 Should return a string. The node argument will be an
264 raise NotImplementedError
267 class BasicTreeModel(wxTreeModel
):
269 A very simple treemodel implementation, but flexible enough for many needs.
277 def SetRoot(self
, root
):
279 def GetChildCount(self
, node
):
280 if self
.children
.has_key(node
):
281 return len(self
.children
[node
])
284 def GetChildAt(self
, node
, index
):
285 return self
.children
[node
][index
]
287 def GetParent(self
, node
):
288 return self
.parents
[node
]
290 def AddChild(self
, parent
, child
):
291 self
.parents
[child
]=parent
292 if not self
.children
.has_key(parent
):
293 self
.children
[parent
]=[]
294 self
.children
[parent
].append(child
)
295 wxTreeModel
.AddChild(self
, parent
, child
)
298 def RemoveNode(self
, node
):
299 parent
= self
.parents
[node
]
300 del self
.parents
[node
]
301 self
.children
[parent
].remove(node
)
302 wxTreeModel
.RemoveNode(self
, node
)
304 def InsertChild(self
, parent
, child
, index
):
305 self
.parents
[child
]=parent
306 if not self
.children
.has_key(parent
):
307 self
.children
[parent
]=[]
308 self
.children
[parent
].insert(child
, index
)
309 wxTreeModel
.InsertChild(self
, parent
, child
, index
)
312 def IsLeaf(self
, node
):
313 return not self
.children
.has_key(node
)
315 def IsEditable(self
, node
):
318 def SetEditable(self
, node
, bool):
322 class FileEditor(Editor
):
323 def Edit(self
, node
):
324 treenode
= self
.tree
.nodemap
[node
]
325 self
.editcomp
= wxTextCtrl(self
.tree
, -1)
326 for rect
in self
.tree
.painter
.rectangles
:
327 if rect
[0] == treenode
:
328 self
.editcomp
.SetPosition((rect
[1][0], rect
[1][1]))
330 self
.editcomp
.SetValue(node
.fileName
)
331 self
.editcomp
.SetSelection(0, len(node
.fileName
))
332 self
.editcomp
.SetFocus()
333 self
.treenode
= treenode
334 EVT_KEY_DOWN(self
.editcomp
, self
._key
)
335 EVT_LEFT_DOWN(self
.editcomp
, self
._mdown
)
336 self
.editcomp
.CaptureMouse()
338 def CanEdit(self
, node
):
339 return isinstance(node
, FileWrapper
)
341 def EndEdit(self
, commit
):
342 if not self
.tree
._EditEnding
(self
.treenode
.data
):
345 node
= self
.treenode
.data
347 os
.rename(node
.path
+ os
.sep
+ node
.fileName
, node
.path
+ os
.sep
+ self
.editcomp
.GetValue())
348 node
.fileName
= self
.editcomp
.GetValue()
350 import traceback
;traceback
.print_exc()
351 self
.editcomp
.ReleaseMouse()
352 self
.editcomp
.Destroy()
357 if evt
.KeyCode() == WXK_RETURN
:
359 elif evt
.KeyCode() == WXK_ESCAPE
:
364 def _mdown(self
, evt
):
366 pos
= evt
.GetPosition()
368 edsize
= self
.editcomp
.GetSize()
369 if pos
.x
< 0 or pos
.y
< 0 or pos
.x
> edsize
.width
or pos
.y
> edsize
.height
:
375 Node class for FSTreeModel.
377 def __init__(self
, path
, fileName
):
379 self
.fileName
= fileName
384 class FSTreeModel(BasicTreeModel
):
386 This treemodel models the filesystem starting from a given path.
388 def __init__(self
, path
):
389 BasicTreeModel
.__init
__(self
)
391 fw
= FileWrapper(path
, string
.split(path
, os
.sep
)[-1])
392 self
._Build
(path
, fw
)
394 self
._editable
= true
395 def _Build(self
, path
, fileWrapper
):
396 for name
in os
.listdir(path
):
397 fw
= FileWrapper(path
, name
)
398 self
.AddChild(fileWrapper
, fw
)
399 childName
= path
+ os
.sep
+ name
400 if os
.path
.isdir(childName
):
401 self
._Build
(childName
, fw
)
403 def IsEditable(self
, node
):
404 return self
._editable
406 def SetEditable(self
, node
, bool):
407 self
._editable
= bool
409 class LateFSTreeModel(FSTreeModel
):
411 This treemodel models the filesystem starting from a given path.
412 It retrieves the directory list as requested.
414 def __init__(self
, path
):
415 BasicTreeModel
.__init
__(self
)
417 name
= string
.split(path
, os
.sep
)[-1]
418 pathpart
= path
[:-len(name
)]
420 fw
= FileWrapper(pathpart
, name
)
421 self
._Build
(path
, fw
)
423 self
._editable
= true
426 def _Build(self
, path
, parent
):
427 ppath
= parent
.path
+ os
.sep
+ parent
.fileName
428 if not os
.path
.isdir(ppath
):
430 for name
in os
.listdir(ppath
):
431 fw
= FileWrapper(ppath
, name
)
432 self
.AddChild(parent
, fw
)
433 def GetChildCount(self
, node
):
434 if self
.children
.has_key(node
):
435 return FSTreeModel
.GetChildCount(self
, node
)
437 self
._Build
(node
.path
, node
)
438 return FSTreeModel
.GetChildCount(self
, node
)
440 def IsLeaf(self
, node
):
441 return not os
.path
.isdir(node
.path
+ os
.sep
+ node
.fileName
)
443 class StrTextConverter(TextConverter
):
444 def convert(self
, node
):
445 return str(node
.data
)
447 class NullTransform(Transform
):
448 def transform(self
, node
, offset
, rotation
):
449 node
.projx
= node
.x
+ offset
[0]
450 node
.projy
= node
.y
+ offset
[1]
451 for kid
in node
.kids
:
452 self
.transform(kid
, offset
, rotation
)
455 def __init__(self
, x
, y
, width
, height
):
460 def __getitem__(self
, index
):
461 return (self
.x
, self
.y
, self
.width
, self
.height
)[index
]
463 def __setitem__(self
, index
, value
):
464 name
= ['x', 'y', 'width', 'height'][index
]
465 setattr(self
, name
, value
)
467 def contains(self
, other
):
468 if type(other
) == type(()):
469 other
= Rect(other
[0], other
[1], 0, 0)
470 if other
.x
>= self
.x
:
471 if other
.y
>= self
.y
:
472 if other
.width
+ other
.x
<= self
.width
+ self
.x
:
473 if other
.height
+ other
.y
<= self
.height
+ self
.y
:
478 return "Rect: " + str([self
.x
, self
.y
, self
.width
, self
.height
])
480 class TreeLayout(LayoutEngine
):
481 def SetHeight(self
, num
):
482 self
.NODE_HEIGHT
= num
484 def __init__(self
, tree
):
485 LayoutEngine
.__init
__(self
, tree
)
487 self
.NODE_HEIGHT
= 20
488 def layout(self
, node
):
489 if node
== self
.tree
.currentRoot
:
491 self
.lastY
= (-self
.NODE_HEIGHT
)
492 node
.x
= self
.NODE_STEP
* node
.level
493 node
.y
= self
.lastY
+ self
.NODE_HEIGHT
496 for kid
in node
.kids
:
497 kid
.level
= node
.level
+ 1
500 class TreePainter(Painter
):
502 The default painter class. Uses double-buffering, delegates the painting of nodes and
503 lines to helper classes deriving from NodePainter and LinePainter.
505 def __init__(self
, tree
, nodePainter
= None, linePainter
= None, textConverter
= None):
506 Painter
.__init
__(self
, tree
)
508 nodePainter
= TreeNodePainter(self
)
509 self
.nodePainter
= nodePainter
511 linePainter
= TreeLinePainter(self
)
512 self
.linePainter
= linePainter
513 if not textConverter
:
514 textConverter
= StrTextConverter(self
)
515 self
.textConverter
= textConverter
518 def paint(self
, dc
, node
):
519 if not self
.charWidths
:
522 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
)
523 self
.charHeight
= dc
.GetTextExtent("D")[1]
524 self
.textpen
= wxPen(self
.GetTextColour(), 1, wxSOLID
)
525 self
.fgpen
= wxPen(self
.GetForegroundColour(), 1, wxSOLID
)
526 self
.bgpen
= wxPen(self
.GetBackgroundColour(), 1, wxSOLID
)
527 self
.linepen
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
528 self
.dashpen
= wxPen(self
.GetLineColour(), 1, wxDOT
)
529 self
.textbrush
= wxBrush(self
.GetTextColour(), wxSOLID
)
530 self
.fgbrush
= wxBrush(self
.GetForegroundColour(), wxSOLID
)
531 self
.bgbrush
= wxBrush(self
.GetBackgroundColour(), wxSOLID
)
532 self
.linebrush
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
536 dc
.SetPen(self
.GetBackgroundPen())
537 dc
.SetBrush(self
.GetBackgroundBrush())
538 size
= self
.tree
.GetSize()
539 dc
.DrawRectangle(0, 0, size
.width
, size
.height
)
541 self
.paintWalk(node
, dc
)
544 def GetDashPen(self
):
547 def SetLinePen(self
, pen
):
548 Painter
.SetLinePen(self
, pen
)
549 self
.dashpen
= wxPen(pen
.GetColour(), 1, wxDOT
)
551 def drawBox(self
, px
, py
, node
, dc
):
552 if self
.tree
.model
.IsLeaf(node
.data
) or ((node
.expanded
or not self
.tree
._assumeChildren
) and not len(node
.kids
)):
554 dc
.SetPen(self
.linepen
)
555 dc
.SetBrush(self
.bgbrush
)
556 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
557 self
.knobs
.append(node
, Rect(px
-4, py
-4, 9, 9))
558 dc
.SetPen(self
.textpen
)
559 if not node
.expanded
:
560 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
561 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
563 def paintWalk(self
, node
, dc
):
564 self
.linePainter
.paint(node
.parent
, node
, dc
)
565 self
.nodePainter
.paint(node
, dc
)
567 for kid
in node
.kids
:
568 if not self
.paintWalk(kid
, dc
):
570 for kid
in node
.kids
:
571 px
= (kid
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
572 py
= kid
.projy
+ kid
.height
/2
573 self
.drawBox(px
, py
, kid
, dc
)
574 if node
== self
.tree
.currentRoot
:
575 px
= (node
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
576 py
= node
.projy
+ node
.height
/2
577 self
.drawBox(px
, py
, node
, dc
)
580 def OnMouse(self
, evt
):
581 Painter
.OnMouse(self
, evt
)
583 class TreeNodePainter(NodePainter
):
584 def paint(self
, node
, dc
, location
= None):
585 text
= self
.painter
.textConverter
.convert(node
)
586 extent
= dc
.GetTextExtent(text
)
587 node
.width
= extent
[0]
588 node
.height
= extent
[1]
589 if node
== self
.painter
.tree
.currentRoot
:
590 self
.painter
.minx
= self
.painter
.maxx
= self
.painter
.miny
= self
.painter
.maxy
= 0
591 if node
.projx
< self
.painter
.minx
:
592 self
.painter
.minx
= node
.projx
593 elif node
.projx
+ node
.width
> self
.painter
.maxx
:
594 self
.painter
.maxx
= node
.projx
+ node
.width
595 if node
.projy
< self
.painter
.miny
:
596 self
.painter
.miny
= node
.projy
597 elif node
.projy
+ node
.height
> self
.painter
.maxy
:
598 self
.painter
.maxy
= node
.projy
+ node
.height
600 dc
.SetPen(self
.painter
.GetLinePen())
601 dc
.SetBrush(self
.painter
.GetForegroundBrush())
602 dc
.SetTextForeground(wxNamedColour("WHITE"))
603 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
605 dc
.SetTextForeground(self
.painter
.GetTextColour())
606 dc
.DrawText(text
, node
.projx
, node
.projy
)
607 self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
)))
609 class TreeLinePainter(LinePainter
):
610 def paint(self
, parent
, child
, dc
):
611 dc
.SetPen(self
.painter
.GetDashPen())
612 px
= py
= cx
= cy
= 0
613 if parent
is None or child
== self
.painter
.tree
.currentRoot
:
614 px
= (child
.projx
- self
.painter
.tree
.layout
.NODE_STEP
) + 5
615 py
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2
618 dc
.DrawLine(px
, py
, cx
, cy
)
620 px
= parent
.projx
+ 5
621 py
= parent
.projy
+ parent
.height
623 cy
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2
624 dc
.DrawLine(px
, py
, px
, cy
)
625 dc
.DrawLine(px
, cy
, cx
, cy
)
628 wxEVT_MVCTREE_BEGIN_EDIT
= 20204 #Start editing. Vetoable.
629 wxEVT_MVCTREE_END_EDIT
= 20205 #Stop editing. Vetoable.
630 wxEVT_MVCTREE_DELETE_ITEM
= 20206 #Item removed from model.
631 wxEVT_MVCTREE_ITEM_EXPANDED
= 20209
632 wxEVT_MVCTREE_ITEM_EXPANDING
= 20210
633 wxEVT_MVCTREE_ITEM_COLLAPSED
= 20211
634 wxEVT_MVCTREE_ITEM_COLLAPSING
= 20212
635 wxEVT_MVCTREE_SEL_CHANGED
= 20213
636 wxEVT_MVCTREE_SEL_CHANGING
= 20214 #Vetoable.
637 wxEVT_MVCTREE_KEY_DOWN
= 20215
638 wxEVT_MVCTREE_ADD_ITEM
= 20216 #Item added to model.
640 def EVT_MVCTREE_SEL_CHANGED(win
, id, func
):
641 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED
, func
)
643 def EVT_MVCTREE_SEL_CHANGING(win
, id, func
):
644 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING
, func
)
646 def EVT_MVCTREE_ITEM_EXPANDED(win
, id, func
):
647 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED
, func
)
649 def EVT_MVCTREE_ITEM_EXPANDING(win
, id, func
):
650 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING
, func
)
652 def EVT_MVCTREE_ITEM_COLLAPSED(win
, id, func
):
653 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED
, func
)
655 def EVT_MVCTREE_ITEM_COLLAPSING(win
, id, func
):
656 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING
, func
)
658 def EVT_MVCTREE_ADD_ITEM(win
, id, func
):
659 win
.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM
, func
)
661 def EVT_MVCTREE_DELETE_ITEM(win
, id, func
):
662 win
.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM
, func
)
664 def EVT_MVCTREE_KEY_DOWN(win
, id, func
):
665 win
.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN
, func
)
668 class wxMVCTreeEvent(wxPyCommandEvent
):
669 def __init__(self
, type, id, node
= None, nodes
= None, keyEvent
= None, **kwargs
):
670 apply(wxPyCommandEvent
.__init
__, (self
, type, id), kwargs
)
673 self
.keyEvent
= keyEvent
676 class wxMVCTreeNotifyEvent(wxMVCTreeEvent
):
677 def __init__(self
, type, id, node
= None, nodes
= None, **kwargs
):
678 apply(wxMVCTreeEvent
.__init
__, (self
, type, id), kwargs
)
679 self
.notify
= wxNotifyEvent(type, id)
681 class wxMVCTree(wxWindow
):
683 The main mvcTree class.
685 def __init__(self
, parent
, id, model
= None, layout
= None, transform
= None,
686 painter
= None, *args
, **kwargs
):
687 apply(wxWindow
.__init
__, (self
, parent
, id), kwargs
)
689 self
._multiselect
= false
690 self
._selections
= []
691 self
._assumeChildren
= false
692 self
._scrollx
= false
693 self
._scrolly
= false
694 self
.doubleBuffered
= true
697 model
= BasicTreeModel()
698 model
.SetRoot("Root")
701 layout
= TreeLayout(self
)
704 transform
= NullTransform(self
)
705 self
.transform
= transform
707 painter
= TreePainter(self
)
708 self
.painter
= painter
709 self
.SetFont(wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
))
710 EVT_MOUSE_EVENTS(self
, self
.OnMouse
)
711 EVT_SCROLLWIN(self
, self
.OnScroll
)
712 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
715 return "<wxMVCTree instance at %s>" % str(hex(id(self
)))
718 return self
.__repr
__()
720 def NodeAdded(self
, parent
, child
):
721 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
722 self
.GetEventHandler().ProcessEvent(e
)
724 def NodeInserted(self
, parent
, child
, index
):
725 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
726 self
.GetEventHandler().ProcessEvent(e
)
728 def NodeRemoved(self
, node
):
729 e
= wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
730 self
.GetEventHandler().ProcessEvent(e
)
732 def OnKeyDown(self
, evt
):
733 e
= wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent
= evt
)
734 self
.GetEventHandler().ProcessEvent(e
)
736 def SetFont(self
, font
):
737 self
.painter
.SetFont(font
)
738 dc
= wxClientDC(self
)
740 self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18)
743 return self
.painter
.GetFont()
745 def AddEditor(self
, editor
):
746 self
._editors
.append(editor
)
748 def RemoveEditor(self
, editor
):
749 self
._editors
.remove(editor
)
751 def OnMouse(self
, evt
):
752 self
.painter
.OnMouse(evt
)
754 def OnNodeClick(self
, node
, mouseEvent
):
756 self
.RemoveFromSelection(node
.data
)
758 self
.AddToSelection(node
.data
, mouseEvent
.ControlDown())
761 def OnKnobClick(self
, node
):
762 self
.SetExpanded(node
.data
, not node
.expanded
)
764 def GetDisplayText(self
, node
):
765 treenode
= self
.nodemap
[node
]
766 return self
.painter
.textConverter
.convert(treenode
)
768 def IsDoubleBuffered(self
):
769 return self
.doubleBuffered
771 def SetDoubleBuffered(self
, bool):
773 By default wxMVCTree is double-buffered.
775 self
.doubleBuffered
= bool
780 def SetModel(self
, model
):
782 Completely change the data to be displayed.
788 self
._selections
= []
789 self
.layoutRoot
= MVCTreeNode()
790 self
.layoutRoot
.data
= self
.model
.GetRoot()
791 self
.layoutRoot
.expanded
= true
792 self
.LoadChildren(self
.layoutRoot
)
793 self
.currentRoot
= self
.layoutRoot
798 def GetCurrentRoot(self
):
799 return self
.currentRoot
801 def LoadChildren(self
, layoutNode
):
805 self
.nodemap
[layoutNode
.data
]=layoutNode
806 for i
in range(self
.GetModel().GetChildCount(layoutNode
.data
)):
807 p
= MVCTreeNode("RAW", layoutNode
, [])
809 p
.data
= self
.GetModel().GetChildAt(layoutNode
.data
, i
)
810 self
.nodemap
[p
.data
]=p
811 layoutNode
.built
= true
812 if not self
._assumeChildren
:
813 for kid
in layoutNode
.kids
:
814 self
.LoadChildren(kid
)
816 def OnEraseBackground(self
, evt
):
819 def OnSize(self
, evt
):
821 size
= self
.GetSizeTuple()
822 self
.center
= (size
[0]/2, size
[1]/2)
827 def GetSelection(self
):
828 "Returns a tuple of selected nodes."
829 return tuple(self
._selections
)
831 def SetSelection(self
, nodeTuple
):
832 if type(nodeTuple
) != type(()):
833 nodeTuple
= (nodeTuple
,)
834 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
835 self
.GetEventHandler().ProcessEvent(e
)
836 if not e
.notify
.IsAllowed():
838 for node
in nodeTuple
:
839 treenode
= self
.nodemap
[node
]
840 treenode
.selected
= true
841 for node
in self
._selections
:
842 treenode
= self
.nodemap
[node
]
843 node
.selected
= false
844 self
._selections
= list(nodeTuple
)
845 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
846 self
.GetEventHandler().ProcessEvent(e
)
848 def IsMultiSelect(self
):
849 return self
._multiselect
851 def SetMultiSelect(self
, bool):
852 self
._multiselect
= bool
854 def IsSelected(self
, node
):
855 return self
.nodemap
[node
].selected
857 def Edit(self
, node
):
858 if not self
.model
.IsEditable(node
):
860 for ed
in self
._editors
:
862 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
)
863 self
.GetEventHandler().ProcessEvent(e
)
864 if not e
.notify
.IsAllowed():
867 self
._currentEditor
= ed
871 if self
._currentEditor
:
872 self
._currentEditor
.EndEdit
873 self
._currentEditor
= None
875 def _EditEnding(self
, node
):
876 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
)
877 self
.GetEventHandler().ProcessEvent(e
)
878 if not e
.notify
.IsAllowed():
880 self
._currentEditor
= None
884 def SetExpanded(self
, node
, bool):
885 treenode
= self
.nodemap
[node
]
887 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
)
888 self
.GetEventHandler().ProcessEvent(e
)
889 if not e
.notify
.IsAllowed():
891 if not treenode
.built
:
892 self
.LoadChildren(treenode
)
894 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
)
895 self
.GetEventHandler().ProcessEvent(e
)
896 if not e
.notify
.IsAllowed():
898 treenode
.expanded
= bool
900 if treenode
.expanded
:
901 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
)
903 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
)
904 self
.GetEventHandler().ProcessEvent(e
)
905 self
.layout
.layout(self
.currentRoot
)
906 self
.transform
.transform(self
.currentRoot
, self
.offset
, self
.rotation
)
909 def IsExpanded(self
, node
):
910 return self
.nodemap
[node
].expanded
912 def AddToSelection(self
, nodeOrTuple
, enableMulti
= true
):
913 nodeTuple
= nodeOrTuple
914 if type(nodeOrTuple
)!= type(()):
915 nodeTuple
= (nodeOrTuple
,)
916 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
917 self
.GetEventHandler().ProcessEvent(e
)
918 if not e
.notify
.IsAllowed():
920 if not self
.IsMultiSelect() or not enableMulti
:
921 for node
in self
._selections
:
922 treenode
= self
.nodemap
[node
]
923 treenode
.selected
= false
925 self
._selections
= [node
]
926 treenode
= self
.nodemap
[node
]
927 treenode
.selected
= true
929 for node
in nodeTuple
:
931 self
._selections
.index(node
)
933 self
._selections
.append(node
)
934 treenode
= self
.nodemap
[node
]
935 treenode
.selected
= true
936 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
937 self
.GetEventHandler().ProcessEvent(e
)
939 def RemoveFromSelection(self
, nodeTuple
):
940 if type(nodeTuple
) != type(()):
941 nodeTuple
= (nodeTuple
,)
942 for node
in nodeTuple
:
944 self
._selections
.index(node
)
946 self
._selections
.remove(node
)
947 treenode
= self
.nodemap
[node
]
948 node
.selected
= false
949 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes
= nodeTuple
)
950 self
.GetEventHandler().ProcessEvent(e
)
954 def GetBackgroundColour(self
):
955 if hasattr(self
, 'painter') and self
.painter
:
956 return self
.painter
.GetBackgroundColour()
958 return wxWindow
.GetBackgroundColour(self
)
959 def SetBackgroundColour(self
, color
):
960 if hasattr(self
, 'painter') and self
.painter
:
961 self
.painter
.SetBackgroundColour(color
)
963 wxWindow
.SetBackgroundColour(self
, color
)
964 def GetForegroundColour(self
):
965 if hasattr(self
, 'painter') and self
.painter
:
966 return self
.painter
.GetForegroundColour()
968 return wxWindow
.GetBackgroundColour(self
)
969 def SetForegroundColour(self
, color
):
970 if hasattr(self
, 'painter') and self
.painter
:
971 self
.painter
.SetForegroundColour(color
)
973 wxWindow
.SetBackgroundColour(self
, color
)
975 def SetAssumeChildren(self
, bool):
976 self
._assumeChildren
= bool
978 def GetAssumeChildren(self
):
979 return self
._assumeChildren
981 def OnScroll(self
, evt
):
982 type = evt
.GetEventType()
983 field
= [self
.painter
.maxx
- self
.painter
.minx
, self
.painter
.maxy
- self
.painter
.miny
]
984 size
= self
.GetSizeTuple()
986 if evt
.GetOrientation() == wxHORIZONTAL
:
992 if type == wxEVT_SCROLLWIN_TOP
:
993 self
.offset
[index
] = 0
994 elif type == wxEVT_SCROLLWIN_LINEUP
:
995 self
.offset
[index
] = self
.offset
[index
] + 1
996 elif type == wxEVT_SCROLLWIN_LINEDOWN
:
997 self
.offset
[index
] = self
.offset
[index
] - 1
998 elif type == wxEVT_SCROLLWIN_PAGEUP
:
999 self
.offset
[index
] = self
.offset
[index
] + int(20 * float(field
[index
])/float(size
[index
]))
1000 elif type == wxEVT_SCROLLWIN_PAGEDOWN
:
1001 self
.offset
[index
] = self
.offset
[index
] - int(20 * float(field
[index
])/float(size
[index
]))
1002 elif type == wxEVT_SCROLLWIN_THUMBTRACK
:
1003 self
.offset
[index
] = -(evt
.GetPosition())
1004 elif type == wxEVT_SCROLLWIN_BOTTOM
:
1005 self
.offset
[index
] = field
[index
]
1006 self
.transformed
= false
1009 def OnPaint(self
, evt
):
1011 Ensures that the tree has been laid out and transformed, then calls the painter
1012 to paint the control.
1015 if not self
.laidOut
:
1016 self
.layout
.layout(self
.currentRoot
)
1018 if not self
.transformed
:
1019 self
.transform
.transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1020 self
.transformed
= true
1021 dc
= wxPaintDC(self
)
1022 dc
.SetFont(self
.GetFont())
1023 if self
.doubleBuffered
:
1024 size
= self
.GetSize()
1025 if not hasattr(self
, 'bmp'):
1026 self
.bmp
= bmp
=wxEmptyBitmap(size
.width
, size
.height
)
1029 mem_dc
= wxMemoryDC()
1030 mem_dc
.SetFont(self
.GetFont())
1031 mem_dc
.SelectObject(bmp
)
1032 self
.painter
.paint(mem_dc
, self
.currentRoot
)
1033 dc
.Blit(0, 0, size
.width
, size
.height
, mem_dc
, 0, 0);
1035 self
.painter
.paint(dc
, self
.currentRoot
)
1036 size
= self
.GetSizeTuple()
1037 if self
._scrollx
or self
.painter
.minx
< 0 or self
.painter
.maxx
> size
[0]:
1038 field
= self
.painter
.maxx
- self
.painter
.minx
1039 self
.SetScrollbar(wxHORIZONTAL
, -self
.offset
[0], size
[0]/field
, field
, true
)
1040 self
._scrollx
= false
1041 if self
._scrolly
or self
.painter
.miny
< 0 or self
.painter
.maxy
> size
[1]:
1042 field
= self
.painter
.maxy
- self
.painter
.miny
1043 self
.SetScrollbar(wxVERTICAL
, -self
.offset
[1], size
[1]/field
, field
, true
)
1044 self
._scrolly
= false
1046 import traceback
;traceback
.print_exc()
1048 if __name__
== '__main__':
1050 import sys
;sys
.exit()
1054 def selchanging(evt
):
1055 print "SelChanging!"
1062 def selchanged(evt
):
1078 f
= wxFrame(NULL
, -1, "wxMVCTree")
1080 p
= wxMVCTree(f
, -1)
1081 p
.SetAssumeChildren(true
)
1082 if len(sys
.argv
) > 1:
1083 p
.SetModel(LateFSTreeModel(sys
.argv
[1]))
1084 p
.AddEditor(FileEditor(p
))
1085 p
.SetMultiSelect(true
)
1088 EVT_MVCTREE_SEL_CHANGED(p
, p
.GetId(), selchanged
)
1089 EVT_MVCTREE_SEL_CHANGING(p
, p
.GetId(), selchanging
)
1090 EVT_MVCTREE_ITEM_EXPANDED(p
, p
.GetId(), expanded
)
1091 EVT_MVCTREE_ITEM_COLLAPSED(p
, p
.GetId(), closed
)
1092 EVT_MVCTREE_ADD_ITEM(p
, p
.GetId(), add
)
1093 EVT_MVCTREE_DELETE_ITEM(p
, p
.GetId(), delitem
)
1094 EVT_MVCTREE_KEY_DOWN(p
, p
.GetId(), key
)
1095 p
.SetForegroundColour(wxNamedColour("GREEN"))
1096 self
.SetTopWindow(f
)