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