]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/customtreectrl.py
merge from 2.8 branch
[wxWidgets.git] / wxPython / wx / lib / customtreectrl.py
CommitLineData
c8f129d0
RD
1# --------------------------------------------------------------------------------- #
2# CUSTOMTREECTRL wxPython IMPLEMENTATION
3# Inspired By And Heavily Based On wxGenericTreeCtrl.
4#
5# Andrea Gavana, @ 17 May 2006
6# Latest Revision: 26 May 2006, 22.30 CET
7#
8#
9# TODO List
10#
11# Almost All The Features Of wx.TreeCtrl Are Available, And There Is Practically
12# No Limit In What Could Be Added To This Class. The First Things That Comes
13# To My Mind Are:
14#
15# 1. Implement The Style TR_EXTENDED (I Have Never Used It, But It May Be Useful).
16#
17# 2. Add Support For 3-State CheckBoxes (Is That Really Useful?).
18#
19# 3. Try To Implement A More Flicker-Free Background Image In Cases Like
20# Centered Or Stretched Image (Now CustomTreeCtrl Supports Only Tiled
21# Background Images).
22#
23# 4. Try To Mimic Windows wx.TreeCtrl Expanding/Collapsing behaviour: CustomTreeCtrl
24# Suddenly Expands/Collapses The Nodes On Mouse Click While The Native Control
25# Has Some Kind Of "Smooth" Expanding/Collapsing, Like A Wave. I Don't Even
26# Know Where To Start To Do That.
27#
28# 5. Speed Up General OnPaint Things? I Have No Idea, Here CustomTreeCtrl Is Quite
29# Fast, But We Should See On Slower Machines.
30#
31#
32# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
33# Write To Me At:
34#
35# gavana@kpo.kz
36# andrea.gavana@gmail.com
37#
38# Or, Obviously, To The wxPython Mailing List!!!
39#
40#
41# End Of Comments
42# --------------------------------------------------------------------------------- #
43
44
45"""
46Description
47===========
48
49CustomTreeCtrl is a class that mimics the behaviour of wx.TreeCtrl, with almost the
50same base functionalities plus some more enhancements. This class does not rely on
51the native control, as it is a full owner-drawn tree control.
52Apart of the base functionalities of CustomTreeCtrl (described below), in addition
53to the standard wx.TreeCtrl behaviour this class supports:
54
55* CheckBox-type items: checkboxes are easy to handle, just selected or unselected
56 state with no particular issues in handling the item's children;
57
58* RadioButton-type items: since I elected to put radiobuttons in CustomTreeCtrl, I
59 needed some way to handle them, that made sense. So, I used the following approach:
60 - All peer-nodes that are radiobuttons will be mutually exclusive. In other words,
61 only one of a set of radiobuttons that share a common parent can be checked at
62 once. If a radiobutton node becomes checked, then all of its peer radiobuttons
63 must be unchecked.
64 - If a radiobutton node becomes unchecked, then all of its child nodes will become
65 inactive.
66
67* Hyperlink-type items: they look like an hyperlink, with the proper mouse cursor on
68 hovering.
69
70* Multiline text items.
71
72* Enabling/disabling items (together with their plain or grayed out icons).
73
74* Whatever non-toplevel widget can be attached next to an item.
75
76* Default selection style, gradient (horizontal/vertical) selection style and Windows
77 Vista selection style.
78
79* Customized drag and drop images built on the fly.
80
81* Setting the CustomTreeCtrl item buttons to a personalized imagelist.
82
83* Setting the CustomTreeCtrl check/radio item icons to a personalized imagelist.
84
85* Changing the style of the lines that connect the items (in terms of wx.Pen styles).
86
87* Using an image as a CustomTreeCtrl background (currently only in "tile" mode).
88
89And a lot more. Check the demo for an almost complete review of the functionalities.
90
91
92Base Functionalities
93====================
94
95CustomTreeCtrl supports all the wx.TreeCtrl styles, except:
96 - TR_EXTENDED: supports for this style is on the todo list (Am I sure of this?).
97
eecfab17 98Plus it has 3 more styles to handle checkbox-type items:
c8f129d0 99 - TR_AUTO_CHECK_CHILD : automatically checks/unchecks the item children;
eecfab17 100 - TR_AUTO_CHECK_PARENT : automatically checks/unchecks the item parent;
c8f129d0
RD
101 - TR_AUTO_TOGGLE_CHILD: automatically toggles the item children.
102
103All the methods available in wx.TreeCtrl are also available in CustomTreeCtrl.
104
105
106Events
107======
108
109All the events supported by wx.TreeCtrl are also available in CustomTreeCtrl, with
110a few exceptions:
111
112 - EVT_TREE_GET_INFO (don't know what this means);
113 - EVT_TREE_SET_INFO (don't know what this means);
114 - EVT_TREE_ITEM_MIDDLE_CLICK (not implemented, but easy to add);
115 - EVT_TREE_STATE_IMAGE_CLICK: no need for that, look at the checking events below.
116
117Plus, CustomTreeCtrl supports the events related to the checkbutton-type items:
118
119 - EVT_TREE_ITEM_CHECKING: an item is being checked;
120 - EVT_TREE_ITEM_CHECKED: an item has been checked.
121
122And to hyperlink-type items:
123
124 - EVT_TREE_ITEM_HYPERLINK: an hyperlink item has been clicked (this event is sent
125 after the EVT_TREE_SEL_CHANGED event).
126
127
128Supported Platforms
129===================
130
131CustomTreeCtrl has been tested on the following platforms:
132 * Windows (Windows XP);
133 * GTK (Thanks to Michele Petrazzo);
134 * Mac OS (Thanks to John Jackson).
135
136
137Latest Revision: Andrea Gavana @ 26 May 2006, 22.30 CET
138Version 0.8
139
140"""
141
142
143import wx
144import zlib
145import cStringIO
146
147# ----------------------------------------------------------------------------
148# Constants
149# ----------------------------------------------------------------------------
150
151_NO_IMAGE = -1
152_PIXELS_PER_UNIT = 10
153
154# Start editing the current item after half a second (if the mouse hasn't
155# been clicked/moved)
156_DELAY = 500
157
158# ----------------------------------------------------------------------------
159# Constants
160# ----------------------------------------------------------------------------
161
162# Enum for different images associated with a treectrl item
163TreeItemIcon_Normal = 0 # not selected, not expanded
164TreeItemIcon_Selected = 1 # selected, not expanded
165TreeItemIcon_Expanded = 2 # not selected, expanded
166TreeItemIcon_SelectedExpanded = 3 # selected, expanded
167
168TreeItemIcon_Checked = 0 # check button, checked
169TreeItemIcon_NotChecked = 1 # check button, not checked
170TreeItemIcon_Flagged = 2 # radio button, selected
171TreeItemIcon_NotFlagged = 3 # radio button, not selected
172
173# ----------------------------------------------------------------------------
174# CustomTreeCtrl flags
175# ----------------------------------------------------------------------------
176
177TR_NO_BUTTONS = wx.TR_NO_BUTTONS # for convenience
178TR_HAS_BUTTONS = wx.TR_HAS_BUTTONS # draw collapsed/expanded btns
179TR_NO_LINES = wx.TR_NO_LINES # don't draw lines at all
180TR_LINES_AT_ROOT = wx.TR_LINES_AT_ROOT # connect top-level nodes
181TR_TWIST_BUTTONS = wx.TR_TWIST_BUTTONS # still used by wxTreeListCtrl
182
183TR_SINGLE = wx.TR_SINGLE # for convenience
184TR_MULTIPLE = wx.TR_MULTIPLE # can select multiple items
185TR_EXTENDED = wx.TR_EXTENDED # TODO: allow extended selection
186TR_HAS_VARIABLE_ROW_HEIGHT = wx.TR_HAS_VARIABLE_ROW_HEIGHT # what it says
187
188TR_EDIT_LABELS = wx.TR_EDIT_LABELS # can edit item labels
189TR_ROW_LINES = wx.TR_ROW_LINES # put border around items
190TR_HIDE_ROOT = wx.TR_HIDE_ROOT # don't display root node
191
192TR_FULL_ROW_HIGHLIGHT = wx.TR_FULL_ROW_HIGHLIGHT # highlight full horz space
193
eecfab17
RD
194TR_AUTO_CHECK_CHILD = 0x04000 # only meaningful for checkboxes
195TR_AUTO_TOGGLE_CHILD = 0x08000 # only meaningful for checkboxes
196TR_AUTO_CHECK_PARENT = 0x10000 # only meaningful for checkboxes
c8f129d0
RD
197
198TR_DEFAULT_STYLE = wx.TR_DEFAULT_STYLE # default style for the tree control
199
200# Values for the `flags' parameter of CustomTreeCtrl.HitTest() which determine
201# where exactly the specified point is situated:
202
203TREE_HITTEST_ABOVE = wx.TREE_HITTEST_ABOVE
204TREE_HITTEST_BELOW = wx.TREE_HITTEST_BELOW
205TREE_HITTEST_NOWHERE = wx.TREE_HITTEST_NOWHERE
206# on the button associated with an item.
207TREE_HITTEST_ONITEMBUTTON = wx.TREE_HITTEST_ONITEMBUTTON
208# on the bitmap associated with an item.
209TREE_HITTEST_ONITEMICON = wx.TREE_HITTEST_ONITEMICON
210# on the indent associated with an item.
211TREE_HITTEST_ONITEMINDENT = wx.TREE_HITTEST_ONITEMINDENT
212# on the label (string) associated with an item.
213TREE_HITTEST_ONITEMLABEL = wx.TREE_HITTEST_ONITEMLABEL
214# on the right of the label associated with an item.
215TREE_HITTEST_ONITEMRIGHT = wx.TREE_HITTEST_ONITEMRIGHT
216# on the label (string) associated with an item.
217TREE_HITTEST_ONITEMSTATEICON = wx.TREE_HITTEST_ONITEMSTATEICON
218# on the left of the CustomTreeCtrl.
219TREE_HITTEST_TOLEFT = wx.TREE_HITTEST_TOLEFT
220# on the right of the CustomTreeCtrl.
221TREE_HITTEST_TORIGHT = wx.TREE_HITTEST_TORIGHT
222# on the upper part (first half) of the item.
223TREE_HITTEST_ONITEMUPPERPART = wx.TREE_HITTEST_ONITEMUPPERPART
224# on the lower part (second half) of the item.
225TREE_HITTEST_ONITEMLOWERPART = wx.TREE_HITTEST_ONITEMLOWERPART
226# on the check icon, if present
227TREE_HITTEST_ONITEMCHECKICON = 0x4000
228# anywhere on the item
229TREE_HITTEST_ONITEM = TREE_HITTEST_ONITEMICON | TREE_HITTEST_ONITEMLABEL | TREE_HITTEST_ONITEMCHECKICON
230
231
232# Background Image Style
233_StyleTile = 0
234_StyleStretch = 1
235
236# Windows Vista Colours
237_rgbSelectOuter = wx.Colour(170, 200, 245)
238_rgbSelectInner = wx.Colour(230, 250, 250)
239_rgbSelectTop = wx.Colour(210, 240, 250)
240_rgbSelectBottom = wx.Colour(185, 215, 250)
241_rgbNoFocusTop = wx.Colour(250, 250, 250)
242_rgbNoFocusBottom = wx.Colour(235, 235, 235)
243_rgbNoFocusOuter = wx.Colour(220, 220, 220)
244_rgbNoFocusInner = wx.Colour(245, 245, 245)
245
246# Flags for wx.RendererNative
247_CONTROL_EXPANDED = 8
248_CONTROL_CURRENT = 16
249
250# Version Info
251__version__ = "0.8"
252
253
254# ----------------------------------------------------------------------------
255# CustomTreeCtrl events and binding for handling them
256# ----------------------------------------------------------------------------
257
258wxEVT_TREE_BEGIN_DRAG = wx.wxEVT_COMMAND_TREE_BEGIN_DRAG
259wxEVT_TREE_BEGIN_RDRAG = wx.wxEVT_COMMAND_TREE_BEGIN_RDRAG
260wxEVT_TREE_BEGIN_LABEL_EDIT = wx.wxEVT_COMMAND_TREE_BEGIN_LABEL_EDIT
261wxEVT_TREE_END_LABEL_EDIT = wx.wxEVT_COMMAND_TREE_END_LABEL_EDIT
262wxEVT_TREE_DELETE_ITEM = wx.wxEVT_COMMAND_TREE_DELETE_ITEM
263wxEVT_TREE_GET_INFO = wx.wxEVT_COMMAND_TREE_GET_INFO
264wxEVT_TREE_SET_INFO = wx.wxEVT_COMMAND_TREE_SET_INFO
265wxEVT_TREE_ITEM_EXPANDED = wx.wxEVT_COMMAND_TREE_ITEM_EXPANDED
266wxEVT_TREE_ITEM_EXPANDING = wx.wxEVT_COMMAND_TREE_ITEM_EXPANDING
267wxEVT_TREE_ITEM_COLLAPSED = wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSED
268wxEVT_TREE_ITEM_COLLAPSING = wx.wxEVT_COMMAND_TREE_ITEM_COLLAPSING
269wxEVT_TREE_SEL_CHANGED = wx.wxEVT_COMMAND_TREE_SEL_CHANGED
270wxEVT_TREE_SEL_CHANGING = wx.wxEVT_COMMAND_TREE_SEL_CHANGING
271wxEVT_TREE_KEY_DOWN = wx.wxEVT_COMMAND_TREE_KEY_DOWN
272wxEVT_TREE_ITEM_ACTIVATED = wx.wxEVT_COMMAND_TREE_ITEM_ACTIVATED
273wxEVT_TREE_ITEM_RIGHT_CLICK = wx.wxEVT_COMMAND_TREE_ITEM_RIGHT_CLICK
274wxEVT_TREE_ITEM_MIDDLE_CLICK = wx.wxEVT_COMMAND_TREE_ITEM_MIDDLE_CLICK
275wxEVT_TREE_END_DRAG = wx.wxEVT_COMMAND_TREE_END_DRAG
276wxEVT_TREE_STATE_IMAGE_CLICK = wx.wxEVT_COMMAND_TREE_STATE_IMAGE_CLICK
277wxEVT_TREE_ITEM_GETTOOLTIP = wx.wxEVT_COMMAND_TREE_ITEM_GETTOOLTIP
278wxEVT_TREE_ITEM_MENU = wx.wxEVT_COMMAND_TREE_ITEM_MENU
279wxEVT_TREE_ITEM_CHECKING = wx.NewEventType()
280wxEVT_TREE_ITEM_CHECKED = wx.NewEventType()
281wxEVT_TREE_ITEM_HYPERLINK = wx.NewEventType()
282
283EVT_TREE_BEGIN_DRAG = wx.EVT_TREE_BEGIN_DRAG
284EVT_TREE_BEGIN_RDRAG = wx.EVT_TREE_BEGIN_RDRAG
285EVT_TREE_BEGIN_LABEL_EDIT = wx.EVT_TREE_BEGIN_LABEL_EDIT
286EVT_TREE_END_LABEL_EDIT = wx.EVT_TREE_END_LABEL_EDIT
287EVT_TREE_DELETE_ITEM = wx.EVT_TREE_DELETE_ITEM
288EVT_TREE_GET_INFO = wx.EVT_TREE_GET_INFO
289EVT_TREE_SET_INFO = wx.EVT_TREE_SET_INFO
290EVT_TREE_ITEM_EXPANDED = wx.EVT_TREE_ITEM_EXPANDED
291EVT_TREE_ITEM_EXPANDING = wx.EVT_TREE_ITEM_EXPANDING
292EVT_TREE_ITEM_COLLAPSED = wx.EVT_TREE_ITEM_COLLAPSED
293EVT_TREE_ITEM_COLLAPSING = wx.EVT_TREE_ITEM_COLLAPSING
294EVT_TREE_SEL_CHANGED = wx.EVT_TREE_SEL_CHANGED
295EVT_TREE_SEL_CHANGING = wx.EVT_TREE_SEL_CHANGING
296EVT_TREE_KEY_DOWN = wx.EVT_TREE_KEY_DOWN
297EVT_TREE_ITEM_ACTIVATED = wx.EVT_TREE_ITEM_ACTIVATED
298EVT_TREE_ITEM_RIGHT_CLICK = wx.EVT_TREE_ITEM_RIGHT_CLICK
299EVT_TREE_ITEM_MIDDLE_CLICK = wx.EVT_TREE_ITEM_MIDDLE_CLICK
300EVT_TREE_END_DRAG = wx.EVT_TREE_END_DRAG
301EVT_TREE_STATE_IMAGE_CLICK = wx.EVT_TREE_STATE_IMAGE_CLICK
302EVT_TREE_ITEM_GETTOOLTIP = wx.EVT_TREE_ITEM_GETTOOLTIP
303EVT_TREE_ITEM_MENU = wx.EVT_TREE_ITEM_MENU
304EVT_TREE_ITEM_CHECKING = wx.PyEventBinder(wxEVT_TREE_ITEM_CHECKING, 1)
305EVT_TREE_ITEM_CHECKED = wx.PyEventBinder(wxEVT_TREE_ITEM_CHECKED, 1)
306EVT_TREE_ITEM_HYPERLINK = wx.PyEventBinder(wxEVT_TREE_ITEM_HYPERLINK, 1)
307
308
309def GetFlaggedData():
310 return zlib.decompress(
311'x\xda\x012\x02\xcd\xfd\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\
312\x00\x00\r\x08\x06\x00\x00\x00r\xeb\xe4|\x00\x00\x00\x04sBIT\x08\x08\x08\x08\
313|\x08d\x88\x00\x00\x01\xe9IDAT(\x91u\x92\xd1K\xd3a\x14\x86\x9f\xef|J2J\xc3%\
314\x85\x8e\x1cb\x93Hl\xd9,\x06F]4\x10\tD3\x83\x88\xc8\xbf\xc0\xb4\xaeBP1\xe9\
315\xa2(\xec\xaan\xc3\x82pD\xa1\x84\xb0\x88@3\x8c\xc9\xa2bT\xa2^\x8c\x81V3\xb6\
316\xb5\x9f\xce9\xbe.j\xb20\xdf\xeb\xf7\xe19\x07^\xa5D\x93\x9f\x9ea\xbf\t\x04\
317\xbf\x12\x8b[\xd8Kl\xf8<.\xeet\xb5\xab\xfc\x8e\xca\x87*ZzM\xf3\xb1j|G\xab\
318\xf0\xd4\x94\x13\x9a_&0\xbb\xc8\xd8\xf4g\xa2\xcfo\xa8-P\xc7\xf5\x07\xa6\xedD\
319\r\x8d\xb5\xfb\x11\x11\xb4\xd6\x88h\xb4\xd6L}\x8a\xf0\xe4\xd5G\x1e\rt*\x00\
320\xc9\x19\xb6\x03D4\xa7\xdcU\\8\xed\xa6\xa2\xa5\xd7\x00\xe8\xab\xf7\x9e\x9a\
321\xca\xb2\x9d\\\xf2\xd5!"dT\x86\xc9\xe4\x14\x83s\x83HF\xe3\xdc\xe5\xa4\xa8\
322\xb0\x88\xaa\xf2=D\x7f$il>\xdf\xafSe\xf5\xfd\x9dM\x87\xa9\xdc\xb7\x1b\xad5\
323\x93\xc9)\xfc\xe9Q\x12\xe9\x04\x13\x0b\x13\x94\xaaR\xdc{\x8f "\xec(,\xe0\xfe\
324\xb3\xb7H,a\xe1\xa9)\xdf<e$2Ble\x85\x94e\xb1\x96\xcep\xfb\xdd-D\x04\xa5\x14\
325\xdeZ\'\xb1\x84\x85\xd8\x8bm\x84\xe6\x977\x7f8kog)\xba\xc4\xb7\xe5\xef$\xe2?\
326\xe9\xa9\xbf\x86R\n\x11a&\x1c\xc1^lC|\r.\x02\xb3\x8b\x9b\xa6&G\x13W\xaa\xbb\
327\x91_\x05\x0c\x1d\xbfI\xc7\xa1\x8e\xbf&a|:\x8c\xaf\xc1\x05J4\x8e\xd6>36\x192\
328\xc9d\xdc\xa4RI\xb3\xbaj\x99tz\xcd\xac\xaf\xa7\xcd\xc6F\xc6d\xb3Y\xf32\xf8\
329\xc58Z\xfb\x8c\x12\xfd\x07R\xa2\xb98\xf0\xd0\xbcx\xf3a[\xe0\xf2\xd0c\x93\xeb\
330nYD\xdb\xc9:\xcex\x0f\xe2\xadu2\x13\x8e0>\x1d\xc6\xff\xfa\xfd\xff\x17\x91K\
331\xf7\xf0\xa8\t\x04\xe7X\x89[\x94\x96\xd8\xf0y\x0ep\xb7\xeb\xdc?\xdb\xfb\r|\
332\xd0\xd1]\x98\xbdm\xdc\x00\x00\x00\x00IEND\xaeB`\x82\x91\xe2\x08\x8f' )
333
334def GetFlaggedBitmap():
335 return wx.BitmapFromImage(GetFlaggedImage())
336
337def GetFlaggedImage():
338 stream = cStringIO.StringIO(GetFlaggedData())
339 return wx.ImageFromStream(stream)
340
341#----------------------------------------------------------------------
342def GetNotFlaggedData():
343 return zlib.decompress(
344'x\xda\x01\xad\x01R\xfe\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\
345\x00\x00\r\x08\x06\x00\x00\x00r\xeb\xe4|\x00\x00\x00\x04sBIT\x08\x08\x08\x08\
346|\x08d\x88\x00\x00\x01dIDAT(\x91\x95\xd21K\x82a\x14\x86\xe1\xe7=\xef798\xb8\
347\x89\x0e"|Cd\x94\x88\x83\x065\x88\x108\x88Q\x8b-\xd1\x1f\x88\x9a\n\x04\x11j\
348\x8eh\x08\xdaZ\x84(\x82\xc2 0\xc1 $\xb4P\xa1\x10\x11D\xb061\xd4\xd4\xcc\xe44\
349\x84 \xa8Hg~.\xcer\x0bA\x12\x83\xb7ux\xce\xd1T\x01\xd5z\x0b:\xad\x06n\xbb\
350\x8a\x83\xcdU1\xb8\x11\x83\xc8\xe0\r\xf0\x92\xdd\x0c\x97\xd5\x04\x9b\xaaG\
351\xb6XA,]B\xe41\x8f\xf7\xab=1\x84Vv\x8e\xd97\xaf\xc29m\x04\x91\x84\x94\n\xa4\
352\x94P\x14\x05\x89\xd77\x9c\xc5_\x10\x0em\x08\x00\xa0\xfe\x87q@J\x89\xc593\
353\xfc\xaeY\x18\xbc\x01\x06\x00\xb1}t\xc9\xf5F\x03\x01\xbfs$ \x92 "\x10I\xec\
354\x9e\xdcBQ\x08\x14M\x15\xe0\xb2\x9a&\x02"\x82\xc71\x85h\xaa\x00\xaa\xd6[\xb0\
355\xa9\xfa\x89\x80\x88\xe0\xb0\x98P\xad\xb7@:\xad\x06\xd9be" "$se\xe8\xb4\x1a\
356\x90\xdb\xae"\x96.M\x04D\x84H"\x07\xb7]\x05\x04I\x18}A\xbe\xbe\x7f\xe6Z\xed\
357\x83\x1b\x8d\x1a7\x9b\x9f\xdcn\xb7\xb8\xd3\xf9\xe2n\xf7\x9b{\xbd\x1f\xbe{\
358\xca\xb3\xd1\x17dA\xf2\x0f\t\x92X\x0b\x9d\xf2\xcdCf,X\xdf\x0fs\x7f;T\xc4\xf2\
359\xc2\x0c<\x8e)8,&$seD\x129\\\xc43\xa3\x8b\xf8O{\xbf\xf1\xb5\xa5\x990\x0co\
360\xd6\x00\x00\x00\x00IEND\xaeB`\x82&\x11\xab!' )
361
362def GetNotFlaggedBitmap():
363 return wx.BitmapFromImage(GetNotFlaggedImage())
364
365def GetNotFlaggedImage():
366 stream = cStringIO.StringIO(GetNotFlaggedData())
367 return wx.ImageFromStream(stream)
368
369#----------------------------------------------------------------------
370def GetCheckedData():
371 return zlib.decompress(
372"x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd1 \xcc\xc1\x06$\
373\x8b^?\xa9\x01R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xaf\xf4tq\x0c\xd1\x98\
374\x98<\x853\xe7\xc7y\x07\xa5\x84\xc4\x84\x84\x04\x0b3C1\xbd\x03'N\x1c9p\x84\
375\xe5\xe0\x993gx||\xce\x14\xcc\xea\xec\xect4^7\xbf\x91\xf3&\x8b\x93\xd4\x8c\
376\x19\n\xa7fv\\L\xd8p\x90C\xebx\xcf\x05\x17\x0ff \xb8c\xb6Cm\x06\xdb\xea\xd8\
377\xb2\x08\xd3\x03W\x0c\x8c\x8c\x16e%\xa5\xb5E\xe4\xee\xba\xca\xe4|\xb8\xb7\
378\xe35OOO\xcf\n\xb3\x83>m\x8c1R\x12\x92\x81s\xd8\x0b/\xb56\x14k|l\\\xc7x\xb4\
379\xf2\xc4\xc1*\xd5'B~\xbc\x19uNG\x98\x85\x85\x8d\xe3x%\x16\xb2_\xee\xf1\x07\
380\x99\xcb\xacl\x99\xc9\xcf\xb0\xc0_.\x87+\xff\x99\x05\xd0\xd1\x0c\x9e\xae~.\
381\xeb\x9c\x12\x9a\x00\x92\xccS\x9f" )
382
383def GetCheckedBitmap():
384 return wx.BitmapFromImage(GetCheckedImage())
385
386def GetCheckedImage():
387 stream = cStringIO.StringIO(GetCheckedData())
388 return wx.ImageFromStream(stream)
389
390#----------------------------------------------------------------------
391def GetNotCheckedData():
392 return zlib.decompress(
393"x\xda\xeb\x0c\xf0s\xe7\xe5\x92\xe2b``\xe0\xf5\xf4p\t\x02\xd1 \xcc\xc1\x06$\
394\x8b^?\xa9\x01R,\xc5N\x9e!\x1c@P\xc3\x91\xd2\x01\xe4\xe7z\xba8\x86hL\x9c{\
395\xe9 o\x83\x01\x07\xeb\x85\xf3\xed\x86w\x0ed\xdaT\x96\x8a\xbc\x9fw\xe7\xc4\
396\xd9/\x01\x8b\x97\x8a\xd7\xab*\xfar\xf0Ob\x93^\xf6\xd5%\x9d\x85A\xe6\xf6\x1f\
397\x11\x8f{/\x0b\xf8wX+\x9d\xf2\xb6:\x96\xca\xfe\x9a3\xbeA\xe7\xed\x1b\xc6%\
398\xfb=X3'sI-il\t\xb9\xa0\xc0;#\xd4\x835m\x9a\xf9J\x85\xda\x16.\x86\x03\xff\
399\xee\xdcc\xdd\xc0\xce\xf9\xc8\xcc(\xbe\x1bh1\x83\xa7\xab\x9f\xcb:\xa7\x84&\
400\x00\x87S=\xbe" )
401
402def GetNotCheckedBitmap():
403 return wx.BitmapFromImage(GetNotCheckedImage())
404
405def GetNotCheckedImage():
406 stream = cStringIO.StringIO(GetNotCheckedData())
407 return wx.ImageFromStream(stream)
408
409
410def GrayOut(anImage):
411 """
412 Convert the given image (in place) to a grayed-out version,
413 appropriate for a 'disabled' appearance.
414 """
415
416 factor = 0.7 # 0 < f < 1. Higher Is Grayer
417
418 if anImage.HasMask():
419 maskColor = (anImage.GetMaskRed(), anImage.GetMaskGreen(), anImage.GetMaskBlue())
420 else:
421 maskColor = None
422
423 data = map(ord, list(anImage.GetData()))
424
425 for i in range(0, len(data), 3):
426
427 pixel = (data[i], data[i+1], data[i+2])
428 pixel = MakeGray(pixel, factor, maskColor)
429
430 for x in range(3):
431 data[i+x] = pixel[x]
432
433 anImage.SetData(''.join(map(chr, data)))
434
435 return anImage
436
437
438def MakeGray((r,g,b), factor, maskColor):
439 """
440 Make a pixel grayed-out. If the pixel matches the maskcolor, it won't be
441 changed.
442 """
443
444 if (r,g,b) != maskColor:
445 return map(lambda x: int((230 - x) * factor) + x, (r,g,b))
446 else:
447 return (r,g,b)
448
449
450def DrawTreeItemButton(win, dc, rect, flags):
451 """ A simple replacement of wx.RendererNative.DrawTreeItemButton. """
452
453 # white background
454 dc.SetPen(wx.GREY_PEN)
455 dc.SetBrush(wx.WHITE_BRUSH)
456 dc.DrawRectangleRect(rect)
457
458 # black lines
459 xMiddle = rect.x + rect.width/2
460 yMiddle = rect.y + rect.height/2
461
462 # half of the length of the horz lines in "-" and "+"
463 halfWidth = rect.width/2 - 2
464 dc.SetPen(wx.BLACK_PEN)
465 dc.DrawLine(xMiddle - halfWidth, yMiddle,
466 xMiddle + halfWidth + 1, yMiddle)
467
468 if not flags & _CONTROL_EXPANDED:
469
470 # turn "-" into "+"
471 halfHeight = rect.height/2 - 2
472 dc.DrawLine(xMiddle, yMiddle - halfHeight,
473 xMiddle, yMiddle + halfHeight + 1)
474
475
476#---------------------------------------------------------------------------
477# DragImage Implementation
478# This Class Handles The Creation Of A Custom Image In Case Of Item Drag
479# And Drop.
480#---------------------------------------------------------------------------
481
482class DragImage(wx.DragImage):
483 """
484 This class handles the creation of a custom image in case of item drag
485 and drop.
486 """
487
488 def __init__(self, treeCtrl, item):
489 """
490 Default class constructor.
491 For internal use: do not call it in your code!
492 """
493
494 text = item.GetText()
495 font = item.Attr().GetFont()
496 colour = item.Attr().GetTextColour()
58dbf9d0 497 if not colour:
c8f129d0 498 colour = wx.BLACK
58dbf9d0 499 if not font:
c8f129d0
RD
500 font = treeCtrl._normalFont
501
502 backcolour = treeCtrl.GetBackgroundColour()
503 r, g, b = int(backcolour.Red()), int(backcolour.Green()), int(backcolour.Blue())
504 backcolour = ((r >> 1) + 20, (g >> 1) + 20, (b >> 1) + 20)
505 backcolour = wx.Colour(backcolour[0], backcolour[1], backcolour[2])
506 self._backgroundColour = backcolour
507
508 tempdc = wx.ClientDC(treeCtrl)
509 tempdc.SetFont(font)
510 width, height, dummy = tempdc.GetMultiLineTextExtent(text + "M")
511
512 image = item.GetCurrentImage()
513
514 image_w, image_h = 0, 0
515 wcheck, hcheck = 0, 0
516 itemcheck = None
517 itemimage = None
518 ximagepos = 0
519 yimagepos = 0
520 xcheckpos = 0
521 ycheckpos = 0
522
523 if image != _NO_IMAGE:
524 if treeCtrl._imageListNormal:
525 image_w, image_h = treeCtrl._imageListNormal.GetSize(image)
526 image_w += 4
527 itemimage = treeCtrl._imageListNormal.GetBitmap(image)
528
529 checkimage = item.GetCurrentCheckedImage()
530
531 if checkimage is not None:
532 if treeCtrl._imageListCheck:
533 wcheck, hcheck = treeCtrl._imageListCheck.GetSize(checkimage)
534 wcheck += 4
535 itemcheck = treeCtrl._imageListCheck.GetBitmap(checkimage)
536
537 total_h = max(hcheck, height)
538 total_h = max(image_h, total_h)
539
540 if image_w:
541 ximagepos = wcheck
542 yimagepos = ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0]
543
544 if checkimage is not None:
545 xcheckpos = 2
546 ycheckpos = ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0] + 2
547
548 extraH = ((total_h > height) and [(total_h - height)/2] or [0])[0]
549
550 xtextpos = wcheck + image_w
551 ytextpos = extraH
552
553 total_h = max(image_h, hcheck)
554 total_h = max(total_h, height)
555
556 if total_h < 30:
557 total_h += 2 # at least 2 pixels
558 else:
559 total_h += total_h/10 # otherwise 10% extra spacing
560
561 total_w = image_w + wcheck + width
562
563 self._total_w = total_w
564 self._total_h = total_h
565 self._itemimage = itemimage
566 self._itemcheck = itemcheck
567 self._text = text
568 self._colour = colour
569 self._font = font
570 self._xtextpos = xtextpos
571 self._ytextpos = ytextpos
572 self._ximagepos = ximagepos
573 self._yimagepos = yimagepos
574 self._xcheckpos = xcheckpos
575 self._ycheckpos = ycheckpos
576 self._textwidth = width
577 self._textheight = height
578 self._extraH = extraH
579
580 self._bitmap = self.CreateBitmap()
581
582 wx.DragImage.__init__(self, self._bitmap)
583
584
585 def CreateBitmap(self):
586 """Actually creates the dnd bitmap."""
587
588 memory = wx.MemoryDC()
589
590 bitmap = wx.EmptyBitmap(self._total_w, self._total_h)
591 memory.SelectObject(bitmap)
592
593 memory.SetTextBackground(self._backgroundColour)
594 memory.SetBackground(wx.Brush(self._backgroundColour))
595 memory.SetFont(self._font)
596 memory.SetTextForeground(self._colour)
597 memory.Clear()
598
599 if self._itemimage:
600 memory.DrawBitmap(self._itemimage, self._ximagepos, self._yimagepos, True)
601
602 if self._itemcheck:
603 memory.DrawBitmap(self._itemcheck, self._xcheckpos, self._ycheckpos, True)
604
605 textrect = wx.Rect(self._xtextpos, self._ytextpos+self._extraH, self._textwidth, self._textheight)
606 memory.DrawLabel(self._text, textrect)
607
608 memory.SelectObject(wx.NullBitmap)
609
610 return bitmap
611
612
613# ----------------------------------------------------------------------------
614# TreeItemAttr: a structure containing the visual attributes of an item
615# ----------------------------------------------------------------------------
616
617class TreeItemAttr:
618 """Creates the item attributes (text colour, background colour and font)."""
619
620 def __init__(self, colText=wx.NullColour, colBack=wx.NullColour, font=wx.NullFont):
621 """
622 Default class constructor.
623 For internal use: do not call it in your code!
624 """
625
626 self._colText = colText
627 self._colBack = colBack
628 self._font = font
629
630 # setters
631 def SetTextColour(self, colText):
632 """Sets the attribute text colour."""
633
634 self._colText = colText
635
636
637 def SetBackgroundColour(self, colBack):
638 """Sets the attribute background colour."""
639
640 self._colBack = colBack
641
642
643 def SetFont(self, font):
644 """Sets the attribute font."""
645
646 self._font = font
647
648
649 # accessors
650 def HasTextColour(self):
651 """Returns whether the attribute has text colour."""
652
653 return self._colText != wx.NullColour
654
655
656 def HasBackgroundColour(self):
657 """Returns whether the attribute has background colour."""
658
659 return self._colBack != wx.NullColour
660
661
662 def HasFont(self):
663 """Returns whether the attribute has font."""
664
665 return self._font != wx.NullFont
666
667
668 # getters
669 def GetTextColour(self):
670 """Returns the attribute text colour."""
671
672 return self._colText
673
674
675 def GetBackgroundColour(self):
676 """Returns the attribute background colour."""
677
678 return self._colBack
679
680
681 def GetFont(self):
682 """Returns the attribute font."""
683
684 return self._font
685
686
687# ----------------------------------------------------------------------------
688# CommandTreeEvent Is A Special Subclassing Of wx.PyCommandEvent
689#
690# NB: Note That Not All The Accessors Make Sense For All The Events, See The
691# Event Description Below.
692# ----------------------------------------------------------------------------
693
694class CommandTreeEvent(wx.PyCommandEvent):
695 """
696 CommandTreeEvent is a special subclassing of wx.PyCommandEvent.
697 NB: note that not all the accessors make sense for all the events, see the
698 event description for every method in this class.
699 """
700
701 def __init__(self, type, id, item=None, evtKey=None, point=None,
702 label=None, **kwargs):
703 """
704 Default class constructor.
705 For internal use: do not call it in your code!
706 """
707
708 wx.PyCommandEvent.__init__(self, type, id, **kwargs)
709 self._item = item
710 self._evtKey = evtKey
711 self._pointDrag = point
712 self._label = label
713
714
715 def GetItem(self):
716 """
717 Gets the item on which the operation was performed or the newly selected
718 item for EVT_TREE_SEL_CHANGED/ING events.
719 """
720
721 return self._item
722
723
724 def SetItem(self, item):
725 """
726 Sets the item on which the operation was performed or the newly selected
727 item for EVT_TREE_SEL_CHANGED/ING events.
728 """
729
730 self._item = item
731
732
733 def GetOldItem(self):
734 """For EVT_TREE_SEL_CHANGED/ING events, gets the previously selected item."""
735
736 return self._itemOld
737
738
739 def SetOldItem(self, item):
740 """For EVT_TREE_SEL_CHANGED/ING events, sets the previously selected item."""
741
742 self._itemOld = item
743
744
745 def GetPoint(self):
746 """
747 Returns the point where the mouse was when the drag operation started
748 (for EVT_TREE_BEGIN(R)DRAG events only) or the click position.
749 """
750
751 return self._pointDrag
752
753
754 def SetPoint(self, pt):
755 """
756 Sets the point where the mouse was when the drag operation started
757 (for EVT_TREE_BEGIN(R)DRAG events only) or the click position.
758 """
759
760 self._pointDrag = pt
761
762
763 def GetKeyEvent(self):
764 """Keyboard data (for EVT_TREE_KEY_DOWN only)."""
765
766 return self._evtKey
767
768
769 def GetKeyCode(self):
770 """Returns the integer key code (for EVT_TREE_KEY_DOWN only)."""
771
772 return self._evtKey.GetKeyCode()
773
774
775 def SetKeyEvent(self, evt):
776 """Keyboard data (for EVT_TREE_KEY_DOWN only)."""
777
778 self._evtKey = evt
779
780
781 def GetLabel(self):
782 """Returns the label-itemtext (for EVT_TREE_BEGIN|END_LABEL_EDIT only)."""
783
784 return self._label
785
786
787 def SetLabel(self, label):
788 """Sets the label-itemtext (for EVT_TREE_BEGIN|END_LABEL_EDIT only)."""
789
790 self._label = label
791
792
793 def IsEditCancelled(self):
794 """Returns the edit cancel flag (for EVT_TREE_BEGIN|END_LABEL_EDIT only)."""
795
796 return self._editCancelled
797
798
799 def SetEditCanceled(self, editCancelled):
800 """Sets the edit cancel flag (for EVT_TREE_BEGIN|END_LABEL_EDIT only)."""
801
802 self._editCancelled = editCancelled
803
804
805 def SetToolTip(self, toolTip):
806 """Sets the tooltip for the item (for EVT_TREE_ITEM_GETTOOLTIP events)."""
807
808 self._label = toolTip
809
810
811 def GetToolTip(self):
812 """Gets the tooltip for the item (for EVT_TREE_ITEM_GETTOOLTIP events)."""
813
814 return self._label
815
816
817# ----------------------------------------------------------------------------
818# TreeEvent is a special class for all events associated with tree controls
819#
820# NB: note that not all accessors make sense for all events, see the event
821# descriptions below
822# ----------------------------------------------------------------------------
823
824class TreeEvent(CommandTreeEvent):
825
826 def __init__(self, type, id, item=None, evtKey=None, point=None,
827 label=None, **kwargs):
828 """
829 Default class constructor.
830 For internal use: do not call it in your code!
831 """
832
833 CommandTreeEvent.__init__(self, type, id, item, evtKey, point, label, **kwargs)
834 self.notify = wx.NotifyEvent(type, id)
835
836
837 def GetNotifyEvent(self):
838 """Returns the actual wx.NotifyEvent."""
839
840 return self.notify
841
842
843 def IsAllowed(self):
844 """Returns whether the event is allowed or not."""
845
846 return self.notify.IsAllowed()
847
848
849 def Veto(self):
850 """Vetos the event."""
851
852 self.notify.Veto()
853
854
855 def Allow(self):
856 """The event is allowed."""
857
858 self.notify.Allow()
859
860
861# -----------------------------------------------------------------------------
862# Auxiliary Classes: TreeRenameTimer
863# -----------------------------------------------------------------------------
864
865class TreeRenameTimer(wx.Timer):
866 """Timer used for enabling in-place edit."""
867
868 def __init__(self, owner):
869 """
870 Default class constructor.
871 For internal use: do not call it in your code!
872 """
873
874 wx.Timer.__init__(self)
875 self._owner = owner
876
877
878 def Notify(self):
879 """The timer has expired."""
880
881 self._owner.OnRenameTimer()
882
883
884# -----------------------------------------------------------------------------
885# Auxiliary Classes: TreeTextCtrl
886# This Is The Temporary wx.TextCtrl Created When You Edit The Text Of An Item
887# -----------------------------------------------------------------------------
888
889class TreeTextCtrl(wx.TextCtrl):
890 """Control used for in-place edit."""
891
892 def __init__(self, owner, item=None):
893 """
894 Default class constructor.
895 For internal use: do not call it in your code!
896 """
897
898 self._owner = owner
899 self._itemEdited = item
900 self._startValue = item.GetText()
901 self._finished = False
902 self._aboutToFinish = False
903
904 w = self._itemEdited.GetWidth()
905 h = self._itemEdited.GetHeight()
906
907 wnd = self._itemEdited.GetWindow()
908 if wnd:
909 w = w - self._itemEdited.GetWindowSize()[0]
910 h = 0
911
912 x, y = self._owner.CalcScrolledPosition(item.GetX(), item.GetY())
913
914 image_h = 0
915 image_w = 0
916
917 image = item.GetCurrentImage()
918
919 if image != _NO_IMAGE:
920
921 if self._owner._imageListNormal:
922 image_w, image_h = self._owner._imageListNormal.GetSize(image)
923 image_w += 4
924
925 else:
926
e1463b9d 927 raise Exception("\n ERROR: You Must Create An Image List To Use Images!")
c8f129d0
RD
928
929 checkimage = item.GetCurrentCheckedImage()
930
931 if checkimage is not None:
932 wcheck, hcheck = self._owner._imageListCheck.GetSize(checkimage)
933 wcheck += 4
934 else:
935 wcheck = 0
936
937 if wnd:
938 h = max(hcheck, image_h)
939 dc = wx.ClientDC(self._owner)
940 h = max(h, dc.GetTextExtent("Aq")[1])
941 h = h + 2
942
943 # FIXME: what are all these hardcoded 4, 8 and 11s really?
944 x += image_w + wcheck
945 w -= image_w + 4 + wcheck
946
c8f129d0
RD
947 wx.TextCtrl.__init__(self, self._owner, wx.ID_ANY, self._startValue,
948 wx.Point(x - 4, y), wx.Size(w + 15, h))
94431133
RD
949 if wx.Platform == "__WXMAC__":
950 self.SetFont(owner.GetFont())
951 bs = self.GetBestSize()
952 self.SetSize((-1, bs.height))
c8f129d0
RD
953
954 self.Bind(wx.EVT_CHAR, self.OnChar)
955 self.Bind(wx.EVT_KEY_UP, self.OnKeyUp)
956 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
957
958
959 def AcceptChanges(self):
960 """Accepts/refuses the changes made by the user."""
961
962 value = self.GetValue()
963
964 if value == self._startValue:
965 # nothing changed, always accept
966 # when an item remains unchanged, the owner
967 # needs to be notified that the user decided
968 # not to change the tree item label, and that
969 # the edit has been cancelled
970 self._owner.OnRenameCancelled(self._itemEdited)
971 return True
972
973 if not self._owner.OnRenameAccept(self._itemEdited, value):
974 # vetoed by the user
975 return False
976
977 # accepted, do rename the item
978 self._owner.SetItemText(self._itemEdited, value)
979
980 return True
981
982
983 def Finish(self):
984 """Finish editing."""
985
986 if not self._finished:
987
988## wxPendingDelete.Append(this)
989 self._finished = True
990 self._owner.SetFocusIgnoringChildren()
991 self._owner.ResetTextControl()
992
993
994 def OnChar(self, event):
995 """Handles the wx.EVT_CHAR event for TreeTextCtrl."""
996
997 keycode = event.GetKeyCode()
998
999 if keycode == wx.WXK_RETURN:
1000 self._aboutToFinish = True
1001 # Notify the owner about the changes
1002 self.AcceptChanges()
1003 # Even if vetoed, close the control (consistent with MSW)
1004 wx.CallAfter(self.Finish)
1005
1006 elif keycode == wx.WXK_ESCAPE:
1007 self.StopEditing()
1008
1009 else:
1010 event.Skip()
1011
1012
1013 def OnKeyUp(self, event):
1014 """Handles the wx.EVT_KEY_UP event for TreeTextCtrl."""
1015
1016 if not self._finished:
1017
1018 # auto-grow the textctrl:
1019 parentSize = self._owner.GetSize()
1020 myPos = self.GetPosition()
1021 mySize = self.GetSize()
1022
1023 sx, sy = self.GetTextExtent(self.GetValue() + "M")
1024 if myPos.x + sx > parentSize.x:
1025 sx = parentSize.x - myPos.x
1026 if mySize.x > sx:
1027 sx = mySize.x
1028
1029 self.SetSize((sx, -1))
1030
1031 event.Skip()
1032
1033
1034 def OnKillFocus(self, event):
1035 """Handles the wx.EVT_KILL_FOCUS event for TreeTextCtrl."""
1036
1037 # I commented out those lines, and everything seems to work fine.
1038 # But why in the world are these lines of code here? Maybe GTK
1039 # or MAC give troubles?
1040
1041## if not self._finished and not self._aboutToFinish:
1042##
1043## # We must finish regardless of success, otherwise we'll get
1044## # focus problems:
1045##
1046## if not self.AcceptChanges():
1047## self._owner.OnRenameCancelled(self._itemEdited)
1048
1049 # We must let the native text control handle focus, too, otherwise
1050 # it could have problems with the cursor (e.g., in wxGTK).
1051 event.Skip()
1052
1053
1054 def StopEditing(self):
1055 """Suddenly stops the editing."""
1056
1057 self._owner.OnRenameCancelled(self._itemEdited)
1058 self.Finish()
1059
1060
1061 def item(self):
1062 """Returns the item currently edited."""
1063
1064 return self._itemEdited
1065
1066
1067# -----------------------------------------------------------------------------
1068# Auxiliary Classes: TreeFindTimer
1069# Timer Used To Clear CustomTreeCtrl._findPrefix If No Key Was Pressed For A
1070# Sufficiently Long Time.
1071# -----------------------------------------------------------------------------
1072
1073class TreeFindTimer(wx.Timer):
1074 """
1075 Timer used to clear CustomTreeCtrl._findPrefix if no key was pressed
1076 for a sufficiently long time.
1077 """
1078
1079 def __init__(self, owner):
1080 """
1081 Default class constructor.
1082 For internal use: do not call it in your code!
1083 """
1084
1085 wx.Timer.__init__(self)
1086 self._owner = owner
1087
1088
1089 def Notify(self):
1090 """The timer has expired."""
1091
1092 self._owner._findPrefix = ""
1093
1094
1095# -----------------------------------------------------------------------------
1096# GenericTreeItem Implementation.
1097# This Class Holds All The Information And Methods For Every Single Item In
1098# CustomTreeCtrl.
1099# -----------------------------------------------------------------------------
1100
1101class GenericTreeItem:
1102 """
1103 This class holds all the information and methods for every single item in
1104 CustomTreeCtrl. No wx based.
1105 """
1106
1107 def __init__(self, parent, text="", ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
1108 """
1109 Default class constructor.
1110 For internal use: do not call it in your code!
1111 """
1112
1113 # since there can be very many of these, we save size by chosing
1114 # the smallest representation for the elements and by ordering
1115 # the members to avoid padding.
1116 self._text = text # label to be rendered for item
1117 self._data = data # user-provided data
1118
1119 self._children = [] # list of children
1120 self._parent = parent # parent of this item
1121
1122 self._attr = None # attributes???
1123
1124 # tree ctrl images for the normal, selected, expanded and
1125 # expanded+selected states
1126 self._images = [-1, -1, -1, -1]
1127 self._images[TreeItemIcon_Normal] = image
1128 self._images[TreeItemIcon_Selected] = selImage
1129 self._images[TreeItemIcon_Expanded] = _NO_IMAGE
1130 self._images[TreeItemIcon_SelectedExpanded] = _NO_IMAGE
1131
1132 self._checkedimages = [None, None, None, None]
1133
1134 self._x = 0 # (virtual) offset from top
1135 self._y = 0 # (virtual) offset from left
1136 self._width = 0 # width of this item
1137 self._height = 0 # height of this item
1138
1139 self._isCollapsed = True
1140 self._hasHilight = False # same as focused
1141 self._hasPlus = False # used for item which doesn't have
1142 # children but has a [+] button
1143 self._isBold = False # render the label in bold font
1144 self._isItalic = False # render the label in italic font
1145 self._ownsAttr = False # delete attribute when done
1146 self._type = ct_type # item type: 0=normal, 1=check, 2=radio
1147 self._checked = False # only meaningful for check and radio
1148 self._enabled = True # flag to enable/disable an item
1149 self._hypertext = False # indicates if the item is hypertext
1150 self._visited = False # visited state for an hypertext item
1151
1152 if self._type > 0:
1153 # do not construct the array for normal items
1154 self._checkedimages[TreeItemIcon_Checked] = 0
1155 self._checkedimages[TreeItemIcon_NotChecked] = 1
1156 self._checkedimages[TreeItemIcon_Flagged] = 2
1157 self._checkedimages[TreeItemIcon_NotFlagged] = 3
1158
1159 if parent:
1160 if parent.GetType() == 2 and not parent.IsChecked():
1161 # if the node parent is a radio not enabled, we are disabled
1162 self._enabled = False
1163
1164 self._wnd = wnd # are we holding a window?
1165
1166 if wnd:
1167 if wnd.GetSizer(): # the window is a complex one hold by a sizer
1168 size = wnd.GetBestSize()
1169 else: # simple window, without sizers
1170 size = wnd.GetSize()
1171
1172 # We have to bind the wx.EVT_SET_FOCUS for the associated window
1173 # No other solution to handle the focus changing from an item in
1174 # CustomTreeCtrl and the window associated to an item
1175 # Do better strategies exist?
1176 self._wnd.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
1177
1178 self._height = size.GetHeight() + 2
1179 self._width = size.GetWidth()
1180 self._windowsize = size
1181
1182 # We don't show the window if the item is collapsed
1183 if self._isCollapsed:
1184 self._wnd.Show(False)
1185
1186 # The window is enabled only if the item is enabled
1187 self._wnd.Enable(self._enabled)
1188 self._windowenabled = self._enabled
1189
1190
1191 def IsOk(self):
1192 """
1193 Returns whether the item is ok or not. Useless on Python, but added for
1194 backward compatibility with the C++ implementation.
1195 """
1196
1197 return True
1198
1199
1200 def GetChildren(self):
1201 """Returns the item's children."""
1202
1203 return self._children
1204
1205
1206 def GetText(self):
1207 """Returns the item text."""
1208
1209 return self._text
1210
1211
1212 def GetImage(self, which=TreeItemIcon_Normal):
1213 """Returns the item image for a particular state."""
1214
1215 return self._images[which]
1216
1217
1218 def GetCheckedImage(self, which=TreeItemIcon_Checked):
1219 """Returns the item check image. Meaningful only for radio & check items."""
1220
1221 return self._checkedimages[which]
1222
1223
1224 def GetData(self):
1225 """Returns the data associated to this item."""
1226
1227 return self._data
1228
1229
1230 def SetImage(self, image, which):
1231 """Sets the item image."""
1232
1233 self._images[which] = image
1234
1235
1236 def SetData(self, data):
1237 """Sets the data associated to this item."""
1238
1239 self._data = data
1240
1241
1242 def SetHasPlus(self, has=True):
1243 """Sets whether an item has the 'plus' button."""
1244
1245 self._hasPlus = has
1246
1247
1248 def SetBold(self, bold):
1249 """Sets the item font bold."""
1250
1251 self._isBold = bold
1252
1253
1254 def SetItalic(self, italic):
1255 """Sets the item font italic."""
1256
1257 self._isItalic = italic
1258
1259
1260 def GetX(self):
1261 """Returns the x position on an item in the ScrolledWindow."""
1262
1263 return self._x
1264
1265
1266 def GetY(self):
1267 """Returns the y position on an item in the ScrolledWindow."""
1268
1269 return self._y
1270
1271
1272 def SetX(self, x):
1273 """Sets the x position on an item in the ScrolledWindow."""
1274
1275 self._x = x
1276
1277
1278 def SetY(self, y):
1279 """Sets the y position on an item in the ScrolledWindow."""
1280
1281 self._y = y
1282
1283
1284 def GetHeight(self):
1285 """Returns the height of the item."""
1286
1287 return self._height
1288
1289
1290 def GetWidth(self):
1291 """Returns the width of the item."""
1292
1293 return self._width
1294
1295
1296 def SetHeight(self, h):
1297 """Sets the height of the item."""
1298
1299 self._height = h
1300
1301
1302 def SetWidth(self, w):
1303 """Sets the width of the item."""
1304
1305 self._width = w
1306
1307
1308 def SetWindow(self, wnd):
1309 """Sets the window associated to the item."""
1310
1311 self._wnd = wnd
1312
1313
1314 def GetWindow(self):
1315 """Returns the window associated to the item."""
1316
1317 return self._wnd
1318
1319
1320 def GetWindowEnabled(self):
1321 """Returns whether the associated window is enabled or not."""
1322
1323 if not self._wnd:
e1463b9d 1324 raise Exception("\nERROR: This Item Has No Window Associated")
c8f129d0
RD
1325
1326 return self._windowenabled
1327
1328
1329 def SetWindowEnabled(self, enable=True):
1330 """Sets whether the associated window is enabled or not."""
1331
1332 if not self._wnd:
e1463b9d 1333 raise Exception("\nERROR: This Item Has No Window Associated")
c8f129d0
RD
1334
1335 self._windowenabled = enable
1336 self._wnd.Enable(enable)
1337
1338
1339 def GetWindowSize(self):
1340 """Returns the associated window size."""
1341
1342 return self._windowsize
1343
1344
1345 def OnSetFocus(self, event):
1346 """Handles the wx.EVT_SET_FOCUS event for the associated window."""
1347
1348 treectrl = self._wnd.GetParent()
1349 select = treectrl.GetSelection()
1350
1351 # If the window is associated to an item that currently is selected
1352 # (has focus) we don't kill the focus. Otherwise we do it.
1353 if select != self:
1354 treectrl._hasFocus = False
1355 else:
1356 treectrl._hasFocus = True
1357
1358 event.Skip()
1359
1360
1361 def GetType(self):
1362 """
1363 Returns the item type. It should be one of:
1364 0: normal items
1365 1: checkbox item
1366 2: radiobutton item
1367 """
1368
1369 return self._type
1370
1371
1372 def SetHyperText(self, hyper=True):
1373 """Sets whether the item is hypertext or not."""
1374
1375 self._hypertext = hyper
1376
1377
1378 def SetVisited(self, visited=True):
1379 """Sets whether an hypertext item was visited or not."""
1380
1381 self._visited = visited
1382
1383
1384 def GetVisited(self):
1385 """Returns whether an hypertext item was visited or not."""
1386
1387 return self._visited
1388
1389
1390 def IsHyperText(self):
1391 """Returns whether the item is hypetext or not."""
1392
1393 return self._hypertext
1394
1395
1396 def GetParent(self):
1397 """Gets the item parent."""
1398
1399 return self._parent
1400
1401
1402 def Insert(self, child, index):
1403 """Inserts an item in the item children."""
1404
1405 self._children.insert(index, child)
1406
1407
1408 def Expand(self):
1409 """Expand the item."""
1410
1411 self._isCollapsed = False
1412
1413
1414 def Collapse(self):
1415 """Collapse the item."""
1416
1417 self._isCollapsed = True
1418
1419
1420 def SetHilight(self, set=True):
1421 """Sets the item focus/unfocus."""
1422
1423 self._hasHilight = set
1424
1425
1426 def HasChildren(self):
1427 """Returns whether the item has children or not."""
1428
1429 return len(self._children) > 0
1430
1431
1432 def IsSelected(self):
1433 """Returns whether the item is selected or not."""
1434
1435 return self._hasHilight != 0
1436
1437
1438 def IsExpanded(self):
1439 """Returns whether the item is expanded or not."""
1440
1441 return not self._isCollapsed
1442
1443
1444 def IsChecked(self):
1445 """Returns whether the item is checked or not."""
1446
1447 return self._checked
1448
1449
1450 def Check(self, checked=True):
1451 """Check an item. Meaningful only for check and radio items."""
1452
1453 self._checked = checked
1454
1455
1456 def HasPlus(self):
1457 """Returns whether the item has the plus button or not."""
1458
1459 return self._hasPlus or self.HasChildren()
1460
1461
1462 def IsBold(self):
1463 """Returns whether the item font is bold or not."""
1464
1465 return self._isBold != 0
1466
1467
1468 def IsItalic(self):
1469 """Returns whether the item font is italic or not."""
1470
1471 return self._isItalic != 0
1472
1473
1474 def Enable(self, enable=True):
1475 """Enables/disables the item."""
1476
1477 self._enabled = enable
1478
1479
1480 def IsEnabled(self):
1481 """Returns whether the item is enabled or not."""
1482
1483 return self._enabled
1484
1485
1486 def GetAttributes(self):
1487 """Returns the item attributes (font, colours)."""
1488
1489 return self._attr
1490
1491
1492 def Attr(self):
1493 """Creates a new attribute (font, colours)."""
1494
1495 if not self._attr:
1496
1497 self._attr = TreeItemAttr()
1498 self._ownsAttr = True
1499
1500 return self._attr
1501
1502
1503 def SetAttributes(self, attr):
1504 """Sets the item attributes (font, colours)."""
1505
1506 if self._ownsAttr:
1507 del self._attr
1508
1509 self._attr = attr
1510 self._ownsAttr = False
1511
1512
1513 def AssignAttributes(self, attr):
1514 """Assigns the item attributes (font, colours)."""
1515
1516 self.SetAttributes(attr)
1517 self._ownsAttr = True
1518
1519
1520 def DeleteChildren(self, tree):
1521 """Deletes the item children."""
1522
1523 for child in self._children:
1524 if tree:
1525 tree.SendDeleteEvent(child)
1526
1527 child.DeleteChildren(tree)
1528
1529 if child == tree._select_me:
1530 tree._select_me = None
1531
1532 # We have to destroy the associated window
1533 wnd = child.GetWindow()
1534 if wnd:
1535 wnd.Destroy()
1536 child._wnd = None
1537
1538 if child in tree._itemWithWindow:
1539 tree._itemWithWindow.remove(child)
1540
1541 del child
1542
1543 self._children = []
1544
1545
1546 def SetText(self, text):
1547 """Sets the item text."""
1548
1549 self._text = text
1550
1551
1552 def GetChildrenCount(self, recursively=True):
1553 """Gets the number of children."""
1554
1555 count = len(self._children)
1556
1557 if not recursively:
1558 return count
1559
1560 total = count
1561
1562 for n in xrange(count):
1563 total += self._children[n].GetChildrenCount()
1564
1565 return total
1566
1567
1568 def GetSize(self, x, y, theButton):
1569 """Returns the item size."""
1570
1571 bottomY = self._y + theButton.GetLineHeight(self)
1572
1573 if y < bottomY:
1574 y = bottomY
1575
1576 width = self._x + self._width
1577
1578 if x < width:
1579 x = width
1580
1581 if self.IsExpanded():
1582 for child in self._children:
1583 x, y = child.GetSize(x, y, theButton)
1584
1585 return x, y
1586
1587
1588 def HitTest(self, point, theCtrl, flags=0, level=0):
1589 """
1590 HitTest method for an item. Called from the main window HitTest.
1591 see the CustomTreeCtrl HitTest method for the flags explanation.
1592 """
1593
1594 # for a hidden root node, don't evaluate it, but do evaluate children
1595 if not (level == 0 and theCtrl.HasFlag(TR_HIDE_ROOT)):
1596
1597 # evaluate the item
1598 h = theCtrl.GetLineHeight(self)
1599
1600 if point.y > self._y and point.y < self._y + h:
1601
1602 y_mid = self._y + h/2
1603
1604 if point.y < y_mid:
1605 flags |= TREE_HITTEST_ONITEMUPPERPART
1606 else:
1607 flags |= TREE_HITTEST_ONITEMLOWERPART
1608
1609 xCross = self._x - theCtrl.GetSpacing()
1610
1611 if wx.Platform == "__WXMAC__":
1612 # according to the drawing code the triangels are drawn
1613 # at -4 , -4 from the position up to +10/+10 max
1614 if point.x > xCross-4 and point.x < xCross+10 and point.y > y_mid-4 and \
1615 point.y < y_mid+10 and self.HasPlus() and theCtrl.HasButtons():
1616
1617 flags |= TREE_HITTEST_ONITEMBUTTON
1618 return self, flags
1619 else:
1620 # 5 is the size of the plus sign
1621 if point.x > xCross-6 and point.x < xCross+6 and point.y > y_mid-6 and \
1622 point.y < y_mid+6 and self.HasPlus() and theCtrl.HasButtons():
1623
1624 flags |= TREE_HITTEST_ONITEMBUTTON
1625 return self, flags
1626
1627 if point.x >= self._x and point.x <= self._x + self._width:
1628
1629 image_w = -1
1630 wcheck = 0
1631
1632 # assuming every image (normal and selected) has the same size!
1633 if self.GetImage() != _NO_IMAGE and theCtrl._imageListNormal:
1634 image_w, image_h = theCtrl._imageListNormal.GetSize(self.GetImage())
1635
1636 if self.GetCheckedImage() is not None:
1637 wcheck, hcheck = theCtrl._imageListCheck.GetSize(self.GetCheckedImage())
1638
1639 if wcheck and point.x <= self._x + wcheck + 1:
1640 flags |= TREE_HITTEST_ONITEMCHECKICON
1641 return self, flags
1642
1643 if image_w != -1 and point.x <= self._x + wcheck + image_w + 1:
1644 flags |= TREE_HITTEST_ONITEMICON
1645 else:
1646 flags |= TREE_HITTEST_ONITEMLABEL
1647
1648 return self, flags
1649
1650 if point.x < self._x:
1651 flags |= TREE_HITTEST_ONITEMINDENT
1652 if point.x > self._x + self._width:
1653 flags |= TREE_HITTEST_ONITEMRIGHT
1654
1655 return self, flags
1656
1657 # if children are expanded, fall through to evaluate them
1658 if self._isCollapsed:
1659 return None, 0
1660
1661 # evaluate children
1662 for child in self._children:
1663 res, flags = child.HitTest(point, theCtrl, flags, level + 1)
1664 if res != None:
1665 return res, flags
1666
1667 return None, 0
1668
1669
1670 def GetCurrentImage(self):
1671 """Returns the current item image."""
1672
1673 image = _NO_IMAGE
1674
1675 if self.IsExpanded():
1676
1677 if self.IsSelected():
1678
1679 image = self.GetImage(TreeItemIcon_SelectedExpanded)
1680
1681 if image == _NO_IMAGE:
1682
1683 # we usually fall back to the normal item, but try just the
1684 # expanded one (and not selected) first in this case
1685 image = self.GetImage(TreeItemIcon_Expanded)
1686
1687 else: # not expanded
1688
1689 if self.IsSelected():
1690 image = self.GetImage(TreeItemIcon_Selected)
1691
1692 # maybe it doesn't have the specific image we want,
1693 # try the default one instead
1694 if image == _NO_IMAGE:
1695 image = self.GetImage()
1696
1697 return image
1698
1699
1700 def GetCurrentCheckedImage(self):
1701 """Returns the current item check image."""
1702
1703 if self._type == 0:
1704 return None
1705
1706 if self.IsChecked():
1707 if self._type == 1: # Checkbox
1708 return self._checkedimages[TreeItemIcon_Checked]
1709 else: # Radiobutton
1710 return self._checkedimages[TreeItemIcon_Flagged]
1711 else:
1712 if self._type == 1: # Checkbox
1713 return self._checkedimages[TreeItemIcon_NotChecked]
1714 else: # Radiobutton
1715 return self._checkedimages[TreeItemIcon_NotFlagged]
1716
1717
1718def EventFlagsToSelType(style, shiftDown=False, ctrlDown=False):
1719 """
1720 Translate the key or mouse event flag to the type of selection we
1721 are dealing with.
1722 """
1723
1724 is_multiple = (style & TR_MULTIPLE) != 0
1725 extended_select = shiftDown and is_multiple
1726 unselect_others = not (extended_select or (ctrlDown and is_multiple))
1727
1728 return is_multiple, extended_select, unselect_others
1729
1730
1731# -----------------------------------------------------------------------------
1732# CustomTreeCtrl Main Implementation.
1733# This Is The Main Class.
1734# -----------------------------------------------------------------------------
1735
94431133 1736class CustomTreeCtrl(wx.PyScrolledWindow):
c8f129d0
RD
1737
1738 def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
1739 style=0, ctstyle=TR_DEFAULT_STYLE, validator=wx.DefaultValidator,
1740 name="CustomTreeCtrl"):
1741 """
1742 Default class constructor.
1743
1744 parent: parent window. Must not be none.
1745
1746 id: window identifier. A value of -1 indicates a default value.
1747
1748 pos: window position.
1749
1750 size: window size. If the default size (-1, -1) is specified then the window is sized appropriately.
1751
1752 style: the underlying wx.ScrolledWindow style
1753
1754 ctstyle: CustomTreeCtrl window style. This can be one of:
1755 TR_NO_BUTTONS
1756 TR_HAS_BUTTONS # draw collapsed/expanded btns
1757 TR_NO_LINES # don't draw lines at all
1758 TR_LINES_AT_ROOT # connect top-level nodes
1759 TR_TWIST_BUTTONS # draw mac-like twist buttons
1760 TR_SINGLE # single selection mode
1761 TR_MULTIPLE # can select multiple items
1762 TR_EXTENDED # todo: allow extended selection
1763 TR_HAS_VARIABLE_ROW_HEIGHT # allows rows to have variable height
1764 TR_EDIT_LABELS # can edit item labels
1765 TR_ROW_LINES # put border around items
1766 TR_HIDE_ROOT # don't display root node
1767 TR_FULL_ROW_HIGHLIGHT # highlight full horizontal space
1768 TR_AUTO_CHECK_CHILD # only meaningful for checkboxes
eecfab17 1769 TR_AUTO_CHECK_PARENT # only meaningful for checkboxes
c8f129d0
RD
1770 TR_AUTO_TOGGLE_CHILD # only meaningful for checkboxes
1771
1772 validator: window validator.
1773
1774 name: window name.
1775 """
1776
1777 self._current = self._key_current = self._anchor = self._select_me = None
1778 self._hasFocus = False
1779 self._dirty = False
1780
1781 # Default line height: it will soon be changed
1782 self._lineHeight = 10
1783 # Item indent wrt parent
1784 self._indent = 15
1785 # item horizontal spacing between the start and the text
1786 self._spacing = 18
1787
1788 # Brushes for focused/unfocused items (also gradient type)
1789 self._hilightBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT))
1790 btnshadow = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)
1791 self._hilightUnfocusedBrush = wx.Brush(btnshadow)
1792 r, g, b = btnshadow.Red(), btnshadow.Green(), btnshadow.Blue()
3c69a2ec
RD
1793 backcolour = (max((r >> 1) - 20, 0),
1794 max((g >> 1) - 20, 0),
1795 max((b >> 1) - 20, 0))
c8f129d0
RD
1796 backcolour = wx.Colour(backcolour[0], backcolour[1], backcolour[2])
1797 self._hilightUnfocusedBrush2 = wx.Brush(backcolour)
1798
1799 # image list for icons
1800 self._imageListNormal = self._imageListButtons = self._imageListState = self._imageListCheck = None
1801 self._ownsImageListNormal = self._ownsImageListButtons = self._ownsImageListState = False
1802
1803 # Drag and drop initial settings
1804 self._dragCount = 0
1805 self._countDrag = 0
1806 self._isDragging = False
1807 self._dropTarget = self._oldSelection = None
1808 self._dragImage = None
1809 self._underMouse = None
1810
1811 # TextCtrl initial settings for editable items
1812 self._textCtrl = None
1813 self._renameTimer = None
1814
1815 # This one allows us to handle Freeze() and Thaw() calls
1816 self._freezeCount = 0
1817
1818 self._findPrefix = ""
1819 self._findTimer = None
1820
1821 self._dropEffectAboveItem = False
1822 self._lastOnSame = False
1823
1824 # Default normal and bold fonts for an item
1825 self._hasFont = True
1826 self._normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
1827 self._boldFont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(),
1828 self._normalFont.GetStyle(), wx.BOLD, self._normalFont.GetUnderlined(),
1829 self._normalFont.GetFaceName(), self._normalFont.GetEncoding())
1830
1831
1832 # Hyperlinks things
1833 self._hypertextfont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(),
1834 self._normalFont.GetStyle(), wx.NORMAL, True,
1835 self._normalFont.GetFaceName(), self._normalFont.GetEncoding())
1836 self._hypertextnewcolour = wx.BLUE
1837 self._hypertextvisitedcolour = wx.Colour(200, 47, 200)
1838 self._isonhyperlink = False
1839
1840 # Default CustomTreeCtrl background colour.
1841 self._backgroundColour = wx.WHITE
1842
1843 # Background image settings
1844 self._backgroundImage = None
1845 self._imageStretchStyle = _StyleTile
1846
1847 # Disabled items colour
1848 self._disabledColour = wx.Colour(180, 180, 180)
1849
1850 # Gradient selection colours
1851 self._firstcolour = color= wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)
1852 self._secondcolour = wx.WHITE
1853 self._usegradients = False
1854 self._gradientstyle = 0 # Horizontal Gradient
1855
1856 # Vista Selection Styles
1857 self._vistaselection = False
1858
1859 # Connection lines style
1860 if wx.Platform != "__WXMAC__":
1861 self._dottedPen = wx.Pen("grey", 1, wx.USER_DASH)
1862 self._dottedPen.SetDashes([1,1])
1863 self._dottedPen.SetCap(wx.CAP_BUTT)
1864 else:
1865 self._dottedPen = wx.Pen("grey", 1)
1866
1867 # Pen Used To Draw The Border Around Selected Items
1868 self._borderPen = wx.BLACK_PEN
1869 self._cursor = wx.StockCursor(wx.CURSOR_ARROW)
1870
1871 # For Appended Windows
1872 self._hasWindows = False
1873 self._itemWithWindow = []
1874
1875 if wx.Platform == "__WXMAC__":
c8f129d0
RD
1876 ctstyle &= ~TR_LINES_AT_ROOT
1877 ctstyle |= TR_NO_LINES
1878
94431133 1879 platform, major, minor = wx.GetOsVersion()
c8f129d0
RD
1880 if major < 10:
1881 ctstyle |= TR_ROW_LINES
1882
1883 self._windowStyle = ctstyle
1884
1885 # Create the default check image list
1886 self.SetImageListCheck(13, 13)
1887
1888 # A constant to use my translation of RendererNative.DrawTreeItemButton
1889 # if the wxPython version is less than 2.6.2.1.
1890 if wx.VERSION_STRING < "2.6.2.1":
1891 self._drawingfunction = DrawTreeItemButton
1892 else:
1893 self._drawingfunction = wx.RendererNative.Get().DrawTreeItemButton
1894
1895 # Create our container... at last!
94431133 1896 wx.PyScrolledWindow.__init__(self, parent, id, pos, size, style|wx.HSCROLL|wx.VSCROLL, name)
c8f129d0
RD
1897
1898 # If the tree display has no buttons, but does have
1899 # connecting lines, we can use a narrower layout.
1900 # It may not be a good idea to force this...
1901 if not self.HasButtons() and not self.HasFlag(TR_NO_LINES):
1902 self._indent= 10
1903 self._spacing = 10
1904
1905 self.SetValidator(validator)
1906
1907 attr = self.GetDefaultAttributes()
1908 self.SetOwnForegroundColour(attr.colFg)
1909 self.SetOwnBackgroundColour(wx.WHITE)
1910
1911 if not self._hasFont:
1912 self.SetOwnFont(attr.font)
1913
1914 self.SetSize(size)
1915
1916 # Bind the events
1917 self.Bind(wx.EVT_PAINT, self.OnPaint)
1918 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1919 self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
1920 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
1921 self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
1922 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
1923 self.Bind(EVT_TREE_ITEM_GETTOOLTIP, self.OnGetToolTip)
c8f129d0
RD
1924 self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
1925
1926 # Sets the focus to ourselves: this is useful if you have items
1927 # with associated widgets.
1928 self.SetFocus()
1929
c8f129d0 1930
94431133
RD
1931 def AcceptsFocus(self):
1932 # overridden base class method, allows this ctrl to
1933 # participate in the tab-order, etc. It's overridable because
1934 # of deriving this class from wx.PyScrolledWindow...
1935 return True
1936
c8f129d0
RD
1937
1938 def OnDestroy(self, event):
1939 """Handles the wx.EVT_WINDOW_DESTROY event."""
1940
1941 # Here there may be something I miss... do I have to destroy
1942 # something else?
1943 if self._renameTimer and self._renameTimer.IsRunning():
1944 self._renameTimer.Stop()
1945 del self._renameTimer
1946
1947 if self._findTimer and self._findTimer.IsRunning():
1948 self._findTimer.Stop()
1949 del self._findTimer
1950
1951 event.Skip()
1952
1953
1954 def GetCount(self):
1955 """Returns the global number of items in the tree."""
1956
1957 if not self._anchor:
1958 # the tree is empty
1959 return 0
1960
1961 count = self._anchor.GetChildrenCount()
1962
1963 if not self.HasFlag(TR_HIDE_ROOT):
1964 # take the root itself into account
1965 count = count + 1
1966
1967 return count
1968
1969
1970 def GetIndent(self):
1971 """Returns the item indentation."""
1972
1973 return self._indent
1974
1975
1976 def GetSpacing(self):
1977 """Returns the spacing between the start and the text."""
1978
1979 return self._spacing
1980
1981
1982 def GetRootItem(self):
1983 """Returns the root item."""
1984
1985 return self._anchor
1986
1987
1988 def GetSelection(self):
1989 """Returns the current selection: TR_SINGLE only."""
1990
1991 return self._current
1992
1993
1994 def ToggleItemSelection(self, item):
1995 """Toggles the item selection."""
1996
1997 if not item:
e1463b9d 1998 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
1999
2000 self.SelectItem(item, not self.IsSelected(item))
2001
2002
2003 def EnableChildren(self, item, enable=True):
2004 """Enables/disables item children. Used internally."""
2005
2006 torefresh = False
2007 if item.IsExpanded():
2008 torefresh = True
2009
2010 if item.GetType() == 2 and enable and not item.IsChecked():
2011 # We hit a radiobutton item not checked, we don't want to
2012 # enable the children
2013 return
2014
2015 child, cookie = self.GetFirstChild(item)
2016 while child:
2017 self.EnableItem(child, enable, torefresh=torefresh)
2018 # Recurse on tree
2019 if child.GetType != 2 or (child.GetType() == 2 and item.IsChecked()):
2020 self.EnableChildren(child, enable)
2021 (child, cookie) = self.GetNextChild(item, cookie)
2022
2023
2024 def EnableItem(self, item, enable=True, torefresh=True):
2025 """Enables/disables an item."""
2026
2027 if not item:
e1463b9d 2028 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2029
2030 if item.IsEnabled() == enable:
2031 return
2032
2033 if not enable and item.IsSelected():
2034 self.SelectItem(item, False)
2035
2036 item.Enable(enable)
2037 wnd = item.GetWindow()
2038
2039 # Handles the eventual window associated to the item
2040 if wnd:
2041 wndenable = item.GetWindowEnabled()
2042 if enable:
2043 if wndenable:
2044 wnd.Enable(enable)
2045 else:
2046 wnd.Enable(enable)
2047
2048 if torefresh:
2049 # We have to refresh the item line
2050 dc = wx.ClientDC(self)
2051 self.CalculateSize(item, dc)
2052 self.RefreshLine(item)
2053
2054
2055 def IsEnabled(self, item):
2056 """Returns whether an item is enabled or disabled."""
2057
2058 if not item:
e1463b9d 2059 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2060
2061 return item.IsEnabled()
2062
2063
2064 def SetDisabledColour(self, colour):
2065 """Sets the items disabled colour."""
2066
2067 self._disabledColour = colour
2068 self._dirty = True
2069
2070
2071 def GetDisabledColour(self):
2072 """Returns the items disabled colour."""
2073
2074 return self._disabledColour
2075
2076
2077 def IsItemChecked(self, item):
2078 """Returns whether an item is checked or not."""
2079
2080 if not item:
e1463b9d 2081 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2082
2083 return item.IsChecked()
2084
2085
2086 def CheckItem2(self, item, checked=True, torefresh=False):
2087 """Used internally to avoid EVT_TREE_ITEM_CHECKED events."""
2088
2089 if item.GetType() == 0:
2090 return
2091
2092 item.Check(checked)
2093
2094 if torefresh:
2095 dc = wx.ClientDC(self)
2096 self.CalculateSize(item, dc)
2097 self.RefreshLine(item)
2098
2099
2100 def UnCheckRadioParent(self, item, checked=False):
2101 """Used internally to handle radio node parent correctly."""
2102
2103 e = TreeEvent(wxEVT_TREE_ITEM_CHECKING, self.GetId())
2104 e.SetItem(item)
2105 e.SetEventObject(self)
2106
2107 if self.GetEventHandler().ProcessEvent(e):
2108 return False
2109
2110 item.Check(checked)
c8f129d0
RD
2111 self.RefreshLine(item)
2112 self.EnableChildren(item, checked)
2113 e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId())
2114 e.SetItem(item)
2115 e.SetEventObject(self)
2116 self.GetEventHandler().ProcessEvent(e)
2117
2118 return True
2119
2120
2121 def CheckItem(self, item, checked=True):
2122 """
2123 Actually checks/uncheks an item, sending (eventually) the two
2124 events EVT_TREE_ITEM_CHECKING/EVT_TREE_ITEM_CHECKED.
2125 """
2126
2127 if not item:
e1463b9d 2128 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2129
2130 # Should we raise an error here?!?
2131 if item.GetType() == 0:
2132 return
2133
2134 if item.GetType() == 2: # it's a radio button
2135 if not checked and item.IsChecked(): # Try To Unckeck?
2136 if item.HasChildren():
2137 self.UnCheckRadioParent(item, checked)
2138 return
2139 else:
2140 if not self.UnCheckRadioParent(item, checked):
2141 return
2142
2143 self.CheckSameLevel(item, False)
2144 return
2145
2146 # Radiobuttons are done, let's handle checkbuttons...
2147 e = TreeEvent(wxEVT_TREE_ITEM_CHECKING, self.GetId())
2148 e.SetItem(item)
2149 e.SetEventObject(self)
2150
2151 if self.GetEventHandler().ProcessEvent(e):
2152 # Blocked by user
2153 return
2154
2155 item.Check(checked)
2156 dc = wx.ClientDC(self)
2157 self.RefreshLine(item)
2158
2159 if self._windowStyle & TR_AUTO_CHECK_CHILD:
2160 ischeck = self.IsItemChecked(item)
2161 self.AutoCheckChild(item, ischeck)
eecfab17
RD
2162 if self._windowStyle & TR_AUTO_CHECK_PARENT:
2163 ischeck = self.IsItemChecked(item)
2164 self.AutoCheckParent(item, ischeck)
c8f129d0
RD
2165 elif self._windowStyle & TR_AUTO_TOGGLE_CHILD:
2166 self.AutoToggleChild(item)
2167
2168 e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId())
2169 e.SetItem(item)
2170 e.SetEventObject(self)
2171 self.GetEventHandler().ProcessEvent(e)
2172
2173
2174 def AutoToggleChild(self, item):
2175 """Transverses the tree and toggles the items. Meaningful only for check items."""
2176
2177 if not item:
e1463b9d 2178 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2179
2180 child, cookie = self.GetFirstChild(item)
2181
2182 torefresh = False
2183 if item.IsExpanded():
2184 torefresh = True
2185
2186 # Recurse on tree
2187 while child:
2188 if child.GetType() == 1 and child.IsEnabled():
2189 self.CheckItem2(child, not child.IsChecked(), torefresh=torefresh)
2190 self.AutoToggleChild(child)
2191 (child, cookie) = self.GetNextChild(item, cookie)
2192
2193
2194 def AutoCheckChild(self, item, checked):
2195 """Transverses the tree and checks/unchecks the items. Meaningful only for check items."""
2196
2197 if not item:
e1463b9d 2198 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2199
2200 (child, cookie) = self.GetFirstChild(item)
2201
2202 torefresh = False
2203 if item.IsExpanded():
2204 torefresh = True
2205
2206 while child:
2207 if child.GetType() == 1 and child.IsEnabled():
2208 self.CheckItem2(child, checked, torefresh=torefresh)
2209 self.AutoCheckChild(child, checked)
2210 (child, cookie) = self.GetNextChild(item, cookie)
2211
2212
eecfab17
RD
2213 def AutoCheckParent(self, item, checked):
2214 """Traverses up the tree and checks/unchecks parent items.
2215 Meaningful only for check items."""
2216
2217 if not item:
e1463b9d 2218 raise Exception("\nERROR: Invalid Tree Item. ")
eecfab17
RD
2219
2220 parent = item.GetParent()
2221 if not parent or parent.GetType() != 1:
2222 return
2223
2224 (child, cookie) = self.GetFirstChild(parent)
2225 while child:
2226 if child.GetType() == 1 and child.IsEnabled():
2227 if checked != child.IsChecked():
2228 return
2229 (child, cookie) = self.GetNextChild(parent, cookie)
2230
2231 self.CheckItem2(parent, checked, torefresh=True)
2232 self.AutoCheckParent(parent, checked)
2233
2234
c8f129d0
RD
2235 def CheckChilds(self, item, checked=True):
2236 """Programatically check/uncheck item children. Does not generate EVT_TREE_CHECK* events."""
2237
2238 if not item:
e1463b9d 2239 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2240
2241 if checked == None:
2242 self.AutoToggleChild(item)
2243 else:
2244 self.AutoCheckChild(item, checked)
2245
2246
2247 def CheckSameLevel(self, item, checked=False):
2248 """
2249 Uncheck radio items which are on the same level of the checked one.
2250 Used internally.
2251 """
2252
2253 parent = item.GetParent()
2254
2255 if not parent:
2256 return
2257
2258 torefresh = False
2259 if parent.IsExpanded():
2260 torefresh = True
2261
2262 (child, cookie) = self.GetFirstChild(parent)
2263 while child:
2264 if child.GetType() == 2 and child != item:
2265 self.CheckItem2(child, checked, torefresh=torefresh)
2266 if child.GetType != 2 or (child.GetType() == 2 and child.IsChecked()):
2267 self.EnableChildren(child, checked)
2268 (child, cookie) = self.GetNextChild(parent, cookie)
2269
2270
2271 def EditLabel(self, item):
2272 """Starts editing an item label."""
2273
2274 if not item:
e1463b9d 2275 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2276
2277 self.Edit(item)
2278
2279
2280 def ShouldInheritColours(self):
2281 """We don't inherit colours from anyone."""
2282
2283 return False
2284
2285
2286 def SetIndent(self, indent):
2287 """Sets item indentation."""
2288
2289 self._indent = indent
2290 self._dirty = True
2291
2292
2293 def SetSpacing(self, spacing):
2294 """Sets item spacing."""
2295
2296 self._spacing = spacing
2297 self._dirty = True
2298
2299
2300 def HasFlag(self, flag):
2301 """Returns whether CustomTreeCtrl has a flag."""
2302
2303 return self._windowStyle & flag
2304
2305
2306 def HasChildren(self, item):
2307 """Returns whether an item has children or not."""
2308
2309 if not item:
e1463b9d 2310 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2311
2312 return len(item.GetChildren()) > 0
2313
2314
2315 def GetChildrenCount(self, item, recursively=True):
2316 """Gets the item children count."""
2317
2318 if not item:
e1463b9d 2319 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2320
2321 return item.GetChildrenCount(recursively)
2322
2323
2324 def SetTreeStyle(self, styles):
2325 """Sets the CustomTreeCtrl style. See __init__ method for the styles explanation."""
2326
2327 # Do not try to expand the root node if it hasn't been created yet
2328 if self._anchor and not self.HasFlag(TR_HIDE_ROOT) and styles & TR_HIDE_ROOT:
2329
2330 # if we will hide the root, make sure children are visible
2331 self._anchor.SetHasPlus()
2332 self._anchor.Expand()
2333 self.CalculatePositions()
2334
2335 # right now, just sets the styles. Eventually, we may
2336 # want to update the inherited styles, but right now
2337 # none of the parents has updatable styles
2338
2339 if self._windowStyle & TR_MULTIPLE and not (styles & TR_MULTIPLE):
2340 selections = self.GetSelections()
2341 for select in selections[0:-1]:
2342 self.SelectItem(select, False)
2343
2344 self._windowStyle = styles
2345 self._dirty = True
2346
2347
2348 def GetTreeStyle(self):
2349 """Returns the CustomTreeCtrl style."""
2350
2351 return self._windowStyle
2352
2353
2354 def HasButtons(self):
2355 """Returns whether CustomTreeCtrl has the TR_AHS_BUTTONS flag."""
2356
2357 return self.HasFlag(TR_HAS_BUTTONS)
2358
2359
2360# -----------------------------------------------------------------------------
2361# functions to work with tree items
2362# -----------------------------------------------------------------------------
2363
2364 def GetItemText(self, item):
2365 """Returns the item text."""
2366
2367 if not item:
e1463b9d 2368 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2369
2370 return item.GetText()
2371
2372
2373 def GetItemImage(self, item, which):
2374 """Returns the item image."""
2375
2376 if not item:
e1463b9d 2377 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2378
2379 return item.GetImage(which)
2380
2381
2382 def GetPyData(self, item):
2383 """Returns the data associated to an item."""
2384
2385 if not item:
e1463b9d 2386 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2387
2388 return item.GetData()
2389
2390 GetItemPyData = GetPyData
2391
2392
2393 def GetItemTextColour(self, item):
2394 """Returns the item text colour."""
2395
2396 if not item:
e1463b9d 2397 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2398
2399 return item.Attr().GetTextColour()
2400
2401
2402 def GetItemBackgroundColour(self, item):
2403 """Returns the item background colour."""
2404
2405 if not item:
e1463b9d 2406 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2407
2408 return item.Attr().GetBackgroundColour()
2409
2410
2411 def GetItemFont(self, item):
2412 """Returns the item font."""
2413
2414 if not item:
e1463b9d 2415 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2416
2417 return item.Attr().GetFont()
2418
2419
2420 def IsItemHyperText(self, item):
2421 """Returns whether an item is hypertext or not."""
2422
2423 if not item:
e1463b9d 2424 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2425
2426 return item.IsHyperText()
2427
2428
2429 def SetItemText(self, item, text):
2430 """Sets the item text."""
2431
2432 if not item:
e1463b9d 2433 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2434
2435 dc = wx.ClientDC(self)
2436 item.SetText(text)
2437 self.CalculateSize(item, dc)
2438 self.RefreshLine(item)
2439
2440
2441 def SetItemImage(self, item, image, which=TreeItemIcon_Normal):
2442 """Sets the item image, depending on the item state."""
2443
2444 if not item:
e1463b9d 2445 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2446
2447 item.SetImage(image, which)
2448
2449 dc = wx.ClientDC(self)
2450 self.CalculateSize(item, dc)
2451 self.RefreshLine(item)
2452
2453
2454 def SetPyData(self, item, data):
2455 """Sets the data associated to an item."""
2456
2457 if not item:
e1463b9d 2458 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2459
2460 item.SetData(data)
2461
2462 SetItemPyData = SetPyData
2463
2464
2465 def SetItemHasChildren(self, item, has=True):
2466 """Forces the appearance of the button next to the item."""
2467
2468 if not item:
e1463b9d 2469 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2470
2471 item.SetHasPlus(has)
2472 self.RefreshLine(item)
2473
2474
2475 def SetItemBold(self, item, bold=True):
2476 """Sets the item font bold/unbold."""
2477
2478 if not item:
e1463b9d 2479 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2480
2481 # avoid redrawing the tree if no real change
2482 if item.IsBold() != bold:
2483 item.SetBold(bold)
2484 self._dirty = True
2485
2486
2487 def SetItemItalic(self, item, italic=True):
2488 """Sets the item font italic/non-italic."""
2489
2490 if not item:
e1463b9d 2491 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2492
2493 if item.IsItalic() != italic:
2494 itemFont = self.GetItemFont(item)
2495 if itemFont != wx.NullFont:
2496 style = wx.ITALIC
2497 if not italic:
2498 style = ~style
2499
2500 item.SetItalic(italic)
2501 itemFont.SetStyle(style)
2502 self.SetItemFont(item, itemFont)
2503 self._dirty = True
2504
2505
2506 def SetItemDropHighlight(self, item, highlight=True):
2507 """
2508 Gives the item the visual feedback for drag and drop operations.
2509 This is useful when something is dragged from outside the CustomTreeCtrl.
2510 """
2511
2512 if not item:
e1463b9d 2513 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2514
2515 if highlight:
2516 bg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)
2517 fg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
2518
2519 item.Attr().SetTextColour(fg)
2520 item.Attr.SetBackgroundColour(bg)
2521 self.RefreshLine(item)
2522
2523
2524 def SetItemTextColour(self, item, col):
2525 """Sets the item text colour."""
2526
2527 if not item:
e1463b9d 2528 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2529
2530 if self.GetItemTextColour(item) == col:
2531 return
2532
2533 item.Attr().SetTextColour(col)
2534 self.RefreshLine(item)
2535
2536
2537 def SetItemBackgroundColour(self, item, col):
2538 """Sets the item background colour."""
2539
2540 if not item:
e1463b9d 2541 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2542
2543 item.Attr().SetBackgroundColour(col)
2544 self.RefreshLine(item)
2545
2546
2547 def SetItemHyperText(self, item, hyper=True):
2548 """Sets whether the item is hypertext or not."""
2549
2550 if not item:
e1463b9d 2551 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2552
2553 item.SetHyperText(hyper)
2554 self.RefreshLine(item)
2555
2556
2557 def SetItemFont(self, item, font):
2558 """Sets the item font."""
2559
2560 if not item:
e1463b9d 2561 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2562
2563 if self.GetItemFont(item) == font:
2564 return
2565
2566 item.Attr().SetFont(font)
2567 self._dirty = True
2568
2569
2570 def SetFont(self, font):
2571 """Sets the CustomTreeCtrl font."""
2572
2573 wx.ScrolledWindow.SetFont(self, font)
2574
2575 self._normalFont = font
2576 self._boldFont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(),
2577 self._normalFont.GetStyle(), wx.BOLD, self._normalFont.GetUnderlined(),
2578 self._normalFont.GetFaceName(), self._normalFont.GetEncoding())
2579
2580 return True
2581
2582
2583 def GetHyperTextFont(self):
2584 """Returns the font used to render an hypertext item."""
2585
2586 return self._hypertextfont
2587
2588
2589 def SetHyperTextFont(self, font):
2590 """Sets the font used to render an hypertext item."""
2591
2592 self._hypertextfont = font
2593 self._dirty = True
2594
2595
2596 def SetHyperTextNewColour(self, colour):
2597 """Sets the colour used to render a non-visited hypertext item."""
2598
2599 self._hypertextnewcolour = colour
2600 self._dirty = True
2601
2602
2603 def GetHyperTextNewColour(self):
2604 """Returns the colour used to render a non-visited hypertext item."""
2605
2606 return self._hypertextnewcolour
2607
2608
2609 def SetHyperTextVisitedColour(self, colour):
2610 """Sets the colour used to render a visited hypertext item."""
2611
2612 self._hypertextvisitedcolour = colour
2613 self._dirty = True
2614
2615
2616 def GetHyperTextVisitedColour(self):
2617 """Returns the colour used to render a visited hypertext item."""
2618
2619 return self._hypertextvisitedcolour
2620
2621
2622 def SetItemVisited(self, item, visited=True):
2623 """Sets whether an hypertext item was visited."""
2624
2625 if not item:
e1463b9d 2626 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2627
2628 item.SetVisited(visited)
2629 self.RefreshLine(item)
2630
2631
2632 def GetItemVisited(self, item):
2633 """Returns whether an hypertext item was visited."""
2634
2635 if not item:
e1463b9d 2636 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2637
2638 return item.GetVisited()
2639
2640
2641 def SetHilightFocusColour(self, colour):
2642 """
2643 Sets the colour used to highlight focused selected items.
2644 This is applied only if gradient and Windows Vista styles are disabled.
2645 """
2646
2647 self._hilightBrush = wx.Brush(colour)
2648 self.RefreshSelected()
2649
2650
2651 def SetHilightNonFocusColour(self, colour):
2652 """
2653 Sets the colour used to highlight unfocused selected items.
2654 This is applied only if gradient and Windows Vista styles are disabled.
2655 """
2656
2657 self._hilightUnfocusedBrush = wx.Brush(colour)
2658 self.RefreshSelected()
2659
2660
2661 def GetHilightFocusColour(self):
2662 """
2663 Returns the colour used to highlight focused selected items.
2664 This is applied only if gradient and Windows Vista styles are disabled.
2665 """
2666
2667 return self._hilightBrush.GetColour()
2668
2669
2670 def GetHilightNonFocusColour(self):
2671 """
2672 Returns the colour used to highlight unfocused selected items.
2673 This is applied only if gradient and Windows Vista styles are disabled.
2674 """
2675
2676 return self._hilightUnfocusedBrush.GetColour()
2677
2678
2679 def SetFirstGradientColour(self, colour=None):
2680 """Sets the first gradient colour."""
2681
2682 if colour is None:
2683 colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)
2684
2685 self._firstcolour = colour
2686 if self._usegradients:
2687 self.RefreshSelected()
2688
2689
2690 def SetSecondGradientColour(self, colour=None):
2691 """Sets the second gradient colour."""
2692
2693 if colour is None:
2694 # No colour given, generate a slightly darker from the
2695 # CustomTreeCtrl background colour
2696 color = self.GetBackgroundColour()
2697 r, g, b = int(color.Red()), int(color.Green()), int(color.Blue())
2698 color = ((r >> 1) + 20, (g >> 1) + 20, (b >> 1) + 20)
2699 colour = wx.Colour(color[0], color[1], color[2])
2700
2701 self._secondcolour = colour
2702
2703 if self._usegradients:
2704 self.RefreshSelected()
2705
2706
2707 def GetFirstGradientColour(self):
2708 """Returns the first gradient colour."""
2709
2710 return self._firstcolour
2711
2712
2713 def GetSecondGradientColour(self):
2714 """Returns the second gradient colour."""
2715
2716 return self._secondcolour
2717
2718
2719 def EnableSelectionGradient(self, enable=True):
2720 """Globally enables/disables drawing of gradient selection."""
2721
2722 self._usegradients = enable
2723 self._vistaselection = False
2724 self.RefreshSelected()
2725
2726
2727 def SetGradientStyle(self, vertical=0):
2728 """
2729 Sets the gradient style:
2730 0: horizontal gradient
2731 1: vertical gradient
2732 """
2733
2734 # 0 = Horizontal, 1 = Vertical
2735 self._gradientstyle = vertical
2736
2737 if self._usegradients:
2738 self.RefreshSelected()
2739
2740
2741 def GetGradientStyle(self):
2742 """
2743 Returns the gradient style:
2744 0: horizontal gradient
2745 1: vertical gradient
2746 """
2747
2748 return self._gradientstyle
2749
2750
2751 def EnableSelectionVista(self, enable=True):
2752 """Globally enables/disables drawing of Windows Vista selection."""
2753
2754 self._usegradients = False
2755 self._vistaselection = enable
2756 self.RefreshSelected()
2757
2758
2759 def SetBorderPen(self, pen):
2760 """
2761 Sets the pen used to draw the selected item border.
2762 The border pen is not used if the Windows Vista style is applied.
2763 """
2764
2765 self._borderPen = pen
2766 self.RefreshSelected()
2767
2768
2769 def GetBorderPen(self):
2770 """
2771 Returns the pen used to draw the selected item border.
2772 The border pen is not used if the Windows Vista style is applied.
2773 """
2774
2775 return self._borderPen
2776
2777
2778 def SetConnectionPen(self, pen):
2779 """Sets the pen used to draw the connecting lines between items."""
2780
2781 self._dottedPen = pen
2782 self._dirty = True
2783
2784
2785 def GetConnectionPen(self):
2786 """Returns the pen used to draw the connecting lines between items."""
2787
2788 return self._dottedPen
2789
2790
2791 def SetBackgroundImage(self, image):
2792 """Sets the CustomTreeCtrl background image (can be none)."""
2793
2794 self._backgroundImage = image
2795 self.Refresh()
2796
2797
2798 def GetBackgroundImage(self):
2799 """Returns the CustomTreeCtrl background image (can be none)."""
2800
2801 return self._backgroundImage
2802
2803
2804 def GetItemWindow(self, item):
2805 """Returns the window associated to the item (if any)."""
2806
2807 if not item:
e1463b9d 2808 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2809
2810 return item.GetWindow()
2811
2812
2813 def GetItemWindowEnabled(self, item):
2814 """Returns whether the window associated to the item is enabled."""
2815
2816 if not item:
e1463b9d 2817 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2818
2819 return item.GetWindowEnabled()
2820
2821
2822 def SetItemWindowEnabled(self, item, enable=True):
2823 """Enables/disables the window associated to the item."""
2824
2825 if not item:
e1463b9d 2826 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2827
2828 item.SetWindowEnabled(enable)
2829
2830
2831 def GetItemType(self, item):
2832 """
2833 Returns the item type:
2834 0: normal
2835 1: checkbox item
2836 2: radiobutton item
2837 """
2838
2839 if not item:
e1463b9d 2840 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2841
2842 return item.GetType()
2843
2844# -----------------------------------------------------------------------------
2845# item status inquiries
2846# -----------------------------------------------------------------------------
2847
2848 def IsVisible(self, item):
2849 """Returns whether the item is visible or not."""
2850
2851 if not item:
e1463b9d 2852 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2853
2854 # An item is only visible if it's not a descendant of a collapsed item
2855 parent = item.GetParent()
2856
2857 while parent:
2858
2859 if not parent.IsExpanded():
2860 return False
2861
2862 parent = parent.GetParent()
2863
2864 startX, startY = self.GetViewStart()
2865 clientSize = self.GetClientSize()
2866
2867 rect = self.GetBoundingRect(item)
2868
2869 if not rect:
2870 return False
2871 if rect.GetWidth() == 0 or rect.GetHeight() == 0:
2872 return False
2873 if rect.GetBottom() < 0 or rect.GetTop() > clientSize.y:
2874 return False
2875 if rect.GetRight() < 0 or rect.GetLeft() > clientSize.x:
2876 return False
2877
2878 return True
2879
2880
2881 def ItemHasChildren(self, item):
2882 """Returns whether the item has children or not."""
2883
2884 if not item:
e1463b9d 2885 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2886
2887 # consider that the item does have children if it has the "+" button: it
2888 # might not have them (if it had never been expanded yet) but then it
2889 # could have them as well and it's better to err on this side rather than
2890 # disabling some operations which are restricted to the items with
2891 # children for an item which does have them
2892 return item.HasPlus()
2893
2894
2895 def IsExpanded(self, item):
2896 """Returns whether the item is expanded or not."""
2897
2898 if not item:
e1463b9d 2899 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2900
2901 return item.IsExpanded()
2902
2903
2904 def IsSelected(self, item):
2905 """Returns whether the item is selected or not."""
2906
2907 if not item:
e1463b9d 2908 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2909
2910 return item.IsSelected()
2911
2912
2913 def IsBold(self, item):
2914 """Returns whether the item font is bold or not."""
2915
2916 if not item:
e1463b9d 2917 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2918
2919 return item.IsBold()
2920
2921
2922 def IsItalic(self, item):
2923 """Returns whether the item font is italic or not."""
2924
2925 if not item:
e1463b9d 2926 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2927
2928 return item.IsItalic()
2929
2930
2931# -----------------------------------------------------------------------------
2932# navigation
2933# -----------------------------------------------------------------------------
2934
2935 def GetItemParent(self, item):
2936 """Gets the item parent."""
2937
2938 if not item:
e1463b9d 2939 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2940
2941 return item.GetParent()
2942
2943
2944 def GetFirstChild(self, item):
2945 """Gets the item first child."""
2946
2947 if not item:
e1463b9d 2948 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2949
2950 cookie = 0
2951 return self.GetNextChild(item, cookie)
2952
2953
2954 def GetNextChild(self, item, cookie):
2955 """
2956 Gets the item next child based on the 'cookie' parameter.
2957 This method has no sense if you do not call GetFirstChild() before.
2958 """
2959
2960 if not item:
e1463b9d 2961 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2962
2963 children = item.GetChildren()
2964
2965 # it's ok to cast cookie to size_t, we never have indices big enough to
2966 # overflow "void *"
2967
2968 if cookie < len(children):
2969
2970 return children[cookie], cookie+1
2971
2972 else:
2973
2974 # there are no more of them
2975 return None, cookie
2976
2977
2978 def GetLastChild(self, item):
2979 """Gets the item last child."""
2980
2981 if not item:
e1463b9d 2982 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2983
2984 children = item.GetChildren()
2985 return (len(children) == 0 and [None] or [children[-1]])[0]
2986
2987
2988 def GetNextSibling(self, item):
2989 """Gets the next sibling of an item."""
2990
2991 if not item:
e1463b9d 2992 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2993
2994 i = item
2995 parent = i.GetParent()
2996
2997 if parent == None:
2998
2999 # root item doesn't have any siblings
3000 return None
3001
3002 siblings = parent.GetChildren()
3003 index = siblings.index(i)
3004
3005 n = index + 1
3006 return (n == len(siblings) and [None] or [siblings[n]])[0]
3007
3008
3009 def GetPrevSibling(self, item):
3010 """Gets the previous sibling of an item."""
3011
3012 if not item:
e1463b9d 3013 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3014
3015 i = item
3016 parent = i.GetParent()
3017
3018 if parent == None:
3019
3020 # root item doesn't have any siblings
3021 return None
3022
3023 siblings = parent.GetChildren()
3024 index = siblings.index(i)
3025
3026 return (index == 0 and [None] or [siblings[index-1]])[0]
3027
3028
3029 def GetNext(self, item):
3030 """Gets the next item. Only for internal use right now."""
3031
3032 if not item:
e1463b9d 3033 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3034
3035 i = item
3036
3037 # First see if there are any children.
3038 children = i.GetChildren()
3039 if len(children) > 0:
3040 return children[0]
3041 else:
3042 # Try a sibling of this or ancestor instead
3043 p = item
3044 toFind = None
3045 while p and not toFind:
3046 toFind = self.GetNextSibling(p)
3047 p = self.GetItemParent(p)
3048
3049 return toFind
3050
3051
3052 def GetFirstVisibleItem(self):
3053 """Returns the first visible item."""
3054
3055 id = self.GetRootItem()
3056 if not id:
3057 return id
3058
3059 while id:
3060 if self.IsVisible(id):
3061 return id
3062 id = self.GetNext(id)
3063
3064 return None
3065
3066
3067 def GetNextVisible(self, item):
3068 """Returns the next visible item."""
3069
3070 if not item:
e1463b9d 3071 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3072
3073 id = item
3074
3075 while id:
3076 id = self.GetNext(id)
3077 if id and self.IsVisible(id):
3078 return id
3079
3080 return None
3081
3082
3083 def GetPrevVisible(self, item):
3084
3085 if not item:
e1463b9d 3086 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0 3087
e1463b9d 3088 raise Exception("\nERROR: Not Implemented")
c8f129d0
RD
3089
3090 return None
3091
3092
3093 def ResetTextControl(self):
3094 """Called by TreeTextCtrl when it marks itself for deletion."""
3095
3096 self._textCtrl.Destroy()
3097 self._textCtrl = None
3098
3099
3100 def FindItem(self, idParent, prefixOrig):
3101 """Finds the first item starting with the given prefix after the given item."""
3102
3103 # match is case insensitive as this is more convenient to the user: having
3104 # to press Shift-letter to go to the item starting with a capital letter
3105 # would be too bothersome
3106 prefix = prefixOrig.lower()
3107
3108 # determine the starting point: we shouldn't take the current item (this
3109 # allows to switch between two items starting with the same letter just by
3110 # pressing it) but we shouldn't jump to the next one if the user is
3111 # continuing to type as otherwise he might easily skip the item he wanted
3112 id = idParent
3113
3114 if len(prefix) == 1:
3115 id = self.GetNext(id)
3116
3117 # look for the item starting with the given prefix after it
3118 while id and not self.GetItemText(id).lower().startswith(prefix):
3119
3120 id = self.GetNext(id)
3121
3122 # if we haven't found anything...
3123 if not id:
3124
3125 # ... wrap to the beginning
3126 id = self.GetRootItem()
3127 if self.HasFlag(TR_HIDE_ROOT):
3128 # can't select virtual root
3129 id = self.GetNext(id)
3130
3131 # and try all the items (stop when we get to the one we started from)
3132 while id != idParent and not self.GetItemText(id).lower().startswith(prefix):
3133 id = self.GetNext(id)
3134
3135 return id
3136
3137
3138# -----------------------------------------------------------------------------
3139# operations
3140# -----------------------------------------------------------------------------
3141
3142 def DoInsertItem(self, parentId, previous, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3143 """Actually inserts an item in the tree."""
3144
3145 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3146 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3147
3148 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3149 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3150
3151 if ct_type < 0 or ct_type > 2:
e1463b9d 3152 raise Exception("\nERROR: Item Type Should Be 0 (Normal), 1 (CheckBox) or 2 (RadioButton). ")
c8f129d0
RD
3153
3154 parent = parentId
3155
3156 if not parent:
3157
3158 # should we give a warning here?
3159 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3160
3161 self._dirty = True # do this first so stuff below doesn't cause flicker
3162
3163 item = GenericTreeItem(parent, text, ct_type, wnd, image, selImage, data)
3164
3165 if wnd is not None:
3166 self._hasWindows = True
3167 self._itemWithWindow.append(item)
3168
3169 parent.Insert(item, previous)
3170
3171 return item
3172
3173
3174 def AddRoot(self, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3175 """Adds a root to the CustomTreeCtrl. Only one root must exist."""
3176
3177 if self._anchor:
e1463b9d 3178 raise Exception("\nERROR: Tree Can Have Only One Root")
c8f129d0
RD
3179
3180 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3181 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3182
3183 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3184 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3185
3186 if ct_type < 0 or ct_type > 2:
e1463b9d 3187 raise Exception("\nERROR: Item Type Should Be 0 (Normal), 1 (CheckBox) or 2 (RadioButton). ")
c8f129d0
RD
3188
3189 self._dirty = True # do this first so stuff below doesn't cause flicker
3190
3191 self._anchor = GenericTreeItem(None, text, ct_type, wnd, image, selImage, data)
3192
3193 if wnd is not None:
3194 self._hasWindows = True
3195 self._itemWithWindow.append(self._anchor)
3196
3197 if self.HasFlag(TR_HIDE_ROOT):
3198
3199 # if root is hidden, make sure we can navigate
3200 # into children
3201 self._anchor.SetHasPlus()
3202 self._anchor.Expand()
3203 self.CalculatePositions()
3204
3205 if not self.HasFlag(TR_MULTIPLE):
3206
3207 self._current = self._key_current = self._anchor
3208 self._current.SetHilight(True)
3209
3210 return self._anchor
3211
3212
3213 def PrependItem(self, parent, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3214 """Appends an item as a first child of parent."""
3215
3216 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3217 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3218
3219 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3220 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3221
3222 return self.DoInsertItem(parent, 0, text, ct_type, wnd, image, selImage, data)
3223
3224
3225 def InsertItemByItem(self, parentId, idPrevious, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3226 """Auxiliary function to cope with the C++ hideous multifunction."""
3227
3228 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3229 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3230
3231 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3232 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3233
3234 parent = parentId
3235
3236 if not parent:
3237 # should we give a warning here?
3238 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3239
3240 index = -1
3241 if idPrevious:
3242
3243 try:
3244 index = parent.GetChildren().index(idPrevious)
3245 except:
e1463b9d 3246 raise Exception("ERROR: Previous Item In CustomTreeCtrl.InsertItem() Is Not A Sibling")
c8f129d0
RD
3247
3248 return self.DoInsertItem(parentId, index+1, text, ct_type, wnd, image, selImage, data)
3249
3250
3251 def InsertItemByIndex(self, parentId, before, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3252 """Auxiliary function to cope with the C++ hideous multifunction."""
3253
3254 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3255 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3256
3257 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3258 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3259
3260 parent = parentId
3261
3262 if not parent:
3263 # should we give a warning here?
3264 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3265
3266 return self.DoInsertItem(parentId, before, text, ct_type, wnd, image, selImage, data)
3267
3268
3269 def InsertItem(self, parentId, input, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3270 """Inserts an item after the given previous."""
3271
3272 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3273 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3274
3275 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3276 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3277
3278 if type(input) == type(1):
3279 return self.InsertItemByIndex(parentId, input, text, ct_type, wnd, image, selImage, data)
3280 else:
3281 return self.InsertItemByItem(parentId, input, text, ct_type, wnd, image, selImage, data)
3282
3283
3284 def AppendItem(self, parentId, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3285 """Appends an item as a last child of its parent."""
3286
3287 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3288 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3289
3290 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3291 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3292
3293 parent = parentId
3294
3295 if not parent:
3296 # should we give a warning here?
3297 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3298
3299 return self.DoInsertItem(parent, len(parent.GetChildren()), text, ct_type, wnd, image, selImage, data)
3300
3301
3302 def SendDeleteEvent(self, item):
3303 """Actully sends the EVT_TREE_DELETE_ITEM event."""
3304
3305 event = TreeEvent(wxEVT_TREE_DELETE_ITEM, self.GetId())
3306 event._item = item
3307 event.SetEventObject(self)
3308 self.ProcessEvent(event)
3309
3310
3311 def IsDescendantOf(self, parent, item):
3312 """Checks if the given item is under another one."""
3313
3314 while item:
3315
3316 if item == parent:
3317
3318 # item is a descendant of parent
3319 return True
3320
3321 item = item.GetParent()
3322
3323 return False
3324
3325
3326 # Don't leave edit or selection on a child which is about to disappear
3327 def ChildrenClosing(self, item):
3328 """We are about to destroy the item children."""
3329
3330 if self._textCtrl != None and item != self._textCtrl.item() and self.IsDescendantOf(item, self._textCtrl.item()):
3331 self._textCtrl.StopEditing()
3332
3333 if item != self._key_current and self.IsDescendantOf(item, self._key_current):
3334 self._key_current = None
3335
3336 if self.IsDescendantOf(item, self._select_me):
3337 self._select_me = item
3338
3339 if item != self._current and self.IsDescendantOf(item, self._current):
3340 self._current.SetHilight(False)
3341 self._current = None
3342 self._select_me = item
3343
3344
3345 def DeleteChildren(self, item):
3346 """Delete item children."""
3347
3348 if not item:
e1463b9d 3349 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3350
3351 self._dirty = True # do this first so stuff below doesn't cause flicker
3352
3353 self.ChildrenClosing(item)
3354 item.DeleteChildren(self)
3355
3356
3357 def Delete(self, item):
3358 """Delete an item."""
3359
3360 if not item:
e1463b9d 3361 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3362
3363 self._dirty = True # do this first so stuff below doesn't cause flicker
3364
3365 if self._textCtrl != None and self.IsDescendantOf(item, self._textCtrl.item()):
3366 # can't delete the item being edited, cancel editing it first
3367 self._textCtrl.StopEditing()
3368
3369 parent = item.GetParent()
3370
3371 # don't keep stale pointers around!
3372 if self.IsDescendantOf(item, self._key_current):
3373
3374 # Don't silently change the selection:
3375 # do it properly in idle time, so event
3376 # handlers get called.
3377
3378 # self._key_current = parent
3379 self._key_current = None
3380
3381 # self._select_me records whether we need to select
3382 # a different item, in idle time.
3383 if self._select_me and self.IsDescendantOf(item, self._select_me):
3384 self._select_me = parent
3385
3386 if self.IsDescendantOf(item, self._current):
3387
3388 # Don't silently change the selection:
3389 # do it properly in idle time, so event
3390 # handlers get called.
3391
3392 # self._current = parent
3393 self._current = None
3394 self._select_me = parent
3395
3396 # remove the item from the tree
3397 if parent:
3398
3399 parent.GetChildren().remove(item) # remove by value
3400
3401 else: # deleting the root
3402
3403 # nothing will be left in the tree
3404 self._anchor = None
3405
3406 # and delete all of its children and the item itself now
3407 item.DeleteChildren(self)
3408 self.SendDeleteEvent(item)
3409
3410 if item == self._select_me:
3411 self._select_me = None
3412
3413 # Remove the item with window
3414 if item in self._itemWithWindow:
3415 wnd = item.GetWindow()
3416 wnd.Hide()
3417 wnd.Destroy()
3418 item._wnd = None
3419 self._itemWithWindow.remove(item)
3420
3421 del item
3422
3423
3424 def DeleteAllItems(self):
3425 """Delete all items in the CustomTreeCtrl."""
3426
3427 if self._anchor:
3428 self.Delete(self._anchor)
3429
3430
3431 def Expand(self, item):
3432 """
3433 Expands an item, sending a EVT_TREE_ITEM_EXPANDING and
3434 EVT_TREE_ITEM_EXPANDED events.
3435 """
3436
3437 if not item:
e1463b9d 3438 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3439
3440 if self.HasFlag(TR_HIDE_ROOT) and item == self.GetRootItem():
e1463b9d 3441 raise Exception("\nERROR: Can't Expand An Hidden Root. ")
c8f129d0
RD
3442
3443 if not item.HasPlus():
3444 return
3445
3446 if item.IsExpanded():
3447 return
3448
3449 event = TreeEvent(wxEVT_TREE_ITEM_EXPANDING, self.GetId())
3450 event._item = item
3451 event.SetEventObject(self)
3452
3453 if self.ProcessEvent(event) and not event.IsAllowed():
3454 # cancelled by program
3455 return
3456
3457 item.Expand()
3458 self.CalculatePositions()
3459
3460 self.RefreshSubtree(item)
3461
3462 if self._hasWindows:
3463 # We hide the associated window here, we may show it after
3464 self.HideWindows()
3465
3466 event.SetEventType(wxEVT_TREE_ITEM_EXPANDED)
3467 self.ProcessEvent(event)
3468
3469
3470 def ExpandAll(self, item):
3471 """Expands all the items."""
3472
3473 if not item:
e1463b9d 3474 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0 3475
2356118e 3476 if not self.HasFlag(TR_HIDE_ROOT) or item != self.GetRootItem():
c8f129d0
RD
3477 self.Expand(item)
3478 if not self.IsExpanded(item):
3479 return
3480
3481 child, cookie = self.GetFirstChild(item)
3482
3483 while child:
3484 self.ExpandAll(child)
3485 child, cookie = self.GetNextChild(item, cookie)
3486
3487
3488 def Collapse(self, item):
3489 """
3490 Collapse an item, sending a EVT_TREE_ITEM_COLLAPSING and
3491 EVT_TREE_ITEM_COLLAPSED events.
3492 """
3493
3494 if not item:
e1463b9d 3495 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3496
3497 if self.HasFlag(TR_HIDE_ROOT) and item == self.GetRootItem():
e1463b9d 3498 raise Exception("\nERROR: Can't Collapse An Hidden Root. ")
c8f129d0
RD
3499
3500 if not item.IsExpanded():
3501 return
3502
3503 event = TreeEvent(wxEVT_TREE_ITEM_COLLAPSING, self.GetId())
3504 event._item = item
3505 event.SetEventObject(self)
3506 if self.ProcessEvent(event) and not event.IsAllowed():
3507 # cancelled by program
3508 return
3509
3510 self.ChildrenClosing(item)
3511 item.Collapse()
3512
3513 self.CalculatePositions()
3514 self.RefreshSubtree(item)
3515
3516 if self._hasWindows:
3517 self.HideWindows()
3518
3519 event.SetEventType(wxEVT_TREE_ITEM_COLLAPSED)
3520 self.ProcessEvent(event)
3521
3522
3523 def CollapseAndReset(self, item):
3524 """Collapse the given item and deletes its children."""
3525
3526 self.Collapse(item)
3527 self.DeleteChildren(item)
3528
3529
3530 def Toggle(self, item):
3531 """Toggles the item state (collapsed/expanded)."""
3532
3533 if item.IsExpanded():
3534 self.Collapse(item)
3535 else:
3536 self.Expand(item)
3537
3538
3539 def HideWindows(self):
3540 """Hides the windows associated to the items. Used internally."""
3541
3542 for child in self._itemWithWindow:
3543 if not self.IsVisible(child):
3544 wnd = child.GetWindow()
3545 wnd.Hide()
3546
3547
3548 def Unselect(self):
3549 """Unselects the current selection."""
3550
3551 if self._current:
3552
3553 self._current.SetHilight(False)
3554 self.RefreshLine(self._current)
3555
3556 self._current = None
3557 self._select_me = None
3558
3559
3560 def UnselectAllChildren(self, item):
3561 """Unselects all the children of the given item."""
3562
3563 if item.IsSelected():
3564
3565 item.SetHilight(False)
3566 self.RefreshLine(item)
3567
3568 if item.HasChildren():
3569 for child in item.GetChildren():
3570 self.UnselectAllChildren(child)
3571
3572
3573 def UnselectAll(self):
3574 """Unselect all the items."""
3575
3576 rootItem = self.GetRootItem()
3577
3578 # the tree might not have the root item at all
3579 if rootItem:
3580 self.UnselectAllChildren(rootItem)
3581
3582
3583 # Recursive function !
3584 # To stop we must have crt_item<last_item
3585 # Algorithm :
3586 # Tag all next children, when no more children,
3587 # Move to parent (not to tag)
3588 # Keep going... if we found last_item, we stop.
3589
3590 def TagNextChildren(self, crt_item, last_item, select):
3591 """Used internally."""
3592
3593 parent = crt_item.GetParent()
3594
3595 if parent == None: # This is root item
3596 return self.TagAllChildrenUntilLast(crt_item, last_item, select)
3597
3598 children = parent.GetChildren()
3599 index = children.index(crt_item)
3600
3601 count = len(children)
3602
3603 for n in xrange(index+1, count):
3604 if self.TagAllChildrenUntilLast(children[n], last_item, select):
3605 return True
3606
3607 return self.TagNextChildren(parent, last_item, select)
3608
3609
3610 def TagAllChildrenUntilLast(self, crt_item, last_item, select):
3611 """Used internally."""
3612
3613 crt_item.SetHilight(select)
3614 self.RefreshLine(crt_item)
3615
3616 if crt_item == last_item:
3617 return True
3618
3619 if crt_item.HasChildren():
3620 for child in crt_item.GetChildren():
3621 if self.TagAllChildrenUntilLast(child, last_item, select):
3622 return True
3623
3624 return False
3625
3626
3627 def SelectItemRange(self, item1, item2):
3628 """Selects all the items between item1 and item2."""
3629
3630 self._select_me = None
3631
3632 # item2 is not necessary after item1
3633 # choice first' and 'last' between item1 and item2
3634 first = (item1.GetY() < item2.GetY() and [item1] or [item2])[0]
3635 last = (item1.GetY() < item2.GetY() and [item2] or [item1])[0]
3636
3637 select = self._current.IsSelected()
3638
3639 if self.TagAllChildrenUntilLast(first, last, select):
3640 return
3641
3642 self.TagNextChildren(first, last, select)
3643
3644
3645 def DoSelectItem(self, item, unselect_others=True, extended_select=False):
3646 """Actually selects/unselects an item, sending a EVT_TREE_SEL_CHANGED event."""
3647
3648 if not item:
e1463b9d 3649 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3650
3651 self._select_me = None
3652
3653 is_single = not (self.GetTreeStyle() & TR_MULTIPLE)
3654
3655 # to keep going anyhow !!!
3656 if is_single:
3657 if item.IsSelected():
3658 return # nothing to do
3659 unselect_others = True
3660 extended_select = False
3661
3662 elif unselect_others and item.IsSelected():
3663
3664 # selection change if there is more than one item currently selected
3665 if len(self.GetSelections()) == 1:
3666 return
3667
3668 event = TreeEvent(wxEVT_TREE_SEL_CHANGING, self.GetId())
3669 event._item = item
3670 event._itemOld = self._current
3671 event.SetEventObject(self)
3672 # TODO : Here we don't send any selection mode yet !
3673
3674 if self.GetEventHandler().ProcessEvent(event) and not event.IsAllowed():
3675 return
3676
3677 parent = self.GetItemParent(item)
3678 while parent:
3679 if not self.IsExpanded(parent):
3680 self.Expand(parent)
3681
3682 parent = self.GetItemParent(parent)
3683
3684 # ctrl press
3685 if unselect_others:
3686 if is_single:
3687 self.Unselect() # to speed up thing
3688 else:
3689 self.UnselectAll()
3690
3691 # shift press
3692 if extended_select:
3693 if not self._current:
3694 self._current = self._key_current = self.GetRootItem()
3695
3696 # don't change the mark (self._current)
3697 self.SelectItemRange(self._current, item)
3698
3699 else:
3700
3701 select = True # the default
3702
3703 # Check if we need to toggle hilight (ctrl mode)
3704 if not unselect_others:
3705 select = not item.IsSelected()
3706
3707 self._current = self._key_current = item
3708 self._current.SetHilight(select)
3709 self.RefreshLine(self._current)
3710
3711 # This can cause idle processing to select the root
3712 # if no item is selected, so it must be after the
3713 # selection is set
3714 self.EnsureVisible(item)
3715
3716 event.SetEventType(wxEVT_TREE_SEL_CHANGED)
3717 self.GetEventHandler().ProcessEvent(event)
3718
3719 # Handles hypertext items
3720 if self.IsItemHyperText(item):
3721 event = TreeEvent(wxEVT_TREE_ITEM_HYPERLINK, self.GetId())
3722 event._item = item
3723 self.GetEventHandler().ProcessEvent(event)
3724
3725
3726 def SelectItem(self, item, select=True):
3727 """Selects/deselects an item."""
3728
3729 if not item:
e1463b9d 3730 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3731
3732 if select:
3733
3734 self.DoSelectItem(item, not self.HasFlag(TR_MULTIPLE))
3735
3736 else: # deselect
3737
3738 item.SetHilight(False)
3739 self.RefreshLine(item)
3740
3741
3742 def FillArray(self, item, array=[]):
3743 """
3744 Internal function. Used to populate an array of selected items when
3745 the style TR_MULTIPLE is used.
3746 """
3747
3748 if not array:
3749 array = []
3750
3751 if item.IsSelected():
3752 array.append(item)
3753
e1463b9d 3754 if item.HasChildren() and item.IsExpanded():
c8f129d0
RD
3755 for child in item.GetChildren():
3756 array = self.FillArray(child, array)
3757
3758 return array
3759
3760
3761 def GetSelections(self):
3762 """
3763 Returns a list of selected items. This can be used only if CustomTreeCtrl has
3764 the TR_MULTIPLE style set.
3765 """
3766
3767 array = []
3768 idRoot = self.GetRootItem()
3769 if idRoot:
3770 array = self.FillArray(idRoot, array)
3771
3772 #else: the tree is empty, so no selections
3773
3774 return array
3775
3776
3777 def EnsureVisible(self, item):
3778 """Ensure that an item is visible in CustomTreeCtrl."""
3779
3780 if not item:
e1463b9d 3781 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3782
3783 # first expand all parent branches
3784 parent = item.GetParent()
3785
3786 if self.HasFlag(TR_HIDE_ROOT):
3787 while parent and parent != self._anchor:
3788 self.Expand(parent)
3789 parent = parent.GetParent()
3790 else:
3791 while parent:
3792 self.Expand(parent)
3793 parent = parent.GetParent()
3794
3795 self.ScrollTo(item)
3796
3797
3798 def ScrollTo(self, item):
3799 """Scrolls the specified item into view."""
3800
3801 if not item:
3802 return
3803
3804 # We have to call this here because the label in
3805 # question might just have been added and no screen
3806 # update taken place.
3807 if self._dirty:
3808 if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
3809 self.Update()
3810 else:
3811 wx.YieldIfNeeded()
3812
3813 # now scroll to the item
3814 item_y = item.GetY()
3815 start_x, start_y = self.GetViewStart()
3816 start_y *= _PIXELS_PER_UNIT
3817
3818 client_w, client_h = self.GetClientSize()
3819
3820 x, y = 0, 0
3821
3822 if item_y < start_y+3:
3823
3824 # going down
3825 x, y = self._anchor.GetSize(x, y, self)
3826 y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3827 x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3828 x_pos = self.GetScrollPos(wx.HORIZONTAL)
3829 # Item should appear at top
3830 self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, item_y/_PIXELS_PER_UNIT)
3831
3832 elif item_y+self.GetLineHeight(item) > start_y+client_h:
3833
3834 # going up
3835 x, y = self._anchor.GetSize(x, y, self)
3836 y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3837 x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3838 item_y += _PIXELS_PER_UNIT+2
3839 x_pos = self.GetScrollPos(wx.HORIZONTAL)
3840 # Item should appear at bottom
3841 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 )
3842
3843
3844 def OnCompareItems(self, item1, item2):
3845 """
3846 Returns whether 2 items have the same text.
3847 Override this function in the derived class to change the sort order of the items
3848 in the CustomTreeCtrl. The function should return a negative, zero or positive
3849 value if the first item is less than, equal to or greater than the second one.
3850
3851 The base class version compares items alphabetically.
3852 """
3853
3854 return self.GetItemText(item1) == self.GetItemText(item2)
3855
3856
3857 def SortChildren(self, item):
3858 """
3859 Sorts the children of the given item using OnCompareItems method of CustomTreeCtrl.
3860 You should override that method to change the sort order (the default is ascending
3861 case-sensitive alphabetical order).
3862 """
3863
3864 if not item:
e1463b9d 3865 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3866
3867 children = item.GetChildren()
3868
3869 if len(children) > 1:
3870 self._dirty = True
3871 children.sort(self.OnCompareItems)
3872
3873
3874 def GetImageList(self):
3875 """Returns the normal image list."""
3876
3877 return self._imageListNormal
3878
3879
3880 def GetButtonsImageList(self):
3881 """Returns the buttons image list (from which application-defined button images are taken)."""
3882
3883 return self._imageListButtons
3884
3885
3886 def GetStateImageList(self):
3887 """Returns the state image list (from which application-defined state images are taken)."""
3888
3889 return self._imageListState
3890
3891
3892 def GetImageListCheck(self):
3893 """Returns the image list used to build the check/radio buttons."""
3894
3895 return self._imageListCheck
3896
3897
3898 def CalculateLineHeight(self):
3899 """Calculates the height of a line."""
3900
3901 dc = wx.ClientDC(self)
3902 self._lineHeight = dc.GetCharHeight()
3903
3904 if self._imageListNormal:
3905
3906 # Calculate a self._lineHeight value from the normal Image sizes.
3907 # May be toggle off. Then CustomTreeCtrl will spread when
3908 # necessary (which might look ugly).
3909 n = self._imageListNormal.GetImageCount()
3910
3911 for i in xrange(n):
3912
3913 width, height = self._imageListNormal.GetSize(i)
3914
3915 if height > self._lineHeight:
3916 self._lineHeight = height
3917
3918 if self._imageListButtons:
3919
3920 # Calculate a self._lineHeight value from the Button image sizes.
3921 # May be toggle off. Then CustomTreeCtrl will spread when
3922 # necessary (which might look ugly).
3923 n = self._imageListButtons.GetImageCount()
3924
3925 for i in xrange(n):
3926
3927 width, height = self._imageListButtons.GetSize(i)
3928
3929 if height > self._lineHeight:
3930 self._lineHeight = height
3931
3932 if self._imageListCheck:
3933
3934 # Calculate a self._lineHeight value from the check/radio image sizes.
3935 # May be toggle off. Then CustomTreeCtrl will spread when
3936 # necessary (which might look ugly).
3937 n = self._imageListCheck.GetImageCount()
3938
3939 for i in xrange(n):
3940
3941 width, height = self._imageListCheck.GetSize(i)
3942
3943 if height > self._lineHeight:
3944 self._lineHeight = height
3945
3946 if self._lineHeight < 30:
3947 self._lineHeight += 2 # at least 2 pixels
3948 else:
3949 self._lineHeight += self._lineHeight/10 # otherwise 10% extra spacing
3950
3951
3952 def SetImageList(self, imageList):
3953 """Sets the normal image list."""
3954
3955 if self._ownsImageListNormal:
3956 del self._imageListNormal
3957
3958 self._imageListNormal = imageList
3959 self._ownsImageListNormal = False
3960 self._dirty = True
3961 # Don't do any drawing if we're setting the list to NULL,
3962 # since we may be in the process of deleting the tree control.
3963 if imageList:
3964 self.CalculateLineHeight()
3965
3966 # We gray out the image list to use the grayed icons with disabled items
3967 self._grayedImageList = wx.ImageList(16, 16, True, 0)
3968
3969 for ii in xrange(imageList.GetImageCount()):
3970
3971 bmp = imageList.GetBitmap(ii)
3972 image = wx.ImageFromBitmap(bmp)
3973 image = GrayOut(image)
3974 newbmp = wx.BitmapFromImage(image)
3975 self._grayedImageList.Add(newbmp)
3976
3977
3978 def SetStateImageList(self, imageList):
3979 """Sets the state image list (from which application-defined state images are taken)."""
3980
3981 if self._ownsImageListState:
3982 del self._imageListState
3983
3984 self._imageListState = imageList
3985 self._ownsImageListState = False
3986
3987
3988 def SetButtonsImageList(self, imageList):
3989 """Sets the buttons image list (from which application-defined button images are taken)."""
3990
3991 if self._ownsImageListButtons:
3992 del self._imageListButtons
3993
3994 self._imageListButtons = imageList
3995 self._ownsImageListButtons = False
3996 self._dirty = True
3997 self.CalculateLineHeight()
3998
3999
4000 def SetImageListCheck(self, sizex, sizey, imglist=None):
4001 """Sets the check image list."""
4002
4003 if imglist is None:
4004
4005 self._imageListCheck = wx.ImageList(sizex, sizey)
4006 self._imageListCheck.Add(GetCheckedBitmap())
4007 self._imageListCheck.Add(GetNotCheckedBitmap())
4008 self._imageListCheck.Add(GetFlaggedBitmap())
4009 self._imageListCheck.Add(GetNotFlaggedBitmap())
4010
4011 else:
4012
4013 sizex, sizey = imglist.GetSize(0)
4014 self._imageListCheck = imglist
4015
4016 # We gray out the image list to use the grayed icons with disabled items
4017 self._grayedCheckList = wx.ImageList(sizex, sizey, True, 0)
4018
4019 for ii in xrange(self._imageListCheck.GetImageCount()):
4020
4021 bmp = self._imageListCheck.GetBitmap(ii)
4022 image = wx.ImageFromBitmap(bmp)
4023 image = GrayOut(image)
4024 newbmp = wx.BitmapFromImage(image)
4025 self._grayedCheckList.Add(newbmp)
4026
4027 self._dirty = True
4028
4029 if imglist:
4030 self.CalculateLineHeight()
4031
4032
4033 def AssignImageList(self, imageList):
4034 """Assigns the normal image list."""
4035
4036 self.SetImageList(imageList)
4037 self._ownsImageListNormal = True
4038
4039
4040 def AssignStateImageList(self, imageList):
4041 """Assigns the state image list."""
4042
4043 self.SetStateImageList(imageList)
4044 self._ownsImageListState = True
4045
4046
4047 def AssignButtonsImageList(self, imageList):
4048 """Assigns the button image list."""
4049
4050 self.SetButtonsImageList(imageList)
4051 self._ownsImageListButtons = True
4052
4053
4054# -----------------------------------------------------------------------------
4055# helpers
4056# -----------------------------------------------------------------------------
4057
4058 def AdjustMyScrollbars(self):
4059 """Adjust the wx.ScrolledWindow scrollbars."""
4060
4061 if self._anchor:
4062
4063 x, y = self._anchor.GetSize(0, 0, self)
4064 y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
4065 x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
4066 x_pos = self.GetScrollPos(wx.HORIZONTAL)
4067 y_pos = self.GetScrollPos(wx.VERTICAL)
4068 self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, y_pos)
4069
4070 else:
4071
4072 self.SetScrollbars(0, 0, 0, 0)
4073
4074
4075 def GetLineHeight(self, item):
4076 """Returns the line height for the given item."""
4077
4078 if self.GetTreeStyle() & TR_HAS_VARIABLE_ROW_HEIGHT:
4079 return item.GetHeight()
4080 else:
4081 return self._lineHeight
4082
4083
4084 def DrawVerticalGradient(self, dc, rect, hasfocus):
4085 """Gradient fill from colour 1 to colour 2 from top to bottom."""
4086
94431133
RD
4087 oldpen = dc.GetPen()
4088 oldbrush = dc.GetBrush()
c8f129d0
RD
4089 dc.SetPen(wx.TRANSPARENT_PEN)
4090
4091 # calculate gradient coefficients
4092 if hasfocus:
4093 col2 = self._secondcolour
4094 col1 = self._firstcolour
4095 else:
4096 col2 = self._hilightUnfocusedBrush.GetColour()
4097 col1 = self._hilightUnfocusedBrush2.GetColour()
4098
4099 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
4100 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())
4101
4102 flrect = float(rect.height)
4103
4104 rstep = float((r2 - r1)) / flrect
4105 gstep = float((g2 - g1)) / flrect
4106 bstep = float((b2 - b1)) / flrect
4107
4108 rf, gf, bf = 0, 0, 0
4109
94431133 4110 for y in xrange(rect.y, rect.y + rect.height):
c8f129d0
RD
4111 currCol = (r1 + rf, g1 + gf, b1 + bf)
4112 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
94431133 4113 dc.DrawRectangle(rect.x, y, rect.width, 1)
c8f129d0
RD
4114 rf = rf + rstep
4115 gf = gf + gstep
4116 bf = bf + bstep
4117
94431133
RD
4118 dc.SetPen(oldpen)
4119 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4120 dc.DrawRectangleRect(rect)
4121 dc.SetBrush(oldbrush)
4122
c8f129d0
RD
4123
4124 def DrawHorizontalGradient(self, dc, rect, hasfocus):
4125 """Gradient fill from colour 1 to colour 2 from left to right."""
4126
94431133
RD
4127 oldpen = dc.GetPen()
4128 oldbrush = dc.GetBrush()
c8f129d0
RD
4129 dc.SetPen(wx.TRANSPARENT_PEN)
4130
4131 # calculate gradient coefficients
4132
4133 if hasfocus:
4134 col2 = self._secondcolour
4135 col1 = self._firstcolour
4136 else:
4137 col2 = self._hilightUnfocusedBrush.GetColour()
4138 col1 = self._hilightUnfocusedBrush2.GetColour()
4139
4140 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
4141 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())
4142
4143 flrect = float(rect.width)
4144
4145 rstep = float((r2 - r1)) / flrect
4146 gstep = float((g2 - g1)) / flrect
4147 bstep = float((b2 - b1)) / flrect
4148
4149 rf, gf, bf = 0, 0, 0
94431133
RD
4150
4151 for x in xrange(rect.x, rect.x + rect.width):
c8f129d0
RD
4152 currCol = (int(r1 + rf), int(g1 + gf), int(b1 + bf))
4153 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
94431133 4154 dc.DrawRectangle(x, rect.y, 1, rect.height)
c8f129d0
RD
4155 rf = rf + rstep
4156 gf = gf + gstep
4157 bf = bf + bstep
94431133
RD
4158
4159 dc.SetPen(oldpen)
4160 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4161 dc.DrawRectangleRect(rect)
4162 dc.SetBrush(oldbrush)
4163
c8f129d0
RD
4164
4165 def DrawVistaRectangle(self, dc, rect, hasfocus):
4166 """Draw the selected item(s) with the Windows Vista style."""
4167
4168 if hasfocus:
4169
4170 outer = _rgbSelectOuter
4171 inner = _rgbSelectInner
4172 top = _rgbSelectTop
4173 bottom = _rgbSelectBottom
4174
4175 else:
4176
4177 outer = _rgbNoFocusOuter
4178 inner = _rgbNoFocusInner
4179 top = _rgbNoFocusTop
4180 bottom = _rgbNoFocusBottom
4181
4182 oldpen = dc.GetPen()
4183 oldbrush = dc.GetBrush()
4184
94431133
RD
4185 bdrRect = wx.Rect(*rect.Get())
4186 filRect = wx.Rect(*rect.Get())
4187 filRect.Deflate(1,1)
4188
c8f129d0
RD
4189 r1, g1, b1 = int(top.Red()), int(top.Green()), int(top.Blue())
4190 r2, g2, b2 = int(bottom.Red()), int(bottom.Green()), int(bottom.Blue())
4191
94431133 4192 flrect = float(filRect.height)
c8f129d0
RD
4193
4194 rstep = float((r2 - r1)) / flrect
4195 gstep = float((g2 - g1)) / flrect
4196 bstep = float((b2 - b1)) / flrect
4197
4198 rf, gf, bf = 0, 0, 0
4199 dc.SetPen(wx.TRANSPARENT_PEN)
4200
94431133 4201 for y in xrange(filRect.y, filRect.y + filRect.height):
c8f129d0
RD
4202 currCol = (r1 + rf, g1 + gf, b1 + bf)
4203 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
94431133 4204 dc.DrawRectangle(filRect.x, y, filRect.width, 1)
c8f129d0
RD
4205 rf = rf + rstep
4206 gf = gf + gstep
4207 bf = bf + bstep
4208
94431133
RD
4209 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4210 dc.SetPen(wx.Pen(outer))
4211 dc.DrawRoundedRectangleRect(bdrRect, 3)
4212 bdrRect.Deflate(1, 1)
4213 dc.SetPen(wx.Pen(inner))
4214 dc.DrawRoundedRectangleRect(bdrRect, 2)
4215
c8f129d0
RD
4216 dc.SetPen(oldpen)
4217 dc.SetBrush(oldbrush)
4218
4219
4220 def PaintItem(self, item, dc):
4221 """Actually paint an item."""
4222
4223 attr = item.GetAttributes()
4224
4225 if attr and attr.HasFont():
4226 dc.SetFont(attr.GetFont())
4227 elif item.IsBold():
4228 dc.SetFont(self._boldFont)
4229 if item.IsHyperText():
4230 dc.SetFont(self.GetHyperTextFont())
4231 if item.GetVisited():
4232 dc.SetTextForeground(self.GetHyperTextVisitedColour())
4233 else:
4234 dc.SetTextForeground(self.GetHyperTextNewColour())
4235
4236 text_w, text_h, dummy = dc.GetMultiLineTextExtent(item.GetText())
4237
4238 image = item.GetCurrentImage()
4239 checkimage = item.GetCurrentCheckedImage()
4240 image_w, image_h = 0, 0
4241
4242 if image != _NO_IMAGE:
4243
4244 if self._imageListNormal:
4245
4246 image_w, image_h = self._imageListNormal.GetSize(image)
4247 image_w += 4
4248
4249 else:
4250
4251 image = _NO_IMAGE
4252
4253 if item.GetType() != 0:
4254 wcheck, hcheck = self._imageListCheck.GetSize(item.GetType())
4255 wcheck += 4
4256 else:
4257 wcheck, hcheck = 0, 0
4258
4259 total_h = self.GetLineHeight(item)
4260 drawItemBackground = False
94431133 4261
c8f129d0
RD
4262 if item.IsSelected():
4263
4264 # under mac selections are only a rectangle in case they don't have the focus
4265 if wx.Platform == "__WXMAC__":
4266 if not self._hasFocus:
4267 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4268 dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT), 1, wx.SOLID))
4269 else:
4270 dc.SetBrush(self._hilightBrush)
4271 else:
4272 dc.SetBrush((self._hasFocus and [self._hilightBrush] or [self._hilightUnfocusedBrush])[0])
4273 drawItemBackground = True
4274 else:
4275 if attr and attr.HasBackgroundColour():
4276 drawItemBackground = True
4277 colBg = attr.GetBackgroundColour()
4278 else:
4279 colBg = self._backgroundColour
4280
4281 dc.SetBrush(wx.Brush(colBg, wx.SOLID))
4282 dc.SetPen(wx.TRANSPARENT_PEN)
4283
4284 offset = (self.HasFlag(TR_ROW_LINES) and [1] or [0])[0]
94431133 4285
c8f129d0 4286 if self.HasFlag(TR_FULL_ROW_HIGHLIGHT):
369be443 4287 x = 0
94431133 4288 w, h = self.GetClientSize()
c8f129d0
RD
4289
4290 itemrect = wx.Rect(x, item.GetY()+offset, w, total_h-offset)
4291
4292 if item.IsSelected():
4293 if self._usegradients:
4294 if self._gradientstyle == 0: # Horizontal
4295 self.DrawHorizontalGradient(dc, itemrect, self._hasFocus)
4296 else: # Vertical
4297 self.DrawVerticalGradient(dc, itemrect, self._hasFocus)
4298 elif self._vistaselection:
4299 self.DrawVistaRectangle(dc, itemrect, self._hasFocus)
4300 else:
8bc0c17a
KO
4301 if wx.Platform in ["__WXGTK2__", "__WXMAC__"]:
4302 flags = wx.CONTROL_SELECTED
4303 if self._hasFocus: flags = flags | wx.CONTROL_FOCUSED
4304 wx.RendererNative.Get().DrawItemSelectionRect(self, dc, itemrect, flags)
4305 else:
4306 dc.DrawRectangleRect(itemrect)
c8f129d0 4307
c8f129d0 4308 else:
6e65f80b
RD
4309
4310 if item.IsSelected():
c8f129d0
RD
4311
4312 # If it's selected, and there's an image, then we should
4313 # take care to leave the area under the image painted in the
4314 # background colour.
4315
4316 wnd = item.GetWindow()
4317 wndx = 0
4318 if wnd:
4319 wndx, wndy = item.GetWindowSize()
4320
94431133
RD
4321 itemrect = wx.Rect(item.GetX() + wcheck + image_w - 2,
4322 item.GetY()+offset,
4323 item.GetWidth() - image_w - wcheck + 2 - wndx,
4324 total_h-offset)
4325
c8f129d0
RD
4326 if self._usegradients:
4327 if self._gradientstyle == 0: # Horizontal
4328 self.DrawHorizontalGradient(dc, itemrect, self._hasFocus)
4329 else: # Vertical
4330 self.DrawVerticalGradient(dc, itemrect, self._hasFocus)
4331 elif self._vistaselection:
4332 self.DrawVistaRectangle(dc, itemrect, self._hasFocus)
4333 else:
8bc0c17a
KO
4334 if wx.Platform in ["__WXGTK2__", "__WXMAC__"]:
4335 flags = wx.CONTROL_SELECTED
4336 if self._hasFocus: flags = flags | wx.CONTROL_FOCUSED
4337 wx.RendererNative.Get().DrawItemSelectionRect(self, dc, itemrect, flags)
4338 else:
4339 dc.DrawRectangleRect(itemrect)
c8f129d0
RD
4340
4341 # On GTK+ 2, drawing a 'normal' background is wrong for themes that
4342 # don't allow backgrounds to be customized. Not drawing the background,
4343 # except for custom item backgrounds, works for both kinds of theme.
4344 elif drawItemBackground:
4345
4346 minusicon = wcheck + image_w - 2
94431133
RD
4347 itemrect = wx.Rect(item.GetX()+minusicon,
4348 item.GetY()+offset,
4349 item.GetWidth()-minusicon,
4350 total_h-offset)
c8f129d0
RD
4351
4352 if self._usegradients and self._hasFocus:
4353 if self._gradientstyle == 0: # Horizontal
4354 self.DrawHorizontalGradient(dc, itemrect, self._hasFocus)
4355 else: # Vertical
4356 self.DrawVerticalGradient(dc, itemrect, self._hasFocus)
4357 else:
4358 dc.DrawRectangleRect(itemrect)
4359
4360 if image != _NO_IMAGE:
4361
4362 dc.SetClippingRegion(item.GetX(), item.GetY(), wcheck+image_w-2, total_h)
4363 if item.IsEnabled():
4364 imglist = self._imageListNormal
4365 else:
4366 imglist = self._grayedImageList
4367
4368 imglist.Draw(image, dc,
4369 item.GetX() + wcheck,
4370 item.GetY() + ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0],
4371 wx.IMAGELIST_DRAW_TRANSPARENT)
4372
4373 dc.DestroyClippingRegion()
4374
4375 if wcheck:
4376 if item.IsEnabled():
4377 imglist = self._imageListCheck
4378 else:
4379 imglist = self._grayedCheckList
4380
4381 imglist.Draw(checkimage, dc,
4382 item.GetX(),
4383 item.GetY() + ((total_h > hcheck) and [(total_h-hcheck)/2] or [0])[0],
4384 wx.IMAGELIST_DRAW_TRANSPARENT)
4385
4386 dc.SetBackgroundMode(wx.TRANSPARENT)
4387 extraH = ((total_h > text_h) and [(total_h - text_h)/2] or [0])[0]
4388
4389 textrect = wx.Rect(wcheck + image_w + item.GetX(), item.GetY() + extraH, text_w, text_h)
4390
4391 if not item.IsEnabled():
4392 foreground = dc.GetTextForeground()
4393 dc.SetTextForeground(self._disabledColour)
4394 dc.DrawLabel(item.GetText(), textrect)
4395 dc.SetTextForeground(foreground)
4396 else:
8bc0c17a
KO
4397 if wx.Platform == "__WXMAC__" and item.IsSelected() and self._hasFocus:
4398 dc.SetTextForeground(wx.WHITE)
c8f129d0
RD
4399 dc.DrawLabel(item.GetText(), textrect)
4400
4401 wnd = item.GetWindow()
4402 if wnd:
4403 wndx = wcheck + image_w + item.GetX() + text_w + 4
4404 xa, ya = self.CalcScrolledPosition((0, item.GetY()))
4405 if not wnd.IsShown():
4406 wnd.Show()
4407 if wnd.GetPosition() != (wndx, ya):
4408 wnd.SetPosition((wndx, ya))
4409
4410 # restore normal font
4411 dc.SetFont(self._normalFont)
4412
4413
4414 # Now y stands for the top of the item, whereas it used to stand for middle !
4415 def PaintLevel(self, item, dc, level, y):
4416 """Paint a level of CustomTreeCtrl."""
4417
4418 x = level*self._indent
4419
4420 if not self.HasFlag(TR_HIDE_ROOT):
4421
4422 x += self._indent
4423
4424 elif level == 0:
4425
4426 # always expand hidden root
4427 origY = y
4428 children = item.GetChildren()
4429 count = len(children)
4430
4431 if count > 0:
4432 n = 0
4433 while n < count:
4434 oldY = y
4435 y = self.PaintLevel(children[n], dc, 1, y)
4436 n = n + 1
4437
4438 if not self.HasFlag(TR_NO_LINES) and self.HasFlag(TR_LINES_AT_ROOT) and count > 0:
4439
4440 # draw line down to last child
4441 origY += self.GetLineHeight(children[0])>>1
4442 oldY += self.GetLineHeight(children[n-1])>>1
4443 dc.DrawLine(3, origY, 3, oldY)
4444
4445 return y
4446
4447 item.SetX(x+self._spacing)
4448 item.SetY(y)
4449
4450 h = self.GetLineHeight(item)
4451 y_top = y
4452 y_mid = y_top + (h>>1)
4453 y += h
4454
4455 exposed_x = dc.LogicalToDeviceX(0)
4456 exposed_y = dc.LogicalToDeviceY(y_top)
4457
4458 if self.IsExposed(exposed_x, exposed_y, 10000, h): # 10000 = very much
4459 if wx.Platform == "__WXMAC__":
4460 # don't draw rect outline if we already have the
4461 # background color under Mac
4462 pen = ((item.IsSelected() and self._hasFocus) and [self._borderPen] or [wx.TRANSPARENT_PEN])[0]
4463 else:
4464 pen = self._borderPen
4465
4466 if item.IsSelected():
4467 if (wx.Platform == "__WXMAC__" and self._hasFocus):
4468 colText = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
4469 else:
4470 colText = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
4471 else:
4472 attr = item.GetAttributes()
4473 if attr and attr.HasTextColour():
4474 colText = attr.GetTextColour()
4475 else:
4476 colText = self.GetForegroundColour()
4477
4478 if self._vistaselection:
4479 colText = wx.BLACK
4480
4481 # prepare to draw
4482 dc.SetTextForeground(colText)
4483 dc.SetPen(pen)
4484 oldpen = pen
4485
4486 # draw
4487 self.PaintItem(item, dc)
4488
4489 if self.HasFlag(TR_ROW_LINES):
4490
4491 # if the background colour is white, choose a
4492 # contrasting color for the lines
4493 medium_grey = wx.Pen(wx.Colour(200, 200, 200))
4494 dc.SetPen(((self.GetBackgroundColour() == wx.WHITE) and [medium_grey] or [wx.WHITE_PEN])[0])
4495 dc.DrawLine(0, y_top, 10000, y_top)
4496 dc.DrawLine(0, y, 10000, y)
4497
4498 # restore DC objects
4499 dc.SetBrush(wx.WHITE_BRUSH)
4500 dc.SetTextForeground(wx.BLACK)
4501
4502 if not self.HasFlag(TR_NO_LINES):
4503
4504 # draw the horizontal line here
4505 dc.SetPen(self._dottedPen)
4506 x_start = x
4507 if x > self._indent:
4508 x_start -= self._indent
4509 elif self.HasFlag(TR_LINES_AT_ROOT):
4510 x_start = 3
4511 dc.DrawLine(x_start, y_mid, x + self._spacing, y_mid)
4512 dc.SetPen(oldpen)
4513
4514 # should the item show a button?
4515 if item.HasPlus() and self.HasButtons():
4516
4517 if self._imageListButtons:
4518
4519 # draw the image button here
4520 image_h = 0
4521 image_w = 0
4522 image = (item.IsExpanded() and [TreeItemIcon_Expanded] or [TreeItemIcon_Normal])[0]
4523 if item.IsSelected():
4524 image += TreeItemIcon_Selected - TreeItemIcon_Normal
4525
4526 image_w, image_h = self._imageListButtons.GetSize(image)
4527 xx = x - image_w/2
4528 yy = y_mid - image_h/2
4529
4530 dc.SetClippingRegion(xx, yy, image_w, image_h)
4531 self._imageListButtons.Draw(image, dc, xx, yy,
4532 wx.IMAGELIST_DRAW_TRANSPARENT)
4533 dc.DestroyClippingRegion()
4534
4535 else: # no custom buttons
4536
4537 if self._windowStyle & TR_TWIST_BUTTONS:
4538 # We draw something like the Mac twist buttons
4539
4540 dc.SetPen(wx.BLACK_PEN)
4541 dc.SetBrush(self._hilightBrush)
4542 button = [wx.Point(), wx.Point(), wx.Point()]
4543
4544 if item.IsExpanded():
4545 button[0].x = x - 5
4546 button[0].y = y_mid - 3
4547 button[1].x = x + 5
4548 button[1].y = button[0].y
4549 button[2].x = x
4550 button[2].y = button[0].y + 6
4551 else:
4552 button[0].x = x - 3
4553 button[0].y = y_mid - 5
4554 button[1].x = button[0].x
4555 button[1].y = y_mid + 5
4556 button[2].x = button[0].x + 5
4557 button[2].y = y_mid
4558
4559 dc.DrawPolygon(button)
4560
4561 else:
4562 # These are the standard wx.TreeCtrl buttons as wx.RendererNative knows
4563
4564 wImage = 9
4565 hImage = 9
4566
4567 flag = 0
4568
4569 if item.IsExpanded():
4570 flag |= _CONTROL_EXPANDED
4571 if item == self._underMouse:
4572 flag |= _CONTROL_CURRENT
4573
4574 self._drawingfunction(self, dc, wx.Rect(x - wImage/2, y_mid - hImage/2,wImage, hImage), flag)
4575
4576 if item.IsExpanded():
4577
4578 children = item.GetChildren()
4579 count = len(children)
4580
4581 if count > 0:
4582
4583 n = 0
4584 level = level + 1
4585
4586 while n < count:
4587 oldY = y
4588 y = self.PaintLevel(children[n], dc, level, y)
4589 n = n + 1
4590
4591 if not self.HasFlag(TR_NO_LINES) and count > 0:
4592
4593 # draw line down to last child
4594 oldY += self.GetLineHeight(children[n-1])>>1
4595 if self.HasButtons():
4596 y_mid += 5
4597
4598 # Only draw the portion of the line that is visible, in case it is huge
4599 xOrigin, yOrigin = dc.GetDeviceOrigin()
4600 yOrigin = abs(yOrigin)
4601 width, height = self.GetClientSize()
4602
4603 # Move end points to the begining/end of the view?
4604 if y_mid < yOrigin:
4605 y_mid = yOrigin
4606 if oldY > yOrigin + height:
4607 oldY = yOrigin + height
4608
4609 # after the adjustments if y_mid is larger than oldY then the line
4610 # isn't visible at all so don't draw anything
4611 if y_mid < oldY:
4612 dc.SetPen(self._dottedPen)
4613 dc.DrawLine(x, y_mid, x, oldY)
4614
4615 return y
4616
4617
4618# -----------------------------------------------------------------------------
4619# wxWidgets callbacks
4620# -----------------------------------------------------------------------------
4621
4622 def OnPaint(self, event):
4623 """Handles the wx.EVT_PAINT event."""
4624
4625 dc = wx.PaintDC(self)
4626 self.PrepareDC(dc)
4627
4628 if not self._anchor:
4629 return
4630
4631 dc.SetFont(self._normalFont)
4632 dc.SetPen(self._dottedPen)
4633
4634 y = 2
4635 self.PaintLevel(self._anchor, dc, 0, y)
4636
4637
4638 def OnEraseBackground(self, event):
4639 """Handles the wx.EVT_ERASE_BACKGROUND event."""
4640
4641 # Can we actually do something here (or in OnPaint()) To Handle
4642 # background images that are stretchable or always centered?
4643 # I tried but I get enormous flickering...
4644
4645 if not self._backgroundImage:
4646 event.Skip()
4647 return
4648
4649 if self._imageStretchStyle == _StyleTile:
4650 dc = event.GetDC()
4651
4652 if not dc:
4653 dc = wx.ClientDC(self)
4654 rect = self.GetUpdateRegion().GetBox()
4655 dc.SetClippingRect(rect)
4656
4657 self.TileBackground(dc)
4658
4659
4660 def TileBackground(self, dc):
4661 """Tiles the background image to fill all the available area."""
4662
4663 sz = self.GetClientSize()
4664 w = self._backgroundImage.GetWidth()
4665 h = self._backgroundImage.GetHeight()
4666
4667 x = 0
4668
4669 while x < sz.width:
4670 y = 0
4671
4672 while y < sz.height:
4673 dc.DrawBitmap(self._backgroundImage, x, y, True)
4674 y = y + h
4675
4676 x = x + w
4677
4678
4679 def OnSetFocus(self, event):
4680 """Handles the wx.EVT_SET_FOCUS event."""
4681
4682 self._hasFocus = True
4683 self.RefreshSelected()
4684 event.Skip()
4685
4686
4687 def OnKillFocus(self, event):
4688 """Handles the wx.EVT_KILL_FOCUS event."""
4689
4690 self._hasFocus = False
4691 self.RefreshSelected()
4692 event.Skip()
4693
4694
4695 def OnKeyDown(self, event):
4696 """Handles the wx.EVT_CHAR event, sending a EVT_TREE_KEY_DOWN event."""
4697
4698 te = TreeEvent(wxEVT_TREE_KEY_DOWN, self.GetId())
4699 te._evtKey = event
4700 te.SetEventObject(self)
4701
4702 if self.GetEventHandler().ProcessEvent(te):
4703 # intercepted by the user code
4704 return
4705
4706 if self._current is None or self._key_current is None:
4707
4708 event.Skip()
4709 return
4710
4711 # how should the selection work for this event?
4712 is_multiple, extended_select, unselect_others = EventFlagsToSelType(self.GetTreeStyle(), event.ShiftDown(), event.CmdDown())
4713
4714 # + : Expand
4715 # - : Collaspe
4716 # * : Expand all/Collapse all
4717 # ' ' | return : activate
4718 # up : go up (not last children!)
4719 # down : go down
4720 # left : go to parent
4721 # right : open if parent and go next
4722 # home : go to root
4723 # end : go to last item without opening parents
4724 # alnum : start or continue searching for the item with this prefix
4725
4726 keyCode = event.GetKeyCode()
4727
4728 if keyCode in [ord("+"), wx.WXK_ADD]: # "+"
4729 if self._current.HasPlus() and not self.IsExpanded(self._current) and self.IsEnabled(self._current):
4730 self.Expand(self._current)
4731
4732 elif keyCode in [ord("*"), wx.WXK_MULTIPLY]: # "*"
4733 if not self.IsExpanded(self._current) and self.IsEnabled(self._current):
4734 # expand all
4735 self.ExpandAll(self._current)
4736
4737 elif keyCode in [ord("-"), wx.WXK_SUBTRACT]: # "-"
4738 if self.IsExpanded(self._current):
4739 self.Collapse(self._current)
4740
4741 elif keyCode == wx.WXK_MENU:
4742 # Use the item's bounding rectangle to determine position for the event
4743 itemRect = self.GetBoundingRect(self._current, True)
4744 event = TreeEvent(wxEVT_TREE_ITEM_MENU, self.GetId())
4745 event._item = self._current
4746 # Use the left edge, vertical middle
4747 event._pointDrag = wx.Point(ItemRect.GetX(), ItemRect.GetY() + ItemRect.GetHeight()/2)
4748 event.SetEventObject(self)
4749 self.GetEventHandler().ProcessEvent(event)
4750
4751 elif keyCode in [wx.WXK_RETURN, wx.WXK_SPACE]:
4752
4753 if not self.IsEnabled(self._current):
4754 event.Skip()
4755 return
4756
4757 if not event.HasModifiers():
4758 event = TreeEvent(wxEVT_TREE_ITEM_ACTIVATED, self.GetId())
4759 event._item = self._current
4760 event.SetEventObject(self)
4761 self.GetEventHandler().ProcessEvent(event)
4762
4763 if keyCode == wx.WXK_SPACE and self.GetItemType(self._current) > 0:
4764 checked = not self.IsItemChecked(self._current)
4765 self.CheckItem(self._current, checked)
4766
4767 # in any case, also generate the normal key event for this key,
4768 # even if we generated the ACTIVATED event above: this is what
4769 # wxMSW does and it makes sense because you might not want to
4770 # process ACTIVATED event at all and handle Space and Return
4771 # directly (and differently) which would be impossible otherwise
4772 event.Skip()
4773
4774 # up goes to the previous sibling or to the last
4775 # of its children if it's expanded
4776 elif keyCode == wx.WXK_UP:
4777 prev = self.GetPrevSibling(self._key_current)
4778 if not prev:
4779 prev = self.GetItemParent(self._key_current)
4780 if prev == self.GetRootItem() and self.HasFlag(TR_HIDE_ROOT):
4781 return
4782
4783 if prev:
4784 current = self._key_current
4785 # TODO: Huh? If we get here, we'd better be the first child of our parent. How else could it be?
4786 if current == self.GetFirstChild(prev)[0] and self.IsEnabled(prev):
4787 # otherwise we return to where we came from
4788 self.DoSelectItem(prev, unselect_others, extended_select)
4789 self._key_current = prev
4790
4791 else:
4792 current = self._key_current
4793
4794 # We are going to another parent node
4795 while self.IsExpanded(prev) and self.HasChildren(prev):
4796 child = self.GetLastChild(prev)
4797 if child:
4798 prev = child
4799 current = prev
4800
4801 # Try to get the previous siblings and see if they are active
4802 while prev and not self.IsEnabled(prev):
4803 prev = self.GetPrevSibling(prev)
4804
4805 if not prev:
4806 # No previous siblings active: go to the parent and up
4807 prev = self.GetItemParent(current)
4808 while prev and not self.IsEnabled(prev):
4809 prev = self.GetItemParent(prev)
4810
4811 if prev:
4812 self.DoSelectItem(prev, unselect_others, extended_select)
4813 self._key_current = prev
4814
4815 # left arrow goes to the parent
4816 elif keyCode == wx.WXK_LEFT:
4817
4818 prev = self.GetItemParent(self._current)
4819 if prev == self.GetRootItem() and self.HasFlag(TR_HIDE_ROOT):
4820 # don't go to root if it is hidden
4821 prev = self.GetPrevSibling(self._current)
4822
4823 if self.IsExpanded(self._current):
4824 self.Collapse(self._current)
4825 else:
4826 if prev and self.IsEnabled(prev):
4827 self.DoSelectItem(prev, unselect_others, extended_select)
4828
4829 elif keyCode == wx.WXK_RIGHT:
4830 # this works the same as the down arrow except that we
4831 # also expand the item if it wasn't expanded yet
4832 if self.IsExpanded(self._current) and self.HasChildren(self._current):
4833 child, cookie = self.GetFirstChild(self._key_current)
4834 if self.IsEnabled(child):
4835 self.DoSelectItem(child, unselect_others, extended_select)
4836 self._key_current = child
4837 else:
4838 self.Expand(self._current)
4839 # fall through
4840
4841 elif keyCode == wx.WXK_DOWN:
4842 if self.IsExpanded(self._key_current) and self.HasChildren(self._key_current):
4843
4844 child = self.GetNextActiveItem(self._key_current)
4845
4846 if child:
4847 self.DoSelectItem(child, unselect_others, extended_select)
4848 self._key_current = child
4849
4850 else:
4851
4852 next = self.GetNextSibling(self._key_current)
4853
4854 if not next:
4855 current = self._key_current
4856 while current and not next:
4857 current = self.GetItemParent(current)
4858 if current:
4859 next = self.GetNextSibling(current)
facc1d35 4860 if not next or not self.IsEnabled(next):
c8f129d0
RD
4861 next = None
4862
4863 else:
4864 while next and not self.IsEnabled(next):
4865 next = self.GetNext(next)
4866
4867 if next:
4868 self.DoSelectItem(next, unselect_others, extended_select)
4869 self._key_current = next
4870
4871
4872 # <End> selects the last visible tree item
4873 elif keyCode == wx.WXK_END:
4874
4875 last = self.GetRootItem()
4876
4877 while last and self.IsExpanded(last):
4878
4879 lastChild = self.GetLastChild(last)
4880
4881 # it may happen if the item was expanded but then all of
4882 # its children have been deleted - so IsExpanded() returned
4883 # true, but GetLastChild() returned invalid item
4884 if not lastChild:
4885 break
4886
4887 last = lastChild
4888
4889 if last and self.IsEnabled(last):
4890
4891 self.DoSelectItem(last, unselect_others, extended_select)
4892
4893 # <Home> selects the root item
4894 elif keyCode == wx.WXK_HOME:
4895
4896 prev = self.GetRootItem()
4897
4898 if not prev:
4899 return
4900
4901 if self.HasFlag(TR_HIDE_ROOT):
4902 prev, cookie = self.GetFirstChild(prev)
4903 if not prev:
4904 return
4905
4906 if self.IsEnabled(prev):
4907 self.DoSelectItem(prev, unselect_others, extended_select)
4908
4909 else:
4910
4911 if not event.HasModifiers() and ((keyCode >= ord('0') and keyCode <= ord('9')) or \
4912 (keyCode >= ord('a') and keyCode <= ord('z')) or \
4913 (keyCode >= ord('A') and keyCode <= ord('Z'))):
4914
4915 # find the next item starting with the given prefix
4916 ch = chr(keyCode)
4917 id = self.FindItem(self._current, self._findPrefix + ch)
4918
4919 if not id:
4920 # no such item
4921 return
4922
4923 if self.IsEnabled(id):
4924 self.SelectItem(id)
4925 self._findPrefix += ch
4926
4927 # also start the timer to reset the current prefix if the user
4928 # doesn't press any more alnum keys soon -- we wouldn't want
4929 # to use this prefix for a new item search
4930 if not self._findTimer:
4931 self._findTimer = TreeFindTimer(self)
4932
4933 self._findTimer.Start(_DELAY, wx.TIMER_ONE_SHOT)
4934
4935 else:
4936
4937 event.Skip()
4938
4939
4940 def GetNextActiveItem(self, item, down=True):
4941 """Returns the next active item. Used Internally at present. """
4942
4943 if down:
4944 sibling = self.GetNextSibling
4945 else:
4946 sibling = self.GetPrevSibling
4947
4948 if self.GetItemType(item) == 2 and not self.IsItemChecked(item):
4949 # Is an unchecked radiobutton... all its children are inactive
4950 # try to get the next/previous sibling
4951 found = 0
4952
4953 while 1:
4954 child = sibling(item)
4955 if (child and self.IsEnabled(child)) or not child:
4956 break
4957 item = child
4958
4959 else:
4960 # Tha's not a radiobutton... but some of its children can be
4961 # inactive
4962 child, cookie = self.GetFirstChild(item)
4963 while child and not self.IsEnabled(child):
4964 child, cookie = self.GetNextChild(item, cookie)
4965
4966 if child and self.IsEnabled(child):
4967 return child
4968
4969 return None
4970
4971
4972 def HitTest(self, point, flags=0):
4973 """
4974 Calculates which (if any) item is under the given point, returning the tree item
4975 at this point plus extra information flags. Flags is a bitlist of the following:
4976
4977 TREE_HITTEST_ABOVE above the client area
4978 TREE_HITTEST_BELOW below the client area
4979 TREE_HITTEST_NOWHERE no item has been hit
4980 TREE_HITTEST_ONITEMBUTTON on the button associated to an item
4981 TREE_HITTEST_ONITEMICON on the icon associated to an item
4982 TREE_HITTEST_ONITEMCHECKICON on the check/radio icon, if present
4983 TREE_HITTEST_ONITEMINDENT on the indent associated to an item
4984 TREE_HITTEST_ONITEMLABEL on the label (string) associated to an item
4985 TREE_HITTEST_ONITEMRIGHT on the right of the label associated to an item
4986 TREE_HITTEST_TOLEFT on the left of the client area
4987 TREE_HITTEST_TORIGHT on the right of the client area
4988 TREE_HITTEST_ONITEMUPPERPART on the upper part (first half) of the item
4989 TREE_HITTEST_ONITEMLOWERPART on the lower part (second half) of the item
4990 TREE_HITTEST_ONITEM anywhere on the item
4991
4992 Note: both the item (if any, None otherwise) and the flag are always returned as a tuple.
4993 """
4994
4995 w, h = self.GetSize()
4996 flags = 0
4997
4998 if point.x < 0:
4999 flags |= TREE_HITTEST_TOLEFT
5000 if point.x > w:
5001 flags |= TREE_HITTEST_TORIGHT
5002 if point.y < 0:
5003 flags |= TREE_HITTEST_ABOVE
5004 if point.y > h:
5005 flags |= TREE_HITTEST_BELOW
5006
5007 if flags:
5008 return None, flags
5009
5010 if self._anchor == None:
5011 flags = TREE_HITTEST_NOWHERE
5012 return None, flags
5013
5014 hit, flags = self._anchor.HitTest(self.CalcUnscrolledPosition(point), self, flags, 0)
5015
5016 if hit == None:
5017 flags = TREE_HITTEST_NOWHERE
5018 return None, flags
5019
5020 if not self.IsEnabled(hit):
5021 return None, flags
5022
5023 return hit, flags
5024
5025
5026 def GetBoundingRect(self, item, textOnly=False):
5027 """Gets the bounding rectangle of the item."""
5028
5029 if not item:
e1463b9d 5030 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
5031
5032 i = item
5033
5034 startX, startY = self.GetViewStart()
5035 rect = wx.Rect()
5036
5037 rect.x = i.GetX() - startX*_PIXELS_PER_UNIT
5038 rect.y = i.GetY() - startY*_PIXELS_PER_UNIT
5039 rect.width = i.GetWidth()
5040 rect.height = self.GetLineHeight(i)
5041
5042 return rect
5043
5044
5045 def Edit(self, item):
5046 """
5047 Internal function. Starts the editing of an item label, sending a
5048 EVT_TREE_BEGIN_LABEL_EDIT event.
5049 """
5050
5051 te = TreeEvent(wxEVT_TREE_BEGIN_LABEL_EDIT, self.GetId())
5052 te._item = item
5053 te.SetEventObject(self)
5054 if self.GetEventHandler().ProcessEvent(te) and not te.IsAllowed():
5055 # vetoed by user
5056 return
5057
5058 # We have to call this here because the label in
5059 # question might just have been added and no screen
5060 # update taken place.
5061 if self._dirty:
5062 if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
5063 self.Update()
5064 else:
5065 wx.YieldIfNeeded()
5066
5067 if self._textCtrl != None and item != self._textCtrl.item():
5068 self._textCtrl.StopEditing()
5069
5070 self._textCtrl = TreeTextCtrl(self, item=item)
5071 self._textCtrl.SetFocus()
5072
5073
5074 def GetEditControl(self):
5075 """
5076 Returns a pointer to the edit TextCtrl if the item is being edited or
5077 None otherwise (it is assumed that no more than one item may be edited
5078 simultaneously).
5079 """
5080
5081 return self._textCtrl
5082
5083
5084 def OnRenameAccept(self, item, value):
5085 """
5086 Called by TreeTextCtrl, to accept the changes and to send the
5087 EVT_TREE_END_LABEL_EDIT event.
5088 """
5089
5090 le = TreeEvent(wxEVT_TREE_END_LABEL_EDIT, self.GetId())
5091 le._item = item
5092 le.SetEventObject(self)
5093 le._label = value
5094 le._editCancelled = False
5095
5096 return not self.GetEventHandler().ProcessEvent(le) or le.IsAllowed()
5097
5098
5099 def OnRenameCancelled(self, item):
5100 """
5101 Called by TreeTextCtrl, to cancel the changes and to send the
5102 EVT_TREE_END_LABEL_EDIT event.
5103 """
5104
5105 # let owner know that the edit was cancelled
5106 le = TreeEvent(wxEVT_TREE_END_LABEL_EDIT, self.GetId())
5107 le._item = item
5108 le.SetEventObject(self)
5109 le._label = ""
5110 le._editCancelled = True
5111
5112 self.GetEventHandler().ProcessEvent(le)
5113
5114
5115 def OnRenameTimer(self):
5116 """The timer for renaming has expired. Start editing."""
5117
5118 self.Edit(self._current)
5119
5120
5121 def OnMouse(self, event):
5122 """Handles a bunch of wx.EVT_MOUSE_EVENTS events."""
5123
5124 if not self._anchor:
5125 return
5126
5127 pt = self.CalcUnscrolledPosition(event.GetPosition())
5128
5129 # Is the mouse over a tree item button?
5130 flags = 0
5131 thisItem, flags = self._anchor.HitTest(pt, self, flags, 0)
5132 underMouse = thisItem
5133 underMouseChanged = underMouse != self._underMouse
5134
facc1d35 5135 if underMouse and (flags & TREE_HITTEST_ONITEM) and not event.LeftIsDown() and \
c8f129d0
RD
5136 not self._isDragging and (not self._renameTimer or not self._renameTimer.IsRunning()):
5137 underMouse = underMouse
5138 else:
5139 underMouse = None
5140
5141 if underMouse != self._underMouse:
5142 if self._underMouse:
5143 # unhighlight old item
5144 self._underMouse = None
5145
5146 self._underMouse = underMouse
5147
5148 # Determines what item we are hovering over and need a tooltip for
5149 hoverItem = thisItem
5150
5151 # We do not want a tooltip if we are dragging, or if the rename timer is running
5152 if underMouseChanged and not self._isDragging and (not self._renameTimer or not self._renameTimer.IsRunning()):
5153
5154 if hoverItem is not None:
5155 # Ask the tree control what tooltip (if any) should be shown
5156 hevent = TreeEvent(wxEVT_TREE_ITEM_GETTOOLTIP, self.GetId())
5157 hevent._item = hoverItem
5158 hevent.SetEventObject(self)
5159
5160 if self.GetEventHandler().ProcessEvent(hevent) and hevent.IsAllowed():
5161 self.SetToolTip(hevent._label)
5162
5163 if hoverItem.IsHyperText() and (flags & TREE_HITTEST_ONITEMLABEL) and hoverItem.IsEnabled():
5164 self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
5165 self._isonhyperlink = True
5166 else:
5167 if self._isonhyperlink:
5168 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
5169 self._isonhyperlink = False
5170
5171 # we process left mouse up event (enables in-place edit), right down
5172 # (pass to the user code), left dbl click (activate item) and
5173 # dragging/moving events for items drag-and-drop
5174
5175 if not (event.LeftDown() or event.LeftUp() or event.RightDown() or event.LeftDClick() or \
5176 event.Dragging() or ((event.Moving() or event.RightUp()) and self._isDragging)):
5177
5178 event.Skip()
5179 return
5180
5181 flags = 0
5182 item, flags = self._anchor.HitTest(pt, self, flags, 0)
5183
5184 if event.Dragging() and not self._isDragging and ((flags & TREE_HITTEST_ONITEMICON) or (flags & TREE_HITTEST_ONITEMLABEL)):
5185
5186 if self._dragCount == 0:
5187 self._dragStart = pt
5188
5189 self._countDrag = 0
5190 self._dragCount = self._dragCount + 1
5191
5192 if self._dragCount != 3:
5193 # wait until user drags a bit further...
5194 return
5195
5196 command = (event.RightIsDown() and [wxEVT_TREE_BEGIN_RDRAG] or [wxEVT_TREE_BEGIN_DRAG])[0]
5197
5198 nevent = TreeEvent(command, self.GetId())
5199 nevent._item = self._current
5200 nevent.SetEventObject(self)
5201 newpt = self.CalcScrolledPosition(pt)
5202 nevent.SetPoint(newpt)
5203
5204 # by default the dragging is not supported, the user code must
5205 # explicitly allow the event for it to take place
5206 nevent.Veto()
5207
5208 if self.GetEventHandler().ProcessEvent(nevent) and nevent.IsAllowed():
5209
5210 # we're going to drag this item
5211 self._isDragging = True
5212
5213 # remember the old cursor because we will change it while
5214 # dragging
5215 self._oldCursor = self._cursor
5216
5217 # in a single selection control, hide the selection temporarily
5218 if not (self.GetTreeStyle() & TR_MULTIPLE):
5219 self._oldSelection = self.GetSelection()
5220
5221 if self._oldSelection:
5222
5223 self._oldSelection.SetHilight(False)
5224 self.RefreshLine(self._oldSelection)
5225 else:
5226 selections = self.GetSelections()
5227 if len(selections) == 1:
5228 self._oldSelection = selections[0]
5229 self._oldSelection.SetHilight(False)
5230 self.RefreshLine(self._oldSelection)
5231
5232 if self._dragImage:
5233 del self._dragImage
5234
5235 # Create the custom draw image from the icons and the text of the item
5236 self._dragImage = DragImage(self, self._current)
5237 self._dragImage.BeginDrag(wx.Point(0,0), self)
5238 self._dragImage.Show()
5239 self._dragImage.Move(self.CalcScrolledPosition(pt))
5240
5241 elif event.Dragging() and self._isDragging:
5242
5243 self._dragImage.Move(self.CalcScrolledPosition(pt))
5244
5245 if self._countDrag == 0 and item:
5246 self._oldItem = item
5247
5248 if item != self._dropTarget:
5249
5250 # unhighlight the previous drop target
5251 if self._dropTarget:
5252 self._dropTarget.SetHilight(False)
5253 self.RefreshLine(self._dropTarget)
5254 if item:
5255 item.SetHilight(True)
5256 self.RefreshLine(item)
5257 self._countDrag = self._countDrag + 1
5258 self._dropTarget = item
5259
5260 self.Update()
5261
5262 if self._countDrag >= 3:
5263 # Here I am trying to avoid ugly repainting problems... hope it works
5264 self.RefreshLine(self._oldItem)
5265 self._countDrag = 0
5266
5267 elif (event.LeftUp() or event.RightUp()) and self._isDragging:
5268
5269 if self._dragImage:
5270 self._dragImage.EndDrag()
5271
5272 if self._dropTarget:
5273 self._dropTarget.SetHilight(False)
5274
5275 if self._oldSelection:
5276
5277 self._oldSelection.SetHilight(True)
5278 self.RefreshLine(self._oldSelection)
5279 self._oldSelection = None
5280
5281 # generate the drag end event
5282 event = TreeEvent(wxEVT_TREE_END_DRAG, self.GetId())
5283 event._item = item
5284 event._pointDrag = self.CalcScrolledPosition(pt)
5285 event.SetEventObject(self)
5286
5287 self.GetEventHandler().ProcessEvent(event)
5288
5289 self._isDragging = False
5290 self._dropTarget = None
5291
5292 self.SetCursor(self._oldCursor)
5293
5294 if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
5295 self.Refresh()
5296 else:
5297 # Probably this is not enough on GTK. Try a Refresh() if it does not work.
5298 wx.YieldIfNeeded()
5299
5300 else:
5301
5302 # If we got to this point, we are not dragging or moving the mouse.
5303 # Because the code in carbon/toplevel.cpp will only set focus to the tree
5304 # if we skip for EVT_LEFT_DOWN, we MUST skip this event here for focus to work.
5305 # We skip even if we didn't hit an item because we still should
5306 # restore focus to the tree control even if we didn't exactly hit an item.
5307 if event.LeftDown():
5308 self._hasFocus = True
5309 self.SetFocusIgnoringChildren()
5310 event.Skip()
5311
5312 # here we process only the messages which happen on tree items
5313
5314 self._dragCount = 0
5315
5316 if item == None:
5317 if self._textCtrl != None and item != self._textCtrl.item():
5318 self._textCtrl.StopEditing()
5319 return # we hit the blank area
5320
5321 if event.RightDown():
5322
5323 if self._textCtrl != None and item != self._textCtrl.item():
5324 self._textCtrl.StopEditing()
5325
5326 self._hasFocus = True
5327 self.SetFocusIgnoringChildren()
5328
5329 # If the item is already selected, do not update the selection.
5330 # Multi-selections should not be cleared if a selected item is clicked.
5331 if not self.IsSelected(item):
5332
5333 self.DoSelectItem(item, True, False)
5334
5335 nevent = TreeEvent(wxEVT_TREE_ITEM_RIGHT_CLICK, self.GetId())
5336 nevent._item = item
5337 nevent._pointDrag = self.CalcScrolledPosition(pt)
5338 nevent.SetEventObject(self)
5339 event.Skip(not self.GetEventHandler().ProcessEvent(nevent))
5340
5341 # Consistent with MSW (for now), send the ITEM_MENU *after*
5342 # the RIGHT_CLICK event. TODO: This behaviour may change.
5343 nevent2 = TreeEvent(wxEVT_TREE_ITEM_MENU, self.GetId())
5344 nevent2._item = item
5345 nevent2._pointDrag = self.CalcScrolledPosition(pt)
5346 nevent2.SetEventObject(self)
5347 self.GetEventHandler().ProcessEvent(nevent2)
5348
5349 elif event.LeftUp():
5350
5351 # this facilitates multiple-item drag-and-drop
5352
5353 if self.HasFlag(TR_MULTIPLE):
5354
5355 selections = self.GetSelections()
5356
5357 if len(selections) > 1 and not event.CmdDown() and not event.ShiftDown():
5358
5359 self.DoSelectItem(item, True, False)
5360
5361 if self._lastOnSame:
5362
5363 if item == self._current and (flags & TREE_HITTEST_ONITEMLABEL) and self.HasFlag(TR_EDIT_LABELS):
5364
5365 if self._renameTimer:
5366
5367 if self._renameTimer.IsRunning():
5368
5369 self._renameTimer.Stop()
5370
5371 else:
5372
5373 self._renameTimer = TreeRenameTimer(self)
5374
5375 self._renameTimer.Start(_DELAY, True)
5376
5377 self._lastOnSame = False
5378
5379
5380 else: # !RightDown() && !LeftUp() ==> LeftDown() || LeftDClick()
5381
5382 if not item or not item.IsEnabled():
5383 if self._textCtrl != None and item != self._textCtrl.item():
5384 self._textCtrl.StopEditing()
5385 return
5386
5387 if self._textCtrl != None and item != self._textCtrl.item():
5388 self._textCtrl.StopEditing()
5389
5390 self._hasFocus = True
5391 self.SetFocusIgnoringChildren()
5392
5393 if event.LeftDown():
5394
5395 self._lastOnSame = item == self._current
5396
5397 if flags & TREE_HITTEST_ONITEMBUTTON:
5398
5399 # only toggle the item for a single click, double click on
5400 # the button doesn't do anything (it toggles the item twice)
5401 if event.LeftDown():
5402
5403 self.Toggle(item)
5404
5405 # don't select the item if the button was clicked
5406 return
5407
5408 if item.GetType() > 0 and (flags & TREE_HITTEST_ONITEMCHECKICON):
5409
5410 if event.LeftDown():
5411
5412 self.CheckItem(item, not self.IsItemChecked(item))
5413
5414 return
5415
5416 # clear the previously selected items, if the
5417 # user clicked outside of the present selection.
5418 # otherwise, perform the deselection on mouse-up.
5419 # this allows multiple drag and drop to work.
5420 # but if Cmd is down, toggle selection of the clicked item
5421 if not self.IsSelected(item) or event.CmdDown():
5422
5423 if flags & TREE_HITTEST_ONITEM:
5424 # how should the selection work for this event?
5425 if item.IsHyperText():
5426 self.SetItemVisited(item, True)
5427
5428 is_multiple, extended_select, unselect_others = EventFlagsToSelType(self.GetTreeStyle(),
5429 event.ShiftDown(),
5430 event.CmdDown())
5431
5432 self.DoSelectItem(item, unselect_others, extended_select)
5433
5434 # For some reason, Windows isn't recognizing a left double-click,
5435 # so we need to simulate it here. Allow 200 milliseconds for now.
5436 if event.LeftDClick():
5437
5438 # double clicking should not start editing the item label
5439 if self._renameTimer:
5440 self._renameTimer.Stop()
5441
5442 self._lastOnSame = False
5443
5444 # send activate event first
5445 nevent = TreeEvent(wxEVT_TREE_ITEM_ACTIVATED, self.GetId())
5446 nevent._item = item
5447 nevent._pointDrag = self.CalcScrolledPosition(pt)
5448 nevent.SetEventObject(self)
5449 if not self.GetEventHandler().ProcessEvent(nevent):
5450
5451 # if the user code didn't process the activate event,
5452 # handle it ourselves by toggling the item when it is
5453 # double clicked
5454## if item.HasPlus():
5455 self.Toggle(item)
5456
5457
94431133 5458 def OnInternalIdle(self):
c8f129d0
RD
5459 """Performs operations in idle time (essentially drawing)."""
5460
5461 # Check if we need to select the root item
5462 # because nothing else has been selected.
5463 # Delaying it means that we can invoke event handlers
5464 # as required, when a first item is selected.
5465 if not self.HasFlag(TR_MULTIPLE) and not self.GetSelection():
5466
5467 if self._select_me:
5468 self.SelectItem(self._select_me)
5469 elif self.GetRootItem():
5470 self.SelectItem(self.GetRootItem())
5471
5472 # after all changes have been done to the tree control,
5473 # we actually redraw the tree when everything is over
5474
5475 if not self._dirty:
5476 return
5477 if self._freezeCount:
5478 return
5479
5480 self._dirty = False
5481
5482 self.CalculatePositions()
5483 self.Refresh()
5484 self.AdjustMyScrollbars()
5485
5486# event.Skip()
5487
5488
5489 def CalculateSize(self, item, dc):
5490 """Calculates overall position and size of an item."""
5491
5492 attr = item.GetAttributes()
5493
5494 if attr and attr.HasFont():
5495 dc.SetFont(attr.GetFont())
5496 elif item.IsBold():
5497 dc.SetFont(self._boldFont)
5498 else:
5499 dc.SetFont(self._normalFont)
5500
5501 text_w, text_h, dummy = dc.GetMultiLineTextExtent(item.GetText())
5502 text_h+=2
5503
5504 # restore normal font
5505 dc.SetFont(self._normalFont)
5506
5507 image_w, image_h = 0, 0
5508 image = item.GetCurrentImage()
5509
5510 if image != _NO_IMAGE:
5511
5512 if self._imageListNormal:
5513
5514 image_w, image_h = self._imageListNormal.GetSize(image)
5515 image_w += 4
5516
5517 total_h = ((image_h > text_h) and [image_h] or [text_h])[0]
5518
5519 checkimage = item.GetCurrentCheckedImage()
5520 if checkimage is not None:
5521 wcheck, hcheck = self._imageListCheck.GetSize(checkimage)
5522 wcheck += 4
5523 else:
5524 wcheck = 0
5525
5526 if total_h < 30:
5527 total_h += 2 # at least 2 pixels
5528 else:
5529 total_h += total_h/10 # otherwise 10% extra spacing
5530
5531 if total_h > self._lineHeight:
5532 self._lineHeight = total_h
5533
5534 if not item.GetWindow():
5535 item.SetWidth(image_w+text_w+wcheck+2)
5536 item.SetHeight(total_h)
5537 else:
5538 item.SetWidth(item.GetWindowSize()[0]+image_w+text_w+wcheck+2)
5539
5540
5541 def CalculateLevel(self, item, dc, level, y):
5542 """Calculates the level of an item."""
5543
5544 x = level*self._indent
5545
5546 if not self.HasFlag(TR_HIDE_ROOT):
5547
5548 x += self._indent
5549
5550 elif level == 0:
5551
5552 # a hidden root is not evaluated, but its
5553 # children are always calculated
5554 children = item.GetChildren()
5555 count = len(children)
5556 level = level + 1
5557 for n in xrange(count):
5558 y = self.CalculateLevel(children[n], dc, level, y) # recurse
5559
5560 return y
5561
5562 self.CalculateSize(item, dc)
5563
5564 # set its position
5565 item.SetX(x+self._spacing)
5566 item.SetY(y)
5567 y += self.GetLineHeight(item)
5568
5569 if not item.IsExpanded():
5570 # we don't need to calculate collapsed branches
5571 return y
5572
5573 children = item.GetChildren()
5574 count = len(children)
5575 level = level + 1
5576 for n in xrange(count):
5577 y = self.CalculateLevel(children[n], dc, level, y) # recurse
5578
5579 return y
5580
5581
5582 def CalculatePositions(self):
5583 """Calculates all the positions of the visible items."""
5584
5585 if not self._anchor:
5586 return
5587
5588 dc = wx.ClientDC(self)
5589 self.PrepareDC(dc)
5590
5591 dc.SetFont(self._normalFont)
5592 dc.SetPen(self._dottedPen)
5593 y = 2
5594 y = self.CalculateLevel(self._anchor, dc, 0, y) # start recursion
5595
5596
5597 def RefreshSubtree(self, item):
5598 """Refreshes a damaged subtree of an item."""
5599
5600 if self._dirty:
5601 return
5602 if self._freezeCount:
5603 return
5604
5605 client = self.GetClientSize()
5606
5607 rect = wx.Rect()
5608 x, rect.y = self.CalcScrolledPosition(0, item.GetY())
5609 rect.width = client.x
5610 rect.height = client.y
5611
5612 self.Refresh(True, rect)
5613 self.AdjustMyScrollbars()
5614
5615
5616 def RefreshLine(self, item):
5617 """Refreshes a damaged item line."""
5618
5619 if self._dirty:
5620 return
5621 if self._freezeCount:
5622 return
5623
5624 rect = wx.Rect()
5625 x, rect.y = self.CalcScrolledPosition(0, item.GetY())
5626 rect.width = self.GetClientSize().x
5627 rect.height = self.GetLineHeight(item)
5628
5629 self.Refresh(True, rect)
5630
5631
5632 def RefreshSelected(self):
5633 """Refreshes a damaged selected item line."""
5634
5635 if self._freezeCount:
5636 return
5637
5638 # TODO: this is awfully inefficient, we should keep the list of all
5639 # selected items internally, should be much faster
5640 if self._anchor:
5641 self.RefreshSelectedUnder(self._anchor)
5642
5643
5644 def RefreshSelectedUnder(self, item):
5645 """Refreshes the selected items under the given item."""
5646
5647 if self._freezeCount:
5648 return
5649
5650 if item.IsSelected():
5651 self.RefreshLine(item)
5652
5653 children = item.GetChildren()
5654 for child in children:
5655 self.RefreshSelectedUnder(child)
5656
5657
5658 def Freeze(self):
5659 """Freeze CustomTreeCtrl."""
5660
5661 self._freezeCount = self._freezeCount + 1
5662
5663
5664 def Thaw(self):
5665 """Thaw CustomTreeCtrl."""
5666
5667 if self._freezeCount == 0:
e1463b9d 5668 raise Exception("\nERROR: Thawing Unfrozen Tree Control?")
c8f129d0
RD
5669
5670 self._freezeCount = self._freezeCount - 1
5671
5672 if not self._freezeCount:
5673 self.Refresh()
5674
5675
5676 # ----------------------------------------------------------------------------
5677 # changing colours: we need to refresh the tree control
5678 # ----------------------------------------------------------------------------
5679
5680 def SetBackgroundColour(self, colour):
5681 """Changes the background colour of CustomTreeCtrl."""
5682
5683 if not wx.Window.SetBackgroundColour(self, colour):
5684 return False
5685
5686 if self._freezeCount:
5687 return True
5688
5689 self.Refresh()
5690
5691 return True
5692
5693
5694 def SetForegroundColour(self, colour):
5695 """Changes the foreground colour of CustomTreeCtrl."""
5696
5697 if not wx.Window.SetForegroundColour(self, colour):
5698 return False
5699
5700 if self._freezeCount:
5701 return True
5702
5703 self.Refresh()
5704
5705 return True
5706
5707
5708 def OnGetToolTip(self, event):
5709 """
5710 Process the tooltip event, to speed up event processing. Does not actually
5711 get a tooltip.
5712 """
5713
5714 event.Veto()
5715
5716
5717 def DoGetBestSize(self):
5718 """Something is better than nothing..."""
5719
5720 # something is better than nothing...
5721 # 100x80 is what the MSW version will get from the default
5722 # wxControl::DoGetBestSize
5723
5724 return wx.Size(100, 80)
5725
5726
5727 def GetClassDefaultAttributes(self):
5728 """Gets the class default attributes."""
5729
5730 attr = wx.VisualAttributes()
5731 attr.colFg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)
5732 attr.colBg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_LISTBOX)
5733 attr.font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
5734 return attr
5735
5736