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