patch from Andrea fixing sending of events and drawing of lines at root.
[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
e734e438 6# Latest Revision: 01 Apr 2007, 22.30 CET
c8f129d0
RD
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
e734e438
RD
137Latest Revision: Andrea Gavana @ 01 Apr 2007, 22.30 CET
138Version 1.0
c8f129d0
RD
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,
cbfc9df6 1739 style=TR_DEFAULT_STYLE, ctstyle=0, validator=wx.DefaultValidator,
c8f129d0
RD
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
cbfc9df6 1752 style: the underlying wx.ScrolledWindow style + CustomTreeCtrl window style. This can be one of:
c8f129d0 1753
c8f129d0
RD
1754 TR_NO_BUTTONS
1755 TR_HAS_BUTTONS # draw collapsed/expanded btns
1756 TR_NO_LINES # don't draw lines at all
1757 TR_LINES_AT_ROOT # connect top-level nodes
1758 TR_TWIST_BUTTONS # draw mac-like twist buttons
1759 TR_SINGLE # single selection mode
1760 TR_MULTIPLE # can select multiple items
1761 TR_EXTENDED # todo: allow extended selection
1762 TR_HAS_VARIABLE_ROW_HEIGHT # allows rows to have variable height
1763 TR_EDIT_LABELS # can edit item labels
1764 TR_ROW_LINES # put border around items
1765 TR_HIDE_ROOT # don't display root node
1766 TR_FULL_ROW_HIGHLIGHT # highlight full horizontal space
1767 TR_AUTO_CHECK_CHILD # only meaningful for checkboxes
eecfab17 1768 TR_AUTO_CHECK_PARENT # only meaningful for checkboxes
c8f129d0
RD
1769 TR_AUTO_TOGGLE_CHILD # only meaningful for checkboxes
1770
cbfc9df6
RD
1771 ctstyle: kept for backward compatibility.
1772
c8f129d0
RD
1773 validator: window validator.
1774
1775 name: window name.
1776 """
cbfc9df6
RD
1777
1778 style = style | ctstyle
c8f129d0
RD
1779
1780 self._current = self._key_current = self._anchor = self._select_me = None
1781 self._hasFocus = False
1782 self._dirty = False
1783
1784 # Default line height: it will soon be changed
1785 self._lineHeight = 10
1786 # Item indent wrt parent
1787 self._indent = 15
1788 # item horizontal spacing between the start and the text
1789 self._spacing = 18
1790
1791 # Brushes for focused/unfocused items (also gradient type)
1792 self._hilightBrush = wx.Brush(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT))
1793 btnshadow = wx.SystemSettings_GetColour(wx.SYS_COLOUR_BTNSHADOW)
1794 self._hilightUnfocusedBrush = wx.Brush(btnshadow)
1795 r, g, b = btnshadow.Red(), btnshadow.Green(), btnshadow.Blue()
3c69a2ec
RD
1796 backcolour = (max((r >> 1) - 20, 0),
1797 max((g >> 1) - 20, 0),
1798 max((b >> 1) - 20, 0))
c8f129d0
RD
1799 backcolour = wx.Colour(backcolour[0], backcolour[1], backcolour[2])
1800 self._hilightUnfocusedBrush2 = wx.Brush(backcolour)
1801
1802 # image list for icons
1803 self._imageListNormal = self._imageListButtons = self._imageListState = self._imageListCheck = None
1804 self._ownsImageListNormal = self._ownsImageListButtons = self._ownsImageListState = False
1805
1806 # Drag and drop initial settings
1807 self._dragCount = 0
1808 self._countDrag = 0
1809 self._isDragging = False
1810 self._dropTarget = self._oldSelection = None
1811 self._dragImage = None
1812 self._underMouse = None
1813
1814 # TextCtrl initial settings for editable items
1815 self._textCtrl = None
1816 self._renameTimer = None
1817
1818 # This one allows us to handle Freeze() and Thaw() calls
1819 self._freezeCount = 0
1820
1821 self._findPrefix = ""
1822 self._findTimer = None
1823
1824 self._dropEffectAboveItem = False
1825 self._lastOnSame = False
1826
1827 # Default normal and bold fonts for an item
1828 self._hasFont = True
1829 self._normalFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
1830 self._boldFont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(),
1831 self._normalFont.GetStyle(), wx.BOLD, self._normalFont.GetUnderlined(),
1832 self._normalFont.GetFaceName(), self._normalFont.GetEncoding())
1833
1834
1835 # Hyperlinks things
1836 self._hypertextfont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(),
1837 self._normalFont.GetStyle(), wx.NORMAL, True,
1838 self._normalFont.GetFaceName(), self._normalFont.GetEncoding())
1839 self._hypertextnewcolour = wx.BLUE
1840 self._hypertextvisitedcolour = wx.Colour(200, 47, 200)
1841 self._isonhyperlink = False
1842
1843 # Default CustomTreeCtrl background colour.
1844 self._backgroundColour = wx.WHITE
1845
1846 # Background image settings
1847 self._backgroundImage = None
1848 self._imageStretchStyle = _StyleTile
1849
1850 # Disabled items colour
1851 self._disabledColour = wx.Colour(180, 180, 180)
1852
1853 # Gradient selection colours
1854 self._firstcolour = color= wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)
1855 self._secondcolour = wx.WHITE
1856 self._usegradients = False
1857 self._gradientstyle = 0 # Horizontal Gradient
1858
1859 # Vista Selection Styles
1860 self._vistaselection = False
1861
1862 # Connection lines style
1863 if wx.Platform != "__WXMAC__":
1864 self._dottedPen = wx.Pen("grey", 1, wx.USER_DASH)
1865 self._dottedPen.SetDashes([1,1])
1866 self._dottedPen.SetCap(wx.CAP_BUTT)
1867 else:
1868 self._dottedPen = wx.Pen("grey", 1)
1869
1870 # Pen Used To Draw The Border Around Selected Items
1871 self._borderPen = wx.BLACK_PEN
1872 self._cursor = wx.StockCursor(wx.CURSOR_ARROW)
1873
1874 # For Appended Windows
1875 self._hasWindows = False
1876 self._itemWithWindow = []
1877
1878 if wx.Platform == "__WXMAC__":
cbfc9df6
RD
1879 style &= ~TR_LINES_AT_ROOT
1880 style |= TR_NO_LINES
c8f129d0 1881
94431133 1882 platform, major, minor = wx.GetOsVersion()
c8f129d0 1883 if major < 10:
cbfc9df6 1884 style |= TR_ROW_LINES
c8f129d0 1885
cbfc9df6 1886 self._windowStyle = style
c8f129d0
RD
1887
1888 # Create the default check image list
1889 self.SetImageListCheck(13, 13)
1890
1891 # A constant to use my translation of RendererNative.DrawTreeItemButton
1892 # if the wxPython version is less than 2.6.2.1.
1893 if wx.VERSION_STRING < "2.6.2.1":
1894 self._drawingfunction = DrawTreeItemButton
1895 else:
1896 self._drawingfunction = wx.RendererNative.Get().DrawTreeItemButton
1897
1898 # Create our container... at last!
94431133 1899 wx.PyScrolledWindow.__init__(self, parent, id, pos, size, style|wx.HSCROLL|wx.VSCROLL, name)
c8f129d0
RD
1900
1901 # If the tree display has no buttons, but does have
1902 # connecting lines, we can use a narrower layout.
1903 # It may not be a good idea to force this...
1904 if not self.HasButtons() and not self.HasFlag(TR_NO_LINES):
1905 self._indent= 10
1906 self._spacing = 10
1907
1908 self.SetValidator(validator)
1909
1910 attr = self.GetDefaultAttributes()
1911 self.SetOwnForegroundColour(attr.colFg)
1912 self.SetOwnBackgroundColour(wx.WHITE)
1913
1914 if not self._hasFont:
1915 self.SetOwnFont(attr.font)
1916
1917 self.SetSize(size)
1918
1919 # Bind the events
1920 self.Bind(wx.EVT_PAINT, self.OnPaint)
1921 self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
1922 self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
1923 self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
1924 self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
1925 self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
1926 self.Bind(EVT_TREE_ITEM_GETTOOLTIP, self.OnGetToolTip)
c8f129d0
RD
1927 self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
1928
1929 # Sets the focus to ourselves: this is useful if you have items
1930 # with associated widgets.
1931 self.SetFocus()
1932
c8f129d0 1933
94431133
RD
1934 def AcceptsFocus(self):
1935 # overridden base class method, allows this ctrl to
1936 # participate in the tab-order, etc. It's overridable because
1937 # of deriving this class from wx.PyScrolledWindow...
1938 return True
1939
c8f129d0
RD
1940
1941 def OnDestroy(self, event):
1942 """Handles the wx.EVT_WINDOW_DESTROY event."""
1943
1944 # Here there may be something I miss... do I have to destroy
1945 # something else?
1946 if self._renameTimer and self._renameTimer.IsRunning():
1947 self._renameTimer.Stop()
1948 del self._renameTimer
1949
1950 if self._findTimer and self._findTimer.IsRunning():
1951 self._findTimer.Stop()
1952 del self._findTimer
1953
1954 event.Skip()
1955
1956
1957 def GetCount(self):
1958 """Returns the global number of items in the tree."""
1959
1960 if not self._anchor:
1961 # the tree is empty
1962 return 0
1963
1964 count = self._anchor.GetChildrenCount()
1965
1966 if not self.HasFlag(TR_HIDE_ROOT):
1967 # take the root itself into account
1968 count = count + 1
1969
1970 return count
1971
1972
1973 def GetIndent(self):
1974 """Returns the item indentation."""
1975
1976 return self._indent
1977
1978
1979 def GetSpacing(self):
1980 """Returns the spacing between the start and the text."""
1981
1982 return self._spacing
1983
1984
1985 def GetRootItem(self):
1986 """Returns the root item."""
1987
1988 return self._anchor
1989
1990
1991 def GetSelection(self):
1992 """Returns the current selection: TR_SINGLE only."""
1993
1994 return self._current
1995
1996
1997 def ToggleItemSelection(self, item):
1998 """Toggles the item selection."""
1999
2000 if not item:
e1463b9d 2001 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2002
2003 self.SelectItem(item, not self.IsSelected(item))
2004
2005
2006 def EnableChildren(self, item, enable=True):
2007 """Enables/disables item children. Used internally."""
2008
2009 torefresh = False
2010 if item.IsExpanded():
2011 torefresh = True
2012
2013 if item.GetType() == 2 and enable and not item.IsChecked():
2014 # We hit a radiobutton item not checked, we don't want to
2015 # enable the children
2016 return
2017
2018 child, cookie = self.GetFirstChild(item)
2019 while child:
2020 self.EnableItem(child, enable, torefresh=torefresh)
2021 # Recurse on tree
2022 if child.GetType != 2 or (child.GetType() == 2 and item.IsChecked()):
2023 self.EnableChildren(child, enable)
2024 (child, cookie) = self.GetNextChild(item, cookie)
2025
2026
2027 def EnableItem(self, item, enable=True, torefresh=True):
2028 """Enables/disables an item."""
2029
2030 if not item:
e1463b9d 2031 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2032
2033 if item.IsEnabled() == enable:
2034 return
2035
2036 if not enable and item.IsSelected():
2037 self.SelectItem(item, False)
2038
2039 item.Enable(enable)
2040 wnd = item.GetWindow()
2041
2042 # Handles the eventual window associated to the item
2043 if wnd:
2044 wndenable = item.GetWindowEnabled()
2045 if enable:
2046 if wndenable:
2047 wnd.Enable(enable)
2048 else:
2049 wnd.Enable(enable)
2050
2051 if torefresh:
2052 # We have to refresh the item line
2053 dc = wx.ClientDC(self)
2054 self.CalculateSize(item, dc)
2055 self.RefreshLine(item)
2056
2057
2058 def IsEnabled(self, item):
2059 """Returns whether an item is enabled or disabled."""
2060
2061 if not item:
e1463b9d 2062 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2063
2064 return item.IsEnabled()
2065
2066
2067 def SetDisabledColour(self, colour):
2068 """Sets the items disabled colour."""
2069
2070 self._disabledColour = colour
2071 self._dirty = True
2072
2073
2074 def GetDisabledColour(self):
2075 """Returns the items disabled colour."""
2076
2077 return self._disabledColour
2078
2079
2080 def IsItemChecked(self, item):
2081 """Returns whether an item is checked or not."""
2082
2083 if not item:
e1463b9d 2084 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2085
2086 return item.IsChecked()
2087
2088
2089 def CheckItem2(self, item, checked=True, torefresh=False):
2090 """Used internally to avoid EVT_TREE_ITEM_CHECKED events."""
2091
2092 if item.GetType() == 0:
2093 return
2094
2095 item.Check(checked)
2096
2097 if torefresh:
2098 dc = wx.ClientDC(self)
2099 self.CalculateSize(item, dc)
2100 self.RefreshLine(item)
2101
2102
2103 def UnCheckRadioParent(self, item, checked=False):
2104 """Used internally to handle radio node parent correctly."""
2105
2106 e = TreeEvent(wxEVT_TREE_ITEM_CHECKING, self.GetId())
2107 e.SetItem(item)
2108 e.SetEventObject(self)
2109
2110 if self.GetEventHandler().ProcessEvent(e):
2111 return False
2112
2113 item.Check(checked)
c8f129d0
RD
2114 self.RefreshLine(item)
2115 self.EnableChildren(item, checked)
2116 e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId())
2117 e.SetItem(item)
2118 e.SetEventObject(self)
2119 self.GetEventHandler().ProcessEvent(e)
2120
2121 return True
2122
2123
2124 def CheckItem(self, item, checked=True):
2125 """
2126 Actually checks/uncheks an item, sending (eventually) the two
2127 events EVT_TREE_ITEM_CHECKING/EVT_TREE_ITEM_CHECKED.
2128 """
2129
2130 if not item:
e1463b9d 2131 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2132
2133 # Should we raise an error here?!?
2134 if item.GetType() == 0:
2135 return
2136
2137 if item.GetType() == 2: # it's a radio button
2138 if not checked and item.IsChecked(): # Try To Unckeck?
2139 if item.HasChildren():
2140 self.UnCheckRadioParent(item, checked)
2141 return
2142 else:
2143 if not self.UnCheckRadioParent(item, checked):
2144 return
2145
2146 self.CheckSameLevel(item, False)
2147 return
2148
2149 # Radiobuttons are done, let's handle checkbuttons...
2150 e = TreeEvent(wxEVT_TREE_ITEM_CHECKING, self.GetId())
2151 e.SetItem(item)
2152 e.SetEventObject(self)
2153
2154 if self.GetEventHandler().ProcessEvent(e):
2155 # Blocked by user
2156 return
2157
2158 item.Check(checked)
2159 dc = wx.ClientDC(self)
2160 self.RefreshLine(item)
2161
2162 if self._windowStyle & TR_AUTO_CHECK_CHILD:
2163 ischeck = self.IsItemChecked(item)
2164 self.AutoCheckChild(item, ischeck)
eecfab17
RD
2165 if self._windowStyle & TR_AUTO_CHECK_PARENT:
2166 ischeck = self.IsItemChecked(item)
2167 self.AutoCheckParent(item, ischeck)
c8f129d0
RD
2168 elif self._windowStyle & TR_AUTO_TOGGLE_CHILD:
2169 self.AutoToggleChild(item)
2170
2171 e = TreeEvent(wxEVT_TREE_ITEM_CHECKED, self.GetId())
2172 e.SetItem(item)
2173 e.SetEventObject(self)
2174 self.GetEventHandler().ProcessEvent(e)
2175
2176
2177 def AutoToggleChild(self, item):
2178 """Transverses the tree and toggles the items. Meaningful only for check items."""
2179
2180 if not item:
e1463b9d 2181 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2182
2183 child, cookie = self.GetFirstChild(item)
2184
2185 torefresh = False
2186 if item.IsExpanded():
2187 torefresh = True
2188
2189 # Recurse on tree
2190 while child:
2191 if child.GetType() == 1 and child.IsEnabled():
2192 self.CheckItem2(child, not child.IsChecked(), torefresh=torefresh)
2193 self.AutoToggleChild(child)
2194 (child, cookie) = self.GetNextChild(item, cookie)
2195
2196
2197 def AutoCheckChild(self, item, checked):
2198 """Transverses the tree and checks/unchecks the items. Meaningful only for check items."""
2199
2200 if not item:
e1463b9d 2201 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2202
2203 (child, cookie) = self.GetFirstChild(item)
2204
2205 torefresh = False
2206 if item.IsExpanded():
2207 torefresh = True
2208
2209 while child:
2210 if child.GetType() == 1 and child.IsEnabled():
2211 self.CheckItem2(child, checked, torefresh=torefresh)
2212 self.AutoCheckChild(child, checked)
2213 (child, cookie) = self.GetNextChild(item, cookie)
2214
2215
eecfab17
RD
2216 def AutoCheckParent(self, item, checked):
2217 """Traverses up the tree and checks/unchecks parent items.
2218 Meaningful only for check items."""
2219
2220 if not item:
e1463b9d 2221 raise Exception("\nERROR: Invalid Tree Item. ")
eecfab17
RD
2222
2223 parent = item.GetParent()
2224 if not parent or parent.GetType() != 1:
2225 return
2226
2227 (child, cookie) = self.GetFirstChild(parent)
2228 while child:
2229 if child.GetType() == 1 and child.IsEnabled():
2230 if checked != child.IsChecked():
2231 return
2232 (child, cookie) = self.GetNextChild(parent, cookie)
2233
2234 self.CheckItem2(parent, checked, torefresh=True)
2235 self.AutoCheckParent(parent, checked)
2236
2237
c8f129d0
RD
2238 def CheckChilds(self, item, checked=True):
2239 """Programatically check/uncheck item children. Does not generate EVT_TREE_CHECK* events."""
2240
2241 if not item:
e1463b9d 2242 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2243
2244 if checked == None:
2245 self.AutoToggleChild(item)
2246 else:
2247 self.AutoCheckChild(item, checked)
2248
2249
2250 def CheckSameLevel(self, item, checked=False):
2251 """
2252 Uncheck radio items which are on the same level of the checked one.
2253 Used internally.
2254 """
2255
2256 parent = item.GetParent()
2257
2258 if not parent:
2259 return
2260
2261 torefresh = False
2262 if parent.IsExpanded():
2263 torefresh = True
2264
2265 (child, cookie) = self.GetFirstChild(parent)
2266 while child:
2267 if child.GetType() == 2 and child != item:
2268 self.CheckItem2(child, checked, torefresh=torefresh)
2269 if child.GetType != 2 or (child.GetType() == 2 and child.IsChecked()):
2270 self.EnableChildren(child, checked)
2271 (child, cookie) = self.GetNextChild(parent, cookie)
2272
2273
2274 def EditLabel(self, item):
2275 """Starts editing an item label."""
2276
2277 if not item:
e1463b9d 2278 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2279
2280 self.Edit(item)
2281
2282
2283 def ShouldInheritColours(self):
2284 """We don't inherit colours from anyone."""
2285
2286 return False
2287
2288
2289 def SetIndent(self, indent):
2290 """Sets item indentation."""
2291
2292 self._indent = indent
2293 self._dirty = True
2294
2295
2296 def SetSpacing(self, spacing):
2297 """Sets item spacing."""
2298
2299 self._spacing = spacing
2300 self._dirty = True
2301
2302
2303 def HasFlag(self, flag):
2304 """Returns whether CustomTreeCtrl has a flag."""
2305
2306 return self._windowStyle & flag
2307
2308
2309 def HasChildren(self, item):
2310 """Returns whether an item has children or not."""
2311
2312 if not item:
e1463b9d 2313 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2314
2315 return len(item.GetChildren()) > 0
2316
2317
2318 def GetChildrenCount(self, item, recursively=True):
2319 """Gets the item children count."""
2320
2321 if not item:
e1463b9d 2322 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2323
2324 return item.GetChildrenCount(recursively)
2325
2326
2327 def SetTreeStyle(self, styles):
2328 """Sets the CustomTreeCtrl style. See __init__ method for the styles explanation."""
2329
2330 # Do not try to expand the root node if it hasn't been created yet
2331 if self._anchor and not self.HasFlag(TR_HIDE_ROOT) and styles & TR_HIDE_ROOT:
2332
2333 # if we will hide the root, make sure children are visible
2334 self._anchor.SetHasPlus()
2335 self._anchor.Expand()
2336 self.CalculatePositions()
2337
2338 # right now, just sets the styles. Eventually, we may
2339 # want to update the inherited styles, but right now
2340 # none of the parents has updatable styles
2341
2342 if self._windowStyle & TR_MULTIPLE and not (styles & TR_MULTIPLE):
2343 selections = self.GetSelections()
2344 for select in selections[0:-1]:
2345 self.SelectItem(select, False)
2346
2347 self._windowStyle = styles
2348 self._dirty = True
2349
2350
2351 def GetTreeStyle(self):
2352 """Returns the CustomTreeCtrl style."""
2353
2354 return self._windowStyle
2355
2356
2357 def HasButtons(self):
2358 """Returns whether CustomTreeCtrl has the TR_AHS_BUTTONS flag."""
2359
2360 return self.HasFlag(TR_HAS_BUTTONS)
2361
2362
2363# -----------------------------------------------------------------------------
2364# functions to work with tree items
2365# -----------------------------------------------------------------------------
2366
2367 def GetItemText(self, item):
2368 """Returns the item text."""
2369
2370 if not item:
e1463b9d 2371 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2372
2373 return item.GetText()
2374
2375
cbfc9df6 2376 def GetItemImage(self, item, which=TreeItemIcon_Normal):
c8f129d0
RD
2377 """Returns the item image."""
2378
2379 if not item:
e1463b9d 2380 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2381
2382 return item.GetImage(which)
2383
2384
2385 def GetPyData(self, item):
2386 """Returns the data associated to an item."""
2387
2388 if not item:
e1463b9d 2389 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2390
2391 return item.GetData()
2392
2393 GetItemPyData = GetPyData
2394
2395
2396 def GetItemTextColour(self, item):
2397 """Returns the item text colour."""
2398
2399 if not item:
e1463b9d 2400 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2401
2402 return item.Attr().GetTextColour()
2403
2404
2405 def GetItemBackgroundColour(self, item):
2406 """Returns the item background colour."""
2407
2408 if not item:
e1463b9d 2409 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2410
2411 return item.Attr().GetBackgroundColour()
2412
2413
2414 def GetItemFont(self, item):
2415 """Returns the item font."""
2416
2417 if not item:
e1463b9d 2418 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2419
2420 return item.Attr().GetFont()
2421
2422
2423 def IsItemHyperText(self, item):
2424 """Returns whether an item is hypertext or not."""
2425
2426 if not item:
e1463b9d 2427 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2428
2429 return item.IsHyperText()
2430
2431
2432 def SetItemText(self, item, text):
2433 """Sets the item text."""
2434
2435 if not item:
e1463b9d 2436 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2437
2438 dc = wx.ClientDC(self)
2439 item.SetText(text)
2440 self.CalculateSize(item, dc)
2441 self.RefreshLine(item)
2442
2443
2444 def SetItemImage(self, item, image, which=TreeItemIcon_Normal):
2445 """Sets the item image, depending on the item state."""
2446
2447 if not item:
e1463b9d 2448 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2449
2450 item.SetImage(image, which)
2451
2452 dc = wx.ClientDC(self)
2453 self.CalculateSize(item, dc)
2454 self.RefreshLine(item)
2455
2456
2457 def SetPyData(self, item, data):
2458 """Sets the data associated to an item."""
2459
2460 if not item:
e1463b9d 2461 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2462
2463 item.SetData(data)
2464
2465 SetItemPyData = SetPyData
2466
2467
2468 def SetItemHasChildren(self, item, has=True):
2469 """Forces the appearance of the button next to the item."""
2470
2471 if not item:
e1463b9d 2472 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2473
2474 item.SetHasPlus(has)
2475 self.RefreshLine(item)
2476
2477
2478 def SetItemBold(self, item, bold=True):
2479 """Sets the item font bold/unbold."""
2480
2481 if not item:
e1463b9d 2482 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2483
2484 # avoid redrawing the tree if no real change
2485 if item.IsBold() != bold:
2486 item.SetBold(bold)
2487 self._dirty = True
2488
2489
2490 def SetItemItalic(self, item, italic=True):
2491 """Sets the item font italic/non-italic."""
2492
2493 if not item:
e1463b9d 2494 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2495
2496 if item.IsItalic() != italic:
2497 itemFont = self.GetItemFont(item)
2498 if itemFont != wx.NullFont:
2499 style = wx.ITALIC
2500 if not italic:
2501 style = ~style
2502
2503 item.SetItalic(italic)
2504 itemFont.SetStyle(style)
2505 self.SetItemFont(item, itemFont)
2506 self._dirty = True
2507
2508
2509 def SetItemDropHighlight(self, item, highlight=True):
2510 """
2511 Gives the item the visual feedback for drag and drop operations.
2512 This is useful when something is dragged from outside the CustomTreeCtrl.
2513 """
2514
2515 if not item:
e1463b9d 2516 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2517
2518 if highlight:
2519 bg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)
2520 fg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
2521
2522 item.Attr().SetTextColour(fg)
2523 item.Attr.SetBackgroundColour(bg)
2524 self.RefreshLine(item)
2525
2526
2527 def SetItemTextColour(self, item, col):
2528 """Sets the item text colour."""
2529
2530 if not item:
e1463b9d 2531 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2532
2533 if self.GetItemTextColour(item) == col:
2534 return
2535
2536 item.Attr().SetTextColour(col)
2537 self.RefreshLine(item)
2538
2539
2540 def SetItemBackgroundColour(self, item, col):
2541 """Sets the item background colour."""
2542
2543 if not item:
e1463b9d 2544 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2545
2546 item.Attr().SetBackgroundColour(col)
2547 self.RefreshLine(item)
2548
2549
2550 def SetItemHyperText(self, item, hyper=True):
2551 """Sets whether the item is hypertext or not."""
2552
2553 if not item:
e1463b9d 2554 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2555
2556 item.SetHyperText(hyper)
2557 self.RefreshLine(item)
2558
2559
2560 def SetItemFont(self, item, font):
2561 """Sets the item font."""
2562
2563 if not item:
e1463b9d 2564 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2565
2566 if self.GetItemFont(item) == font:
2567 return
2568
2569 item.Attr().SetFont(font)
2570 self._dirty = True
2571
2572
2573 def SetFont(self, font):
2574 """Sets the CustomTreeCtrl font."""
2575
2576 wx.ScrolledWindow.SetFont(self, font)
2577
2578 self._normalFont = font
2579 self._boldFont = wx.Font(self._normalFont.GetPointSize(), self._normalFont.GetFamily(),
2580 self._normalFont.GetStyle(), wx.BOLD, self._normalFont.GetUnderlined(),
2581 self._normalFont.GetFaceName(), self._normalFont.GetEncoding())
2582
2583 return True
2584
2585
2586 def GetHyperTextFont(self):
2587 """Returns the font used to render an hypertext item."""
2588
2589 return self._hypertextfont
2590
2591
2592 def SetHyperTextFont(self, font):
2593 """Sets the font used to render an hypertext item."""
2594
2595 self._hypertextfont = font
2596 self._dirty = True
2597
2598
2599 def SetHyperTextNewColour(self, colour):
2600 """Sets the colour used to render a non-visited hypertext item."""
2601
2602 self._hypertextnewcolour = colour
2603 self._dirty = True
2604
2605
2606 def GetHyperTextNewColour(self):
2607 """Returns the colour used to render a non-visited hypertext item."""
2608
2609 return self._hypertextnewcolour
2610
2611
2612 def SetHyperTextVisitedColour(self, colour):
2613 """Sets the colour used to render a visited hypertext item."""
2614
2615 self._hypertextvisitedcolour = colour
2616 self._dirty = True
2617
2618
2619 def GetHyperTextVisitedColour(self):
2620 """Returns the colour used to render a visited hypertext item."""
2621
2622 return self._hypertextvisitedcolour
2623
2624
2625 def SetItemVisited(self, item, visited=True):
2626 """Sets whether an hypertext item was visited."""
2627
2628 if not item:
e1463b9d 2629 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2630
2631 item.SetVisited(visited)
2632 self.RefreshLine(item)
2633
2634
2635 def GetItemVisited(self, item):
2636 """Returns whether an hypertext item was visited."""
2637
2638 if not item:
e1463b9d 2639 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2640
2641 return item.GetVisited()
2642
2643
2644 def SetHilightFocusColour(self, colour):
2645 """
2646 Sets the colour used to highlight focused selected items.
2647 This is applied only if gradient and Windows Vista styles are disabled.
2648 """
2649
2650 self._hilightBrush = wx.Brush(colour)
2651 self.RefreshSelected()
2652
2653
2654 def SetHilightNonFocusColour(self, colour):
2655 """
2656 Sets the colour used to highlight unfocused selected items.
2657 This is applied only if gradient and Windows Vista styles are disabled.
2658 """
2659
2660 self._hilightUnfocusedBrush = wx.Brush(colour)
2661 self.RefreshSelected()
2662
2663
2664 def GetHilightFocusColour(self):
2665 """
2666 Returns the colour used to highlight focused selected items.
2667 This is applied only if gradient and Windows Vista styles are disabled.
2668 """
2669
2670 return self._hilightBrush.GetColour()
2671
2672
2673 def GetHilightNonFocusColour(self):
2674 """
2675 Returns the colour used to highlight unfocused selected items.
2676 This is applied only if gradient and Windows Vista styles are disabled.
2677 """
2678
2679 return self._hilightUnfocusedBrush.GetColour()
2680
2681
2682 def SetFirstGradientColour(self, colour=None):
2683 """Sets the first gradient colour."""
2684
2685 if colour is None:
2686 colour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT)
2687
2688 self._firstcolour = colour
2689 if self._usegradients:
2690 self.RefreshSelected()
2691
2692
2693 def SetSecondGradientColour(self, colour=None):
2694 """Sets the second gradient colour."""
2695
2696 if colour is None:
2697 # No colour given, generate a slightly darker from the
2698 # CustomTreeCtrl background colour
2699 color = self.GetBackgroundColour()
2700 r, g, b = int(color.Red()), int(color.Green()), int(color.Blue())
2701 color = ((r >> 1) + 20, (g >> 1) + 20, (b >> 1) + 20)
2702 colour = wx.Colour(color[0], color[1], color[2])
2703
2704 self._secondcolour = colour
2705
2706 if self._usegradients:
2707 self.RefreshSelected()
2708
2709
2710 def GetFirstGradientColour(self):
2711 """Returns the first gradient colour."""
2712
2713 return self._firstcolour
2714
2715
2716 def GetSecondGradientColour(self):
2717 """Returns the second gradient colour."""
2718
2719 return self._secondcolour
2720
2721
2722 def EnableSelectionGradient(self, enable=True):
2723 """Globally enables/disables drawing of gradient selection."""
2724
2725 self._usegradients = enable
2726 self._vistaselection = False
2727 self.RefreshSelected()
2728
2729
2730 def SetGradientStyle(self, vertical=0):
2731 """
2732 Sets the gradient style:
2733 0: horizontal gradient
2734 1: vertical gradient
2735 """
2736
2737 # 0 = Horizontal, 1 = Vertical
2738 self._gradientstyle = vertical
2739
2740 if self._usegradients:
2741 self.RefreshSelected()
2742
2743
2744 def GetGradientStyle(self):
2745 """
2746 Returns the gradient style:
2747 0: horizontal gradient
2748 1: vertical gradient
2749 """
2750
2751 return self._gradientstyle
2752
2753
2754 def EnableSelectionVista(self, enable=True):
2755 """Globally enables/disables drawing of Windows Vista selection."""
2756
2757 self._usegradients = False
2758 self._vistaselection = enable
2759 self.RefreshSelected()
2760
2761
2762 def SetBorderPen(self, pen):
2763 """
2764 Sets the pen used to draw the selected item border.
2765 The border pen is not used if the Windows Vista style is applied.
2766 """
2767
2768 self._borderPen = pen
2769 self.RefreshSelected()
2770
2771
2772 def GetBorderPen(self):
2773 """
2774 Returns the pen used to draw the selected item border.
2775 The border pen is not used if the Windows Vista style is applied.
2776 """
2777
2778 return self._borderPen
2779
2780
2781 def SetConnectionPen(self, pen):
2782 """Sets the pen used to draw the connecting lines between items."""
2783
2784 self._dottedPen = pen
2785 self._dirty = True
2786
2787
2788 def GetConnectionPen(self):
2789 """Returns the pen used to draw the connecting lines between items."""
2790
2791 return self._dottedPen
2792
2793
2794 def SetBackgroundImage(self, image):
2795 """Sets the CustomTreeCtrl background image (can be none)."""
2796
2797 self._backgroundImage = image
2798 self.Refresh()
2799
2800
2801 def GetBackgroundImage(self):
2802 """Returns the CustomTreeCtrl background image (can be none)."""
2803
2804 return self._backgroundImage
2805
2806
2807 def GetItemWindow(self, item):
2808 """Returns the window associated to the item (if any)."""
2809
2810 if not item:
e1463b9d 2811 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2812
2813 return item.GetWindow()
2814
2815
2816 def GetItemWindowEnabled(self, item):
2817 """Returns whether the window associated to the item is enabled."""
2818
2819 if not item:
e1463b9d 2820 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2821
2822 return item.GetWindowEnabled()
2823
2824
2825 def SetItemWindowEnabled(self, item, enable=True):
2826 """Enables/disables the window associated to the item."""
2827
2828 if not item:
e1463b9d 2829 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2830
2831 item.SetWindowEnabled(enable)
2832
2833
2834 def GetItemType(self, item):
2835 """
2836 Returns the item type:
2837 0: normal
2838 1: checkbox item
2839 2: radiobutton item
2840 """
2841
2842 if not item:
e1463b9d 2843 raise Exception("\nERROR: Invalid Item")
c8f129d0
RD
2844
2845 return item.GetType()
2846
2847# -----------------------------------------------------------------------------
2848# item status inquiries
2849# -----------------------------------------------------------------------------
2850
2851 def IsVisible(self, item):
2852 """Returns whether the item is visible or not."""
2853
2854 if not item:
e1463b9d 2855 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2856
2857 # An item is only visible if it's not a descendant of a collapsed item
2858 parent = item.GetParent()
2859
2860 while parent:
2861
2862 if not parent.IsExpanded():
2863 return False
2864
2865 parent = parent.GetParent()
2866
2867 startX, startY = self.GetViewStart()
2868 clientSize = self.GetClientSize()
2869
2870 rect = self.GetBoundingRect(item)
2871
2872 if not rect:
2873 return False
2874 if rect.GetWidth() == 0 or rect.GetHeight() == 0:
2875 return False
2876 if rect.GetBottom() < 0 or rect.GetTop() > clientSize.y:
2877 return False
2878 if rect.GetRight() < 0 or rect.GetLeft() > clientSize.x:
2879 return False
2880
2881 return True
2882
2883
2884 def ItemHasChildren(self, item):
2885 """Returns whether the item has children or not."""
2886
2887 if not item:
e1463b9d 2888 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2889
2890 # consider that the item does have children if it has the "+" button: it
2891 # might not have them (if it had never been expanded yet) but then it
2892 # could have them as well and it's better to err on this side rather than
2893 # disabling some operations which are restricted to the items with
2894 # children for an item which does have them
2895 return item.HasPlus()
2896
2897
2898 def IsExpanded(self, item):
2899 """Returns whether the item is expanded or not."""
2900
2901 if not item:
e1463b9d 2902 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2903
2904 return item.IsExpanded()
2905
2906
2907 def IsSelected(self, item):
2908 """Returns whether the item is selected or not."""
2909
2910 if not item:
e1463b9d 2911 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2912
2913 return item.IsSelected()
2914
2915
2916 def IsBold(self, item):
2917 """Returns whether the item font is bold or not."""
2918
2919 if not item:
e1463b9d 2920 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2921
2922 return item.IsBold()
2923
2924
2925 def IsItalic(self, item):
2926 """Returns whether the item font is italic or not."""
2927
2928 if not item:
e1463b9d 2929 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2930
2931 return item.IsItalic()
2932
2933
2934# -----------------------------------------------------------------------------
2935# navigation
2936# -----------------------------------------------------------------------------
2937
2938 def GetItemParent(self, item):
2939 """Gets the item parent."""
2940
2941 if not item:
e1463b9d 2942 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2943
2944 return item.GetParent()
2945
2946
2947 def GetFirstChild(self, item):
2948 """Gets the item first child."""
2949
2950 if not item:
e1463b9d 2951 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2952
2953 cookie = 0
2954 return self.GetNextChild(item, cookie)
2955
2956
2957 def GetNextChild(self, item, cookie):
2958 """
2959 Gets the item next child based on the 'cookie' parameter.
2960 This method has no sense if you do not call GetFirstChild() before.
2961 """
2962
2963 if not item:
e1463b9d 2964 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2965
2966 children = item.GetChildren()
2967
2968 # it's ok to cast cookie to size_t, we never have indices big enough to
2969 # overflow "void *"
2970
2971 if cookie < len(children):
2972
2973 return children[cookie], cookie+1
2974
2975 else:
2976
2977 # there are no more of them
2978 return None, cookie
2979
2980
2981 def GetLastChild(self, item):
2982 """Gets the item last child."""
2983
2984 if not item:
e1463b9d 2985 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2986
2987 children = item.GetChildren()
2988 return (len(children) == 0 and [None] or [children[-1]])[0]
2989
2990
2991 def GetNextSibling(self, item):
2992 """Gets the next sibling of an item."""
2993
2994 if not item:
e1463b9d 2995 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
2996
2997 i = item
2998 parent = i.GetParent()
2999
3000 if parent == None:
3001
3002 # root item doesn't have any siblings
3003 return None
3004
3005 siblings = parent.GetChildren()
3006 index = siblings.index(i)
3007
3008 n = index + 1
3009 return (n == len(siblings) and [None] or [siblings[n]])[0]
3010
3011
3012 def GetPrevSibling(self, item):
3013 """Gets the previous sibling of an item."""
3014
3015 if not item:
e1463b9d 3016 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3017
3018 i = item
3019 parent = i.GetParent()
3020
3021 if parent == None:
3022
3023 # root item doesn't have any siblings
3024 return None
3025
3026 siblings = parent.GetChildren()
3027 index = siblings.index(i)
3028
3029 return (index == 0 and [None] or [siblings[index-1]])[0]
3030
3031
3032 def GetNext(self, item):
3033 """Gets the next item. Only for internal use right now."""
3034
3035 if not item:
e1463b9d 3036 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3037
3038 i = item
3039
3040 # First see if there are any children.
3041 children = i.GetChildren()
3042 if len(children) > 0:
3043 return children[0]
3044 else:
3045 # Try a sibling of this or ancestor instead
3046 p = item
3047 toFind = None
3048 while p and not toFind:
3049 toFind = self.GetNextSibling(p)
3050 p = self.GetItemParent(p)
3051
3052 return toFind
3053
3054
3055 def GetFirstVisibleItem(self):
3056 """Returns the first visible item."""
3057
3058 id = self.GetRootItem()
3059 if not id:
3060 return id
3061
3062 while id:
3063 if self.IsVisible(id):
3064 return id
3065 id = self.GetNext(id)
3066
3067 return None
3068
3069
3070 def GetNextVisible(self, item):
3071 """Returns the next visible item."""
3072
3073 if not item:
e1463b9d 3074 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3075
3076 id = item
3077
3078 while id:
3079 id = self.GetNext(id)
3080 if id and self.IsVisible(id):
3081 return id
3082
3083 return None
3084
3085
3086 def GetPrevVisible(self, item):
3087
3088 if not item:
e1463b9d 3089 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0 3090
e1463b9d 3091 raise Exception("\nERROR: Not Implemented")
c8f129d0
RD
3092
3093 return None
3094
3095
3096 def ResetTextControl(self):
3097 """Called by TreeTextCtrl when it marks itself for deletion."""
3098
3099 self._textCtrl.Destroy()
3100 self._textCtrl = None
3101
3102
3103 def FindItem(self, idParent, prefixOrig):
3104 """Finds the first item starting with the given prefix after the given item."""
3105
3106 # match is case insensitive as this is more convenient to the user: having
3107 # to press Shift-letter to go to the item starting with a capital letter
3108 # would be too bothersome
3109 prefix = prefixOrig.lower()
3110
3111 # determine the starting point: we shouldn't take the current item (this
3112 # allows to switch between two items starting with the same letter just by
3113 # pressing it) but we shouldn't jump to the next one if the user is
3114 # continuing to type as otherwise he might easily skip the item he wanted
3115 id = idParent
3116
3117 if len(prefix) == 1:
3118 id = self.GetNext(id)
3119
3120 # look for the item starting with the given prefix after it
3121 while id and not self.GetItemText(id).lower().startswith(prefix):
3122
3123 id = self.GetNext(id)
3124
3125 # if we haven't found anything...
3126 if not id:
3127
3128 # ... wrap to the beginning
3129 id = self.GetRootItem()
3130 if self.HasFlag(TR_HIDE_ROOT):
3131 # can't select virtual root
3132 id = self.GetNext(id)
3133
3134 # and try all the items (stop when we get to the one we started from)
3135 while id != idParent and not self.GetItemText(id).lower().startswith(prefix):
3136 id = self.GetNext(id)
3137
3138 return id
3139
3140
3141# -----------------------------------------------------------------------------
3142# operations
3143# -----------------------------------------------------------------------------
3144
3145 def DoInsertItem(self, parentId, previous, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3146 """Actually inserts an item in the tree."""
3147
3148 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3149 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3150
3151 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3152 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3153
3154 if ct_type < 0 or ct_type > 2:
e1463b9d 3155 raise Exception("\nERROR: Item Type Should Be 0 (Normal), 1 (CheckBox) or 2 (RadioButton). ")
c8f129d0
RD
3156
3157 parent = parentId
3158
3159 if not parent:
3160
3161 # should we give a warning here?
3162 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3163
3164 self._dirty = True # do this first so stuff below doesn't cause flicker
3165
3166 item = GenericTreeItem(parent, text, ct_type, wnd, image, selImage, data)
3167
3168 if wnd is not None:
3169 self._hasWindows = True
3170 self._itemWithWindow.append(item)
3171
3172 parent.Insert(item, previous)
3173
3174 return item
3175
3176
3177 def AddRoot(self, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3178 """Adds a root to the CustomTreeCtrl. Only one root must exist."""
3179
3180 if self._anchor:
e1463b9d 3181 raise Exception("\nERROR: Tree Can Have Only One Root")
c8f129d0
RD
3182
3183 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3184 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3185
3186 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3187 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3188
3189 if ct_type < 0 or ct_type > 2:
e1463b9d 3190 raise Exception("\nERROR: Item Type Should Be 0 (Normal), 1 (CheckBox) or 2 (RadioButton). ")
c8f129d0
RD
3191
3192 self._dirty = True # do this first so stuff below doesn't cause flicker
3193
3194 self._anchor = GenericTreeItem(None, text, ct_type, wnd, image, selImage, data)
3195
3196 if wnd is not None:
3197 self._hasWindows = True
3198 self._itemWithWindow.append(self._anchor)
3199
3200 if self.HasFlag(TR_HIDE_ROOT):
3201
3202 # if root is hidden, make sure we can navigate
3203 # into children
3204 self._anchor.SetHasPlus()
3205 self._anchor.Expand()
3206 self.CalculatePositions()
3207
3208 if not self.HasFlag(TR_MULTIPLE):
3209
3210 self._current = self._key_current = self._anchor
3211 self._current.SetHilight(True)
3212
3213 return self._anchor
3214
3215
3216 def PrependItem(self, parent, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3217 """Appends an item as a first child of parent."""
3218
3219 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3220 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3221
3222 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3223 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3224
3225 return self.DoInsertItem(parent, 0, text, ct_type, wnd, image, selImage, data)
3226
3227
3228 def InsertItemByItem(self, parentId, idPrevious, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3229 """Auxiliary function to cope with the C++ hideous multifunction."""
3230
3231 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3232 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3233
3234 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3235 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3236
3237 parent = parentId
3238
3239 if not parent:
3240 # should we give a warning here?
3241 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3242
3243 index = -1
3244 if idPrevious:
3245
3246 try:
3247 index = parent.GetChildren().index(idPrevious)
3248 except:
e1463b9d 3249 raise Exception("ERROR: Previous Item In CustomTreeCtrl.InsertItem() Is Not A Sibling")
c8f129d0
RD
3250
3251 return self.DoInsertItem(parentId, index+1, text, ct_type, wnd, image, selImage, data)
3252
3253
3254 def InsertItemByIndex(self, parentId, before, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3255 """Auxiliary function to cope with the C++ hideous multifunction."""
3256
3257 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3258 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3259
3260 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3261 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3262
3263 parent = parentId
3264
3265 if not parent:
3266 # should we give a warning here?
3267 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3268
3269 return self.DoInsertItem(parentId, before, text, ct_type, wnd, image, selImage, data)
3270
3271
3272 def InsertItem(self, parentId, input, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3273 """Inserts an item after the given previous."""
3274
3275 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3276 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3277
3278 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3279 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3280
3281 if type(input) == type(1):
3282 return self.InsertItemByIndex(parentId, input, text, ct_type, wnd, image, selImage, data)
3283 else:
3284 return self.InsertItemByItem(parentId, input, text, ct_type, wnd, image, selImage, data)
3285
3286
3287 def AppendItem(self, parentId, text, ct_type=0, wnd=None, image=-1, selImage=-1, data=None):
3288 """Appends an item as a last child of its parent."""
3289
3290 if wnd is not None and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3291 raise Exception("\nERROR: In Order To Append/Insert Controls You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3292
3293 if text.find("\n") >= 0 and not (self._windowStyle & TR_HAS_VARIABLE_ROW_HEIGHT):
e1463b9d 3294 raise Exception("\nERROR: In Order To Append/Insert A MultiLine Text You Have To Use The Style TR_HAS_VARIABLE_ROW_HEIGHT")
c8f129d0
RD
3295
3296 parent = parentId
3297
3298 if not parent:
3299 # should we give a warning here?
3300 return self.AddRoot(text, ct_type, wnd, image, selImage, data)
3301
3302 return self.DoInsertItem(parent, len(parent.GetChildren()), text, ct_type, wnd, image, selImage, data)
3303
3304
3305 def SendDeleteEvent(self, item):
3306 """Actully sends the EVT_TREE_DELETE_ITEM event."""
3307
3308 event = TreeEvent(wxEVT_TREE_DELETE_ITEM, self.GetId())
3309 event._item = item
3310 event.SetEventObject(self)
e734e438 3311 self.GetEventHandler().ProcessEvent(event)
c8f129d0
RD
3312
3313
3314 def IsDescendantOf(self, parent, item):
3315 """Checks if the given item is under another one."""
3316
3317 while item:
3318
3319 if item == parent:
3320
3321 # item is a descendant of parent
3322 return True
3323
3324 item = item.GetParent()
3325
3326 return False
3327
3328
3329 # Don't leave edit or selection on a child which is about to disappear
3330 def ChildrenClosing(self, item):
3331 """We are about to destroy the item children."""
3332
3333 if self._textCtrl != None and item != self._textCtrl.item() and self.IsDescendantOf(item, self._textCtrl.item()):
3334 self._textCtrl.StopEditing()
3335
3336 if item != self._key_current and self.IsDescendantOf(item, self._key_current):
3337 self._key_current = None
3338
3339 if self.IsDescendantOf(item, self._select_me):
3340 self._select_me = item
3341
3342 if item != self._current and self.IsDescendantOf(item, self._current):
3343 self._current.SetHilight(False)
3344 self._current = None
3345 self._select_me = item
3346
3347
3348 def DeleteChildren(self, item):
3349 """Delete item children."""
3350
3351 if not item:
e1463b9d 3352 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3353
3354 self._dirty = True # do this first so stuff below doesn't cause flicker
3355
3356 self.ChildrenClosing(item)
3357 item.DeleteChildren(self)
3358
3359
3360 def Delete(self, item):
3361 """Delete an item."""
3362
3363 if not item:
e1463b9d 3364 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3365
3366 self._dirty = True # do this first so stuff below doesn't cause flicker
3367
3368 if self._textCtrl != None and self.IsDescendantOf(item, self._textCtrl.item()):
3369 # can't delete the item being edited, cancel editing it first
3370 self._textCtrl.StopEditing()
3371
3372 parent = item.GetParent()
3373
3374 # don't keep stale pointers around!
3375 if self.IsDescendantOf(item, self._key_current):
3376
3377 # Don't silently change the selection:
3378 # do it properly in idle time, so event
3379 # handlers get called.
3380
3381 # self._key_current = parent
3382 self._key_current = None
3383
3384 # self._select_me records whether we need to select
3385 # a different item, in idle time.
3386 if self._select_me and self.IsDescendantOf(item, self._select_me):
3387 self._select_me = parent
3388
3389 if self.IsDescendantOf(item, self._current):
3390
3391 # Don't silently change the selection:
3392 # do it properly in idle time, so event
3393 # handlers get called.
3394
3395 # self._current = parent
3396 self._current = None
3397 self._select_me = parent
3398
3399 # remove the item from the tree
3400 if parent:
3401
3402 parent.GetChildren().remove(item) # remove by value
3403
3404 else: # deleting the root
3405
3406 # nothing will be left in the tree
3407 self._anchor = None
3408
3409 # and delete all of its children and the item itself now
3410 item.DeleteChildren(self)
3411 self.SendDeleteEvent(item)
3412
3413 if item == self._select_me:
3414 self._select_me = None
3415
3416 # Remove the item with window
3417 if item in self._itemWithWindow:
3418 wnd = item.GetWindow()
3419 wnd.Hide()
3420 wnd.Destroy()
3421 item._wnd = None
3422 self._itemWithWindow.remove(item)
3423
3424 del item
3425
3426
3427 def DeleteAllItems(self):
3428 """Delete all items in the CustomTreeCtrl."""
3429
3430 if self._anchor:
3431 self.Delete(self._anchor)
3432
3433
3434 def Expand(self, item):
3435 """
3436 Expands an item, sending a EVT_TREE_ITEM_EXPANDING and
3437 EVT_TREE_ITEM_EXPANDED events.
3438 """
3439
3440 if not item:
e1463b9d 3441 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3442
3443 if self.HasFlag(TR_HIDE_ROOT) and item == self.GetRootItem():
e1463b9d 3444 raise Exception("\nERROR: Can't Expand An Hidden Root. ")
c8f129d0
RD
3445
3446 if not item.HasPlus():
3447 return
3448
3449 if item.IsExpanded():
3450 return
3451
3452 event = TreeEvent(wxEVT_TREE_ITEM_EXPANDING, self.GetId())
3453 event._item = item
3454 event.SetEventObject(self)
3455
e734e438 3456 if self.GetEventHandler().ProcessEvent(event) and not event.IsAllowed():
c8f129d0
RD
3457 # cancelled by program
3458 return
3459
3460 item.Expand()
3461 self.CalculatePositions()
3462
3463 self.RefreshSubtree(item)
3464
3465 if self._hasWindows:
3466 # We hide the associated window here, we may show it after
3467 self.HideWindows()
3468
3469 event.SetEventType(wxEVT_TREE_ITEM_EXPANDED)
e734e438 3470 self.GetEventHandler().ProcessEvent(event)
c8f129d0
RD
3471
3472
cbfc9df6
RD
3473 def ExpandAllChildren(self, item):
3474 """Expands all the items children of the input item."""
c8f129d0
RD
3475
3476 if not item:
e1463b9d 3477 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0 3478
2356118e 3479 if not self.HasFlag(TR_HIDE_ROOT) or item != self.GetRootItem():
c8f129d0
RD
3480 self.Expand(item)
3481 if not self.IsExpanded(item):
3482 return
3483
3484 child, cookie = self.GetFirstChild(item)
3485
3486 while child:
cbfc9df6 3487 self.ExpandAllChildren(child)
c8f129d0
RD
3488 child, cookie = self.GetNextChild(item, cookie)
3489
3490
cbfc9df6
RD
3491 def ExpandAll(self):
3492 """Expands all CustomTreeCtrl items."""
3493
3494 if self._anchor:
3495 self.ExpandAllChildren(self._anchor)
3496
3497
c8f129d0
RD
3498 def Collapse(self, item):
3499 """
3500 Collapse an item, sending a EVT_TREE_ITEM_COLLAPSING and
3501 EVT_TREE_ITEM_COLLAPSED events.
3502 """
3503
3504 if not item:
e1463b9d 3505 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3506
3507 if self.HasFlag(TR_HIDE_ROOT) and item == self.GetRootItem():
e1463b9d 3508 raise Exception("\nERROR: Can't Collapse An Hidden Root. ")
c8f129d0
RD
3509
3510 if not item.IsExpanded():
3511 return
3512
3513 event = TreeEvent(wxEVT_TREE_ITEM_COLLAPSING, self.GetId())
3514 event._item = item
3515 event.SetEventObject(self)
e734e438 3516 if self.GetEventHandler().ProcessEvent(event) and not event.IsAllowed():
c8f129d0
RD
3517 # cancelled by program
3518 return
3519
3520 self.ChildrenClosing(item)
3521 item.Collapse()
3522
3523 self.CalculatePositions()
3524 self.RefreshSubtree(item)
3525
3526 if self._hasWindows:
3527 self.HideWindows()
3528
3529 event.SetEventType(wxEVT_TREE_ITEM_COLLAPSED)
e734e438 3530 self.GetEventHandler().ProcessEvent(event)
c8f129d0
RD
3531
3532
3533 def CollapseAndReset(self, item):
3534 """Collapse the given item and deletes its children."""
3535
3536 self.Collapse(item)
3537 self.DeleteChildren(item)
3538
3539
3540 def Toggle(self, item):
3541 """Toggles the item state (collapsed/expanded)."""
3542
3543 if item.IsExpanded():
3544 self.Collapse(item)
3545 else:
3546 self.Expand(item)
3547
3548
3549 def HideWindows(self):
3550 """Hides the windows associated to the items. Used internally."""
3551
3552 for child in self._itemWithWindow:
3553 if not self.IsVisible(child):
3554 wnd = child.GetWindow()
3555 wnd.Hide()
3556
3557
3558 def Unselect(self):
3559 """Unselects the current selection."""
3560
3561 if self._current:
3562
3563 self._current.SetHilight(False)
3564 self.RefreshLine(self._current)
3565
3566 self._current = None
3567 self._select_me = None
3568
3569
3570 def UnselectAllChildren(self, item):
3571 """Unselects all the children of the given item."""
3572
3573 if item.IsSelected():
3574
3575 item.SetHilight(False)
3576 self.RefreshLine(item)
3577
3578 if item.HasChildren():
3579 for child in item.GetChildren():
3580 self.UnselectAllChildren(child)
3581
3582
3583 def UnselectAll(self):
3584 """Unselect all the items."""
3585
3586 rootItem = self.GetRootItem()
3587
3588 # the tree might not have the root item at all
3589 if rootItem:
3590 self.UnselectAllChildren(rootItem)
cbfc9df6
RD
3591
3592 self.Unselect()
c8f129d0
RD
3593
3594 # Recursive function !
3595 # To stop we must have crt_item<last_item
3596 # Algorithm :
3597 # Tag all next children, when no more children,
3598 # Move to parent (not to tag)
3599 # Keep going... if we found last_item, we stop.
3600
3601 def TagNextChildren(self, crt_item, last_item, select):
3602 """Used internally."""
3603
3604 parent = crt_item.GetParent()
3605
3606 if parent == None: # This is root item
3607 return self.TagAllChildrenUntilLast(crt_item, last_item, select)
3608
3609 children = parent.GetChildren()
3610 index = children.index(crt_item)
3611
3612 count = len(children)
3613
3614 for n in xrange(index+1, count):
3615 if self.TagAllChildrenUntilLast(children[n], last_item, select):
3616 return True
3617
3618 return self.TagNextChildren(parent, last_item, select)
3619
3620
3621 def TagAllChildrenUntilLast(self, crt_item, last_item, select):
3622 """Used internally."""
3623
3624 crt_item.SetHilight(select)
3625 self.RefreshLine(crt_item)
3626
3627 if crt_item == last_item:
3628 return True
3629
3630 if crt_item.HasChildren():
3631 for child in crt_item.GetChildren():
3632 if self.TagAllChildrenUntilLast(child, last_item, select):
3633 return True
3634
3635 return False
3636
3637
3638 def SelectItemRange(self, item1, item2):
3639 """Selects all the items between item1 and item2."""
3640
3641 self._select_me = None
3642
3643 # item2 is not necessary after item1
3644 # choice first' and 'last' between item1 and item2
3645 first = (item1.GetY() < item2.GetY() and [item1] or [item2])[0]
3646 last = (item1.GetY() < item2.GetY() and [item2] or [item1])[0]
3647
3648 select = self._current.IsSelected()
3649
3650 if self.TagAllChildrenUntilLast(first, last, select):
3651 return
3652
3653 self.TagNextChildren(first, last, select)
3654
3655
3656 def DoSelectItem(self, item, unselect_others=True, extended_select=False):
3657 """Actually selects/unselects an item, sending a EVT_TREE_SEL_CHANGED event."""
3658
3659 if not item:
e1463b9d 3660 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3661
3662 self._select_me = None
3663
3664 is_single = not (self.GetTreeStyle() & TR_MULTIPLE)
3665
3666 # to keep going anyhow !!!
3667 if is_single:
3668 if item.IsSelected():
3669 return # nothing to do
3670 unselect_others = True
3671 extended_select = False
3672
3673 elif unselect_others and item.IsSelected():
3674
3675 # selection change if there is more than one item currently selected
3676 if len(self.GetSelections()) == 1:
3677 return
3678
3679 event = TreeEvent(wxEVT_TREE_SEL_CHANGING, self.GetId())
3680 event._item = item
3681 event._itemOld = self._current
3682 event.SetEventObject(self)
3683 # TODO : Here we don't send any selection mode yet !
3684
3685 if self.GetEventHandler().ProcessEvent(event) and not event.IsAllowed():
3686 return
3687
3688 parent = self.GetItemParent(item)
3689 while parent:
3690 if not self.IsExpanded(parent):
3691 self.Expand(parent)
3692
3693 parent = self.GetItemParent(parent)
3694
3695 # ctrl press
3696 if unselect_others:
3697 if is_single:
3698 self.Unselect() # to speed up thing
3699 else:
3700 self.UnselectAll()
3701
3702 # shift press
3703 if extended_select:
3704 if not self._current:
3705 self._current = self._key_current = self.GetRootItem()
3706
3707 # don't change the mark (self._current)
3708 self.SelectItemRange(self._current, item)
3709
3710 else:
3711
3712 select = True # the default
3713
3714 # Check if we need to toggle hilight (ctrl mode)
3715 if not unselect_others:
3716 select = not item.IsSelected()
3717
3718 self._current = self._key_current = item
3719 self._current.SetHilight(select)
3720 self.RefreshLine(self._current)
3721
3722 # This can cause idle processing to select the root
3723 # if no item is selected, so it must be after the
3724 # selection is set
3725 self.EnsureVisible(item)
3726
3727 event.SetEventType(wxEVT_TREE_SEL_CHANGED)
3728 self.GetEventHandler().ProcessEvent(event)
3729
3730 # Handles hypertext items
3731 if self.IsItemHyperText(item):
3732 event = TreeEvent(wxEVT_TREE_ITEM_HYPERLINK, self.GetId())
3733 event._item = item
3734 self.GetEventHandler().ProcessEvent(event)
3735
3736
3737 def SelectItem(self, item, select=True):
3738 """Selects/deselects an item."""
3739
3740 if not item:
e1463b9d 3741 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3742
3743 if select:
3744
3745 self.DoSelectItem(item, not self.HasFlag(TR_MULTIPLE))
3746
3747 else: # deselect
3748
3749 item.SetHilight(False)
3750 self.RefreshLine(item)
3751
3752
3753 def FillArray(self, item, array=[]):
3754 """
3755 Internal function. Used to populate an array of selected items when
3756 the style TR_MULTIPLE is used.
3757 """
3758
3759 if not array:
3760 array = []
3761
3762 if item.IsSelected():
3763 array.append(item)
3764
e1463b9d 3765 if item.HasChildren() and item.IsExpanded():
c8f129d0
RD
3766 for child in item.GetChildren():
3767 array = self.FillArray(child, array)
3768
3769 return array
3770
3771
3772 def GetSelections(self):
3773 """
3774 Returns a list of selected items. This can be used only if CustomTreeCtrl has
3775 the TR_MULTIPLE style set.
3776 """
3777
3778 array = []
3779 idRoot = self.GetRootItem()
3780 if idRoot:
3781 array = self.FillArray(idRoot, array)
3782
3783 #else: the tree is empty, so no selections
3784
3785 return array
3786
3787
3788 def EnsureVisible(self, item):
3789 """Ensure that an item is visible in CustomTreeCtrl."""
3790
3791 if not item:
e1463b9d 3792 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3793
3794 # first expand all parent branches
3795 parent = item.GetParent()
3796
3797 if self.HasFlag(TR_HIDE_ROOT):
3798 while parent and parent != self._anchor:
3799 self.Expand(parent)
3800 parent = parent.GetParent()
3801 else:
3802 while parent:
3803 self.Expand(parent)
3804 parent = parent.GetParent()
3805
3806 self.ScrollTo(item)
3807
3808
3809 def ScrollTo(self, item):
3810 """Scrolls the specified item into view."""
3811
3812 if not item:
3813 return
3814
3815 # We have to call this here because the label in
3816 # question might just have been added and no screen
3817 # update taken place.
3818 if self._dirty:
3819 if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
3820 self.Update()
3821 else:
3822 wx.YieldIfNeeded()
3823
3824 # now scroll to the item
3825 item_y = item.GetY()
3826 start_x, start_y = self.GetViewStart()
3827 start_y *= _PIXELS_PER_UNIT
3828
3829 client_w, client_h = self.GetClientSize()
3830
3831 x, y = 0, 0
3832
3833 if item_y < start_y+3:
3834
3835 # going down
3836 x, y = self._anchor.GetSize(x, y, self)
3837 y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3838 x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3839 x_pos = self.GetScrollPos(wx.HORIZONTAL)
3840 # Item should appear at top
3841 self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, item_y/_PIXELS_PER_UNIT)
3842
3843 elif item_y+self.GetLineHeight(item) > start_y+client_h:
3844
3845 # going up
3846 x, y = self._anchor.GetSize(x, y, self)
3847 y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3848 x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
3849 item_y += _PIXELS_PER_UNIT+2
3850 x_pos = self.GetScrollPos(wx.HORIZONTAL)
3851 # Item should appear at bottom
3852 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 )
3853
3854
3855 def OnCompareItems(self, item1, item2):
3856 """
3857 Returns whether 2 items have the same text.
3858 Override this function in the derived class to change the sort order of the items
3859 in the CustomTreeCtrl. The function should return a negative, zero or positive
3860 value if the first item is less than, equal to or greater than the second one.
3861
3862 The base class version compares items alphabetically.
3863 """
3864
3865 return self.GetItemText(item1) == self.GetItemText(item2)
3866
3867
3868 def SortChildren(self, item):
3869 """
3870 Sorts the children of the given item using OnCompareItems method of CustomTreeCtrl.
3871 You should override that method to change the sort order (the default is ascending
3872 case-sensitive alphabetical order).
3873 """
3874
3875 if not item:
e1463b9d 3876 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
3877
3878 children = item.GetChildren()
3879
3880 if len(children) > 1:
3881 self._dirty = True
3882 children.sort(self.OnCompareItems)
3883
3884
3885 def GetImageList(self):
3886 """Returns the normal image list."""
3887
3888 return self._imageListNormal
3889
3890
3891 def GetButtonsImageList(self):
3892 """Returns the buttons image list (from which application-defined button images are taken)."""
3893
3894 return self._imageListButtons
3895
3896
3897 def GetStateImageList(self):
3898 """Returns the state image list (from which application-defined state images are taken)."""
3899
3900 return self._imageListState
3901
3902
3903 def GetImageListCheck(self):
3904 """Returns the image list used to build the check/radio buttons."""
3905
3906 return self._imageListCheck
3907
3908
3909 def CalculateLineHeight(self):
3910 """Calculates the height of a line."""
3911
3912 dc = wx.ClientDC(self)
3913 self._lineHeight = dc.GetCharHeight()
3914
3915 if self._imageListNormal:
3916
3917 # Calculate a self._lineHeight value from the normal Image sizes.
3918 # May be toggle off. Then CustomTreeCtrl will spread when
3919 # necessary (which might look ugly).
3920 n = self._imageListNormal.GetImageCount()
3921
3922 for i in xrange(n):
3923
3924 width, height = self._imageListNormal.GetSize(i)
3925
3926 if height > self._lineHeight:
3927 self._lineHeight = height
3928
3929 if self._imageListButtons:
3930
3931 # Calculate a self._lineHeight value from the Button image sizes.
3932 # May be toggle off. Then CustomTreeCtrl will spread when
3933 # necessary (which might look ugly).
3934 n = self._imageListButtons.GetImageCount()
3935
3936 for i in xrange(n):
3937
3938 width, height = self._imageListButtons.GetSize(i)
3939
3940 if height > self._lineHeight:
3941 self._lineHeight = height
3942
3943 if self._imageListCheck:
3944
3945 # Calculate a self._lineHeight value from the check/radio image sizes.
3946 # May be toggle off. Then CustomTreeCtrl will spread when
3947 # necessary (which might look ugly).
3948 n = self._imageListCheck.GetImageCount()
3949
3950 for i in xrange(n):
3951
3952 width, height = self._imageListCheck.GetSize(i)
3953
3954 if height > self._lineHeight:
3955 self._lineHeight = height
3956
3957 if self._lineHeight < 30:
3958 self._lineHeight += 2 # at least 2 pixels
3959 else:
3960 self._lineHeight += self._lineHeight/10 # otherwise 10% extra spacing
3961
3962
3963 def SetImageList(self, imageList):
3964 """Sets the normal image list."""
3965
3966 if self._ownsImageListNormal:
3967 del self._imageListNormal
3968
3969 self._imageListNormal = imageList
3970 self._ownsImageListNormal = False
3971 self._dirty = True
cbfc9df6 3972
c8f129d0
RD
3973 # Don't do any drawing if we're setting the list to NULL,
3974 # since we may be in the process of deleting the tree control.
3975 if imageList:
3976 self.CalculateLineHeight()
3977
cbfc9df6
RD
3978 # We gray out the image list to use the grayed icons with disabled items
3979 sz = imageList.GetSize(0)
3980 self._grayedImageList = wx.ImageList(sz[0], sz[1], True, 0)
3981
3982 for ii in xrange(imageList.GetImageCount()):
3983 bmp = imageList.GetBitmap(ii)
3984 image = wx.ImageFromBitmap(bmp)
3985 image = GrayOut(image)
3986 newbmp = wx.BitmapFromImage(image)
3987 self._grayedImageList.Add(newbmp)
c8f129d0
RD
3988
3989
3990 def SetStateImageList(self, imageList):
3991 """Sets the state image list (from which application-defined state images are taken)."""
3992
3993 if self._ownsImageListState:
3994 del self._imageListState
3995
3996 self._imageListState = imageList
3997 self._ownsImageListState = False
3998
3999
4000 def SetButtonsImageList(self, imageList):
4001 """Sets the buttons image list (from which application-defined button images are taken)."""
4002
4003 if self._ownsImageListButtons:
4004 del self._imageListButtons
4005
4006 self._imageListButtons = imageList
4007 self._ownsImageListButtons = False
4008 self._dirty = True
4009 self.CalculateLineHeight()
4010
4011
4012 def SetImageListCheck(self, sizex, sizey, imglist=None):
4013 """Sets the check image list."""
4014
4015 if imglist is None:
4016
4017 self._imageListCheck = wx.ImageList(sizex, sizey)
4018 self._imageListCheck.Add(GetCheckedBitmap())
4019 self._imageListCheck.Add(GetNotCheckedBitmap())
4020 self._imageListCheck.Add(GetFlaggedBitmap())
4021 self._imageListCheck.Add(GetNotFlaggedBitmap())
4022
4023 else:
4024
4025 sizex, sizey = imglist.GetSize(0)
4026 self._imageListCheck = imglist
4027
4028 # We gray out the image list to use the grayed icons with disabled items
4029 self._grayedCheckList = wx.ImageList(sizex, sizey, True, 0)
4030
4031 for ii in xrange(self._imageListCheck.GetImageCount()):
4032
4033 bmp = self._imageListCheck.GetBitmap(ii)
4034 image = wx.ImageFromBitmap(bmp)
4035 image = GrayOut(image)
4036 newbmp = wx.BitmapFromImage(image)
4037 self._grayedCheckList.Add(newbmp)
4038
4039 self._dirty = True
4040
4041 if imglist:
4042 self.CalculateLineHeight()
4043
4044
4045 def AssignImageList(self, imageList):
4046 """Assigns the normal image list."""
4047
4048 self.SetImageList(imageList)
4049 self._ownsImageListNormal = True
4050
4051
4052 def AssignStateImageList(self, imageList):
4053 """Assigns the state image list."""
4054
4055 self.SetStateImageList(imageList)
4056 self._ownsImageListState = True
4057
4058
4059 def AssignButtonsImageList(self, imageList):
4060 """Assigns the button image list."""
4061
4062 self.SetButtonsImageList(imageList)
4063 self._ownsImageListButtons = True
4064
4065
4066# -----------------------------------------------------------------------------
4067# helpers
4068# -----------------------------------------------------------------------------
4069
4070 def AdjustMyScrollbars(self):
4071 """Adjust the wx.ScrolledWindow scrollbars."""
4072
4073 if self._anchor:
4074
4075 x, y = self._anchor.GetSize(0, 0, self)
4076 y += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
4077 x += _PIXELS_PER_UNIT + 2 # one more scrollbar unit + 2 pixels
4078 x_pos = self.GetScrollPos(wx.HORIZONTAL)
4079 y_pos = self.GetScrollPos(wx.VERTICAL)
4080 self.SetScrollbars(_PIXELS_PER_UNIT, _PIXELS_PER_UNIT, x/_PIXELS_PER_UNIT, y/_PIXELS_PER_UNIT, x_pos, y_pos)
4081
4082 else:
4083
4084 self.SetScrollbars(0, 0, 0, 0)
4085
4086
4087 def GetLineHeight(self, item):
4088 """Returns the line height for the given item."""
4089
4090 if self.GetTreeStyle() & TR_HAS_VARIABLE_ROW_HEIGHT:
4091 return item.GetHeight()
4092 else:
4093 return self._lineHeight
4094
4095
4096 def DrawVerticalGradient(self, dc, rect, hasfocus):
4097 """Gradient fill from colour 1 to colour 2 from top to bottom."""
4098
94431133
RD
4099 oldpen = dc.GetPen()
4100 oldbrush = dc.GetBrush()
c8f129d0
RD
4101 dc.SetPen(wx.TRANSPARENT_PEN)
4102
4103 # calculate gradient coefficients
4104 if hasfocus:
4105 col2 = self._secondcolour
4106 col1 = self._firstcolour
4107 else:
4108 col2 = self._hilightUnfocusedBrush.GetColour()
4109 col1 = self._hilightUnfocusedBrush2.GetColour()
4110
4111 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
4112 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())
4113
4114 flrect = float(rect.height)
4115
4116 rstep = float((r2 - r1)) / flrect
4117 gstep = float((g2 - g1)) / flrect
4118 bstep = float((b2 - b1)) / flrect
4119
4120 rf, gf, bf = 0, 0, 0
4121
94431133 4122 for y in xrange(rect.y, rect.y + rect.height):
c8f129d0
RD
4123 currCol = (r1 + rf, g1 + gf, b1 + bf)
4124 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
94431133 4125 dc.DrawRectangle(rect.x, y, rect.width, 1)
c8f129d0
RD
4126 rf = rf + rstep
4127 gf = gf + gstep
4128 bf = bf + bstep
4129
94431133
RD
4130 dc.SetPen(oldpen)
4131 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4132 dc.DrawRectangleRect(rect)
4133 dc.SetBrush(oldbrush)
4134
c8f129d0
RD
4135
4136 def DrawHorizontalGradient(self, dc, rect, hasfocus):
4137 """Gradient fill from colour 1 to colour 2 from left to right."""
4138
94431133
RD
4139 oldpen = dc.GetPen()
4140 oldbrush = dc.GetBrush()
c8f129d0
RD
4141 dc.SetPen(wx.TRANSPARENT_PEN)
4142
4143 # calculate gradient coefficients
4144
4145 if hasfocus:
4146 col2 = self._secondcolour
4147 col1 = self._firstcolour
4148 else:
4149 col2 = self._hilightUnfocusedBrush.GetColour()
4150 col1 = self._hilightUnfocusedBrush2.GetColour()
4151
4152 r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
4153 r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())
4154
4155 flrect = float(rect.width)
4156
4157 rstep = float((r2 - r1)) / flrect
4158 gstep = float((g2 - g1)) / flrect
4159 bstep = float((b2 - b1)) / flrect
4160
4161 rf, gf, bf = 0, 0, 0
94431133
RD
4162
4163 for x in xrange(rect.x, rect.x + rect.width):
c8f129d0
RD
4164 currCol = (int(r1 + rf), int(g1 + gf), int(b1 + bf))
4165 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
94431133 4166 dc.DrawRectangle(x, rect.y, 1, rect.height)
c8f129d0
RD
4167 rf = rf + rstep
4168 gf = gf + gstep
4169 bf = bf + bstep
94431133
RD
4170
4171 dc.SetPen(oldpen)
4172 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4173 dc.DrawRectangleRect(rect)
4174 dc.SetBrush(oldbrush)
4175
c8f129d0
RD
4176
4177 def DrawVistaRectangle(self, dc, rect, hasfocus):
4178 """Draw the selected item(s) with the Windows Vista style."""
4179
4180 if hasfocus:
4181
4182 outer = _rgbSelectOuter
4183 inner = _rgbSelectInner
4184 top = _rgbSelectTop
4185 bottom = _rgbSelectBottom
4186
4187 else:
4188
4189 outer = _rgbNoFocusOuter
4190 inner = _rgbNoFocusInner
4191 top = _rgbNoFocusTop
4192 bottom = _rgbNoFocusBottom
4193
4194 oldpen = dc.GetPen()
4195 oldbrush = dc.GetBrush()
4196
94431133
RD
4197 bdrRect = wx.Rect(*rect.Get())
4198 filRect = wx.Rect(*rect.Get())
4199 filRect.Deflate(1,1)
4200
c8f129d0
RD
4201 r1, g1, b1 = int(top.Red()), int(top.Green()), int(top.Blue())
4202 r2, g2, b2 = int(bottom.Red()), int(bottom.Green()), int(bottom.Blue())
4203
94431133 4204 flrect = float(filRect.height)
c8f129d0
RD
4205
4206 rstep = float((r2 - r1)) / flrect
4207 gstep = float((g2 - g1)) / flrect
4208 bstep = float((b2 - b1)) / flrect
4209
4210 rf, gf, bf = 0, 0, 0
4211 dc.SetPen(wx.TRANSPARENT_PEN)
4212
94431133 4213 for y in xrange(filRect.y, filRect.y + filRect.height):
c8f129d0
RD
4214 currCol = (r1 + rf, g1 + gf, b1 + bf)
4215 dc.SetBrush(wx.Brush(currCol, wx.SOLID))
94431133 4216 dc.DrawRectangle(filRect.x, y, filRect.width, 1)
c8f129d0
RD
4217 rf = rf + rstep
4218 gf = gf + gstep
4219 bf = bf + bstep
4220
94431133
RD
4221 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4222 dc.SetPen(wx.Pen(outer))
4223 dc.DrawRoundedRectangleRect(bdrRect, 3)
4224 bdrRect.Deflate(1, 1)
4225 dc.SetPen(wx.Pen(inner))
4226 dc.DrawRoundedRectangleRect(bdrRect, 2)
4227
c8f129d0
RD
4228 dc.SetPen(oldpen)
4229 dc.SetBrush(oldbrush)
4230
4231
4232 def PaintItem(self, item, dc):
4233 """Actually paint an item."""
4234
4235 attr = item.GetAttributes()
4236
4237 if attr and attr.HasFont():
4238 dc.SetFont(attr.GetFont())
4239 elif item.IsBold():
4240 dc.SetFont(self._boldFont)
4241 if item.IsHyperText():
4242 dc.SetFont(self.GetHyperTextFont())
4243 if item.GetVisited():
4244 dc.SetTextForeground(self.GetHyperTextVisitedColour())
4245 else:
4246 dc.SetTextForeground(self.GetHyperTextNewColour())
4247
4248 text_w, text_h, dummy = dc.GetMultiLineTextExtent(item.GetText())
4249
4250 image = item.GetCurrentImage()
4251 checkimage = item.GetCurrentCheckedImage()
4252 image_w, image_h = 0, 0
4253
4254 if image != _NO_IMAGE:
4255
4256 if self._imageListNormal:
4257
4258 image_w, image_h = self._imageListNormal.GetSize(image)
4259 image_w += 4
4260
4261 else:
4262
4263 image = _NO_IMAGE
4264
4265 if item.GetType() != 0:
4266 wcheck, hcheck = self._imageListCheck.GetSize(item.GetType())
4267 wcheck += 4
4268 else:
4269 wcheck, hcheck = 0, 0
4270
4271 total_h = self.GetLineHeight(item)
4272 drawItemBackground = False
94431133 4273
c8f129d0
RD
4274 if item.IsSelected():
4275
4276 # under mac selections are only a rectangle in case they don't have the focus
4277 if wx.Platform == "__WXMAC__":
4278 if not self._hasFocus:
4279 dc.SetBrush(wx.TRANSPARENT_BRUSH)
4280 dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHT), 1, wx.SOLID))
4281 else:
4282 dc.SetBrush(self._hilightBrush)
4283 else:
4284 dc.SetBrush((self._hasFocus and [self._hilightBrush] or [self._hilightUnfocusedBrush])[0])
4285 drawItemBackground = True
4286 else:
4287 if attr and attr.HasBackgroundColour():
4288 drawItemBackground = True
4289 colBg = attr.GetBackgroundColour()
4290 else:
4291 colBg = self._backgroundColour
4292
4293 dc.SetBrush(wx.Brush(colBg, wx.SOLID))
4294 dc.SetPen(wx.TRANSPARENT_PEN)
4295
4296 offset = (self.HasFlag(TR_ROW_LINES) and [1] or [0])[0]
94431133 4297
c8f129d0 4298 if self.HasFlag(TR_FULL_ROW_HIGHLIGHT):
369be443 4299 x = 0
94431133 4300 w, h = self.GetClientSize()
c8f129d0
RD
4301
4302 itemrect = wx.Rect(x, item.GetY()+offset, w, total_h-offset)
4303
4304 if item.IsSelected():
4305 if self._usegradients:
4306 if self._gradientstyle == 0: # Horizontal
4307 self.DrawHorizontalGradient(dc, itemrect, self._hasFocus)
4308 else: # Vertical
4309 self.DrawVerticalGradient(dc, itemrect, self._hasFocus)
4310 elif self._vistaselection:
4311 self.DrawVistaRectangle(dc, itemrect, self._hasFocus)
4312 else:
8bc0c17a
KO
4313 if wx.Platform in ["__WXGTK2__", "__WXMAC__"]:
4314 flags = wx.CONTROL_SELECTED
4315 if self._hasFocus: flags = flags | wx.CONTROL_FOCUSED
4316 wx.RendererNative.Get().DrawItemSelectionRect(self, dc, itemrect, flags)
4317 else:
4318 dc.DrawRectangleRect(itemrect)
c8f129d0 4319
c8f129d0 4320 else:
6e65f80b
RD
4321
4322 if item.IsSelected():
c8f129d0
RD
4323
4324 # If it's selected, and there's an image, then we should
4325 # take care to leave the area under the image painted in the
4326 # background colour.
4327
4328 wnd = item.GetWindow()
4329 wndx = 0
4330 if wnd:
4331 wndx, wndy = item.GetWindowSize()
4332
94431133
RD
4333 itemrect = wx.Rect(item.GetX() + wcheck + image_w - 2,
4334 item.GetY()+offset,
4335 item.GetWidth() - image_w - wcheck + 2 - wndx,
4336 total_h-offset)
4337
c8f129d0
RD
4338 if self._usegradients:
4339 if self._gradientstyle == 0: # Horizontal
4340 self.DrawHorizontalGradient(dc, itemrect, self._hasFocus)
4341 else: # Vertical
4342 self.DrawVerticalGradient(dc, itemrect, self._hasFocus)
4343 elif self._vistaselection:
4344 self.DrawVistaRectangle(dc, itemrect, self._hasFocus)
4345 else:
8bc0c17a
KO
4346 if wx.Platform in ["__WXGTK2__", "__WXMAC__"]:
4347 flags = wx.CONTROL_SELECTED
4348 if self._hasFocus: flags = flags | wx.CONTROL_FOCUSED
4349 wx.RendererNative.Get().DrawItemSelectionRect(self, dc, itemrect, flags)
4350 else:
4351 dc.DrawRectangleRect(itemrect)
c8f129d0
RD
4352
4353 # On GTK+ 2, drawing a 'normal' background is wrong for themes that
4354 # don't allow backgrounds to be customized. Not drawing the background,
4355 # except for custom item backgrounds, works for both kinds of theme.
4356 elif drawItemBackground:
4357
4358 minusicon = wcheck + image_w - 2
94431133
RD
4359 itemrect = wx.Rect(item.GetX()+minusicon,
4360 item.GetY()+offset,
4361 item.GetWidth()-minusicon,
4362 total_h-offset)
c8f129d0
RD
4363
4364 if self._usegradients and self._hasFocus:
4365 if self._gradientstyle == 0: # Horizontal
4366 self.DrawHorizontalGradient(dc, itemrect, self._hasFocus)
4367 else: # Vertical
4368 self.DrawVerticalGradient(dc, itemrect, self._hasFocus)
4369 else:
4370 dc.DrawRectangleRect(itemrect)
4371
4372 if image != _NO_IMAGE:
4373
4374 dc.SetClippingRegion(item.GetX(), item.GetY(), wcheck+image_w-2, total_h)
4375 if item.IsEnabled():
4376 imglist = self._imageListNormal
4377 else:
4378 imglist = self._grayedImageList
4379
4380 imglist.Draw(image, dc,
4381 item.GetX() + wcheck,
4382 item.GetY() + ((total_h > image_h) and [(total_h-image_h)/2] or [0])[0],
4383 wx.IMAGELIST_DRAW_TRANSPARENT)
4384
4385 dc.DestroyClippingRegion()
4386
4387 if wcheck:
4388 if item.IsEnabled():
4389 imglist = self._imageListCheck
4390 else:
4391 imglist = self._grayedCheckList
4392
4393 imglist.Draw(checkimage, dc,
4394 item.GetX(),
4395 item.GetY() + ((total_h > hcheck) and [(total_h-hcheck)/2] or [0])[0],
4396 wx.IMAGELIST_DRAW_TRANSPARENT)
4397
4398 dc.SetBackgroundMode(wx.TRANSPARENT)
4399 extraH = ((total_h > text_h) and [(total_h - text_h)/2] or [0])[0]
4400
4401 textrect = wx.Rect(wcheck + image_w + item.GetX(), item.GetY() + extraH, text_w, text_h)
4402
4403 if not item.IsEnabled():
4404 foreground = dc.GetTextForeground()
4405 dc.SetTextForeground(self._disabledColour)
4406 dc.DrawLabel(item.GetText(), textrect)
4407 dc.SetTextForeground(foreground)
4408 else:
8bc0c17a
KO
4409 if wx.Platform == "__WXMAC__" and item.IsSelected() and self._hasFocus:
4410 dc.SetTextForeground(wx.WHITE)
c8f129d0
RD
4411 dc.DrawLabel(item.GetText(), textrect)
4412
4413 wnd = item.GetWindow()
4414 if wnd:
4415 wndx = wcheck + image_w + item.GetX() + text_w + 4
4416 xa, ya = self.CalcScrolledPosition((0, item.GetY()))
cbfc9df6 4417 wndx += xa
c8f129d0
RD
4418 if not wnd.IsShown():
4419 wnd.Show()
4420 if wnd.GetPosition() != (wndx, ya):
4421 wnd.SetPosition((wndx, ya))
4422
4423 # restore normal font
4424 dc.SetFont(self._normalFont)
4425
4426
4427 # Now y stands for the top of the item, whereas it used to stand for middle !
4428 def PaintLevel(self, item, dc, level, y):
4429 """Paint a level of CustomTreeCtrl."""
4430
4431 x = level*self._indent
4432
4433 if not self.HasFlag(TR_HIDE_ROOT):
4434
4435 x += self._indent
4436
4437 elif level == 0:
4438
4439 # always expand hidden root
4440 origY = y
4441 children = item.GetChildren()
4442 count = len(children)
4443
4444 if count > 0:
4445 n = 0
4446 while n < count:
4447 oldY = y
4448 y = self.PaintLevel(children[n], dc, 1, y)
4449 n = n + 1
4450
4451 if not self.HasFlag(TR_NO_LINES) and self.HasFlag(TR_LINES_AT_ROOT) and count > 0:
4452
4453 # draw line down to last child
4454 origY += self.GetLineHeight(children[0])>>1
4455 oldY += self.GetLineHeight(children[n-1])>>1
e734e438
RD
4456 oldPen = dc.GetPen()
4457 dc.SetPen(self._dottedPen)
c8f129d0 4458 dc.DrawLine(3, origY, 3, oldY)
e734e438 4459 dc.SetPen(oldPen)
c8f129d0
RD
4460
4461 return y
4462
4463 item.SetX(x+self._spacing)
4464 item.SetY(y)
4465
4466 h = self.GetLineHeight(item)
4467 y_top = y
4468 y_mid = y_top + (h>>1)
4469 y += h
4470
4471 exposed_x = dc.LogicalToDeviceX(0)
4472 exposed_y = dc.LogicalToDeviceY(y_top)
4473
4474 if self.IsExposed(exposed_x, exposed_y, 10000, h): # 10000 = very much
4475 if wx.Platform == "__WXMAC__":
4476 # don't draw rect outline if we already have the
4477 # background color under Mac
4478 pen = ((item.IsSelected() and self._hasFocus) and [self._borderPen] or [wx.TRANSPARENT_PEN])[0]
4479 else:
4480 pen = self._borderPen
4481
4482 if item.IsSelected():
4483 if (wx.Platform == "__WXMAC__" and self._hasFocus):
4484 colText = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
4485 else:
4486 colText = wx.SystemSettings_GetColour(wx.SYS_COLOUR_HIGHLIGHTTEXT)
4487 else:
4488 attr = item.GetAttributes()
4489 if attr and attr.HasTextColour():
4490 colText = attr.GetTextColour()
4491 else:
4492 colText = self.GetForegroundColour()
4493
4494 if self._vistaselection:
4495 colText = wx.BLACK
4496
4497 # prepare to draw
4498 dc.SetTextForeground(colText)
4499 dc.SetPen(pen)
4500 oldpen = pen
4501
4502 # draw
4503 self.PaintItem(item, dc)
4504
4505 if self.HasFlag(TR_ROW_LINES):
4506
4507 # if the background colour is white, choose a
4508 # contrasting color for the lines
4509 medium_grey = wx.Pen(wx.Colour(200, 200, 200))
4510 dc.SetPen(((self.GetBackgroundColour() == wx.WHITE) and [medium_grey] or [wx.WHITE_PEN])[0])
4511 dc.DrawLine(0, y_top, 10000, y_top)
4512 dc.DrawLine(0, y, 10000, y)
4513
4514 # restore DC objects
4515 dc.SetBrush(wx.WHITE_BRUSH)
4516 dc.SetTextForeground(wx.BLACK)
4517
4518 if not self.HasFlag(TR_NO_LINES):
4519
4520 # draw the horizontal line here
4521 dc.SetPen(self._dottedPen)
4522 x_start = x
4523 if x > self._indent:
4524 x_start -= self._indent
4525 elif self.HasFlag(TR_LINES_AT_ROOT):
4526 x_start = 3
4527 dc.DrawLine(x_start, y_mid, x + self._spacing, y_mid)
4528 dc.SetPen(oldpen)
4529
4530 # should the item show a button?
4531 if item.HasPlus() and self.HasButtons():
4532
4533 if self._imageListButtons:
4534
4535 # draw the image button here
4536 image_h = 0
4537 image_w = 0
4538 image = (item.IsExpanded() and [TreeItemIcon_Expanded] or [TreeItemIcon_Normal])[0]
4539 if item.IsSelected():
4540 image += TreeItemIcon_Selected - TreeItemIcon_Normal
4541
4542 image_w, image_h = self._imageListButtons.GetSize(image)
4543 xx = x - image_w/2
4544 yy = y_mid - image_h/2
4545
4546 dc.SetClippingRegion(xx, yy, image_w, image_h)
4547 self._imageListButtons.Draw(image, dc, xx, yy,
4548 wx.IMAGELIST_DRAW_TRANSPARENT)
4549 dc.DestroyClippingRegion()
4550
4551 else: # no custom buttons
4552
4553 if self._windowStyle & TR_TWIST_BUTTONS:
4554 # We draw something like the Mac twist buttons
4555
4556 dc.SetPen(wx.BLACK_PEN)
4557 dc.SetBrush(self._hilightBrush)
4558 button = [wx.Point(), wx.Point(), wx.Point()]
4559
4560 if item.IsExpanded():
4561 button[0].x = x - 5
4562 button[0].y = y_mid - 3
4563 button[1].x = x + 5
4564 button[1].y = button[0].y
4565 button[2].x = x
4566 button[2].y = button[0].y + 6
4567 else:
4568 button[0].x = x - 3
4569 button[0].y = y_mid - 5
4570 button[1].x = button[0].x
4571 button[1].y = y_mid + 5
4572 button[2].x = button[0].x + 5
4573 button[2].y = y_mid
4574
4575 dc.DrawPolygon(button)
4576
4577 else:
4578 # These are the standard wx.TreeCtrl buttons as wx.RendererNative knows
4579
4580 wImage = 9
4581 hImage = 9
4582
4583 flag = 0
4584
4585 if item.IsExpanded():
4586 flag |= _CONTROL_EXPANDED
4587 if item == self._underMouse:
4588 flag |= _CONTROL_CURRENT
4589
4590 self._drawingfunction(self, dc, wx.Rect(x - wImage/2, y_mid - hImage/2,wImage, hImage), flag)
4591
4592 if item.IsExpanded():
4593
4594 children = item.GetChildren()
4595 count = len(children)
4596
4597 if count > 0:
4598
4599 n = 0
4600 level = level + 1
4601
4602 while n < count:
4603 oldY = y
4604 y = self.PaintLevel(children[n], dc, level, y)
4605 n = n + 1
4606
4607 if not self.HasFlag(TR_NO_LINES) and count > 0:
4608
4609 # draw line down to last child
4610 oldY += self.GetLineHeight(children[n-1])>>1
4611 if self.HasButtons():
4612 y_mid += 5
4613
4614 # Only draw the portion of the line that is visible, in case it is huge
4615 xOrigin, yOrigin = dc.GetDeviceOrigin()
4616 yOrigin = abs(yOrigin)
4617 width, height = self.GetClientSize()
4618
4619 # Move end points to the begining/end of the view?
4620 if y_mid < yOrigin:
4621 y_mid = yOrigin
4622 if oldY > yOrigin + height:
4623 oldY = yOrigin + height
4624
4625 # after the adjustments if y_mid is larger than oldY then the line
4626 # isn't visible at all so don't draw anything
4627 if y_mid < oldY:
4628 dc.SetPen(self._dottedPen)
4629 dc.DrawLine(x, y_mid, x, oldY)
4630
4631 return y
4632
4633
4634# -----------------------------------------------------------------------------
4635# wxWidgets callbacks
4636# -----------------------------------------------------------------------------
4637
4638 def OnPaint(self, event):
4639 """Handles the wx.EVT_PAINT event."""
4640
4641 dc = wx.PaintDC(self)
4642 self.PrepareDC(dc)
4643
4644 if not self._anchor:
4645 return
4646
4647 dc.SetFont(self._normalFont)
4648 dc.SetPen(self._dottedPen)
4649
4650 y = 2
4651 self.PaintLevel(self._anchor, dc, 0, y)
4652
4653
4654 def OnEraseBackground(self, event):
4655 """Handles the wx.EVT_ERASE_BACKGROUND event."""
4656
4657 # Can we actually do something here (or in OnPaint()) To Handle
4658 # background images that are stretchable or always centered?
4659 # I tried but I get enormous flickering...
4660
4661 if not self._backgroundImage:
4662 event.Skip()
4663 return
4664
4665 if self._imageStretchStyle == _StyleTile:
4666 dc = event.GetDC()
4667
4668 if not dc:
4669 dc = wx.ClientDC(self)
4670 rect = self.GetUpdateRegion().GetBox()
4671 dc.SetClippingRect(rect)
4672
4673 self.TileBackground(dc)
4674
4675
4676 def TileBackground(self, dc):
4677 """Tiles the background image to fill all the available area."""
4678
4679 sz = self.GetClientSize()
4680 w = self._backgroundImage.GetWidth()
4681 h = self._backgroundImage.GetHeight()
4682
4683 x = 0
4684
4685 while x < sz.width:
4686 y = 0
4687
4688 while y < sz.height:
4689 dc.DrawBitmap(self._backgroundImage, x, y, True)
4690 y = y + h
4691
4692 x = x + w
4693
4694
4695 def OnSetFocus(self, event):
4696 """Handles the wx.EVT_SET_FOCUS event."""
4697
4698 self._hasFocus = True
4699 self.RefreshSelected()
4700 event.Skip()
4701
4702
4703 def OnKillFocus(self, event):
4704 """Handles the wx.EVT_KILL_FOCUS event."""
4705
4706 self._hasFocus = False
4707 self.RefreshSelected()
4708 event.Skip()
4709
4710
4711 def OnKeyDown(self, event):
4712 """Handles the wx.EVT_CHAR event, sending a EVT_TREE_KEY_DOWN event."""
4713
4714 te = TreeEvent(wxEVT_TREE_KEY_DOWN, self.GetId())
4715 te._evtKey = event
4716 te.SetEventObject(self)
4717
4718 if self.GetEventHandler().ProcessEvent(te):
4719 # intercepted by the user code
4720 return
4721
4722 if self._current is None or self._key_current is None:
4723
4724 event.Skip()
4725 return
4726
4727 # how should the selection work for this event?
4728 is_multiple, extended_select, unselect_others = EventFlagsToSelType(self.GetTreeStyle(), event.ShiftDown(), event.CmdDown())
4729
4730 # + : Expand
4731 # - : Collaspe
4732 # * : Expand all/Collapse all
4733 # ' ' | return : activate
4734 # up : go up (not last children!)
4735 # down : go down
4736 # left : go to parent
4737 # right : open if parent and go next
4738 # home : go to root
4739 # end : go to last item without opening parents
4740 # alnum : start or continue searching for the item with this prefix
4741
4742 keyCode = event.GetKeyCode()
4743
4744 if keyCode in [ord("+"), wx.WXK_ADD]: # "+"
4745 if self._current.HasPlus() and not self.IsExpanded(self._current) and self.IsEnabled(self._current):
4746 self.Expand(self._current)
4747
4748 elif keyCode in [ord("*"), wx.WXK_MULTIPLY]: # "*"
4749 if not self.IsExpanded(self._current) and self.IsEnabled(self._current):
4750 # expand all
4751 self.ExpandAll(self._current)
4752
4753 elif keyCode in [ord("-"), wx.WXK_SUBTRACT]: # "-"
4754 if self.IsExpanded(self._current):
4755 self.Collapse(self._current)
4756
4757 elif keyCode == wx.WXK_MENU:
4758 # Use the item's bounding rectangle to determine position for the event
4759 itemRect = self.GetBoundingRect(self._current, True)
4760 event = TreeEvent(wxEVT_TREE_ITEM_MENU, self.GetId())
4761 event._item = self._current
4762 # Use the left edge, vertical middle
4763 event._pointDrag = wx.Point(ItemRect.GetX(), ItemRect.GetY() + ItemRect.GetHeight()/2)
4764 event.SetEventObject(self)
4765 self.GetEventHandler().ProcessEvent(event)
4766
4767 elif keyCode in [wx.WXK_RETURN, wx.WXK_SPACE]:
4768
4769 if not self.IsEnabled(self._current):
4770 event.Skip()
4771 return
4772
4773 if not event.HasModifiers():
4774 event = TreeEvent(wxEVT_TREE_ITEM_ACTIVATED, self.GetId())
4775 event._item = self._current
4776 event.SetEventObject(self)
4777 self.GetEventHandler().ProcessEvent(event)
4778
4779 if keyCode == wx.WXK_SPACE and self.GetItemType(self._current) > 0:
4780 checked = not self.IsItemChecked(self._current)
4781 self.CheckItem(self._current, checked)
4782
4783 # in any case, also generate the normal key event for this key,
4784 # even if we generated the ACTIVATED event above: this is what
4785 # wxMSW does and it makes sense because you might not want to
4786 # process ACTIVATED event at all and handle Space and Return
4787 # directly (and differently) which would be impossible otherwise
4788 event.Skip()
4789
4790 # up goes to the previous sibling or to the last
4791 # of its children if it's expanded
4792 elif keyCode == wx.WXK_UP:
4793 prev = self.GetPrevSibling(self._key_current)
4794 if not prev:
4795 prev = self.GetItemParent(self._key_current)
4796 if prev == self.GetRootItem() and self.HasFlag(TR_HIDE_ROOT):
4797 return
4798
4799 if prev:
4800 current = self._key_current
4801 # TODO: Huh? If we get here, we'd better be the first child of our parent. How else could it be?
4802 if current == self.GetFirstChild(prev)[0] and self.IsEnabled(prev):
4803 # otherwise we return to where we came from
4804 self.DoSelectItem(prev, unselect_others, extended_select)
4805 self._key_current = prev
4806
4807 else:
4808 current = self._key_current
4809
4810 # We are going to another parent node
4811 while self.IsExpanded(prev) and self.HasChildren(prev):
4812 child = self.GetLastChild(prev)
4813 if child:
4814 prev = child
4815 current = prev
4816
4817 # Try to get the previous siblings and see if they are active
4818 while prev and not self.IsEnabled(prev):
4819 prev = self.GetPrevSibling(prev)
4820
4821 if not prev:
4822 # No previous siblings active: go to the parent and up
4823 prev = self.GetItemParent(current)
4824 while prev and not self.IsEnabled(prev):
4825 prev = self.GetItemParent(prev)
4826
4827 if prev:
4828 self.DoSelectItem(prev, unselect_others, extended_select)
4829 self._key_current = prev
4830
4831 # left arrow goes to the parent
4832 elif keyCode == wx.WXK_LEFT:
4833
4834 prev = self.GetItemParent(self._current)
4835 if prev == self.GetRootItem() and self.HasFlag(TR_HIDE_ROOT):
4836 # don't go to root if it is hidden
4837 prev = self.GetPrevSibling(self._current)
4838
4839 if self.IsExpanded(self._current):
4840 self.Collapse(self._current)
4841 else:
4842 if prev and self.IsEnabled(prev):
4843 self.DoSelectItem(prev, unselect_others, extended_select)
4844
4845 elif keyCode == wx.WXK_RIGHT:
4846 # this works the same as the down arrow except that we
4847 # also expand the item if it wasn't expanded yet
4848 if self.IsExpanded(self._current) and self.HasChildren(self._current):
4849 child, cookie = self.GetFirstChild(self._key_current)
4850 if self.IsEnabled(child):
4851 self.DoSelectItem(child, unselect_others, extended_select)
4852 self._key_current = child
4853 else:
4854 self.Expand(self._current)
4855 # fall through
4856
4857 elif keyCode == wx.WXK_DOWN:
4858 if self.IsExpanded(self._key_current) and self.HasChildren(self._key_current):
4859
4860 child = self.GetNextActiveItem(self._key_current)
4861
4862 if child:
4863 self.DoSelectItem(child, unselect_others, extended_select)
4864 self._key_current = child
4865
4866 else:
4867
4868 next = self.GetNextSibling(self._key_current)
4869
4870 if not next:
4871 current = self._key_current
4872 while current and not next:
4873 current = self.GetItemParent(current)
4874 if current:
4875 next = self.GetNextSibling(current)
facc1d35 4876 if not next or not self.IsEnabled(next):
c8f129d0
RD
4877 next = None
4878
4879 else:
4880 while next and not self.IsEnabled(next):
4881 next = self.GetNext(next)
4882
4883 if next:
4884 self.DoSelectItem(next, unselect_others, extended_select)
4885 self._key_current = next
4886
4887
4888 # <End> selects the last visible tree item
4889 elif keyCode == wx.WXK_END:
4890
4891 last = self.GetRootItem()
4892
4893 while last and self.IsExpanded(last):
4894
4895 lastChild = self.GetLastChild(last)
4896
4897 # it may happen if the item was expanded but then all of
4898 # its children have been deleted - so IsExpanded() returned
4899 # true, but GetLastChild() returned invalid item
4900 if not lastChild:
4901 break
4902
4903 last = lastChild
4904
4905 if last and self.IsEnabled(last):
4906
4907 self.DoSelectItem(last, unselect_others, extended_select)
4908
4909 # <Home> selects the root item
4910 elif keyCode == wx.WXK_HOME:
4911
4912 prev = self.GetRootItem()
4913
4914 if not prev:
4915 return
4916
4917 if self.HasFlag(TR_HIDE_ROOT):
4918 prev, cookie = self.GetFirstChild(prev)
4919 if not prev:
4920 return
4921
4922 if self.IsEnabled(prev):
4923 self.DoSelectItem(prev, unselect_others, extended_select)
4924
4925 else:
4926
4927 if not event.HasModifiers() and ((keyCode >= ord('0') and keyCode <= ord('9')) or \
4928 (keyCode >= ord('a') and keyCode <= ord('z')) or \
4929 (keyCode >= ord('A') and keyCode <= ord('Z'))):
4930
4931 # find the next item starting with the given prefix
4932 ch = chr(keyCode)
4933 id = self.FindItem(self._current, self._findPrefix + ch)
4934
4935 if not id:
4936 # no such item
4937 return
4938
4939 if self.IsEnabled(id):
4940 self.SelectItem(id)
4941 self._findPrefix += ch
4942
4943 # also start the timer to reset the current prefix if the user
4944 # doesn't press any more alnum keys soon -- we wouldn't want
4945 # to use this prefix for a new item search
4946 if not self._findTimer:
4947 self._findTimer = TreeFindTimer(self)
4948
4949 self._findTimer.Start(_DELAY, wx.TIMER_ONE_SHOT)
4950
4951 else:
4952
4953 event.Skip()
4954
4955
4956 def GetNextActiveItem(self, item, down=True):
4957 """Returns the next active item. Used Internally at present. """
4958
4959 if down:
4960 sibling = self.GetNextSibling
4961 else:
4962 sibling = self.GetPrevSibling
4963
4964 if self.GetItemType(item) == 2 and not self.IsItemChecked(item):
4965 # Is an unchecked radiobutton... all its children are inactive
4966 # try to get the next/previous sibling
4967 found = 0
4968
4969 while 1:
4970 child = sibling(item)
4971 if (child and self.IsEnabled(child)) or not child:
4972 break
4973 item = child
4974
4975 else:
4976 # Tha's not a radiobutton... but some of its children can be
4977 # inactive
4978 child, cookie = self.GetFirstChild(item)
4979 while child and not self.IsEnabled(child):
4980 child, cookie = self.GetNextChild(item, cookie)
4981
4982 if child and self.IsEnabled(child):
4983 return child
4984
4985 return None
4986
4987
4988 def HitTest(self, point, flags=0):
4989 """
4990 Calculates which (if any) item is under the given point, returning the tree item
4991 at this point plus extra information flags. Flags is a bitlist of the following:
4992
4993 TREE_HITTEST_ABOVE above the client area
4994 TREE_HITTEST_BELOW below the client area
4995 TREE_HITTEST_NOWHERE no item has been hit
4996 TREE_HITTEST_ONITEMBUTTON on the button associated to an item
4997 TREE_HITTEST_ONITEMICON on the icon associated to an item
4998 TREE_HITTEST_ONITEMCHECKICON on the check/radio icon, if present
4999 TREE_HITTEST_ONITEMINDENT on the indent associated to an item
5000 TREE_HITTEST_ONITEMLABEL on the label (string) associated to an item
5001 TREE_HITTEST_ONITEMRIGHT on the right of the label associated to an item
5002 TREE_HITTEST_TOLEFT on the left of the client area
5003 TREE_HITTEST_TORIGHT on the right of the client area
5004 TREE_HITTEST_ONITEMUPPERPART on the upper part (first half) of the item
5005 TREE_HITTEST_ONITEMLOWERPART on the lower part (second half) of the item
5006 TREE_HITTEST_ONITEM anywhere on the item
5007
5008 Note: both the item (if any, None otherwise) and the flag are always returned as a tuple.
5009 """
5010
5011 w, h = self.GetSize()
5012 flags = 0
5013
5014 if point.x < 0:
5015 flags |= TREE_HITTEST_TOLEFT
5016 if point.x > w:
5017 flags |= TREE_HITTEST_TORIGHT
5018 if point.y < 0:
5019 flags |= TREE_HITTEST_ABOVE
5020 if point.y > h:
5021 flags |= TREE_HITTEST_BELOW
5022
5023 if flags:
5024 return None, flags
5025
5026 if self._anchor == None:
5027 flags = TREE_HITTEST_NOWHERE
5028 return None, flags
5029
5030 hit, flags = self._anchor.HitTest(self.CalcUnscrolledPosition(point), self, flags, 0)
5031
5032 if hit == None:
5033 flags = TREE_HITTEST_NOWHERE
5034 return None, flags
5035
5036 if not self.IsEnabled(hit):
5037 return None, flags
5038
5039 return hit, flags
5040
5041
5042 def GetBoundingRect(self, item, textOnly=False):
5043 """Gets the bounding rectangle of the item."""
5044
5045 if not item:
e1463b9d 5046 raise Exception("\nERROR: Invalid Tree Item. ")
c8f129d0
RD
5047
5048 i = item
5049
5050 startX, startY = self.GetViewStart()
5051 rect = wx.Rect()
5052
5053 rect.x = i.GetX() - startX*_PIXELS_PER_UNIT
5054 rect.y = i.GetY() - startY*_PIXELS_PER_UNIT
5055 rect.width = i.GetWidth()
5056 rect.height = self.GetLineHeight(i)
5057
5058 return rect
5059
5060
5061 def Edit(self, item):
5062 """
5063 Internal function. Starts the editing of an item label, sending a
5064 EVT_TREE_BEGIN_LABEL_EDIT event.
5065 """
5066
5067 te = TreeEvent(wxEVT_TREE_BEGIN_LABEL_EDIT, self.GetId())
5068 te._item = item
5069 te.SetEventObject(self)
5070 if self.GetEventHandler().ProcessEvent(te) and not te.IsAllowed():
5071 # vetoed by user
5072 return
5073
5074 # We have to call this here because the label in
5075 # question might just have been added and no screen
5076 # update taken place.
5077 if self._dirty:
5078 if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
5079 self.Update()
5080 else:
5081 wx.YieldIfNeeded()
5082
5083 if self._textCtrl != None and item != self._textCtrl.item():
5084 self._textCtrl.StopEditing()
5085
5086 self._textCtrl = TreeTextCtrl(self, item=item)
5087 self._textCtrl.SetFocus()
5088
5089
5090 def GetEditControl(self):
5091 """
5092 Returns a pointer to the edit TextCtrl if the item is being edited or
5093 None otherwise (it is assumed that no more than one item may be edited
5094 simultaneously).
5095 """
5096
5097 return self._textCtrl
5098
5099
5100 def OnRenameAccept(self, item, value):
5101 """
5102 Called by TreeTextCtrl, to accept the changes and to send the
5103 EVT_TREE_END_LABEL_EDIT event.
5104 """
5105
5106 le = TreeEvent(wxEVT_TREE_END_LABEL_EDIT, self.GetId())
5107 le._item = item
5108 le.SetEventObject(self)
5109 le._label = value
5110 le._editCancelled = False
5111
5112 return not self.GetEventHandler().ProcessEvent(le) or le.IsAllowed()
5113
5114
5115 def OnRenameCancelled(self, item):
5116 """
5117 Called by TreeTextCtrl, to cancel the changes and to send the
5118 EVT_TREE_END_LABEL_EDIT event.
5119 """
5120
5121 # let owner know that the edit was cancelled
5122 le = TreeEvent(wxEVT_TREE_END_LABEL_EDIT, self.GetId())
5123 le._item = item
5124 le.SetEventObject(self)
5125 le._label = ""
5126 le._editCancelled = True
5127
5128 self.GetEventHandler().ProcessEvent(le)
5129
5130
5131 def OnRenameTimer(self):
5132 """The timer for renaming has expired. Start editing."""
5133
5134 self.Edit(self._current)
5135
5136
5137 def OnMouse(self, event):
5138 """Handles a bunch of wx.EVT_MOUSE_EVENTS events."""
5139
5140 if not self._anchor:
5141 return
5142
5143 pt = self.CalcUnscrolledPosition(event.GetPosition())
5144
5145 # Is the mouse over a tree item button?
5146 flags = 0
5147 thisItem, flags = self._anchor.HitTest(pt, self, flags, 0)
5148 underMouse = thisItem
5149 underMouseChanged = underMouse != self._underMouse
5150
facc1d35 5151 if underMouse and (flags & TREE_HITTEST_ONITEM) and not event.LeftIsDown() and \
c8f129d0
RD
5152 not self._isDragging and (not self._renameTimer or not self._renameTimer.IsRunning()):
5153 underMouse = underMouse
5154 else:
5155 underMouse = None
5156
5157 if underMouse != self._underMouse:
5158 if self._underMouse:
5159 # unhighlight old item
5160 self._underMouse = None
5161
5162 self._underMouse = underMouse
5163
5164 # Determines what item we are hovering over and need a tooltip for
5165 hoverItem = thisItem
5166
5167 # We do not want a tooltip if we are dragging, or if the rename timer is running
5168 if underMouseChanged and not self._isDragging and (not self._renameTimer or not self._renameTimer.IsRunning()):
5169
5170 if hoverItem is not None:
5171 # Ask the tree control what tooltip (if any) should be shown
5172 hevent = TreeEvent(wxEVT_TREE_ITEM_GETTOOLTIP, self.GetId())
5173 hevent._item = hoverItem
5174 hevent.SetEventObject(self)
5175
5176 if self.GetEventHandler().ProcessEvent(hevent) and hevent.IsAllowed():
5177 self.SetToolTip(hevent._label)
5178
5179 if hoverItem.IsHyperText() and (flags & TREE_HITTEST_ONITEMLABEL) and hoverItem.IsEnabled():
5180 self.SetCursor(wx.StockCursor(wx.CURSOR_HAND))
5181 self._isonhyperlink = True
5182 else:
5183 if self._isonhyperlink:
5184 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
5185 self._isonhyperlink = False
5186
5187 # we process left mouse up event (enables in-place edit), right down
5188 # (pass to the user code), left dbl click (activate item) and
5189 # dragging/moving events for items drag-and-drop
5190
5191 if not (event.LeftDown() or event.LeftUp() or event.RightDown() or event.LeftDClick() or \
5192 event.Dragging() or ((event.Moving() or event.RightUp()) and self._isDragging)):
5193
5194 event.Skip()
5195 return
5196
5197 flags = 0
5198 item, flags = self._anchor.HitTest(pt, self, flags, 0)
5199
5200 if event.Dragging() and not self._isDragging and ((flags & TREE_HITTEST_ONITEMICON) or (flags & TREE_HITTEST_ONITEMLABEL)):
5201
5202 if self._dragCount == 0:
5203 self._dragStart = pt
5204
5205 self._countDrag = 0
5206 self._dragCount = self._dragCount + 1
5207
5208 if self._dragCount != 3:
5209 # wait until user drags a bit further...
5210 return
5211
5212 command = (event.RightIsDown() and [wxEVT_TREE_BEGIN_RDRAG] or [wxEVT_TREE_BEGIN_DRAG])[0]
5213
5214 nevent = TreeEvent(command, self.GetId())
5215 nevent._item = self._current
5216 nevent.SetEventObject(self)
5217 newpt = self.CalcScrolledPosition(pt)
5218 nevent.SetPoint(newpt)
5219
5220 # by default the dragging is not supported, the user code must
5221 # explicitly allow the event for it to take place
5222 nevent.Veto()
5223
5224 if self.GetEventHandler().ProcessEvent(nevent) and nevent.IsAllowed():
5225
5226 # we're going to drag this item
5227 self._isDragging = True
5228
5229 # remember the old cursor because we will change it while
5230 # dragging
5231 self._oldCursor = self._cursor
5232
5233 # in a single selection control, hide the selection temporarily
5234 if not (self.GetTreeStyle() & TR_MULTIPLE):
5235 self._oldSelection = self.GetSelection()
5236
5237 if self._oldSelection:
5238
5239 self._oldSelection.SetHilight(False)
5240 self.RefreshLine(self._oldSelection)
5241 else:
5242 selections = self.GetSelections()
5243 if len(selections) == 1:
5244 self._oldSelection = selections[0]
5245 self._oldSelection.SetHilight(False)
5246 self.RefreshLine(self._oldSelection)
5247
5248 if self._dragImage:
5249 del self._dragImage
5250
5251 # Create the custom draw image from the icons and the text of the item
5252 self._dragImage = DragImage(self, self._current)
5253 self._dragImage.BeginDrag(wx.Point(0,0), self)
5254 self._dragImage.Show()
5255 self._dragImage.Move(self.CalcScrolledPosition(pt))
5256
5257 elif event.Dragging() and self._isDragging:
5258
5259 self._dragImage.Move(self.CalcScrolledPosition(pt))
5260
5261 if self._countDrag == 0 and item:
5262 self._oldItem = item
5263
5264 if item != self._dropTarget:
5265
5266 # unhighlight the previous drop target
5267 if self._dropTarget:
5268 self._dropTarget.SetHilight(False)
5269 self.RefreshLine(self._dropTarget)
5270 if item:
5271 item.SetHilight(True)
5272 self.RefreshLine(item)
5273 self._countDrag = self._countDrag + 1
5274 self._dropTarget = item
5275
5276 self.Update()
5277
5278 if self._countDrag >= 3:
5279 # Here I am trying to avoid ugly repainting problems... hope it works
5280 self.RefreshLine(self._oldItem)
5281 self._countDrag = 0
5282
5283 elif (event.LeftUp() or event.RightUp()) and self._isDragging:
5284
5285 if self._dragImage:
5286 self._dragImage.EndDrag()
5287
5288 if self._dropTarget:
5289 self._dropTarget.SetHilight(False)
5290
5291 if self._oldSelection:
5292
5293 self._oldSelection.SetHilight(True)
5294 self.RefreshLine(self._oldSelection)
5295 self._oldSelection = None
5296
5297 # generate the drag end event
5298 event = TreeEvent(wxEVT_TREE_END_DRAG, self.GetId())
5299 event._item = item
5300 event._pointDrag = self.CalcScrolledPosition(pt)
5301 event.SetEventObject(self)
5302
5303 self.GetEventHandler().ProcessEvent(event)
5304
5305 self._isDragging = False
5306 self._dropTarget = None
5307
5308 self.SetCursor(self._oldCursor)
5309
5310 if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
5311 self.Refresh()
5312 else:
5313 # Probably this is not enough on GTK. Try a Refresh() if it does not work.
5314 wx.YieldIfNeeded()
5315
5316 else:
5317
5318 # If we got to this point, we are not dragging or moving the mouse.
5319 # Because the code in carbon/toplevel.cpp will only set focus to the tree
5320 # if we skip for EVT_LEFT_DOWN, we MUST skip this event here for focus to work.
5321 # We skip even if we didn't hit an item because we still should
5322 # restore focus to the tree control even if we didn't exactly hit an item.
5323 if event.LeftDown():
5324 self._hasFocus = True
5325 self.SetFocusIgnoringChildren()
5326 event.Skip()
5327
5328 # here we process only the messages which happen on tree items
5329
5330 self._dragCount = 0
5331
5332 if item == None:
5333 if self._textCtrl != None and item != self._textCtrl.item():
5334 self._textCtrl.StopEditing()
5335 return # we hit the blank area
5336
5337 if event.RightDown():
5338
5339 if self._textCtrl != None and item != self._textCtrl.item():
5340 self._textCtrl.StopEditing()
5341
5342 self._hasFocus = True
5343 self.SetFocusIgnoringChildren()
5344
5345 # If the item is already selected, do not update the selection.
5346 # Multi-selections should not be cleared if a selected item is clicked.
5347 if not self.IsSelected(item):
5348
5349 self.DoSelectItem(item, True, False)
5350
5351 nevent = TreeEvent(wxEVT_TREE_ITEM_RIGHT_CLICK, self.GetId())
5352 nevent._item = item
5353 nevent._pointDrag = self.CalcScrolledPosition(pt)
5354 nevent.SetEventObject(self)
5355 event.Skip(not self.GetEventHandler().ProcessEvent(nevent))
5356
5357 # Consistent with MSW (for now), send the ITEM_MENU *after*
5358 # the RIGHT_CLICK event. TODO: This behaviour may change.
5359 nevent2 = TreeEvent(wxEVT_TREE_ITEM_MENU, self.GetId())
5360 nevent2._item = item
5361 nevent2._pointDrag = self.CalcScrolledPosition(pt)
5362 nevent2.SetEventObject(self)
5363 self.GetEventHandler().ProcessEvent(nevent2)
5364
5365 elif event.LeftUp():
5366
5367 # this facilitates multiple-item drag-and-drop
5368
5369 if self.HasFlag(TR_MULTIPLE):
5370
5371 selections = self.GetSelections()
5372
5373 if len(selections) > 1 and not event.CmdDown() and not event.ShiftDown():
5374
5375 self.DoSelectItem(item, True, False)
5376
5377 if self._lastOnSame:
5378
5379 if item == self._current and (flags & TREE_HITTEST_ONITEMLABEL) and self.HasFlag(TR_EDIT_LABELS):
5380
5381 if self._renameTimer:
5382
5383 if self._renameTimer.IsRunning():
5384
5385 self._renameTimer.Stop()
5386
5387 else:
5388
5389 self._renameTimer = TreeRenameTimer(self)
5390
5391 self._renameTimer.Start(_DELAY, True)
5392
5393 self._lastOnSame = False
5394
5395
5396 else: # !RightDown() && !LeftUp() ==> LeftDown() || LeftDClick()
5397
5398 if not item or not item.IsEnabled():
5399 if self._textCtrl != None and item != self._textCtrl.item():
5400 self._textCtrl.StopEditing()
5401 return
5402
5403 if self._textCtrl != None and item != self._textCtrl.item():
5404 self._textCtrl.StopEditing()
5405
5406 self._hasFocus = True
5407 self.SetFocusIgnoringChildren()
5408
5409 if event.LeftDown():
5410
5411 self._lastOnSame = item == self._current
5412
5413 if flags & TREE_HITTEST_ONITEMBUTTON:
5414
5415 # only toggle the item for a single click, double click on
5416 # the button doesn't do anything (it toggles the item twice)
5417 if event.LeftDown():
5418
5419 self.Toggle(item)
5420
5421 # don't select the item if the button was clicked
5422 return
5423
5424 if item.GetType() > 0 and (flags & TREE_HITTEST_ONITEMCHECKICON):
5425
5426 if event.LeftDown():
5427
5428 self.CheckItem(item, not self.IsItemChecked(item))
5429
5430 return
5431
5432 # clear the previously selected items, if the
5433 # user clicked outside of the present selection.
5434 # otherwise, perform the deselection on mouse-up.
5435 # this allows multiple drag and drop to work.
5436 # but if Cmd is down, toggle selection of the clicked item
5437 if not self.IsSelected(item) or event.CmdDown():
5438
5439 if flags & TREE_HITTEST_ONITEM:
5440 # how should the selection work for this event?
5441 if item.IsHyperText():
5442 self.SetItemVisited(item, True)
5443
5444 is_multiple, extended_select, unselect_others = EventFlagsToSelType(self.GetTreeStyle(),
5445 event.ShiftDown(),
5446 event.CmdDown())
5447
5448 self.DoSelectItem(item, unselect_others, extended_select)
5449
5450 # For some reason, Windows isn't recognizing a left double-click,
5451 # so we need to simulate it here. Allow 200 milliseconds for now.
5452 if event.LeftDClick():
5453
5454 # double clicking should not start editing the item label
5455 if self._renameTimer:
5456 self._renameTimer.Stop()
5457
5458 self._lastOnSame = False
5459
5460 # send activate event first
5461 nevent = TreeEvent(wxEVT_TREE_ITEM_ACTIVATED, self.GetId())
5462 nevent._item = item
5463 nevent._pointDrag = self.CalcScrolledPosition(pt)
5464 nevent.SetEventObject(self)
5465 if not self.GetEventHandler().ProcessEvent(nevent):
5466
5467 # if the user code didn't process the activate event,
5468 # handle it ourselves by toggling the item when it is
5469 # double clicked
5470## if item.HasPlus():
5471 self.Toggle(item)
5472
5473
94431133 5474 def OnInternalIdle(self):
c8f129d0
RD
5475 """Performs operations in idle time (essentially drawing)."""
5476
5477 # Check if we need to select the root item
5478 # because nothing else has been selected.
5479 # Delaying it means that we can invoke event handlers
5480 # as required, when a first item is selected.
5481 if not self.HasFlag(TR_MULTIPLE) and not self.GetSelection():
5482
5483 if self._select_me:
5484 self.SelectItem(self._select_me)
5485 elif self.GetRootItem():
5486 self.SelectItem(self.GetRootItem())
5487
5488 # after all changes have been done to the tree control,
5489 # we actually redraw the tree when everything is over
5490
5491 if not self._dirty:
5492 return
5493 if self._freezeCount:
5494 return
5495
5496 self._dirty = False
5497
5498 self.CalculatePositions()
5499 self.Refresh()
5500 self.AdjustMyScrollbars()
5501
5502# event.Skip()
5503
5504
5505 def CalculateSize(self, item, dc):
5506 """Calculates overall position and size of an item."""
5507
5508 attr = item.GetAttributes()
5509
5510 if attr and attr.HasFont():
5511 dc.SetFont(attr.GetFont())
5512 elif item.IsBold():
5513 dc.SetFont(self._boldFont)
5514 else:
5515 dc.SetFont(self._normalFont)
5516
5517 text_w, text_h, dummy = dc.GetMultiLineTextExtent(item.GetText())
5518 text_h+=2
5519
5520 # restore normal font
5521 dc.SetFont(self._normalFont)
5522
5523 image_w, image_h = 0, 0
5524 image = item.GetCurrentImage()
5525
5526 if image != _NO_IMAGE:
5527
5528 if self._imageListNormal:
5529
5530 image_w, image_h = self._imageListNormal.GetSize(image)
5531 image_w += 4
5532
5533 total_h = ((image_h > text_h) and [image_h] or [text_h])[0]
5534
5535 checkimage = item.GetCurrentCheckedImage()
5536 if checkimage is not None:
5537 wcheck, hcheck = self._imageListCheck.GetSize(checkimage)
5538 wcheck += 4
5539 else:
5540 wcheck = 0
5541
5542 if total_h < 30:
5543 total_h += 2 # at least 2 pixels
5544 else:
5545 total_h += total_h/10 # otherwise 10% extra spacing
5546
5547 if total_h > self._lineHeight:
5548 self._lineHeight = total_h
5549
5550 if not item.GetWindow():
5551 item.SetWidth(image_w+text_w+wcheck+2)
5552 item.SetHeight(total_h)
5553 else:
5554 item.SetWidth(item.GetWindowSize()[0]+image_w+text_w+wcheck+2)
5555
5556
5557 def CalculateLevel(self, item, dc, level, y):
5558 """Calculates the level of an item."""
5559
5560 x = level*self._indent
5561
5562 if not self.HasFlag(TR_HIDE_ROOT):
5563
5564 x += self._indent
5565
5566 elif level == 0:
5567
5568 # a hidden root is not evaluated, but its
5569 # children are always calculated
5570 children = item.GetChildren()
5571 count = len(children)
5572 level = level + 1
5573 for n in xrange(count):
5574 y = self.CalculateLevel(children[n], dc, level, y) # recurse
5575
5576 return y
5577
5578 self.CalculateSize(item, dc)
5579
5580 # set its position
5581 item.SetX(x+self._spacing)
5582 item.SetY(y)
5583 y += self.GetLineHeight(item)
5584
5585 if not item.IsExpanded():
5586 # we don't need to calculate collapsed branches
5587 return y
5588
5589 children = item.GetChildren()
5590 count = len(children)
5591 level = level + 1
5592 for n in xrange(count):
5593 y = self.CalculateLevel(children[n], dc, level, y) # recurse
5594
5595 return y
5596
5597
5598 def CalculatePositions(self):
5599 """Calculates all the positions of the visible items."""
5600
5601 if not self._anchor:
5602 return
5603
5604 dc = wx.ClientDC(self)
5605 self.PrepareDC(dc)
5606
5607 dc.SetFont(self._normalFont)
5608 dc.SetPen(self._dottedPen)
5609 y = 2
5610 y = self.CalculateLevel(self._anchor, dc, 0, y) # start recursion
5611
5612
5613 def RefreshSubtree(self, item):
5614 """Refreshes a damaged subtree of an item."""
5615
5616 if self._dirty:
5617 return
5618 if self._freezeCount:
5619 return
5620
5621 client = self.GetClientSize()
5622
5623 rect = wx.Rect()
5624 x, rect.y = self.CalcScrolledPosition(0, item.GetY())
5625 rect.width = client.x
5626 rect.height = client.y
5627
5628 self.Refresh(True, rect)
5629 self.AdjustMyScrollbars()
5630
5631
5632 def RefreshLine(self, item):
5633 """Refreshes a damaged item line."""
5634
5635 if self._dirty:
5636 return
5637 if self._freezeCount:
5638 return
5639
5640 rect = wx.Rect()
5641 x, rect.y = self.CalcScrolledPosition(0, item.GetY())
5642 rect.width = self.GetClientSize().x
5643 rect.height = self.GetLineHeight(item)
5644
5645 self.Refresh(True, rect)
5646
5647
5648 def RefreshSelected(self):
5649 """Refreshes a damaged selected item line."""
5650
5651 if self._freezeCount:
5652 return
5653
5654 # TODO: this is awfully inefficient, we should keep the list of all
5655 # selected items internally, should be much faster
5656 if self._anchor:
5657 self.RefreshSelectedUnder(self._anchor)
5658
5659
5660 def RefreshSelectedUnder(self, item):
5661 """Refreshes the selected items under the given item."""
5662
5663 if self._freezeCount:
5664 return
5665
5666 if item.IsSelected():
5667 self.RefreshLine(item)
5668
5669 children = item.GetChildren()
5670 for child in children:
5671 self.RefreshSelectedUnder(child)
5672
5673
5674 def Freeze(self):
5675 """Freeze CustomTreeCtrl."""
5676
5677 self._freezeCount = self._freezeCount + 1
5678
5679
5680 def Thaw(self):
5681 """Thaw CustomTreeCtrl."""
5682
5683 if self._freezeCount == 0:
e1463b9d 5684 raise Exception("\nERROR: Thawing Unfrozen Tree Control?")
c8f129d0
RD
5685
5686 self._freezeCount = self._freezeCount - 1
5687
5688 if not self._freezeCount:
5689 self.Refresh()
5690
5691
5692 # ----------------------------------------------------------------------------
5693 # changing colours: we need to refresh the tree control
5694 # ----------------------------------------------------------------------------
5695
5696 def SetBackgroundColour(self, colour):
5697 """Changes the background colour of CustomTreeCtrl."""
5698
5699 if not wx.Window.SetBackgroundColour(self, colour):
5700 return False
5701
5702 if self._freezeCount:
5703 return True
5704
5705 self.Refresh()
5706
5707 return True
5708
5709
5710 def SetForegroundColour(self, colour):
5711 """Changes the foreground colour of CustomTreeCtrl."""
5712
5713 if not wx.Window.SetForegroundColour(self, colour):
5714 return False
5715
5716 if self._freezeCount:
5717 return True
5718
5719 self.Refresh()
5720
5721 return True
5722
5723
5724 def OnGetToolTip(self, event):
5725 """
5726 Process the tooltip event, to speed up event processing. Does not actually
5727 get a tooltip.
5728 """
5729
5730 event.Veto()
5731
5732
5733 def DoGetBestSize(self):
5734 """Something is better than nothing..."""
5735
5736 # something is better than nothing...
5737 # 100x80 is what the MSW version will get from the default
5738 # wxControl::DoGetBestSize
5739
5740 return wx.Size(100, 80)
5741
5742
5743 def GetClassDefaultAttributes(self):
5744 """Gets the class default attributes."""
5745
5746 attr = wx.VisualAttributes()
5747 attr.colFg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT)
5748 attr.colBg = wx.SystemSettings_GetColour(wx.SYS_COLOUR_LISTBOX)
5749 attr.font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
5750 return attr
5751
cbfc9df6 5752 GetClassDefaultAttributes = classmethod(GetClassDefaultAttributes)
c8f129d0 5753
cbfc9df6 5754