X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/1fded56b375bf7a4687af1cdb182899614c1b2a8..44b8f8e051af41775c9eaeace6a3be139bcbe729:/wxPython/wx/lib/mvctree.py?ds=sidebyside diff --git a/wxPython/wx/lib/mvctree.py b/wxPython/wx/lib/mvctree.py index cc1b6c94a7..dd84720c36 100644 --- a/wxPython/wx/lib/mvctree.py +++ b/wxPython/wx/lib/mvctree.py @@ -1,11 +1,1145 @@ +# 12/09/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o 2.5 compatability update. +# o I'm a little nervous about some of it though. +# +# 12/20/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o wxTreeModel -> TreeModel +# o wxMVCTree -> MVCTree +# o wxMVCTreeEvent -> MVCTreeEvent +# o wxMVCTreeNotifyEvent -> MVCTreeNotifyEvent +# + +""" +MVCTree is a control which handles hierarchical data. It is constructed +in model-view-controller architecture, so the display of that data, and +the content of the data can be changed greatly without affecting the other parts. + +MVCTree actually is even more configurable than MVC normally implies, because +almost every aspect of it is pluggable: + MVCTree - Overall controller, and the window that actually gets placed + in the GUI. + Painter - Paints the control. The 'view' part of MVC. + NodePainter - Paints just the nodes + LinePainter - Paints just the lines between the nodes + TextConverter - Figures out what text to print for each node + Editor - Edits the contents of a node, if the model is editable. + LayoutEngine - Determines initial placement of nodes + Transform - Adjusts positions of nodes for movement or special effects. + TreeModel - Contains the data which the rest of the control acts + on. The 'model' part of MVC. + +Author/Maintainer - Bryn Keller + + +NOTE: This module is *not* supported in any way. Use it however you + wish, but be warned that dealing with any consequences is + entirly up to you. + --Robin +""" + +#------------------------------------------------------------------------ +import os +import sys +import traceback +import warnings + +import wx +#------------------------------------------------------------------------ + +warningmsg = r"""\ + +################################################\ +# This module is not supported in any way! | +# | +# See cource code for wx.lib.mvctree for more | +# information. | +################################################/ + +""" + +warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) +#------------------------------------------------------------------------ + +class MVCTreeNode: + """ + Used internally by MVCTree to manage its data. Contains information about + screen placement, the actual data associated with it, and more. These are + the nodes passed to all the other helper parts to do their work with. + """ + def __init__(self, data=None, parent = None, kids = None, x = 0, y = 0): + self.x = 0 + self.y = 0 + self.projx = 0 + self.projy = 0 + self.parent = parent + self.kids = kids + if self.kids is None: + self.kids = [] + self.data = data + self.expanded = False + self.selected = False + self.built = False + self.scale = 0 + + def GetChildren(self): + return self.kids + + def GetParent(self): + return self.parent + + def Remove(self, node): + try: + self.kids.remove(node) + except: + pass + def Add(self, node): + self.kids.append(node) + node.SetParent(self) + + def SetParent(self, parent): + if self.parent and not (self.parent is parent): + self.parent.Remove(self) + self.parent = parent + def __str__(self): + return "Node: " + str(self.data) + " (" + str(self.x) + ", " + str(self.y) + ")" + def __repr__(self): + return str(self.data) + def GetTreeString(self, tabs=0): + s = tabs * '\t' + str(self) + '\n' + for kid in self.kids: + s = s + kid.GetTreeString(tabs + 1) + return s + + +class Editor: + def __init__(self, tree): + self.tree = tree + def Edit(self, node): + raise NotImplementedError + def EndEdit(self, node, commit): + raise NotImplementedError + def CanEdit(self, node): + raise NotImplementedError + +class LayoutEngine: + """ + Interface for layout engines. + """ + def __init__(self, tree): + self.tree = tree + def Layout(self, node): + raise NotImplementedError + def GetNodeList(self): + raise NotImplementedError + +class Transform: + """ + Transform interface. + """ + def __init__(self, tree): + self.tree = tree + def Transform(self, node, offset, rotation): + """ + This method should only change the projx and projy attributes of + the node. These represent the position of the node as it should + be drawn on screen. Adjusting the x and y attributes can and + should cause havoc. + """ + raise NotImplementedError + + def GetSize(self): + """ + Returns the size of the entire tree as laid out and transformed + as a tuple + """ + raise NotImplementedError + +class Painter: + """ + This is the interface that MVCTree expects from painters. All painters should + be Painter subclasses. + """ + def __init__(self, tree): + self.tree = tree + self.textcolor = wx.NamedColour("BLACK") + self.bgcolor = wx.NamedColour("WHITE") + self.fgcolor = wx.NamedColour("BLUE") + self.linecolor = wx.NamedColour("GREY") + self.font = wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False) + self.bmp = None + + def GetFont(self): + return self.font + + def SetFont(self, font): + self.font = font + self.tree.Refresh() + def GetBuffer(self): + return self.bmp + def ClearBuffer(self): + self.bmp = None + def Paint(self, dc, node, doubleBuffered=1, paintBackground=1): + raise NotImplementedError + def GetTextColour(self): + return self.textcolor + def SetTextColour(self, color): + self.textcolor = color + self.textbrush = wx.Brush(color) + self.textpen = wx.Pen(color, 1, wx.SOLID) + def GetBackgroundColour(self): + return self.bgcolor + def SetBackgroundColour(self, color): + self.bgcolor = color + self.bgbrush = wx.Brush(color) + self.bgpen = wx.Pen(color, 1, wx.SOLID) + def GetForegroundColour(self): + return self.fgcolor + def SetForegroundColour(self, color): + self.fgcolor = color + self.fgbrush = wx.Brush(color) + self.fgpen = wx.Pen(color, 1, wx.SOLID) + def GetLineColour(self): + return self.linecolor + def SetLineColour(self, color): + self.linecolor = color + self.linebrush = wx.Brush(color) + self.linepen = wx.Pen( color, 1, wx.SOLID) + def GetForegroundPen(self): + return self.fgpen + def GetBackgroundPen(self): + return self.bgpen + def GetTextPen(self): + return self.textpen + def GetForegroundBrush(self): + return self.fgbrush + def GetBackgroundBrush(self): + return self.bgbrush + def GetTextBrush(self): + return self.textbrush + def GetLinePen(self): + return self.linepen + def GetLineBrush(self): + return self.linebrush + def OnMouse(self, evt): + if evt.LeftDClick(): + x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) + for item in self.rectangles: + if item[1].Contains((x,y)): + self.tree.Edit(item[0].data) + self.tree.OnNodeClick(item[0], evt) + return + elif evt.ButtonDown(): + x, y = self.tree.CalcUnscrolledPosition(evt.GetX(), evt.GetY()) + for item in self.rectangles: + if item[1].Contains((x, y)): + self.tree.OnNodeClick(item[0], evt) + return + for item in self.knobs: + if item[1].Contains((x, y)): + self.tree.OnKnobClick(item[0]) + return + evt.Skip() + + +class TreeModel: + """ + Interface for tree models + """ + def GetRoot(self): + raise NotImplementedError + def SetRoot(self, root): + raise NotImplementedError + def GetChildCount(self, node): + raise NotImplementedError + def GetChildAt(self, node, index): + raise NotImplementedError + def GetParent(self, node): + raise NotImplementedError + def AddChild(self, parent, child): + if hasattr(self, 'tree') and self.tree: + self.tree.NodeAdded(parent, child) + def RemoveNode(self, child): + if hasattr(self, 'tree') and self.tree: + self.tree.NodeRemoved(child) + def InsertChild(self, parent, child, index): + if hasattr(self, 'tree') and self.tree: + self.tree.NodeInserted(parent, child, index) + def IsLeaf(self, node): + raise NotImplementedError + + def IsEditable(self, node): + return False + + def SetEditable(self, node): + return False + +class NodePainter: + """ + This is the interface expected of a nodepainter. + """ + def __init__(self, painter): + self.painter = painter + def Paint(self, node, dc, location = None): + """ + location should be provided only to draw in an unusual position + (not the node's normal position), otherwise the node's projected x and y + coordinates will be used. + """ + raise NotImplementedError + +class LinePainter: + """ + The linepainter interface. + """ + def __init__(self, painter): + self.painter = painter + def Paint(self, parent, child, dc): + raise NotImplementedError + +class TextConverter: + """ + TextConverter interface. + """ + def __init__(self, painter): + self.painter = painter + def Convert(node): + """ + Should return a string. The node argument will be an + MVCTreeNode. + """ + raise NotImplementedError + + +class BasicTreeModel(TreeModel): + """ + A very simple treemodel implementation, but flexible enough for many needs. + """ + def __init__(self): + self.children = {} + self.parents = {} + self.root = None + def GetRoot(self): + return self.root + def SetRoot(self, root): + self.root = root + def GetChildCount(self, node): + if self.children.has_key(node): + return len(self.children[node]) + else: + return 0 + def GetChildAt(self, node, index): + return self.children[node][index] + + def GetParent(self, node): + return self.parents[node] + + def AddChild(self, parent, child): + self.parents[child]=parent + if not self.children.has_key(parent): + self.children[parent]=[] + self.children[parent].append(child) + TreeModel.AddChild(self, parent, child) + return child + + def RemoveNode(self, node): + parent = self.parents[node] + del self.parents[node] + self.children[parent].remove(node) + TreeModel.RemoveNode(self, node) + + def InsertChild(self, parent, child, index): + self.parents[child]=parent + if not self.children.has_key(parent): + self.children[parent]=[] + self.children[parent].insert(child, index) + TreeModel.InsertChild(self, parent, child, index) + return child + + def IsLeaf(self, node): + return not self.children.has_key(node) + + def IsEditable(self, node): + return False + + def SetEditable(self, node, bool): + return False + + +class FileEditor(Editor): + def Edit(self, node): + treenode = self.tree.nodemap[node] + self.editcomp = wxTextCtrl(self.tree, -1) + for rect in self.tree.painter.rectangles: + if rect[0] == treenode: + self.editcomp.SetPosition((rect[1][0], rect[1][1])) + break + self.editcomp.SetValue(node.fileName) + self.editcomp.SetSelection(0, len(node.fileName)) + self.editcomp.SetFocus() + self.treenode = treenode +# self.editcomp.Bind(wx.EVT_KEY_DOWN, self._key) + self.editcomp.Bind(wx.EVT_KEY_UP, self._key) + self.editcomp.Bind(wx.EVT_LEFT_DOWN, self._mdown) + self.editcomp.CaptureMouse() + + def CanEdit(self, node): + return isinstance(node, FileWrapper) + + def EndEdit(self, commit): + if not self.tree._EditEnding(self.treenode.data): + return + if commit: + node = self.treenode.data + try: + os.rename(node.path + os.sep + node.fileName, node.path + os.sep + self.editcomp.GetValue()) + node.fileName = self.editcomp.GetValue() + except: + traceback.print_exc() + self.editcomp.ReleaseMouse() + self.editcomp.Destroy() + del self.editcomp + self.tree.Refresh() + + + def _key(self, evt): + if evt.KeyCode() == wx.WXK_RETURN: + self.EndEdit(True) + elif evt.KeyCode() == wx.WXK_ESCAPE: + self.EndEdit(False) + else: + evt.Skip() + + def _mdown(self, evt): + if evt.IsButton(): + x, y = evt.GetPosition() + w, h = self.editcomp.GetSize() + if x < 0 or y < 0 or x > w or y > h: + self.EndEdit(False) + + +class FileWrapper: + """ + Node class for FSTreeModel. + """ + def __init__(self, path, fileName): + self.path = path + self.fileName = fileName + + def __str__(self): + return self.fileName + +class FSTreeModel(BasicTreeModel): + """ + This treemodel models the filesystem starting from a given path. + """ + def __init__(self, path): + BasicTreeModel.__init__(self) + fw = FileWrapper(path, path.split(os.sep)[-1]) + self._Build(path, fw) + self.SetRoot(fw) + self._editable = True + def _Build(self, path, fileWrapper): + for name in os.listdir(path): + fw = FileWrapper(path, name) + self.AddChild(fileWrapper, fw) + childName = path + os.sep + name + if os.path.isdir(childName): + self._Build(childName, fw) + + def IsEditable(self, node): + return self._editable + + def SetEditable(self, node, bool): + self._editable = bool + +class LateFSTreeModel(FSTreeModel): + """ + This treemodel models the filesystem starting from a given path. + It retrieves the directory list as requested. + """ + def __init__(self, path): + BasicTreeModel.__init__(self) + name = path.split(os.sep)[-1] + pathpart = path[:-len(name)] + fw = FileWrapper(pathpart, name) + self._Build(path, fw) + self.SetRoot(fw) + self._editable = True + self.children = {} + self.parents = {} + def _Build(self, path, parent): + ppath = parent.path + os.sep + parent.fileName + if not os.path.isdir(ppath): + return + for name in os.listdir(ppath): + fw = FileWrapper(ppath, name) + self.AddChild(parent, fw) + def GetChildCount(self, node): + if self.children.has_key(node): + return FSTreeModel.GetChildCount(self, node) + else: + self._Build(node.path, node) + return FSTreeModel.GetChildCount(self, node) + + def IsLeaf(self, node): + return not os.path.isdir(node.path + os.sep + node.fileName) + +class StrTextConverter(TextConverter): + def Convert(self, node): + return str(node.data) + +class NullTransform(Transform): + def GetSize(self): + return tuple(self.size) + + def Transform(self, node, offset, rotation): + self.size = [0,0] + list = self.tree.GetLayoutEngine().GetNodeList() + for node in list: + node.projx = node.x + offset[0] + node.projy = node.y + offset[1] + if node.projx > self.size[0]: + self.size[0] = node.projx + if node.projy > self.size[1]: + self.size[1] = node.projy + +class Rect: + def __init__(self, x, y, width, height): + self.x = x + self.y = y + self.width = width + self.height = height + def __getitem__(self, index): + return (self.x, self.y, self.width, self.height)[index] + + def __setitem__(self, index, value): + name = ['x', 'y', 'width', 'height'][index] + setattr(self, name, value) + + def Contains(self, other): + if type(other) == type(()): + other = Rect(other[0], other[1], 0, 0) + if other.x >= self.x: + if other.y >= self.y: + if other.width + other.x <= self.width + self.x: + if other.height + other.y <= self.height + self.y: + return True + return False + + def __str__(self): + return "Rect: " + str([self.x, self.y, self.width, self.height]) + +class TreeLayout(LayoutEngine): + def SetHeight(self, num): + self.NODE_HEIGHT = num + + def __init__(self, tree): + LayoutEngine.__init__(self, tree) + self.NODE_STEP = 20 + self.NODE_HEIGHT = 20 + self.nodelist = [] + + def Layout(self, node): + self.nodelist = [] + self.NODE_HEIGHT = self.tree.GetFont().GetPointSize() * 2 + self.layoutwalk(node) + + def GetNodeList(self): + return self.nodelist + + def layoutwalk(self, node): + if node == self.tree.currentRoot: + node.level = 1 + self.lastY = (-self.NODE_HEIGHT) + node.x = self.NODE_STEP * node.level + node.y = self.lastY + self.NODE_HEIGHT + self.lastY = node.y + self.nodelist.append(node) + if node.expanded: + for kid in node.kids: + kid.level = node.level + 1 + self.layoutwalk(kid) + +class TreePainter(Painter): + """ + The default painter class. Uses double-buffering, delegates the painting of nodes and + lines to helper classes deriving from NodePainter and LinePainter. + """ + def __init__(self, tree, nodePainter = None, linePainter = None, textConverter = None): + Painter.__init__(self, tree) + if not nodePainter: + nodePainter = TreeNodePainter(self) + self.nodePainter = nodePainter + if not linePainter: + linePainter = TreeLinePainter(self) + self.linePainter = linePainter + if not textConverter: + textConverter = StrTextConverter(self) + self.textConverter = textConverter + self.charWidths = [] + + def Paint(self, dc, node, doubleBuffered=1, paintBackground=1): + if not self.charWidths: + self.charWidths = [] + for i in range(25): + self.charWidths.append(dc.GetTextExtent("D")[0] * i) + self.charHeight = dc.GetTextExtent("D")[1] + self.textpen = wx.Pen(self.GetTextColour(), 1, wx.SOLID) + self.fgpen = wx.Pen(self.GetForegroundColour(), 1, wx.SOLID) + self.bgpen = wx.Pen(self.GetBackgroundColour(), 1, wx.SOLID) + self.linepen = wx.Pen(self.GetLineColour(), 1, wx.SOLID) + self.dashpen = wx.Pen(self.GetLineColour(), 1, wx.DOT) + self.textbrush = wx.Brush(self.GetTextColour(), wx.SOLID) + self.fgbrush = wx.Brush(self.GetForegroundColour(), wx.SOLID) + self.bgbrush = wx.Brush(self.GetBackgroundColour(), wx.SOLID) + self.linebrush = wx.Pen(self.GetLineColour(), 1, wx.SOLID) + treesize = self.tree.GetSize() + size = self.tree.transform.GetSize() + size = (max(treesize.width, size[0]+50), max(treesize.height, size[1]+50)) + dc.BeginDrawing() + if doubleBuffered: + mem_dc = wx.MemoryDC() + if not self.GetBuffer(): + self.knobs = [] + self.rectangles = [] + self.bmp = wx.EmptyBitmap(size[0], size[1]) + mem_dc.SelectObject(self.GetBuffer()) + mem_dc.SetPen(self.GetBackgroundPen()) + mem_dc.SetBrush(self.GetBackgroundBrush()) + mem_dc.DrawRectangle(0, 0, size[0], size[1]) + mem_dc.SetFont(self.tree.GetFont()) + self.paintWalk(node, mem_dc) + else: + mem_dc.SelectObject(self.GetBuffer()) + xstart, ystart = self.tree.CalcUnscrolledPosition(0,0) + size = self.tree.GetClientSizeTuple() + dc.Blit(xstart, ystart, size[0], size[1], mem_dc, xstart, ystart) + else: + if node == self.tree.currentRoot: + self.knobs = [] + self.rectangles = [] + dc.SetPen(self.GetBackgroundPen()) + dc.SetBrush(self.GetBackgroundBrush()) + dc.SetFont(self.tree.GetFont()) + if paintBackground: + dc.DrawRectangle(0, 0, size[0], size[1]) + if node: + #Call with not paintBackground because if we are told not to paint the + #whole background, we have to paint in parts to undo selection coloring. + pb = paintBackground + self.paintWalk(node, dc, not pb) + dc.EndDrawing() + + def GetDashPen(self): + return self.dashpen + + def SetLinePen(self, pen): + Painter.SetLinePen(self, pen) + self.dashpen = wx.Pen(pen.GetColour(), 1, wx.DOT) + + def paintWalk(self, node, dc, paintRects=0): + self.linePainter.Paint(node.parent, node, dc) + self.nodePainter.Paint(node, dc, drawRects = paintRects) + if node.expanded: + for kid in node.kids: + if not self.paintWalk(kid, dc, paintRects): + return False + for kid in node.kids: + px = (kid.projx - self.tree.layout.NODE_STEP) + 5 + py = kid.projy + kid.height/2 + if (not self.tree.model.IsLeaf(kid.data)) or ((kid.expanded or self.tree._assumeChildren) and len(kid.kids)): + dc.SetPen(self.linepen) + dc.SetBrush(self.bgbrush) + dc.DrawRectangle(px -4, py-4, 9, 9) + self.knobs.append( (kid, Rect(px -4, py -4, 9, 9)) ) + dc.SetPen(self.textpen) + if not kid.expanded: + dc.DrawLine(px, py -2, px, py + 3) + dc.DrawLine(px -2, py, px + 3, py) + if node == self.tree.currentRoot: + px = (node.projx - self.tree.layout.NODE_STEP) + 5 + py = node.projy + node.height/2 + dc.SetPen(self.linepen) + dc.SetBrush(self.bgbrush) + dc.DrawRectangle(px -4, py-4, 9, 9) + self.knobs.append( (node, Rect(px -4, py -4, 9, 9)) ) + dc.SetPen(self.textpen) + if not node.expanded: + dc.DrawLine(px, py -2, px, py + 3) + dc.DrawLine(px -2, py, px + 3, py) + return True + + def OnMouse(self, evt): + Painter.OnMouse(self, evt) + +class TreeNodePainter(NodePainter): + def Paint(self, node, dc, location = None, drawRects = 0): + text = self.painter.textConverter.Convert(node) + extent = dc.GetTextExtent(text) + node.width = extent[0] + node.height = extent[1] + if node.selected: + dc.SetPen(self.painter.GetLinePen()) + dc.SetBrush(self.painter.GetForegroundBrush()) + dc.SetTextForeground(wx.NamedColour("WHITE")) + dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3) + else: + if drawRects: + dc.SetBrush(self.painter.GetBackgroundBrush()) + dc.SetPen(self.painter.GetBackgroundPen()) + dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3) + dc.SetTextForeground(self.painter.GetTextColour()) + dc.DrawText(text, node.projx, node.projy) + self.painter.rectangles.append((node, Rect(node.projx, node.projy, node.width, node.height))) + +class TreeLinePainter(LinePainter): + def Paint(self, parent, child, dc): + dc.SetPen(self.painter.GetDashPen()) + px = py = cx = cy = 0 + if parent is None or child == self.painter.tree.currentRoot: + px = (child.projx - self.painter.tree.layout.NODE_STEP) + 5 + py = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -2 + cx = child.projx + cy = py + dc.DrawLine(px, py, cx, cy) + else: + px = parent.projx + 5 + py = parent.projy + parent.height + cx = child.projx -5 + cy = child.projy + self.painter.tree.layout.NODE_HEIGHT/2 -3 + dc.DrawLine(px, py, px, cy) + dc.DrawLine(px, cy, cx, cy) + +#>> Event defs +wxEVT_MVCTREE_BEGIN_EDIT = wx.NewEventType() #Start editing. Vetoable. +wxEVT_MVCTREE_END_EDIT = wx.NewEventType() #Stop editing. Vetoable. +wxEVT_MVCTREE_DELETE_ITEM = wx.NewEventType() #Item removed from model. +wxEVT_MVCTREE_ITEM_EXPANDED = wx.NewEventType() +wxEVT_MVCTREE_ITEM_EXPANDING = wx.NewEventType() +wxEVT_MVCTREE_ITEM_COLLAPSED = wx.NewEventType() +wxEVT_MVCTREE_ITEM_COLLAPSING = wx.NewEventType() +wxEVT_MVCTREE_SEL_CHANGED = wx.NewEventType() +wxEVT_MVCTREE_SEL_CHANGING = wx.NewEventType() #Vetoable. +wxEVT_MVCTREE_KEY_DOWN = wx.NewEventType() +wxEVT_MVCTREE_ADD_ITEM = wx.NewEventType() #Item added to model. + +EVT_MVCTREE_SEL_CHANGED = wx.PyEventBinder(wxEVT_MVCTREE_SEL_CHANGED, 1) +EVT_MVCTREE_SEL_CHANGING = wx.PyEventBinder(wxEVT_MVCTREE_SEL_CHANGING, 1) +EVT_MVCTREE_ITEM_EXPANDED = wx.PyEventBinder(wxEVT_MVCTREE_ITEM_EXPANDED, 1) +EVT_MVCTREE_ITEM_EXPANDING = wx.PyEventBinder(wxEVT_MVCTREE_ITEM_EXPANDING, 1) +EVT_MVCTREE_ITEM_COLLAPSED = wx.PyEventBinder(wxEVT_MVCTREE_ITEM_COLLAPSED, 1) +EVT_MVCTREE_ITEM_COLLAPSING = wx.PyEventBinder(wxEVT_MVCTREE_ITEM_COLLAPSING, 1) +EVT_MVCTREE_ADD_ITEM = wx.PyEventBinder(wxEVT_MVCTREE_ADD_ITEM, 1) +EVT_MVCTREE_DELETE_ITEM = wx.PyEventBinder(wxEVT_MVCTREE_DELETE_ITEM, 1) +EVT_MVCTREE_KEY_DOWN = wx.PyEventBinder(wxEVT_MVCTREE_KEY_DOWN, 1) + +class MVCTreeEvent(wx.PyCommandEvent): + def __init__(self, type, id, node = None, nodes = None, keyEvent = None, **kwargs): + apply(wx.PyCommandEvent.__init__, (self, type, id), kwargs) + self.node = node + self.nodes = nodes + self.keyEvent = keyEvent + def GetNode(self): + return self.node + def GetNodes(self): + return self.nodes + def getKeyEvent(self): + return self.keyEvent + +class MVCTreeNotifyEvent(MVCTreeEvent): + def __init__(self, type, id, node = None, nodes = None, **kwargs): + apply(MVCTreeEvent.__init__, (self, type, id, node, nodes), kwargs) + self.notify = wx.NotifyEvent(type, id) + def getNotifyEvent(self): + return self.notify + +class MVCTree(wx.ScrolledWindow): + """ + The main mvc tree class. + """ + def __init__(self, parent, id, model = None, layout = None, transform = None, + painter = None, *args, **kwargs): + apply(wx.ScrolledWindow.__init__, (self, parent, id), kwargs) + self.nodemap = {} + self._multiselect = False + self._selections = [] + self._assumeChildren = False + self._scrollx = False + self._scrolly = False + self.doubleBuffered = False + self._lastPhysicalSize = self.GetSize() + self._editors = [] + if not model: + model = BasicTreeModel() + model.SetRoot("Root") + self.SetModel(model) + if not layout: + layout = TreeLayout(self) + self.layout = layout + if not transform: + transform = NullTransform(self) + self.transform = transform + if not painter: + painter = TreePainter(self) + self.painter = painter + self.SetFont(wx.Font(9, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False)) + self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse) + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + self.doubleBuffered = True + self.Bind(wx.EVT_SIZE, self.OnSize) + self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) + self.Bind(wx.EVT_PAINT, self.OnPaint) + + + def Refresh(self): + if self.doubleBuffered: + self.painter.ClearBuffer() + wx.ScrolledWindow.Refresh(self, False) + + def GetPainter(self): + return self.painter + + def GetLayoutEngine(self): + return self.layout + + def GetTransform(self): + return self.transform + + def __repr__(self): + return "" % str(hex(id(self))) + + def __str__(self): + return self.__repr__() + + def NodeAdded(self, parent, child): + e = MVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child]) + self.GetEventHandler().ProcessEvent(e) + self.painter.ClearBuffer() + + def NodeInserted(self, parent, child, index): + e = MVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child]) + self.GetEventHandler().ProcessEvent(e) + self.painter.ClearBuffer() + + def NodeRemoved(self, node): + e = MVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM, self.GetId(), node = child, nodes = [parent, child]) + self.GetEventHandler().ProcessEvent(e) + self.painter.ClearBuffer() + + def OnKeyDown(self, evt): + e = MVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN, self.GetId(), keyEvent = evt) + self.GetEventHandler().ProcessEvent(e) + + def SetFont(self, font): + self.painter.SetFont(font) + dc = wx.ClientDC(self) + dc.SetFont(font) + self.layout.SetHeight(dc.GetTextExtent("")[1] + 18) + self.painter.ClearBuffer() + + def GetFont(self): + return self.painter.GetFont() + + def AddEditor(self, editor): + self._editors.append(editor) + + def RemoveEditor(self, editor): + self._editors.remove(editor) + + def OnMouse(self, evt): + self.painter.OnMouse(evt) + + def OnNodeClick(self, node, mouseEvent): + if node.selected and (self.IsMultiSelect() and mouseEvent.ControlDown()): + self.RemoveFromSelection(node.data) + else: + self.AddToSelection(node.data, mouseEvent.ControlDown(), mouseEvent.ShiftDown()) + + def OnKnobClick(self, node): + self.SetExpanded(node.data, not node.expanded) + + def GetDisplayText(self, node): + treenode = self.nodemap[node] + return self.painter.textConverter.Convert(treenode) + + def IsDoubleBuffered(self): + return self.doubleBuffered + + def SetDoubleBuffered(self, bool): + """ + By default MVCTree is double-buffered. + """ + self.doubleBuffered = bool + + def GetModel(self): + return self.model + + def SetModel(self, model): + """ + Completely change the data to be displayed. + """ + self.model = model + model.tree = self + self.laidOut = 0 + self.transformed = 0 + self._selections = [] + self.layoutRoot = MVCTreeNode() + self.layoutRoot.data = self.model.GetRoot() + self.layoutRoot.expanded = True + self.LoadChildren(self.layoutRoot) + self.currentRoot = self.layoutRoot + self.offset = [0,0] + self.rotation = 0 + self._scrollset = None + self.Refresh() + + def GetCurrentRoot(self): + return self.currentRoot + + def LoadChildren(self, layoutNode): + if layoutNode.built: + return + else: + self.nodemap[layoutNode.data]=layoutNode + for i in range(self.GetModel().GetChildCount(layoutNode.data)): + p = MVCTreeNode("RAW", layoutNode, []) + layoutNode.Add(p) + p.data = self.GetModel().GetChildAt(layoutNode.data, i) + self.nodemap[p.data]=p + layoutNode.built = True + if not self._assumeChildren: + for kid in layoutNode.kids: + self.LoadChildren(kid) + + def OnEraseBackground(self, evt): + pass + + def OnSize(self, evt): + size = self.GetSize() + self.center = (size.width/2, size.height/2) + if self._lastPhysicalSize.width < size.width or self._lastPhysicalSize.height < size.height: + self.painter.ClearBuffer() + self._lastPhysicalSize = size + + def GetSelection(self): + "Returns a tuple of selected nodes." + return tuple(self._selections) + + def SetSelection(self, nodeTuple): + if type(nodeTuple) != type(()): + nodeTuple = (nodeTuple,) + e = MVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return + for node in nodeTuple: + treenode = self.nodemap[node] + treenode.selected = True + for node in self._selections: + treenode = self.nodemap[node] + node.selected = False + self._selections = list(nodeTuple) + e = MVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + + def IsMultiSelect(self): + return self._multiselect + + def SetMultiSelect(self, bool): + self._multiselect = bool + + def IsSelected(self, node): + return self.nodemap[node].selected + + def Edit(self, node): + if not self.model.IsEditable(node): + return + for ed in self._editors: + if ed.CanEdit(node): + e = MVCTreeNotifyEvent(wxEVT_MVCTREE_BEGIN_EDIT, self.GetId(), node) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return + ed.Edit(node) + self._currentEditor = ed + break + + def EndEdit(self): + if self._currentEditor: + self._currentEditor.EndEdit + self._currentEditor = None + + def _EditEnding(self, node): + e = MVCTreeNotifyEvent(wxEVT_MVCTREE_END_EDIT, self.GetId(), node) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return False + self._currentEditor = None + return True + + + def SetExpanded(self, node, bool): + treenode = self.nodemap[node] + if bool: + e = MVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_EXPANDING, self.GetId(), node) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return + if not treenode.built: + self.LoadChildren(treenode) + else: + e = MVCTreeNotifyEvent(wxEVT_MVCTREE_ITEM_COLLAPSING, self.GetId(), node) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return + treenode.expanded = bool + e = None + if treenode.expanded: + e = MVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED, self.GetId(), node) + else: + e = MVCTreeEvent(wxEVT_MVCTREE_ITEM_COLLAPSED, self.GetId(), node) + self.GetEventHandler().ProcessEvent(e) + self.layout.Layout(self.currentRoot) + self.transform.Transform(self.currentRoot, self.offset, self.rotation) + self.Refresh() + + def IsExpanded(self, node): + return self.nodemap[node].expanded + + def AddToSelection(self, nodeOrTuple, enableMulti = True, shiftMulti = False): + nodeTuple = nodeOrTuple + if type(nodeOrTuple)!= type(()): + nodeTuple = (nodeOrTuple,) + e = MVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return + changeparents = [] + if not (self.IsMultiSelect() and (enableMulti or shiftMulti)): + for node in self._selections: + treenode = self.nodemap[node] + treenode.selected = False + changeparents.append(treenode) + node = nodeTuple[0] + self._selections = [node] + treenode = self.nodemap[node] + changeparents.append(treenode) + treenode.selected = True + else: + if shiftMulti: + for node in nodeTuple: + treenode = self.nodemap[node] + oldtreenode = self.nodemap[self._selections[0]] + if treenode.parent == oldtreenode.parent: + found = 0 + for kid in oldtreenode.parent.kids: + if kid == treenode or kid == oldtreenode: + found = not found + kid.selected = True + self._selections.append(kid.data) + changeparents.append(kid) + elif found: + kid.selected = True + self._selections.append(kid.data) + changeparents.append(kid) + else: + for node in nodeTuple: + try: + self._selections.index(node) + except ValueError: + self._selections.append(node) + treenode = self.nodemap[node] + treenode.selected = True + changeparents.append(treenode) + e = MVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + dc = wx.ClientDC(self) + self.PrepareDC(dc) + for node in changeparents: + if node: + self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0) + self.painter.ClearBuffer() + + def RemoveFromSelection(self, nodeTuple): + if type(nodeTuple) != type(()): + nodeTuple = (nodeTuple,) + changeparents = [] + for node in nodeTuple: + self._selections.remove(node) + treenode = self.nodemap[node] + changeparents.append(treenode) + treenode.selected = False + e = MVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), node, nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + dc = wx.ClientDC(self) + self.PrepareDC(dc) + for node in changeparents: + if node: + self.painter.Paint(dc, node, doubleBuffered = 0, paintBackground = 0) + self.painter.ClearBuffer() + + + def GetBackgroundColour(self): + if hasattr(self, 'painter') and self.painter: + return self.painter.GetBackgroundColour() + else: + return wx.Window.GetBackgroundColour(self) + def SetBackgroundColour(self, color): + if hasattr(self, 'painter') and self.painter: + self.painter.SetBackgroundColour(color) + else: + wx.Window.SetBackgroundColour(self, color) + def GetForegroundColour(self): + if hasattr(self, 'painter') and self.painter: + return self.painter.GetForegroundColour() + else: + return wx.Window.GetBackgroundColour(self) + def SetForegroundColour(self, color): + if hasattr(self, 'painter') and self.painter: + self.painter.SetForegroundColour(color) + else: + wx.Window.SetBackgroundColour(self, color) + + def SetAssumeChildren(self, bool): + self._assumeChildren = bool + + def GetAssumeChildren(self): + return self._assumeChildren + + def OnPaint(self, evt): + """ + Ensures that the tree has been laid out and transformed, then calls the painter + to paint the control. + """ + try: + self.EnableScrolling(False, False) + if not self.laidOut: + self.layout.Layout(self.currentRoot) + self.laidOut = True + self.transformed = False + if not self.transformed: + self.transform.Transform(self.currentRoot, self.offset, self.rotation) + self.transformed = True + tsize = None + tsize = list(self.transform.GetSize()) + tsize[0] = tsize[0] + 50 + tsize[1] = tsize[1] + 50 + w, h = self.GetSize() + if tsize[0] > w or tsize[1] > h: + if not hasattr(self, '_oldsize') or (tsize[0] > self._oldsize[0] or tsize[1] > self._oldsize[1]): + self._oldsize = tsize + oldstart = self.GetViewStart() + self._lastPhysicalSize = self.GetSize() + self.SetScrollbars(10, 10, tsize[0]/10, tsize[1]/10) + self.Scroll(oldstart[0], oldstart[1]) + dc = wx.PaintDC(self) + self.PrepareDC(dc) + dc.SetFont(self.GetFont()) + self.painter.Paint(dc, self.currentRoot, self.doubleBuffered) + except: + traceback.print_exc() -"""Renamer stub: provides a way to drop the wx prefix from wxPython objects.""" -__cvsid__ = "$Id$" -__revision__ = "$Revision$"[11:-2] -from wx import _rename -from wxPython.lib import mvctree -_rename(globals(), mvctree.__dict__, modulename='lib.mvctree') -del mvctree -del _rename