# --------------------------------------------------------------------------------- # # CUSTOMTREECTRL wxPython IMPLEMENTATION # Inspired By And Heavily Based On wxGenericTreeCtrl. # # Andrea Gavana, @ 17 May 2006 # Latest Revision: 26 May 2006, 22.30 CET # # # TODO List # # Almost All The Features Of wx.TreeCtrl Are Available, And There Is Practically # No Limit In What Could Be Added To This Class. The First Things That Comes # To My Mind Are: # # 1. Implement The Style TR_EXTENDED (I Have Never Used It, But It May Be Useful). # # 2. Add Support For 3-State CheckBoxes (Is That Really Useful?). # # 3. Try To Implement A More Flicker-Free Background Image In Cases Like # Centered Or Stretched Image (Now CustomTreeCtrl Supports Only Tiled # Background Images). # # 4. Try To Mimic Windows wx.TreeCtrl Expanding/Collapsing behaviour: CustomTreeCtrl # Suddenly Expands/Collapses The Nodes On Mouse Click While The Native Control # Has Some Kind Of "Smooth" Expanding/Collapsing, Like A Wave. I Don't Even # Know Where To Start To Do That. # # 5. Speed Up General OnPaint Things? I Have No Idea, Here CustomTreeCtrl Is Quite # Fast, But We Should See On Slower Machines. # # # For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please # Write To Me At: # # gavana@kpo.kz # andrea.gavana@gmail.com # # Or, Obviously, To The wxPython Mailing List!!! # # # End Of Comments # --------------------------------------------------------------------------------- # """ Description =========== CustomTreeCtrl is a class that mimics the behaviour of wx.TreeCtrl, with almost the same base functionalities plus some more enhancements. This class does not rely on the native control, as it is a full owner-drawn tree control. Apart of the base functionalities of CustomTreeCtrl (described below), in addition to the standard wx.TreeCtrl behaviour this class supports: * CheckBox-type items: checkboxes are easy to handle, just selected or unselected state with no particular issues in handling the item's children; * RadioButton-type items: since I elected to put radiobuttons in CustomTreeCtrl, I needed some way to handle them, that made sense. So, I used the following approach: - All peer-nodes that are radiobuttons will be mutually exclusive. In other words, only one of a set of radiobuttons that share a common parent can be checked at once. If a radiobutton node becomes checked, then all of its peer radiobuttons must be unchecked. - If a radiobutton node becomes unchecked, then all of its child nodes will become inactive. * Hyperlink-type items: they look like an hyperlink, with the proper mouse cursor on hovering. * Multiline text items. * Enabling/disabling items (together with their plain or grayed out icons). * Whatever non-toplevel widget can be attached next to an item. * Default selection style, gradient (horizontal/vertical) selection style and Windows Vista selection style. * Customized drag and drop images built on the fly. * Setting the CustomTreeCtrl item buttons to a personalized imagelist. * Setting the CustomTreeCtrl check/radio item icons to a personalized imagelist. * Changing the style of the lines that connect the items (in terms of wx.Pen styles). * Using an image as a CustomTreeCtrl background (currently only in "tile" mode). And a lot more. Check the demo for an almost complete review of the functionalities. Base Functionalities ==================== CustomTreeCtrl supports all the wx.TreeCtrl styles, except: - TR_EXTENDED: supports for this style is on the todo list (Am I sure of this?). Plus it has 3 more styles to handle checkbox-type items: - TR_AUTO_CHECK_CHILD : automatically checks/unchecks the item children; - TR_AUTO_CHECK_PARENT : automatically checks/unchecks the item parent; - TR_AUTO_TOGGLE_CHILD: automatically toggles the item children. All the methods available in wx.TreeCtrl are also available in CustomTreeCtrl. Events ====== All the events supported by wx.TreeCtrl are also available in CustomTreeCtrl, with a few exceptions: - EVT_TREE_GET_INFO (don't know what this means); - EVT_TREE_SET_INFO (don't know what this means); - EVT_TREE_ITEM_MIDDLE_CLICK (not implemented, but easy to add); - EVT_TREE_STATE_IMAGE_CLICK: no need for that, look at the checking events below. Plus, CustomTreeCtrl supports the events related to the checkbutton-type items: - EVT_TREE_ITEM_CHECKING: an item is being checked; - EVT_TREE_ITEM_CHECKED: an item has been checked. And to hyperlink-type items: - EVT_TREE_ITEM_HYPERLINK: an hyperlink item has been clicked (this event is sent after the EVT_TREE_SEL_CHANGED event). Supported Platforms =================== CustomTreeCtrl has been tested on the following platforms: * Windows (Windows XP); * GTK (Thanks to Michele Petrazzo); * Mac OS (Thanks to John Jackson). Latest Revision: Andrea Gavana @ 26 May 2006, 22.30 CET Version 0.8 """ import wx import zlib import cStringIO # ---------------------------------------------------------------------------- # Constants # ---------------------------------------------------------------------------- _NO_IMAGE = -1 _PIXELS_PER_UNIT = 10 # Start editing the current item after half a second (if the mouse hasn't # been clicked/moved) _DELAY = 500 # ---------------------------------------------------------------------------- # Constants # ---------------------------------------------------------------------------- # Enum for different images associated with a treectrl item TreeItemIcon_Normal = 0 # not selected, not expanded TreeItemIcon_Selected = 1 # selected, not expanded TreeItemIcon_Expanded = 2 # not selected, expanded TreeItemIcon_SelectedExpanded = 3 # selected, expanded TreeItemIcon_Checked = 0 # check button, checked TreeItemIcon_NotChecked = 1 # check button, not checked TreeItemIcon_Flagged = 2 # radio button, selected TreeItemIcon_NotFlagged = 3 # radio button, not selected # ---------------------------------------------------------------------------- # CustomTreeCtrl flags # ---------------------------------------------------------------------------- TR_NO_BUTTONS = wx.TR_NO_BUTTONS # for convenience TR_HAS_BUTTONS = wx.TR_HAS_BUTTONS # draw collapsed/expanded btns TR_NO_LINES = wx.TR_NO_LINES # don't draw lines at all TR_LINES_AT_ROOT = wx.TR_LINES_AT_ROOT # connect top-level nodes TR_TWIST_BUTTONS = wx.TR_TWIST_BUTTONS # still used by wxTreeListCtrl TR_SINGLE = wx.TR_SINGLE # for convenience TR_MULTIPLE = wx.TR_MULTIPLE # can select multiple items TR_EXTENDED = wx.TR_EXTENDED # TODO: allow extended selection TR_HAS_VARIABLE_ROW_HEIGHT = wx.TR_HAS_VARIABLE_ROW_HEIGHT # what it says TR_EDIT_LABELS = wx.TR_EDIT_LABELS # can edit item labels TR_ROW_LINES = wx.TR_ROW_LINES # put border around items TR_HIDE_ROOT = wx.TR_HIDE_ROOT # don't display root node TR_FULL_ROW_HIGHLIGHT = wx.TR_FULL_ROW_HIGHLIGHT # highlight full horz space TR_AUTO_CHECK_CHILD = 0x04000 # only meaningful for checkboxes TR_AUTO_TOGGLE_CHILD = 0x08000 # only meaningful for checkboxes TR_AUTO_CHECK_PARENT = 0x10000 # only meaningful for checkboxes TR_DEFAULT_STYLE = wx.TR_DEFAULT_STYLE # default style for the tree control # Values for the `flags' parameter of CustomTreeCtrl.HitTest() which determine # where exactly the specified point is situated: TREE_HITTEST_ABOVE = wx.TREE_HITTEST_ABOVE TREE_HITTEST_BELOW = wx.TREE_HITTEST_BELOW TREE_HITTEST_NOWHERE = wx.TREE_HITTEST_NOWHERE # on the button associated with an item. TREE_HITTEST_ONITEMBUTTON = wx.TREE_HITTEST_ONITEMBUTTON # on the bitmap associated with an item. TREE_HITTEST_ONITEMICON = wx.TREE_HITTEST_ONITEMICON # on the indent associated with an item. TREE_HITTEST_ONITEMINDENT = wx.TREE_HITTEST_ONITEMINDENT # on the label (string) associated with an item. TREE_HITTEST_ONITEMLABEL = wx.TREE_HITTEST_ONITEMLABEL # on the right of the label associated with an item. TREE_HITTEST_ONITEMRIGHT = wx.TREE_HITTEST_ONITEMRIGHT # on the label (string) associated with an item. TREE_HITTEST_ONITEMSTATEICON = wx.TREE_HITTEST_ONITEMSTATEICON # on the left of the CustomTreeCtrl. TREE_HITTEST_TOLEFT = wx.TREE_HITTEST_TOLEFT # on the right of the CustomTreeCtrl. TREE_HITTEST_TORIGHT = wx.TREE_HITTEST_TORIGHT # on the upper part (first half) of the item. TREE_HITTEST_ONITEMUPPERPART = wx.TREE_HITTEST_ONITEMUPPERPART # on the lower part (second half) of the item. TREE_HITTEST_ONITEMLOWERPART = wx.TREE_HITTEST_ONITEMLOWERPART # on the check icon, if present TREE_HITTEST_ONITEMCHECKICON = 0x4000 # anywhere on the item TREE_HITTEST_ONITEM = TREE_HITTEST_ONITEMICON | TREE_HITTEST_ONITEMLABEL | TREE_HITTEST_ONITEMCHECKICON # Background Image Style _StyleTile = 0 _StyleStretch = 1 # Windows Vista Colours _rgbSelectOuter = wx.Colour(170, 200, 245) _rgbSelectInner = wx.Colour(230, 250, 250) _rgbSelectTop = wx.Colour(210, 240, 250) _rgbSelectBottom = wx.Colour(185, 215, 250) _rgbNoFocusTop = wx.Colour(250, 250, 250) _rgbNoFocusBottom = wx.Colour(235, 235, 235) _rgbNoFocusOuter = wx.Colour(220, 220, 220) _rgbNoFocusInner = wx.Colour(245, 245, 245) # Flags for wx.RendererNative _CONTROL_EXPANDED = 8 _CONTROL_CURRENT = 16 # Version Info __version__ = "0.8" # ---------------------------------------------------------------------------- # CustomTreeCtrl events and binding for handling them # ---------------------------------------------------------------------------- wxEVT_TREE_BEGIN_DRAG = wx.wxEVT_COMMAND_TREE_BEGIN_DRAG wxEVT_TREE_BEGIN_RDRAG = wx.wxEVT_COMMAND_TREE_BEGIN_RDRAG wxEVT_TREE_BEGIN_LABEL_EDIT = wx.wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT wxEVT_TREE_END_LABEL_EDIT = wx.wxEVT_COMMAND_TREE_END_LABEL_EDIT wxEVT_TREE_DELETE_ITEM = wx.wxEVT_COMMAND_TREE_DELETE_ITEM wxEVT_TREE_GET_INFO = wx.wxEVT_COMMAND_TREE_GET_INFO wxEVT_TREE_SET_INFO = wx.wxEVT_COMMAND_TREE_SET_INFO wxEVT_TREE_ITEM_EXPANDED = wx.wxEVT_COMMAND_TREE_ITEM_EXPANDED wxEVT_TREE_ITEM_EXPANDING = wx.wxEVT_COMMAND_TREE_ITEM_EXPANDING wxEVT_TREE_ITEM_COLLAPSED = wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSED wxEVT_TREE_ITEM_COLLAPSING = wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSING wxEVT_TREE_SEL_CHANGED = wx.wxEVT_COMMAND_TREE_SEL_CHANGED wxEVT_TREE_SEL_CHANGING = wx.wxEVT_COMMAND_TREE_SEL_CHANGING wxEVT_TREE_KEY_DOWN = wx.wxEVT_COMMAND_TREE_KEY_DOWN wxEVT_TREE_ITEM_ACTIVATED = wx.wxEVT_COMMAND_TREE_ITEM_ACTIVATED wxEVT_TREE_ITEM_RIGHT_CLICK = wx.wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK wxEVT_TREE_ITEM_MIDDLE_CLICK = wx.wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLICK wxEVT_TREE_END_DRAG = wx.wxEVT_COMMAND_TREE_END_DRAG wxEVT_TREE_STATE_IMAGE_CLICK = wx.wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK wxEVT_TREE_ITEM_GETTOOLTIP = wx.wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP wxEVT_TREE_ITEM_MENU = wx.wxEVT_COMMAND_TREE_ITEM_MENU wxEVT_TREE_ITEM_CHECKING = wx.NewEventType() wxEVT_TREE_ITEM_CHECKED = wx.NewEventType() wxEVT_TREE_ITEM_HYPERLINK = wx.NewEventType() EVT_TREE_BEGIN_DRAG = wx.EVT_TREE_BEGIN_DRAG EVT_TREE_BEGIN_RDRAG = wx.EVT_TREE_BEGIN_RDRAG EVT_TREE_BEGIN_LABEL_EDIT = wx.EVT_TREE_BEGIN_LABEL_EDIT EVT_TREE_END_LABEL_EDIT = wx.EVT_TREE_END_LABEL_EDIT EVT_TREE_DELETE_ITEM = wx.EVT_TREE_DELETE_ITEM EVT_TREE_GET_INFO = wx.EVT_TREE_GET_INFO EVT_TREE_SET_INFO = wx.EVT_TREE_SET_INFO EVT_TREE_ITEM_EXPANDED = wx.EVT_TREE_ITEM_EXPANDED EVT_TREE_ITEM_EXPANDING = wx.EVT_TREE_ITEM_EXPANDING EVT_TREE_ITEM_COLLAPSED = wx.EVT_TREE_ITEM_COLLAPSED EVT_TREE_ITEM_COLLAPSING = wx.EVT_TREE_ITEM_COLLAPSING EVT_TREE_SEL_CHANGED = wx.EVT_TREE_SEL_CHANGED EVT_TREE_SEL_CHANGING = wx.EVT_TREE_SEL_CHANGING EVT_TREE_KEY_DOWN = wx.EVT_TREE_KEY_DOWN EVT_TREE_ITEM_ACTIVATED = wx.EVT_TREE_ITEM_ACTIVATED EVT_TREE_ITEM_RIGHT_CLICK = wx.EVT_TREE_ITEM_RIGHT_CLICK EVT_TREE_ITEM_MIDDLE_CLICK = wx.EVT_TREE_ITEM_MIDDLE_CLICK EVT_TREE_END_DRAG = wx.EVT_TREE_END_DRAG EVT_TREE_STATE_IMAGE_CLICK = wx.EVT_TREE_STATE_IMAGE_CLICK EVT_TREE_ITEM_GETTOOLTIP = wx.EVT_TREE_ITEM_GETTOOLTIP EVT_TREE_ITEM_MENU = wx.EVT_TREE_ITEM_MENU EVT_TREE_ITEM_CHECKING = wx.PyEventBinder(wxEVT_TREE_ITEM_CHECKING, 1) EVT_TREE_ITEM_CHECKED = wx.PyEventBinder(wxEVT_TREE_ITEM_CHECKED, 1) EVT_TREE_ITEM_HYPERLINK = wx.PyEventBinder(wxEVT_TREE_ITEM_HYPERLINK, 1) def GetFlaggedData(): return zlib.decompress( 'x\xda\x012\x02\xcd\xfd\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\ \x00\x00\r\x08\x06\x00\x00\x00r\xeb\xe4|\x00\x00\x00\x04sBIT\x08\x08\x08\x08\ |\x08d\x88\x00\x00\x01\xe9IDAT(\x91u\x92\xd1K\xd3a\x14\x86\x9f\xef|J2J\xc3%\ \x85\x8e\x1cb\x93Hl\xd9,\x06F]4\x10\tD3\x83\x88\xc8\xbf\xc0\xb4\xaeBP1\xe9\ \xa2(\xec\xaan\xc3\x82pD\xa1\x84\xb0\x88@3\x8c\xc9\xa2bT\xa2^\x8c\x81V3\xb6\ \xb5\x9f\xce9\xbe.j\xb20\xdf\xeb\xf7\xe19\x07^\xa5D\x93\x9f\x9ea\xbf\t\x04\ \xbf\x12\x8b[\xd8Kl\xf8<.\xeet\xb5\xab\xfc\x8e\xca\x87*ZzM\xf3\xb1j|G\xab\ \xf0\xd4\x94\x13\x9a_&0\xbb\xc8\xd8\xf4g\xa2\xcfo\xa8-P\xc7\xf5\x07\xa6\xedD\ \r\x8d\xb5\xfb\x11\x11\xb4\xd6\x88h\xb4\xd6L}\x8a\xf0\xe4\xd5G\x1e\rt*\x00\ \xc9\x19\xb6\x03D4\xa7\xdcU\\8\xed\xa6\xa2\xa5\xd7\x00\xe8\xab\xf7\x9e\x9a\ \xca\xb2\x9d\\\xf2\xd5!"dT\x86\xc9\xe4\x14\x83s\x83HF\xe3\xdc\xe5\xa4\xa8\ \xb0\x88\xaa\xf2=D\x7f$il>\xdf\xafSe\xf5\xfd\x9dM\x87\xa9\xdc\xb7\x1b\xad5\ \x93\xc9)\xfc\xe9Q\x12\xe9\x04\x13\x0b\x13\x94\xaaR\xdc{\x8f "\xec(,\xe0\xfe\ \xb3\xb7H,a\xe1\xa9)\xdf36\x192\ \xc9d\xdc\xa4RI\xb3\xbaj\x99tz\xcd\xac\xaf\xa7\xcd\xc6F\xc6d\xb3Y\xf32\xf8\ \xc58Z\xfb\x8c\x12\xfd\x07R\xa2\xb98\xf0\xd0\xbcx\xf3a[\xe0\xf2\xd0c\x93\xeb\ nYD\xdb\xc9:\xcex\x0f\xe2\xadu2\x13\x8e0>\x1d\xc6\xff\xfa\xfd\xff\x17\x91K\ \xf7\xf0\xa8\t\x04\xe7X\x89[\x94\x96\xd8\xf0y\x0ep\xb7\xeb\xdc?\xdb\xfb\r|\ \xd0\xd1]\x98\xbdm\xdc\x00\x00\x00\x00IEND\xaeB`\x82\x91\xe2\x08\x8f' ) def GetFlaggedBitmap(): return wx.BitmapFromImage(GetFlaggedImage()) def GetFlaggedImage(): stream = cStringIO.StringIO(GetFlaggedData()) return wx.ImageFromStream(stream) #---------------------------------------------------------------------- def GetNotFlaggedData(): return zlib.decompress( 'x\xda\x01\xad\x01R\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\ \x00\x00\r\x08\x06\x00\x00\x00r\xeb\xe4|\x00\x00\x00\x04sBIT\x08\x08\x08\x08\ |\x08d\x88\x00\x00\x01dIDAT(\x91\x95\xd21K\x82a\x14\x86\xe1\xe7=\xef798\xb8\ \x89\x0e"|Cd\x94\x88\x83\x065\x88\x108\x88Q\x8b-\xd1\x1f\x88\x9a\n\x04\x11j\ \x8eh\x08\xdaZ\x84(\x82\xc2 0\xc1 $\xb4P\xa1\x10\x11D\xb061\xd4\xd4\xcc\xe44\ \x84 \xa8Hg~.\xcer\x0bA\x12\x83\xb7ux\xce\xd1T\x01\xd5z\x0b:\xad\x06n\xbb\ \x8a\x83\xcdU1\xb8\x11\x83\xc8\xe0\r\xf0\x92\xdd\x0c\x97\xd5\x04\x9b\xaaG\ \xb6XA,]B\xe41\x8f\xf7\xab=1\x84Vv\x8e\xd97\xaf\xc29m\x04\x91\x84\x94\n\xa4\ \x94P\x14\x05\x89\xd77\x9c\xc5_\x10\x0em\x08\x00\xa0\xfe\x87q@J\x89\xc593\ \xfc\xaeY\x18\xbc\x01\x06\x00\xb1}t\xc9\xf5F\x03\x01\xbfs$ \x92 "\x10I\xec\ \x9e\xdcBQ\x08\x14M\x15\xe0\xb2\x9a&\x02"\x82\xc71\x85h\xaa\x00\xaa\xd6[\xb0\ \xa9\xfa\x89\x80\x88\xe0\xb0\x98P\xad\xb7@:\xad\x06\xd9be" "$se\xe8\xb4\x1a\ \x90\xdb\xae"\x96.M\x04D\x84H"\x07\xb7]\x05\x04I\x18}A\xbe\xbe\x7f\xe6Z\xed\ \x83\x1b\x8d\x1a7\x9b\x9f\xdcn\xb7\xb8\xd3\xf9\xe2n\xf7\x9b{\xbd\x1f\xbe{\ \xca\xb3\xd1\x17dA\xf2\x0f\t\x92X\x0b\x9d\xf2\xcdCf,X\xdf\x0fs\x7f;T\xc4\xf2\ \xc2\x0c<\x8e)8,&$seD\x129\\\xc43\xa3\x8b\xf8O{\xbf\xf1\xb5\xa5\x990\x0co\ \xd6\x00\x00\x00\x00IEND\xaeB`\x82&\x11\xab!' ) def GetNotFlaggedBitmap(): return wx.BitmapFromImage(GetNotFlaggedImage()) def GetNotFlaggedImage(): stream = cStringIO.StringIO(GetNotFlaggedData()) return wx.ImageFromStream(stream) #---------------------------------------------------------------------- def GetCheckedData(): return zlib.decompress( "x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd1 \xcc\xc1\x06$\ \x8b^?\xa9\x01R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xaf\xf4tq\x0c\xd1\x98\ \x98<\x853\xe7\xc7y\x07\xa5\x84\xc4\x84\x84\x04\x0b3C1\xbd\x03'N\x1c9p\x84\ \xe5\xe0\x993gx||\xce\x14\xcc\xea\xec\xect4^7\xbf\x91\xf3&\x8b\x93\xd4\x8c\ \x19\n\xa7fv\\L\xd8p\x90C\xebx\xcf\x05\x17\x0ff \xb8c\xb6Cm\x06\xdb\xea\xd8\ \xb2\x08\xd3\x03W\x0c\x8c\x8c\x16e%\xa5\xb5E\xe4\xee\xba\xca\xe4|\xb8\xb7\ \xe35OOO\xcf\n\xb3\x83>m\x8c1R\x12\x92\x81s\xd8\x0b/\xb56\x14k|l\\\xc7x\xb4\ \xf2\xc4\xc1*\xd5'B~\xbc\x19uNG\x98\x85\x85\x8d\xe3x%\x16\xb2_\xee\xf1\x07\ \x99\xcb\xacl\x99\xc9\xcf\xb0\xc0_.\x87+\xff\x99\x05\xd0\xd1\x0c\x9e\xae~.\ \xeb\x9c\x12\x9a\x00\x92\xccS\x9f" ) def GetCheckedBitmap(): return wx.BitmapFromImage(GetCheckedImage()) def GetCheckedImage(): stream = cStringIO.StringIO(GetCheckedData()) return wx.ImageFromStream(stream) #---------------------------------------------------------------------- def GetNotCheckedData(): return zlib.decompress( "x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd1 \xcc\xc1\x06$\ \x8b^?\xa9\x01R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xe7z\xba8\x86hL\x9c{\ \xe9 o\x83\x01\x07\xeb\x85\xf3\xed\x86w\x0ed\xdaT\x96\x8a\xbc\x9fw\xe7\xc4\ \xd9/\x01\x8b\x97\x8a\xd7\xab*\xfar\xf0Ob\x93^\xf6\xd5%\x9d\x85A\xe6\xf6\x1f\ \x11\x8f{/\x0b\xf8wX+\x9d\xf2\xb6:\x96\xca\xfe\x9a3\xbeA\xe7\xed\x1b\xc6%\ \xfb=X3'sI-il\t\xb9\xa0\xc0;#\xd4\x835m\x9a\xf9J\x85\xda\x16.\x86\x03\xff\ \xee\xdcc\xdd\xc0\xce\xf9\xc8\xcc(\xbe\x1bh1\x83\xa7\xab\x9f\xcb:\xa7\x84&\ \x00\x87S=\xbe" ) def GetNotCheckedBitmap(): return wx.BitmapFromImage(GetNotCheckedImage()) def GetNotCheckedImage(): stream = cStringIO.StringIO(GetNotCheckedData()) return wx.ImageFromStream(stream) def GrayOut(anImage): """ Convert the given image (in place) to a grayed-out version, appropriate for a 'disabled' appearance. """ factor = 0.7 # 0 < f < 1. Higher Is Grayer if anImage.HasMask(): maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue()) else: maskColor = None data = map(ord, list(anImage.GetData())) for i in range(0, len(data), 3): pixel = (data[i], data[i+1], data[i+2]) pixel = MakeGray(pixel, factor, maskColor) for x in range(3): data[i+x] = pixel[x] anImage.SetData(''.join(map(chr, data))) return anImage def MakeGray((r,g,b), factor, maskColor): """ Make a pixel grayed-out. If the pixel matches the maskcolor, it won't be changed. """ if (r,g,b) != maskColor: return map(lambda x: int((230 - x) * factor) + x, (r,g,b)) else: return (r,g,b) def DrawTreeItemButton(win, dc, rect, flags): """ A simple replacement of wx.RendererNative.DrawTreeItemButton. """ # white background dc.SetPen(wx.GREY_PEN) dc.SetBrush(wx.WHITE_BRUSH) dc.DrawRectangleRect(rect) # black lines xMiddle = rect.x + rect.width/2 yMiddle = rect.y + rect.height/2 # half of the length of the horz lines in "-" and "+" halfWidth = rect.width/2 - 2 dc.SetPen(wx.BLACK_PEN) dc.DrawLine(xMiddle - halfWidth, yMiddle, xMiddle + halfWidth + 1, yMiddle) if not flags & _CONTROL_EXPANDED: # turn "-" into "+" halfHeight = rect.height/2 - 2 dc.DrawLine(xMiddle, yMiddle - halfHeight, xMiddle, yMiddle + halfHeight + 1) #--------------------------------------------------------------------------- # DragImage Implementation # This Class Handles The Creation Of A Custom Image In Case Of Item Drag # And Drop. #--------------------------------------------------------------------------- class DragImage(wx.DragImage): """ This class handles the creation of a custom image in case of item drag and drop. """ def __init__(self, treeCtrl, item): """ Default class constructor. For internal use: do not call it in your code! """ text = item.GetText() font = item.Attr().GetFont() colour = item.Attr().GetTextColour() if not colour: colour = wx.BLACK if not font: font = treeCtrl._normalFont backcolour = treeCtrl.GetBackgroundColour() r, g, b = int(backcolour.Red()), int(backcolour.Green()), int(backcolour.Blue()) backcolour = ((r >> 1) + 20, (g >> 1) + 20, (b >> 1) + 20) backcolour = wx.Colour(backcolour[0], backcolour[1], backcolour[2]) self._backgroundColour = backcolour tempdc = wx.ClientDC(treeCtrl) tempdc.SetFont(font) width, height, dummy = tempdc.GetMultiLineTextExtent(text + "M") image = item.GetCurrentImage() image_w, image_h = 0, 0 wcheck, hcheck = 0, 0 itemcheck = None itemimage = None ximagepos = 0 yimagepos = 0 xcheckpos = 0 ycheckpos = 0 if image != _NO_IMAGE: if treeCtrl._imageListNormal: image_w, image_h = treeCtrl._imageListNormal.GetSize(image) image_w += 4 itemimage = treeCtrl._imageListNormal.GetBitmap(image) checkimage = item.GetCurrentCheckedImage() if checkimage is not None: if treeCtrl._imageListCheck: wcheck, hcheck = treeCtrl._imageListCheck.GetSize(checkimage) wcheck += 4 itemcheck = treeCtrl._imageListCheck.GetBitmap(checkimage) total_h = max(hcheck, height) total_h = max(image_h, total_h) if image_w: ximagepos = wcheck yimagepos = ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0] if checkimage is not None: xcheckpos = 2 ycheckpos = ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0] + 2 extraH = ((total_h > height) and [(total_h - height)/2] or [0])[0] xtextpos = wcheck + image_w ytextpos = extraH total_h = max(image_h, hcheck) total_h = max(total_h, height) if total_h < 30: total_h += 2 # at least 2 pixels else: total_h += total_h/10 # otherwise 10% extra spacing total_w = image_w + wcheck + width self._total_w = total_w self._total_h = total_h self._itemimage = itemimage self._itemcheck = itemcheck self._text = text self._colour = colour self._font = font self._xtextpos = xtextpos self._ytextpos = ytextpos self._ximagepos = ximagepos self._yimagepos = yimagepos self._xcheckpos = xcheckpos self._ycheckpos = ycheckpos self._textwidth = width self._textheight = height self._extraH = extraH self._bitmap = self.CreateBitmap() wx.DragImage.__init__(self, self._bitmap) def CreateBitmap(self): """Actually creates the dnd bitmap.""" memory = wx.MemoryDC() bitmap = wx.EmptyBitmap(self._total_w, self._total_h) memory.SelectObject(bitmap) memory.SetTextBackground(self._backgroundColour) memory.SetBackground(wx.Brush(self._backgroundColour)) memory.SetFont(self._font) memory.SetTextForeground(self._colour) memory.Clear() if self._itemimage: memory.DrawBitmap(self._itemimage, self._ximagepos, self._yimagepos, True) if self._itemcheck: memory.DrawBitmap(self._itemcheck, self._xcheckpos, self._ycheckpos, True) textrect = wx.Rect(self._xtextpos, self._ytextpos+self._extraH, self._textwidth, self._textheight) memory.DrawLabel(self._text, textrect) memory.SelectObject(wx.NullBitmap) return bitmap # ---------------------------------------------------------------------------- # TreeItemAttr: a structure containing the visual attributes of an item # ---------------------------------------------------------------------------- class TreeItemAttr: """Creates the item attributes (text colour, background colour and font).""" def __init__(self, colText=wx.NullColour, colBack=wx.NullColour, font=wx.NullFont): """ Default class constructor. For internal use: do not call it in your code! """ self._colText = colText self._colBack = colBack self._font = font # setters def SetTextColour(self, colText): """Sets the attribute text colour.""" self._colText = colText def SetBackgroundColour(self, colBack): """Sets the attribute background colour.""" self._colBack = colBack def SetFont(self, font): """Sets the attribute font.""" self._font = font # accessors def HasTextColour(self): """Returns whether the attribute has text colour.""" return self._colText != wx.NullColour def HasBackgroundColour(self): """Returns whether the attribute has background colour.""" return self._colBack != wx.NullColour def HasFont(self): """Returns whether the attribute has font.""" return self._font != wx.NullFont # getters def GetTextColour(self): """Returns the attribute text colour.""" return self._colText def GetBackgroundColour(self): """Returns the attribute background colour.""" return self._colBack def GetFont(self): """Returns the attribute font.""" return self._font # ---------------------------------------------------------------------------- # CommandTreeEvent Is A Special Subclassing Of wx.PyCommandEvent # # NB: Note That Not All The Accessors Make Sense For All The Events, See The # Event Description Below. # ---------------------------------------------------------------------------- class CommandTreeEvent(wx.PyCommandEvent): """ CommandTreeEvent is a special subclassing of wx.PyCommandEvent. NB: note that not all the accessors make sense for all the events, see the event description for every method in this class. """ def __init__(self, type, id, item=None, evtKey=None, point=None, label=None, **kwargs): """ Default class constructor. For internal use: do not call it in your code! """ wx.PyCommandEvent.__init__(self, type, id, **kwargs) self._item = item self._evtKey = evtKey self._pointDrag = point self._label = label def GetItem(self): """ Gets the item on which the operation was performed or the newly selected item for EVT_TREE_SEL_CHANGED/ING events. """ return self._item def SetItem(self, item): """ Sets the item on which the operation was performed or the newly selected item for EVT_TREE_SEL_CHANGED/ING events. """ self._item = item def GetOldItem(self): """For EVT_TREE_SEL_CHANGED/ING events, gets the previously selected item.""" return self._itemOld def SetOldItem(self, item): """For EVT_TREE_SEL_CHANGED/ING events, sets the previously selected item.""" self._itemOld = item def GetPoint(self): """ Returns the point where the mouse was when the drag operation started (for EVT_TREE_BEGIN(R)DRAG events only) or the click position. """ return self._pointDrag def SetPoint(self, pt): """ Sets the point where the mouse was when the drag operation started (for EVT_TREE_BEGIN(R)DRAG events only) or the click position. """ self._pointDrag = pt def GetKeyEvent(self): """Keyboard data (for EVT_TREE_KEY_DOWN only).""" return self._evtKey def GetKeyCode(self): """Returns the integer key code (for EVT_TREE_KEY_DOWN only).""" return self._evtKey.GetKeyCode() def SetKeyEvent(self, evt): """Keyboard data (for EVT_TREE_KEY_DOWN only).""" self._evtKey = evt def GetLabel(self): """Returns the label-itemtext (for EVT_TREE_BEGIN|END_LABEL_EDIT only).""" return self._label def SetLabel(self, label): """Sets the label-itemtext (for EVT_TREE_BEGIN|END_LABEL_EDIT only).""" self._label = label def IsEditCancelled(self): """Returns the edit cancel flag (for EVT_TREE_BEGIN|END_LABEL_EDIT only).""" return self._editCancelled def SetEditCanceled(self, editCancelled): """Sets the edit cancel flag (for EVT_TREE_BEGIN|END_LABEL_EDIT only).""" self._editCancelled = editCancelled def SetToolTip(self, toolTip): """Sets the tooltip for the item (for EVT_TREE_ITEM_GETTOOLTIP events).""" self._label = toolTip def GetToolTip(self): """Gets the tooltip for the item (for EVT_TREE_ITEM_GETTOOLTIP events).""" return self._label # ---------------------------------------------------------------------------- # TreeEvent is a special class for all events associated with tree controls # # NB: note that not all accessors make sense for all events, see the event # descriptions below # ---------------------------------------------------------------------------- class TreeEvent(CommandTreeEvent): def __init__(self, type, id, item=None, evtKey=None, point=None, label=None, **kwargs): """ Default class constructor. For internal use: do not call it in your code! """ CommandTreeEvent.__init__(self, type, id, item, evtKey, point, label, **kwargs) self.notify = wx.NotifyEvent(type, id) def GetNotifyEvent(self): """Returns the actual wx.NotifyEvent.""" return self.notify def IsAllowed(self): """Returns whether the event is allowed or not.""" return self.notify.IsAllowed() def Veto(self): """Vetos the event.""" self.notify.Veto() def Allow(self): """The event is allowed.""" self.notify.Allow() # ----------------------------------------------------------------------------- # Auxiliary Classes: TreeRenameTimer # ----------------------------------------------------------------------------- class TreeRenameTimer(wx.Timer): """Timer used for enabling in-place edit.""" def __init__(self, owner): """ Default class constructor. For internal use: do not call it in your code! """ wx.Timer.__init__(self) self._owner = owner def Notify(self): """The timer has expired.""" self._owner.OnRenameTimer() # ----------------------------------------------------------------------------- # Auxiliary Classes: TreeTextCtrl # This Is The Temporary wx.TextCtrl Created When You Edit The Text Of An Item # ----------------------------------------------------------------------------- class TreeTextCtrl(wx.TextCtrl): """Control used for in-place edit.""" def __init__(self, owner, item=None): """ Default class constructor. For internal use: do not call it in your code! """ self._owner = owner self._itemEdited = item self._startValue = item.GetText() self._finished = False self._aboutToFinish = False w = self._itemEdited.GetWidth() h = self._itemEdited.GetHeight() wnd = self._itemEdited.GetWindow() if wnd: w = w - self._itemEdited.GetWindowSize()[0] h = 0 x, y = self._owner.CalcScrolledPosition(item.GetX(), item.GetY()) image_h = 0 image_w = 0 image = item.GetCurrentImage() if image != _NO_IMAGE: if self._owner._imageListNormal: image_w, image_h = self._owner._imageListNormal.GetSize(image) image_w += 4 else: raise Exception("\n ERROR: You Must Create An Image List To Use Images!") checkimage = item.GetCurrentCheckedImage() if checkimage is not None: wcheck, hcheck = self._owner._imageListCheck.GetSize(checkimage) wcheck += 4 else: wcheck = 0 if wnd: h = max(hcheck, image_h) dc = wx.ClientDC(self._owner) h = max(h, dc.GetTextExtent("Aq")[1]) h = h + 2 # FIXME: what are all these hardcoded 4, 8 and 11s really? x += image_w + wcheck w -= image_w + 4 + wcheck wx.TextCtrl.__init__(self, self._owner, wx.ID_ANY, self._startValue, wx.Point(x - 4, y), wx.Size(w + 15, h)) if wx.Platform == "__WXMAC__": self.SetFont(owner.GetFont()) bs = self.GetBestSize() self.SetSize((-1, bs.height)) self.Bind(wx.EVT_CHAR, self.OnChar) self.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) def AcceptChanges(self): """Accepts/refuses the changes made by the user.""" value = self.GetValue() if value == self._startValue: # nothing changed, always accept # when an item remains unchanged, the owner # needs to be notified that the user decided # not to change the tree item label, and that # the edit has been cancelled self._owner.OnRenameCancelled(self._itemEdited) return True if not self._owner.OnRenameAccept(self._itemEdited, value): # vetoed by the user return False # accepted, do rename the item self._owner.SetItemText(self._itemEdited, value) return True def Finish(self): """Finish editing.""" if not self._finished: ## wxPendingDelete.Append(this) self._finished = True self._owner.SetFocusIgnoringChildren() self._owner.ResetTextControl() def OnChar(self, event): """Handles the wx.EVT_CHAR event for TreeTextCtrl.""" keycode = event.GetKeyCode() if keycode == wx.WXK_RETURN: self._aboutToFinish = True # Notify the owner about the changes self.AcceptChanges() # Even if vetoed, close the control (consistent with MSW) wx.CallAfter(self.Finish) elif keycode == wx.WXK_ESCAPE: self.StopEditing() else: event.Skip() def OnKeyUp(self, event): """Handles the wx.EVT_KEY_UP event for TreeTextCtrl.""" if not self._finished: # auto-grow the textctrl: parentSize = self._owner.GetSize() myPos = self.GetPosition() mySize = self.GetSize() sx, sy = self.GetTextExtent(self.GetValue() + "M") if myPos.x + sx > parentSize.x: sx = parentSize.x - myPos.x if mySize.x > sx: sx = mySize.x self.SetSize((sx, -1)) event.Skip() def OnKillFocus(self, event): """Handles the wx.EVT_KILL_FOCUS event for TreeTextCtrl.""" # I commented out those lines, and everything seems to work fine. # But why in the world are these lines of code here? Maybe GTK # or MAC give troubles? ## if not self._finished and not self._aboutToFinish: ## ## # We must finish regardless of success, otherwise we'll get ## # focus problems: ## ## if not self.AcceptChanges(): ## self._owner.OnRenameCancelled(self._itemEdited) # We must let the native text control handle focus, too, otherwise # it could have problems with the cursor (e.g., in wxGTK). event.Skip() def StopEditing(self): """Suddenly stops the editing.""" self._owner.OnRenameCancelled(self._itemEdited) self.Finish() def item(self): """Returns the item currently edited.""" return self._itemEdited # ----------------------------------------------------------------------------- # Auxiliary Classes: TreeFindTimer # Timer Used To Clear CustomTreeCtrl._findPrefix If No Key Was Pressed For A # Sufficiently Long Time. # ----------------------------------------------------------------------------- class TreeFindTimer(wx.Timer): """ Timer used to clear CustomTreeCtrl._findPrefix if no key was pressed for a sufficiently long time. """ def __init__(self, owner): """ Default class constructor. For internal use: do not call it in your code! """ wx.Timer.__init__(self) self._owner = owner def Notify(self): """The timer has expired.""" self._owner._findPrefix = "" # ----------------------------------------------------------------------------- # GenericTreeItem Implementation. # This Class Holds All The Information And Methods For Every Single Item In # CustomTreeCtrl. # ----------------------------------------------------------------------------- class GenericTreeItem: """ This class holds all the information and methods for every single item in CustomTreeCtrl. No wx based. """ def __init__(self, parent, text="", ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """ Default class constructor. For internal use: do not call it in your code! """ # since there can be very many of these, we save size by chosing # the smallest representation for the elements and by ordering # the members to avoid padding. self._text = text # label to be rendered for item self._data = data # user-provided data self._children = [] # list of children self._parent = parent # parent of this item self._attr = None # attributes??? # tree ctrl images for the normal, selected, expanded and # expanded+selected states self._images = [-1, -1, -1, -1] self._images[TreeItemIcon_Normal] = image self._images[TreeItemIcon_Selected] = selImage self._images[TreeItemIcon_Expanded] = _NO_IMAGE self._images[TreeItemIcon_SelectedExpanded] = _NO_IMAGE self._checkedimages = [None, None, None, None] self._x = 0 # (virtual) offset from top self._y = 0 # (virtual) offset from left self._width = 0 # width of this item self._height = 0 # height of this item self._isCollapsed = True self._hasHilight = False # same as focused self._hasPlus = False # used for item which doesn't have # children but has a [+] button self._isBold = False # render the label in bold font self._isItalic = False # render the label in italic font self._ownsAttr = False # delete attribute when done self._type = ct_type # item type: 0=normal, 1=check, 2=radio self._checked = False # only meaningful for check and radio self._enabled = True # flag to enable/disable an item self._hypertext = False # indicates if the item is hypertext self._visited = False # visited state for an hypertext item if self._type > 0: # do not construct the array for normal items self._checkedimages[TreeItemIcon_Checked] = 0 self._checkedimages[TreeItemIcon_NotChecked] = 1 self._checkedimages[TreeItemIcon_Flagged] = 2 self._checkedimages[TreeItemIcon_NotFlagged] = 3 if parent: if parent.GetType() == 2 and not parent.IsChecked(): # if the node parent is a radio not enabled, we are disabled self._enabled = False self._wnd = wnd # are we holding a window? if wnd: if wnd.GetSizer(): # the window is a complex one hold by a sizer size = wnd.GetBestSize() else: # simple window, without sizers size = wnd.GetSize() # We have to bind the wx.EVT_SET_FOCUS for the associated window # No other solution to handle the focus changing from an item in # CustomTreeCtrl and the window associated to an item # Do better strategies exist? self._wnd.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self._height = size.GetHeight() + 2 self._width = size.GetWidth() self._windowsize = size # We don't show the window if the item is collapsed if self._isCollapsed: self._wnd.Show(False) # The window is enabled only if the item is enabled self._wnd.Enable(self._enabled) self._windowenabled = self._enabled def IsOk(self): """ Returns whether the item is ok or not. Useless on Python, but added for backward compatibility with the C++ implementation. """ return True def GetChildren(self): """Returns the item's children.""" return self._children def GetText(self): """Returns the item text.""" return self._text def GetImage(self, which=TreeItemIcon_Normal): """Returns the item image for a particular state.""" return self._images[which] def GetCheckedImage(self, which=TreeItemIcon_Checked): """Returns the item check image. Meaningful only for radio & check items.""" return self._checkedimages[which] def GetData(self): """Returns the data associated to this item.""" return self._data def SetImage(self, image, which): """Sets the item image.""" self._images[which] = image def SetData(self, data): """Sets the data associated to this item.""" self._data = data def SetHasPlus(self, has=True): """Sets whether an item has the 'plus' button.""" self._hasPlus = has def SetBold(self, bold): """Sets the item font bold.""" self._isBold = bold def SetItalic(self, italic): """Sets the item font italic.""" self._isItalic = italic def GetX(self): """Returns the x position on an item in the ScrolledWindow.""" return self._x def GetY(self): """Returns the y position on an item in the ScrolledWindow.""" return self._y def SetX(self, x): """Sets the x position on an item in the ScrolledWindow.""" self._x = x def SetY(self, y): """Sets the y position on an item in the ScrolledWindow.""" self._y = y def GetHeight(self): """Returns the height of the item.""" return self._height def GetWidth(self): """Returns the width of the item.""" return self._width def SetHeight(self, h): """Sets the height of the item.""" self._height = h def SetWidth(self, w): """Sets the width of the item.""" self._width = w def SetWindow(self, wnd): """Sets the window associated to the item.""" self._wnd = wnd def GetWindow(self): """Returns the window associated to the item.""" return self._wnd def GetWindowEnabled(self): """Returns whether the associated window is enabled or not.""" if not self._wnd: raise Exception("\nERROR: This Item Has No Window Associated") return self._windowenabled def SetWindowEnabled(self, enable=True): """Sets whether the associated window is enabled or not.""" if not self._wnd: raise Exception("\nERROR: This Item Has No Window Associated") self._windowenabled = enable self._wnd.Enable(enable) def GetWindowSize(self): """Returns the associated window size.""" return self._windowsize def OnSetFocus(self, event): """Handles the wx.EVT_SET_FOCUS event for the associated window.""" treectrl = self._wnd.GetParent() select = treectrl.GetSelection() # If the window is associated to an item that currently is selected # (has focus) we don't kill the focus. Otherwise we do it. if select != self: treectrl._hasFocus = False else: treectrl._hasFocus = True event.Skip() def GetType(self): """ Returns the item type. It should be one of: 0: normal items 1: checkbox item 2: radiobutton item """ return self._type def SetHyperText(self, hyper=True): """Sets whether the item is hypertext or not.""" self._hypertext = hyper def SetVisited(self, visited=True): """Sets whether an hypertext item was visited or not.""" self._visited = visited def GetVisited(self): """Returns whether an hypertext item was visited or not.""" return self._visited def IsHyperText(self): """Returns whether the item is hypetext or not.""" return self._hypertext def GetParent(self): """Gets the item parent.""" return self._parent def Insert(self, child, index): """Inserts an item in the item children.""" self._children.insert(index, child) def Expand(self): """Expand the item.""" self._isCollapsed = False def Collapse(self): """Collapse the item.""" self._isCollapsed = True def SetHilight(self, set=True): """Sets the item focus/unfocus.""" self._hasHilight = set def HasChildren(self): """Returns whether the item has children or not.""" return len(self._children) > 0 def IsSelected(self): """Returns whether the item is selected or not.""" return self._hasHilight != 0 def IsExpanded(self): """Returns whether the item is expanded or not.""" return not self._isCollapsed def IsChecked(self): """Returns whether the item is checked or not.""" return self._checked def Check(self, checked=True): """Check an item. Meaningful only for check and radio items.""" self._checked = checked def HasPlus(self): """Returns whether the item has the plus button or not.""" return self._hasPlus or self.HasChildren() def IsBold(self): """Returns whether the item font is bold or not.""" return self._isBold != 0 def IsItalic(self): """Returns whether the item font is italic or not.""" return self._isItalic != 0 def Enable(self, enable=True): """Enables/disables the item.""" self._enabled = enable def IsEnabled(self): """Returns whether the item is enabled or not.""" return self._enabled def GetAttributes(self): """Returns the item attributes (font, colours).""" return self._attr def Attr(self): """Creates a new attribute (font, colours).""" if not self._attr: self._attr = TreeItemAttr() self._ownsAttr = True return self._attr def SetAttributes(self, attr): """Sets the item attributes (font, colours).""" if self._ownsAttr: del self._attr self._attr = attr self._ownsAttr = False def AssignAttributes(self, attr): """Assigns the item attributes (font, colours).""" self.SetAttributes(attr) self._ownsAttr = True def DeleteChildren(self, tree): """Deletes the item children.""" for child in self._children: if tree: tree.SendDeleteEvent(child) child.DeleteChildren(tree) if child == tree._select_me: tree._select_me = None # We have to destroy the associated window wnd = child.GetWindow() if wnd: wnd.Destroy() child._wnd = None if child in tree._itemWithWindow: tree._itemWithWindow.remove(child) del child self._children = [] def SetText(self, text): """Sets the item text.""" self._text = text def GetChildrenCount(self, recursively=True): """Gets the number of children.""" count = len(self._children) if not recursively: return count total = count for n in xrange(count): total += self._children[n].GetChildrenCount() return total def GetSize(self, x, y, theButton): """Returns the item size.""" bottomY = self._y + theButton.GetLineHeight(self) if y < bottomY: y = bottomY width = self._x + self._width if x < width: x = width if self.IsExpanded(): for child in self._children: x, y = child.GetSize(x, y, theButton) return x, y def HitTest(self, point, theCtrl, flags=0, level=0): """ HitTest method for an item. Called from the main window HitTest. see the CustomTreeCtrl HitTest method for the flags explanation. """ # for a hidden root node, don't evaluate it, but do evaluate children if not (level == 0 and theCtrl.HasFlag(TR_HIDE_ROOT)): # evaluate the item h = theCtrl.GetLineHeight(self) if point.y > self._y and point.y < self._y + h: y_mid = self._y + h/2 if point.y < y_mid: flags |= TREE_HITTEST_ONITEMUPPERPART else: flags |= TREE_HITTEST_ONITEMLOWERPART xCross = self._x - theCtrl.GetSpacing() if wx.Platform == "__WXMAC__": # according to the drawing code the triangels are drawn # at -4 , -4 from the position up to +10/+10 max if point.x > xCross-4 and point.x < xCross+10 and point.y > y_mid-4 and \ point.y < y_mid+10 and self.HasPlus() and theCtrl.HasButtons(): flags |= TREE_HITTEST_ONITEMBUTTON return self, flags else: # 5 is the size of the plus sign if point.x > xCross-6 and point.x < xCross+6 and point.y > y_mid-6 and \ point.y < y_mid+6 and self.HasPlus() and theCtrl.HasButtons(): flags |= TREE_HITTEST_ONITEMBUTTON return self, flags if point.x >= self._x and point.x <= self._x + self._width: image_w = -1 wcheck = 0 # assuming every image (normal and selected) has the same size! if self.GetImage() != _NO_IMAGE and theCtrl._imageListNormal: image_w, image_h = theCtrl._imageListNormal.GetSize(self.GetImage()) if self.GetCheckedImage() is not None: wcheck, hcheck = theCtrl._imageListCheck.GetSize(self.GetCheckedImage()) if wcheck and point.x <= self._x + wcheck + 1: flags |= TREE_HITTEST_ONITEMCHECKICON return self, flags if image_w != -1 and point.x <= self._x + wcheck + image_w + 1: flags |= TREE_HITTEST_ONITEMICON else: flags |= TREE_HITTEST_ONITEMLABEL return self, flags if point.x < self._x: flags |= TREE_HITTEST_ONITEMINDENT if point.x > self._x + self._width: flags |= TREE_HITTEST_ONITEMRIGHT return self, flags # if children are expanded, fall through to evaluate them if self._isCollapsed: return None, 0 # evaluate children for child in self._children: res, flags = child.HitTest(point, theCtrl, flags, level + 1) if res != None: return res, flags return None, 0 def GetCurrentImage(self): """Returns the current item image.""" image = _NO_IMAGE if self.IsExpanded(): if self.IsSelected(): image = self.GetImage(TreeItemIcon_SelectedExpanded) if image == _NO_IMAGE: # we usually fall back to the normal item, but try just the # expanded one (and not selected) first in this case image = self.GetImage(TreeItemIcon_Expanded) else: # not expanded if self.IsSelected(): image = self.GetImage(TreeItemIcon_Selected) # maybe it doesn't have the specific image we want, # try the default one instead if image == _NO_IMAGE: image = self.GetImage() return image def GetCurrentCheckedImage(self): """Returns the current item check image.""" if self._type == 0: return None if self.IsChecked(): if self._type == 1: # Checkbox return self._checkedimages[TreeItemIcon_Checked] else: # Radiobutton return self._checkedimages[TreeItemIcon_Flagged] else: if self._type == 1: # Checkbox return self._checkedimages[TreeItemIcon_NotChecked] else: # Radiobutton return self._checkedimages[TreeItemIcon_NotFlagged] def EventFlagsToSelType(style, shiftDown=False, ctrlDown=False): """ Translate the key or mouse event flag to the type of selection we are dealing with. """ is_multiple = (style & TR_MULTIPLE) != 0 extended_select = shiftDown and is_multiple unselect_others = not (extended_select or (ctrlDown and is_multiple)) return is_multiple, extended_select, unselect_others # ----------------------------------------------------------------------------- # CustomTreeCtrl Main Implementation. # This Is The Main Class. # ----------------------------------------------------------------------------- class CustomTreeCtrl(wx.PyScrolledWindow): def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, ctstyle=TR_DEFAULT_STYLE, validator=wx.DefaultValidator, name="CustomTreeCtrl"): """ Default class constructor. parent: parent window. Must not be none. id: window identifier. A value of -1 indicates a default value. pos: window position. size: window size. If the default size (-1, -1) is specified then the window is sized appropriately. style: the underlying wx.ScrolledWindow style ctstyle: CustomTreeCtrl window style. This can be one of: TR_NO_BUTTONS TR_HAS_BUTTONS # draw collapsed/expanded btns TR_NO_LINES # don't draw lines at all TR_LINES_AT_ROOT # connect top-level nodes TR_TWIST_BUTTONS # draw mac-like twist buttons TR_SINGLE # single selection mode TR_MULTIPLE # can select multiple items TR_EXTENDED # todo: allow extended selection TR_HAS_VARIABLE_ROW_HEIGHT # allows rows to have variable height TR_EDIT_LABELS # can edit item labels TR_ROW_LINES # put border around items TR_HIDE_ROOT # don't display root node TR_FULL_ROW_HIGHLIGHT # highlight full horizontal space TR_AUTO_CHECK_CHILD # only meaningful for checkboxes TR_AUTO_CHECK_PARENT # only meaningful for checkboxes TR_AUTO_TOGGLE_CHILD # only meaningful for checkboxes validator: window validator. name: window name. """ self._current = self._key_current = self._anchor = self._select_me = None self._hasFocus = False self._dirty = False # Default line height: it will soon be changed self._lineHeight = 10 # Item indent wrt parent self._indent = 15 # item horizontal spacing between the start and the text self._spacing = 18 # Brushes for focused/unfocused items (also gradient type) self._hilightBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)) btnshadow = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW) self._hilightUnfocusedBrush = wx.Brush(btnshadow) r, g, b = btnshadow.Red(), btnshadow.Green(), btnshadow.Blue() backcolour = (max((r >> 1) - 20, 0), max((g >> 1) - 20, 0), max((b >> 1) - 20, 0)) backcolour = wx.Colour(backcolour[0], backcolour[1], backcolour[2]) self._hilightUnfocusedBrush2 = wx.Brush(backcolour) # image list for icons self._imageListNormal = self._imageListButtons = self._imageListState = self._imageListCheck = None self._ownsImageListNormal = self._ownsImageListButtons = self._ownsImageListState = False # Drag and drop initial settings self._dragCount = 0 self._countDrag = 0 self._isDragging = False self._dropTarget = self._oldSelection = None self._dragImage = None self._underMouse = None # TextCtrl initial settings for editable items self._textCtrl = None self._renameTimer = None # This one allows us to handle Freeze() and Thaw() calls self._freezeCount = 0 self._findPrefix = "" self._findTimer = None self._dropEffectAboveItem = False self._lastOnSame = False # Default normal and bold fonts for an item self._hasFont = True self._normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) self._boldFont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(), self._normalFont.GetStyle(), wx.BOLD, self._normalFont.GetUnderlined(), self._normalFont.GetFaceName(), self._normalFont.GetEncoding()) # Hyperlinks things self._hypertextfont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(), self._normalFont.GetStyle(), wx.NORMAL, True, self._normalFont.GetFaceName(), self._normalFont.GetEncoding()) self._hypertextnewcolour = wx.BLUE self._hypertextvisitedcolour = wx.Colour(200, 47, 200) self._isonhyperlink = False # Default CustomTreeCtrl background colour. self._backgroundColour = wx.WHITE # Background image settings self._backgroundImage = None self._imageStretchStyle = _StyleTile # Disabled items colour self._disabledColour = wx.Colour(180, 180, 180) # Gradient selection colours self._firstcolour = color= wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT) self._secondcolour = wx.WHITE self._usegradients = False self._gradientstyle = 0 # Horizontal Gradient # Vista Selection Styles self._vistaselection = False # Connection lines style if wx.Platform != "__WXMAC__": self._dottedPen = wx.Pen("grey", 1, wx.USER_DASH) self._dottedPen.SetDashes([1,1]) self._dottedPen.SetCap(wx.CAP_BUTT) else: self._dottedPen = wx.Pen("grey", 1) # Pen Used To Draw The Border Around Selected Items self._borderPen = wx.BLACK_PEN self._cursor = wx.StockCursor(wx.CURSOR_ARROW) # For Appended Windows self._hasWindows = False self._itemWithWindow = [] if wx.Platform == "__WXMAC__": ctstyle &= ~TR_LINES_AT_ROOT ctstyle |= TR_NO_LINES platform, major, minor = wx.GetOsVersion() if major < 10: ctstyle |= TR_ROW_LINES self._windowStyle = ctstyle # Create the default check image list self.SetImageListCheck(13, 13) # A constant to use my translation of RendererNative.DrawTreeItemButton # if the wxPython version is less than 2.6.2.1. if wx.VERSION_STRING < "2.6.2.1": self._drawingfunction = DrawTreeItemButton else: self._drawingfunction = wx.RendererNative.Get().DrawTreeItemButton # Create our container... at last! wx.PyScrolledWindow.__init__(self, parent, id, pos, size, style|wx.HSCROLL|wx.VSCROLL, name) # If the tree display has no buttons, but does have # connecting lines, we can use a narrower layout. # It may not be a good idea to force this... if not self.HasButtons() and not self.HasFlag(TR_NO_LINES): self._indent= 10 self._spacing = 10 self.SetValidator(validator) attr = self.GetDefaultAttributes() self.SetOwnForegroundColour(attr.colFg) self.SetOwnBackgroundColour(wx.WHITE) if not self._hasFont: self.SetOwnFont(attr.font) self.SetSize(size) # Bind the events self.Bind(wx.EVT_PAINT, self.OnPaint) self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse) self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) self.Bind(EVT_TREE_ITEM_GETTOOLTIP, self.OnGetToolTip) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) # Sets the focus to ourselves: this is useful if you have items # with associated widgets. self.SetFocus() def AcceptsFocus(self): # overridden base class method, allows this ctrl to # participate in the tab-order, etc. It's overridable because # of deriving this class from wx.PyScrolledWindow... return True def OnDestroy(self, event): """Handles the wx.EVT_WINDOW_DESTROY event.""" # Here there may be something I miss... do I have to destroy # something else? if self._renameTimer and self._renameTimer.IsRunning(): self._renameTimer.Stop() del self._renameTimer if self._findTimer and self._findTimer.IsRunning(): self._findTimer.Stop() del self._findTimer event.Skip() def GetCount(self): """Returns the global number of items in the tree.""" if not self._anchor: # the tree is empty return 0 count = self._anchor.GetChildrenCount() if not self.HasFlag(TR_HIDE_ROOT): # take the root itself into account count = count + 1 return count def GetIndent(self): """Returns the item indentation.""" return self._indent def GetSpacing(self): """Returns the spacing between the start and the text.""" return self._spacing def GetRootItem(self): """Returns the root item.""" return self._anchor def GetSelection(self): """Returns the current selection: TR_SINGLE only.""" return self._current def ToggleItemSelection(self, item): """Toggles the item selection.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") self.SelectItem(item, not self.IsSelected(item)) def EnableChildren(self, item, enable=True): """Enables/disables item children. Used internally.""" torefresh = False if item.IsExpanded(): torefresh = True if item.GetType() == 2 and enable and not item.IsChecked(): # We hit a radiobutton item not checked, we don't want to # enable the children return child, cookie = self.GetFirstChild(item) while child: self.EnableItem(child, enable, torefresh=torefresh) # Recurse on tree if child.GetType != 2 or (child.GetType() == 2 and item.IsChecked()): self.EnableChildren(child, enable) (child, cookie) = self.GetNextChild(item, cookie) def EnableItem(self, item, enable=True, torefresh=True): """Enables/disables an item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") if item.IsEnabled() == enable: return if not enable and item.IsSelected(): self.SelectItem(item, False) item.Enable(enable) wnd = item.GetWindow() # Handles the eventual window associated to the item if wnd: wndenable = item.GetWindowEnabled() if enable: if wndenable: wnd.Enable(enable) else: wnd.Enable(enable) if torefresh: # We have to refresh the item line dc = wx.ClientDC(self) self.CalculateSize(item, dc) self.RefreshLine(item) def IsEnabled(self, item): """Returns whether an item is enabled or disabled.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsEnabled() def SetDisabledColour(self, colour): """Sets the items disabled colour.""" self._disabledColour = colour self._dirty = True def GetDisabledColour(self): """Returns the items disabled colour.""" return self._disabledColour def IsItemChecked(self, item): """Returns whether an item is checked or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsChecked() def CheckItem2(self, item, checked=True, torefresh=False): """Used internally to avoid EVT_TREE_ITEM_CHECKED events.""" if item.GetType() == 0: return item.Check(checked) if torefresh: dc = wx.ClientDC(self) self.CalculateSize(item, dc) self.RefreshLine(item) def UnCheckRadioParent(self, item, checked=False): """Used internally to handle radio node parent correctly.""" e = TreeEvent(wxEVT_TREE_ITEM_CHECKING, self.GetId()) e.SetItem(item) e.SetEventObject(self) if self.GetEventHandler().ProcessEvent(e): return False item.Check(checked) self.RefreshLine(item) self.EnableChildren(item, checked) e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId()) e.SetItem(item) e.SetEventObject(self) self.GetEventHandler().ProcessEvent(e) return True def CheckItem(self, item, checked=True): """ Actually checks/uncheks an item, sending (eventually) the two events EVT_TREE_ITEM_CHECKING/EVT_TREE_ITEM_CHECKED. """ if not item: raise Exception("\nERROR: Invalid Tree Item. ") # Should we raise an error here?!? if item.GetType() == 0: return if item.GetType() == 2: # it's a radio button if not checked and item.IsChecked(): # Try To Unckeck? if item.HasChildren(): self.UnCheckRadioParent(item, checked) return else: if not self.UnCheckRadioParent(item, checked): return self.CheckSameLevel(item, False) return # Radiobuttons are done, let's handle checkbuttons... e = TreeEvent(wxEVT_TREE_ITEM_CHECKING, self.GetId()) e.SetItem(item) e.SetEventObject(self) if self.GetEventHandler().ProcessEvent(e): # Blocked by user return item.Check(checked) dc = wx.ClientDC(self) self.RefreshLine(item) if self._windowStyle & TR_AUTO_CHECK_CHILD: ischeck = self.IsItemChecked(item) self.AutoCheckChild(item, ischeck) if self._windowStyle & TR_AUTO_CHECK_PARENT: ischeck = self.IsItemChecked(item) self.AutoCheckParent(item, ischeck) elif self._windowStyle & TR_AUTO_TOGGLE_CHILD: self.AutoToggleChild(item) e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId()) e.SetItem(item) e.SetEventObject(self) self.GetEventHandler().ProcessEvent(e) def AutoToggleChild(self, item): """Transverses the tree and toggles the items. Meaningful only for check items.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") child, cookie = self.GetFirstChild(item) torefresh = False if item.IsExpanded(): torefresh = True # Recurse on tree while child: if child.GetType() == 1 and child.IsEnabled(): self.CheckItem2(child, not child.IsChecked(), torefresh=torefresh) self.AutoToggleChild(child) (child, cookie) = self.GetNextChild(item, cookie) def AutoCheckChild(self, item, checked): """Transverses the tree and checks/unchecks the items. Meaningful only for check items.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") (child, cookie) = self.GetFirstChild(item) torefresh = False if item.IsExpanded(): torefresh = True while child: if child.GetType() == 1 and child.IsEnabled(): self.CheckItem2(child, checked, torefresh=torefresh) self.AutoCheckChild(child, checked) (child, cookie) = self.GetNextChild(item, cookie) def AutoCheckParent(self, item, checked): """Traverses up the tree and checks/unchecks parent items. Meaningful only for check items.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") parent = item.GetParent() if not parent or parent.GetType() != 1: return (child, cookie) = self.GetFirstChild(parent) while child: if child.GetType() == 1 and child.IsEnabled(): if checked != child.IsChecked(): return (child, cookie) = self.GetNextChild(parent, cookie) self.CheckItem2(parent, checked, torefresh=True) self.AutoCheckParent(parent, checked) def CheckChilds(self, item, checked=True): """Programatically check/uncheck item children. Does not generate EVT_TREE_CHECK* events.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") if checked == None: self.AutoToggleChild(item) else: self.AutoCheckChild(item, checked) def CheckSameLevel(self, item, checked=False): """ Uncheck radio items which are on the same level of the checked one. Used internally. """ parent = item.GetParent() if not parent: return torefresh = False if parent.IsExpanded(): torefresh = True (child, cookie) = self.GetFirstChild(parent) while child: if child.GetType() == 2 and child != item: self.CheckItem2(child, checked, torefresh=torefresh) if child.GetType != 2 or (child.GetType() == 2 and child.IsChecked()): self.EnableChildren(child, checked) (child, cookie) = self.GetNextChild(parent, cookie) def EditLabel(self, item): """Starts editing an item label.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") self.Edit(item) def ShouldInheritColours(self): """We don't inherit colours from anyone.""" return False def SetIndent(self, indent): """Sets item indentation.""" self._indent = indent self._dirty = True def SetSpacing(self, spacing): """Sets item spacing.""" self._spacing = spacing self._dirty = True def HasFlag(self, flag): """Returns whether CustomTreeCtrl has a flag.""" return self._windowStyle & flag def HasChildren(self, item): """Returns whether an item has children or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return len(item.GetChildren()) > 0 def GetChildrenCount(self, item, recursively=True): """Gets the item children count.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.GetChildrenCount(recursively) def SetTreeStyle(self, styles): """Sets the CustomTreeCtrl style. See __init__ method for the styles explanation.""" # Do not try to expand the root node if it hasn't been created yet if self._anchor and not self.HasFlag(TR_HIDE_ROOT) and styles & TR_HIDE_ROOT: # if we will hide the root, make sure children are visible self._anchor.SetHasPlus() self._anchor.Expand() self.CalculatePositions() # right now, just sets the styles. Eventually, we may # want to update the inherited styles, but right now # none of the parents has updatable styles if self._windowStyle & TR_MULTIPLE and not (styles & TR_MULTIPLE): selections = self.GetSelections() for select in selections[0:-1]: self.SelectItem(select, False) self._windowStyle = styles self._dirty = True def GetTreeStyle(self): """Returns the CustomTreeCtrl style.""" return self._windowStyle def HasButtons(self): """Returns whether CustomTreeCtrl has the TR_AHS_BUTTONS flag.""" return self.HasFlag(TR_HAS_BUTTONS) # ----------------------------------------------------------------------------- # functions to work with tree items # ----------------------------------------------------------------------------- def GetItemText(self, item): """Returns the item text.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.GetText() def GetItemImage(self, item, which): """Returns the item image.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.GetImage(which) def GetPyData(self, item): """Returns the data associated to an item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.GetData() GetItemPyData = GetPyData def GetItemTextColour(self, item): """Returns the item text colour.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.Attr().GetTextColour() def GetItemBackgroundColour(self, item): """Returns the item background colour.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.Attr().GetBackgroundColour() def GetItemFont(self, item): """Returns the item font.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.Attr().GetFont() def IsItemHyperText(self, item): """Returns whether an item is hypertext or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsHyperText() def SetItemText(self, item, text): """Sets the item text.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") dc = wx.ClientDC(self) item.SetText(text) self.CalculateSize(item, dc) self.RefreshLine(item) def SetItemImage(self, item, image, which=TreeItemIcon_Normal): """Sets the item image, depending on the item state.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") item.SetImage(image, which) dc = wx.ClientDC(self) self.CalculateSize(item, dc) self.RefreshLine(item) def SetPyData(self, item, data): """Sets the data associated to an item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") item.SetData(data) SetItemPyData = SetPyData def SetItemHasChildren(self, item, has=True): """Forces the appearance of the button next to the item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") item.SetHasPlus(has) self.RefreshLine(item) def SetItemBold(self, item, bold=True): """Sets the item font bold/unbold.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") # avoid redrawing the tree if no real change if item.IsBold() != bold: item.SetBold(bold) self._dirty = True def SetItemItalic(self, item, italic=True): """Sets the item font italic/non-italic.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") if item.IsItalic() != italic: itemFont = self.GetItemFont(item) if itemFont != wx.NullFont: style = wx.ITALIC if not italic: style = ~style item.SetItalic(italic) itemFont.SetStyle(style) self.SetItemFont(item, itemFont) self._dirty = True def SetItemDropHighlight(self, item, highlight=True): """ Gives the item the visual feedback for drag and drop operations. This is useful when something is dragged from outside the CustomTreeCtrl. """ if not item: raise Exception("\nERROR: Invalid Tree Item. ") if highlight: bg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT) fg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT) item.Attr().SetTextColour(fg) item.Attr.SetBackgroundColour(bg) self.RefreshLine(item) def SetItemTextColour(self, item, col): """Sets the item text colour.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") if self.GetItemTextColour(item) == col: return item.Attr().SetTextColour(col) self.RefreshLine(item) def SetItemBackgroundColour(self, item, col): """Sets the item background colour.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") item.Attr().SetBackgroundColour(col) self.RefreshLine(item) def SetItemHyperText(self, item, hyper=True): """Sets whether the item is hypertext or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") item.SetHyperText(hyper) self.RefreshLine(item) def SetItemFont(self, item, font): """Sets the item font.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") if self.GetItemFont(item) == font: return item.Attr().SetFont(font) self._dirty = True def SetFont(self, font): """Sets the CustomTreeCtrl font.""" wx.ScrolledWindow.SetFont(self, font) self._normalFont = font self._boldFont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(), self._normalFont.GetStyle(), wx.BOLD, self._normalFont.GetUnderlined(), self._normalFont.GetFaceName(), self._normalFont.GetEncoding()) return True def GetHyperTextFont(self): """Returns the font used to render an hypertext item.""" return self._hypertextfont def SetHyperTextFont(self, font): """Sets the font used to render an hypertext item.""" self._hypertextfont = font self._dirty = True def SetHyperTextNewColour(self, colour): """Sets the colour used to render a non-visited hypertext item.""" self._hypertextnewcolour = colour self._dirty = True def GetHyperTextNewColour(self): """Returns the colour used to render a non-visited hypertext item.""" return self._hypertextnewcolour def SetHyperTextVisitedColour(self, colour): """Sets the colour used to render a visited hypertext item.""" self._hypertextvisitedcolour = colour self._dirty = True def GetHyperTextVisitedColour(self): """Returns the colour used to render a visited hypertext item.""" return self._hypertextvisitedcolour def SetItemVisited(self, item, visited=True): """Sets whether an hypertext item was visited.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") item.SetVisited(visited) self.RefreshLine(item) def GetItemVisited(self, item): """Returns whether an hypertext item was visited.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.GetVisited() def SetHilightFocusColour(self, colour): """ Sets the colour used to highlight focused selected items. This is applied only if gradient and Windows Vista styles are disabled. """ self._hilightBrush = wx.Brush(colour) self.RefreshSelected() def SetHilightNonFocusColour(self, colour): """ Sets the colour used to highlight unfocused selected items. This is applied only if gradient and Windows Vista styles are disabled. """ self._hilightUnfocusedBrush = wx.Brush(colour) self.RefreshSelected() def GetHilightFocusColour(self): """ Returns the colour used to highlight focused selected items. This is applied only if gradient and Windows Vista styles are disabled. """ return self._hilightBrush.GetColour() def GetHilightNonFocusColour(self): """ Returns the colour used to highlight unfocused selected items. This is applied only if gradient and Windows Vista styles are disabled. """ return self._hilightUnfocusedBrush.GetColour() def SetFirstGradientColour(self, colour=None): """Sets the first gradient colour.""" if colour is None: colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT) self._firstcolour = colour if self._usegradients: self.RefreshSelected() def SetSecondGradientColour(self, colour=None): """Sets the second gradient colour.""" if colour is None: # No colour given, generate a slightly darker from the # CustomTreeCtrl background colour color = self.GetBackgroundColour() r, g, b = int(color.Red()), int(color.Green()), int(color.Blue()) color = ((r >> 1) + 20, (g >> 1) + 20, (b >> 1) + 20) colour = wx.Colour(color[0], color[1], color[2]) self._secondcolour = colour if self._usegradients: self.RefreshSelected() def GetFirstGradientColour(self): """Returns the first gradient colour.""" return self._firstcolour def GetSecondGradientColour(self): """Returns the second gradient colour.""" return self._secondcolour def EnableSelectionGradient(self, enable=True): """Globally enables/disables drawing of gradient selection.""" self._usegradients = enable self._vistaselection = False self.RefreshSelected() def SetGradientStyle(self, vertical=0): """ Sets the gradient style: 0: horizontal gradient 1: vertical gradient """ # 0 = Horizontal, 1 = Vertical self._gradientstyle = vertical if self._usegradients: self.RefreshSelected() def GetGradientStyle(self): """ Returns the gradient style: 0: horizontal gradient 1: vertical gradient """ return self._gradientstyle def EnableSelectionVista(self, enable=True): """Globally enables/disables drawing of Windows Vista selection.""" self._usegradients = False self._vistaselection = enable self.RefreshSelected() def SetBorderPen(self, pen): """ Sets the pen used to draw the selected item border. The border pen is not used if the Windows Vista style is applied. """ self._borderPen = pen self.RefreshSelected() def GetBorderPen(self): """ Returns the pen used to draw the selected item border. The border pen is not used if the Windows Vista style is applied. """ return self._borderPen def SetConnectionPen(self, pen): """Sets the pen used to draw the connecting lines between items.""" self._dottedPen = pen self._dirty = True def GetConnectionPen(self): """Returns the pen used to draw the connecting lines between items.""" return self._dottedPen def SetBackgroundImage(self, image): """Sets the CustomTreeCtrl background image (can be none).""" self._backgroundImage = image self.Refresh() def GetBackgroundImage(self): """Returns the CustomTreeCtrl background image (can be none).""" return self._backgroundImage def GetItemWindow(self, item): """Returns the window associated to the item (if any).""" if not item: raise Exception("\nERROR: Invalid Item") return item.GetWindow() def GetItemWindowEnabled(self, item): """Returns whether the window associated to the item is enabled.""" if not item: raise Exception("\nERROR: Invalid Item") return item.GetWindowEnabled() def SetItemWindowEnabled(self, item, enable=True): """Enables/disables the window associated to the item.""" if not item: raise Exception("\nERROR: Invalid Item") item.SetWindowEnabled(enable) def GetItemType(self, item): """ Returns the item type: 0: normal 1: checkbox item 2: radiobutton item """ if not item: raise Exception("\nERROR: Invalid Item") return item.GetType() # ----------------------------------------------------------------------------- # item status inquiries # ----------------------------------------------------------------------------- def IsVisible(self, item): """Returns whether the item is visible or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") # An item is only visible if it's not a descendant of a collapsed item parent = item.GetParent() while parent: if not parent.IsExpanded(): return False parent = parent.GetParent() startX, startY = self.GetViewStart() clientSize = self.GetClientSize() rect = self.GetBoundingRect(item) if not rect: return False if rect.GetWidth() == 0 or rect.GetHeight() == 0: return False if rect.GetBottom() < 0 or rect.GetTop() > clientSize.y: return False if rect.GetRight() < 0 or rect.GetLeft() > clientSize.x: return False return True def ItemHasChildren(self, item): """Returns whether the item has children or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") # consider that the item does have children if it has the "+" button: it # might not have them (if it had never been expanded yet) but then it # could have them as well and it's better to err on this side rather than # disabling some operations which are restricted to the items with # children for an item which does have them return item.HasPlus() def IsExpanded(self, item): """Returns whether the item is expanded or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsExpanded() def IsSelected(self, item): """Returns whether the item is selected or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsSelected() def IsBold(self, item): """Returns whether the item font is bold or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsBold() def IsItalic(self, item): """Returns whether the item font is italic or not.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.IsItalic() # ----------------------------------------------------------------------------- # navigation # ----------------------------------------------------------------------------- def GetItemParent(self, item): """Gets the item parent.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") return item.GetParent() def GetFirstChild(self, item): """Gets the item first child.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") cookie = 0 return self.GetNextChild(item, cookie) def GetNextChild(self, item, cookie): """ Gets the item next child based on the 'cookie' parameter. This method has no sense if you do not call GetFirstChild() before. """ if not item: raise Exception("\nERROR: Invalid Tree Item. ") children = item.GetChildren() # it's ok to cast cookie to size_t, we never have indices big enough to # overflow "void *" if cookie < len(children): return children[cookie], cookie+1 else: # there are no more of them return None, cookie def GetLastChild(self, item): """Gets the item last child.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") children = item.GetChildren() return (len(children) == 0 and [None] or [children[-1]])[0] def GetNextSibling(self, item): """Gets the next sibling of an item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") i = item parent = i.GetParent() if parent == None: # root item doesn't have any siblings return None siblings = parent.GetChildren() index = siblings.index(i) n = index + 1 return (n == len(siblings) and [None] or [siblings[n]])[0] def GetPrevSibling(self, item): """Gets the previous sibling of an item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") i = item parent = i.GetParent() if parent == None: # root item doesn't have any siblings return None siblings = parent.GetChildren() index = siblings.index(i) return (index == 0 and [None] or [siblings[index-1]])[0] def GetNext(self, item): """Gets the next item. Only for internal use right now.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") i = item # First see if there are any children. children = i.GetChildren() if len(children) > 0: return children[0] else: # Try a sibling of this or ancestor instead p = item toFind = None while p and not toFind: toFind = self.GetNextSibling(p) p = self.GetItemParent(p) return toFind def GetFirstVisibleItem(self): """Returns the first visible item.""" id = self.GetRootItem() if not id: return id while id: if self.IsVisible(id): return id id = self.GetNext(id) return None def GetNextVisible(self, item): """Returns the next visible item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") id = item while id: id = self.GetNext(id) if id and self.IsVisible(id): return id return None def GetPrevVisible(self, item): if not item: raise Exception("\nERROR: Invalid Tree Item. ") raise Exception("\nERROR: Not Implemented") return None def ResetTextControl(self): """Called by TreeTextCtrl when it marks itself for deletion.""" self._textCtrl.Destroy() self._textCtrl = None def FindItem(self, idParent, prefixOrig): """Finds the first item starting with the given prefix after the given item.""" # match is case insensitive as this is more convenient to the user: having # to press Shift-letter to go to the item starting with a capital letter # would be too bothersome prefix = prefixOrig.lower() # determine the starting point: we shouldn't take the current item (this # allows to switch between two items starting with the same letter just by # pressing it) but we shouldn't jump to the next one if the user is # continuing to type as otherwise he might easily skip the item he wanted id = idParent if len(prefix) == 1: id = self.GetNext(id) # look for the item starting with the given prefix after it while id and not self.GetItemText(id).lower().startswith(prefix): id = self.GetNext(id) # if we haven't found anything... if not id: # ... wrap to the beginning id = self.GetRootItem() if self.HasFlag(TR_HIDE_ROOT): # can't select virtual root id = self.GetNext(id) # and try all the items (stop when we get to the one we started from) while id != idParent and not self.GetItemText(id).lower().startswith(prefix): id = self.GetNext(id) return id # ----------------------------------------------------------------------------- # operations # ----------------------------------------------------------------------------- def DoInsertItem(self, parentId, previous, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Actually inserts an item in the tree.""" if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if ct_type < 0 or ct_type > 2: raise Exception("\nERROR: Item Type Should Be 0 (Normal), 1 (CheckBox) or 2 (RadioButton). ") parent = parentId if not parent: # should we give a warning here? return self.AddRoot(text, ct_type, wnd, image, selImage, data) self._dirty = True # do this first so stuff below doesn't cause flicker item = GenericTreeItem(parent, text, ct_type, wnd, image, selImage, data) if wnd is not None: self._hasWindows = True self._itemWithWindow.append(item) parent.Insert(item, previous) return item def AddRoot(self, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Adds a root to the CustomTreeCtrl. Only one root must exist.""" if self._anchor: raise Exception("\nERROR: Tree Can Have Only One Root") if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if ct_type < 0 or ct_type > 2: raise Exception("\nERROR: Item Type Should Be 0 (Normal), 1 (CheckBox) or 2 (RadioButton). ") self._dirty = True # do this first so stuff below doesn't cause flicker self._anchor = GenericTreeItem(None, text, ct_type, wnd, image, selImage, data) if wnd is not None: self._hasWindows = True self._itemWithWindow.append(self._anchor) if self.HasFlag(TR_HIDE_ROOT): # if root is hidden, make sure we can navigate # into children self._anchor.SetHasPlus() self._anchor.Expand() self.CalculatePositions() if not self.HasFlag(TR_MULTIPLE): self._current = self._key_current = self._anchor self._current.SetHilight(True) return self._anchor def PrependItem(self, parent, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Appends an item as a first child of parent.""" if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") return self.DoInsertItem(parent, 0, text, ct_type, wnd, image, selImage, data) def InsertItemByItem(self, parentId, idPrevious, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Auxiliary function to cope with the C++ hideous multifunction.""" if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") parent = parentId if not parent: # should we give a warning here? return self.AddRoot(text, ct_type, wnd, image, selImage, data) index = -1 if idPrevious: try: index = parent.GetChildren().index(idPrevious) except: raise Exception("ERROR: Previous Item In CustomTreeCtrl.InsertItem() Is Not A Sibling") return self.DoInsertItem(parentId, index+1, text, ct_type, wnd, image, selImage, data) def InsertItemByIndex(self, parentId, before, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Auxiliary function to cope with the C++ hideous multifunction.""" if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") parent = parentId if not parent: # should we give a warning here? return self.AddRoot(text, ct_type, wnd, image, selImage, data) return self.DoInsertItem(parentId, before, text, ct_type, wnd, image, selImage, data) def InsertItem(self, parentId, input, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Inserts an item after the given previous.""" if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if type(input) == type(1): return self.InsertItemByIndex(parentId, input, text, ct_type, wnd, image, selImage, data) else: return self.InsertItemByItem(parentId, input, text, ct_type, wnd, image, selImage, data) def AppendItem(self, parentId, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None): """Appends an item as a last child of its parent.""" if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT): raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT") parent = parentId if not parent: # should we give a warning here? return self.AddRoot(text, ct_type, wnd, image, selImage, data) return self.DoInsertItem(parent, len(parent.GetChildren()), text, ct_type, wnd, image, selImage, data) def SendDeleteEvent(self, item): """Actully sends the EVT_TREE_DELETE_ITEM event.""" event = TreeEvent(wxEVT_TREE_DELETE_ITEM, self.GetId()) event._item = item event.SetEventObject(self) self.ProcessEvent(event) def IsDescendantOf(self, parent, item): """Checks if the given item is under another one.""" while item: if item == parent: # item is a descendant of parent return True item = item.GetParent() return False # Don't leave edit or selection on a child which is about to disappear def ChildrenClosing(self, item): """We are about to destroy the item children.""" if self._textCtrl != None and item != self._textCtrl.item() and self.IsDescendantOf(item, self._textCtrl.item()): self._textCtrl.StopEditing() if item != self._key_current and self.IsDescendantOf(item, self._key_current): self._key_current = None if self.IsDescendantOf(item, self._select_me): self._select_me = item if item != self._current and self.IsDescendantOf(item, self._current): self._current.SetHilight(False) self._current = None self._select_me = item def DeleteChildren(self, item): """Delete item children.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") self._dirty = True # do this first so stuff below doesn't cause flicker self.ChildrenClosing(item) item.DeleteChildren(self) def Delete(self, item): """Delete an item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") self._dirty = True # do this first so stuff below doesn't cause flicker if self._textCtrl != None and self.IsDescendantOf(item, self._textCtrl.item()): # can't delete the item being edited, cancel editing it first self._textCtrl.StopEditing() parent = item.GetParent() # don't keep stale pointers around! if self.IsDescendantOf(item, self._key_current): # Don't silently change the selection: # do it properly in idle time, so event # handlers get called. # self._key_current = parent self._key_current = None # self._select_me records whether we need to select # a different item, in idle time. if self._select_me and self.IsDescendantOf(item, self._select_me): self._select_me = parent if self.IsDescendantOf(item, self._current): # Don't silently change the selection: # do it properly in idle time, so event # handlers get called. # self._current = parent self._current = None self._select_me = parent # remove the item from the tree if parent: parent.GetChildren().remove(item) # remove by value else: # deleting the root # nothing will be left in the tree self._anchor = None # and delete all of its children and the item itself now item.DeleteChildren(self) self.SendDeleteEvent(item) if item == self._select_me: self._select_me = None # Remove the item with window if item in self._itemWithWindow: wnd = item.GetWindow() wnd.Hide() wnd.Destroy() item._wnd = None self._itemWithWindow.remove(item) del item def DeleteAllItems(self): """Delete all items in the CustomTreeCtrl.""" if self._anchor: self.Delete(self._anchor) def Expand(self, item): """ Expands an item, sending a EVT_TREE_ITEM_EXPANDING and EVT_TREE_ITEM_EXPANDED events. """ if not item: raise Exception("\nERROR: Invalid Tree Item. ") if self.HasFlag(TR_HIDE_ROOT) and item == self.GetRootItem(): raise Exception("\nERROR: Can't Expand An Hidden Root. ") if not item.HasPlus(): return if item.IsExpanded(): return event = TreeEvent(wxEVT_TREE_ITEM_EXPANDING, self.GetId()) event._item = item event.SetEventObject(self) if self.ProcessEvent(event) and not event.IsAllowed(): # cancelled by program return item.Expand() self.CalculatePositions() self.RefreshSubtree(item) if self._hasWindows: # We hide the associated window here, we may show it after self.HideWindows() event.SetEventType(wxEVT_TREE_ITEM_EXPANDED) self.ProcessEvent(event) def ExpandAll(self, item): """Expands all the items.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") if not self.HasFlag(TR_HIDE_ROOT) or item != self.GetRootItem(): self.Expand(item) if not self.IsExpanded(item): return child, cookie = self.GetFirstChild(item) while child: self.ExpandAll(child) child, cookie = self.GetNextChild(item, cookie) def Collapse(self, item): """ Collapse an item, sending a EVT_TREE_ITEM_COLLAPSING and EVT_TREE_ITEM_COLLAPSED events. """ if not item: raise Exception("\nERROR: Invalid Tree Item. ") if self.HasFlag(TR_HIDE_ROOT) and item == self.GetRootItem(): raise Exception("\nERROR: Can't Collapse An Hidden Root. ") if not item.IsExpanded(): return event = TreeEvent(wxEVT_TREE_ITEM_COLLAPSING, self.GetId()) event._item = item event.SetEventObject(self) if self.ProcessEvent(event) and not event.IsAllowed(): # cancelled by program return self.ChildrenClosing(item) item.Collapse() self.CalculatePositions() self.RefreshSubtree(item) if self._hasWindows: self.HideWindows() event.SetEventType(wxEVT_TREE_ITEM_COLLAPSED) self.ProcessEvent(event) def CollapseAndReset(self, item): """Collapse the given item and deletes its children.""" self.Collapse(item) self.DeleteChildren(item) def Toggle(self, item): """Toggles the item state (collapsed/expanded).""" if item.IsExpanded(): self.Collapse(item) else: self.Expand(item) def HideWindows(self): """Hides the windows associated to the items. Used internally.""" for child in self._itemWithWindow: if not self.IsVisible(child): wnd = child.GetWindow() wnd.Hide() def Unselect(self): """Unselects the current selection.""" if self._current: self._current.SetHilight(False) self.RefreshLine(self._current) self._current = None self._select_me = None def UnselectAllChildren(self, item): """Unselects all the children of the given item.""" if item.IsSelected(): item.SetHilight(False) self.RefreshLine(item) if item.HasChildren(): for child in item.GetChildren(): self.UnselectAllChildren(child) def UnselectAll(self): """Unselect all the items.""" rootItem = self.GetRootItem() # the tree might not have the root item at all if rootItem: self.UnselectAllChildren(rootItem) # Recursive function ! # To stop we must have crt_item start_y+client_h: # going up x, y = self._anchor.GetSize(x, y, self) y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels item_y += _PIXELS_PER_UNIT+2 x_pos = self.GetScrollPos(wx.HORIZONTAL) # Item should appear at bottom self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, (item_y+self.GetLineHeight(item)-client_h)/_PIXELS_PER_UNIT ) def OnCompareItems(self, item1, item2): """ Returns whether 2 items have the same text. Override this function in the derived class to change the sort order of the items in the CustomTreeCtrl. The function should return a negative, zero or positive value if the first item is less than, equal to or greater than the second one. The base class version compares items alphabetically. """ return self.GetItemText(item1) == self.GetItemText(item2) def SortChildren(self, item): """ Sorts the children of the given item using OnCompareItems method of CustomTreeCtrl. You should override that method to change the sort order (the default is ascending case-sensitive alphabetical order). """ if not item: raise Exception("\nERROR: Invalid Tree Item. ") children = item.GetChildren() if len(children) > 1: self._dirty = True children.sort(self.OnCompareItems) def GetImageList(self): """Returns the normal image list.""" return self._imageListNormal def GetButtonsImageList(self): """Returns the buttons image list (from which application-defined button images are taken).""" return self._imageListButtons def GetStateImageList(self): """Returns the state image list (from which application-defined state images are taken).""" return self._imageListState def GetImageListCheck(self): """Returns the image list used to build the check/radio buttons.""" return self._imageListCheck def CalculateLineHeight(self): """Calculates the height of a line.""" dc = wx.ClientDC(self) self._lineHeight = dc.GetCharHeight() if self._imageListNormal: # Calculate a self._lineHeight value from the normal Image sizes. # May be toggle off. Then CustomTreeCtrl will spread when # necessary (which might look ugly). n = self._imageListNormal.GetImageCount() for i in xrange(n): width, height = self._imageListNormal.GetSize(i) if height > self._lineHeight: self._lineHeight = height if self._imageListButtons: # Calculate a self._lineHeight value from the Button image sizes. # May be toggle off. Then CustomTreeCtrl will spread when # necessary (which might look ugly). n = self._imageListButtons.GetImageCount() for i in xrange(n): width, height = self._imageListButtons.GetSize(i) if height > self._lineHeight: self._lineHeight = height if self._imageListCheck: # Calculate a self._lineHeight value from the check/radio image sizes. # May be toggle off. Then CustomTreeCtrl will spread when # necessary (which might look ugly). n = self._imageListCheck.GetImageCount() for i in xrange(n): width, height = self._imageListCheck.GetSize(i) if height > self._lineHeight: self._lineHeight = height if self._lineHeight < 30: self._lineHeight += 2 # at least 2 pixels else: self._lineHeight += self._lineHeight/10 # otherwise 10% extra spacing def SetImageList(self, imageList): """Sets the normal image list.""" if self._ownsImageListNormal: del self._imageListNormal self._imageListNormal = imageList self._ownsImageListNormal = False self._dirty = True # Don't do any drawing if we're setting the list to NULL, # since we may be in the process of deleting the tree control. if imageList: self.CalculateLineHeight() # We gray out the image list to use the grayed icons with disabled items self._grayedImageList = wx.ImageList(16, 16, True, 0) for ii in xrange(imageList.GetImageCount()): bmp = imageList.GetBitmap(ii) image = wx.ImageFromBitmap(bmp) image = GrayOut(image) newbmp = wx.BitmapFromImage(image) self._grayedImageList.Add(newbmp) def SetStateImageList(self, imageList): """Sets the state image list (from which application-defined state images are taken).""" if self._ownsImageListState: del self._imageListState self._imageListState = imageList self._ownsImageListState = False def SetButtonsImageList(self, imageList): """Sets the buttons image list (from which application-defined button images are taken).""" if self._ownsImageListButtons: del self._imageListButtons self._imageListButtons = imageList self._ownsImageListButtons = False self._dirty = True self.CalculateLineHeight() def SetImageListCheck(self, sizex, sizey, imglist=None): """Sets the check image list.""" if imglist is None: self._imageListCheck = wx.ImageList(sizex, sizey) self._imageListCheck.Add(GetCheckedBitmap()) self._imageListCheck.Add(GetNotCheckedBitmap()) self._imageListCheck.Add(GetFlaggedBitmap()) self._imageListCheck.Add(GetNotFlaggedBitmap()) else: sizex, sizey = imglist.GetSize(0) self._imageListCheck = imglist # We gray out the image list to use the grayed icons with disabled items self._grayedCheckList = wx.ImageList(sizex, sizey, True, 0) for ii in xrange(self._imageListCheck.GetImageCount()): bmp = self._imageListCheck.GetBitmap(ii) image = wx.ImageFromBitmap(bmp) image = GrayOut(image) newbmp = wx.BitmapFromImage(image) self._grayedCheckList.Add(newbmp) self._dirty = True if imglist: self.CalculateLineHeight() def AssignImageList(self, imageList): """Assigns the normal image list.""" self.SetImageList(imageList) self._ownsImageListNormal = True def AssignStateImageList(self, imageList): """Assigns the state image list.""" self.SetStateImageList(imageList) self._ownsImageListState = True def AssignButtonsImageList(self, imageList): """Assigns the button image list.""" self.SetButtonsImageList(imageList) self._ownsImageListButtons = True # ----------------------------------------------------------------------------- # helpers # ----------------------------------------------------------------------------- def AdjustMyScrollbars(self): """Adjust the wx.ScrolledWindow scrollbars.""" if self._anchor: x, y = self._anchor.GetSize(0, 0, self) y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels x_pos = self.GetScrollPos(wx.HORIZONTAL) y_pos = self.GetScrollPos(wx.VERTICAL) self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, y_pos) else: self.SetScrollbars(0, 0, 0, 0) def GetLineHeight(self, item): """Returns the line height for the given item.""" if self.GetTreeStyle() & TR_HAS_VARIABLE_ROW_HEIGHT: return item.GetHeight() else: return self._lineHeight def DrawVerticalGradient(self, dc, rect, hasfocus): """Gradient fill from colour 1 to colour 2 from top to bottom.""" oldpen = dc.GetPen() oldbrush = dc.GetBrush() dc.SetPen(wx.TRANSPARENT_PEN) # calculate gradient coefficients if hasfocus: col2 = self._secondcolour col1 = self._firstcolour else: col2 = self._hilightUnfocusedBrush.GetColour() col1 = self._hilightUnfocusedBrush2.GetColour() r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue()) r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue()) flrect = float(rect.height) rstep = float((r2 - r1)) / flrect gstep = float((g2 - g1)) / flrect bstep = float((b2 - b1)) / flrect rf, gf, bf = 0, 0, 0 for y in xrange(rect.y, rect.y + rect.height): currCol = (r1 + rf, g1 + gf, b1 + bf) dc.SetBrush(wx.Brush(currCol, wx.SOLID)) dc.DrawRectangle(rect.x, y, rect.width, 1) rf = rf + rstep gf = gf + gstep bf = bf + bstep dc.SetPen(oldpen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangleRect(rect) dc.SetBrush(oldbrush) def DrawHorizontalGradient(self, dc, rect, hasfocus): """Gradient fill from colour 1 to colour 2 from left to right.""" oldpen = dc.GetPen() oldbrush = dc.GetBrush() dc.SetPen(wx.TRANSPARENT_PEN) # calculate gradient coefficients if hasfocus: col2 = self._secondcolour col1 = self._firstcolour else: col2 = self._hilightUnfocusedBrush.GetColour() col1 = self._hilightUnfocusedBrush2.GetColour() r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue()) r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue()) flrect = float(rect.width) rstep = float((r2 - r1)) / flrect gstep = float((g2 - g1)) / flrect bstep = float((b2 - b1)) / flrect rf, gf, bf = 0, 0, 0 for x in xrange(rect.x, rect.x + rect.width): currCol = (int(r1 + rf), int(g1 + gf), int(b1 + bf)) dc.SetBrush(wx.Brush(currCol, wx.SOLID)) dc.DrawRectangle(x, rect.y, 1, rect.height) rf = rf + rstep gf = gf + gstep bf = bf + bstep dc.SetPen(oldpen) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.DrawRectangleRect(rect) dc.SetBrush(oldbrush) def DrawVistaRectangle(self, dc, rect, hasfocus): """Draw the selected item(s) with the Windows Vista style.""" if hasfocus: outer = _rgbSelectOuter inner = _rgbSelectInner top = _rgbSelectTop bottom = _rgbSelectBottom else: outer = _rgbNoFocusOuter inner = _rgbNoFocusInner top = _rgbNoFocusTop bottom = _rgbNoFocusBottom oldpen = dc.GetPen() oldbrush = dc.GetBrush() bdrRect = wx.Rect(*rect.Get()) filRect = wx.Rect(*rect.Get()) filRect.Deflate(1,1) r1, g1, b1 = int(top.Red()), int(top.Green()), int(top.Blue()) r2, g2, b2 = int(bottom.Red()), int(bottom.Green()), int(bottom.Blue()) flrect = float(filRect.height) rstep = float((r2 - r1)) / flrect gstep = float((g2 - g1)) / flrect bstep = float((b2 - b1)) / flrect rf, gf, bf = 0, 0, 0 dc.SetPen(wx.TRANSPARENT_PEN) for y in xrange(filRect.y, filRect.y + filRect.height): currCol = (r1 + rf, g1 + gf, b1 + bf) dc.SetBrush(wx.Brush(currCol, wx.SOLID)) dc.DrawRectangle(filRect.x, y, filRect.width, 1) rf = rf + rstep gf = gf + gstep bf = bf + bstep dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetPen(wx.Pen(outer)) dc.DrawRoundedRectangleRect(bdrRect, 3) bdrRect.Deflate(1, 1) dc.SetPen(wx.Pen(inner)) dc.DrawRoundedRectangleRect(bdrRect, 2) dc.SetPen(oldpen) dc.SetBrush(oldbrush) def PaintItem(self, item, dc): """Actually paint an item.""" attr = item.GetAttributes() if attr and attr.HasFont(): dc.SetFont(attr.GetFont()) elif item.IsBold(): dc.SetFont(self._boldFont) if item.IsHyperText(): dc.SetFont(self.GetHyperTextFont()) if item.GetVisited(): dc.SetTextForeground(self.GetHyperTextVisitedColour()) else: dc.SetTextForeground(self.GetHyperTextNewColour()) text_w, text_h, dummy = dc.GetMultiLineTextExtent(item.GetText()) image = item.GetCurrentImage() checkimage = item.GetCurrentCheckedImage() image_w, image_h = 0, 0 if image != _NO_IMAGE: if self._imageListNormal: image_w, image_h = self._imageListNormal.GetSize(image) image_w += 4 else: image = _NO_IMAGE if item.GetType() != 0: wcheck, hcheck = self._imageListCheck.GetSize(item.GetType()) wcheck += 4 else: wcheck, hcheck = 0, 0 total_h = self.GetLineHeight(item) drawItemBackground = False if item.IsSelected(): # under mac selections are only a rectangle in case they don't have the focus if wx.Platform == "__WXMAC__": if not self._hasFocus: dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT), 1, wx.SOLID)) else: dc.SetBrush(self._hilightBrush) else: dc.SetBrush((self._hasFocus and [self._hilightBrush] or [self._hilightUnfocusedBrush])[0]) drawItemBackground = True else: if attr and attr.HasBackgroundColour(): drawItemBackground = True colBg = attr.GetBackgroundColour() else: colBg = self._backgroundColour dc.SetBrush(wx.Brush(colBg, wx.SOLID)) dc.SetPen(wx.TRANSPARENT_PEN) offset = (self.HasFlag(TR_ROW_LINES) and [1] or [0])[0] if self.HasFlag(TR_FULL_ROW_HIGHLIGHT): x = 0 w, h = self.GetClientSize() itemrect = wx.Rect(x, item.GetY()+offset, w, total_h-offset) if item.IsSelected(): if self._usegradients: if self._gradientstyle == 0: # Horizontal self.DrawHorizontalGradient(dc, itemrect, self._hasFocus) else: # Vertical self.DrawVerticalGradient(dc, itemrect, self._hasFocus) elif self._vistaselection: self.DrawVistaRectangle(dc, itemrect, self._hasFocus) else: if wx.Platform in ["__WXGTK2__", "__WXMAC__"]: flags = wx.CONTROL_SELECTED if self._hasFocus: flags = flags | wx.CONTROL_FOCUSED wx.RendererNative.Get().DrawItemSelectionRect(self, dc, itemrect, flags) else: dc.DrawRectangleRect(itemrect) else: if item.IsSelected(): # If it's selected, and there's an image, then we should # take care to leave the area under the image painted in the # background colour. wnd = item.GetWindow() wndx = 0 if wnd: wndx, wndy = item.GetWindowSize() itemrect = wx.Rect(item.GetX() + wcheck + image_w - 2, item.GetY()+offset, item.GetWidth() - image_w - wcheck + 2 - wndx, total_h-offset) if self._usegradients: if self._gradientstyle == 0: # Horizontal self.DrawHorizontalGradient(dc, itemrect, self._hasFocus) else: # Vertical self.DrawVerticalGradient(dc, itemrect, self._hasFocus) elif self._vistaselection: self.DrawVistaRectangle(dc, itemrect, self._hasFocus) else: if wx.Platform in ["__WXGTK2__", "__WXMAC__"]: flags = wx.CONTROL_SELECTED if self._hasFocus: flags = flags | wx.CONTROL_FOCUSED wx.RendererNative.Get().DrawItemSelectionRect(self, dc, itemrect, flags) else: dc.DrawRectangleRect(itemrect) # On GTK+ 2, drawing a 'normal' background is wrong for themes that # don't allow backgrounds to be customized. Not drawing the background, # except for custom item backgrounds, works for both kinds of theme. elif drawItemBackground: minusicon = wcheck + image_w - 2 itemrect = wx.Rect(item.GetX()+minusicon, item.GetY()+offset, item.GetWidth()-minusicon, total_h-offset) if self._usegradients and self._hasFocus: if self._gradientstyle == 0: # Horizontal self.DrawHorizontalGradient(dc, itemrect, self._hasFocus) else: # Vertical self.DrawVerticalGradient(dc, itemrect, self._hasFocus) else: dc.DrawRectangleRect(itemrect) if image != _NO_IMAGE: dc.SetClippingRegion(item.GetX(), item.GetY(), wcheck+image_w-2, total_h) if item.IsEnabled(): imglist = self._imageListNormal else: imglist = self._grayedImageList imglist.Draw(image, dc, item.GetX() + wcheck, item.GetY() + ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0], wx.IMAGELIST_DRAW_TRANSPARENT) dc.DestroyClippingRegion() if wcheck: if item.IsEnabled(): imglist = self._imageListCheck else: imglist = self._grayedCheckList imglist.Draw(checkimage, dc, item.GetX(), item.GetY() + ((total_h > hcheck) and [(total_h-hcheck)/2] or [0])[0], wx.IMAGELIST_DRAW_TRANSPARENT) dc.SetBackgroundMode(wx.TRANSPARENT) extraH = ((total_h > text_h) and [(total_h - text_h)/2] or [0])[0] textrect = wx.Rect(wcheck + image_w + item.GetX(), item.GetY() + extraH, text_w, text_h) if not item.IsEnabled(): foreground = dc.GetTextForeground() dc.SetTextForeground(self._disabledColour) dc.DrawLabel(item.GetText(), textrect) dc.SetTextForeground(foreground) else: if wx.Platform == "__WXMAC__" and item.IsSelected() and self._hasFocus: dc.SetTextForeground(wx.WHITE) dc.DrawLabel(item.GetText(), textrect) wnd = item.GetWindow() if wnd: wndx = wcheck + image_w + item.GetX() + text_w + 4 xa, ya = self.CalcScrolledPosition((0, item.GetY())) if not wnd.IsShown(): wnd.Show() if wnd.GetPosition() != (wndx, ya): wnd.SetPosition((wndx, ya)) # restore normal font dc.SetFont(self._normalFont) # Now y stands for the top of the item, whereas it used to stand for middle ! def PaintLevel(self, item, dc, level, y): """Paint a level of CustomTreeCtrl.""" x = level*self._indent if not self.HasFlag(TR_HIDE_ROOT): x += self._indent elif level == 0: # always expand hidden root origY = y children = item.GetChildren() count = len(children) if count > 0: n = 0 while n < count: oldY = y y = self.PaintLevel(children[n], dc, 1, y) n = n + 1 if not self.HasFlag(TR_NO_LINES) and self.HasFlag(TR_LINES_AT_ROOT) and count > 0: # draw line down to last child origY += self.GetLineHeight(children[0])>>1 oldY += self.GetLineHeight(children[n-1])>>1 dc.DrawLine(3, origY, 3, oldY) return y item.SetX(x+self._spacing) item.SetY(y) h = self.GetLineHeight(item) y_top = y y_mid = y_top + (h>>1) y += h exposed_x = dc.LogicalToDeviceX(0) exposed_y = dc.LogicalToDeviceY(y_top) if self.IsExposed(exposed_x, exposed_y, 10000, h): # 10000 = very much if wx.Platform == "__WXMAC__": # don't draw rect outline if we already have the # background color under Mac pen = ((item.IsSelected() and self._hasFocus) and [self._borderPen] or [wx.TRANSPARENT_PEN])[0] else: pen = self._borderPen if item.IsSelected(): if (wx.Platform == "__WXMAC__" and self._hasFocus): colText = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT) else: colText = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT) else: attr = item.GetAttributes() if attr and attr.HasTextColour(): colText = attr.GetTextColour() else: colText = self.GetForegroundColour() if self._vistaselection: colText = wx.BLACK # prepare to draw dc.SetTextForeground(colText) dc.SetPen(pen) oldpen = pen # draw self.PaintItem(item, dc) if self.HasFlag(TR_ROW_LINES): # if the background colour is white, choose a # contrasting color for the lines medium_grey = wx.Pen(wx.Colour(200, 200, 200)) dc.SetPen(((self.GetBackgroundColour() == wx.WHITE) and [medium_grey] or [wx.WHITE_PEN])[0]) dc.DrawLine(0, y_top, 10000, y_top) dc.DrawLine(0, y, 10000, y) # restore DC objects dc.SetBrush(wx.WHITE_BRUSH) dc.SetTextForeground(wx.BLACK) if not self.HasFlag(TR_NO_LINES): # draw the horizontal line here dc.SetPen(self._dottedPen) x_start = x if x > self._indent: x_start -= self._indent elif self.HasFlag(TR_LINES_AT_ROOT): x_start = 3 dc.DrawLine(x_start, y_mid, x + self._spacing, y_mid) dc.SetPen(oldpen) # should the item show a button? if item.HasPlus() and self.HasButtons(): if self._imageListButtons: # draw the image button here image_h = 0 image_w = 0 image = (item.IsExpanded() and [TreeItemIcon_Expanded] or [TreeItemIcon_Normal])[0] if item.IsSelected(): image += TreeItemIcon_Selected - TreeItemIcon_Normal image_w, image_h = self._imageListButtons.GetSize(image) xx = x - image_w/2 yy = y_mid - image_h/2 dc.SetClippingRegion(xx, yy, image_w, image_h) self._imageListButtons.Draw(image, dc, xx, yy, wx.IMAGELIST_DRAW_TRANSPARENT) dc.DestroyClippingRegion() else: # no custom buttons if self._windowStyle & TR_TWIST_BUTTONS: # We draw something like the Mac twist buttons dc.SetPen(wx.BLACK_PEN) dc.SetBrush(self._hilightBrush) button = [wx.Point(), wx.Point(), wx.Point()] if item.IsExpanded(): button[0].x = x - 5 button[0].y = y_mid - 3 button[1].x = x + 5 button[1].y = button[0].y button[2].x = x button[2].y = button[0].y + 6 else: button[0].x = x - 3 button[0].y = y_mid - 5 button[1].x = button[0].x button[1].y = y_mid + 5 button[2].x = button[0].x + 5 button[2].y = y_mid dc.DrawPolygon(button) else: # These are the standard wx.TreeCtrl buttons as wx.RendererNative knows wImage = 9 hImage = 9 flag = 0 if item.IsExpanded(): flag |= _CONTROL_EXPANDED if item == self._underMouse: flag |= _CONTROL_CURRENT self._drawingfunction(self, dc, wx.Rect(x - wImage/2, y_mid - hImage/2,wImage, hImage), flag) if item.IsExpanded(): children = item.GetChildren() count = len(children) if count > 0: n = 0 level = level + 1 while n < count: oldY = y y = self.PaintLevel(children[n], dc, level, y) n = n + 1 if not self.HasFlag(TR_NO_LINES) and count > 0: # draw line down to last child oldY += self.GetLineHeight(children[n-1])>>1 if self.HasButtons(): y_mid += 5 # Only draw the portion of the line that is visible, in case it is huge xOrigin, yOrigin = dc.GetDeviceOrigin() yOrigin = abs(yOrigin) width, height = self.GetClientSize() # Move end points to the begining/end of the view? if y_mid < yOrigin: y_mid = yOrigin if oldY > yOrigin + height: oldY = yOrigin + height # after the adjustments if y_mid is larger than oldY then the line # isn't visible at all so don't draw anything if y_mid < oldY: dc.SetPen(self._dottedPen) dc.DrawLine(x, y_mid, x, oldY) return y # ----------------------------------------------------------------------------- # wxWidgets callbacks # ----------------------------------------------------------------------------- def OnPaint(self, event): """Handles the wx.EVT_PAINT event.""" dc = wx.PaintDC(self) self.PrepareDC(dc) if not self._anchor: return dc.SetFont(self._normalFont) dc.SetPen(self._dottedPen) y = 2 self.PaintLevel(self._anchor, dc, 0, y) def OnEraseBackground(self, event): """Handles the wx.EVT_ERASE_BACKGROUND event.""" # Can we actually do something here (or in OnPaint()) To Handle # background images that are stretchable or always centered? # I tried but I get enormous flickering... if not self._backgroundImage: event.Skip() return if self._imageStretchStyle == _StyleTile: dc = event.GetDC() if not dc: dc = wx.ClientDC(self) rect = self.GetUpdateRegion().GetBox() dc.SetClippingRect(rect) self.TileBackground(dc) def TileBackground(self, dc): """Tiles the background image to fill all the available area.""" sz = self.GetClientSize() w = self._backgroundImage.GetWidth() h = self._backgroundImage.GetHeight() x = 0 while x < sz.width: y = 0 while y < sz.height: dc.DrawBitmap(self._backgroundImage, x, y, True) y = y + h x = x + w def OnSetFocus(self, event): """Handles the wx.EVT_SET_FOCUS event.""" self._hasFocus = True self.RefreshSelected() event.Skip() def OnKillFocus(self, event): """Handles the wx.EVT_KILL_FOCUS event.""" self._hasFocus = False self.RefreshSelected() event.Skip() def OnKeyDown(self, event): """Handles the wx.EVT_CHAR event, sending a EVT_TREE_KEY_DOWN event.""" te = TreeEvent(wxEVT_TREE_KEY_DOWN, self.GetId()) te._evtKey = event te.SetEventObject(self) if self.GetEventHandler().ProcessEvent(te): # intercepted by the user code return if self._current is None or self._key_current is None: event.Skip() return # how should the selection work for this event? is_multiple, extended_select, unselect_others = EventFlagsToSelType(self.GetTreeStyle(), event.ShiftDown(), event.CmdDown()) # + : Expand # - : Collaspe # * : Expand all/Collapse all # ' ' | return : activate # up : go up (not last children!) # down : go down # left : go to parent # right : open if parent and go next # home : go to root # end : go to last item without opening parents # alnum : start or continue searching for the item with this prefix keyCode = event.GetKeyCode() if keyCode in [ord("+"), wx.WXK_ADD]: # "+" if self._current.HasPlus() and not self.IsExpanded(self._current) and self.IsEnabled(self._current): self.Expand(self._current) elif keyCode in [ord("*"), wx.WXK_MULTIPLY]: # "*" if not self.IsExpanded(self._current) and self.IsEnabled(self._current): # expand all self.ExpandAll(self._current) elif keyCode in [ord("-"), wx.WXK_SUBTRACT]: # "-" if self.IsExpanded(self._current): self.Collapse(self._current) elif keyCode == wx.WXK_MENU: # Use the item's bounding rectangle to determine position for the event itemRect = self.GetBoundingRect(self._current, True) event = TreeEvent(wxEVT_TREE_ITEM_MENU, self.GetId()) event._item = self._current # Use the left edge, vertical middle event._pointDrag = wx.Point(ItemRect.GetX(), ItemRect.GetY() + ItemRect.GetHeight()/2) event.SetEventObject(self) self.GetEventHandler().ProcessEvent(event) elif keyCode in [wx.WXK_RETURN, wx.WXK_SPACE]: if not self.IsEnabled(self._current): event.Skip() return if not event.HasModifiers(): event = TreeEvent(wxEVT_TREE_ITEM_ACTIVATED, self.GetId()) event._item = self._current event.SetEventObject(self) self.GetEventHandler().ProcessEvent(event) if keyCode == wx.WXK_SPACE and self.GetItemType(self._current) > 0: checked = not self.IsItemChecked(self._current) self.CheckItem(self._current, checked) # in any case, also generate the normal key event for this key, # even if we generated the ACTIVATED event above: this is what # wxMSW does and it makes sense because you might not want to # process ACTIVATED event at all and handle Space and Return # directly (and differently) which would be impossible otherwise event.Skip() # up goes to the previous sibling or to the last # of its children if it's expanded elif keyCode == wx.WXK_UP: prev = self.GetPrevSibling(self._key_current) if not prev: prev = self.GetItemParent(self._key_current) if prev == self.GetRootItem() and self.HasFlag(TR_HIDE_ROOT): return if prev: current = self._key_current # TODO: Huh? If we get here, we'd better be the first child of our parent. How else could it be? if current == self.GetFirstChild(prev)[0] and self.IsEnabled(prev): # otherwise we return to where we came from self.DoSelectItem(prev, unselect_others, extended_select) self._key_current = prev else: current = self._key_current # We are going to another parent node while self.IsExpanded(prev) and self.HasChildren(prev): child = self.GetLastChild(prev) if child: prev = child current = prev # Try to get the previous siblings and see if they are active while prev and not self.IsEnabled(prev): prev = self.GetPrevSibling(prev) if not prev: # No previous siblings active: go to the parent and up prev = self.GetItemParent(current) while prev and not self.IsEnabled(prev): prev = self.GetItemParent(prev) if prev: self.DoSelectItem(prev, unselect_others, extended_select) self._key_current = prev # left arrow goes to the parent elif keyCode == wx.WXK_LEFT: prev = self.GetItemParent(self._current) if prev == self.GetRootItem() and self.HasFlag(TR_HIDE_ROOT): # don't go to root if it is hidden prev = self.GetPrevSibling(self._current) if self.IsExpanded(self._current): self.Collapse(self._current) else: if prev and self.IsEnabled(prev): self.DoSelectItem(prev, unselect_others, extended_select) elif keyCode == wx.WXK_RIGHT: # this works the same as the down arrow except that we # also expand the item if it wasn't expanded yet if self.IsExpanded(self._current) and self.HasChildren(self._current): child, cookie = self.GetFirstChild(self._key_current) if self.IsEnabled(child): self.DoSelectItem(child, unselect_others, extended_select) self._key_current = child else: self.Expand(self._current) # fall through elif keyCode == wx.WXK_DOWN: if self.IsExpanded(self._key_current) and self.HasChildren(self._key_current): child = self.GetNextActiveItem(self._key_current) if child: self.DoSelectItem(child, unselect_others, extended_select) self._key_current = child else: next = self.GetNextSibling(self._key_current) if not next: current = self._key_current while current and not next: current = self.GetItemParent(current) if current: next = self.GetNextSibling(current) if not next or not self.IsEnabled(next): next = None else: while next and not self.IsEnabled(next): next = self.GetNext(next) if next: self.DoSelectItem(next, unselect_others, extended_select) self._key_current = next # selects the last visible tree item elif keyCode == wx.WXK_END: last = self.GetRootItem() while last and self.IsExpanded(last): lastChild = self.GetLastChild(last) # it may happen if the item was expanded but then all of # its children have been deleted - so IsExpanded() returned # true, but GetLastChild() returned invalid item if not lastChild: break last = lastChild if last and self.IsEnabled(last): self.DoSelectItem(last, unselect_others, extended_select) # selects the root item elif keyCode == wx.WXK_HOME: prev = self.GetRootItem() if not prev: return if self.HasFlag(TR_HIDE_ROOT): prev, cookie = self.GetFirstChild(prev) if not prev: return if self.IsEnabled(prev): self.DoSelectItem(prev, unselect_others, extended_select) else: if not event.HasModifiers() and ((keyCode >= ord('0') and keyCode <= ord('9')) or \ (keyCode >= ord('a') and keyCode <= ord('z')) or \ (keyCode >= ord('A') and keyCode <= ord('Z'))): # find the next item starting with the given prefix ch = chr(keyCode) id = self.FindItem(self._current, self._findPrefix + ch) if not id: # no such item return if self.IsEnabled(id): self.SelectItem(id) self._findPrefix += ch # also start the timer to reset the current prefix if the user # doesn't press any more alnum keys soon -- we wouldn't want # to use this prefix for a new item search if not self._findTimer: self._findTimer = TreeFindTimer(self) self._findTimer.Start(_DELAY, wx.TIMER_ONE_SHOT) else: event.Skip() def GetNextActiveItem(self, item, down=True): """Returns the next active item. Used Internally at present. """ if down: sibling = self.GetNextSibling else: sibling = self.GetPrevSibling if self.GetItemType(item) == 2 and not self.IsItemChecked(item): # Is an unchecked radiobutton... all its children are inactive # try to get the next/previous sibling found = 0 while 1: child = sibling(item) if (child and self.IsEnabled(child)) or not child: break item = child else: # Tha's not a radiobutton... but some of its children can be # inactive child, cookie = self.GetFirstChild(item) while child and not self.IsEnabled(child): child, cookie = self.GetNextChild(item, cookie) if child and self.IsEnabled(child): return child return None def HitTest(self, point, flags=0): """ Calculates which (if any) item is under the given point, returning the tree item at this point plus extra information flags. Flags is a bitlist of the following: TREE_HITTEST_ABOVE above the client area TREE_HITTEST_BELOW below the client area TREE_HITTEST_NOWHERE no item has been hit TREE_HITTEST_ONITEMBUTTON on the button associated to an item TREE_HITTEST_ONITEMICON on the icon associated to an item TREE_HITTEST_ONITEMCHECKICON on the check/radio icon, if present TREE_HITTEST_ONITEMINDENT on the indent associated to an item TREE_HITTEST_ONITEMLABEL on the label (string) associated to an item TREE_HITTEST_ONITEMRIGHT on the right of the label associated to an item TREE_HITTEST_TOLEFT on the left of the client area TREE_HITTEST_TORIGHT on the right of the client area TREE_HITTEST_ONITEMUPPERPART on the upper part (first half) of the item TREE_HITTEST_ONITEMLOWERPART on the lower part (second half) of the item TREE_HITTEST_ONITEM anywhere on the item Note: both the item (if any, None otherwise) and the flag are always returned as a tuple. """ w, h = self.GetSize() flags = 0 if point.x < 0: flags |= TREE_HITTEST_TOLEFT if point.x > w: flags |= TREE_HITTEST_TORIGHT if point.y < 0: flags |= TREE_HITTEST_ABOVE if point.y > h: flags |= TREE_HITTEST_BELOW if flags: return None, flags if self._anchor == None: flags = TREE_HITTEST_NOWHERE return None, flags hit, flags = self._anchor.HitTest(self.CalcUnscrolledPosition(point), self, flags, 0) if hit == None: flags = TREE_HITTEST_NOWHERE return None, flags if not self.IsEnabled(hit): return None, flags return hit, flags def GetBoundingRect(self, item, textOnly=False): """Gets the bounding rectangle of the item.""" if not item: raise Exception("\nERROR: Invalid Tree Item. ") i = item startX, startY = self.GetViewStart() rect = wx.Rect() rect.x = i.GetX() - startX*_PIXELS_PER_UNIT rect.y = i.GetY() - startY*_PIXELS_PER_UNIT rect.width = i.GetWidth() rect.height = self.GetLineHeight(i) return rect def Edit(self, item): """ Internal function. Starts the editing of an item label, sending a EVT_TREE_BEGIN_LABEL_EDIT event. """ te = TreeEvent(wxEVT_TREE_BEGIN_LABEL_EDIT, self.GetId()) te._item = item te.SetEventObject(self) if self.GetEventHandler().ProcessEvent(te) and not te.IsAllowed(): # vetoed by user return # We have to call this here because the label in # question might just have been added and no screen # update taken place. if self._dirty: if wx.Platform in ["__WXMSW__", "__WXMAC__"]: self.Update() else: wx.YieldIfNeeded() if self._textCtrl != None and item != self._textCtrl.item(): self._textCtrl.StopEditing() self._textCtrl = TreeTextCtrl(self, item=item) self._textCtrl.SetFocus() def GetEditControl(self): """ Returns a pointer to the edit TextCtrl if the item is being edited or None otherwise (it is assumed that no more than one item may be edited simultaneously). """ return self._textCtrl def OnRenameAccept(self, item, value): """ Called by TreeTextCtrl, to accept the changes and to send the EVT_TREE_END_LABEL_EDIT event. """ le = TreeEvent(wxEVT_TREE_END_LABEL_EDIT, self.GetId()) le._item = item le.SetEventObject(self) le._label = value le._editCancelled = False return not self.GetEventHandler().ProcessEvent(le) or le.IsAllowed() def OnRenameCancelled(self, item): """ Called by TreeTextCtrl, to cancel the changes and to send the EVT_TREE_END_LABEL_EDIT event. """ # let owner know that the edit was cancelled le = TreeEvent(wxEVT_TREE_END_LABEL_EDIT, self.GetId()) le._item = item le.SetEventObject(self) le._label = "" le._editCancelled = True self.GetEventHandler().ProcessEvent(le) def OnRenameTimer(self): """The timer for renaming has expired. Start editing.""" self.Edit(self._current) def OnMouse(self, event): """Handles a bunch of wx.EVT_MOUSE_EVENTS events.""" if not self._anchor: return pt = self.CalcUnscrolledPosition(event.GetPosition()) # Is the mouse over a tree item button? flags = 0 thisItem, flags = self._anchor.HitTest(pt, self, flags, 0) underMouse = thisItem underMouseChanged = underMouse != self._underMouse if underMouse and (flags & TREE_HITTEST_ONITEM) and not event.LeftIsDown() and \ not self._isDragging and (not self._renameTimer or not self._renameTimer.IsRunning()): underMouse = underMouse else: underMouse = None if underMouse != self._underMouse: if self._underMouse: # unhighlight old item self._underMouse = None self._underMouse = underMouse # Determines what item we are hovering over and need a tooltip for hoverItem = thisItem # We do not want a tooltip if we are dragging, or if the rename timer is running if underMouseChanged and not self._isDragging and (not self._renameTimer or not self._renameTimer.IsRunning()): if hoverItem is not None: # Ask the tree control what tooltip (if any) should be shown hevent = TreeEvent(wxEVT_TREE_ITEM_GETTOOLTIP, self.GetId()) hevent._item = hoverItem hevent.SetEventObject(self) if self.GetEventHandler().ProcessEvent(hevent) and hevent.IsAllowed(): self.SetToolTip(hevent._label) if hoverItem.IsHyperText() and (flags & TREE_HITTEST_ONITEMLABEL) and hoverItem.IsEnabled(): self.SetCursor(wx.StockCursor(wx.CURSOR_HAND)) self._isonhyperlink = True else: if self._isonhyperlink: self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) self._isonhyperlink = False # we process left mouse up event (enables in-place edit), right down # (pass to the user code), left dbl click (activate item) and # dragging/moving events for items drag-and-drop if not (event.LeftDown() or event.LeftUp() or event.RightDown() or event.LeftDClick() or \ event.Dragging() or ((event.Moving() or event.RightUp()) and self._isDragging)): event.Skip() return flags = 0 item, flags = self._anchor.HitTest(pt, self, flags, 0) if event.Dragging() and not self._isDragging and ((flags & TREE_HITTEST_ONITEMICON) or (flags & TREE_HITTEST_ONITEMLABEL)): if self._dragCount == 0: self._dragStart = pt self._countDrag = 0 self._dragCount = self._dragCount + 1 if self._dragCount != 3: # wait until user drags a bit further... return command = (event.RightIsDown() and [wxEVT_TREE_BEGIN_RDRAG] or [wxEVT_TREE_BEGIN_DRAG])[0] nevent = TreeEvent(command, self.GetId()) nevent._item = self._current nevent.SetEventObject(self) newpt = self.CalcScrolledPosition(pt) nevent.SetPoint(newpt) # by default the dragging is not supported, the user code must # explicitly allow the event for it to take place nevent.Veto() if self.GetEventHandler().ProcessEvent(nevent) and nevent.IsAllowed(): # we're going to drag this item self._isDragging = True # remember the old cursor because we will change it while # dragging self._oldCursor = self._cursor # in a single selection control, hide the selection temporarily if not (self.GetTreeStyle() & TR_MULTIPLE): self._oldSelection = self.GetSelection() if self._oldSelection: self._oldSelection.SetHilight(False) self.RefreshLine(self._oldSelection) else: selections = self.GetSelections() if len(selections) == 1: self._oldSelection = selections[0] self._oldSelection.SetHilight(False) self.RefreshLine(self._oldSelection) if self._dragImage: del self._dragImage # Create the custom draw image from the icons and the text of the item self._dragImage = DragImage(self, self._current) self._dragImage.BeginDrag(wx.Point(0,0), self) self._dragImage.Show() self._dragImage.Move(self.CalcScrolledPosition(pt)) elif event.Dragging() and self._isDragging: self._dragImage.Move(self.CalcScrolledPosition(pt)) if self._countDrag == 0 and item: self._oldItem = item if item != self._dropTarget: # unhighlight the previous drop target if self._dropTarget: self._dropTarget.SetHilight(False) self.RefreshLine(self._dropTarget) if item: item.SetHilight(True) self.RefreshLine(item) self._countDrag = self._countDrag + 1 self._dropTarget = item self.Update() if self._countDrag >= 3: # Here I am trying to avoid ugly repainting problems... hope it works self.RefreshLine(self._oldItem) self._countDrag = 0 elif (event.LeftUp() or event.RightUp()) and self._isDragging: if self._dragImage: self._dragImage.EndDrag() if self._dropTarget: self._dropTarget.SetHilight(False) if self._oldSelection: self._oldSelection.SetHilight(True) self.RefreshLine(self._oldSelection) self._oldSelection = None # generate the drag end event event = TreeEvent(wxEVT_TREE_END_DRAG, self.GetId()) event._item = item event._pointDrag = self.CalcScrolledPosition(pt) event.SetEventObject(self) self.GetEventHandler().ProcessEvent(event) self._isDragging = False self._dropTarget = None self.SetCursor(self._oldCursor) if wx.Platform in ["__WXMSW__", "__WXMAC__"]: self.Refresh() else: # Probably this is not enough on GTK. Try a Refresh() if it does not work. wx.YieldIfNeeded() else: # If we got to this point, we are not dragging or moving the mouse. # Because the code in carbon/toplevel.cpp will only set focus to the tree # if we skip for EVT_LEFT_DOWN, we MUST skip this event here for focus to work. # We skip even if we didn't hit an item because we still should # restore focus to the tree control even if we didn't exactly hit an item. if event.LeftDown(): self._hasFocus = True self.SetFocusIgnoringChildren() event.Skip() # here we process only the messages which happen on tree items self._dragCount = 0 if item == None: if self._textCtrl != None and item != self._textCtrl.item(): self._textCtrl.StopEditing() return # we hit the blank area if event.RightDown(): if self._textCtrl != None and item != self._textCtrl.item(): self._textCtrl.StopEditing() self._hasFocus = True self.SetFocusIgnoringChildren() # If the item is already selected, do not update the selection. # Multi-selections should not be cleared if a selected item is clicked. if not self.IsSelected(item): self.DoSelectItem(item, True, False) nevent = TreeEvent(wxEVT_TREE_ITEM_RIGHT_CLICK, self.GetId()) nevent._item = item nevent._pointDrag = self.CalcScrolledPosition(pt) nevent.SetEventObject(self) event.Skip(not self.GetEventHandler().ProcessEvent(nevent)) # Consistent with MSW (for now), send the ITEM_MENU *after* # the RIGHT_CLICK event. TODO: This behaviour may change. nevent2 = TreeEvent(wxEVT_TREE_ITEM_MENU, self.GetId()) nevent2._item = item nevent2._pointDrag = self.CalcScrolledPosition(pt) nevent2.SetEventObject(self) self.GetEventHandler().ProcessEvent(nevent2) elif event.LeftUp(): # this facilitates multiple-item drag-and-drop if self.HasFlag(TR_MULTIPLE): selections = self.GetSelections() if len(selections) > 1 and not event.CmdDown() and not event.ShiftDown(): self.DoSelectItem(item, True, False) if self._lastOnSame: if item == self._current and (flags & TREE_HITTEST_ONITEMLABEL) and self.HasFlag(TR_EDIT_LABELS): if self._renameTimer: if self._renameTimer.IsRunning(): self._renameTimer.Stop() else: self._renameTimer = TreeRenameTimer(self) self._renameTimer.Start(_DELAY, True) self._lastOnSame = False else: # !RightDown() && !LeftUp() ==> LeftDown() || LeftDClick() if not item or not item.IsEnabled(): if self._textCtrl != None and item != self._textCtrl.item(): self._textCtrl.StopEditing() return if self._textCtrl != None and item != self._textCtrl.item(): self._textCtrl.StopEditing() self._hasFocus = True self.SetFocusIgnoringChildren() if event.LeftDown(): self._lastOnSame = item == self._current if flags & TREE_HITTEST_ONITEMBUTTON: # only toggle the item for a single click, double click on # the button doesn't do anything (it toggles the item twice) if event.LeftDown(): self.Toggle(item) # don't select the item if the button was clicked return if item.GetType() > 0 and (flags & TREE_HITTEST_ONITEMCHECKICON): if event.LeftDown(): self.CheckItem(item, not self.IsItemChecked(item)) return # clear the previously selected items, if the # user clicked outside of the present selection. # otherwise, perform the deselection on mouse-up. # this allows multiple drag and drop to work. # but if Cmd is down, toggle selection of the clicked item if not self.IsSelected(item) or event.CmdDown(): if flags & TREE_HITTEST_ONITEM: # how should the selection work for this event? if item.IsHyperText(): self.SetItemVisited(item, True) is_multiple, extended_select, unselect_others = EventFlagsToSelType(self.GetTreeStyle(), event.ShiftDown(), event.CmdDown()) self.DoSelectItem(item, unselect_others, extended_select) # For some reason, Windows isn't recognizing a left double-click, # so we need to simulate it here. Allow 200 milliseconds for now. if event.LeftDClick(): # double clicking should not start editing the item label if self._renameTimer: self._renameTimer.Stop() self._lastOnSame = False # send activate event first nevent = TreeEvent(wxEVT_TREE_ITEM_ACTIVATED, self.GetId()) nevent._item = item nevent._pointDrag = self.CalcScrolledPosition(pt) nevent.SetEventObject(self) if not self.GetEventHandler().ProcessEvent(nevent): # if the user code didn't process the activate event, # handle it ourselves by toggling the item when it is # double clicked ## if item.HasPlus(): self.Toggle(item) def OnInternalIdle(self): """Performs operations in idle time (essentially drawing).""" # Check if we need to select the root item # because nothing else has been selected. # Delaying it means that we can invoke event handlers # as required, when a first item is selected. if not self.HasFlag(TR_MULTIPLE) and not self.GetSelection(): if self._select_me: self.SelectItem(self._select_me) elif self.GetRootItem(): self.SelectItem(self.GetRootItem()) # after all changes have been done to the tree control, # we actually redraw the tree when everything is over if not self._dirty: return if self._freezeCount: return self._dirty = False self.CalculatePositions() self.Refresh() self.AdjustMyScrollbars() # event.Skip() def CalculateSize(self, item, dc): """Calculates overall position and size of an item.""" attr = item.GetAttributes() if attr and attr.HasFont(): dc.SetFont(attr.GetFont()) elif item.IsBold(): dc.SetFont(self._boldFont) else: dc.SetFont(self._normalFont) text_w, text_h, dummy = dc.GetMultiLineTextExtent(item.GetText()) text_h+=2 # restore normal font dc.SetFont(self._normalFont) image_w, image_h = 0, 0 image = item.GetCurrentImage() if image != _NO_IMAGE: if self._imageListNormal: image_w, image_h = self._imageListNormal.GetSize(image) image_w += 4 total_h = ((image_h > text_h) and [image_h] or [text_h])[0] checkimage = item.GetCurrentCheckedImage() if checkimage is not None: wcheck, hcheck = self._imageListCheck.GetSize(checkimage) wcheck += 4 else: wcheck = 0 if total_h < 30: total_h += 2 # at least 2 pixels else: total_h += total_h/10 # otherwise 10% extra spacing if total_h > self._lineHeight: self._lineHeight = total_h if not item.GetWindow(): item.SetWidth(image_w+text_w+wcheck+2) item.SetHeight(total_h) else: item.SetWidth(item.GetWindowSize()[0]+image_w+text_w+wcheck+2) def CalculateLevel(self, item, dc, level, y): """Calculates the level of an item.""" x = level*self._indent if not self.HasFlag(TR_HIDE_ROOT): x += self._indent elif level == 0: # a hidden root is not evaluated, but its # children are always calculated children = item.GetChildren() count = len(children) level = level + 1 for n in xrange(count): y = self.CalculateLevel(children[n], dc, level, y) # recurse return y self.CalculateSize(item, dc) # set its position item.SetX(x+self._spacing) item.SetY(y) y += self.GetLineHeight(item) if not item.IsExpanded(): # we don't need to calculate collapsed branches return y children = item.GetChildren() count = len(children) level = level + 1 for n in xrange(count): y = self.CalculateLevel(children[n], dc, level, y) # recurse return y def CalculatePositions(self): """Calculates all the positions of the visible items.""" if not self._anchor: return dc = wx.ClientDC(self) self.PrepareDC(dc) dc.SetFont(self._normalFont) dc.SetPen(self._dottedPen) y = 2 y = self.CalculateLevel(self._anchor, dc, 0, y) # start recursion def RefreshSubtree(self, item): """Refreshes a damaged subtree of an item.""" if self._dirty: return if self._freezeCount: return client = self.GetClientSize() rect = wx.Rect() x, rect.y = self.CalcScrolledPosition(0, item.GetY()) rect.width = client.x rect.height = client.y self.Refresh(True, rect) self.AdjustMyScrollbars() def RefreshLine(self, item): """Refreshes a damaged item line.""" if self._dirty: return if self._freezeCount: return rect = wx.Rect() x, rect.y = self.CalcScrolledPosition(0, item.GetY()) rect.width = self.GetClientSize().x rect.height = self.GetLineHeight(item) self.Refresh(True, rect) def RefreshSelected(self): """Refreshes a damaged selected item line.""" if self._freezeCount: return # TODO: this is awfully inefficient, we should keep the list of all # selected items internally, should be much faster if self._anchor: self.RefreshSelectedUnder(self._anchor) def RefreshSelectedUnder(self, item): """Refreshes the selected items under the given item.""" if self._freezeCount: return if item.IsSelected(): self.RefreshLine(item) children = item.GetChildren() for child in children: self.RefreshSelectedUnder(child) def Freeze(self): """Freeze CustomTreeCtrl.""" self._freezeCount = self._freezeCount + 1 def Thaw(self): """Thaw CustomTreeCtrl.""" if self._freezeCount == 0: raise Exception("\nERROR: Thawing Unfrozen Tree Control?") self._freezeCount = self._freezeCount - 1 if not self._freezeCount: self.Refresh() # ---------------------------------------------------------------------------- # changing colours: we need to refresh the tree control # ---------------------------------------------------------------------------- def SetBackgroundColour(self, colour): """Changes the background colour of CustomTreeCtrl.""" if not wx.Window.SetBackgroundColour(self, colour): return False if self._freezeCount: return True self.Refresh() return True def SetForegroundColour(self, colour): """Changes the foreground colour of CustomTreeCtrl.""" if not wx.Window.SetForegroundColour(self, colour): return False if self._freezeCount: return True self.Refresh() return True def OnGetToolTip(self, event): """ Process the tooltip event, to speed up event processing. Does not actually get a tooltip. """ event.Veto() def DoGetBestSize(self): """Something is better than nothing...""" # something is better than nothing... # 100x80 is what the MSW version will get from the default # wxControl::DoGetBestSize return wx.Size(100, 80) def GetClassDefaultAttributes(self): """Gets the class default attributes.""" attr = wx.VisualAttributes() attr.colFg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) attr.colBg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_LISTBOX) attr.font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT) return attr