]> git.saurik.com Git - wxWidgets.git/blob - wxPython/demo/ComboCtrl.py
fixed child windows scrolling to use wxSIZE_ALLOW_MINUS_ONE
[wxWidgets.git] / wxPython / demo / ComboCtrl.py
1
2 import wx
3 import wx.combo
4 import os
5
6 #----------------------------------------------------------------------
7
8 class NullLog:
9 def write(*args):
10 pass
11
12
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
19 # GetControl method.
20
21 class ListCtrlComboPopup(wx.ListCtrl, wx.combo.ComboPopup):
22
23 def __init__(self, log=None):
24 if log:
25 self.log = log
26 else:
27 self.log = NullLog()
28
29
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())
35
36 # Also init the ComboPopup base class.
37 wx.combo.ComboPopup.__init__(self)
38
39
40 def AddItem(self, txt):
41 self.InsertStringItem(self.GetItemCount(), txt)
42
43 def OnMotion(self, evt):
44 item, flags = self.HitTest(evt.GetPosition())
45 if item >= 0:
46 self.Select(item)
47 self.curitem = item
48
49 def OnLeftDown(self, evt):
50 self.value = self.curitem
51 self.Dismiss()
52
53
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.
57
58
59 # This is called immediately after construction finishes. You can
60 # use self.GetCombo if needed to get to the ComboCtrl instance.
61 def Init(self):
62 self.log.write("ListCtrlComboPopup.Init")
63 self.value = -1
64 self.curitem = -1
65
66
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)
74 return True
75
76
77 # Return the widget that is to be used for the popup
78 def GetControl(self):
79 #self.log.write("ListCtrlComboPopup.GetControl")
80 return self
81
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:
88 self.Select(idx)
89
90 # Return a string representation of the current item.
91 def GetStringValue(self):
92 self.log.write("ListCtrlComboPopup.GetStringValue")
93 if self.value >= 0:
94 return self.GetItemText(self.value)
95 return ""
96
97 # Called immediately after the popup is shown
98 def OnPopup(self):
99 self.log.write("ListCtrlComboPopup.OnPopup")
100 wx.combo.ComboPopup.OnPopup(self)
101
102 # Called when popup is dismissed
103 def OnDismiss(self):
104 self.log.write("ListCtrlComboPopup.OnDismiss")
105 wx.combo.ComboPopup.OnDismiss(self)
106
107 # This is called to custom paint in the combo control itself
108 # (ie. not the popup). Default implementation draws value as
109 # string.
110 def PaintComboControl(self, dc, rect):
111 self.log.write("ListCtrlComboPopup.PaintComboControl")
112 wx.combo.ComboPopup.PaintComboControl(self, dc, rect)
113
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)
119
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)
125
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)
134
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
138 # immediately.
139 # Default returns false.
140 def LazyCreate(self):
141 self.log.write("ListCtrlComboPopup.LazyCreate")
142 return wx.combo.ComboPopup.LazyCreate(self)
143
144
145
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.)
149
150 class TreeCtrlComboPopup(wx.combo.ComboPopup):
151
152 # overridden ComboPopup methods
153
154 def Init(self):
155 self.value = None
156 self.curitem = None
157
158
159 def Create(self, parent):
160 self.tree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
161 |wx.TR_HAS_BUTTONS
162 |wx.TR_SINGLE
163 |wx.TR_LINES_AT_ROOT
164 |wx.SIMPLE_BORDER)
165 self.tree.Bind(wx.EVT_MOTION, self.OnMotion)
166 self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
167
168
169 def GetControl(self):
170 return self.tree
171
172
173 def GetStringValue(self):
174 if self.value:
175 return self.tree.GetItemText(self.value)
176 return ""
177
178
179 def OnPopup(self):
180 if self.value:
181 self.tree.EnsureVisible(self.value)
182 self.tree.SelectItem(self.value)
183
184
185 def SetStringValue(self, value):
186 # this assumes that item strings are unique...
187 root = self.tree.GetRootItem()
188 if not root:
189 return
190 found = self.FindItem(root, value)
191 if found:
192 self.value = found
193 self.tree.SelectItem(found)
194
195
196 def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
197 return wx.Size(minWidth, min(200, maxHeight))
198
199
200 # helpers
201
202 def FindItem(self, parentItem, text):
203 item, cookie = self.tree.GetFirstChild(parentItem)
204 while item:
205 if self.tree.GetItemText(item) == text:
206 return item
207 if self.tree.ItemHasChildren(item):
208 item = self.FindItem(item, text)
209 item, cookie = self.tree.GetNextChild(parentItem, cookie)
210 return wx.TreeItemId();
211
212
213 def AddItem(self, value, parent=None):
214 if not parent:
215 root = self.tree.GetRootItem()
216 if not root:
217 root = self.tree.AddRoot("<hidden root>")
218 parent = root
219
220 item = self.tree.AppendItem(parent, value)
221 return item
222
223
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)
229 self.curitem = item
230 evt.Skip()
231
232
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:
237 self.curitem = item
238 self.value = item
239 self.Dismiss()
240 evt.Skip()
241
242
243 #----------------------------------------------------------------------
244 # Here we subclass wx.combo.ComboCtrl to do some custom popup animation
245
246 CUSTOM_COMBOBOX_ANIMATION_DURATION = 200
247
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)
253
254
255 def AnimateShow(self, rect, flags):
256 self.aniStart = wx.GetLocalTimeMillis()
257 self.aniRect = wx.Rect(*rect)
258 self.aniFlags = flags
259
260 dc = wx.ScreenDC()
261 bmp = wx.EmptyBitmap(rect.width, rect.height)
262 mdc = wx.MemoryDC(bmp)
263 if "wxMac" in wx.PlatformInfo:
264 pass
265 else:
266 mdc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
267 del mdc
268 self.aniBackBitmap = bmp
269
270 self.aniTimer.Start(10, wx.TIMER_CONTINUOUS)
271 self.OnTimer(None)
272 return False
273
274
275 def OnTimer(self, evt):
276 stopTimer = False
277 popup = self.GetPopupControl().GetControl()
278 rect = self.aniRect
279 dc = wx.ScreenDC()
280
281 if self.IsPopupWindowState(self.Hidden):
282 stopTimer = True
283 else:
284 pos = wx.GetLocalTimeMillis() - self.aniStart
285 if pos < CUSTOM_COMBOBOX_ANIMATION_DURATION:
286 # Actual animation happens here
287 width = rect.width
288 height = rect.height
289
290 center_x = rect.x + (width/2)
291 center_y = rect.y + (height/2)
292
293 dc.SetPen( wx.BLACK_PEN )
294 dc.SetBrush( wx.TRANSPARENT_BRUSH )
295
296 w = (((pos*256)/CUSTOM_COMBOBOX_ANIMATION_DURATION)*width)/256
297 ratio = float(w) / float(width)
298 h = int(height * ratio)
299
300 dc.DrawBitmap( self.aniBackBitmap, rect.x, rect.y )
301 dc.DrawRectangle( center_x - w/2, center_y - h/2, w, h )
302 else:
303 stopTimer = True
304
305 if stopTimer:
306 dc.DrawBitmap( self.aniBackBitmap, rect.x, rect.y )
307 popup.Move( (0, 0) )
308 self.aniTimer.Stop()
309 self.DoShowPopup( rect, self.aniFlags )
310
311
312 #----------------------------------------------------------------------
313 # FileSelectorCombo displays a dialog instead of a popup control, it
314 # also uses a custom bitmap on the combo button.
315
316 class FileSelectorCombo(wx.combo.ComboCtrl):
317 def __init__(self, *args, **kw):
318 wx.combo.ComboCtrl.__init__(self, *args, **kw)
319
320 # make a custom bitmap showing "..."
321 bw, bh = 14, 16
322 bmp = wx.EmptyBitmap(bw,bh)
323 dc = wx.MemoryDC(bmp)
324
325 # clear to a specific background colour
326 bgcolor = wx.Colour(255,254,255)
327 dc.SetBackground(wx.Brush(bgcolor))
328 dc.Clear()
329
330 # draw the label onto the bitmap
331 label = "..."
332 font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
333 font.SetWeight(wx.FONTWEIGHT_BOLD)
334 dc.SetFont(font)
335 tw,th = dc.GetTextExtent(label)
336 dc.DrawText(label, (bw-tw)/2, (bw-tw)/2)
337 del dc
338
339 # now apply a mask using the bgcolor
340 bmp.SetMaskColour(bgcolor)
341
342 # and tell the ComboCtrl to use it
343 self.SetButtonBitmaps(bmp, True)
344
345
346 # Overridden from ComboCtrl, called when the combo button is clicked
347 def OnButtonClick(self):
348 path = ""
349 name = ""
350 if self.GetValue():
351 path, name = os.path.split(self.GetValue())
352
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())
357 dlg.Destroy()
358 self.SetFocus()
359
360 # Overridden from ComboCtrl to avoid assert since there is no ComboPopup
361 def DoSetPopupControl(self, popup):
362 pass
363
364
365 #----------------------------------------------------------------------
366
367
368 class TestPanel(wx.Panel):
369 def __init__(self, parent, log):
370 self.log = log
371 wx.Panel.__init__(self, parent, -1)
372
373 fgs = wx.FlexGridSizer(cols=3, hgap=10, vgap=10)
374
375 cc = self.MakeLCCombo(log=self.log)
376 fgs.Add(cc)
377 fgs.Add((10,10))
378 fgs.Add(wx.StaticText(self, -1, "wx.ComboCtrl with a ListCtrl popup"))
379
380 cc = self.MakeLCCombo(style=wx.CB_READONLY)
381 fgs.Add(cc)
382 fgs.Add((10,10))
383 fgs.Add(wx.StaticText(self, -1, " Read-only"))
384
385 cc = self.MakeLCCombo()
386 cc.SetButtonPosition(side=wx.LEFT)
387 fgs.Add(cc)
388 fgs.Add((10,10))
389 fgs.Add(wx.StaticText(self, -1, " Button on the left"))
390
391 cc = self.MakeLCCombo()
392 cc.SetPopupMaxHeight(250)
393 fgs.Add(cc)
394 fgs.Add((10,10))
395 fgs.Add(wx.StaticText(self, -1, " Max height of popup set"))
396
397 cc = wx.combo.ComboCtrl(self, size=(250,-1))
398 tcp = TreeCtrlComboPopup()
399 cc.SetPopupControl(tcp)
400 fgs.Add(cc)
401 fgs.Add((10,10))
402 fgs.Add(wx.StaticText(self, -1, "TreeCtrl popup"))
403 # add some items to the tree
404 for i in range(5):
405 item = tcp.AddItem('Item %d' % (i+1))
406 for j in range(15):
407 tcp.AddItem('Subitem %d-%d' % (i+1, j+1), parent=item)
408
409 cc = ComboCtrlWithCustomPopupAnim(self, size=(250, -1))
410 popup = ListCtrlComboPopup()
411 cc.SetPopupMaxHeight(150)
412 cc.SetPopupControl(popup)
413 fgs.Add(cc)
414 fgs.Add((10,10))
415 fgs.Add(wx.StaticText(self, -1, "Custom popup animation"))
416 for word in "How cool was that!? Way COOL!".split():
417 popup.AddItem(word)
418 if "wxMac" in wx.PlatformInfo:
419 cc.SetValue("Sorry, animation not working yet on Mac")
420
421
422 cc = FileSelectorCombo(self, size=(250, -1))
423 fgs.Add(cc)
424 fgs.Add((10,10))
425 fgs.Add(wx.StaticText(self, -1, "Custom popup action, and custom button bitmap"))
426
427 box = wx.BoxSizer()
428 box.Add(fgs, 1, wx.EXPAND|wx.ALL, 20)
429 self.SetSizer(box)
430
431
432 def MakeLCCombo(self, log=None, style=0):
433 # Create a ComboCtrl
434 cc = wx.combo.ComboCtrl(self, style=style, size=(250,-1))
435
436 # Create a Popup
437 popup = ListCtrlComboPopup(log)
438
439 # Associate them with each other. This also triggers the
440 # creation of the ListCtrl.
441 cc.SetPopupControl(popup)
442
443 # Add some items to the listctrl.
444 for x in range(75):
445 popup.AddItem("Item-%02d" % x)
446
447 return cc
448
449
450 #----------------------------------------------------------------------
451
452 def runTest(frame, nb, log):
453 win = TestPanel(nb, log)
454 return win
455
456 #----------------------------------------------------------------------
457
458
459
460 overview = """<html><body>
461 <h2><center>wx.combo.ComboCtrl</center></h2>
462
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.
466
467 </body></html>
468 """
469
470
471
472 if __name__ == '__main__':
473 import sys,os
474 import run
475 run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])