]>
Commit | Line | Data |
---|---|---|
84bc0d49 RD |
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| | |
72 | wx.LC_SORT_ASCENDING|wx.SIMPLE_BORDER) | |
73 | self.Bind(wx.EVT_MOTION, self.OnMotion) | |
74 | self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) | |
75 | return True | |
76 | ||
77 | ||
78 | # Return the widget that is to be used for the popup | |
79 | def GetControl(self): | |
80 | #self.log.write("ListCtrlComboPopup.GetControl") | |
81 | return self | |
82 | ||
83 | # Called just prior to displaying the popup, you can use it to | |
84 | # 'select' the current item. | |
85 | def SetStringValue(self, val): | |
86 | self.log.write("ListCtrlComboPopup.SetStringValue") | |
87 | idx = self.FindItem(-1, val) | |
88 | if idx != wx.NOT_FOUND: | |
89 | self.Select(idx) | |
90 | ||
91 | # Return a string representation of the current item. | |
92 | def GetStringValue(self): | |
93 | self.log.write("ListCtrlComboPopup.GetStringValue") | |
94 | if self.value >= 0: | |
95 | return self.GetItemText(self.value) | |
96 | return "" | |
97 | ||
98 | # Called immediately after the popup is shown | |
99 | def OnPopup(self): | |
100 | self.log.write("ListCtrlComboPopup.OnPopup") | |
101 | wx.combo.ComboPopup.OnPopup(self) | |
102 | ||
103 | # Called when popup is dismissed | |
104 | def OnDismiss(self): | |
105 | self.log.write("ListCtrlComboPopup.OnDismiss") | |
106 | wx.combo.ComboPopup.OnDismiss(self) | |
107 | ||
108 | # This is called to custom paint in the combo control itself | |
109 | # (ie. not the popup). Default implementation draws value as | |
110 | # string. | |
111 | def PaintComboControl(self, dc, rect): | |
112 | self.log.write("ListCtrlComboPopup.PaintComboControl") | |
113 | wx.combo.ComboPopup.PaintComboControl(self, dc, rect) | |
114 | ||
115 | # Receives key events from the parent ComboCtrl. Events not | |
116 | # handled should be skipped, as usual. | |
117 | def OnComboKeyEvent(self, event): | |
118 | self.log.write("ListCtrlComboPopup.OnComboKeyEvent") | |
119 | wx.combo.ComboPopup.OnComboKeyEvent(self, event) | |
120 | ||
121 | # Implement if you need to support special action when user | |
122 | # double-clicks on the parent wxComboCtrl. | |
123 | def OnComboDoubleClick(self): | |
124 | self.log.write("ListCtrlComboPopup.OnComboDoubleClick") | |
125 | wx.combo.ComboPopup.OnComboDoubleClick(self) | |
126 | ||
127 | # Return final size of popup. Called on every popup, just prior to OnPopup. | |
128 | # minWidth = preferred minimum width for window | |
129 | # prefHeight = preferred height. Only applies if > 0, | |
130 | # maxHeight = max height for window, as limited by screen size | |
131 | # and should only be rounded down, if necessary. | |
132 | def GetAdjustedSize(self, minWidth, prefHeight, maxHeight): | |
133 | self.log.write("ListCtrlComboPopup.GetAdjustedSize: %d, %d, %d" % (minWidth, prefHeight, maxHeight)) | |
134 | return wx.combo.ComboPopup.GetAdjustedSize(self, minWidth, prefHeight, maxHeight) | |
135 | ||
136 | # Return true if you want delay the call to Create until the popup | |
137 | # is shown for the first time. It is more efficient, but note that | |
138 | # it is often more convenient to have the control created | |
139 | # immediately. | |
140 | # Default returns false. | |
141 | def LazyCreate(self): | |
142 | self.log.write("ListCtrlComboPopup.LazyCreate") | |
143 | return wx.combo.ComboPopup.LazyCreate(self) | |
144 | ||
145 | ||
146 | ||
147 | #---------------------------------------------------------------------- | |
148 | # This class is a popup containing a TreeCtrl. This time we'll use a | |
149 | # has-a style (instead of is-a like above.) | |
150 | ||
151 | class TreeCtrlComboPopup(wx.combo.ComboPopup): | |
152 | ||
153 | # overridden ComboPopup methods | |
154 | ||
155 | def Init(self): | |
156 | self.value = None | |
157 | self.curitem = None | |
158 | ||
159 | ||
160 | def Create(self, parent): | |
161 | self.tree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT | |
162 | |wx.TR_HAS_BUTTONS | |
163 | |wx.TR_SINGLE | |
164 | |wx.TR_LINES_AT_ROOT | |
165 | |wx.SIMPLE_BORDER) | |
166 | self.tree.Bind(wx.EVT_MOTION, self.OnMotion) | |
167 | self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) | |
168 | ||
169 | ||
170 | def GetControl(self): | |
171 | return self.tree | |
172 | ||
173 | ||
174 | def GetStringValue(self): | |
175 | if self.value: | |
176 | return self.tree.GetItemText(self.value) | |
177 | return "" | |
178 | ||
179 | ||
180 | def OnPopup(self): | |
181 | if self.value: | |
182 | self.tree.EnsureVisible(self.value) | |
183 | self.tree.SelectItem(self.value) | |
184 | ||
185 | ||
186 | def SetStringValue(self, value): | |
187 | # this assumes that item strings are unique... | |
188 | root = self.tree.GetRootItem() | |
189 | if not root: | |
190 | return | |
191 | found = self.FindItem(root, value) | |
192 | if found: | |
193 | self.value = found | |
194 | self.tree.SelectItem(found) | |
195 | ||
196 | ||
197 | def GetAdjustedSize(self, minWidth, prefHeight, maxHeight): | |
198 | return wx.Size(minWidth, min(200, maxHeight)) | |
199 | ||
200 | ||
201 | # helpers | |
202 | ||
203 | def FindItem(self, parentItem, text): | |
204 | item, cookie = self.tree.GetFirstChild(parentItem) | |
205 | while item: | |
206 | if self.tree.GetItemText(item) == text: | |
207 | return item | |
208 | if self.tree.ItemHasChildren(item): | |
209 | item = self.FindItem(item, text) | |
210 | item, cookie = self.tree.GetNextChild(parentItem, cookie) | |
211 | return wx.TreeItemId(); | |
212 | ||
213 | ||
214 | def AddItem(self, value, parent=None): | |
215 | if not parent: | |
216 | root = self.tree.GetRootItem() | |
217 | if not root: | |
218 | root = self.tree.AddRoot("<hidden root>") | |
219 | parent = root | |
220 | ||
221 | item = self.tree.AppendItem(parent, value) | |
222 | return item | |
223 | ||
224 | ||
225 | def OnMotion(self, evt): | |
226 | # have the selection follow the mouse, like in a real combobox | |
227 | item, flags = self.tree.HitTest(evt.GetPosition()) | |
228 | if item and flags & wx.TREE_HITTEST_ONITEMLABEL: | |
229 | self.tree.SelectItem(item) | |
230 | self.curitem = item | |
231 | evt.Skip() | |
232 | ||
233 | ||
234 | def OnLeftDown(self, evt): | |
235 | # do the combobox selection | |
236 | item, flags = self.tree.HitTest(evt.GetPosition()) | |
237 | if item and flags & wx.TREE_HITTEST_ONITEMLABEL: | |
238 | self.curitem = item | |
239 | self.value = item | |
240 | self.Dismiss() | |
241 | evt.Skip() | |
242 | ||
243 | ||
244 | #---------------------------------------------------------------------- | |
245 | # Here we subclass wx.combo.ComboCtrl to do some custom popup animation | |
246 | ||
247 | CUSTOM_COMBOBOX_ANIMATION_DURATION = 200 | |
248 | ||
249 | class ComboCtrlWithCustomPopupAnim(wx.combo.ComboCtrl): | |
250 | def __init__(self, *args, **kw): | |
251 | wx.combo.ComboCtrl.__init__(self, *args, **kw) | |
252 | self.Bind(wx.EVT_TIMER, self.OnTimer) | |
253 | self.aniTimer = wx.Timer(self) | |
254 | ||
255 | ||
256 | def AnimateShow(self, rect, flags): | |
257 | self.aniStart = wx.GetLocalTimeMillis() | |
258 | self.aniRect = wx.Rect(*rect) | |
259 | self.aniFlags = flags | |
260 | ||
261 | dc = wx.ScreenDC() | |
262 | bmp = wx.EmptyBitmap(rect.width, rect.height) | |
263 | mdc = wx.MemoryDC(bmp) | |
264 | mdc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y) | |
265 | del mdc | |
266 | self.aniBackBitmap = bmp | |
267 | ||
268 | self.aniTimer.Start(10, wx.TIMER_CONTINUOUS) | |
269 | self.OnTimer(None) | |
270 | return False | |
271 | ||
272 | ||
273 | def OnTimer(self, evt): | |
274 | stopTimer = False | |
275 | popup = self.GetPopupControl().GetControl() | |
276 | rect = self.aniRect | |
277 | dc = wx.ScreenDC() | |
278 | ||
279 | if self.IsPopupWindowState(self.Hidden): | |
280 | stopTimer = True | |
281 | else: | |
282 | pos = wx.GetLocalTimeMillis() - self.aniStart | |
283 | if pos < CUSTOM_COMBOBOX_ANIMATION_DURATION: | |
284 | # Actual animation happens here | |
285 | width = rect.width | |
286 | height = rect.height | |
287 | ||
288 | center_x = rect.x + (width/2) | |
289 | center_y = rect.y + (height/2) | |
290 | ||
291 | dc.SetPen( wx.BLACK_PEN ) | |
292 | dc.SetBrush( wx.TRANSPARENT_BRUSH ) | |
293 | ||
294 | w = (((pos*256)/CUSTOM_COMBOBOX_ANIMATION_DURATION)*width)/256 | |
295 | ratio = float(w) / float(width) | |
296 | h = int(height * ratio) | |
297 | ||
298 | dc.DrawBitmap( self.aniBackBitmap, rect.x, rect.y ) | |
299 | dc.DrawRectangle( center_x - w/2, center_y - h/2, w, h ) | |
300 | else: | |
301 | stopTimer = True | |
302 | ||
303 | if stopTimer: | |
304 | dc.DrawBitmap( self.aniBackBitmap, rect.x, rect.y ) | |
305 | popup.Move( (0, 0) ) | |
306 | self.aniTimer.Stop() | |
307 | self.DoShowPopup( rect, self.aniFlags ) | |
308 | ||
309 | ||
310 | #---------------------------------------------------------------------- | |
311 | # FileSelectorCombo displays a dialog instead of a popup control, it | |
312 | # also uses a custom bitmap on the combo button. | |
313 | ||
314 | class FileSelectorCombo(wx.combo.ComboCtrl): | |
315 | def __init__(self, *args, **kw): | |
316 | wx.combo.ComboCtrl.__init__(self, *args, **kw) | |
317 | ||
318 | # make a custom bitmap showing "..." | |
319 | bw, bh = 16, 16 | |
320 | bmp = wx.EmptyBitmap(bw,bh) | |
321 | dc = wx.MemoryDC(bmp) | |
322 | ||
323 | # clear to a specific background colour | |
324 | bgcolor = wx.Colour(255,0,255) | |
325 | dc.SetBackground(wx.Brush(bgcolor)) | |
326 | dc.Clear() | |
327 | ||
328 | # draw the label onto the bitmap | |
329 | label = "..." | |
330 | font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) | |
331 | font.SetWeight(wx.FONTWEIGHT_BOLD) | |
332 | dc.SetFont(font) | |
333 | tw,th = dc.GetTextExtent(label) | |
334 | print tw, th | |
335 | dc.DrawText(label, (bw-tw)/2, (bw-tw)/2) | |
336 | del dc | |
337 | ||
338 | # now apply a mask using the bgcolor | |
339 | bmp.SetMaskColour(bgcolor) | |
340 | ||
341 | # and tell the ComboCtrl to use it | |
342 | self.SetButtonBitmaps(bmp, True) | |
343 | ||
344 | ||
345 | # Overridden from ComboCtrl, called when the combo button is clicked | |
346 | def OnButtonClick(self): | |
347 | path = "" | |
348 | name = "" | |
349 | if self.GetValue(): | |
350 | path, name = os.path.split(self.GetValue()) | |
351 | ||
352 | dlg = wx.FileDialog(self, "Choose File", path, name, | |
353 | "All files (*.*)|*.*", wx.FD_OPEN) | |
354 | if dlg.ShowModal() == wx.ID_OK: | |
355 | self.SetValue(dlg.GetPath()) | |
356 | dlg.Destroy() | |
357 | ||
358 | ||
359 | # Overridden from ComboCtrl to avoid assert since there is no ComboPopup | |
360 | def DoSetPopupControl(self, popup): | |
361 | pass | |
362 | ||
363 | ||
364 | #---------------------------------------------------------------------- | |
365 | ||
366 | ||
367 | class TestPanel(wx.Panel): | |
368 | def __init__(self, parent, log): | |
369 | self.log = log | |
370 | wx.Panel.__init__(self, parent, -1) | |
371 | ||
372 | fgs = wx.FlexGridSizer(cols=3, hgap=10, vgap=10) | |
373 | ||
374 | cc = self.MakeLCCombo(log=self.log) | |
375 | fgs.Add(cc) | |
376 | fgs.Add((10,10)) | |
377 | fgs.Add(wx.StaticText(self, -1, "wx.ComboCtrl with a ListCtrl popup")) | |
378 | ||
379 | cc = self.MakeLCCombo(style=wx.CB_READONLY) | |
380 | fgs.Add(cc) | |
381 | fgs.Add((10,10)) | |
382 | fgs.Add(wx.StaticText(self, -1, " Read-only")) | |
383 | ||
384 | cc = self.MakeLCCombo() | |
385 | cc.SetButtonPosition(side=wx.LEFT) | |
386 | fgs.Add(cc) | |
387 | fgs.Add((10,10)) | |
388 | fgs.Add(wx.StaticText(self, -1, " Button on the left")) | |
389 | ||
390 | cc = self.MakeLCCombo() | |
391 | cc.SetPopupMaxHeight(250) | |
392 | fgs.Add(cc) | |
393 | fgs.Add((10,10)) | |
394 | fgs.Add(wx.StaticText(self, -1, " Max height of popup set")) | |
395 | ||
396 | cc = wx.combo.ComboCtrl(self, size=(250,-1)) | |
397 | tcp = TreeCtrlComboPopup() | |
398 | cc.SetPopupControl(tcp) | |
399 | fgs.Add(cc) | |
400 | fgs.Add((10,10)) | |
401 | fgs.Add(wx.StaticText(self, -1, "TreeCtrl popup")) | |
402 | # add some items to the tree | |
403 | for i in range(5): | |
404 | item = tcp.AddItem('Item %d' % (i+1)) | |
405 | for j in range(15): | |
406 | tcp.AddItem('Subitem %d-%d' % (i+1, j+1), parent=item) | |
407 | ||
408 | cc = ComboCtrlWithCustomPopupAnim(self, size=(250, -1)) | |
409 | popup = ListCtrlComboPopup() | |
410 | cc.SetPopupMaxHeight(150) | |
411 | cc.SetPopupControl(popup) | |
412 | fgs.Add(cc) | |
413 | fgs.Add((10,10)) | |
414 | fgs.Add(wx.StaticText(self, -1, "Custom popup animation")) | |
415 | for word in "How cool was that!? Way COOL!".split(): | |
416 | popup.AddItem(word) | |
417 | ||
418 | ||
419 | cc = FileSelectorCombo(self, size=(250, -1)) | |
420 | fgs.Add(cc) | |
421 | fgs.Add((10,10)) | |
422 | fgs.Add(wx.StaticText(self, -1, "Custom popup action, and custom button bitmap")) | |
423 | ||
424 | ||
425 | box = wx.BoxSizer() | |
426 | box.Add(fgs, 1, wx.EXPAND|wx.ALL, 20) | |
427 | self.SetSizer(box) | |
428 | ||
429 | ||
430 | def MakeLCCombo(self, log=None, style=0): | |
431 | # Create a ComboCtrl | |
432 | cc = wx.combo.ComboCtrl(self, style=style, size=(250,-1)) | |
433 | ||
434 | # Create a Popup | |
435 | popup = ListCtrlComboPopup(log) | |
436 | ||
437 | # Associate them with each other. This also triggers the | |
438 | # creation of the ListCtrl. | |
439 | cc.SetPopupControl(popup) | |
440 | ||
441 | # Add some items to the listctrl. | |
442 | for x in range(75): | |
443 | popup.AddItem("Item-%02d" % x) | |
444 | ||
445 | return cc | |
446 | ||
447 | ||
448 | #---------------------------------------------------------------------- | |
449 | ||
450 | def runTest(frame, nb, log): | |
451 | win = TestPanel(nb, log) | |
452 | return win | |
453 | ||
454 | #---------------------------------------------------------------------- | |
455 | ||
456 | ||
457 | ||
458 | overview = """<html><body> | |
459 | <h2><center>wx.combo.ComboCtrl</center></h2> | |
460 | ||
461 | </body></html> | |
462 | """ | |
463 | ||
464 | ||
465 | ||
466 | if __name__ == '__main__': | |
467 | import sys,os | |
468 | import run | |
469 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) |