From e19b71646053a07255b308b24321837f8ea76176 Mon Sep 17 00:00:00 2001 From: Robin Dunn Date: Sat, 13 Nov 1999 05:52:53 +0000 Subject: [PATCH] Added a demo showing how to use wxPostEvent Added an MVCTree contribution Some final tweaks for this release git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@4536 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- utils/wxPython/README.txt | 10 +- utils/wxPython/demo/Main.py | 3 +- utils/wxPython/demo/Threads.py | 216 +++++++ utils/wxPython/lib/mvctree.py | 1111 ++++++++++++++++++++++++++++++++ utils/wxPython/src/helpers.cpp | 8 +- utils/wxPython/src/helpers.h | 10 +- 6 files changed, 1343 insertions(+), 15 deletions(-) create mode 100644 utils/wxPython/demo/Threads.py create mode 100644 utils/wxPython/lib/mvctree.py diff --git a/utils/wxPython/README.txt b/utils/wxPython/README.txt index 103e9298ea..d884e91b57 100644 --- a/utils/wxPython/README.txt +++ b/utils/wxPython/README.txt @@ -45,6 +45,7 @@ Or you can send mail directly to the list using this address: ---------------------------------------------------------------------- + What's new in 2.1.11 -------------------- Skipped a few version numbers so wxMSW, wxGTK and wxPython are all @@ -88,13 +89,12 @@ Many updates to wxMenu, wxMenuBar. wxPyEvent and wxPyCommandEvent derived classes now give you the actual Python object in the event handler instead of a new shadow. -Added a Calendar widget from Lorne White. - -Made some fixes to the wxFloatbar. - -Added the HTML printing classes. +Added a Calendar widget from Lorne White to the library. +Made some fixes to the wxFloatbar. It still has some troubles on +wxGTK... +Added an MVC tree control from Bryn Keller to the library. diff --git a/utils/wxPython/demo/Main.py b/utils/wxPython/demo/Main.py index d3c072c009..5df81877ac 100644 --- a/utils/wxPython/demo/Main.py +++ b/utils/wxPython/demo/Main.py @@ -42,7 +42,8 @@ _treeList = [ ('Miscellaneous', [ 'DragAndDrop', 'CustomDragAndDrop', 'FontEnumerator', 'wxTimer', 'wxValidator', 'wxGLCanvas', 'DialogUnits', - 'wxImage', 'PrintFramework', 'wxOGL', 'PythonEvents']), + 'wxImage', 'PrintFramework', 'wxOGL', 'PythonEvents', + 'Threads']), ('wxPython Library', ['Layoutf', 'wxScrolledMessageDialog', 'wxMultipleChoiceDialog', 'wxPlotCanvas', 'wxFloatBar', diff --git a/utils/wxPython/demo/Threads.py b/utils/wxPython/demo/Threads.py new file mode 100644 index 0000000000..75a96e7869 --- /dev/null +++ b/utils/wxPython/demo/Threads.py @@ -0,0 +1,216 @@ + +from wxPython.wx import * + +import thread +import time +from whrandom import random + +#---------------------------------------------------------------------- + +wxEVT_UPDATE_BARGRAPH = 25015 + +def EVT_UPDATE_BARGRAPH(win, func): + win.Connect(-1, -1, wxEVT_UPDATE_BARGRAPH, func) + + +class UpdateBarEvent(wxPyEvent): + def __init__(self, barNum, value): + wxPyEvent.__init__(self) + self.SetEventType(wxEVT_UPDATE_BARGRAPH) + self.barNum = barNum + self.value = value + + +#---------------------------------------------------------------------- + +class CalcBarThread: + def __init__(self, win, barNum, val): + self.win = win + self.barNum = barNum + self.val = val + + def Start(self): + self.keepGoing = self.running = true + thread.start_new_thread(self.Run, ()) + + def Stop(self): + self.keepGoing = false + + def IsRunning(self): + return self.running + + def Run(self): + while self.keepGoing: + evt = UpdateBarEvent(self.barNum, int(self.val)) + wxPostEvent(self.win, evt) + del evt + + sleeptime = (random() * 2) + 0.5 + #print self.barNum, 'sleeping for', sleeptime + time.sleep(sleeptime) + + sleeptime = sleeptime * 5 + if int(random() * 2): + self.val = self.val + sleeptime + else: + self.val = self.val - sleeptime + + if self.val < 0: self.val = 0 + if self.val > 300: self.val = 300 + + self.running = false + +#---------------------------------------------------------------------- + + +class GraphWindow(wxWindow): + def __init__(self, parent, labels): + wxWindow.__init__(self, parent, -1) + + self.values = [] + for label in labels: + self.values.append((label, 0)) + + self.font = wxFont(12, wxSWISS, wxNORMAL, wxBOLD) + self.SetFont(self.font) + + self.colors = [ wxRED, wxGREEN, wxBLUE, wxCYAN] #, wxNamedColour("Yellow") ] + + + def SetValue(self, index, value): + assert index < len(self.values) + cur = self.values[index] + self.values[index:index+1] = [(cur[0], value)] + + + def SetFont(self, font): + wxWindow.SetFont(self, font) + wmax = hmax = 0 + for label, val in self.values: + w,h = self.GetTextExtent(label) + if w > wmax: wmax = w + if h > hmax: hmax = h + self.linePos = wmax + 10 + self.barHeight = hmax + + + def OnPaint(self, evt): + size = self.GetSize() + dc = wxPaintDC(self) + dc.BeginDrawing() + dc.SetFont(self.font) + dc.SetTextForeground(wxBLUE) + dc.SetPen(wxPen(wxBLACK, 3, wxSOLID)) + dc.DrawLine(self.linePos, 0, self.linePos, size.height-10) + + bh = ypos = self.barHeight + for x in range(len(self.values)): + label, val = self.values[x] + dc.DrawText(label, 5, ypos) + + if val: + color = self.colors[ x % len(self.colors) ] + dc.SetPen(wxPen(color)) + dc.SetBrush(wxBrush(color)) + dc.DrawRectangle(self.linePos+3, ypos, val, bh) + + ypos = ypos + 2*bh + if ypos > size.height-10: + break + + dc.EndDrawing() + + +#---------------------------------------------------------------------- + +class TestFrame(wxFrame): + def __init__(self, parent, log): + wxFrame.__init__(self, parent, -1, "Thread Test", size=(450,300)) + self.log = log + + #self.CenterOnParent() + + panel = wxPanel(self, -1) + panel.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD)) + wxStaticText(panel, -1, + "This demo shows multiple threads interacting with this\n" + "window by sending events to it.", wxPoint(5,5)) + panel.Fit() + + self.graph = GraphWindow(self, ['Zero', 'One', 'Two', 'Three']) + + sizer = wxBoxSizer(wxVERTICAL) + sizer.Add(panel, 0, wxEXPAND) + sizer.Add(self.graph, 1, wxEXPAND) + + self.SetSizer(sizer) + self.SetAutoLayout(true) + + #self.graph.SetValue(0, 25) + #self.graph.SetValue(1, 50) + #self.graph.SetValue(2, 75) + #self.graph.SetValue(3, 100) + + EVT_UPDATE_BARGRAPH(self, self.OnUpdate) + self.threads = [] + self.threads.append(CalcBarThread(self, 0, 25)) + self.threads.append(CalcBarThread(self, 1, 50)) + self.threads.append(CalcBarThread(self, 2, 75)) + self.threads.append(CalcBarThread(self, 3, 100)) + + for t in self.threads: + t.Start() + + + + def OnUpdate(self, evt): + self.graph.SetValue(evt.barNum, evt.value) + self.graph.Refresh() + + + def OnCloseWindow(self, evt): + busy = wxBusyInfo("One moment please, waiting for threads to die...") + for t in self.threads: + t.Stop() + running = 1 + while running: + running = 0 + for t in self.threads: + running = running + t.IsRunning() + time.sleep(0.1) + self.Destroy() + + + +#---------------------------------------------------------------------- + +def runTest(frame, nb, log): + win = TestFrame(frame, log) + frame.otherWin = win + win.Show(true) + return None + +#---------------------------------------------------------------------- + + + + +overview = """\ +The main issue with multi-threaded GUI programming is the thread safty +of the GUI itself. On most platforms the GUI is not thread safe and +so any cross platform GUI Toolkit and applications written with it +need to take that into account. + +The solution is to only allow interaction with the GUI from a single +thread, but this often severly limits what can be done in an +application and makes it difficult to use additional threads at all. + +Since wxPython already makes extensive use of event handlers, it is a +logical extension to allow events to be sent to GUI objects from +alternate threads. A function called wxPostEvent allows you to do +this. It accepts an event and an event handler (window) and instead +of sending the event immediately in the current context like +ProcessEvent does, it processes it later from the context of the GUI +thread. + +""" diff --git a/utils/wxPython/lib/mvctree.py b/utils/wxPython/lib/mvctree.py new file mode 100644 index 0000000000..49eb4b7980 --- /dev/null +++ b/utils/wxPython/lib/mvctree.py @@ -0,0 +1,1111 @@ +""" +wxMVCTree 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. +This module contains the wxMVCTree class (the 'controller' of the MVC trio) +and PathfinderNode, which it uses internally to manage its info. + +Pathfinder actually is even more configurable than MVC normally implies, because +almost every aspect of it is pluggable: + wxMVCTree - 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 +""" + +#------------------------------------------------------------------------ +from wxPython.wx import * +import os, sys +#------------------------------------------------------------------------ + +class MVCTreeNode: + """ + Used internally by wxMVCTree 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 = [], x = 0, y = 0): + self.x = 0 + self.y = 0 + self.projx = 0 + self.projy = 0 + self.parent = parent + self.kids = 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 + +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 + +class Painter: + """ + This is the interface that wxMVCTree expects from painters. All painters should + be Painter subclasses. + """ + def __init__(self, tree): + self.tree = tree + self.textcolor = wxNamedColour("BLACK") + self.bgcolor = wxNamedColour("WHITE") + self.fgcolor = wxNamedColour("BLUE") + self.linecolor = wxNamedColour("GREY") + self.font = wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, false) + self.knobs = [] + self.rectangles = [] + self.minx = self.maxx = self.miny = self.maxy = 0 + + def GetFont(self): + return self.font + + def SetFont(self, font): + self.font = font + self.tree.Refresh() + + def paint(self, dc, node): + raise NotImplementedError + def GetTextColour(self): + return self.textcolor + def SetTextColour(self, color): + self.textcolor = color + self.textbrush = wxBrush(color) + self.textpen = wxPen(color, 1, wxSOLID) + def GetBackgroundColour(self): + return self.bgcolor + def SetBackgroundColour(self, color): + self.bgcolor = color + self.bgbrush = wxBrush(color) + self.bgpen = wxPen(color, 1, wxSOLID) + def GetForegroundColour(self): + return self.fgcolor + def SetForegroundColour(self, color): + self.fgcolor = color + self.fgbrush = wxBrush(color) + self.fgpen = wxPen(color, 1, wxSOLID) + def GetLineColour(self): + return self.linecolor + def SetLineColour(self, color): + self.linecolor = color + self.linebrush = wxBrush(color) + self.linepen = wxPen( color, 1, wxSOLID) + 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(): + for item in self.rectangles: + if item[1].contains((evt.GetX(), evt.GetY())): + self.tree.Edit(item[0].data) + self.tree.OnNodeClick(item[0], evt) + return + elif evt.ButtonDown(): + #self.oldpos = (evt.GetX(), evt.GetY()) + for item in self.rectangles: + if item[1].contains((evt.GetX(), evt.GetY())): + self.tree.OnNodeClick(item[0], evt) + return + for item in self.knobs: + if item[1].contains((evt.GetX(), evt.GetY())): + self.tree.OnKnobClick(item[0]) + return + evt.Skip() + + +class wxTreeModel: + """ + 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(wxTreeModel): + """ + 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) + wxTreeModel.AddChild(self, parent, child) + return child + + def RemoveNode(self, node): + parent = self.parents[node] + del self.parents[node] + self.children[parent].remove(node) + wxTreeModel.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) + wxTreeModel.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 + EVT_KEY_DOWN(self.editcomp, self._key) + EVT_LEFT_DOWN(self.editcomp, 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: + import traceback;traceback.print_exc() + self.editcomp.ReleaseMouse() + self.editcomp.Destroy() + del self.editcomp + + + def _key(self, evt): + if evt.KeyCode() == WXK_RETURN: + self.EndEdit(true) + elif evt.KeyCode() == WXK_ESCAPE: + self.EndEdit(false) + else: + evt.Skip() + + def _mdown(self, evt): + if evt.IsButton(): + pos = evt.GetPosition() + print pos.x, pos.y + edsize = self.editcomp.GetSize() + if pos.x < 0 or pos.y < 0 or pos.x > edsize.width or pos.y > edsize.height: + 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) + import string + fw = FileWrapper(path, string.split(path, 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) + import string + name = string.split(path, os.sep)[-1] + pathpart = path[:-len(name)] + print pathpart + 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 transform(self, node, offset, rotation): + node.projx = node.x + offset[0] + node.projy = node.y + offset[1] + for kid in node.kids: + self.transform(kid, offset, rotation) + +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 + def layout(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 + if node.expanded: + for kid in node.kids: + kid.level = node.level + 1 + self.layout(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): + 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 = wxPen(self.GetTextColour(), 1, wxSOLID) + self.fgpen = wxPen(self.GetForegroundColour(), 1, wxSOLID) + self.bgpen = wxPen(self.GetBackgroundColour(), 1, wxSOLID) + self.linepen = wxPen(self.GetLineColour(), 1, wxSOLID) + self.dashpen = wxPen(self.GetLineColour(), 1, wxDOT) + self.textbrush = wxBrush(self.GetTextColour(), wxSOLID) + self.fgbrush = wxBrush(self.GetForegroundColour(), wxSOLID) + self.bgbrush = wxBrush(self.GetBackgroundColour(), wxSOLID) + self.linebrush = wxPen(self.GetLineColour(), 1, wxSOLID) + self.rectangles = [] + self.knobs = [] + dc.BeginDrawing() + dc.SetPen(self.GetBackgroundPen()) + dc.SetBrush(self.GetBackgroundBrush()) + size = self.tree.GetSize() + dc.DrawRectangle(0, 0, size.width, size.height) + if node: + self.paintWalk(node, dc) + dc.EndDrawing() + + def GetDashPen(self): + return self.dashpen + + def SetLinePen(self, pen): + Painter.SetLinePen(self, pen) + self.dashpen = wxPen(pen.GetColour(), 1, wxDOT) + + def drawBox(self, px, py, node, dc): + if self.tree.model.IsLeaf(node.data) or ((node.expanded or not self.tree._assumeChildren) and not len(node.kids)): + return + 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) + + def paintWalk(self, node, dc): + self.linePainter.paint(node.parent, node, dc) + self.nodePainter.paint(node, dc) + if node.expanded: + for kid in node.kids: + if not self.paintWalk(kid, dc): + return false + for kid in node.kids: + px = (kid.projx - self.tree.layout.NODE_STEP) + 5 + py = kid.projy + kid.height/2 + self.drawBox(px, py, kid, dc) + if node == self.tree.currentRoot: + px = (node.projx - self.tree.layout.NODE_STEP) + 5 + py = node.projy + node.height/2 + self.drawBox(px, py, node, dc) + return true + + def OnMouse(self, evt): + Painter.OnMouse(self, evt) + +class TreeNodePainter(NodePainter): + def paint(self, node, dc, location = None): + text = self.painter.textConverter.convert(node) + extent = dc.GetTextExtent(text) + node.width = extent[0] + node.height = extent[1] + if node == self.painter.tree.currentRoot: + self.painter.minx = self.painter.maxx = self.painter.miny = self.painter.maxy = 0 + if node.projx < self.painter.minx: + self.painter.minx = node.projx + elif node.projx + node.width > self.painter.maxx: + self.painter.maxx = node.projx + node.width + if node.projy < self.painter.miny: + self.painter.miny = node.projy + elif node.projy + node.height > self.painter.maxy: + self.painter.maxy = node.projy + node.height + if node.selected: + dc.SetPen(self.painter.GetLinePen()) + dc.SetBrush(self.painter.GetForegroundBrush()) + dc.SetTextForeground(wxNamedColour("WHITE")) + dc.DrawRectangle(node.projx -1, node.projy -1, node.width + 3, node.height + 3) + else: + 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 + 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 + dc.DrawLine(px, py, px, cy) + dc.DrawLine(px, cy, cx, cy) + + +wxEVT_MVCTREE_BEGIN_EDIT = 20204 #Start editing. Vetoable. +wxEVT_MVCTREE_END_EDIT = 20205 #Stop editing. Vetoable. +wxEVT_MVCTREE_DELETE_ITEM = 20206 #Item removed from model. +wxEVT_MVCTREE_ITEM_EXPANDED = 20209 +wxEVT_MVCTREE_ITEM_EXPANDING = 20210 +wxEVT_MVCTREE_ITEM_COLLAPSED = 20211 +wxEVT_MVCTREE_ITEM_COLLAPSING = 20212 +wxEVT_MVCTREE_SEL_CHANGED = 20213 +wxEVT_MVCTREE_SEL_CHANGING = 20214 #Vetoable. +wxEVT_MVCTREE_KEY_DOWN = 20215 +wxEVT_MVCTREE_ADD_ITEM = 20216 #Item added to model. + +def EVT_MVCTREE_SEL_CHANGED(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGED, func) + +def EVT_MVCTREE_SEL_CHANGING(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_SEL_CHANGING, func) + +def EVT_MVCTREE_ITEM_EXPANDED(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDED, func) + +def EVT_MVCTREE_ITEM_EXPANDING(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_ITEM_EXPANDING, func) + +def EVT_MVCTREE_ITEM_COLLAPSED(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSED, func) + +def EVT_MVCTREE_ITEM_COLLAPSING(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_ITEM_COLLAPSING, func) + +def EVT_MVCTREE_ADD_ITEM(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_ADD_ITEM, func) + +def EVT_MVCTREE_DELETE_ITEM(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_DELETE_ITEM, func) + +def EVT_MVCTREE_KEY_DOWN(win, id, func): + win.Connect(id, -1, wxEVT_MVCTREE_KEY_DOWN, func) + + +class wxMVCTreeEvent(wxPyCommandEvent): + def __init__(self, type, id, node = None, nodes = None, keyEvent = None, **kwargs): + apply(wxPyCommandEvent.__init__, (self, type, id), kwargs) + self.node = node + self.nodes = nodes + self.keyEvent = keyEvent + + +class wxMVCTreeNotifyEvent(wxMVCTreeEvent): + def __init__(self, type, id, node = None, nodes = None, **kwargs): + apply(wxMVCTreeEvent.__init__, (self, type, id), kwargs) + self.notify = wxNotifyEvent(type, id) + +class wxMVCTree(wxWindow): + """ + The main mvcTree class. + """ + def __init__(self, parent, id, model = None, layout = None, transform = None, + painter = None, *args, **kwargs): + apply(wxWindow.__init__, (self, parent, id), kwargs) + self.nodemap = {} + self._multiselect = false + self._selections = [] + self._assumeChildren = false + self._scrollx = false + self._scrolly = false + self.doubleBuffered = true + 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(wxFont(9, wxDEFAULT, wxNORMAL, wxNORMAL, false)) + EVT_MOUSE_EVENTS(self, self.OnMouse) + EVT_SCROLLWIN(self, self.OnScroll) + EVT_KEY_DOWN(self, self.OnKeyDown) + + def __repr__(self): + return "" % str(hex(id(self))) + + def __str__(self): + return self.__repr__() + + def NodeAdded(self, parent, child): + e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child]) + self.GetEventHandler().ProcessEvent(e) + + def NodeInserted(self, parent, child, index): + e = wxMVCTreeEvent(wxEVT_MVCTREE_ADD_ITEM, self.GetId(), node = child, nodes = [parent, child]) + self.GetEventHandler().ProcessEvent(e) + + def NodeRemoved(self, node): + e = wxMVCTreeEvent(wxEVT_MVCTREE_DELETE_ITEM, self.GetId(), node = child, nodes = [parent, child]) + self.GetEventHandler().ProcessEvent(e) + + def OnKeyDown(self, evt): + e = wxMVCTreeEvent(wxEVT_MVCTREE_KEY_DOWN, self.GetId(), keyEvent = evt) + self.GetEventHandler().ProcessEvent(e) + + def SetFont(self, font): + self.painter.SetFont(font) + dc = wxClientDC(self) + dc.SetFont(font) + self.layout.SetHeight(dc.GetTextExtent("")[1] + 18) + + 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: + self.RemoveFromSelection(node.data) + else: + self.AddToSelection(node.data, mouseEvent.ControlDown()) + self.Refresh() + + 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 wxMVCTree 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.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): + try: + size = self.GetSizeTuple() + self.center = (size[0]/2, size[1]/2) + del self.bmp + except: + pass + + def GetSelection(self): + "Returns a tuple of selected nodes." + return tuple(self._selections) + + def SetSelection(self, nodeTuple): + if type(nodeTuple) != type(()): + nodeTuple = (nodeTuple,) + e = wxMVCTreeNotifyEvent(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 = wxMVCTreeEvent(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 = wxMVCTreeNotifyEvent(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 = wxMVCTreeNotifyEvent(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 = wxMVCTreeNotifyEvent(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 = wxMVCTreeNotifyEvent(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 = wxMVCTreeEvent(wxEVT_MVCTREE_ITEM_EXPANDED, self.GetId(), node) + else: + e = wxMVCTreeEvent(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): + nodeTuple = nodeOrTuple + if type(nodeOrTuple)!= type(()): + nodeTuple = (nodeOrTuple,) + e = wxMVCTreeNotifyEvent(wxEVT_MVCTREE_SEL_CHANGING, self.GetId(), nodeTuple[0], nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + if not e.notify.IsAllowed(): + return + if not self.IsMultiSelect() or not enableMulti: + for node in self._selections: + treenode = self.nodemap[node] + treenode.selected = false + node = nodeTuple[0] + self._selections = [node] + treenode = self.nodemap[node] + treenode.selected = true + else: + for node in nodeTuple: + try: + self._selections.index(node) + except ValueError: + self._selections.append(node) + treenode = self.nodemap[node] + treenode.selected = true + e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), nodeTuple[0], nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + + def RemoveFromSelection(self, nodeTuple): + if type(nodeTuple) != type(()): + nodeTuple = (nodeTuple,) + for node in nodeTuple: + try: + self._selections.index(node) + except IndexError: + self._selections.remove(node) + treenode = self.nodemap[node] + node.selected = false + e = wxMVCTreeEvent(wxEVT_MVCTREE_SEL_CHANGED, self.GetId(), node, nodes = nodeTuple) + self.GetEventHandler().ProcessEvent(e) + + + + def GetBackgroundColour(self): + if hasattr(self, 'painter') and self.painter: + return self.painter.GetBackgroundColour() + else: + return wxWindow.GetBackgroundColour(self) + def SetBackgroundColour(self, color): + if hasattr(self, 'painter') and self.painter: + self.painter.SetBackgroundColour(color) + else: + wxWindow.SetBackgroundColour(self, color) + def GetForegroundColour(self): + if hasattr(self, 'painter') and self.painter: + return self.painter.GetForegroundColour() + else: + return wxWindow.GetBackgroundColour(self) + def SetForegroundColour(self, color): + if hasattr(self, 'painter') and self.painter: + self.painter.SetForegroundColour(color) + else: + wxWindow.SetBackgroundColour(self, color) + + def SetAssumeChildren(self, bool): + self._assumeChildren = bool + + def GetAssumeChildren(self): + return self._assumeChildren + + def OnScroll(self, evt): + type = evt.GetEventType() + field = [self.painter.maxx - self.painter.minx, self.painter.maxy - self.painter.miny] + size = self.GetSizeTuple() + index = 1 + if evt.GetOrientation() == wxHORIZONTAL: + index = 0 + self._scrollx = true + else: + self._scrolly = true + index = 1 + if type == wxEVT_SCROLLWIN_TOP: + self.offset[index] = 0 + elif type == wxEVT_SCROLLWIN_LINEUP: + self.offset[index] = self.offset[index] + 1 + elif type == wxEVT_SCROLLWIN_LINEDOWN: + self.offset[index] = self.offset[index] - 1 + elif type == wxEVT_SCROLLWIN_PAGEUP: + self.offset[index] = self.offset[index] + int(20 * float(field[index])/float(size[index])) + elif type == wxEVT_SCROLLWIN_PAGEDOWN: + self.offset[index] = self.offset[index] - int(20 * float(field[index])/float(size[index])) + elif type == wxEVT_SCROLLWIN_THUMBTRACK: + self.offset[index] = -(evt.GetPosition()) + elif type == wxEVT_SCROLLWIN_BOTTOM: + self.offset[index] = field[index] + self.transformed = false + self.Refresh() + + def OnPaint(self, evt): + """ + Ensures that the tree has been laid out and transformed, then calls the painter + to paint the control. + """ + try: + if not self.laidOut: + self.layout.layout(self.currentRoot) + self.laidOut = true + if not self.transformed: + self.transform.transform(self.currentRoot, self.offset, self.rotation) + self.transformed = true + dc = wxPaintDC(self) + dc.SetFont(self.GetFont()) + if self.doubleBuffered: + size = self.GetSize() + if not hasattr(self, 'bmp'): + self.bmp = bmp =wxEmptyBitmap(size.width, size.height) + else: + bmp = self.bmp + mem_dc = wxMemoryDC() + mem_dc.SetFont(self.GetFont()) + mem_dc.SelectObject(bmp) + self.painter.paint(mem_dc, self.currentRoot) + dc.Blit(0, 0, size.width, size.height, mem_dc, 0, 0); + else: + self.painter.paint(dc, self.currentRoot) + size = self.GetSizeTuple() + if self._scrollx or self.painter.minx < 0 or self.painter.maxx > size[0]: + field = self.painter.maxx - self.painter.minx + self.SetScrollbar(wxHORIZONTAL, -self.offset[0], size[0]/field, field, true) + self._scrollx = false + if self._scrolly or self.painter.miny < 0 or self.painter.maxy > size[1]: + field = self.painter.maxy - self.painter.miny + self.SetScrollbar(wxVERTICAL, -self.offset[1], size[1]/field, field, true) + self._scrolly = false + except: + import traceback;traceback.print_exc() + +if __name__ == '__main__': + def exit(evt): + import sys;sys.exit() + + block = 0 + + def selchanging(evt): + print "SelChanging!" + print evt.node + global block + if block: + evt.notify.Veto() + block = not block + + def selchanged(evt): + print "SelChange!" + print evt.node + def expanded(evt): + print "Expanded!" + def closed(evt): + print "Closed!" + def key(evt): + print "Key" + def add(evt): + print "Add" + def delitem(evt): + print "Delete" + + class MyApp(wxApp): + def OnInit(self): + f = wxFrame(NULL, -1, "wxMVCTree") + p = None + p = wxMVCTree(f, -1) + p.SetAssumeChildren(true) + if len(sys.argv) > 1: + p.SetModel(LateFSTreeModel(sys.argv[1])) + p.AddEditor(FileEditor(p)) + p.SetMultiSelect(true) + f.Show(true) + EVT_CLOSE(f, exit) + EVT_MVCTREE_SEL_CHANGED(p, p.GetId(), selchanged) + EVT_MVCTREE_SEL_CHANGING(p, p.GetId(), selchanging) + EVT_MVCTREE_ITEM_EXPANDED(p, p.GetId(), expanded) + EVT_MVCTREE_ITEM_COLLAPSED(p, p.GetId(), closed) + EVT_MVCTREE_ADD_ITEM(p, p.GetId(), add) + EVT_MVCTREE_DELETE_ITEM(p, p.GetId(), delitem) + EVT_MVCTREE_KEY_DOWN(p, p.GetId(), key) + p.SetForegroundColour(wxNamedColour("GREEN")) + self.SetTopWindow(f) + return true + + app = MyApp(false) + app.MainLoop() + + + + + + + + + + + diff --git a/utils/wxPython/src/helpers.cpp b/utils/wxPython/src/helpers.cpp index d73f04007d..2d0466944a 100644 --- a/utils/wxPython/src/helpers.cpp +++ b/utils/wxPython/src/helpers.cpp @@ -450,20 +450,20 @@ PyObject* wxPyCallbackHelper::callCallbackObj(PyObject* argTuple) { // themselves and some special case handling in wxPyCallback::EventThunker. -wxPySelfRef::wxPySelfRef() { +wxPyEvtSelfRef::wxPyEvtSelfRef() { //m_self = Py_None; // **** We don't do normal ref counting to prevent //Py_INCREF(m_self); // circular loops... m_cloned = false; } -wxPySelfRef::~wxPySelfRef() { +wxPyEvtSelfRef::~wxPyEvtSelfRef() { bool doSave = wxPyRestoreThread(); if (m_cloned) Py_DECREF(m_self); wxPySaveThread(doSave); } -void wxPySelfRef::SetSelf(PyObject* self, bool clone) { +void wxPyEvtSelfRef::SetSelf(PyObject* self, bool clone) { bool doSave = wxPyRestoreThread(); if (m_cloned) Py_DECREF(m_self); @@ -475,7 +475,7 @@ void wxPySelfRef::SetSelf(PyObject* self, bool clone) { wxPySaveThread(doSave); } -PyObject* wxPySelfRef::GetSelf() const { +PyObject* wxPyEvtSelfRef::GetSelf() const { Py_INCREF(m_self); return m_self; } diff --git a/utils/wxPython/src/helpers.h b/utils/wxPython/src/helpers.h index 0d39ba0038..bcf992aca6 100644 --- a/utils/wxPython/src/helpers.h +++ b/utils/wxPython/src/helpers.h @@ -189,10 +189,10 @@ private: // themselves and some special case handling in wxPyCallback::EventThunker. -class wxPySelfRef { +class wxPyEvtSelfRef { public: - wxPySelfRef(); - ~wxPySelfRef(); + wxPyEvtSelfRef(); + ~wxPyEvtSelfRef(); void SetSelf(PyObject* self, bool clone=FALSE); PyObject* GetSelf() const; @@ -203,7 +203,7 @@ protected: }; -class wxPyEvent : public wxEvent, public wxPySelfRef { +class wxPyEvent : public wxEvent, public wxPyEvtSelfRef { DECLARE_DYNAMIC_CLASS(wxPyEvent) public: wxPyEvent(int id=0); @@ -213,7 +213,7 @@ public: }; -class wxPyCommandEvent : public wxCommandEvent, public wxPySelfRef { +class wxPyCommandEvent : public wxCommandEvent, public wxPyEvtSelfRef { DECLARE_DYNAMIC_CLASS(wxPyCommandEvent) public: wxPyCommandEvent(wxEventType commandType = wxEVT_NULL, int id=0); -- 2.45.2