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