]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/mvctree.py
1853c04077250fd8d94d840d5f1cef5f815dda75
[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 = [], 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 self.data = data
48 self.expanded = false
49 self.selected = false
50 self.built = false
51 self.scale = 0
52
53 def GetChildren(self):
54 return self.kids
55
56 def GetParent(self):
57 return self.parent
58
59 def Remove(self, node):
60 try:
61 self.kids.remove(node)
62 except:
63 pass
64 def Add(self, node):
65 self.kids.append(node)
66 node.SetParent(self)
67
68 def SetParent(self, parent):
69 if self.parent and not (self.parent is parent):
70 self.parent.Remove(self)
71 self.parent = parent
72 def __str__(self):
73 return "Node: " + str(self.data) + " (" + str(self.x) + ", " + str(self.y) + ")"
74 def __repr__(self):
75 return str(self.data)
76 def GetTreeString(self, tabs=0):
77 s = tabs * '\t' + str(self) + '\n'
78 for kid in self.kids:
79 s = s + kid.GetTreeString(tabs + 1)
80 return s
81
82
83 class Editor:
84 def __init__(self, tree):
85 self.tree = tree
86 def Edit(self, node):
87 raise NotImplementedError
88 def EndEdit(self, node, commit):
89 raise NotImplementedError
90 def CanEdit(self, node):
91 raise NotImplementedError
92
93 class LayoutEngine:
94 """
95 Interface for layout engines.
96 """
97 def __init__(self, tree):
98 self.tree = tree
99 def Layout(self, node):
100 raise NotImplementedError
101 def GetNodeList(self):
102 raise NotImplementedError
103
104 class Transform:
105 """
106 Transform interface.
107 """
108 def __init__(self, tree):
109 self.tree = tree
110 def Transform(self, node, offset, rotation):
111 """
112 This method should only change the projx and projy attributes of
113 the node. These represent the position of the node as it should
114 be drawn on screen. Adjusting the x and y attributes can and
115 should cause havoc.
116 """
117 raise NotImplementedError
118
119 def GetSize(self):
120 """
121 Returns the size of the entire tree as laid out and transformed
122 as a tuple
123 """
124 raise NotImplementedError
125
126 class Painter:
127 """
128 This is the interface that wxMVCTree expects from painters. All painters should
129 be Painter subclasses.
130 """
131 def __init__(self, tree):
132 self.tree = tree
133 self.textcolor = wxNamedColour("BLACK")
134 self.bgcolor = wxNamedColour("WHITE")
135 self.fgcolor = wxNamedColour("BLUE")
136 self.linecolor = wxNamedColour("GREY")
137 self.font = wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, false)
138 self.bmp = None
139
140 def GetFont(self):
141 return self.font
142
143 def SetFont(self, font):
144 self.font = font
145 self.tree.Refresh()
146 def GetBuffer(self):
147 return self.bmp
148 def ClearBuffer(self):
149 self.bmp = None
150 def Paint(self, dc, node, doubleBuffered=1, paintBackground=1):
151 raise NotImplementedError
152 def GetTextColour(self):
153 return self.textcolor
154 def SetTextColour(self, color):
155 self.textcolor = color
156 self.textbrush = wxBrush(color)
157 self.textpen = wxPen(color, 1, wxSOLID)
158 def GetBackgroundColour(self):
159 return self.bgcolor
160 def SetBackgroundColour(self, color):
161 self.bgcolor = color
162 self.bgbrush = wxBrush(color)
163 self.bgpen = wxPen(color, 1, wxSOLID)
164 def GetForegroundColour(self):
165 return self.fgcolor
166 def SetForegroundColour(self, color):
167 self.fgcolor = color
168 self.fgbrush = wxBrush(color)
169 self.fgpen = wxPen(color, 1, wxSOLID)
170 def GetLineColour(self):
171 return self.linecolor
172 def SetLineColour(self, color):
173 self.linecolor = color
174 self.linebrush = wxBrush(color)
175 self.linepen = wxPen( color, 1, wxSOLID)
176 def GetForegroundPen(self):
177 return self.fgpen
178 def GetBackgroundPen(self):
179 return self.bgpen
180 def GetTextPen(self):
181 return self.textpen
182 def GetForegroundBrush(self):
183 return self.fgbrush
184 def GetBackgroundBrush(self):
185 return self.bgbrush
186 def GetTextBrush(self):
187 return self.textbrush
188 def GetLinePen(self):
189 return self.linepen
190 def GetLineBrush(self):
191 return self.linebrush
192 def OnMouse(self, evt):
193 if evt.LeftDClick():
194 x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
195 for item in self.rectangles:
196 if item[1].Contains((x,y)):
197 self.tree.Edit(item[0].data)
198 self.tree.OnNodeClick(item[0], evt)
199 return
200 elif evt.ButtonDown():
201 x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY())
202 for item in self.rectangles:
203 if item[1].Contains((x, y)):
204 self.tree.OnNodeClick(item[0], evt)
205 return
206 for item in self.knobs:
207 if item[1].Contains((x, y)):
208 self.tree.OnKnobClick(item[0])
209 return
210 evt.Skip()
211
212
213 class wxTreeModel:
214 """
215 Interface for tree models
216 """
217 def GetRoot(self):
218 raise NotImplementedError
219 def SetRoot(self, root):
220 raise NotImplementedError
221 def GetChildCount(self, node):
222 raise NotImplementedError
223 def GetChildAt(self, node, index):
224 raise NotImplementedError
225 def GetParent(self, node):
226 raise NotImplementedError
227 def AddChild(self, parent, child):
228 if hasattr(self, 'tree') and self.tree:
229 self.tree.NodeAdded(parent, child)
230 def RemoveNode(self, child):
231 if hasattr(self, 'tree') and self.tree:
232 self.tree.NodeRemoved(child)
233 def InsertChild(self, parent, child, index):
234 if hasattr(self, 'tree') and self.tree:
235 self.tree.NodeInserted(parent, child, index)
236 def IsLeaf(self, node):
237 raise NotImplementedError
238
239 def IsEditable(self, node):
240 return false
241
242 def SetEditable(self, node):
243 return false
244
245 class NodePainter:
246 """
247 This is the interface expected of a nodepainter.
248 """
249 def __init__(self, painter):
250 self.painter = painter
251 def Paint(self, node, dc, location = None):
252 """
253 location should be provided only to draw in an unusual position
254 (not the node's normal position), otherwise the node's projected x and y
255 coordinates will be used.
256 """
257 raise NotImplementedError
258
259 class LinePainter:
260 """
261 The linepainter interface.
262 """
263 def __init__(self, painter):
264 self.painter = painter
265 def Paint(self, parent, child, dc):
266 raise NotImplementedError
267
268 class TextConverter:
269 """
270 TextConverter interface.
271 """
272 def __init__(self, painter):
273 self.painter = painter
274 def Convert(node):
275 """
276 Should return a string. The node argument will be an
277 MVCTreeNode.
278 """
279 raise NotImplementedError
280
281
282 class BasicTreeModel(wxTreeModel):
283 """
284 A very simple treemodel implementation, but flexible enough for many needs.
285 """
286 def __init__(self):
287 self.children = {}
288 self.parents = {}
289 self.root = None
290 def GetRoot(self):
291 return self.root
292 def SetRoot(self, root):
293 self.root = root
294 def GetChildCount(self, node):
295 if self.children.has_key(node):
296 return len(self.children[node])
297 else:
298 return 0
299 def GetChildAt(self, node, index):
300 return self.children[node][index]
301
302 def GetParent(self, node):
303 return self.parents[node]
304
305 def AddChild(self, parent, child):
306 self.parents[child]=parent
307 if not self.children.has_key(parent):
308 self.children[parent]=[]
309 self.children[parent].append(child)
310 wxTreeModel.AddChild(self, parent, child)
311 return child
312
313 def RemoveNode(self, node):
314 parent = self.parents[node]
315 del self.parents[node]
316 self.children[parent].remove(node)
317 wxTreeModel.RemoveNode(self, node)
318
319 def InsertChild(self, parent, child, index):
320 self.parents[child]=parent
321 if not self.children.has_key(parent):
322 self.children[parent]=[]
323 self.children[parent].insert(child, index)
324 wxTreeModel.InsertChild(self, parent, child, index)
325 return child
326
327 def IsLeaf(self, node):
328 return not self.children.has_key(node)
329
330 def IsEditable(self, node):
331 return false
332
333 def SetEditable(self, node, bool):
334 return false
335
336
337 class FileEditor(Editor):
338 def Edit(self, node):
339 treenode = self.tree.nodemap[node]
340 self.editcomp = wxTextCtrl(self.tree, -1)
341 for rect in self.tree.painter.rectangles:
342 if rect[0] == treenode:
343 self.editcomp.SetPosition((rect[1][0], rect[1][1]))
344 break
345 self.editcomp.SetValue(node.fileName)
346 self.editcomp.SetSelection(0, len(node.fileName))
347 self.editcomp.SetFocus()
348 self.treenode = treenode
349 # EVT_KEY_DOWN(self.editcomp, self._key)
350 EVT_KEY_UP(self.editcomp, self._key)
351 EVT_LEFT_DOWN(self.editcomp, self._mdown)
352 self.editcomp.CaptureMouse()
353
354 def CanEdit(self, node):
355 return isinstance(node, FileWrapper)
356
357 def EndEdit(self, commit):
358 if not self.tree._EditEnding(self.treenode.data):
359 return
360 if commit:
361 node = self.treenode.data
362 try:
363 os.rename(node.path + os.sep + node.fileName, node.path + os.sep + self.editcomp.GetValue())
364 node.fileName = self.editcomp.GetValue()
365 except:
366 traceback.print_exc()
367 self.editcomp.ReleaseMouse()
368 self.editcomp.Destroy()
369 del self.editcomp
370 self.tree.Refresh()
371
372
373 def _key(self, evt):
374 if evt.KeyCode() == WXK_RETURN:
375 self.EndEdit(true)
376 elif evt.KeyCode() == WXK_ESCAPE:
377 self.EndEdit(false)
378 else:
379 evt.Skip()
380
381 def _mdown(self, evt):
382 if evt.IsButton():
383 pos = evt.GetPosition()
384 edsize = self.editcomp.GetSize()
385 if pos.x < 0 or pos.y < 0 or pos.x > edsize.width or pos.y > edsize.height:
386 self.EndEdit(false)
387
388
389 class FileWrapper:
390 """
391 Node class for FSTreeModel.
392 """
393 def __init__(self, path, fileName):
394 self.path = path
395 self.fileName = fileName
396
397 def __str__(self):
398 return self.fileName
399
400 class FSTreeModel(BasicTreeModel):
401 """
402 This treemodel models the filesystem starting from a given path.
403 """
404 def __init__(self, path):
405 BasicTreeModel.__init__(self)
406 import string
407 fw = FileWrapper(path, string.split(path, os.sep)[-1])
408 self._Build(path, fw)
409 self.SetRoot(fw)
410 self._editable = true
411 def _Build(self, path, fileWrapper):
412 for name in os.listdir(path):
413 fw = FileWrapper(path, name)
414 self.AddChild(fileWrapper, fw)
415 childName = path + os.sep + name
416 if os.path.isdir(childName):
417 self._Build(childName, fw)
418
419 def IsEditable(self, node):
420 return self._editable
421
422 def SetEditable(self, node, bool):
423 self._editable = bool
424
425 class LateFSTreeModel(FSTreeModel):
426 """
427 This treemodel models the filesystem starting from a given path.
428 It retrieves the directory list as requested.
429 """
430 def __init__(self, path):
431 BasicTreeModel.__init__(self)
432 import string
433 name = string.split(path, 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