6 #---------------------------------------------------------------------- 
  13 # This class is used to provide an interface between a ComboCtrl and a 
  14 # ListCtrl that is used as the popoup for the combo widget.  In this 
  15 # case we use multiple inheritance to derive from both wx.ListCtrl and 
  16 # wx.ComboPopup, but it also works well when deriving from just 
  17 # ComboPopup and using a has-a relationship with the popup control, 
  18 # you just need to be sure to return the control itself from the 
  21 class ListCtrlComboPopup(wx
.ListCtrl
, wx
.combo
.ComboPopup
): 
  23     def __init__(self
, log
=None): 
  30         # Since we are using multiple inheritance, and don't know yet 
  31         # which window is to be the parent, we'll do 2-phase create of 
  32         # the ListCtrl instead, and call its Create method later in 
  33         # our Create method.  (See Create below.) 
  34         self
.PostCreate(wx
.PreListCtrl()) 
  36         # Also init the ComboPopup base class. 
  37         wx
.combo
.ComboPopup
.__init
__(self
) 
  40     def AddItem(self
, txt
): 
  41         self
.InsertStringItem(self
.GetItemCount(), txt
) 
  43     def OnMotion(self
, evt
): 
  44         item
, flags 
= self
.HitTest(evt
.GetPosition()) 
  49     def OnLeftDown(self
, evt
): 
  50         self
.value 
= self
.curitem
 
  54     # The following methods are those that are overridable from the 
  55     # ComboPopup base class.  Most of them are not required, but all 
  56     # are shown here for demonstration purposes. 
  59     # This is called immediately after construction finishes.  You can 
  60     # use self.GetCombo if needed to get to the ComboCtrl instance. 
  62         self
.log
.write("ListCtrlComboPopup.Init") 
  67     # Create the popup child control.  Return true for success. 
  68     def Create(self
, parent
): 
  69         self
.log
.write("ListCtrlComboPopup.Create") 
  70         wx
.ListCtrl
.Create(self
, parent
, 
  71                            style
=wx
.LC_LIST|wx
.LC_SINGLE_SEL|wx
.SIMPLE_BORDER
) 
  72         self
.Bind(wx
.EVT_MOTION
, self
.OnMotion
) 
  73         self
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
) 
  77     # Return the widget that is to be used for the popup 
  79         #self.log.write("ListCtrlComboPopup.GetControl") 
  82     # Called just prior to displaying the popup, you can use it to 
  83     # 'select' the current item. 
  84     def SetStringValue(self
, val
): 
  85         self
.log
.write("ListCtrlComboPopup.SetStringValue") 
  86         idx 
= self
.FindItem(-1, val
) 
  87         if idx 
!= wx
.NOT_FOUND
: 
  90     # Return a string representation of the current item. 
  91     def GetStringValue(self
): 
  92         self
.log
.write("ListCtrlComboPopup.GetStringValue") 
  94             return self
.GetItemText(self
.value
) 
  97     # Called immediately after the popup is shown 
  99         self
.log
.write("ListCtrlComboPopup.OnPopup") 
 100         wx
.combo
.ComboPopup
.OnPopup(self
) 
 102     # Called when popup is dismissed 
 104         self
.log
.write("ListCtrlComboPopup.OnDismiss") 
 105         wx
.combo
.ComboPopup
.OnDismiss(self
) 
 107     # This is called to custom paint in the combo control itself 
 108     # (ie. not the popup).  Default implementation draws value as 
 110     def PaintComboControl(self
, dc
, rect
): 
 111         self
.log
.write("ListCtrlComboPopup.PaintComboControl") 
 112         wx
.combo
.ComboPopup
.PaintComboControl(self
, dc
, rect
) 
 114     # Receives key events from the parent ComboCtrl.  Events not 
 115     # handled should be skipped, as usual. 
 116     def OnComboKeyEvent(self
, event
): 
 117         self
.log
.write("ListCtrlComboPopup.OnComboKeyEvent") 
 118         wx
.combo
.ComboPopup
.OnComboKeyEvent(self
, event
) 
 120     # Implement if you need to support special action when user 
 121     # double-clicks on the parent wxComboCtrl. 
 122     def OnComboDoubleClick(self
): 
 123         self
