]>
Commit | Line | Data |
---|---|---|
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 | ||
428 | box = wx.BoxSizer() | |
429 | box.Add(fgs, 1, wx.EXPAND|wx.ALL, 20) | |
430 | self.SetSizer(box) | |
431 | ||
432 | ||
433 | def MakeLCCombo(self, log=None, style=0): | |
434 | # Create a ComboCtrl | |
435 | cc = wx.combo.ComboCtrl(self, style=style, size=(250,-1)) | |
436 | ||
437 | # Create a Popup | |
438 | popup = ListCtrlComboPopup(log) | |
439 | ||
440 | # Associate them with each other. This also triggers the | |
441 | # creation of the ListCtrl. | |
442 | cc.SetPopupControl(popup) | |
443 | ||
444 | # Add some items to the listctrl. | |
445 | for x in range(75): | |
446 | popup.AddItem("Item-%02d" % x) | |
447 | ||
448 | return cc | |
449 | ||
450 | ||
451 | #---------------------------------------------------------------------- | |
452 | ||
453 | def runTest(frame, nb, log): | |
454 | win = TestPanel(nb, log) | |
455 | return win | |
456 | ||
457 | #---------------------------------------------------------------------- | |
458 | ||
459 | ||
460 | ||
461 | overview = """<html><body> | |
462 | <h2><center>wx.combo.ComboCtrl</center></h2> | |
463 | ||
464 | A combo control is a generic combobox that allows a totally custom | |
465 | popup. In addition it has other customization features. For instance, | |
466 | position and size of the dropdown button can be changed. | |
467 | ||
468 | </body></html> | |
469 | """ | |
470 | ||
471 | ||
472 | ||
473 | if __name__ == '__main__': | |
474 | import sys,os | |
475 | import run | |
476 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) |