]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/mvctree.py
virtual function hiding problem
[wxWidgets.git] / wxPython / 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 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 import string
409 fw = FileWrapper(path, string.split(path, os.sep)[-1])
410 self._Build(path, fw)
411 self.SetRoot(fw)
412 self._editable = true
413 def _Build(self, path, fileWrapper):
414 for name in os.listdir(path):
415 fw = FileWrapper(path, name)
416 self.AddChild(fileWrapper, fw)
417 childName = path + os.sep + name
418 if os.path.isdir(childName):
419 self._Build(childName, fw)
420
421 def IsEditable(self, node):
422 return self._editable
423
424 def SetEditable(self, node, bool):
425 self._editable = bool
426
427 class LateFSTreeModel(FSTreeModel):
428 """
429 This treemodel models the filesystem starting from a given path.
430 It retrieves the directory list as requested.
431 """
432 def __init__(self, path):
433 BasicTreeModel.__init__(self)
434 import string
435 name = string.split(path, os.sep)[-1]
436 pathpart = path[:-len(name)]
437 fw = FileWrapper(pathpart, name)
438 self._Build(path, fw)
439 self.SetRoot(fw)
440 self._editable = true
441 self.children = {}
442 self.parents = {}
443 def _Build(self, path, parent):
444 ppath = parent.path + os.sep + parent.fileName
445 if not os.path.isdir(ppath):
446 return
447 for name in os.listdir(ppath):
448 fw = FileWrapper(ppath, name)
449 self.AddChild(parent, fw)
450 def GetChildCount(self, node):
451 if self.children.has_key(node):
452 return FSTreeModel.GetChildCount(self, node)
453 else:
454 self._Build(node.path, node)
455 return FSTreeModel.GetChildCount(self, node)
456
457 def IsLeaf(self, node):
458 return not os.path.isdir(node.path + os.sep + node.fileName)
459
460 class StrTextConverter(TextConverter):
461 def Convert(self, node):
462 return str(node.data)
463
464 class NullTransform(Transform):
465 def GetSize(self):
466 return tuple(self.size)
467
468 def Transform(self, node, offset, rotation):
469 self.size = [0,0]
470 list = self.tree.GetLayoutEngine().GetNodeList()
471 for node in list:
472 node.projx = node.x + offset[0]
473 node.projy = node.y + offset[1]
474 if node.projx > self.size[0]:
475 self.size[0] = node.projx
476 if node.projy > self.size[1]:
477 self.size[1] = node.projy
478
479 class Rect:
480 def __init__(self, x, y, width, height):
481 self.x = x
482 self.y = y
483 self.width = width
484 self.height = height
485 def __getitem__(self, index):
486 return (self.x, self.y, self.width, self.height)[index]
487
488 def __setitem__(self, index, value):
489 name = ['x', 'y', 'width', 'height'][index]
490 setattr(self, name, value)
491
492 def Contains(self, other):
493 if type(other) == type(()):
494 other = Rect(other[0], other[1], 0, 0)
495 if other.x >= self.x:
496 if other.y >= self.y:
497 if other.width + other.x <= self.width + self.x:
498 if other.height + other.y <= self.height + self.y:
499 return true
500 return false
501
502 def __str__(self):
503 return "Rect: " + str([self.x, self.y, self.width, self.height])
504
505 class TreeLayout(LayoutEngine):
506 def SetHeight(self, num):
507 self.NODE_HEIGHT = num
508
509 def __init__(self, tree):
510 LayoutEngine.__init__(self, tree)
511 self.NODE_STEP = 20
512 self.NODE_HEIGHT = 20
513 self.nodelist = []
514
515 def Layout(self, node):
516 self.nodelist = []
517 self.NODE_HEIGHT = self.tree.GetFont().GetPointSize() * 2
518 self.layoutwalk(node)
519
520 def GetNodeList(self):
521 return self.nodelist
522
523 def layoutwalk(self, node):
524 if node == self.tree.currentRoot:
525 node.level = 1
526 self.lastY = (-self.NODE_HEIGHT)
527 node.x = self.NODE_STEP * node.level
528 node.y = self.lastY + self.NODE_HEIGHT
529 self.lastY = node.y
530 self.nodelist.append(node)
531 if node.expanded:
532 for kid in node.kids:
533 kid.level = node.level + 1
534 self.layoutwalk(kid)
535
536 class TreePainter(Painter):
537 """
538 The default painter class. Uses double-buffering, delegates the painting of nodes and
539 lines to helper classes deriving from NodePainter and LinePainter.
540 """
541 def __init__(self, tree, nodePainter = None, linePainter = None, textConverter = None):
542 Painter.__init__(self, tree)
543 if not nodePainter:
544 nodePainter = TreeNodePainter(self)
545 self.nodePainter = nodePainter
546 if not linePainter:
547 linePainter = TreeLinePainter(self)
548 self.linePainter = linePainter
549 if not textConverter:
550 textConverter = StrTextConverter(self)
551 self.textConverter = textConverter
552 self.charWidths = []
553
554 def Paint(self, dc, node, doubleBuffered=1, paintBackground=1):
555 if not self.charWidths:
556 self.charWidths = []
557 for i in range(25):
558 self.charWidths.append(dc.GetTextExtent("D")[0] * i)
559 self.charHeight = dc.GetTextExtent("D")[1]
560 self.textpen = wxPen(self.GetTextColour(), 1, wxSOLID)
561 self.fgpen = wxPen(self.GetForegroundColour(), 1, wxSOLID)
562 self.bgpen = wxPen(self.GetBackgroundColour(), 1, wxSOLID)
563 self.linepen = wxPen(self.GetLineColour(), 1, wxSOLID)
564 self.dashpen = wxPen(self.GetLineColour(), 1, wxDOT)
565 self.textbrush = wxBrush(self.GetTextColour(), wxSOLID)
566 self.fgbrush = wxBrush(self.GetForegroundColour(), wxSOLID)
567 self.bgbrush = wxBrush(self.GetBackgroundColour(), wxSOLID)
568 self.linebrush = wxPen(self.GetLineColour(), 1, wxSOLID)
569 treesize = self.tree.GetSize()
570 size = self.tree.transform.GetSize()
571 size = (max(treesize.width, size[0]+50), max(treesize.height, size[1]+50))
572 dc.BeginDrawing()
573 if doubleBuffered:
574 mem_dc = wxMemoryDC()
575 if not self.GetBuffer():
576 self.knobs = []
577 self.rectangles = []
578 self.bmp = wxEmptyBitmap(size[0], size[1])
579 mem_dc.SelectObject(self.GetBuffer())
580 mem_dc.SetPen(self.GetBackgroundPen())
581 mem_dc.SetBrush(self.GetBackgroundBrush())
582 mem_dc.DrawRectangle(0, 0, size[0], size[1])
583 mem_dc.SetFont(self.tree.GetFont())
584 self.paintWalk(node, mem_dc)
585 else:
586 mem_dc.SelectObject(self.GetBuffer())
587 xstart, ystart = self.tree.CalcUnscrolledPosition(0,0)
588 size = self.tree.GetClientSizeTuple()
589 dc.Blit(xstart, ystart, size[0], size[1], mem_dc, xstart, ystart)
590 else:
591 if node == self.tree.currentRoot:
592 self.knobs = []
593 self.rectangles = []
594 dc.SetPen(self.GetBackgroundPen())
595 dc.SetBrush(self.GetBackgroundBrush())
596 dc.SetFont(self.tree.GetFont())
597 if paintBackground:
598 dc.DrawRectangle(0, 0, size[0], size[1])
599 if node:
600 #Call with not paintBackground because if we are told not to paint the
601 #whole background, we have to paint in parts to undo selection coloring.
602 pb = paintBackground
603 self.paintWalk(node, dc, not pb)
604 dc.EndDrawing()
605
606 def GetDashPen(self):
607 return self.dashpen
608
609 def SetLinePen(self, pen):
610 Painter.SetLinePen(self, pen)
611 self.dashpen = wxPen(pen.GetColour(), 1, wxDOT)
612
613 def paintWalk(self, node, dc, paintRects=0):
614 self.linePainter.Paint(node.parent, node, dc)
615 self.nodePainter.Paint(node, dc, drawRects = paintRects)
616 if node.expanded:
617 for kid in node.kids:
618 if not self.paintWalk(kid, dc, paintRects):
619 return false
620 for kid in node.kids:
621 px = (kid.projx - self.tree.layout.NODE_STEP) + 5
622 py = kid.projy + kid.height/2
623 if (not self.tree.model.IsLeaf(kid.data)) or ((kid.expanded or self.tree._assumeChildren) and len(kid.kids)):
624 dc.SetPen(self.linepen)
625 dc.SetBrush(self.bgbrush)
626 dc.DrawRectangle(px -4, py-4, 9, 9)
627 self.knobs.append( (kid, Rect(px -4, py -4, 9, 9)) )
628 dc.SetPen(self.textpen)
629 if not kid.expanded:
630 dc.DrawLine(px, py -2, px, py + 3)
631 dc.DrawLine(px -2, py, px + 3, py)
632 if node == self.tree.currentRoot:
633 px = (node.projx - self.tree.layout.NODE_STEP) + 5
634 py = node.projy + node.height/2
635 dc.SetPen(self.linepen)
636 dc.SetBrush(self.bgbrush)
637 dc.DrawRectangle(px -4, py-4, 9, 9)
638 self.knobs.append( (node, Rect(px -4, py -4, 9, 9)) )
639 dc.SetPen(self.textpen)
640 if not node.expanded:
641 dc.DrawLine(px, py -2, px, py + 3)
642 dc.DrawLine(px -2, py, px + 3, py)
643 return true
644
645 def OnMouse(self, evt):
646 Painter.OnMouse(self, evt)
647
648 class TreeNodePainter(NodePainter):
649 def Paint(self, node, dc, location = None, drawRects = 0):
650 text = self.painter.textConverter.Convert(node)
651 extent = dc.GetTextExtent(text)
652 node.width = extent[0]
653 node.height = extent[1]
654 if node.selected:
655 dc.SetPen(self.painter.GetLinePen())
656 dc.SetBrush(self.painter.GetForegroundBrush())
657 dc.SetTextForeground(wxNamedColour("WHITE"))
658 dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3)
659 else:
660 if drawRects:
661 dc.SetBrush(self.painter.GetBackgroundBrush())
662 dc.SetPen(self.painter.GetBackgroundPen())
663 dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3)
664 dc.SetTextForeground(self.painter.GetTextColour())
665 dc.DrawText(text, node.projx, node.projy)
666 self.painter.rectangles.append((node, Rect(node.projx, node.projy, node.width, node.height)))
667
668 class TreeLinePainter(LinePainter):
669 def Paint(self, parent, child, dc):
670 dc.SetPen(self.painter.GetDashPen())
671 px = py = cx = cy = 0
672 if parent is None or child == self.painter.tree.currentRoot:
673 px = (child.projx - self.painter.tree.layout.NODE_STEP) + 5
674 py = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -2
675 cx = child.projx
676 cy = py
677 dc.DrawLine(px, py, cx, cy)
678 else:
679 px = parent.projx + 5
680 py = parent.projy + parent.height
681 cx = child.projx -5
682 cy = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -3
683 dc.DrawLine(px, py, px, cy)
684 dc.DrawLine(px, cy, cx, cy)
685
686
687 wxEVT_MVCTREE_BEGIN_EDIT = 20204 #Start editing. Vetoable.
688 wxEVT_MVCTREE_END_EDIT = 20205 #Stop editing. Vetoable.
689 wxEVT_MVCTREE_DELETE_ITEM = 20206 #Item removed from model.
690 wxEVT_MVCTREE_ITEM_EXPANDED = 20209
691 wxEVT_MVCTREE_ITEM_EXPANDING = 20210
692 wxEVT_MVCTREE_ITEM_COLLAPSED = 20211
693 wxEVT_MVCTREE_ITEM_COLLAPSING = 20212
694 wxEVT_MVCTREE_SEL_CHANGED = 20213
695 wxEVT_MVCTREE_SEL_CHANGING = 20214 #Vetoable.
696 wxEVT_MVCTREE_KEY_DOWN = 20215
697 wxEVT_MVCTREE_ADD_ITEM = 20216 #Item added to model.
698
699 def EVT_MVCTREE_SEL_CHANGED(win, id, func):
700 win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED, func)
701
702 def EVT_MVCTREE_SEL_CHANGING(win, id, func):
703 win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING, func)
704
705 def EVT_MVCTREE_ITEM_EXPANDED(win, id, func):
706 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED, func)
707
708 def EVT_MVCTREE_ITEM_EXPANDING(win, id, func):
709 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING, func)
710
711 def EVT_MVCTREE_ITEM_COLLAPSED(win, id, func):
712 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED, func)
713
714 def EVT_MVCTREE_ITEM_COLLAPSING(win, id, func):
715 win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING, func)
716
717 def EVT_MVCTREE_ADD_ITEM(win, id, func):
718 win.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM, func)
719
720 def EVT_MVCTREE_DELETE_ITEM(win, id, func):
721 win.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM, func)
722
723 def EVT_MVCTREE_KEY_DOWN(win, id, func):
724 win.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN, func)
725
726
727 class wxMVCTreeEvent(wxPyCommandEvent):
728 def __init__(self, type, id, node = None, nodes = None, keyEvent = None, **kwargs):
729 apply(wxPyCommandEvent.__init__, (self, type, id), kwargs)
730 self.node = node
731 self.nodes = nodes
732 self.keyEvent = keyEvent
733 def GetNode(self):
734 return self.node
735 def GetNodes(self):
736 return self.nodes
737 def getKeyEvent(self):
738 return self.keyEvent
739
740 class wxMVCTreeNotifyEvent(wxMVCTreeEvent):
741 def __init__(self, type, id, node = None, nodes = None, **kwargs):
742 apply(wxMVCTreeEvent.__init__, (self, type, id, node, nodes), kwargs)
743 self.notify = wxNotifyEvent(type, id)
744 def getNotifyEvent(self):
745 return self.notify
746
747 class wxMVCTree(wxScrolledWindow):
748 """
749 The main mvc tree class.
750 """
751 def __init__(self, parent, id, model = None, layout = None, transform = None,
752 painter = None, *args, **kwargs):
753 apply(wxScrolledWindow.__init__, (self, parent, id), kwargs)
754 self.nodemap = {}
755 self._multiselect = false
756 self._selections = []
757 self._assumeChildren = false
758 self._scrollx = false
759 self._scrolly = false
760 self.doubleBuffered = false
761 self._lastPhysicalSize = self.GetSize()
762 self._editors = []
763 if not model:
764 model = BasicTreeModel()
765 model.SetRoot("Root")
766 self.SetModel(model)
767 if not layout:
768 layout = TreeLayout(self)
769 self.layout = layout
770 if not transform:
771 transform = NullTransform(self)
772 self.transform = transform
773 if not painter:
774 painter = TreePainter(self)
775 self.painter = painter
776 self.SetFont(wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, false))
777 EVT_MOUSE_EVENTS(self, self.OnMouse)
778 EVT_KEY_DOWN(self, self.OnKeyDown)
779 self.doubleBuffered = true
780 EVT_SIZE(self, self.OnSize)
781 EVT_ERASE_BACKGROUND(self, self.OnEraseBackground)
782 EVT_PAINT(self, self.OnPaint)
783
784
785 def Refresh(self):
786 if self.doubleBuffered:
787 self.painter.ClearBuffer()
788 wxScrolledWindow.Refresh(self, false)
789
790 def GetPainter(self):
791 return self.painter
792
793 def GetLayoutEngine(self):
794 return self.layout
795
796 def GetTransform(self):
797 return self.transform
798
799 def __repr__(self):
800 return "<wxMVCTree instance at %s>" % str(hex(id(self)))
801
802 def __str__(self):
803 return self.__repr__()
804
805 def NodeAdded(self, parent, child):
806 e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child])
807 self.GetEventHandler().ProcessEvent(e)
808 self.painter.ClearBuffer()
809
810 def NodeInserted(self, parent, child, index):
811 e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child])
812 self.GetEventHandler().ProcessEvent(e)
813 self.painter.ClearBuffer()
814
815 def NodeRemoved(self, node):
816 e = wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM, self.GetId(), node = child, nodes = [parent, child])
817 self.GetEventHandler().ProcessEvent(e)
818 self.painter.ClearBuffer()
819
820 def OnKeyDown(self, evt):
821 e = wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN, self.GetId(), keyEvent = evt)
822 self.GetEventHandler().ProcessEvent(e)
823
824 def SetFont(self, font):
825 self.painter.SetFont(font)
826 dc = wxClientDC(self)
827 dc.SetFont(font)
828 self.layout.SetHeight(dc.GetTextExtent("")[1] + 18)
829 self.painter.ClearBuffer()
830
831 def GetFont(self):
832 return self.painter.GetFont()
833
834 def AddEditor(self, editor):
835 self._editors.append(editor)
836
837 def RemoveEditor(self, editor):
838 self._editors.remove(editor)
839
840 def OnMouse(self, evt):
841 self.painter.OnMouse(evt)
842
843 def OnNodeClick(self, node, mouseEvent):
844 if node.selected and (self.IsMultiSelect() and mouseEvent.ControlDown()):
845 self.RemoveFromSelection(node.data)
846 else:
847 self.AddToSelection(node.data, mouseEvent.ControlDown(), mouseEvent.ShiftDown())
848
849 def OnKnobClick(self, node):
850 self.SetExpanded(node.data, not node.expanded)
851
852 def GetDisplayText(self, node):
853 treenode = self.nodemap[node]
854 return self.painter.textConverter.Convert(treenode)
855
856 def IsDoubleBuffered(self):
857 return self.doubleBuffered
858
859 def SetDoubleBuffered(self, bool):
860 """
861 By default wxMVCTree is double-buffered.
862 """
863 self.doubleBuffered = bool
864
865 def GetModel(self):
866 return self.model
867
868 def SetModel(self, model):
869 """
870 Completely change the data to be displayed.
871 """
872 self.model = model
873 model.tree = self
874 self.laidOut = 0
875 self.transformed = 0
876 self._selections = []
877 self.layoutRoot = MVCTreeNode()
878 self.layoutRoot.data = self.model.GetRoot()
879 self.layoutRoot.expanded = true
880 self.LoadChildren(self.layoutRoot)
881 self.currentRoot = self.layoutRoot
882 self.offset = [0,0]
883 self.rotation = 0
884 self._scrollset = None
885 self.Refresh()
886
887 def GetCurrentRoot(self):
888 return self.currentRoot
889
890 def LoadChildren(self, layoutNode):
891 if layoutNode.built:
892 return
893 else:
894 self.nodemap[layoutNode.data]=layoutNode
895 for i in range(self.GetModel().GetChildCount(layoutNode.data)):
896 p = MVCTreeNode("RAW", layoutNode, [])
897 layoutNode.Add(p)
898 p.data = self.GetModel().GetChildAt(layoutNode.data, i)
899 self.nodemap[p.data]=p
900 layoutNode.built = true
901 if not self._assumeChildren:
902 for kid in layoutNode.kids:
903 self.LoadChildren(kid)
904
905 def OnEraseBackground(self, evt):
906 pass
907
908 def OnSize(self, evt):
909 size = self.GetSize()
910 self.center = (size.width/2, size.height/2)
911 if self._lastPhysicalSize.width < size.width or self._lastPhysicalSize.height < size.height:
912 self.painter.ClearBuffer()
913 self._lastPhysicalSize = size
914
915 def GetSelection(self):
916 "Returns a tuple of selected nodes."
917 return tuple(self._selections)
918
919 def SetSelection(self, nodeTuple):
920 if type(nodeTuple) != type(()):
921 nodeTuple = (nodeTuple,)
922 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple)
923 self.GetEventHandler().ProcessEvent(e)
924 if not e.notify.IsAllowed():
925 return
926 for node in nodeTuple:
927 treenode = self.nodemap[node]
928 treenode.selected = true
929 for node in self._selections:
930 treenode = self.nodemap[node]
931 node.selected = false
932 self._selections = list(nodeTuple)
933 e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple)
934 self.GetEventHandler().ProcessEvent(e)
935
936 def IsMultiSelect(self):
937 return self._multiselect
938
939 def SetMultiSelect(self, bool):
940 self._multiselect = bool
941
942 def IsSelected(self, node):
943 return self.nodemap[node].selected
944
945 def Edit(self, node):
946 if not self.model.IsEditable(node):
947 return
948 for ed in self._editors:
949 if ed.CanEdit(node):
950 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT, self.GetId(), node)
951 self.GetEventHandler().ProcessEvent(e)
952 if not e.notify.IsAllowed():
953 return
954 ed.Edit(node)
955 self._currentEditor = ed
956 break
957
958 def EndEdit(self):
959 if self._currentEditor:
960 self._currentEditor.EndEdit
961 self._currentEditor = None
962
963 def _EditEnding(self, node):
964 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT, self.GetId(), node)
965 self.GetEventHandler().ProcessEvent(e)
966 if not e.notify.IsAllowed():
967 return false
968 self._currentEditor = None
969 return true
970
971
972 def SetExpanded(self, node, bool):
973 treenode = self.nodemap[node]
974 if bool:
975 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING, self.GetId(), node)
976 self.GetEventHandler().ProcessEvent(e)
977 if not e.notify.IsAllowed():
978 return
979 if not treenode.built:
980 self.LoadChildren(treenode)
981 else:
982 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING, self.GetId(), node)
983 self.GetEventHandler().ProcessEvent(e)
984 if not e.notify.IsAllowed():
985 return
986 treenode.expanded = bool
987 e = None
988 if treenode.expanded:
989 e = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED, self.GetId(), node)
990 else:
991 e = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED, self.GetId(), node)
992 self.GetEventHandler().ProcessEvent(e)
993 self.layout.Layout(self.currentRoot)
994 self.transform.Transform(self.currentRoot, self.offset, self.rotation)
995 self.Refresh()
996
997 def IsExpanded(self, node):
998 return self.nodemap[node].expanded
999
1000 def AddToSelection(self, nodeOrTuple, enableMulti = true, shiftMulti = false):
1001 nodeTuple = nodeOrTuple
1002 if type(nodeOrTuple)!= type(()):
1003 nodeTuple = (nodeOrTuple,)
1004 e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple)
1005 self.GetEventHandler().ProcessEvent(e)
1006 if not e.notify.IsAllowed():
1007 return
1008 changeparents = []
1009 if not (self.IsMultiSelect() and (enableMulti or shiftMulti)):
1010 for node in self._selections:
1011 treenode = self.nodemap[node]
1012 treenode.selected = false
1013 changeparents.append(treenode)
1014 node = nodeTuple[0]
1015 self._selections = [node]
1016 treenode = self.nodemap[node]
1017 changeparents.append(treenode)
1018 treenode.selected = true
1019 else:
1020 if shiftMulti:
1021 for node in nodeTuple:
1022 treenode = self.nodemap[node]
1023 oldtreenode = self.nodemap[self._selections[0]]
1024 if treenode.parent == oldtreenode.parent:
1025 found = 0
1026 for kid in oldtreenode.parent.kids:
1027 if kid == treenode or kid == oldtreenode:
1028 found = not found
1029 kid.selected = true
1030 self._selections.append(kid.data)
1031 changeparents.append(kid)
1032 elif found:
1033 kid.selected = true
1034 self._selections.append(kid.data)
1035 changeparents.append(kid)
1036 else:
1037 for node in nodeTuple:
1038 try:
1039 self._selections.index(node)
1040 except ValueError:
1041 self._selections.append(node)
1042 treenode = self.nodemap[node]
1043 treenode.selected = true
1044 changeparents.append(treenode)
1045 e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple)
1046 self.GetEventHandler().ProcessEvent(e)
1047 dc = wxClientDC(self)
1048 self.PrepareDC(dc)
1049 for node in changeparents:
1050 if node:
1051 self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0)
1052 self.painter.ClearBuffer()
1053
1054 def RemoveFromSelection(self, nodeTuple):
1055 if type(nodeTuple) != type(()):
1056 nodeTuple = (nodeTuple,)
1057 changeparents = []
1058 for node in nodeTuple:
1059 self._selections.remove(node)
1060 treenode = self.nodemap[node]
1061 changeparents.append(treenode)
1062 treenode.selected = false
1063 e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), node, nodes = nodeTuple)
1064 self.GetEventHandler().ProcessEvent(e)
1065 dc = wxClientDC(self)
1066 self.PrepareDC(dc)
1067 for node in changeparents:
1068 if node:
1069 self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0)
1070 self.painter.ClearBuffer()
1071
1072
1073 def GetBackgroundColour(self):
1074 if hasattr(self, 'painter') and self.painter:
1075 return self.painter.GetBackgroundColour()
1076 else:
1077 return wxWindow.GetBackgroundColour(self)
1078 def SetBackgroundColour(self, color):
1079 if hasattr(self, 'painter') and self.painter:
1080 self.painter.SetBackgroundColour(color)
1081 else:
1082 wxWindow.SetBackgroundColour(self, color)
1083 def GetForegroundColour(self):
1084 if hasattr(self, 'painter') and self.painter:
1085 return self.painter.GetForegroundColour()
1086 else:
1087 return wxWindow.GetBackgroundColour(self)
1088 def SetForegroundColour(self, color):
1089 if hasattr(self, 'painter') and self.painter:
1090 self.painter.SetForegroundColour(color)
1091 else:
1092 wxWindow.SetBackgroundColour(self, color)
1093
1094 def SetAssumeChildren(self, bool):
1095 self._assumeChildren = bool
1096
1097 def GetAssumeChildren(self):
1098 return self._assumeChildren
1099
1100 def OnPaint(self, evt):
1101 """
1102 Ensures that the tree has been laid out and transformed, then calls the painter
1103 to paint the control.
1104 """
1105 try:
1106 self.EnableScrolling(false, false)
1107 if not self.laidOut:
1108 self.layout.Layout(self.currentRoot)
1109 self.laidOut = true
1110 self.transformed = false
1111 if not self.transformed:
1112 self.transform.Transform(self.currentRoot, self.offset, self.rotation)
1113 self.transformed = true
1114 tsize = None
1115 tsize = list(self.transform.GetSize())
1116 tsize[0] = tsize[0] + 50
1117 tsize[1] = tsize[1] + 50
1118 size = self.GetSizeTuple()
1119 if tsize[0] > size[0] or tsize[1] > size[1]:
1120 if not hasattr(self, '_oldsize') or (tsize[0] > self._oldsize[0] or tsize[1] > self._oldsize[1]):
1121 self._oldsize = tsize
1122 oldstart = self.ViewStart()
1123 self._lastPhysicalSize = self.GetSize()
1124 self.SetScrollbars(10, 10, tsize[0]/10, tsize[1]/10)
1125 self.Scroll(oldstart[0], oldstart[1])
1126 dc = wxPaintDC(self)
1127 self.PrepareDC(dc)
1128 dc.SetFont(self.GetFont())
1129 self.painter.Paint(dc, self.currentRoot, self.doubleBuffered)
1130 except:
1131 traceback.print_exc()
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143