.log
.write("ListCtrlComboPopup.OnComboDoubleClick") 
 124         wx
.combo
.ComboPopup
.OnComboDoubleClick(self
) 
 126     # Return final size of popup. Called on every popup, just prior to OnPopup. 
 127     # minWidth = preferred minimum width for window 
 128     # prefHeight = preferred height. Only applies if > 0, 
 129     # maxHeight = max height for window, as limited by screen size 
 130     #   and should only be rounded down, if necessary. 
 131     def GetAdjustedSize(self
, minWidth
, prefHeight
, maxHeight
): 
 132         self
.log
.write("ListCtrlComboPopup.GetAdjustedSize: %d, %d, %d" % (minWidth
, prefHeight
, maxHeight
)) 
 133         return wx
.combo
.ComboPopup
.GetAdjustedSize(self
, minWidth
, prefHeight
, maxHeight
) 
 135     # Return true if you want delay the call to Create until the popup 
 136     # is shown for the first time. It is more efficient, but note that 
 137     # it is often more convenient to have the control created 
 139     # Default returns false. 
 140     def LazyCreate(self
): 
 141         self
.log
.write("ListCtrlComboPopup.LazyCreate") 
 142         return wx
.combo
.ComboPopup
.LazyCreate(self
) 
 146 #---------------------------------------------------------------------- 
 147 # This class is a popup containing a TreeCtrl.  This time we'll use a 
 148 # has-a style (instead of is-a like above.) 
 150 class TreeCtrlComboPopup(wx
.combo
.ComboPopup
): 
 152     # overridden ComboPopup methods 
 159     def Create(self
, parent
): 
 160         self
