wxWidgets/wxPython/wx/lib/customtreectrl.py
Robin Dunn 3c69a2ec86 merge from 2.8 branch
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@44403 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
2007-02-07 16:24:56 +00:00

5737 lines
185 KiB
Python

# --------------------------------------------------------------------------------- #
# 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)\xdf<e$2Ble\x85\x94e\xb1\x96\xcep\xfb\xdd-D\x04\xa5\x14\
\xdeZ\'\xb1\x84\x85\xd8\x8bm\x84\xe6\x977\x7f8kog)\xba\xc4\xb7\xe5\xef$\xe2?\
\xe9\xa9\xbf\x86R\n\x11a&\x1c\xc1^lC|\r.\x02\xb3\x8b\x9b\xa6&G\x13W\xaa\xbb\
\x91_\x05\x0c\x1d\xbfI\xc7\xa1\x8e\xbf&a|:\x8c\xaf\xc1\x05J4\x8e\xd6>36\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<last_item
# Algorithm :
# Tag all next children, when no more children,
# Move to parent (not to tag)
# Keep going... if we found last_item, we stop.
def TagNextChildren(self, crt_item, last_item, select):
"""Used internally."""
parent = crt_item.GetParent()
if parent == None: # This is root item
return self.TagAllChildrenUntilLast(crt_item, last_item, select)
children = parent.GetChildren()
index = children.index(crt_item)
count = len(children)
for n in xrange(index+1, count):
if self.TagAllChildrenUntilLast(children[n], last_item, select):
return True
return self.TagNextChildren(parent, last_item, select)
def TagAllChildrenUntilLast(self, crt_item, last_item, select):
"""Used internally."""
crt_item.SetHilight(select)
self.RefreshLine(crt_item)
if crt_item == last_item:
return True
if crt_item.HasChildren():
for child in crt_item.GetChildren():
if self.TagAllChildrenUntilLast(child, last_item, select):
return True
return False
def SelectItemRange(self, item1, item2):
"""Selects all the items between item1 and item2."""
self._select_me = None
# item2 is not necessary after item1
# choice first' and 'last' between item1 and item2
first = (item1.GetY() < item2.GetY() and [item1] or [item2])[0]
last = (item1.GetY() < item2.GetY() and [item2] or [item1])[0]
select = self._current.IsSelected()
if self.TagAllChildrenUntilLast(first, last, select):
return
self.TagNextChildren(first, last, select)
def DoSelectItem(self, item, unselect_others=True, extended_select=False):
"""Actually selects/unselects an item, sending a EVT_TREE_SEL_CHANGED event."""
if not item:
raise Exception("\nERROR: Invalid Tree Item. ")
self._select_me = None
is_single = not (self.GetTreeStyle() & TR_MULTIPLE)
# to keep going anyhow !!!
if is_single:
if item.IsSelected():
return # nothing to do
unselect_others = True
extended_select = False
elif unselect_others and item.IsSelected():
# selection change if there is more than one item currently selected
if len(self.GetSelections()) == 1:
return
event = TreeEvent(wxEVT_TREE_SEL_CHANGING, self.GetId())
event._item = item
event._itemOld = self._current
event.SetEventObject(self)
# TODO : Here we don't send any selection mode yet !
if self.GetEventHandler().ProcessEvent(event) and not event.IsAllowed():
return
parent = self.GetItemParent(item)
while parent:
if not self.IsExpanded(parent):
self.Expand(parent)
parent = self.GetItemParent(parent)
# ctrl press
if unselect_others:
if is_single:
self.Unselect() # to speed up thing
else:
self.UnselectAll()
# shift press
if extended_select:
if not self._current:
self._current = self._key_current = self.GetRootItem()
# don't change the mark (self._current)
self.SelectItemRange(self._current, item)
else:
select = True # the default
# Check if we need to toggle hilight (ctrl mode)
if not unselect_others:
select = not item.IsSelected()
self._current = self._key_current = item
self._current.SetHilight(select)
self.RefreshLine(self._current)
# This can cause idle processing to select the root
# if no item is selected, so it must be after the
# selection is set
self.EnsureVisible(item)
event.SetEventType(wxEVT_TREE_SEL_CHANGED)
self.GetEventHandler().ProcessEvent(event)
# Handles hypertext items
if self.IsItemHyperText(item):
event = TreeEvent(wxEVT_TREE_ITEM_HYPERLINK, self.GetId())
event._item = item
self.GetEventHandler().ProcessEvent(event)
def SelectItem(self, item, select=True):
"""Selects/deselects an item."""
if not item:
raise Exception("\nERROR: Invalid Tree Item. ")
if select:
self.DoSelectItem(item, not self.HasFlag(TR_MULTIPLE))
else: # deselect
item.SetHilight(False)
self.RefreshLine(item)
def FillArray(self, item, array=[]):
"""
Internal function. Used to populate an array of selected items when
the style TR_MULTIPLE is used.
"""
if not array:
array = []
if item.IsSelected():
array.append(item)
if item.HasChildren() and item.IsExpanded():
for child in item.GetChildren():
array = self.FillArray(child, array)
return array
def GetSelections(self):
"""
Returns a list of selected items. This can be used only if CustomTreeCtrl has
the TR_MULTIPLE style set.
"""
array = []
idRoot = self.GetRootItem()
if idRoot:
array = self.FillArray(idRoot, array)
#else: the tree is empty, so no selections
return array
def EnsureVisible(self, item):
"""Ensure that an item is visible in CustomTreeCtrl."""
if not item:
raise Exception("\nERROR: Invalid Tree Item. ")
# first expand all parent branches
parent = item.GetParent()
if self.HasFlag(TR_HIDE_ROOT):
while parent and parent != self._anchor:
self.Expand(parent)
parent = parent.GetParent()
else:
while parent:
self.Expand(parent)
parent = parent.GetParent()
self.ScrollTo(item)
def ScrollTo(self, item):
"""Scrolls the specified item into view."""
if not item:
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()
# now scroll to the item
item_y = item.GetY()
start_x, start_y = self.GetViewStart()
start_y *= _PIXELS_PER_UNIT
client_w, client_h = self.GetClientSize()
x, y = 0, 0
if item_y < start_y+3:
# going down
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
x_pos = self.GetScrollPos(wx.HORIZONTAL)
# Item should appear at top
self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, item_y/_PIXELS_PER_UNIT)
elif item_y+self.GetLineHeight(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
# <End> 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)
# <Home> 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