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