]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/combotreebox.py
Don't autosize if the height is too small. Interaction with the
[wxWidgets.git] / wxPython / wx / lib / combotreebox.py
1 """
2 ComboTreeBox provides a ComboBox that pops up a tree instead of a list.
3
4 ComboTreeBox tries to provide the same interface as ComboBox as much as
5 possible. However, whereas the ComboBox widget uses indices to access
6 items in the list of choices, ComboTreeBox uses TreeItemId's instead. If
7 you add an item to the ComboTreeBox (using Append or Insert), the
8 TreeItemId associated with the added item is returned. You can then use
9 that TreeItemId to add items as children of that first item. For
10 example:
11
12 >>> from wx.lib.combotreebox import ComboTreeBox
13 >>> combo = ComboTreeBox(parent)
14 >>> item1 = combo.Append('Item 1') # Add a root item
15 >>> item1a = combo.Append('Item 1a', parent=item1) # Add a child to item1
16
17 You can also add client data to each of the items like this:
18 >>> item1 = combo.Append('Item 1', clientData=somePythonObject)
19 >>> item1a = combo.Append('Item 1a', parent=item1,
20 ... clientData=someOtherPythonObject)
21
22 And later fetch the client data like this:
23 >>> somePythonObject = combo.GetClientData(item1)
24
25 To get the client data of the currently selected item (if any):
26 >>> currentItem = combo.GetSelection()
27 >>> if currentItem:
28 >>> somePythonObject = combo.GetClientData(currentItem)
29
30 Supported styles are the same as for ComboBox, i.e. wx.CB_READONLY and
31 wx.CB_SORT. Provide them as usual:
32 >>> combo = ComboTreeBox(parent, style=wx.CB_READONLY|wx.CB_SORT)
33
34 Supported platforms: wxMSW and wxMAC natively, wxGTK by means of a
35 workaround.
36
37 Author: Frank Niessink <frank@niessink.com>
38 Copyright 2006, Frank Niessink
39 License: wxWidgets license
40 Version: 0.9
41 Date: September 6, 2006
42 """
43
44 import wx
45
46 __all__ = ['ComboTreeBox'] # Export only the ComboTreeBox widget
47
48
49 # ---------------------------------------------------------------------------
50
51
52 class IterableTreeCtrl(wx.TreeCtrl):
53 """
54 TreeCtrl is the same as wx.TreeCtrl, with a few convenience methods
55 added for easier navigation of items. """
56
57 def GetPreviousItem(self, item):
58 """
59 GetPreviousItem(self, TreeItemId item) -> TreeItemId
60
61 Returns the item that is on the line immediately above item
62 (as is displayed when the tree is fully expanded). The returned
63 item is invalid if item is the first item in the tree.
64 """
65 previousSibling = self.GetPrevSibling(item)
66 if previousSibling:
67 return self.GetLastChildRecursively(previousSibling)
68 else:
69 parent = self.GetItemParent(item)
70 if parent == self.GetRootItem() and \
71 (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
72 # Return an invalid item, because the root item is hidden
73 return previousSibling
74 else:
75 return parent
76
77 def GetNextItem(self, item):
78 """
79 GetNextItem(self, TreeItemId item) -> TreeItemId
80
81 Returns the item that is on the line immediately below item
82 (as is displayed when the tree is fully expanded). The returned
83 item is invalid if item is the last item in the tree.
84 """
85 if self.ItemHasChildren(item):
86 firstChild, cookie = self.GetFirstChild(item)
87 return firstChild
88 else:
89 return self.GetNextSiblingRecursively(item)
90
91 def GetFirstItem(self):
92 """
93 GetFirstItem(self) -> TreeItemId
94
95 Returns the very first item in the tree. This is the root item
96 unless the root item is hidden. In that case the first child of
97 the root item is returned, if any. If the tree is empty, an
98 invalid tree item is returned.
99 """
100 rootItem = self.GetRootItem()
101 if rootItem and (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
102 firstChild, cookie = self.GetFirstChild(rootItem)
103 return firstChild
104 else:
105 return rootItem
106
107 def GetLastChildRecursively(self, item):
108 """
109 GetLastChildRecursively(self, TreeItemId item) -> TreeItemId
110
111 Returns the last child of the last child ... of item. If item
112 has no children, item itself is returned. So the returned item
113 is always valid, assuming a valid item has been passed.
114 """
115 lastChild = item
116 while self.ItemHasChildren(lastChild):
117 lastChild = self.GetLastChild(lastChild)
118 return lastChild
119
120 def GetNextSiblingRecursively(self, item):
121 """
122 GetNextSiblingRecursively(self, TreeItemId item) -> TreeItemId
123
124 Returns the next sibling of item if it has one. If item has no
125 next sibling the next sibling of the parent of item is returned.
126 If the parent has no next sibling the next sibling of the parent
127 of the parent is returned, etc. If none of the ancestors of item
128 has a next sibling, an invalid item is returned.
129 """
130 if item == self.GetRootItem():
131 return wx.TreeItemId() # Return an invalid TreeItemId
132 nextSibling = self.GetNextSibling(item)
133 if nextSibling:
134 return nextSibling
135 else:
136 parent = self.GetItemParent(item)
137 return self.GetNextSiblingRecursively(parent)
138
139 def GetSelection(self):
140 """ Extend GetSelection to never return the root item if the
141 root item is hidden. """
142 selection = super(IterableTreeCtrl, self).GetSelection()
143 if selection == self.GetRootItem() and \
144 (self.GetWindowStyle() & wx.TR_HIDE_ROOT):
145 return wx.TreeItemId() # Return an invalid TreeItemId
146 else:
147 return selection
148
149
150 # ---------------------------------------------------------------------------
151
152
153 class BasePopupFrame(wx.Frame):
154 """
155 BasePopupFrame is the base class for platform specific
156 versions of the PopupFrame. The PopupFrame is the frame that
157 is popped up by ComboTreeBox. It contains the tree of items
158 that the user can select one item from. Upon selection, or
159 when focus is lost, the frame is hidden. """
160
161 def __init__(self, parent):
162 super(BasePopupFrame, self).__init__(parent,
163 style=wx.DEFAULT_FRAME_STYLE & wx.FRAME_FLOAT_ON_PARENT &
164 ~(wx.RESIZE_BORDER | wx.CAPTION))
165 self._createInterior()
166 self._layoutInterior()
167 self._bindEventHandlers()
168
169 def _createInterior(self):
170 self._tree = IterableTreeCtrl(self,
171 style=wx.TR_HIDE_ROOT|wx.TR_LINES_AT_ROOT|wx.TR_HAS_BUTTONS)
172 self._tree.AddRoot('Hidden root node')
173
174 def _layoutInterior(self):
175 frameSizer = wx.BoxSizer(wx.HORIZONTAL)
176 frameSizer.Add(self._tree, flag=wx.EXPAND, proportion=1)
177 self.SetSizerAndFit(frameSizer)
178
179 def _bindEventHandlers(self):
180 self._tree.Bind(wx.EVT_CHAR, self.OnChar)
181 self._tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnItemActivated)
182 self._tree.Bind(wx.EVT_LEFT_DOWN, self.OnMouseClick)
183
184 def _bindKillFocus(self):
185 self._tree.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)
186
187 def _unbindKillFocus(self):
188 self._tree.Unbind(wx.EVT_KILL_FOCUS)
189
190 def OnKillFocus(self, event):
191 # We hide the frame rather than destroy it, so it can be
192 # popped up again later:
193 self.Hide()
194 self.GetParent().NotifyNoItemSelected()
195 event.Skip()
196
197 def OnChar(self, keyEvent):
198 if self._keyShouldHidePopup(keyEvent):
199 self.Hide()
200 self.GetParent().NotifyNoItemSelected()
201 keyEvent.Skip()
202
203 def _keyShouldHidePopup(self, keyEvent):
204 return keyEvent.GetKeyCode() == wx.WXK_ESCAPE
205
206 def OnMouseClick(self, event):
207 item, flags = self._tree.HitTest(event.GetPosition())
208 if item and (flags & wx.TREE_HITTEST_ONITEMLABEL):
209 self._tree.SelectItem(item)
210 self.Hide()
211 self.GetParent().NotifyItemSelected(self._tree.GetItemText(item))
212 else:
213 event.Skip()
214
215 def OnItemActivated(self, event):
216 item = event.GetItem()
217 self.Hide()
218 self.GetParent().NotifyItemSelected(self._tree.GetItemText(item))
219
220 def Show(self):
221 self._bindKillFocus()
222 wx.CallAfter(self._tree.SetFocus)
223 super(BasePopupFrame, self).Show()
224
225 def Hide(self):
226 self._unbindKillFocus()
227 super(BasePopupFrame, self).Hide()
228
229 def GetTree(self):
230 return self._tree
231
232
233 class MSWPopupFrame(BasePopupFrame):
234 def Show(self):
235 # Comply with the MS Windows Combobox behaviour: if the text in
236 # the text field is not in the tree, the first item in the tree
237 # is selected.
238 if not self._tree.GetSelection():
239 self._tree.SelectItem(self._tree.GetFirstItem())
240 super(MSWPopupFrame, self).Show()
241
242
243 class MACPopupFrame(BasePopupFrame):
244 def _bindKillFocus(self):
245 # On wxMac, the kill focus event doesn't work, but the
246 # deactivate event does:
247 self.Bind(wx.EVT_ACTIVATE, self.OnKillFocus)
248
249 def _unbindKillFocus(self):
250 self.Unbind(wx.EVT_ACTIVATE)
251
252 def OnKillFocus(self, event):
253 if not event.GetActive(): # We received a deactivate event
254 self.Hide()
255 wx.CallAfter(self.GetParent().NotifyNoItemSelected)
256 event.Skip()
257
258
259 class GTKPopupFrame(BasePopupFrame):
260 def _keyShouldHidePopup(self, keyEvent):
261 # On wxGTK, Alt-Up also closes the popup:
262 return super(GTKPopupFrame, self)._keyShouldHidePopup(keyEvent) or \
263 (keyEvent.AltDown() and keyEvent.GetKeyCode() == wx.WXK_UP)
264
265
266 # ---------------------------------------------------------------------------
267
268
269 class BaseComboTreeBox(object):
270 """ BaseComboTreeBox is the base class for platform specific
271 versions of the ComboTreeBox. """
272
273 def __init__(self, *args, **kwargs):
274 style = kwargs.pop('style', 0)
275 if style & wx.CB_READONLY:
276 style &= ~wx.CB_READONLY # We manage readonlyness ourselves
277 self._readOnly = True
278 else:
279 self._readOnly = False
280 if style & wx.CB_SORT:
281 style &= ~wx.CB_SORT # We manage sorting ourselves
282 self._sort = True
283 else:
284 self._sort = False
285 super(BaseComboTreeBox, self).__init__(style=style, *args, **kwargs)
286 self._createInterior()
287 self._layoutInterior()
288 self._bindEventHandlers()
289
290 # Methods to construct the widget.
291
292 def _createInterior(self):
293 self._popupFrame = self._createPopupFrame()
294 self._text = self._createTextCtrl()
295 self._button = self._createButton()
296 self._tree = self._popupFrame.GetTree()
297
298 def _createTextCtrl(self):
299 return self # By default, the text control is the control itself.
300
301 def _createButton(self):
302 return self # By default, the dropdown button is the control itself.
303
304 def _createPopupFrame(self):
305 # It is a subclass responsibility to provide the right PopupFrame,
306 # depending on platform:
307 raise NotImplementedError
308
309 def _layoutInterior(self):
310 pass # By default, there is no layout to be done.
311
312 def _bindEventHandlers(self):
313 for eventSource, eventType, eventHandler in self._eventsToBind():
314 eventSource.Bind(eventType, eventHandler)
315
316 def _eventsToBind(self):
317 """
318 _eventsToBind(self) -> [(eventSource, eventType, eventHandler), ...]
319
320 _eventsToBind returns a list of eventSource, eventType,
321 eventHandlers tuples that will be bound. This method can be
322 extended to bind additional events. In that case, don't
323 forget to call _eventsToBind on the super class. """
324 return [(self._text, wx.EVT_KEY_DOWN, self.OnKeyDown),
325 (self._text, wx.EVT_TEXT, self.OnText),
326 (self._button, wx.EVT_BUTTON, self.OnMouseClick)]
327
328 # Event handlers
329
330 def OnMouseClick(self, event):
331 self.Popup()
332 # Note that we don't call event.Skip() to prevent popping up the
333 # ComboBox's own box.
334
335 def OnKeyDown(self, keyEvent):
336 if self._keyShouldNavigate(keyEvent):
337 self._navigateUpOrDown(keyEvent)
338 elif self._keyShouldPopUpTree(keyEvent):
339 self.Popup()
340 else:
341 keyEvent.Skip()
342
343 def _keyShouldPopUpTree(self, keyEvent):
344 return (keyEvent.AltDown() or keyEvent.MetaDown()) and \
345 keyEvent.GetKeyCode() == wx.WXK_DOWN
346
347 def _keyShouldNavigate(self, keyEvent):
348 return keyEvent.GetKeyCode() in (wx.WXK_DOWN, wx.WXK_UP) and not \
349 self._keyShouldPopUpTree(keyEvent)
350
351 def _navigateUpOrDown(self, keyEvent):
352 item = self.GetSelection()
353 if item:
354 navigationMethods = {wx.WXK_DOWN: self._tree.GetNextItem,
355 wx.WXK_UP: self._tree.GetPreviousItem}
356 getNextItem = navigationMethods[keyEvent.GetKeyCode()]
357 nextItem = getNextItem(item)
358 else:
359 nextItem = self._tree.GetFirstItem()
360 if nextItem:
361 self.SetSelection(nextItem)
362
363 def OnText(self, event):
364 event.Skip()
365 item = self.FindString(self._text.GetValue())
366 if item:
367 if self._tree.GetSelection() != item:
368 self._tree.SelectItem(item)
369 else:
370 self._tree.Unselect()
371
372 # Methods called by the PopupFrame, to let the ComboTreeBox know
373 # about what the user did.
374
375 def NotifyItemSelected(self, text):
376 """ Simulate selection of an item by the user. This is meant to
377 be called by the PopupFrame when the user selects an item. """
378 self._text.SetValue(text)
379 self._postComboBoxSelectedEvent(text)
380 self.SetFocus()
381
382 def _postComboBoxSelectedEvent(self, text):
383 """ Simulate a selection event. """
384 event = wx.CommandEvent(wx.wxEVT_COMMAND_COMBOBOX_SELECTED,
385 self.GetId())
386 event.SetString(text)
387 self.GetEventHandler().ProcessEvent(event)
388
389 def NotifyNoItemSelected(self):
390 """ This is called by the PopupFrame when the user closes the
391 PopupFrame, without selecting an item. """
392 self.SetFocus()
393
394 # Misc methods, not part of the ComboBox API.
395
396 def Popup(self):
397 """
398 Popup(self)
399
400 Pops up the frame with the tree.
401 """
402 comboBoxSize = self.GetSize()
403 x, y = self.GetParent().ClientToScreen(self.GetPosition())
404 y += comboBoxSize[1]
405 width = comboBoxSize[0]
406 height = 300
407 self._popupFrame.SetDimensions(x, y, width, height)
408 # On wxGTK, when the Combobox width has been increased a call
409 # to SetMinSize is needed to force a resize of the popupFrame:
410 self._popupFrame.SetMinSize((width, height))
411 self._popupFrame.Show()
412
413 def GetTree(self):
414 """
415 GetTree(self) -> wx.TreeCtrl
416
417 Returns the tree control that is popped up.
418 """
419 return self._popupFrame.GetTree()
420
421 def FindClientData(self, clientData, parent=None):
422 """
423 FindClientData(self, PyObject clientData, TreeItemId parent=None)
424 -> TreeItemId
425
426 Finds the *first* item in the tree with client data equal to the
427 given clientData. If no such item exists, an invalid item is
428 returned.
429 """
430 parent = parent or self._tree.GetRootItem()
431 child, cookie = self._tree.GetFirstChild(parent)
432 while child:
433 if self.GetClientData(child) == clientData:
434 return child
435 else:
436 result = self.FindClientData(clientData, child)
437 if result:
438 return result
439 child, cookie = self._tree.GetNextChild(parent, cookie)
440 return child
441
442 def SetClientDataSelection(self, clientData):
443 """
444 SetClientDataSelection(self, PyObject clientData) -> bool
445
446 Selects the item with the provided clientData in the control.
447 Returns True if the item belonging to the clientData has been
448 selected, False if it wasn't found in the control.
449 """
450 item = self.FindClientData(clientData)
451 if item:
452 self._tree.SelectItem(item)
453 string = self._tree.GetItemText(item)
454 if self._text.GetValue() != string:
455 self._text.SetValue(string)
456 return True
457 else:
458 return False
459
460 # The following methods are all part of the ComboBox API (actually
461 # the ControlWithItems API) and have been adapted to take TreeItemIds
462 # as parameter and return TreeItemIds, rather than indices.
463
464 def Append(self, itemText, parent=None, clientData=None):
465 """
466 Append(self, String itemText, TreeItemId parent=None, PyObject
467 clientData=None) -> TreeItemId
468
469 Adds the itemText to the control, associating the given clientData
470 with the item if not None. If parent is None, itemText is added
471 as a root item, else itemText is added as a child item of
472 parent. The return value is the TreeItemId of the newly added
473 item. """
474 if parent is None:
475 parent = self._tree.GetRootItem()
476 item = self._tree.AppendItem(parent, itemText,
477 data=wx.TreeItemData(clientData))
478 if self._sort:
479 self._tree.SortChildren(parent)
480 return item
481
482 def Clear(self):
483 """
484 Clear(self)
485
486 Removes all items from the control.
487 """
488 return self._tree.DeleteAllItems()
489
490 def Delete(self, item):
491 """
492 Delete(self, TreeItemId item)
493
494 Deletes the item from the control.
495 """
496 return self._tree.Delete(item)
497
498 def FindString(self, string, parent=None):
499 """
500 FindString(self, String string, TreeItemId parent=None) -> TreeItemId
501
502 Finds the *first* item in the tree with a label equal to the
503 given string. If no such item exists, an invalid item is
504 returned.
505 """
506 parent = parent or self._tree.GetRootItem()
507 child, cookie = self._tree.GetFirstChild(parent)
508 while child:
509 if self._tree.GetItemText(child) == string:
510 return child
511 else:
512 result = self.FindString(string, child)
513 if result:
514 return result
515 child, cookie = self._tree.GetNextChild(parent, cookie)
516 return child
517
518 def GetSelection(self):
519 """
520 GetSelection(self) -> TreeItemId
521
522 Returns the TreeItemId of the selected item or an invalid item
523 if no item is selected.
524 """
525 selectedItem = self._tree.GetSelection()
526 if selectedItem and selectedItem != self._tree.GetRootItem():
527 return selectedItem
528 else:
529 return self.FindString(self.GetValue())
530
531 def GetString(self, item):
532 """
533 GetString(self, TreeItemId item) -> String
534
535 Returns the label of the given item.
536 """
537 if item:
538 return self._tree.GetItemText(item)
539 else:
540 return ''
541
542 def GetStringSelection(self):
543 """
544 GetStringSelection(self) -> String
545
546 Returns the label of the selected item or an empty string if no item
547 is selected.
548 """
549 return self.GetValue()
550
551 def Insert(self, itemText, previous=None, parent=None, clientData=None):
552 """
553 Insert(self, String itemText, TreeItemId previous=None, TreeItemId
554 parent=None, PyObject clientData=None) -> TreeItemId
555
556 Insert an item into the control before the ``previous`` item
557 and/or as child of the ``parent`` item. The itemText is associated
558 with clientData when not None.
559 """
560 data = wx.TreeItemData(clientData)
561 if parent is None:
562 parent = self._tree.GetRootItem()
563 if previous is None:
564 item = self._tree.InsertItemBefore(parent, 0, itemText, data=data)
565 else:
566 item = self._tree.InsertItem(parent, previous, itemText, data=data)
567 if self._sort:
568 self._tree.SortChildren(parent)
569 return item
570
571 def IsEmpty(self):
572 """
573 IsEmpty(self) -> bool
574
575 Returns True if the control is empty or False if it has some items.
576 """
577 return self.GetCount() == 0
578
579 def GetCount(self):
580 """
581 GetCount(self) -> int
582
583 Returns the number of items in the control.
584 """
585 # Note: We don't need to substract 1 for the hidden root item,
586 # because the TreeCtrl does that for us
587 return self._tree.GetCount()
588
589 def SetSelection(self, item):
590 """
591 SetSelection(self, TreeItemId item)
592
593 Sets the provided item to be the selected item.
594 """
595 self._tree.SelectItem(item)
596 self._text.SetValue(self._tree.GetItemText(item))
597
598 Select = SetSelection
599
600 def SetString(self, item, string):
601 """
602 SetString(self, TreeItemId item, String string)
603
604 Sets the label for the provided item.
605 """
606 self._tree.SetItemText(item, string)
607 if self._sort:
608 self._tree.SortChildren(self._tree.GetItemParent(item))
609
610 def SetStringSelection(self, string):
611 """
612 SetStringSelection(self, String string) -> bool
613
614 Selects the item with the provided string in the control.
615 Returns True if the provided string has been selected, False if
616 it wasn't found in the control.
617 """
618 item = self.FindString(string)
619 if item:
620 if self._text.GetValue() != string:
621 self._text.SetValue(string)
622 self._tree.SelectItem(item)
623 return True
624 else:
625 return False
626
627 def GetClientData(self, item):
628 """
629 GetClientData(self, TreeItemId item) -> PyObject
630
631 Returns the client data associated with the given item, if any.
632 """
633 return self._tree.GetItemPyData(item)
634
635 def SetClientData(self, item, clientData):
636 """
637 SetClientData(self, TreeItemId item, PyObject clientData)
638
639 Associate the given client data with the provided item.
640 """
641 self._tree.SetItemPyData(item, clientData)
642
643 def GetValue(self):
644 """
645 GetValue(self) -> String
646
647 Returns the current value in the combobox text field.
648 """
649 if self._text == self:
650 return super(BaseComboTreeBox, self).GetValue()
651 else:
652 return self._text.GetValue()
653
654 def SetValue(self, value):
655 """
656 SetValue(self, String value)
657
658 Sets the text for the combobox text field.
659
660 NB: For a combobox with wxCB_READONLY style the string must be
661 in the combobox choices list, otherwise the call to SetValue()
662 is ignored.
663 """
664 item = self._tree.GetSelection()
665 if not item or self._tree.GetItemText(item) != value:
666 item = self.FindString(value)
667 if self._readOnly and not item:
668 return
669 if self._text == self:
670 super(BaseComboTreeBox, self).SetValue(value)
671 else:
672 self._text.SetValue(value)
673 if item:
674 if self._tree.GetSelection() != item:
675 self._tree.SelectItem(item)
676 else:
677 self._tree.Unselect()
678
679
680 class NativeComboTreeBox(BaseComboTreeBox, wx.ComboBox):
681 """ NativeComboTreeBox, and any subclass, uses the native ComboBox as
682 basis, but prevent it from popping up its drop down list and
683 instead pops up a PopupFrame containing a tree of items. """
684
685 def _eventsToBind(self):
686 events = super(NativeComboTreeBox, self)._eventsToBind()
687 # Bind all mouse click events to self.OnMouseClick so we can
688 # intercept those events and prevent the native Combobox from
689 # popping up its list of choices.
690 for eventType in (wx.EVT_LEFT_DOWN, wx.EVT_LEFT_DCLICK,
691 wx.EVT_MIDDLE_DOWN, wx.EVT_MIDDLE_DCLICK,
692 wx.EVT_RIGHT_DOWN, wx.EVT_RIGHT_DCLICK):
693 events.append((self._button, eventType, self.OnMouseClick))
694 if self._readOnly:
695 events.append((self, wx.EVT_CHAR, self.OnChar))
696 return events
697
698 def OnChar(self, event):
699 # OnChar is only called when in read only mode. We don't call
700 # event.Skip() on purpose, to prevent the characters from being
701 # displayed in the text field.
702 pass
703
704
705 class MSWComboTreeBox(NativeComboTreeBox):
706 """ MSWComboTreeBox adds one piece of functionality as compared to
707 NativeComboTreeBox: when the user browses through the tree, the
708 ComboTreeBox's text field is continuously updated to show the
709 currently selected item in the tree. If the user cancels
710 selecting a new item from the tree, e.g. by hitting escape, the
711 previous value (the one that was selected before the PopupFrame
712 was popped up) is restored. """
713
714 def _createPopupFrame(self):
715 return MSWPopupFrame(self)
716
717 def _eventsToBind(self):
718 events = super(MSWComboTreeBox, self)._eventsToBind()
719 events.append((self._tree, wx.EVT_TREE_SEL_CHANGED,
720 self.OnSelectionChangedInTree))
721 return events
722
723 def OnSelectionChangedInTree(self, event):
724 if self.IsBeingDeleted():
725 return
726 item = event.GetItem()
727 if item:
728 selectedValue = self._tree.GetItemText(item)
729 if self.GetValue() != selectedValue:
730 self.SetValue(selectedValue)
731 event.Skip()
732
733 def _keyShouldPopUpTree(self, keyEvent):
734 return super(MSWComboTreeBox, self)._keyShouldPopUpTree(keyEvent) or \
735 (keyEvent.GetKeyCode() == wx.WXK_F4) or \
736 ((keyEvent.AltDown() or keyEvent.MetaDown()) and \
737 keyEvent.GetKeyCode() == wx.WXK_UP)
738
739 def SetValue(self, value):
740 """ Extend SetValue to also select the text in the
741 ComboTreeBox's text field. """
742 super(MSWComboTreeBox, self).SetValue(value)
743 # We select the text in the ComboTreeBox's text field.
744 # There is a slight complication, however. When the control is
745 # deleted, SetValue is called. But if we call SetMark at that
746 # time, wxPython will crash. We can prevent this by comparing the
747 # result of GetLastPosition and the length of the value. If they
748 # match, all is fine. If they don't match, we don't call SetMark.
749 if self._text.GetLastPosition() == len(value):
750 self._text.SetMark(0, self._text.GetLastPosition())
751
752 def Popup(self, *args, **kwargs):
753 """ Extend Popup to store a copy of the current value, so we can
754 restore it later (in NotifyNoItemSelected). This is necessary
755 because MSWComboTreeBox will change the value as the user
756 browses through the items in the popped up tree. """
757 self._previousValue = self.GetValue()
758 super(MSWComboTreeBox, self).Popup(*args, **kwargs)
759
760 def NotifyNoItemSelected(self, *args, **kwargs):
761 """ Restore the value copied previously, because the user has
762 not selected a new value. """
763 self.SetValue(self._previousValue)
764 super(MSWComboTreeBox, self).NotifyNoItemSelected(*args, **kwargs)
765
766
767 class MACComboTreeBox(NativeComboTreeBox):
768 def _createPopupFrame(self):
769 return MACPopupFrame(self)
770
771 def _createButton(self):
772 return self.GetChildren()[0] # The choice button
773
774 def _keyShouldNavigate(self, keyEvent):
775 return False # No navigation with up and down on wxMac
776
777 def _keyShouldPopUpTree(self, keyEvent):
778 return super(MACComboTreeBox, self)._keyShouldPopUpTree(keyEvent) or \
779 keyEvent.GetKeyCode() == wx.WXK_DOWN
780
781
782 class GTKComboTreeBox(BaseComboTreeBox, wx.Panel):
783 """ The ComboTreeBox widget for wxGTK. This is actually a work
784 around because on wxGTK, there doesn't seem to be a way to intercept
785 mouse events sent to the Combobox. Intercepting those events is
786 necessary to prevent the Combobox from popping up the list and pop up
787 the tree instead. So, until wxPython makes intercepting those events
788 possible we build a poor man's Combobox ourselves using a TextCtrl and
789 a BitmapButton. """
790
791 def _createPopupFrame(self):
792 return GTKPopupFrame(self)
793
794 def _createTextCtrl(self):
795 if self._readOnly:
796 style = wx.TE_READONLY
797 else:
798 style = 0
799 return wx.TextCtrl(self, style=style)
800
801 def _createButton(self):
802 bitmap = wx.ArtProvider.GetBitmap(wx.ART_GO_DOWN, client=wx.ART_BUTTON)
803 return wx.BitmapButton(self, bitmap=bitmap)
804
805 def _layoutInterior(self):
806 panelSizer = wx.BoxSizer(wx.HORIZONTAL)
807 panelSizer.Add(self._text, flag=wx.EXPAND, proportion=1)
808 panelSizer.Add(self._button)
809 self.SetSizerAndFit(panelSizer)
810
811
812 # ---------------------------------------------------------------------------
813
814
815 def ComboTreeBox(*args, **kwargs):
816 """ Factory function to create the right ComboTreeBox depending on
817 platform. You may force a specific class, e.g. for testing
818 purposes, by setting the keyword argument 'platform', e.g.
819 'platform=GTK' or 'platform=MSW' or platform='MAC'. """
820
821 platform = kwargs.pop('platform', None) or wx.PlatformInfo[0][4:7]
822 ComboTreeBoxClassName = '%sComboTreeBox' % platform
823 ComboTreeBoxClass = globals()[ComboTreeBoxClassName]
824 return ComboTreeBoxClass(*args, **kwargs)
825