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