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