]> git.saurik.com Git - wxWidgets.git/blob - utils/wxPython/lib/mvctree.py
updated wxMVCTree, VTK, and the demo
[wxWidgets.git] / utils / wxPython / lib / mvctree.py
1 """
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
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
9 in the GUI.
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.
19
20 Author/Maintainer - Bryn Keller <xoltar@starship.python.net>
21 """
22
23 #------------------------------------------------------------------------
24 from wxPython.wx import *
25 import os, sys, traceback
26 #------------------------------------------------------------------------
27
28 class MVCTreeNode:
29 """
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.
33 """
34 def __init__(self, data=None, parent = None, kids = [], x = 0, y = 0):
35 self.x = 0
36 self.y = 0
37 self.projx = 0
38 self.projy = 0
39 self.parent = parent
40 self.kids = kids
41 self.data = data
42 self.expanded = false
43 self.selected = false
44 self.built = false
45 self.scale = 0
46
47 def GetChildren(self):
48 return self.kids
49
50 def GetParent(self):
51 return self.parent
52
53 def Remove(self, node):
54 try:
55 self.kids.remove(node)
56 except:
57 pass
58 def Add(self, node):
59 self.kids.append(node)
60 node.SetParent(self)
61
62 def SetParent(self, parent):
63 if self.parent and not (self.parent is parent):
64 self.parent.Remove(self)
65 self.parent = parent
66 def __str__(self):
67 return "Node: " + str(self.data) + " (" + str(self.x) + ", " + str(self.y) + ")"
68 def __repr__(self):
69 return str(self.data)
70 def GetTreeString(self, tabs=0):
71 s = tabs * '\t' + str(self) + '\n'
72 for kid in self.kids:
73 s = s + kid.GetTreeString(tabs + 1)
74 return s
75
76
77 class Editor:
78 def __init__(self, tree):
79 self.tree = tree
80 def Edit(self, node):
81 raise NotImplementedError
82 def EndEdit(self, node, commit):
83 raise NotImplementedError
84 def CanEdit(self, node):
85 raise NotImplementedError
86
87 class LayoutEngine:
88 """
89 Interface for layout engines.
90 """
91 def __init__(self, tree):
92 self.tree = tree
93 def layout(self, node):
94 raise NotImplementedError
95 def GetNodeList(self):
96 raise NotImplementedError
97
98 class Transform:
99 """
100 Transform interface.
101 """
102 def __init__(self, tree):
103 self.tree = tree
104 def transform(self, node, offset, rotation):
105 """
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
109 should cause havoc.
110 """
111 raise NotImplementedError
112
113 def GetSize(self):
114 """
115 Returns the size of the entire tree as laid out and transformed
116 as a tuple
117 """
118 raise NotImplementedError
119
120 class Painter:
121 """
122 This is the interface that wxMVCTree expects from painters. All painters should
123 be Painter subclasses.
124 """
125 def __init__(self, tree):
126 self.tree = 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)
132 self.bmp = None
133
134 def GetFont(self):
135 return self.font
136
137 def SetFont(self, font):
138 self.font = font
139 self.tree.Refresh()
140 def GetBuffer(self):
141 return self.bmp
142 def ClearBuffer(self):
143 self.bmp = None
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):
153 return self.bgcolor
154 def SetBackgroundColour(self, color):
155 self.bgcolor = color
156 self.bgbrush = wxBrush(color)
157 self.bgpen = wxPen(color, 1, wxSOLID)
158 def GetForegroundColour(self):
159 return self.fgcolor
160 def SetForegroundColour(self, color):
161 self.fgcolor = 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):
171 return self.fgpen
172 def GetBackgroundPen(self):
173 return self.bgpen
174 def GetTextPen(self):
175 return self.textpen
176 def GetForegroundBrush(self):
177 return self.fgbrush
178 def GetBackgroundBrush(self):
179 return self.bgbrush
180 def GetTextBrush(self):
181 return self.textbrush
182 def GetLinePen(self):
183 return self.linepen
184 def GetLineBrush(self):
185 return self.linebrush
186 def OnMouse(self, evt):
187 if evt.LeftDClick():
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)
193 return
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)
199 return
200 for item in self.knobs:
201 if item[1].contains((x, y)):
202 self.tree.OnKnobClick(item[0])
203 return
204 evt.Skip()
205
206
207 class wxTreeModel:
208 """
209 Interface for tree models
210 """
211 def GetRoot(self):
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
232
233 def IsEditable(self, node):
234 return false
235
236 def SetEditable(self, node):
237 return false
238
239 class NodePainter:
240 """
241 This is the interface expected of a nodepainter.
242 """
243 def __init__(self, painter):
244 self.painter = painter
245 def paint(self, node, dc, location = None):
246 """
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.
250 """
251 raise NotImplementedError
252
253 class LinePainter:
254 """
255 The linepainter interface.
256 """
257 def __init__(self, painter):
258 self.painter = painter
259 def paint(self, parent, child, dc):
260 raise NotImplementedError
261
262 class TextConverter:
263 """
264 TextConverter interface.
265 """
266 def __init__(self, painter):
267 self.painter = painter
268 def convert(node):
269 """
270 Should return a string. The node argument will be an
271 MVCTreeNode.
272 """
273 raise NotImplementedError
274
275
276 class BasicTreeModel(wxTreeModel):
277 """
278 A very simple treemodel implementation, but flexible enough for many needs.
279 """
280 def __init__(self):
281 self.children = {}
282 self.parents = {}
283 self.root = None
284 def GetRoot(self):
285 return self.root
286 def SetRoot(self, root):
287 self.root = root
288 def GetChildCount(self, node):
289 if self.children.has_key(node):
290 return len(self.children[node])
291 else:
292 return 0
293 def GetChildAt(self, node, index):
294 return self.children[node][index]
295
296 def GetParent(self, node):
297 return self.parents[node]
298
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)
305 return child
306
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)
312
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)
319 return child
320
321 def IsLeaf(self, node):
322 return not self.children.has_key(node)
323
324 def IsEditable(self, node):
325 return false
326
327 def SetEditable(self, node, bool):
328 return false
329
330
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]))
338 break
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()
347
348 def CanEdit(self, node):
349 return isinstance(node, FileWrapper)
350
351 def EndEdit(self, commit):
352 if not self.tree._EditEnding(self.treenode.data):
353 return
354 if commit:
355 node = self.treenode.data
356 try:
357 os.rename(node.path + os.sep + node.fileName, node.path + os.sep + self.editcomp.GetValue())
358 node.fileName = self.editcomp.GetValue()
359 except:
360 traceback.print_exc()
361 self.editcomp.ReleaseMouse()
362 self.editcomp.Destroy()
363 del self.editcomp
364 self.tree.Refresh()
365
366
367 def _key(self, evt):
368 if evt.KeyCode() == WXK_RETURN:
369 self.EndEdit(true)
370 elif evt.KeyCode() == WXK_ESCAPE:
371 self.EndEdit(false)
372 else:
373 evt.Skip()
374
375 def _mdown(self, evt):
376 if evt.IsButton():
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:
380 self.EndEdit(false)
381
382
383 class FileWrapper:
384 """
385 Node class for FSTreeModel.
386 """
387 def __init__(self, path, fileName):
388 self.path = path
389 self.fileName = fileName
390
391 def __str__(self):
392 return self.fileName
393
394 class FSTreeModel(BasicTreeModel):
395 """
396 This treemodel models the filesystem starting from a given path.
397 """
398 def __init__(self, path):
399 BasicTreeModel.__init__(self)
400 import string
401 fw = FileWrapper(path, string.split(path, os.sep)[-1])
402 self._Build(path, fw)
403 self.SetRoot(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)
412
413 def IsEditable(self, node):
414 return self._editable
415
416 def SetEditable(self, node, bool):
417 self._editable = bool
418
419 class LateFSTreeModel(FSTreeModel):
420 """
421 This treemodel models the filesystem starting from a given path.
422 It retrieves the directory list as requested.
423 """
424 def __init__(self, path):
425 BasicTreeModel.__init__(self)
426 import string
427 name = string.split(path, os.sep)[-1]
428 pathpart = path[:-len(name)]
429 fw = FileWrapper(pathpart, name)
430 self._Build(path, fw)
431 self.SetRoot(fw)
432 self._editable = true
433 self.children = {}
434 self.parents = {}
435 def _Build(self, path, parent):
436 ppath = parent.path + os.sep + parent.fileName
437 if not os.path.isdir(ppath):
438 return
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)
445 else:
446 self._Build(node.path, node)
447 return FSTreeModel.GetChildCount(self, node)
448
449 def IsLeaf(self, node):
450 return not os.path.isdir(node.path + os.sep + node.fileName)
451
452 class StrTextConverter(TextConverter):
453 def convert(self, node):
454 return str(node.data)
455
456 class NullTransform(Transform):
457 def GetSize(self):
458 return tuple(self.size)
459
460 def transform(self, node, offset, rotation):
461 self.size = [0,0]
462 list = self.tree.GetLayoutEngine().GetNodeList()
463 for node in list:
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
470
471 class Rect:
472 def __init__(self, x, y, width, height):
473 self.x = x
474 self.y = y
475 self.width = width
476 self.height = height
477 def __getitem__(self, index):
478 return (self.x, self.y, self.width, self.height)[index]
479
480 def __setitem__(self, index, value):
481 name = ['x', 'y', 'width', 'height'][index]
482 setattr(self, name, value)
483
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:
491 return true
492 return false
493
494 def __str__(self):
495 return "Rect: " + str([self.x, self.y, self.width, self.height])
496
497 class TreeLayout(LayoutEngine):
498 def SetHeight(self, num):
499 self.NODE_HEIGHT = num
500
501 def __init__(self, tree):
502 LayoutEngine.__init__(self, tree)
503 self.NODE_STEP = 20
504 self.NODE_HEIGHT = 20
505 self.nodelist = []
506
507 def layout(self, node):
508 self.nodelist = []
509 self.layoutwalk(node)
510
511 def GetNodeList(self):
512 return self.nodelist
513
514 def layoutwalk(self, node):
515 if node == self.tree.currentRoot:
516 node.level = 1
517 self.lastY = (-self.NODE_HEIGHT)
518 node.x = self.NODE_STEP * node.level
519 node.y = self.lastY + self.NODE_HEIGHT
520 self.lastY = node.y
521 self.nodelist.append(node)
522 if node.expanded:
523 for kid in node.kids:
524 kid.level = node.level + 1
525 self.layoutwalk(kid)
526
527 class TreePainter(Painter):
528 """
529 The default painter class. Uses double-buffering, delegates the painting of nodes and
530 lines to helper classes deriving from NodePainter and LinePainter.
531 """
532 def __init__(self, tree, nodePainter = None, linePainter = None, textConverter = None):
533 Painter.__init__(self, tree)
534 if not nodePainter:
535 nodePainter = TreeNodePainter(self)
536 self.nodePainter = nodePainter
537 if not linePainter:
538 linePainter = TreeLinePainter(self)
539 self.linePainter = linePainter
540 if not textConverter:
541 textConverter = StrTextConverter(self)
542 self.textConverter = textConverter
543 self.charWidths = []
544
545 def paint(self, dc, node, doubleBuffered=1, paintBackground=1):
546 if not self.charWidths:
547 self.charWidths = []
548 for i in range(25):
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))
563 dc.BeginDrawing()
564 if doubleBuffered:
565 mem_dc = wxMemoryDC()
566 if not self.GetBuffer():
567 self.knobs = []
568 self.rectangles = []
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)
576 else:
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)
581 else:
582 if node == self.tree.currentRoot:
583 self.knobs = []
584 self.rectangles = []
585 dc.SetPen(self.GetBackgroundPen())
586 dc.SetBrush(self.GetBackgroundBrush())
587 dc.SetFont(self.tree.GetFont())
588 if paintBackground:
589 dc.DrawRectangle(0, 0, size[0], size[1])
590 if node:
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.
593 pb = paintBackground
594 self.paintWalk(node, dc, not pb)
595 dc.EndDrawing()
596
597 def GetDashPen(self):
598 return self.dashpen
599
600 def SetLinePen(self, pen):
601 Painter.SetLinePen(self, pen)
602 self.dashpen = wxPen(pen.GetColour(), 1, wxDOT)
603
604 def paintWalk(self, node, dc, paintRects=0):
605 self.linePainter.paint(node.parent, node, dc)
606 self.nodePainter.paint(node, dc, drawRects = paintRects)
607 if node.expanded:
608 for kid in node.kids:
609 if not self.paintWalk(kid, dc, paintRects):
610 return false
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)
620 if not kid.expanded:
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)
634 return true
635
636 def OnMouse(self, evt):
637 Painter.OnMouse(self, evt)
638
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]
645 if node.selected:
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)
650 else:
651 if drawRects:
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)))
658
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
666 cx = child.projx
667 cy = py
668 dc.DrawLine(px, py, cx, cy)
669 else:
670 px = parent.projx + 5
671 py = parent.projy + parent.height
672 cx = child.projx -5
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)
676
677
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.
689
690 def EVT_MVCTREE_SEL_CHANGED(win, id, func):
691 win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED, func)
692
693 def EVT_MVCTREE_SEL_CHANGING(win, id, func):
694 win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING, func)
695
696 def EVT_MVCTREE_ITEM_EXPANDED(win, id, func):
697 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED, func)
698
699 def EVT_MVCTREE_ITEM_EXPANDING(win, id, func):
700 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING, func)
701
702 def EVT_MVCTREE_ITEM_COLLAPSED(win, id, func):
703 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED, func)
704
705 def EVT_MVCTREE_ITEM_COLLAPSING(win, id, func):
706 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING, func)
707
708 def EVT_MVCTREE_ADD_ITEM(win, id, func):
709 win.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM, func)
710
711 def EVT_MVCTREE_DELETE_ITEM(win, id, func):
712 win.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM, func)
713
714 def EVT_MVCTREE_KEY_DOWN(win, id, func):
715 win.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN, func)
716
717
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)
721 self.node = node
722 self.nodes = nodes
723 self.keyEvent = keyEvent
724 def GetNode(self):
725 return self.node
726 def GetNodes(self):
727 return self.nodes
728 def getKeyEvent(self):
729 return self.keyEvent
730
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):
736 return self.notify
737
738 class wxMVCTree(wxScrolledWindow):
739 """
740 The main mvc tree class.
741 """
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)
745 self.nodemap = {}
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()
753 self._editors = []
754 if not model:
755 model = BasicTreeModel()
756 model.SetRoot("Root")
757 self.SetModel(model)
758 if not layout:
759 layout = TreeLayout(self)
760 self.layout = layout
761 if not transform:
762 transform = NullTransform(self)
763 self.transform = transform
764 if not painter:
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
771
772 def Refresh(self):
773 if self.doubleBuffered:
774 self.painter.ClearBuffer()
775 wxScrolledWindow.Refresh(self)
776
777 def GetPainter(self):
778 return self.painter
779
780 def GetLayoutEngine(self):
781 return self.layout
782
783 def GetTransform(self):
784 return self.transform
785
786 def __repr__(self):
787 return "<wxMVCTree instance at %s>" % str(hex(id(self)))
788
789 def __str__(self):
790 return self.__repr__()
791
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()
796
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)
808
809 def SetFont(self, font):
810 self.painter.SetFont(font)
811 dc = wxClientDC(self)
812 dc.SetFont(font)
813 self.layout.SetHeight(dc.GetTextExtent("")[1] + 18)
814 self.painter.ClearBuffer()
815 def GetFont(self):
816 return self.painter.GetFont()
817
818 def AddEditor(self, editor):
819 self._editors.append(editor)
820
821 def RemoveEditor(self, editor):
822 self._editors.remove(editor)
823
824 def OnMouse(self, evt):
825 self.painter.OnMouse(evt)
826
827 def OnNodeClick(self, node, mouseEvent):
828 if node.selected and (self.IsMultiSelect() and mouseEvent.ControlDown()):
829 self.RemoveFromSelection(node.data)
830 else:
831 self.AddToSelection(node.data, mouseEvent.ControlDown(), mouseEvent.ShiftDown())
832
833 def OnKnobClick(self, node):
834 self.SetExpanded(node.data, not node.expanded)
835
836 def GetDisplayText(self, node):
837 treenode = self.nodemap[node]
838 return self.painter.textConverter.convert(treenode)
839
840 def IsDoubleBuffered(self):
841 return self.doubleBuffered
842
843 def SetDoubleBuffered(self, bool):
844 """
845 By default wxMVCTree is double-buffered.
846 """
847 self.doubleBuffered = bool
848
849 def GetModel(self):
850 return self.model
851
852 def SetModel(self, model):
853 """
854 Completely change the data to be displayed.
855 """
856 self.model = model
857 model.tree = self
858 self.laidOut = 0
859 self.transformed = 0
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
866 self.offset = [0,0]
867 self.rotation = 0
868 self._scrollset = None
869 self.Refresh()
870
871 def GetCurrentRoot(self):
872 return self.currentRoot
873
874 def LoadChildren(self, layoutNode):
875 if layoutNode.built:
876 return
877 else:
878 self.nodemap[layoutNode.data]=layoutNode
879 for i in range(self.GetModel().GetChildCount(layoutNode.data)):
880 p = MVCTreeNode("RAW", layoutNode, [])
881 layoutNode.Add(p)
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)
888
889 def OnEraseBackground(self, evt):
890 pass
891
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
898
899 def GetSelection(self):
900 "Returns a tuple of selected nodes."
901 return tuple(self._selections)
902
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():
909 return
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)
919
920 def IsMultiSelect(self):
921 return self._multiselect
922
923 def SetMultiSelect(self, bool):
924 self._multiselect = bool
925
926 def IsSelected(self, node):
927 return self.nodemap[node].selected
928
929 def Edit(self, node):
930 if not self.model.IsEditable(node):
931 return
932 for ed in self._editors:
933 if ed.CanEdit(node):
934 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT, self.GetId(), node)
935 self.GetEventHandler().ProcessEvent(e)
936 if not e.notify.IsAllowed():
937 return
938 ed.Edit(node)
939 self._currentEditor = ed
940 break
941
942 def EndEdit(self):
943 if self._currentEditor:
944 self._currentEditor.EndEdit
945 self._currentEditor = None
946
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():
951 return false
952 self._currentEditor = None
953 return true
954
955
956 def SetExpanded(self, node, bool):
957 treenode = self.nodemap[node]
958 if bool:
959 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING, self.GetId(), node)
960 self.GetEventHandler().ProcessEvent(e)
961 if not e.notify.IsAllowed():
962 return
963 if not treenode.built:
964 self.LoadChildren(treenode)
965 else:
966 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING, self.GetId(), node)
967 self.GetEventHandler().ProcessEvent(e)
968 if not e.notify.IsAllowed():
969 return
970 treenode.expanded = bool
971 e = None
972 if treenode.expanded:
973 e = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED, self.GetId(), node)
974 else:
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)
979 self.Refresh()
980
981 def IsExpanded(self, node):
982 return self.nodemap[node].expanded
983
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():
991 return
992 changeparents = []
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)
998 node = nodeTuple[0]
999 self._selections = [node]
1000 treenode = self.nodemap[node]
1001 changeparents.append(treenode)
1002 treenode.selected = true
1003 else:
1004 if shiftMulti:
1005 for node in nodeTuple:
1006 treenode = self.nodemap[node]
1007 oldtreenode = self.nodemap[self._selections[0]]
1008 if treenode.parent == oldtreenode.parent:
1009 found = 0
1010 for kid in oldtreenode.parent.kids:
1011 if kid == treenode or kid == oldtreenode:
1012 found = not found
1013 kid.selected = true
1014 self._selections.append(kid.data)
1015 changeparents.append(kid)
1016 elif found:
1017 kid.selected = true
1018 self._selections.append(kid.data)
1019 changeparents.append(kid)
1020 else:
1021 for node in nodeTuple:
1022 try:
1023 self._selections.index(node)
1024 except ValueError:
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)
1032 self.PrepareDC(dc)
1033 for node in changeparents:
1034 if node:
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,)
1040 changeparents = []
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)
1049 self.PrepareDC(dc)
1050 for node in changeparents:
1051 if node:
1052 self.painter.paint(dc, node, doubleBuffered = 0, paintBackground = 0)
1053 self.painter.ClearBuffer()
1054
1055
1056 def GetBackgroundColour(self):
1057 if hasattr(self, 'painter') and self.painter:
1058 return self.painter.GetBackgroundColour()
1059 else:
1060 return wxWindow.GetBackgroundColour(self)
1061 def SetBackgroundColour(self, color):
1062 if hasattr(self, 'painter') and self.painter:
1063 self.painter.SetBackgroundColour(color)
1064 else:
1065 wxWindow.SetBackgroundColour(self, color)
1066 def GetForegroundColour(self):
1067 if hasattr(self, 'painter') and self.painter:
1068 return self.painter.GetForegroundColour()
1069 else:
1070 return wxWindow.GetBackgroundColour(self)
1071 def SetForegroundColour(self, color):
1072 if hasattr(self, 'painter') and self.painter:
1073 self.painter.SetForegroundColour(color)
1074 else:
1075 wxWindow.SetBackgroundColour(self, color)
1076
1077 def SetAssumeChildren(self, bool):
1078 self._assumeChildren = bool
1079
1080 def GetAssumeChildren(self):
1081 return self._assumeChildren
1082
1083 def OnPaint(self, evt):
1084 """
1085 Ensures that the tree has been laid out and transformed, then calls the painter
1086 to paint the control.
1087 """
1088 try:
1089 self.EnableScrolling(false, false)
1090 if not self.laidOut:
1091 self.layout.layout(self.currentRoot)
1092 self.laidOut = true
1093 self.transformed = false
1094 if not self.transformed:
1095 self.transform.transform(self.currentRoot, self.offset, self.rotation)
1096 self.transformed = true
1097 tsize = None
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)
1110 self.PrepareDC(dc)
1111 dc.SetFont(self.GetFont())
1112 self.painter.paint(dc, self.currentRoot, self.doubleBuffered)
1113 except:
1114 traceback.print_exc()
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126