]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/mvctree.py
removed second parameter from GetFirstChild calls
[wxWidgets.git] / wxPython / wx / 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 NOTE: This module is *not* supported in any way. Use it however you
24 wish, but be warned that dealing with any consequences is
25 entirly up to you.
26 --Robin
27 """
28
29 #------------------------------------------------------------------------
30 from wxPython.wx import *
31 import os, sys, traceback
32 #------------------------------------------------------------------------
33
34 class MVCTreeNode:
35 """
36 Used internally by wxMVCTree to manage its data. Contains information about
37 screen placement, the actual data associated with it, and more. These are
38 the nodes passed to all the other helper parts to do their work with.
39 """
40 def __init__(self, data=None, parent = None, kids = None, x = 0, y = 0):
41 self.x = 0
42 self.y = 0
43 self.projx = 0
44 self.projy = 0
45 self.parent = parent
46 self.kids = kids
47 if self.kids is None:
48 self.kids = []
49 self.data = data
50 self.expanded = False
51 self.selected = False
52 self.built = False
53 self.scale = 0
54
55 def GetChildren(self):
56 return self.kids
57
58 def GetParent(self):
59 return self.parent
60
61 def Remove(self, node):
62 try:
63 self.kids.remove(node)
64 except:
65 pass
66 def Add(self, node):
67 self.kids.append(node)
68 node.SetParent(self)
69
70 def SetParent(self, parent):
71 if self.parent and not (self.parent is parent):
72 self.parent.Remove(self)
73 self.parent = parent
74 def __str__(self):
75 return "Node: " + str(self.data) + " (" + str(self.x) + ", " + str(self.y) + ")"
76 def __repr__(self):
77 return str(self.data)
78 def GetTreeString(self, tabs=0):
79 s = tabs * '\t' + str(self) + '\n'
80 for kid in self.kids:
81 s = s + kid.GetTreeString(tabs + 1)
82 return s
83
84
85 class Editor:
86 def __init__(self, tree):
87 self.tree = tree
88 def Edit(self, node):
89 raise NotImplementedError
90 def EndEdit(self, node, commit):
91 raise NotImplementedError
92 def CanEdit(self, node):
93 raise NotImplementedError
94
95 class LayoutEngine:
96 """
97 Interface for layout engines.
98 """
99 def __init__(self, tree):
100 self.tree = tree
101 def Layout(self, node):
102 raise NotImplementedError
103 def GetNodeList(self):
104 raise NotImplementedError
105
106 class Transform:
107 """
108 Transform interface.
109 """
110 def __init__(self, tree):
111 self.tree = tree
112 def Transform(self, node, offset, rotation):
113 """
114 This method should only change the projx and projy attributes of
115 the node. These represent the position of the node as it should
116 be drawn on screen. Adjusting the x and y attributes can and
117 should cause havoc.
118 """
119 raise NotImplementedError
120
121 def GetSize(self):
122 """
123 Returns the size of the entire tree as laid out and transformed
124 as a tuple
125 """
126 raise NotImplementedError
127
128 class Painter:
129 """
130 This is the interface that wxMVCTree expects from painters. All painters should
131 be Painter subclasses.
132 """
133 def __init__(self, tree):
134 self.tree = tree
135 self.textcolor = wxNamedColour("BLACK")
136 self.bgcolor = wxNamedColour("WHITE")
137 self.fgcolor = wxNamedColour("BLUE")
138 self.linecolor = wxNamedColour("GREY")
139 self.font = wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, False)
140 self.bmp = None
141
142 def GetFont(self):
143 return self.font
144
145 def SetFont(self, font):
146 self.font = font
147 self.tree.Refresh()
148 def GetBuffer(self):
149 return self.bmp
150 def ClearBuffer(self):
151 self.bmp = None
152 def Paint(self, dc, node, doubleBuffered=1, paintBackground=1):
153 raise NotImplementedError
154 def GetTextColour(self):
155 return self.textcolor
156 def SetTextColour(self, color):
157 self.textcolor = color
158 self.textbrush = wxBrush(color)
159 self.textpen = wxPen(color, 1, wxSOLID)
160 def GetBackgroundColour(self):
161 return self.bgcolor
162 def SetBackgroundColour(self, color):
163 self.bgcolor = color
164 self.bgbrush = wxBrush(color)
165 self.bgpen = wxPen(color, 1, wxSOLID)
166 def GetForegroundColour(self):
167 return self.fgcolor
168 def SetForegroundColour(self, color):
169 self.fgcolor = color
170 self.fgbrush = wxBrush(color)
171 self.fgpen = wxPen(color, 1, wxSOLID)
172 def GetLineColour(self):
173 return self.linecolor
174 def SetLineColour(self, color):
175 self.linecolor = color
176 self.linebrush = wxBrush(color)
177 self.linepen = wxPen( color, 1, wxSOLID)
178 def GetForegroundPen(self):
179 return self.fgpen
180 def GetBackgroundPen(self):
181 return self.bgpen
182 def GetTextPen(self):
183 return self.textpen
184 def GetForegroundBrush(self):
185 return self.fgbrush
186 def GetBackgroundBrush(self):
187 return self.bgbrush
188 def GetTextBrush(self):
189 return self.textbrush
190 def GetLinePen(self):
191 return self.linepen
192 def GetLineBrush(self):
193 return self.linebrush
194 def OnMouse(self, evt):
195 if evt.LeftDClick():
196 x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
197 for item in self.rectangles:
198 if item[1].Contains((x,y)):
199 self.tree.Edit(item[0].data)
200 self.tree.OnNodeClick(item[0], evt)
201 return
202 elif evt.ButtonDown():
203 x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
204 for item in self.rectangles:
205 if item[1].Contains((x, y)):
206 self.tree.OnNodeClick(item[0], evt)
207 return
208 for item in self.knobs:
209 if item[1].Contains((x, y)):
210 self.tree.OnKnobClick(item[0])
211 return
212 evt.Skip()
213
214
215 class wxTreeModel:
216 """
217 Interface for tree models
218 """
219 def GetRoot(self):
220 raise NotImplementedError
221 def SetRoot(self, root):
222 raise NotImplementedError
223 def GetChildCount(self, node):
224 raise NotImplementedError
225 def GetChildAt(self, node, index):
226 raise NotImplementedError
227 def GetParent(self, node):
228 raise NotImplementedError
229 def AddChild(self, parent, child):
230 if hasattr(self, 'tree') and self.tree:
231 self.tree.NodeAdded(parent, child)
232 def RemoveNode(self, child):
233 if hasattr(self, 'tree') and self.tree:
234 self.tree.NodeRemoved(child)
235 def InsertChild(self, parent, child, index):
236 if hasattr(self, 'tree') and self.tree:
237 self.tree.NodeInserted(parent, child, index)
238 def IsLeaf(self, node):
239 raise NotImplementedError
240
241 def IsEditable(self, node):
242 return False
243
244 def SetEditable(self, node):
245 return False
246
247 class NodePainter:
248 """
249 This is the interface expected of a nodepainter.
250 """
251 def __init__(self, painter):
252 self.painter = painter
253 def Paint(self, node, dc, location = None):
254 """
255 location should be provided only to draw in an unusual position
256 (not the node's normal position), otherwise the node's projected x and y
257 coordinates will be used.
258 """
259 raise NotImplementedError
260
261 class LinePainter:
262 """
263 The linepainter interface.
264 """
265 def __init__(self, painter):
266 self.painter = painter
267 def Paint(self, parent, child, dc):
268 raise NotImplementedError
269
270 class TextConverter:
271 """
272 TextConverter interface.
273 """
274 def __init__(self, painter):
275 self.painter = painter
276 def Convert(node):
277 """
278 Should return a string. The node argument will be an
279 MVCTreeNode.
280 """
281 raise NotImplementedError
282
283
284 class BasicTreeModel(wxTreeModel):
285 """
286 A very simple treemodel implementation, but flexible enough for many needs.
287 """
288 def __init__(self):
289 self.children = {}
290 self.parents = {}
291 self.root = None
292 def GetRoot(self):
293 return self.root
294 def SetRoot(self, root):
295 self.root = root
296 def GetChildCount(self, node):
297 if self.children.has_key(node):
298 return len(self.children[node])
299 else:
300 return 0
301 def GetChildAt(self, node, index):
302 return self.children[node][index]
303
304 def GetParent(self, node):
305 return self.parents[node]
306
307 def AddChild(self, parent, child):
308 self.parents[child]=parent
309 if not self.children.has_key(parent):
310 self.children[parent]=[]
311 self.children[parent].append(child)
312 wxTreeModel.AddChild(self, parent, child)
313 return child
314
315 def RemoveNode(self, node):
316 parent = self.parents[node]
317 del self.parents[node]
318 self.children[parent].remove(node)
319 wxTreeModel.RemoveNode(self, node)
320
321 def InsertChild(self, parent, child, index):
322 self.parents[child]=parent
323 if not self.children.has_key(parent):
324 self.children[parent]=[]
325 self.children[parent].insert(child, index)
326 wxTreeModel.InsertChild(self, parent, child, index)
327 return child
328
329 def IsLeaf(self, node):
330 return not self.children.has_key(node)
331
332 def IsEditable(self, node):
333 return False
334
335 def SetEditable(self, node, bool):
336 return False
337
338
339 class FileEditor(Editor):
340 def Edit(self, node):
341 treenode = self.tree.nodemap[node]
342 self.editcomp = wxTextCtrl(self.tree, -1)
343 for rect in self.tree.painter.rectangles:
344 if rect[0] == treenode:
345 self.editcomp.SetPosition((rect[1][0], rect[1][1]))
346 break
347 self.editcomp.SetValue(node.fileName)
348 self.editcomp.SetSelection(0, len(node.fileName))
349 self.editcomp.SetFocus()
350 self.treenode = treenode
351 # EVT_KEY_DOWN(self.editcomp, self._key)
352 EVT_KEY_UP(self.editcomp, self._key)
353 EVT_LEFT_DOWN(self.editcomp, self._mdown)
354 self.editcomp.CaptureMouse()
355
356 def CanEdit(self, node):
357 return isinstance(node, FileWrapper)
358
359 def EndEdit(self, commit):
360 if not self.tree._EditEnding(self.treenode.data):
361 return
362 if commit:
363 node = self.treenode.data
364 try:
365 os.rename(node.path + os.sep + node.fileName, node.path + os.sep + self.editcomp.GetValue())
366 node.fileName = self.editcomp.GetValue()
367 except:
368 traceback.print_exc()
369 self.editcomp.ReleaseMouse()
370 self.editcomp.Destroy()
371 del self.editcomp
372 self.tree.Refresh()
373
374
375 def _key(self, evt):
376 if evt.KeyCode() == WXK_RETURN:
377 self.EndEdit(True)
378 elif evt.KeyCode() == WXK_ESCAPE:
379 self.EndEdit(False)
380 else:
381 evt.Skip()
382
383 def _mdown(self, evt):
384 if evt.IsButton():
385 pos = evt.GetPosition()
386 edsize = self.editcomp.GetSize()
387 if pos.x < 0 or pos.y < 0 or pos.x > edsize.width or pos.y > edsize.height:
388 self.EndEdit(False)
389
390
391 class FileWrapper:
392 """
393 Node class for FSTreeModel.
394 """
395 def __init__(self, path, fileName):
396 self.path = path
397 self.fileName = fileName
398
399 def __str__(self):
400 return self.fileName
401
402 class FSTreeModel(BasicTreeModel):
403 """
404 This treemodel models the filesystem starting from a given path.
405 """
406 def __init__(self, path):
407 BasicTreeModel.__init__(self)
408 fw = FileWrapper(path, path.split(os.sep)[-1])
409 self._Build(path, fw)
410 self.SetRoot(fw)
411 self._editable = True
412 def _Build(self, path, fileWrapper):
413 for name in os.listdir(path):
414 fw = FileWrapper(path, name)
415 self.AddChild(fileWrapper, fw)
416 childName = path + os.sep + name
417 if os.path.isdir(childName):
418 self._Build(childName, fw)
419
420 def IsEditable(self, node):
421 return self._editable
422
423 def SetEditable(self, node, bool):
424 self._editable = bool
425
426 class LateFSTreeModel(FSTreeModel):
427 """
428 This treemodel models the filesystem starting from a given path.
429 It retrieves the directory list as requested.
430 """
431 def __init__(self, path):
432 BasicTreeModel.__init__(self)
433 name = path.split(os.sep)[-1]
434 pathpart = path[:-len(name)]
435 fw = FileWrapper(pathpart, name)
436 self._Build(path, fw)
437 self.SetRoot(fw)
438 self._editable = True
439 self.children = {}
440 self.parents = {}
441 def _Build(self, path, parent):
442 ppath = parent.path + os.sep + parent.fileName
443 if not os.path.isdir(ppath):
444 return
445 for name in os.listdir(ppath):
446 fw = FileWrapper(ppath, name)
447 self.AddChild(parent, fw)
448 def GetChildCount(self, node):
449 if self.children.has_key(node):
450 return FSTreeModel.GetChildCount(self, node)
451 else:
452 self._Build(node.path, node)
453 return FSTreeModel.GetChildCount(self, node)
454
455 def IsLeaf(self, node):
456 return not os.path.isdir(node.path + os.sep + node.fileName)
457
458 class StrTextConverter(TextConverter):
459 def Convert(self, node):
460 return str(node.data)
461
462 class NullTransform(Transform):
463 def GetSize(self):
464 return tuple(self.size)
465
466 def Transform(self, node, offset, rotation):
467 self.size = [0,0]
468 list = self.tree.GetLayoutEngine().GetNodeList()
469 for node in list:
470 node.projx = node.x + offset[0]
471 node.projy = node.y + offset[1]
472 if node.projx > self.size[0]:
473 self.size[0] = node.projx
474 if node.projy > self.size[1]:
475 self.size[1] = node.projy
476
477 class Rect:
478 def __init__(self, x, y, width, height):
479 self.x = x
480 self.y = y
481 self.width = width
482 self.height = height
483 def __getitem__(self, index):
484 return (self.x, self.y, self.width, self.height)[index]
485
486 def __setitem__(self, index, value):
487 name = ['x', 'y', 'width', 'height'][index]
488 setattr(self, name, value)
489
490 def Contains(self, other):
491 if type(other) == type(()):
492 other = Rect(other[0], other[1], 0, 0)
493 if other.x >= self.x:
494 if other.y >= self.y:
495 if other.width + other.x <= self.width + self.x:
496 if other.height + other.y <= self.height + self.y:
497 return True
498 return False
499
500 def __str__(self):
501 return "Rect: " + str([self.x, self.y, self.width, self.height])
502
503 class TreeLayout(LayoutEngine):
504 def SetHeight(self, num):
505 self.NODE_HEIGHT = num
506
507 def __init__(self, tree):
508 LayoutEngine.__init__(self, tree)
509 self.NODE_STEP = 20
510 self.NODE_HEIGHT = 20
511 self.nodelist = []
512
513 def Layout(self, node):
514 self.nodelist = []
515 self.NODE_HEIGHT = self.tree.GetFont().GetPointSize() * 2
516 self.layoutwalk(node)
517
518 def GetNodeList(self):
519 return self.nodelist
520
521 def layoutwalk(self, node):
522 if node == self.tree.currentRoot:
523 node.level = 1
524 self.lastY = (-self.NODE_HEIGHT)
525 node.x = self.NODE_STEP * node.level
526 node.y = self.lastY + self.NODE_HEIGHT
527 self.lastY = node.y
528 self.nodelist.append(node)
529 if node.expanded:
530 for kid in node.kids:
531 kid.level = node.level + 1
532 self.layoutwalk(kid)
533
534 class TreePainter(Painter):
535 """
536 The default painter class. Uses double-buffering, delegates the painting of nodes and
537 lines to helper classes deriving from NodePainter and LinePainter.
538 """
539 def __init__(self, tree, nodePainter = None, linePainter = None, textConverter = None):
540 Painter.__init__(self, tree)
541 if not nodePainter:
542 nodePainter = TreeNodePainter(self)
543 self.nodePainter = nodePainter
544 if not linePainter:
545 linePainter = TreeLinePainter(self)
546 self.linePainter = linePainter
547 if not textConverter:
548 textConverter = StrTextConverter(self)
549 self.textConverter = textConverter
550 self.charWidths = []
551
552 def Paint(self, dc, node, doubleBuffered=1, paintBackground=1):
553 if not self.charWidths:
554 self.charWidths = []
555 for i in range(25):
556 self.charWidths.append(dc.GetTextExtent("D")[0] * i)
557 self.charHeight = dc.GetTextExtent("D")[1]
558 self.textpen = wxPen(self.GetTextColour(), 1, wxSOLID)
559 self.fgpen = wxPen(self.GetForegroundColour(), 1, wxSOLID)
560 self.bgpen = wxPen(self.GetBackgroundColour(), 1, wxSOLID)
561 self.linepen = wxPen(self.GetLineColour(), 1, wxSOLID)
562 self.dashpen = wxPen(self.GetLineColour(), 1, wxDOT)
563 self.textbrush = wxBrush(self.GetTextColour(), wxSOLID)
564 self.fgbrush = wxBrush(self.GetForegroundColour(), wxSOLID)
565 self.bgbrush = wxBrush(self.GetBackgroundColour(), wxSOLID)
566 self.linebrush = wxPen(self.GetLineColour(), 1, wxSOLID)
567 treesize = self.tree.GetSize()
568 size = self.tree.transform.GetSize()
569 size = (max(treesize.width, size[0]+50), max(treesize.height, size[1]+50))
570 dc.BeginDrawing()
571 if doubleBuffered:
572 mem_dc = wxMemoryDC()
573 if not self.GetBuffer():
574 self.knobs = []
575 self.rectangles = []
576 self.bmp = wxEmptyBitmap(size[0], size[1])
577 mem_dc.SelectObject(self.GetBuffer())
578 mem_dc.SetPen(self.GetBackgroundPen())
579 mem_dc.SetBrush(self.GetBackgroundBrush())
580 mem_dc.DrawRectangle(0, 0, size[0], size[1])
581 mem_dc.SetFont(self.tree.GetFont())
582 self.paintWalk(node, mem_dc)
583 else:
584 mem_dc.SelectObject(self.GetBuffer())
585 xstart, ystart = self.tree.CalcUnscrolledPosition(0,0)
586 size = self.tree.GetClientSizeTuple()
587 dc.Blit(xstart, ystart, size[0], size[1], mem_dc, xstart, ystart)
588 else:
589 if node == self.tree.currentRoot:
590 self.knobs = []
591 self.rectangles = []
592 dc.SetPen(self.GetBackgroundPen())
593 dc.SetBrush(self.GetBackgroundBrush())
594 dc.SetFont(self.tree.GetFont())
595 if paintBackground:
596 dc.DrawRectangle(0, 0, size[0], size[1])
597 if node:
598 #Call with not paintBackground because if we are told not to paint the
599 #whole background, we have to paint in parts to undo selection coloring.
600 pb = paintBackground
601 self.paintWalk(node, dc, not pb)
602 dc.EndDrawing()
603
604 def GetDashPen(self):
605 return self.dashpen
606
607 def SetLinePen(self, pen):
608 Painter.SetLinePen(self, pen)
609 self.dashpen = wxPen(pen.GetColour(), 1, wxDOT)
610
611 def paintWalk(self, node, dc, paintRects=0):
612 self.linePainter.Paint(node.parent, node, dc)
613 self.nodePainter.Paint(node, dc, drawRects = paintRects)
614 if node.expanded:
615 for kid in node.kids:
616 if not self.paintWalk(kid, dc, paintRects):
617 return False
618 for kid in node.kids:
619 px = (kid.projx - self.tree.layout.NODE_STEP) + 5
620 py = kid.projy + kid.height/2
621 if (not self.tree.model.IsLeaf(kid.data)) or ((kid.expanded or self.tree._assumeChildren) and len(kid.kids)):
622 dc.SetPen(self.linepen)
623 dc.SetBrush(self.bgbrush)
624 dc.DrawRectangle(px -4, py-4, 9, 9)
625 self.knobs.append( (kid, Rect(px -4, py -4, 9, 9)) )
626 dc.SetPen(self.textpen)
627 if not kid.expanded:
628 dc.DrawLine(px, py -2, px, py + 3)
629 dc.DrawLine(px -2, py, px + 3, py)
630 if node == self.tree.currentRoot:
631 px = (node.projx - self.tree.layout.NODE_STEP) + 5
632 py = node.projy + node.height/2
633 dc.SetPen(self.linepen)
634 dc.SetBrush(self.bgbrush)
635 dc.DrawRectangle(px -4, py-4, 9, 9)
636 self.knobs.append( (node, Rect(px -4, py -4, 9, 9)) )
637 dc.SetPen(self.textpen)
638 if not node.expanded:
639 dc.DrawLine(px, py -2, px, py + 3)
640 dc.DrawLine(px -2, py, px + 3, py)
641 return True
642
643 def OnMouse(self, evt):
644 Painter.OnMouse(self, evt)
645
646 class TreeNodePainter(NodePainter):
647 def Paint(self, node, dc, location = None, drawRects = 0):
648 text = self.painter.textConverter.Convert(node)
649 extent = dc.GetTextExtent(text)
650 node.width = extent[0]
651 node.height = extent[1]
652 if node.selected:
653 dc.SetPen(self.painter.GetLinePen())
654 dc.SetBrush(self.painter.GetForegroundBrush())
655 dc.SetTextForeground(wxNamedColour("WHITE"))
656 dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3)
657 else:
658 if drawRects:
659 dc.SetBrush(self.painter.GetBackgroundBrush())
660 dc.SetPen(self.painter.GetBackgroundPen())
661 dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3)
662 dc.SetTextForeground(self.painter.GetTextColour())
663 dc.DrawText(text, node.projx, node.projy)
664 self.painter.rectangles.append((node, Rect(node.projx, node.projy, node.width, node.height)))
665
666 class TreeLinePainter(LinePainter):
667 def Paint(self, parent, child, dc):
668 dc.SetPen(self.painter.GetDashPen())
669 px = py = cx = cy = 0
670 if parent is None or child == self.painter.tree.currentRoot:
671 px = (child.projx - self.painter.tree.layout.NODE_STEP) + 5
672 py = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -2
673 cx = child.projx
674 cy = py
675 dc.DrawLine(px, py, cx, cy)
676 else:
677 px = parent.projx + 5
678 py = parent.projy + parent.height
679 cx = child.projx -5
680 cy = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -3
681 dc.DrawLine(px, py, px, cy)
682 dc.DrawLine(px, cy, cx, cy)
683
684
685 wxEVT_MVCTREE_BEGIN_EDIT = 20204 #Start editing. Vetoable.
686 wxEVT_MVCTREE_END_EDIT = 20205 #Stop editing. Vetoable.
687 wxEVT_MVCTREE_DELETE_ITEM = 20206 #Item removed from model.
688 wxEVT_MVCTREE_ITEM_EXPANDED = 20209
689 wxEVT_MVCTREE_ITEM_EXPANDING = 20210
690 wxEVT_MVCTREE_ITEM_COLLAPSED = 20211
691 wxEVT_MVCTREE_ITEM_COLLAPSING = 20212
692 wxEVT_MVCTREE_SEL_CHANGED = 20213
693 wxEVT_MVCTREE_SEL_CHANGING = 20214 #Vetoable.
694 wxEVT_MVCTREE_KEY_DOWN = 20215
695 wxEVT_MVCTREE_ADD_ITEM = 20216 #Item added to model.
696
697 def EVT_MVCTREE_SEL_CHANGED(win, id, func):
698 win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED, func)
699
700 def EVT_MVCTREE_SEL_CHANGING(win, id, func):
701 win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING, func)
702
703 def EVT_MVCTREE_ITEM_EXPANDED(win, id, func):
704 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED, func)
705
706 def EVT_MVCTREE_ITEM_EXPANDING(win, id, func):
707 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING, func)
708
709 def EVT_MVCTREE_ITEM_COLLAPSED(win, id, func):
710 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED, func)
711
712 def EVT_MVCTREE_ITEM_COLLAPSING(win, id, func):
713 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING, func)
714
715 def EVT_MVCTREE_ADD_ITEM(win, id, func):
716 win.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM, func)
717
718 def EVT_MVCTREE_DELETE_ITEM(win, id, func):
719 win.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM, func)
720
721 def EVT_MVCTREE_KEY_DOWN(win, id, func):
722 win.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN, func)
723
724
725 class wxMVCTreeEvent(wxPyCommandEvent):
726 def __init__(self, type, id, node = None, nodes = None, keyEvent = None, **kwargs):
727 apply(wxPyCommandEvent.__init__, (self, type, id), kwargs)
728 self.node = node
729 self.nodes = nodes
730 self.keyEvent = keyEvent
731 def GetNode(self):
732 return self.node
733 def GetNodes(self):
734 return self.nodes
735 def getKeyEvent(self):
736 return self.keyEvent
737
738 class wxMVCTreeNotifyEvent(wxMVCTreeEvent):
739 def __init__(self, type, id, node = None, nodes = None, **kwargs):
740 apply(wxMVCTreeEvent.__init__, (self, type, id, node, nodes), kwargs)
741 self.notify = wxNotifyEvent(type, id)
742 def getNotifyEvent(self):
743 return self.notify
744
745 class wxMVCTree(wxScrolledWindow):
746 """
747 The main mvc tree class.
748 """
749 def __init__(self, parent, id, model = None, layout = None, transform = None,
750 painter = None, *args, **kwargs):
751 apply(wxScrolledWindow.__init__, (self, parent, id), kwargs)
752 self.nodemap = {}
753 self._multiselect = False
754 self._selections = []
755 self._assumeChildren = False
756 self._scrollx = False
757 self._scrolly = False
758 self.doubleBuffered = False
759 self._lastPhysicalSize = self.GetSize()
760 self._editors = []
761 if not model:
762 model = BasicTreeModel()
763 model.SetRoot("Root")
764 self.SetModel(model)
765 if not layout:
766 layout = TreeLayout(self)
767 self.layout = layout
768 if not transform:
769 transform = NullTransform(self)
770 self.transform = transform
771 if not painter:
772 painter = TreePainter(self)
773 self.painter = painter
774 self.SetFont(wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, False))
775 EVT_MOUSE_EVENTS(self, self.OnMouse)
776 EVT_KEY_DOWN(self, self.OnKeyDown)
777 self.doubleBuffered = True
778 EVT_SIZE(self, self.OnSize)
779 EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
780 EVT_PAINT(self, self.OnPaint)
781
782
783 def Refresh(self):
784 if self.doubleBuffered:
785 self.painter.ClearBuffer()
786 wxScrolledWindow.Refresh(self, False)
787
788 def GetPainter(self):
789 return self.painter
790
791 def GetLayoutEngine(self):
792 return self.layout
793
794 def GetTransform(self):
795 return self.transform
796
797 def __repr__(self):
798 return "<wxMVCTree instance at %s>" % str(hex(id(self)))
799
800 def __str__(self):
801 return self.__repr__()
802
803 def NodeAdded(self, parent, child):
804 e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child])
805 self.GetEventHandler().ProcessEvent(e)
806 self.painter.ClearBuffer()
807
808 def NodeInserted(self, parent, child, index):
809 e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child])
810 self.GetEventHandler().ProcessEvent(e)
811 self.painter.ClearBuffer()
812
813 def NodeRemoved(self, node):
814 e = wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM, self.GetId(), node = child, nodes = [parent, child])
815 self.GetEventHandler().ProcessEvent(e)
816 self.painter.ClearBuffer()
817
818 def OnKeyDown(self, evt):
819 e = wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN, self.GetId(), keyEvent = evt)
820 self.GetEventHandler().ProcessEvent(e)
821
822 def SetFont(self, font):
823 self.painter.SetFont(font)
824 dc = wxClientDC(self)
825 dc.SetFont(font)
826 self.layout.SetHeight(dc.GetTextExtent("")[1] + 18)
827 self.painter.ClearBuffer()
828
829 def GetFont(self):
830 return self.painter.GetFont()
831
832 def AddEditor(self, editor):
833 self._editors.append(editor)
834
835 def RemoveEditor(self, editor):
836 self._editors.remove(editor)
837
838 def OnMouse(self, evt):
839 self.painter.OnMouse(evt)
840
841 def OnNodeClick(self, node, mouseEvent):
842 if node.selected and (self.IsMultiSelect() and mouseEvent.ControlDown()):
843 self.RemoveFromSelection(node.data)
844 else:
845 self.AddToSelection(node.data, mouseEvent.ControlDown(), mouseEvent.ShiftDown())
846
847 def OnKnobClick(self, node):
848 self.SetExpanded(node.data, not node.expanded)
849
850 def GetDisplayText(self, node):
851 treenode = self.nodemap[node]
852 return self.painter.textConverter.Convert(treenode)
853
854 def IsDoubleBuffered(self):
855 return self.doubleBuffered
856
857 def SetDoubleBuffered(self, bool):
858 """
859 By default wxMVCTree is double-buffered.
860 """
861 self.doubleBuffered = bool
862
863 def GetModel(self):
864 return self.model
865
866 def SetModel(self, model):
867 """
868 Completely change the data to be displayed.
869 """
870 self.model = model
871 model.tree = self
872 self.laidOut = 0
873 self.transformed = 0
874 self._selections = []
875 self.layoutRoot = MVCTreeNode()
876 self.layoutRoot.data = self.model.GetRoot()
877 self.layoutRoot.expanded = True
878 self.LoadChildren(self.layoutRoot)
879 self.currentRoot = self.layoutRoot
880 self.offset = [0,0]
881 self.rotation = 0
882 self._scrollset = None
883 self.Refresh()
884
885 def GetCurrentRoot(self):
886 return self.currentRoot
887
888 def LoadChildren(self, layoutNode):
889 if layoutNode.built:
890 return
891 else:
892 self.nodemap[layoutNode.data]=layoutNode
893 for i in range(self.GetModel().GetChildCount(layoutNode.data)):
894 p = MVCTreeNode("RAW", layoutNode, [])
895 layoutNode.Add(p)
896 p.data = self.GetModel().GetChildAt(layoutNode.data, i)
897 self.nodemap[p.data]=p
898 layoutNode.built = True
899 if not self._assumeChildren:
900 for kid in layoutNode.kids:
901 self.LoadChildren(kid)
902
903 def OnEraseBackground(self, evt):
904 pass
905
906 def OnSize(self, evt):
907 size = self.GetSize()
908 self.center = (size.width/2, size.height/2)
909 if self._lastPhysicalSize.width < size.width or self._lastPhysicalSize.height < size.height:
910 self.painter.ClearBuffer()
911 self._lastPhysicalSize = size
912
913 def GetSelection(self):
914 "Returns a tuple of selected nodes."
915 return tuple(self._selections)
916
917 def SetSelection(self, nodeTuple):
918 if type(nodeTuple) != type(()):
919 nodeTuple = (nodeTuple,)
920 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple)
921 self.GetEventHandler().ProcessEvent(e)
922 if not e.notify.IsAllowed():
923 return
924 for node in nodeTuple:
925 treenode = self.nodemap[node]
926 treenode.selected = True
927 for node in self._selections:
928 treenode = self.nodemap[node]
929 node.selected = False
930 self._selections = list(nodeTuple)
931 e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple)
932 self.GetEventHandler().ProcessEvent(e)
933
934 def IsMultiSelect(self):
935 return self._multiselect
936
937 def SetMultiSelect(self, bool):
938 self._multiselect = bool
939
940 def IsSelected(self, node):
941 return self.nodemap[node].selected
942
943 def Edit(self, node):
944 if not self.model.IsEditable(node):
945 return
946 for ed in self._editors:
947 if ed.CanEdit(node):
948 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT, self.GetId(), node)
949 self.GetEventHandler().ProcessEvent(e)
950 if not e.notify.IsAllowed():
951 return
952 ed.Edit(node)
953 self._currentEditor = ed
954 break
955
956 def EndEdit(self):
957 if self._currentEditor:
958 self._currentEditor.EndEdit
959 self._currentEditor = None
960
961 def _EditEnding(self, node):
962 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT, self.GetId(), node)
963 self.GetEventHandler().ProcessEvent(e)
964 if not e.notify.IsAllowed():
965 return False
966 self._currentEditor = None
967 return True
968
969
970 def SetExpanded(self, node, bool):
971 treenode = self.nodemap[node]
972 if bool:
973 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING, self.GetId(), node)
974 self.GetEventHandler().ProcessEvent(e)
975 if not e.notify.IsAllowed():
976 return
977 if not treenode.built:
978 self.LoadChildren(treenode)
979 else:
980 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING, self.GetId(), node)
981 self.GetEventHandler().ProcessEvent(e)
982 if not e.notify.IsAllowed():
983 return
984 treenode.expanded = bool
985 e = None
986 if treenode.expanded:
987 e = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED, self.GetId(), node)
988 else:
989 e = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED, self.GetId(), node)
990 self.GetEventHandler().ProcessEvent(e)
991 self.layout.Layout(self.currentRoot)
992 self.transform.Transform(self.currentRoot, self.offset, self.rotation)
993 self.Refresh()
994
995 def IsExpanded(self, node):
996 return self.nodemap[node].expanded
997
998 def AddToSelection(self, nodeOrTuple, enableMulti = True, shiftMulti = False):
999 nodeTuple = nodeOrTuple
1000 if type(nodeOrTuple)!= type(()):
1001 nodeTuple = (nodeOrTuple,)
1002 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple)
1003 self.GetEventHandler().ProcessEvent(e)
1004 if not e.notify.IsAllowed():
1005 return
1006 changeparents = []
1007 if not (self.IsMultiSelect() and (enableMulti or shiftMulti)):
1008 for node in self._selections:
1009 treenode = self.nodemap[node]
1010 treenode.selected = False
1011 changeparents.append(treenode)
1012 node = nodeTuple[0]
1013 self._selections = [node]
1014 treenode = self.nodemap[node]
1015 changeparents.append(treenode)
1016 treenode.selected = True
1017 else:
1018 if shiftMulti:
1019 for node in nodeTuple:
1020 treenode = self.nodemap[node]
1021 oldtreenode = self.nodemap[self._selections[0]]
1022 if treenode.parent == oldtreenode.parent:
1023 found = 0
1024 for kid in oldtreenode.parent.kids:
1025 if kid == treenode or kid == oldtreenode:
1026 found = not found
1027 kid.selected = True
1028 self._selections.append(kid.data)
1029 changeparents.append(kid)
1030 elif found:
1031 kid.selected = True
1032 self._selections.append(kid.data)
1033 changeparents.append(kid)
1034 else:
1035 for node in nodeTuple:
1036 try:
1037 self._selections.index(node)
1038 except ValueError:
1039 self._selections.append(node)
1040 treenode = self.nodemap[node]
1041 treenode.selected = True
1042 changeparents.append(treenode)
1043 e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple)
1044 self.GetEventHandler().ProcessEvent(e)
1045 dc = wxClientDC(self)
1046 self.PrepareDC(dc)
1047 for node in changeparents:
1048 if node:
1049 self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0)
1050 self.painter.ClearBuffer()
1051
1052 def RemoveFromSelection(self, nodeTuple):
1053 if type(nodeTuple) != type(()):
1054 nodeTuple = (nodeTuple,)
1055 changeparents = []
1056 for node in nodeTuple:
1057 self._selections.remove(node)
1058 treenode = self.nodemap[node]
1059 changeparents.append(treenode)
1060 treenode.selected = False
1061 e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), node, nodes = nodeTuple)
1062 self.GetEventHandler().ProcessEvent(e)
1063 dc = wxClientDC(self)
1064 self.PrepareDC(dc)
1065 for node in changeparents:
1066 if node:
1067 self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0)
1068 self.painter.ClearBuffer()
1069
1070
1071 def GetBackgroundColour(self):
1072 if hasattr(self, 'painter') and self.painter:
1073 return self.painter.GetBackgroundColour()
1074 else:
1075 return wxWindow.GetBackgroundColour(self)
1076 def SetBackgroundColour(self, color):
1077 if hasattr(self, 'painter') and self.painter:
1078 self.painter.SetBackgroundColour(color)
1079 else:
1080 wxWindow.SetBackgroundColour(self, color)
1081 def GetForegroundColour(self):
1082 if hasattr(self, 'painter') and self.painter:
1083 return self.painter.GetForegroundColour()
1084 else:
1085 return wxWindow.GetBackgroundColour(self)
1086 def SetForegroundColour(self, color):
1087 if hasattr(self, 'painter') and self.painter:
1088 self.painter.SetForegroundColour(color)
1089 else:
1090 wxWindow.SetBackgroundColour(self, color)
1091
1092 def SetAssumeChildren(self, bool):
1093 self._assumeChildren = bool
1094
1095 def GetAssumeChildren(self):
1096 return self._assumeChildren
1097
1098 def OnPaint(self, evt):
1099 """
1100 Ensures that the tree has been laid out and transformed, then calls the painter
1101 to paint the control.
1102 """
1103 try:
1104 self.EnableScrolling(False, False)
1105 if not self.laidOut:
1106 self.layout.Layout(self.currentRoot)
1107 self.laidOut = True
1108 self.transformed = False
1109 if not self.transformed:
1110 self.transform.Transform(self.currentRoot, self.offset, self.rotation)
1111 self.transformed = True
1112 tsize = None
1113 tsize = list(self.transform.GetSize())
1114 tsize[0] = tsize[0] + 50
1115 tsize[1] = tsize[1] + 50
1116 size = self.GetSizeTuple()
1117 if tsize[0] > size[0] or tsize[1] > size[1]:
1118 if not hasattr(self, '_oldsize') or (tsize[0] > self._oldsize[0] or tsize[1] > self._oldsize[1]):
1119 self._oldsize = tsize
1120 oldstart = self.ViewStart()
1121 self._lastPhysicalSize = self.GetSize()
1122 self.SetScrollbars(10, 10, tsize[0]/10, tsize[1]/10)
1123 self.Scroll(oldstart[0], oldstart[1])
1124 dc = wxPaintDC(self)
1125 self.PrepareDC(dc)
1126 dc.SetFont(self.GetFont())
1127 self.painter.Paint(dc, self.currentRoot, self.doubleBuffered)
1128 except:
1129 traceback.print_exc()
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141