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
.NODE_HEIGHT
= self
.tree
.GetFont().GetPointSize() * 2
510 self
.layoutwalk(node
)
512 def GetNodeList(self
):
515 def layoutwalk(self
, node
):
516 if node
== self
.tree
.currentRoot
:
518 self
.lastY
= (-self
.NODE_HEIGHT
)
519 node
.x
= self
.NODE_STEP
* node
.level
520 node
.y
= self
.lastY
+ self
.NODE_HEIGHT
522 self
.nodelist
.append(node
)
524 for kid
in node
.kids
:
525 kid
.level
= node
.level
+ 1
528 class TreePainter(Painter
):
530 The default painter class. Uses double-buffering, delegates the painting of nodes and
531 lines to helper classes deriving from NodePainter and LinePainter.
533 def __init__(self
, tree
, nodePainter
= None, linePainter
= None, textConverter
= None):
534 Painter
.__init
__(self
, tree
)
536 nodePainter
= TreeNodePainter(self
)
537 self
.nodePainter
= nodePainter
539 linePainter
= TreeLinePainter(self
)
540 self
.linePainter
= linePainter
541 if not textConverter
:
542 textConverter
= StrTextConverter(self
)
543 self
.textConverter
= textConverter
546 def Paint(self
, dc
, node
, doubleBuffered
=1, paintBackground
=1):
547 if not self
.charWidths
:
550 self
.charWidths
.append(dc
.GetTextExtent("D")[0] * i
)
551 self
.charHeight
= dc
.GetTextExtent("D")[1]
552 self
.textpen
= wxPen(self
.GetTextColour(), 1, wxSOLID
)
553 self
.fgpen
= wxPen(self
.GetForegroundColour(), 1, wxSOLID
)
554 self
.bgpen
= wxPen(self
.GetBackgroundColour(), 1, wxSOLID
)
555 self
.linepen
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
556 self
.dashpen
= wxPen(self
.GetLineColour(), 1, wxDOT
)
557 self
.textbrush
= wxBrush(self
.GetTextColour(), wxSOLID
)
558 self
.fgbrush
= wxBrush(self
.GetForegroundColour(), wxSOLID
)
559 self
.bgbrush
= wxBrush(self
.GetBackgroundColour(), wxSOLID
)
560 self
.linebrush
= wxPen(self
.GetLineColour(), 1, wxSOLID
)
561 treesize
= self
.tree
.GetSize()
562 size
= self
.tree
.transform
.GetSize()
563 size
= (max(treesize
.width
, size
[0]+50), max(treesize
.height
, size
[1]+50))
566 mem_dc
= wxMemoryDC()
567 if not self
.GetBuffer():
570 self
.bmp
= wxEmptyBitmap(size
[0], size
[1])
571 mem_dc
.SelectObject(self
.GetBuffer())
572 mem_dc
.SetPen(self
.GetBackgroundPen())
573 mem_dc
.SetBrush(self
.GetBackgroundBrush())
574 mem_dc
.DrawRectangle(0, 0, size
[0], size
[1])
575 mem_dc
.SetFont(self
.tree
.GetFont())
576 self
.paintWalk(node
, mem_dc
)
578 mem_dc
.SelectObject(self
.GetBuffer())
579 xstart
, ystart
= self
.tree
.CalcUnscrolledPosition(0,0)
580 size
= self
.tree
.GetClientSizeTuple()
581 dc
.Blit(xstart
, ystart
, size
[0], size
[1], mem_dc
, xstart
, ystart
)
583 if node
== self
.tree
.currentRoot
:
586 dc
.SetPen(self
.GetBackgroundPen())
587 dc
.SetBrush(self
.GetBackgroundBrush())
588 dc
.SetFont(self
.tree
.GetFont())
590 dc
.DrawRectangle(0, 0, size
[0], size
[1])
592 #Call with not paintBackground because if we are told not to paint the
593 #whole background, we have to paint in parts to undo selection coloring.
595 self
.paintWalk(node
, dc
, not pb
)
598 def GetDashPen(self
):
601 def SetLinePen(self
, pen
):
602 Painter
.SetLinePen(self
, pen
)
603 self
.dashpen
= wxPen(pen
.GetColour(), 1, wxDOT
)
605 def paintWalk(self
, node
, dc
, paintRects
=0):
606 self
.linePainter
.Paint(node
.parent
, node
, dc
)
607 self
.nodePainter
.Paint(node
, dc
, drawRects
= paintRects
)
609 for kid
in node
.kids
:
610 if not self
.paintWalk(kid
, dc
, paintRects
):
612 for kid
in node
.kids
:
613 px
= (kid
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
614 py
= kid
.projy
+ kid
.height
/2
615 if (not self
.tree
.model
.IsLeaf(kid
.data
)) or ((kid
.expanded
or self
.tree
._assumeChildren
) and len(kid
.kids
)):
616 dc
.SetPen(self
.linepen
)
617 dc
.SetBrush(self
.bgbrush
)
618 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
619 self
.knobs
.append(kid
, Rect(px
-4, py
-4, 9, 9))
620 dc
.SetPen(self
.textpen
)
622 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
623 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
624 if node
== self
.tree
.currentRoot
:
625 px
= (node
.projx
- self
.tree
.layout
.NODE_STEP
) + 5
626 py
= node
.projy
+ node
.height
/2
627 dc
.SetPen(self
.linepen
)
628 dc
.SetBrush(self
.bgbrush
)
629 dc
.DrawRectangle(px
-4, py
-4, 9, 9)
630 self
.knobs
.append(node
, Rect(px
-4, py
-4, 9, 9))
631 dc
.SetPen(self
.textpen
)
632 if not node
.expanded
:
633 dc
.DrawLine(px
, py
-2, px
, py
+ 3)
634 dc
.DrawLine(px
-2, py
, px
+ 3, py
)
637 def OnMouse(self
, evt
):
638 Painter
.OnMouse(self
, evt
)
640 class TreeNodePainter(NodePainter
):
641 def Paint(self
, node
, dc
, location
= None, drawRects
= 0):
642 text
= self
.painter
.textConverter
.Convert(node
)
643 extent
= dc
.GetTextExtent(text
)
644 node
.width
= extent
[0]
645 node
.height
= extent
[1]
647 dc
.SetPen(self
.painter
.GetLinePen())
648 dc
.SetBrush(self
.painter
.GetForegroundBrush())
649 dc
.SetTextForeground(wxNamedColour("WHITE"))
650 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
653 dc
.SetBrush(self
.painter
.GetBackgroundBrush())
654 dc
.SetPen(self
.painter
.GetBackgroundPen())
655 dc
.DrawRectangle(node
.projx
-1, node
.projy
-1, node
.width
+ 3, node
.height
+ 3)
656 dc
.SetTextForeground(self
.painter
.GetTextColour())
657 dc
.DrawText(text
, node
.projx
, node
.projy
)
658 self
.painter
.rectangles
.append((node
, Rect(node
.projx
, node
.projy
, node
.width
, node
.height
)))
660 class TreeLinePainter(LinePainter
):
661 def Paint(self
, parent
, child
, dc
):
662 dc
.SetPen(self
.painter
.GetDashPen())
663 px
= py
= cx
= cy
= 0
664 if parent
is None or child
== self
.painter
.tree
.currentRoot
:
665 px
= (child
.projx
- self
.painter
.tree
.layout
.NODE_STEP
) + 5
666 py
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -2
669 dc
.DrawLine(px
, py
, cx
, cy
)
671 px
= parent
.projx
+ 5
672 py
= parent
.projy
+ parent
.height
674 cy
= child
.projy
+ self
.painter
.tree
.layout
.NODE_HEIGHT
/2 -3
675 dc
.DrawLine(px
, py
, px
, cy
)
676 dc
.DrawLine(px
, cy
, cx
, cy
)
679 wxEVT_MVCTREE_BEGIN_EDIT
= 20204 #Start editing. Vetoable.
680 wxEVT_MVCTREE_END_EDIT
= 20205 #Stop editing. Vetoable.
681 wxEVT_MVCTREE_DELETE_ITEM
= 20206 #Item removed from model.
682 wxEVT_MVCTREE_ITEM_EXPANDED
= 20209
683 wxEVT_MVCTREE_ITEM_EXPANDING
= 20210
684 wxEVT_MVCTREE_ITEM_COLLAPSED
= 20211
685 wxEVT_MVCTREE_ITEM_COLLAPSING
= 20212
686 wxEVT_MVCTREE_SEL_CHANGED
= 20213
687 wxEVT_MVCTREE_SEL_CHANGING
= 20214 #Vetoable.
688 wxEVT_MVCTREE_KEY_DOWN
= 20215
689 wxEVT_MVCTREE_ADD_ITEM
= 20216 #Item added to model.
691 def EVT_MVCTREE_SEL_CHANGED(win
, id, func
):
692 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED
, func
)
694 def EVT_MVCTREE_SEL_CHANGING(win
, id, func
):
695 win
.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING
, func
)
697 def EVT_MVCTREE_ITEM_EXPANDED(win
, id, func
):
698 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED
, func
)
700 def EVT_MVCTREE_ITEM_EXPANDING(win
, id, func
):
701 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING
, func
)
703 def EVT_MVCTREE_ITEM_COLLAPSED(win
, id, func
):
704 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED
, func
)
706 def EVT_MVCTREE_ITEM_COLLAPSING(win
, id, func
):
707 win
.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING
, func
)
709 def EVT_MVCTREE_ADD_ITEM(win
, id, func
):
710 win
.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM
, func
)
712 def EVT_MVCTREE_DELETE_ITEM(win
, id, func
):
713 win
.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM
, func
)
715 def EVT_MVCTREE_KEY_DOWN(win
, id, func
):
716 win
.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN
, func
)
719 class wxMVCTreeEvent(wxPyCommandEvent
):
720 def __init__(self
, type, id, node
= None, nodes
= None, keyEvent
= None, **kwargs
):
721 apply(wxPyCommandEvent
.__init
__, (self
, type, id), kwargs
)
724 self
.keyEvent
= keyEvent
729 def getKeyEvent(self
):
732 class wxMVCTreeNotifyEvent(wxMVCTreeEvent
):
733 def __init__(self
, type, id, node
= None, nodes
= None, **kwargs
):
734 apply(wxMVCTreeEvent
.__init
__, (self
, type, id), kwargs
)
735 self
.notify
= wxNotifyEvent(type, id)
736 def getNotifyEvent(self
):
739 class wxMVCTree(wxScrolledWindow
):
741 The main mvc tree class.
743 def __init__(self
, parent
, id, model
= None, layout
= None, transform
= None,
744 painter
= None, *args
, **kwargs
):
745 apply(wxScrolledWindow
.__init
__, (self
, parent
, id), kwargs
)
747 self
._multiselect
= false
748 self
._selections
= []
749 self
._assumeChildren
= false
750 self
._scrollx
= false
751 self
._scrolly
= false
752 self
.doubleBuffered
= false
753 self
._lastPhysicalSize
= self
.GetSize()
756 model
= BasicTreeModel()
757 model
.SetRoot("Root")
760 layout
= TreeLayout(self
)
763 transform
= NullTransform(self
)
764 self
.transform
= transform
766 painter
= TreePainter(self
)
767 self
.painter
= painter
768 self
.SetFont(wxFont(9, wxDEFAULT
, wxNORMAL
, wxNORMAL
, false
))
769 EVT_MOUSE_EVENTS(self
, self
.OnMouse
)
770 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
771 self
.doubleBuffered
= true
775 if self
.doubleBuffered
:
776 self
.painter
.ClearBuffer()
777 wxScrolledWindow
.Refresh(self
, false
)
779 def GetPainter(self
):
782 def GetLayoutEngine(self
):
785 def GetTransform(self
):
786 return self
.transform
789 return "<wxMVCTree instance at %s>" % str(hex(id(self
)))
792 return self
.__repr
__()
794 def NodeAdded(self
, parent
, child
):
795 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
796 self
.GetEventHandler().ProcessEvent(e
)
797 self
.painter
.ClearBuffer()
799 def NodeInserted(self
, parent
, child
, index
):
800 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
801 self
.GetEventHandler().ProcessEvent(e
)
802 self
.painter
.ClearBuffer()
804 def NodeRemoved(self
, node
):
805 e
= wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM
, self
.GetId(), node
= child
, nodes
= [parent
, child
])
806 self
.GetEventHandler().ProcessEvent(e
)
807 self
.painter
.ClearBuffer()
809 def OnKeyDown(self
, evt
):
810 e
= wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN
, self
.GetId(), keyEvent
= evt
)
811 self
.GetEventHandler().ProcessEvent(e
)
813 def SetFont(self
, font
):
814 self
.painter
.SetFont(font
)
815 dc
= wxClientDC(self
)
817 self
.layout
.SetHeight(dc
.GetTextExtent("")[1] + 18)
818 self
.painter
.ClearBuffer()
821 return self
.painter
.GetFont()
823 def AddEditor(self
, editor
):
824 self
._editors
.append(editor
)
826 def RemoveEditor(self
, editor
):
827 self
._editors
.remove(editor
)
829 def OnMouse(self
, evt
):
830 self
.painter
.OnMouse(evt
)
832 def OnNodeClick(self
, node
, mouseEvent
):
833 if node
.selected
and (self
.IsMultiSelect() and mouseEvent
.ControlDown()):
834 self
.RemoveFromSelection(node
.data
)
836 self
.AddToSelection(node
.data
, mouseEvent
.ControlDown(), mouseEvent
.ShiftDown())
838 def OnKnobClick(self
, node
):
839 self
.SetExpanded(node
.data
, not node
.expanded
)
841 def GetDisplayText(self
, node
):
842 treenode
= self
.nodemap
[node
]
843 return self
.painter
.textConverter
.Convert(treenode
)
845 def IsDoubleBuffered(self
):
846 return self
.doubleBuffered
848 def SetDoubleBuffered(self
, bool):
850 By default wxMVCTree is double-buffered.
852 self
.doubleBuffered
= bool
857 def SetModel(self
, model
):
859 Completely change the data to be displayed.
865 self
._selections
= []
866 self
.layoutRoot
= MVCTreeNode()
867 self
.layoutRoot
.data
= self
.model
.GetRoot()
868 self
.layoutRoot
.expanded
= true
869 self
.LoadChildren(self
.layoutRoot
)
870 self
.currentRoot
= self
.layoutRoot
873 self
._scrollset
= None
876 def GetCurrentRoot(self
):
877 return self
.currentRoot
879 def LoadChildren(self
, layoutNode
):
883 self
.nodemap
[layoutNode
.data
]=layoutNode
884 for i
in range(self
.GetModel().GetChildCount(layoutNode
.data
)):
885 p
= MVCTreeNode("RAW", layoutNode
, [])
887 p
.data
= self
.GetModel().GetChildAt(layoutNode
.data
, i
)
888 self
.nodemap
[p
.data
]=p
889 layoutNode
.built
= true
890 if not self
._assumeChildren
:
891 for kid
in layoutNode
.kids
:
892 self
.LoadChildren(kid
)
894 def OnEraseBackground(self
, evt
):
897 def OnSize(self
, evt
):
898 size
= self
.GetSize()
899 self
.center
= (size
.width
/2, size
.height
/2)
900 if self
._lastPhysicalSize
.width
< size
.width
or self
._lastPhysicalSize
.height
< size
.height
:
901 self
.painter
.ClearBuffer()
902 self
._lastPhysicalSize
= size
904 def GetSelection(self
):
905 "Returns a tuple of selected nodes."
906 return tuple(self
._selections
)
908 def SetSelection(self
, nodeTuple
):
909 if type(nodeTuple
) != type(()):
910 nodeTuple
= (nodeTuple
,)
911 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
912 self
.GetEventHandler().ProcessEvent(e
)
913 if not e
.notify
.IsAllowed():
915 for node
in nodeTuple
:
916 treenode
= self
.nodemap
[node
]
917 treenode
.selected
= true
918 for node
in self
._selections
:
919 treenode
= self
.nodemap
[node
]
920 node
.selected
= false
921 self
._selections
= list(nodeTuple
)
922 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
923 self
.GetEventHandler().ProcessEvent(e
)
925 def IsMultiSelect(self
):
926 return self
._multiselect
928 def SetMultiSelect(self
, bool):
929 self
._multiselect
= bool
931 def IsSelected(self
, node
):
932 return self
.nodemap
[node
].selected
934 def Edit(self
, node
):
935 if not self
.model
.IsEditable(node
):
937 for ed
in self
._editors
:
939 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT
, self
.GetId(), node
)
940 self
.GetEventHandler().ProcessEvent(e
)
941 if not e
.notify
.IsAllowed():
944 self
._currentEditor
= ed
948 if self
._currentEditor
:
949 self
._currentEditor
.EndEdit
950 self
._currentEditor
= None
952 def _EditEnding(self
, node
):
953 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT
, self
.GetId(), node
)
954 self
.GetEventHandler().ProcessEvent(e
)
955 if not e
.notify
.IsAllowed():
957 self
._currentEditor
= None
961 def SetExpanded(self
, node
, bool):
962 treenode
= self
.nodemap
[node
]
964 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING
, self
.GetId(), node
)
965 self
.GetEventHandler().ProcessEvent(e
)
966 if not e
.notify
.IsAllowed():
968 if not treenode
.built
:
969 self
.LoadChildren(treenode
)
971 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING
, self
.GetId(), node
)
972 self
.GetEventHandler().ProcessEvent(e
)
973 if not e
.notify
.IsAllowed():
975 treenode
.expanded
= bool
977 if treenode
.expanded
:
978 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED
, self
.GetId(), node
)
980 e
= wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED
, self
.GetId(), node
)
981 self
.GetEventHandler().ProcessEvent(e
)
982 self
.layout
.Layout(self
.currentRoot
)
983 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
986 def IsExpanded(self
, node
):
987 return self
.nodemap
[node
].expanded
989 def AddToSelection(self
, nodeOrTuple
, enableMulti
= true
, shiftMulti
= false
):
990 nodeTuple
= nodeOrTuple
991 if type(nodeOrTuple
)!= type(()):
992 nodeTuple
= (nodeOrTuple
,)
993 e
= wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
994 self
.GetEventHandler().ProcessEvent(e
)
995 if not e
.notify
.IsAllowed():
998 if not (self
.IsMultiSelect() and (enableMulti
or shiftMulti
)):
999 for node
in self
._selections
:
1000 treenode
= self
.nodemap
[node
]
1001 treenode
.selected
= false
1002 changeparents
.append(treenode
)
1004 self
._selections
= [node
]
1005 treenode
= self
.nodemap
[node
]
1006 changeparents
.append(treenode
)
1007 treenode
.selected
= true
1010 for node
in nodeTuple
:
1011 treenode
= self
.nodemap
[node
]
1012 oldtreenode
= self
.nodemap
[self
._selections
[0]]
1013 if treenode
.parent
== oldtreenode
.parent
:
1015 for kid
in oldtreenode
.parent
.kids
:
1016 if kid
== treenode
or kid
== oldtreenode
:
1019 self
._selections
.append(kid
.data
)
1020 changeparents
.append(kid
)
1023 self
._selections
.append(kid
.data
)
1024 changeparents
.append(kid
)
1026 for node
in nodeTuple
:
1028 self
._selections
.index(node
)
1030 self
._selections
.append(node
)
1031 treenode
= self
.nodemap
[node
]
1032 treenode
.selected
= true
1033 changeparents
.append(treenode
)
1034 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), nodeTuple
[0], nodes
= nodeTuple
)
1035 self
.GetEventHandler().ProcessEvent(e
)
1036 dc
= wxClientDC(self
)
1038 for node
in changeparents
:
1040 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1041 self
.painter
.ClearBuffer()
1043 def RemoveFromSelection(self
, nodeTuple
):
1044 if type(nodeTuple
) != type(()):
1045 nodeTuple
= (nodeTuple
,)
1047 for node
in nodeTuple
:
1048 self
._selections
.remove(node
)
1049 treenode
= self
.nodemap
[node
]
1050 changeparents
.append(treenode
)
1051 treenode
.selected
= false
1052 e
= wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED
, self
.GetId(), node
, nodes
= nodeTuple
)
1053 self
.GetEventHandler().ProcessEvent(e
)
1054 dc
= wxClientDC(self
)
1056 for node
in changeparents
:
1058 self
.painter
.Paint(dc
, node
, doubleBuffered
= 0, paintBackground
= 0)
1059 self
.painter
.ClearBuffer()
1062 def GetBackgroundColour(self
):
1063 if hasattr(self
, 'painter') and self
.painter
:
1064 return self
.painter
.GetBackgroundColour()
1066 return wxWindow
.GetBackgroundColour(self
)
1067 def SetBackgroundColour(self
, color
):
1068 if hasattr(self
, 'painter') and self
.painter
:
1069 self
.painter
.SetBackgroundColour(color
)
1071 wxWindow
.SetBackgroundColour(self
, color
)
1072 def GetForegroundColour(self
):
1073 if hasattr(self
, 'painter') and self
.painter
:
1074 return self
.painter
.GetForegroundColour()
1076 return wxWindow
.GetBackgroundColour(self
)
1077 def SetForegroundColour(self
, color
):
1078 if hasattr(self
, 'painter') and self
.painter
:
1079 self
.painter
.SetForegroundColour(color
)
1081 wxWindow
.SetBackgroundColour(self
, color
)
1083 def SetAssumeChildren(self
, bool):
1084 self
._assumeChildren
= bool
1086 def GetAssumeChildren(self
):
1087 return self
._assumeChildren
1089 def OnPaint(self
, evt
):
1091 Ensures that the tree has been laid out and transformed, then calls the painter
1092 to paint the control.
1095 self
.EnableScrolling(false
, false
)
1096 if not self
.laidOut
:
1097 self
.layout
.Layout(self
.currentRoot
)
1099 self
.transformed
= false
1100 if not self
.transformed
:
1101 self
.transform
.Transform(self
.currentRoot
, self
.offset
, self
.rotation
)
1102 self
.transformed
= true
1104 tsize
= list(self
.transform
.GetSize())
1105 tsize
[0] = tsize
[0] + 50
1106 tsize
[1] = tsize
[1] + 50
1107 size
= self
.GetSizeTuple()
1108 if tsize
[0] > size
[0] or tsize
[1] > size
[1]:
1109 if not hasattr(self
, '_oldsize') or (tsize
[0] > self
._oldsize
[0] or tsize
[1] > self
._oldsize
[1]):
1110 self
._oldsize
= tsize
1111 oldstart
= self
.ViewStart()
1112 self
._lastPhysicalSize
= self
.GetSize()
1113 self
.SetScrollbars(10, 10, tsize
[0]/10, tsize
[1]/10)
1114 self
.Scroll(oldstart
[0], oldstart
[1])
1115 dc
= wxPaintDC(self
)
1117 dc
.SetFont(self
.GetFont())
1118 self
.painter
.Paint(dc
, self
.currentRoot
, self
.doubleBuffered
)
1120 traceback
.print_exc()