1 /////////////////////////////////////////////////////////////////////////////
2 // Name:        src/msw/choice.cpp
3 // Purpose:     wxChoice
4 // Author:      Julian Smart
5 // Modified by: Vadim Zeitlin to derive from wxChoiceBase
6 // Created:     04/01/98
7 // RCS-ID:      $Id: choice.cpp 51616 2008-02-09 15:22:15Z VZ $
8 // Copyright:   (c) Julian Smart
9 // Licence:     wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11 
12 // ============================================================================
13 // declarations
14 // ============================================================================
15 
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19 
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22 
23 #ifdef __BORLANDC__
24     #pragma hdrstop
25 #endif
26 
27 #if wxUSE_CHOICE && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
28 
29 #include "wx/choice.h"
30 
31 #ifndef WX_PRECOMP
32     #include "wx/utils.h"
33     #include "wx/log.h"
34     #include "wx/brush.h"
35     #include "wx/settings.h"
36 #endif
37 
38 #include "wx/msw/private.h"
39 
40 #if wxUSE_EXTENDED_RTTI
41 WX_DEFINE_FLAGS( wxChoiceStyle )
42 
wxBEGIN_FLAGS(wxChoiceStyle)43 wxBEGIN_FLAGS( wxChoiceStyle )
44     // new style border flags, we put them first to
45     // use them for streaming out
46     wxFLAGS_MEMBER(wxBORDER_SIMPLE)
47     wxFLAGS_MEMBER(wxBORDER_SUNKEN)
48     wxFLAGS_MEMBER(wxBORDER_DOUBLE)
49     wxFLAGS_MEMBER(wxBORDER_RAISED)
50     wxFLAGS_MEMBER(wxBORDER_STATIC)
51     wxFLAGS_MEMBER(wxBORDER_NONE)
52 
53     // old style border flags
54     wxFLAGS_MEMBER(wxSIMPLE_BORDER)
55     wxFLAGS_MEMBER(wxSUNKEN_BORDER)
56     wxFLAGS_MEMBER(wxDOUBLE_BORDER)
57     wxFLAGS_MEMBER(wxRAISED_BORDER)
58     wxFLAGS_MEMBER(wxSTATIC_BORDER)
59     wxFLAGS_MEMBER(wxBORDER)
60 
61     // standard window styles
62     wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
63     wxFLAGS_MEMBER(wxCLIP_CHILDREN)
64     wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
65     wxFLAGS_MEMBER(wxWANTS_CHARS)
66     wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
67     wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
68     wxFLAGS_MEMBER(wxVSCROLL)
69     wxFLAGS_MEMBER(wxHSCROLL)
70 
71 wxEND_FLAGS( wxChoiceStyle )
72 
73 IMPLEMENT_DYNAMIC_CLASS_XTI(wxChoice, wxControl,"wx/choice.h")
74 
75 wxBEGIN_PROPERTIES_TABLE(wxChoice)
76     wxEVENT_PROPERTY( Select , wxEVT_COMMAND_CHOICE_SELECTED , wxCommandEvent )
77 
78     wxPROPERTY( Font , wxFont , SetFont , GetFont  , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
79     wxPROPERTY_COLLECTION( Choices , wxArrayString , wxString , AppendString , GetStrings , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
80     wxPROPERTY( Selection ,int, SetSelection, GetSelection, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
81     wxPROPERTY_FLAGS( WindowStyle , wxChoiceStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
82 wxEND_PROPERTIES_TABLE()
83 
84 wxBEGIN_HANDLERS_TABLE(wxChoice)
85 wxEND_HANDLERS_TABLE()
86 
87 wxCONSTRUCTOR_4( wxChoice , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size )
88 #else
89 IMPLEMENT_DYNAMIC_CLASS(wxChoice, wxControl)
90 #endif
91 /*
92     TODO PROPERTIES
93         selection (long)
94         content (list)
95             item
96 */
97 
98 // ============================================================================
99 // implementation
100 // ============================================================================
101 
102 // ----------------------------------------------------------------------------
103 // creation
104 // ----------------------------------------------------------------------------
105 
106 bool wxChoice::Create(wxWindow *parent,
107                       wxWindowID id,
108                       const wxPoint& pos,
109                       const wxSize& size,
110                       int n, const wxString choices[],
111                       long style,
112                       const wxValidator& validator,
113                       const wxString& name)
114 {
115     // Experience shows that wxChoice vs. wxComboBox distinction confuses
116     // quite a few people - try to help them
117     wxASSERT_MSG( !(style & wxCB_DROPDOWN) &&
118                   !(style & wxCB_READONLY) &&
119                   !(style & wxCB_SIMPLE),
120                   _T("this style flag is ignored by wxChoice, you ")
121                   _T("probably want to use a wxComboBox") );
122 
123     return CreateAndInit(parent, id, pos, size, n, choices, style,
124                          validator, name);
125 }
126 
CreateAndInit(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],long style,const wxValidator & validator,const wxString & name)127 bool wxChoice::CreateAndInit(wxWindow *parent,
128                              wxWindowID id,
129                              const wxPoint& pos,
130                              const wxSize& size,
131                              int n, const wxString choices[],
132                              long style,
133                              const wxValidator& validator,
134                              const wxString& name)
135 {
136     // initialize wxControl
137     if ( !CreateControl(parent, id, pos, size, style, validator, name) )
138         return false;
139 
140     // now create the real HWND
141     if ( !MSWCreateControl(wxT("COMBOBOX"), wxEmptyString, pos, size) )
142         return false;
143 
144 
145     // choice/combobox normally has "white" (depends on colour scheme, of
146     // course) background rather than inheriting the parent's background
147     SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
148 
149     // initialize the controls contents
150     for ( int i = 0; i < n; i++ )
151     {
152         Append(choices[i]);
153     }
154 
155     // and now we may finally size the control properly (if needed)
156     SetInitialSize(size);
157 
158     return true;
159 }
160 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,long style,const wxValidator & validator,const wxString & name)161 bool wxChoice::Create(wxWindow *parent,
162                       wxWindowID id,
163                       const wxPoint& pos,
164                       const wxSize& size,
165                       const wxArrayString& choices,
166                       long style,
167                       const wxValidator& validator,
168                       const wxString& name)
169 {
170     wxCArrayString chs(choices);
171     return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
172                   style, validator, name);
173 }
174 
SetLabel(const wxString & label)175 void wxChoice::SetLabel(const wxString& label)
176 {
177     if ( FindString(label) == wxNOT_FOUND )
178     {
179         // unless we explicitly do this here, CB_GETCURSEL will continue to
180         // return the index of the previously selected item which will result
181         // in wrongly replacing the value being set now with the previously
182         // value if the user simply opens and closes (without selecting
183         // anything) the combobox popup
184         SetSelection(-1);
185     }
186 
187     wxChoiceBase::SetLabel(label);
188 }
189 
MSWShouldPreProcessMessage(WXMSG * pMsg)190 bool wxChoice::MSWShouldPreProcessMessage(WXMSG *pMsg)
191 {
192     MSG *msg = (MSG *) pMsg;
193 
194     // if the dropdown list is visible, don't preprocess certain keys
195     if ( msg->message == WM_KEYDOWN
196         && (msg->wParam == VK_ESCAPE || msg->wParam == VK_RETURN) )
197     {
198         if (::SendMessage(GetHwndOf(this), CB_GETDROPPEDSTATE, 0, 0))
199         {
200             return false;
201         }
202     }
203 
204     return wxControl::MSWShouldPreProcessMessage(pMsg);
205 }
206 
MSWGetStyle(long style,WXDWORD * exstyle) const207 WXDWORD wxChoice::MSWGetStyle(long style, WXDWORD *exstyle) const
208 {
209     // we never have an external border
210     WXDWORD msStyle = wxControl::MSWGetStyle
211                       (
212                         (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
213                       );
214 
215     // WS_CLIPSIBLINGS is useful with wxChoice and doesn't seem to result in
216     // any problems
217     msStyle |= WS_CLIPSIBLINGS;
218 
219     // wxChoice-specific styles
220     msStyle |= CBS_DROPDOWNLIST | WS_HSCROLL | WS_VSCROLL;
221     if ( style & wxCB_SORT )
222         msStyle |= CBS_SORT;
223 
224     return msStyle;
225 }
226 
~wxChoice()227 wxChoice::~wxChoice()
228 {
229     Free();
230 }
231 
232 // ----------------------------------------------------------------------------
233 // adding/deleting items to/from the list
234 // ----------------------------------------------------------------------------
235 
DoAppend(const wxString & item)236 int wxChoice::DoAppend(const wxString& item)
237 {
238     int n = (int)SendMessage(GetHwnd(), CB_ADDSTRING, 0, (LPARAM)item.c_str());
239     if ( n == CB_ERR )
240     {
241         wxLogLastError(wxT("SendMessage(CB_ADDSTRING)"));
242     }
243     else // ok
244     {
245         // we need to refresh our size in order to have enough space for the
246         // newly added items
247         if ( !IsFrozen() )
248             UpdateVisibleHeight();
249     }
250 
251     InvalidateBestSize();
252     return n;
253 }
254 
DoInsert(const wxString & item,unsigned int pos)255 int wxChoice::DoInsert(const wxString& item, unsigned int pos)
256 {
257     wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT), -1, wxT("can't insert into sorted list"));
258     wxCHECK_MSG(IsValidInsert(pos), -1, wxT("invalid index"));
259 
260     int n = (int)SendMessage(GetHwnd(), CB_INSERTSTRING, pos, (LPARAM)item.c_str());
261     if ( n == CB_ERR )
262     {
263         wxLogLastError(wxT("SendMessage(CB_INSERTSTRING)"));
264     }
265     else // ok
266     {
267         if ( !IsFrozen() )
268             UpdateVisibleHeight();
269     }
270 
271     InvalidateBestSize();
272     return n;
273 }
274 
Delete(unsigned int n)275 void wxChoice::Delete(unsigned int n)
276 {
277     wxCHECK_RET( IsValid(n), wxT("invalid item index in wxChoice::Delete") );
278 
279     if ( HasClientObjectData() )
280     {
281         delete GetClientObject(n);
282     }
283 
284     SendMessage(GetHwnd(), CB_DELETESTRING, n, 0);
285 
286     if ( !IsFrozen() )
287         UpdateVisibleHeight();
288 
289     InvalidateBestSize();
290 }
291 
Clear()292 void wxChoice::Clear()
293 {
294     Free();
295 
296     SendMessage(GetHwnd(), CB_RESETCONTENT, 0, 0);
297 
298     if ( !IsFrozen() )
299         UpdateVisibleHeight();
300 
301     InvalidateBestSize();
302 }
303 
Free()304 void wxChoice::Free()
305 {
306     if ( HasClientObjectData() )
307     {
308         unsigned int count = GetCount();
309         for ( unsigned int n = 0; n < count; n++ )
310         {
311             delete GetClientObject(n);
312         }
313     }
314 }
315 
316 // ----------------------------------------------------------------------------
317 // selection
318 // ----------------------------------------------------------------------------
319 
GetSelection() const320 int wxChoice::GetSelection() const
321 {
322     // if m_lastAcceptedSelection is set, it means that the dropdown is
323     // currently shown and that we want to use the last "permanent" selection
324     // instead of whatever is under the mouse pointer currently
325     //
326     // otherwise, get the selection from the control
327     return m_lastAcceptedSelection == wxID_NONE ? GetCurrentSelection()
328                                                 : m_lastAcceptedSelection;
329 }
330 
GetCurrentSelection() const331 int wxChoice::GetCurrentSelection() const
332 {
333     return (int)SendMessage(GetHwnd(), CB_GETCURSEL, 0, 0);
334 }
335 
SetSelection(int n)336 void wxChoice::SetSelection(int n)
337 {
338     SendMessage(GetHwnd(), CB_SETCURSEL, n, 0);
339 }
340 
341 // ----------------------------------------------------------------------------
342 // string list functions
343 // ----------------------------------------------------------------------------
344 
GetCount() const345 unsigned int wxChoice::GetCount() const
346 {
347     return (unsigned int)SendMessage(GetHwnd(), CB_GETCOUNT, 0, 0);
348 }
349 
FindString(const wxString & s,bool bCase) const350 int wxChoice::FindString(const wxString& s, bool bCase) const
351 {
352 #if defined(__WATCOMC__) && defined(__WIN386__)
353     // For some reason, Watcom in WIN386 mode crashes in the CB_FINDSTRINGEXACT message.
354     // wxChoice::Do it the long way instead.
355     unsigned int count = GetCount();
356     for ( unsigned int i = 0; i < count; i++ )
357     {
358         // as CB_FINDSTRINGEXACT is case insensitive, be case insensitive too
359         if (GetString(i).IsSameAs(s, bCase))
360             return i;
361     }
362 
363     return wxNOT_FOUND;
364 #else // !Watcom
365    //TODO:  Evidently some MSW versions (all?) don't like empty strings
366    //passed to SendMessage, so we have to do it ourselves in that case
367    if ( s.empty() )
368    {
369        unsigned int count = GetCount();
370        for ( unsigned int i = 0; i < count; i++ )
371        {
372          if (GetString(i).empty())
373              return i;
374        }
375 
376        return wxNOT_FOUND;
377    }
378    else if (bCase)
379    {
380        // back to base class search for not native search type
381        return wxItemContainerImmutable::FindString( s, bCase );
382    }
383    else
384    {
385        int pos = (int)SendMessage(GetHwnd(), CB_FINDSTRINGEXACT,
386                                   (WPARAM)-1, (LPARAM)s.c_str());
387 
388        return pos == LB_ERR ? wxNOT_FOUND : pos;
389    }
390 #endif // Watcom/!Watcom
391 }
392 
SetString(unsigned int n,const wxString & s)393 void wxChoice::SetString(unsigned int n, const wxString& s)
394 {
395     wxCHECK_RET( IsValid(n), wxT("invalid item index in wxChoice::SetString") );
396 
397     // we have to delete and add back the string as there is no way to change a
398     // string in place
399 
400     // we need to preserve the client data
401     void *data;
402     if ( m_clientDataItemsType != wxClientData_None )
403     {
404         data = DoGetItemClientData(n);
405     }
406     else // no client data
407     {
408         data = NULL;
409     }
410 
411     ::SendMessage(GetHwnd(), CB_DELETESTRING, n, 0);
412     ::SendMessage(GetHwnd(), CB_INSERTSTRING, n, (LPARAM)s.c_str() );
413 
414     if ( data )
415     {
416         DoSetItemClientData(n, data);
417     }
418     //else: it's already NULL by default
419 
420     InvalidateBestSize();
421 }
422 
GetString(unsigned int n) const423 wxString wxChoice::GetString(unsigned int n) const
424 {
425     int len = (int)::SendMessage(GetHwnd(), CB_GETLBTEXTLEN, n, 0);
426 
427     wxString str;
428     if ( len != CB_ERR && len > 0 )
429     {
430         if ( ::SendMessage
431                (
432                 GetHwnd(),
433                 CB_GETLBTEXT,
434                 n,
435                 (LPARAM)(wxChar *)wxStringBuffer(str, len)
436                ) == CB_ERR )
437         {
438             wxLogLastError(wxT("SendMessage(CB_GETLBTEXT)"));
439         }
440     }
441 
442     return str;
443 }
444 
445 // ----------------------------------------------------------------------------
446 // client data
447 // ----------------------------------------------------------------------------
448 
DoSetItemClientData(unsigned int n,void * clientData)449 void wxChoice::DoSetItemClientData(unsigned int n, void* clientData)
450 {
451     if ( ::SendMessage(GetHwnd(), CB_SETITEMDATA,
452                        n, (LPARAM)clientData) == CB_ERR )
453     {
454         wxLogLastError(wxT("CB_SETITEMDATA"));
455     }
456 }
457 
DoGetItemClientData(unsigned int n) const458 void* wxChoice::DoGetItemClientData(unsigned int n) const
459 {
460     LPARAM rc = SendMessage(GetHwnd(), CB_GETITEMDATA, n, 0);
461     if ( rc == CB_ERR )
462     {
463         wxLogLastError(wxT("CB_GETITEMDATA"));
464 
465         // unfortunately, there is no way to return an error code to the user
466         rc = (LPARAM) NULL;
467     }
468 
469     return (void *)rc;
470 }
471 
DoSetItemClientObject(unsigned int n,wxClientData * clientData)472 void wxChoice::DoSetItemClientObject(unsigned int n, wxClientData* clientData)
473 {
474     DoSetItemClientData(n, clientData);
475 }
476 
DoGetItemClientObject(unsigned int n) const477 wxClientData* wxChoice::DoGetItemClientObject(unsigned int n) const
478 {
479     return (wxClientData *)DoGetItemClientData(n);
480 }
481 
482 // ----------------------------------------------------------------------------
483 // wxMSW specific helpers
484 // ----------------------------------------------------------------------------
485 
UpdateVisibleHeight()486 void wxChoice::UpdateVisibleHeight()
487 {
488     // be careful to not change the width here
489     DoSetSize(wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, GetSize().y, wxSIZE_USE_EXISTING);
490 }
491 
DoMoveWindow(int x,int y,int width,int height)492 void wxChoice::DoMoveWindow(int x, int y, int width, int height)
493 {
494     // here is why this is necessary: if the width is negative, the combobox
495     // window proc makes the window of the size width*height instead of
496     // interpreting height in the usual manner (meaning the height of the drop
497     // down list - usually the height specified in the call to MoveWindow()
498     // will not change the height of combo box per se)
499     //
500     // this behaviour is not documented anywhere, but this is just how it is
501     // here (NT 4.4) and, anyhow, the check shouldn't hurt - however without
502     // the check, constraints/sizers using combos may break the height
503     // constraint will have not at all the same value as expected
504     if ( width < 0 )
505         return;
506 
507     wxControl::DoMoveWindow(x, y, width, height);
508 }
509 
DoGetSize(int * w,int * h) const510 void wxChoice::DoGetSize(int *w, int *h) const
511 {
512     // this is weird: sometimes, the height returned by Windows is clearly the
513     // total height of the control including the drop down list -- but only
514     // sometimes, and normally it isn't... I have no idea about what to do with
515     // this
516     wxControl::DoGetSize(w, h);
517 }
518 
DoSetSize(int x,int y,int width,int height,int sizeFlags)519 void wxChoice::DoSetSize(int x, int y,
520                          int width, int height,
521                          int sizeFlags)
522 {
523     int heightOrig = height;
524 
525     // the height which we must pass to Windows should be the total height of
526     // the control including the drop down list while the height given to us
527     // is, of course, just the height of the permanently visible part of it
528     if ( height != wxDefaultCoord )
529     {
530         // don't make the drop down list too tall, arbitrarily limit it to 40
531         // items max and also don't leave it empty
532         size_t nItems = GetCount();
533         if ( !nItems )
534             nItems = 9;
535         else if ( nItems > 24 )
536             nItems = 24;
537 
538         // add space for the drop down list
539         const int hItem = SendMessage(GetHwnd(), CB_GETITEMHEIGHT, 0, 0);
540         height += hItem*(nItems + 1);
541     }
542     else
543     {
544         // We cannot pass wxDefaultCoord as height to wxControl. wxControl uses
545         // wxGetWindowRect() to determine the current height of the combobox,
546         // and then again sets the combobox's height to that value. Unfortunately,
547         // wxGetWindowRect doesn't include the dropdown list's height (at least
548         // on Win2K), so this would result in a combobox with dropdown height of
549         // 1 pixel. We have to determine the default height ourselves and call
550         // wxControl with that value instead.
551         int w, h;
552         RECT r;
553         DoGetSize(&w, &h);
554         if (::SendMessage(GetHwnd(), CB_GETDROPPEDCONTROLRECT, 0, (LPARAM) &r) != 0)
555         {
556             height = h + r.bottom - r.top;
557         }
558     }
559 
560     wxControl::DoSetSize(x, y, width, height, sizeFlags);
561 
562     // If we're storing a pending size, make sure we store
563     // the original size for reporting back to the app.
564     if (m_pendingSize != wxDefaultSize)
565         m_pendingSize = wxSize(width, heightOrig);
566 
567     // This solution works on XP, but causes choice/combobox lists to be
568     // too short on W2K and earlier.
569 #if 0
570     int widthCurrent, heightCurrent;
571     DoGetSize(&widthCurrent, &heightCurrent);
572 
573     // the height which we must pass to Windows should be the total height of
574     // the control including the drop down list while the height given to us
575     // is, of course, just the height of the permanently visible part of it
576     if ( height != wxDefaultCoord && height != heightCurrent )
577     {
578         // don't make the drop down list too tall, arbitrarily limit it to 40
579         // items max and also don't leave it empty
580         unsigned int nItems = GetCount();
581         if ( !nItems )
582             nItems = 9;
583         else if ( nItems > 24 )
584             nItems = 24;
585 
586         // add space for the drop down list
587         const int hItem = SendMessage(GetHwnd(), CB_GETITEMHEIGHT, 0, 0);
588         height += hItem*(nItems + 1);
589     }
590     else // keep the same height as now
591     {
592         // normally wxWindow::DoSetSize() checks if we set the same size as the
593         // window already has and does nothing in this case, but for us the
594         // check fails as the size we pass to it includes the dropdown while
595         // the size returned by our GetSize() does not, so test if the size
596         // didn't really change ourselves here
597         if ( width == wxDefaultCoord || width == widthCurrent )
598         {
599             // size doesn't change, what about position?
600             int xCurrent, yCurrent;
601             DoGetPosition(&xCurrent, &yCurrent);
602             const bool defMeansUnchanged = !(sizeFlags & wxSIZE_ALLOW_MINUS_ONE);
603             if ( ((x == wxDefaultCoord && defMeansUnchanged) || x == xCurrent)
604                     &&
605                  ((y == wxDefaultCoord && defMeansUnchanged) || y == yCurrent) )
606             {
607                 // nothing changes, nothing to do
608                 return;
609             }
610         }
611 
612         // We cannot pass wxDefaultCoord as height to wxControl. wxControl uses
613         // wxGetWindowRect() to determine the current height of the combobox,
614         // and then again sets the combobox's height to that value. Unfortunately,
615         // wxGetWindowRect doesn't include the dropdown list's height (at least
616         // on Win2K), so this would result in a combobox with dropdown height of
617         // 1 pixel. We have to determine the default height ourselves and call
618         // wxControl with that value instead.
619         //
620         // Also notice that sometimes CB_GETDROPPEDCONTROLRECT seems to return
621         // wildly incorrect values (~32000) which looks like a bug in it, just
622         // ignore them in this case
623         RECT r;
624         if ( ::SendMessage(GetHwnd(), CB_GETDROPPEDCONTROLRECT, 0, (LPARAM) &r)
625                     && r.bottom < 30000 )
626         {
627             height = heightCurrent + r.bottom - r.top;
628         }
629     }
630 
631     wxControl::DoSetSize(x, y, width, height, sizeFlags);
632 #endif
633 }
634 
DoGetBestSize() const635 wxSize wxChoice::DoGetBestSize() const
636 {
637     // find the widest string
638     int wChoice = 0;
639     const unsigned int nItems = GetCount();
640     for ( unsigned int i = 0; i < nItems; i++ )
641     {
642         int wLine;
643         GetTextExtent(GetString(i), &wLine, NULL);
644         if ( wLine > wChoice )
645             wChoice = wLine;
646     }
647 
648     // give it some reasonable default value if there are no strings in the
649     // list
650     if ( wChoice == 0 )
651         wChoice = 100;
652 
653     // the combobox should be slightly larger than the widest string
654     wChoice += 5*GetCharWidth();
655 
656     wxSize best(wChoice, EDIT_HEIGHT_FROM_CHAR_HEIGHT(GetCharHeight()));
657     CacheBestSize(best);
658     return best;
659 }
660 
MSWWindowProc(WXUINT nMsg,WXWPARAM wParam,WXLPARAM lParam)661 WXLRESULT wxChoice::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
662 {
663     switch ( nMsg )
664     {
665         case WM_LBUTTONUP:
666             {
667                 int x = (int)LOWORD(lParam);
668                 int y = (int)HIWORD(lParam);
669 
670                 // Ok, this is truly weird, but if a panel with a wxChoice
671                 // loses the focus, then you get a *fake* WM_LBUTTONUP message
672                 // with x = 65535 and y = 65535. Filter out this nonsense.
673                 //
674                 // VZ: I'd like to know how to reproduce this please...
675                 if ( x == 65535 && y == 65535 )
676                     return 0;
677             }
678             break;
679 
680             // we have to handle both: one for the normal case and the other
681             // for readonly
682         case WM_CTLCOLOREDIT:
683         case WM_CTLCOLORLISTBOX:
684         case WM_CTLCOLORSTATIC:
685             {
686                 WXHDC hdc;
687                 WXHWND hwnd;
688                 UnpackCtlColor(wParam, lParam, &hdc, &hwnd);
689 
690                 WXHBRUSH hbr = MSWControlColor((WXHDC)hdc, hwnd);
691                 if ( hbr )
692                     return (WXLRESULT)hbr;
693                 //else: fall through to default window proc
694             }
695     }
696 
697     return wxWindow::MSWWindowProc(nMsg, wParam, lParam);
698 }
699 
MSWCommand(WXUINT param,WXWORD WXUNUSED (id))700 bool wxChoice::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
701 {
702     /*
703         The native control provides a great variety in the events it sends in
704         the different selection scenarios (undoubtedly for greater amusement of
705         the programmers using it). For the reference, here are the cases when
706         the final selection is accepted (things are quite interesting when it
707         is cancelled too):
708 
709         A. Selecting with just the arrows without opening the dropdown:
710             1. CBN_SELENDOK
711             2. CBN_SELCHANGE
712 
713         B. Opening dropdown with F4 and selecting with arrows:
714             1. CBN_DROPDOWN
715             2. many CBN_SELCHANGE while changing selection in the list
716             3. CBN_SELENDOK
717             4. CBN_CLOSEUP
718 
719         C. Selecting with the mouse:
720             1. CBN_DROPDOWN
721             -- no intermediate CBN_SELCHANGEs --
722             2. CBN_SELENDOK
723             3. CBN_CLOSEUP
724             4. CBN_SELCHANGE
725 
726         Admire the different order of messages in all of those cases, it must
727         surely have taken a lot of effort to Microsoft developers to achieve
728         such originality.
729      */
730     switch ( param )
731     {
732         case CBN_DROPDOWN:
733             // we use this value both because we don't want to track selection
734             // using CB_GETCURSEL while the dropdown is opened and because we
735             // need to reset the selection back to it if it's eventually
736             // cancelled by user
737             m_lastAcceptedSelection = GetCurrentSelection();
738             if ( m_lastAcceptedSelection == -1 )
739             {
740                 // no current selection so no need to restore it later (this
741                 // happens when opening a combobox containing text not from its
742                 // list of items and we shouldn't erase this text)
743                 m_lastAcceptedSelection = wxID_NONE;
744             }
745             break;
746 
747         case CBN_CLOSEUP:
748             // if the selection was accepted by the user, it should have been
749             // reset to wxID_NONE by CBN_SELENDOK, otherwise the selection was
750             // cancelled and we must restore the old one
751             if ( m_lastAcceptedSelection != wxID_NONE )
752             {
753                 SetSelection(m_lastAcceptedSelection);
754                 m_lastAcceptedSelection = wxID_NONE;
755             }
756             break;
757 
758         case CBN_SELENDOK:
759             // reset it to prevent CBN_CLOSEUP from undoing the selection (it's
760             // ok to reset it now as GetCurrentSelection() will now return the
761             // same thing anyhow)
762             m_lastAcceptedSelection = wxID_NONE;
763 
764             {
765                 const int n = GetSelection();
766 
767                 wxCommandEvent event(wxEVT_COMMAND_CHOICE_SELECTED, m_windowId);
768                 event.SetInt(n);
769                 event.SetEventObject(this);
770 
771                 if ( n > -1 )
772                 {
773                     event.SetString(GetStringSelection());
774                     InitCommandEventWithItems(event, n);
775                 }
776 
777                 ProcessCommand(event);
778             }
779             break;
780 
781         // don't handle CBN_SELENDCANCEL: just leave m_lastAcceptedSelection
782         // valid and the selection will be undone in CBN_CLOSEUP above
783 
784         // don't handle CBN_SELCHANGE neither, we don't want to generate events
785         // while the dropdown is opened -- but do add it if we ever need this
786 
787         default:
788             return false;
789     }
790 
791     return true;
792 }
793 
MSWControlColor(WXHDC hDC,WXHWND hWnd)794 WXHBRUSH wxChoice::MSWControlColor(WXHDC hDC, WXHWND hWnd)
795 {
796     if ( !IsEnabled() )
797         return MSWControlColorDisabled(hDC);
798 
799     return wxChoiceBase::MSWControlColor(hDC, hWnd);
800 }
801 
802 #endif // wxUSE_CHOICE && !(__SMARTPHONE__ && __WXWINCE__)
803