.tree 
= wx
.TreeCtrl(parent
, style
=wx
.TR_HIDE_ROOT
 
 165         self
.tree
.Bind(wx
.EVT_MOTION
, self
.OnMotion
) 
 166         self
.tree
.Bind(wx
.EVT_LEFT_DOWN
, self
.OnLeftDown
) 
 169     def GetControl(self
): 
 173     def GetStringValue(self
): 
 175             return self
.tree
.GetItemText(self
.value
) 
 181             self
.tree
.EnsureVisible(self
.value
) 
 182             self
.tree
.SelectItem(self
.value
) 
 185     def SetStringValue(self
, value
): 
 186         # this assumes that item strings are unique... 
 187         root 
= self
.tree
.GetRootItem() 
 190         found 
= self
.FindItem(root
, value
) 
 193             self
.tree
.SelectItem(found
) 
 196     def GetAdjustedSize(self
, minWidth
, prefHeight
, maxHeight
): 
 197         return wx
.Size(minWidth
, min(200, maxHeight
)) 
 202     def FindItem(self
, parentItem
, text
):         
 203         item
, cookie 
= self
.tree
.GetFirstChild(parentItem
) 
 205             if self
.tree
.GetItemText(item
) == text
: 
 207             if self
.tree
.ItemHasChildren(item
): 
 208                 item 
= self
.FindItem(item
, text
) 
 209             item
, cookie 
= self
.tree
.GetNextChild(parentItem
, cookie
) 
 210         return wx
.TreeItemId(); 
 213     def AddItem(self
, value
, parent
=None): 
 215             root 
= self
.tree
.GetRootItem() 
 217                 root 
= self
.tree
.AddRoot("<hidden root>") 
 220         item 
= self
.tree
.AppendItem(parent
, value
) 
 224     def OnMotion(self
, evt
): 
 225         # have the selection follow the mouse, like in a real combobox 
 226         item
, flags 
= self
.tree
.HitTest(evt
.GetPosition()) 
 227         if item 
and flags 
& wx
.TREE_HITTEST_ONITEMLABEL
: 
 228             self
.tree
.SelectItem(item
) 
 233     def OnLeftDown(self
, evt
): 
 234         # do the combobox selection 
 235         item
, flags 
= self
.tree
.HitTest(evt
.GetPosition()) 
 236         if item 
and flags 
& wx
.TREE_HITTEST_ONITEMLABEL
: 
 243 #---------------------------------------------------------------------- 
 244 # Here we subclass wx.combo.ComboCtrl to do some custom popup animation 
 246 CUSTOM_COMBOBOX_ANIMATION_DURATION 
= 200 
 248 class ComboCtrlWithCustomPopupAnim(wx
.combo
.ComboCtrl
): 
 249     def __init__(self
, *args
, **kw
): 
 250         wx
.combo
.ComboCtrl
.__init
__(self
, *args
, **kw
) 
 251         self
.Bind(wx
.EVT_TIMER
, self
.OnTimer
) 
 252         self
.aniTimer 
= wx
.Timer(self
) 
 255     def AnimateShow(self
, rect
, flags
): 
 256         self
.aniStart 
= wx
.GetLocalTimeMillis() 
 257         self
.aniRect 
= wx
.Rect(*rect
) 
 258         self
.aniFlags 
= flags
 
 261         bmp 
= wx
.EmptyBitmap(rect
.width
, rect
.height
) 
 262         mdc 
= wx
.MemoryDC(bmp
) 
 263         if "wxMac" in wx
.PlatformInfo
: 
 266             mdc
.Blit(0, 0, rect
.width
, rect
.height
, dc
, rect
.x
, rect
.y
) 
 268         self
.aniBackBitmap 
= bmp
 
 270         self
.aniTimer
.Start(10, wx
.TIMER_CONTINUOUS
) 
 275     def OnTimer(self
, evt
): 
 277         popup 
= self
.GetPopupControl().GetControl() 
 281         if self
.IsPopupWindowState(self
.Hidden
): 
 284             pos 
= wx
.GetLocalTimeMillis() - self
.aniStart
 
 285             if pos 
< CUSTOM_COMBOBOX_ANIMATION_DURATION
: 
 286                 # Actual animation happens here 
 290                 center_x 
= rect
.x 
+ (width
/2) 
 291                 center_y 
= rect
.y 
+ (height
/2) 
 293                 dc
.SetPen( wx
.BLACK_PEN 
) 
 294                 dc
.SetBrush( wx
.TRANSPARENT_BRUSH 
) 
 296                 w 
= (((pos
*256)/CUSTOM_COMBOBOX_ANIMATION_DURATION
)*width
)/256 
 297                 ratio 
= float(w
) / float(width
) 
 298                 h 
= int(height 
* ratio
) 
 300                 dc
.DrawBitmap( self
.aniBackBitmap
, rect
.x
, rect
.y 
) 
 301                 dc
.DrawRectangle( center_x 
- w
/2, center_y 
- h
/2, w
, h 
) 
 306             dc
.DrawBitmap( self
.aniBackBitmap
, rect
.x
, rect
.y 
) 
 309             self
.DoShowPopup( rect
, self
.aniFlags 
) 
 312 #---------------------------------------------------------------------- 
 313 # FileSelectorCombo displays a dialog instead of a popup control, it 
 314 # also uses a custom bitmap on the combo button. 
 316 class FileSelectorCombo(wx
.combo
.ComboCtrl
): 
 317     def __init__(self
, *args
, **kw
): 
 318         wx
.combo
.ComboCtrl
.__init
__(self
, *args
, **kw
) 
 320         # make a custom bitmap showing "..." 
 322         bmp 
= wx
.EmptyBitmap(bw
,bh
) 
 323         dc 
= wx
.MemoryDC(bmp
) 
 325         # clear to a specific background colour 
 326         bgcolor 
= wx
.Colour(255,254,255) 
 327         dc
.SetBackground(wx
.Brush(bgcolor
)) 
 330         # draw the label onto the bitmap 
 332         font 
= wx
.SystemSettings
.GetFont(wx
.SYS_DEFAULT_GUI_FONT
) 
 333         font
.SetWeight(wx
.FONTWEIGHT_BOLD
) 
 335         tw
,th 
= dc
.GetTextExtent(label
) 
 336         dc
.DrawText(label
, (bw
-tw
)/2, (bw
-tw
)/2) 
 339         # now apply a mask using the bgcolor 
 340         bmp
.SetMaskColour(bgcolor
) 
 342         # and tell the ComboCtrl to use it 
 343         self
.SetButtonBitmaps(bmp
, True) 
 346     # Overridden from ComboCtrl, called when the combo button is clicked 
 347     def OnButtonClick(self
): 
 351             path
, name 
= os
.path
.split(self
.GetValue()) 
 353         dlg 
= wx
.FileDialog(self
, "Choose File", path
, name
, 
 354                             "All files (*.*)|*.*", wx
.FD_OPEN
) 
 355         if dlg
.ShowModal() == wx
.ID_OK
: 
 356             self
.SetValue(dlg
.GetPath()) 
 360     # Overridden from ComboCtrl to avoid assert since there is no ComboPopup 
 361     def DoSetPopupControl(self
, popup
): 
 365 #---------------------------------------------------------------------- 
 368 class TestPanel(wx
.Panel
): 
 369     def __init__(self
, parent
, log
): 
 371         wx
.Panel
.__init
__(self
, parent
, -1) 
 373         fgs 
= wx
.FlexGridSizer(cols
=3, hgap
=10, vgap
=10) 
 375         cc 
= self
.MakeLCCombo(log
=self
.log
) 
 378         fgs
.Add(wx
.StaticText(self
, -1, "wx.ComboCtrl with a ListCtrl popup")) 
 380         cc 
= self
.MakeLCCombo(style
=wx
.CB_READONLY
) 
 383         fgs
.Add(wx
.StaticText(self
, -1, "        Read-only")) 
 385         cc 
= self
.MakeLCCombo() 
 386         cc
.SetButtonPosition(side
=wx
.LEFT
) 
 389         fgs
.Add(wx
.StaticText(self
, -1, "        Button on the left")) 
 391         cc 
= self
.MakeLCCombo() 
 392         cc
.SetPopupMaxHeight(250) 
 395         fgs
.Add(wx
.StaticText(self
, -1, "        Max height of popup set")) 
 397         cc 
= wx
.combo
.ComboCtrl(self
, size
=(250,-1)) 
 398         tcp 
= TreeCtrlComboPopup() 
 399         cc
.SetPopupControl(tcp
) 
 402         fgs
.Add(wx
.StaticText(self
, -1, "TreeCtrl popup")) 
 403         # add some items to the tree 
 405             item 
= tcp
.AddItem('Item %d' % (i
+1)) 
 407                 tcp
.AddItem('Subitem %d-%d' % (i
+1, j
+1), parent
=item
) 
 409         cc 
= ComboCtrlWithCustomPopupAnim(self
, size
=(250, -1)) 
 410         popup 
= ListCtrlComboPopup() 
 411         cc
.SetPopupMaxHeight(150) 
 412         cc
.SetPopupControl(popup
) 
 415         fgs
.Add(wx
.StaticText(self
, -1, "Custom popup animation")) 
 416         for word 
in "How cool was that!?  Way COOL!".split(): 
 418         if "wxMac" in wx
.PlatformInfo
: 
 419             cc
.SetValue("Sorry, animation not working yet on Mac") 
 422         cc 
= FileSelectorCombo(self
, size
=(250, -1)) 
 425         fgs
.Add(wx
.StaticText(self
, -1, "Custom popup action, and custom button bitmap")) 
 428         box
.Add(fgs
, 1, wx
.EXPAND|wx
.ALL
, 20) 
 432     def MakeLCCombo(self
, log
=None, style
=0): 
 434         cc 
= wx
.combo
.ComboCtrl(self
, style
=style
, size
=(250,-1)) 
 437         popup 
= ListCtrlComboPopup(log
) 
 439         # Associate them with each other.  This also triggers the 
 440         # creation of the ListCtrl. 
 441         cc
.SetPopupControl(popup
) 
 443         # Add some items to the listctrl. 
 445             popup
.AddItem("Item-%02d" % x
) 
 450 #---------------------------------------------------------------------- 
 452 def runTest(frame
, nb
, log
): 
 453     win 
= TestPanel(nb
, log
) 
 456 #---------------------------------------------------------------------- 
 460 overview 
= """<html><body> 
 461 <h2><center>wx.combo.ComboCtrl</center></h2> 
 463 A combo control is a generic combobox that allows a totally custom 
 464 popup. In addition it has other customization features. For instance, 
 465 position and size of the dropdown button can be changed. 
 472 if __name__ 
== '__main__': 
 475     run
.main(['', os
.path
.basename(sys
.argv
[0])] + sys
.argv
[1:])