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