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