fixed uninitialized variable (depending on wxChoice ctor used it resulted in an out...
[wxWidgets.git] / src / msw / choice.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: 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$
8 // Copyright: (c) Julian Smart
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #if defined(__GNUG__) && !defined(NO_GCC_PRAGMA)
21 #pragma implementation "choice.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_CHOICE && !(defined(__SMARTPHONE__) && defined(__WXWINCE__))
32
33 #ifndef WX_PRECOMP
34 #include "wx/choice.h"
35 #include "wx/utils.h"
36 #include "wx/log.h"
37 #include "wx/brush.h"
38 #include "wx/settings.h"
39 #endif
40
41 #include "wx/msw/private.h"
42
43 #if wxUSE_EXTENDED_RTTI
44 WX_DEFINE_FLAGS( wxChoiceStyle )
45
46 wxBEGIN_FLAGS( wxChoiceStyle )
47 // new style border flags, we put them first to
48 // use them for streaming out
49 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
50 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
51 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
52 wxFLAGS_MEMBER(wxBORDER_RAISED)
53 wxFLAGS_MEMBER(wxBORDER_STATIC)
54 wxFLAGS_MEMBER(wxBORDER_NONE)
55
56 // old style border flags
57 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
58 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
59 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
60 wxFLAGS_MEMBER(wxRAISED_BORDER)
61 wxFLAGS_MEMBER(wxSTATIC_BORDER)
62 wxFLAGS_MEMBER(wxBORDER)
63
64 // standard window styles
65 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
66 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
67 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
68 wxFLAGS_MEMBER(wxWANTS_CHARS)
69 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
70 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
71 wxFLAGS_MEMBER(wxVSCROLL)
72 wxFLAGS_MEMBER(wxHSCROLL)
73
74 wxEND_FLAGS( wxChoiceStyle )
75
76 IMPLEMENT_DYNAMIC_CLASS_XTI(wxChoice, wxControl,"wx/choice.h")
77
78 wxBEGIN_PROPERTIES_TABLE(wxChoice)
79 wxEVENT_PROPERTY( Select , wxEVT_COMMAND_CHOICE_SELECTED , wxCommandEvent )
80
81 wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
82 wxPROPERTY_COLLECTION( Choices , wxArrayString , wxString , AppendString , GetStrings , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
83 wxPROPERTY( Selection ,int, SetSelection, GetSelection, EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
84 wxPROPERTY_FLAGS( WindowStyle , wxChoiceStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
85 wxEND_PROPERTIES_TABLE()
86
87 wxBEGIN_HANDLERS_TABLE(wxChoice)
88 wxEND_HANDLERS_TABLE()
89
90 wxCONSTRUCTOR_4( wxChoice , wxWindow* , Parent , wxWindowID , Id , wxPoint , Position , wxSize , Size )
91 #else
92 IMPLEMENT_DYNAMIC_CLASS(wxChoice, wxControl)
93 #endif
94 /*
95 TODO PROPERTIES
96 selection (long)
97 content (list)
98 item
99 */
100
101 // ============================================================================
102 // implementation
103 // ============================================================================
104
105 // ----------------------------------------------------------------------------
106 // creation
107 // ----------------------------------------------------------------------------
108
109 bool wxChoice::Create(wxWindow *parent,
110 wxWindowID id,
111 const wxPoint& pos,
112 const wxSize& size,
113 int n, const wxString choices[],
114 long style,
115 const wxValidator& validator,
116 const wxString& name)
117 {
118 // Experience shows that wxChoice vs. wxComboBox distinction confuses
119 // quite a few people - try to help them
120 wxASSERT_MSG( !(style & wxCB_DROPDOWN) &&
121 !(style & wxCB_READONLY) &&
122 !(style & wxCB_SIMPLE),
123 _T("this style flag is ignored by wxChoice, you ")
124 _T("probably want to use a wxComboBox") );
125
126 return CreateAndInit(parent, id, pos, size, n, choices, style,
127 validator, name);
128 }
129
130 bool wxChoice::CreateAndInit(wxWindow *parent,
131 wxWindowID id,
132 const wxPoint& pos,
133 const wxSize& size,
134 int n, const wxString choices[],
135 long style,
136 const wxValidator& validator,
137 const wxString& name)
138 {
139 Init();
140
141 // initialize wxControl
142 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
143 return false;
144
145 // now create the real HWND
146 if ( !MSWCreateControl(wxT("COMBOBOX"), wxEmptyString, pos, size) )
147 return false;
148
149
150 // choice/combobox normally has "white" (depends on colour scheme, of
151 // course) background rather than inheriting the parent's background
152 SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
153
154 // initialize the controls contents
155 for ( int i = 0; i < n; i++ )
156 {
157 Append(choices[i]);
158 }
159
160 // and now we may finally size the control properly (if needed)
161 SetBestSize(size);
162
163 return true;
164 }
165
166 bool wxChoice::Create(wxWindow *parent,
167 wxWindowID id,
168 const wxPoint& pos,
169 const wxSize& size,
170 const wxArrayString& choices,
171 long style,
172 const wxValidator& validator,
173 const wxString& name)
174 {
175 wxCArrayString chs(choices);
176 return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
177 style, validator, name);
178 }
179
180 bool wxChoice::MSWShouldPreProcessMessage(WXMSG *pMsg)
181 {
182 MSG *msg = (MSG *) pMsg;
183
184 // if the dropdown list is visible, don't preprocess certain keys
185 if ( msg->message == WM_KEYDOWN
186 && (msg->wParam == VK_ESCAPE || msg->wParam == VK_RETURN) )
187 {
188 if (::SendMessage(GetHwndOf(this), CB_GETDROPPEDSTATE, 0, 0))
189 {
190 return false;
191 }
192 }
193
194 return wxControl::MSWShouldPreProcessMessage(pMsg);
195 }
196
197 WXDWORD wxChoice::MSWGetStyle(long style, WXDWORD *exstyle) const
198 {
199 // we never have an external border
200 WXDWORD msStyle = wxControl::MSWGetStyle
201 (
202 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
203 );
204
205 // WS_CLIPSIBLINGS is useful with wxChoice and doesn't seem to result in
206 // any problems
207 msStyle |= WS_CLIPSIBLINGS;
208
209 // wxChoice-specific styles
210 msStyle |= CBS_DROPDOWNLIST | WS_HSCROLL | WS_VSCROLL;
211 if ( style & wxCB_SORT )
212 msStyle |= CBS_SORT;
213
214 return msStyle;
215 }
216
217 wxChoice::~wxChoice()
218 {
219 Free();
220 }
221
222 // ----------------------------------------------------------------------------
223 // adding/deleting items to/from the list
224 // ----------------------------------------------------------------------------
225
226 int wxChoice::DoAppend(const wxString& item)
227 {
228 int n = (int)SendMessage(GetHwnd(), CB_ADDSTRING, 0, (LPARAM)item.c_str());
229 if ( n == CB_ERR )
230 {
231 wxLogLastError(wxT("SendMessage(CB_ADDSTRING)"));
232 }
233 else // ok
234 {
235 // we need to refresh our size in order to have enough space for the
236 // newly added items
237 if ( !IsFrozen() )
238 UpdateVisibleHeight();
239 }
240
241 InvalidateBestSize();
242 return n;
243 }
244
245 int wxChoice::DoInsert(const wxString& item, int pos)
246 {
247 wxCHECK_MSG(!(GetWindowStyle() & wxCB_SORT), -1, wxT("can't insert into sorted list"));
248 wxCHECK_MSG((pos>=0) && (pos<=GetCount()), -1, wxT("invalid index"));
249
250 int n = (int)SendMessage(GetHwnd(), CB_INSERTSTRING, pos, (LPARAM)item.c_str());
251 if ( n == CB_ERR )
252 {
253 wxLogLastError(wxT("SendMessage(CB_INSERTSTRING)"));
254 }
255 else // ok
256 {
257 if ( !IsFrozen() )
258 UpdateVisibleHeight();
259 }
260
261 InvalidateBestSize();
262 return n;
263 }
264
265 void wxChoice::Delete(int n)
266 {
267 wxCHECK_RET( n < GetCount(), wxT("invalid item index in wxChoice::Delete") );
268
269 if ( HasClientObjectData() )
270 {
271 delete GetClientObject(n);
272 }
273
274 SendMessage(GetHwnd(), CB_DELETESTRING, n, 0);
275
276 if ( !IsFrozen() )
277 UpdateVisibleHeight();
278
279 InvalidateBestSize();
280 }
281
282 void wxChoice::Clear()
283 {
284 Free();
285
286 SendMessage(GetHwnd(), CB_RESETCONTENT, 0, 0);
287
288 if ( !IsFrozen() )
289 UpdateVisibleHeight();
290
291 InvalidateBestSize();
292 }
293
294 void wxChoice::Free()
295 {
296 if ( HasClientObjectData() )
297 {
298 size_t count = GetCount();
299 for ( size_t n = 0; n < count; n++ )
300 {
301 delete GetClientObject(n);
302 }
303 }
304 }
305
306 // ----------------------------------------------------------------------------
307 // selection
308 // ----------------------------------------------------------------------------
309
310 int wxChoice::GetSelection() const
311 {
312 // if m_lastAcceptedSelection is set, it means that the dropdown is
313 // currently shown and that we want to use the last "permanent" selection
314 // instead of whatever is under the mouse pointer currently
315 //
316 // otherwise, get the selection from the control
317 return m_lastAcceptedSelection == wxID_NONE ? GetCurrentSelection()
318 : m_lastAcceptedSelection;
319 }
320
321 int wxChoice::GetCurrentSelection() const
322 {
323 return (int)SendMessage(GetHwnd(), CB_GETCURSEL, 0, 0);
324 }
325
326 void wxChoice::SetSelection(int n)
327 {
328 SendMessage(GetHwnd(), CB_SETCURSEL, n, 0);
329 }
330
331 // ----------------------------------------------------------------------------
332 // string list functions
333 // ----------------------------------------------------------------------------
334
335 int wxChoice::GetCount() const
336 {
337 return (int)SendMessage(GetHwnd(), CB_GETCOUNT, 0, 0);
338 }
339
340 int wxChoice::FindString(const wxString& s) const
341 {
342 #if defined(__WATCOMC__) && defined(__WIN386__)
343 // For some reason, Watcom in WIN386 mode crashes in the CB_FINDSTRINGEXACT message.
344 // wxChoice::Do it the long way instead.
345 int count = GetCount();
346 for ( int i = 0; i < count; i++ )
347 {
348 // as CB_FINDSTRINGEXACT is case insensitive, be case insensitive too
349 if ( GetString(i).IsSameAs(s, false) )
350 return i;
351 }
352
353 return wxNOT_FOUND;
354 #else // !Watcom
355 //TODO: Evidently some MSW versions (all?) don't like empty strings
356 //passed to SendMessage, so we have to do it ourselves in that case
357 if ( s.empty() )
358 {
359 int count = GetCount();
360 for ( int i = 0; i < count; i++ )
361 {
362 if ( GetString(i).empty() )
363 return i;
364 }
365
366 return wxNOT_FOUND;
367 }
368 else
369 {
370 int pos = (int)SendMessage(GetHwnd(), CB_FINDSTRINGEXACT,
371 (WPARAM)-1, (LPARAM)s.c_str());
372
373 return pos == LB_ERR ? wxNOT_FOUND : pos;
374 }
375 #endif // Watcom/!Watcom
376 }
377
378 void wxChoice::SetString(int n, const wxString& s)
379 {
380 wxCHECK_RET( n >= 0 && n < GetCount(),
381 wxT("invalid item index in wxChoice::SetString") );
382
383 // we have to delete and add back the string as there is no way to change a
384 // string in place
385
386 // we need to preserve the client data
387 void *data;
388 if ( m_clientDataItemsType != wxClientData_None )
389 {
390 data = DoGetItemClientData(n);
391 }
392 else // no client data
393 {
394 data = NULL;
395 }
396
397 ::SendMessage(GetHwnd(), CB_DELETESTRING, n, 0);
398 ::SendMessage(GetHwnd(), CB_INSERTSTRING, n, (LPARAM)s.c_str() );
399
400 if ( data )
401 {
402 DoSetItemClientData(n, data);
403 }
404 //else: it's already NULL by default
405
406 InvalidateBestSize();
407 }
408
409 wxString wxChoice::GetString(int n) const
410 {
411 int len = (int)::SendMessage(GetHwnd(), CB_GETLBTEXTLEN, n, 0);
412
413 wxString str;
414 if ( len != CB_ERR && len > 0 )
415 {
416 if ( ::SendMessage
417 (
418 GetHwnd(),
419 CB_GETLBTEXT,
420 n,
421 (LPARAM)(wxChar *)wxStringBuffer(str, len)
422 ) == CB_ERR )
423 {
424 wxLogLastError(wxT("SendMessage(CB_GETLBTEXT)"));
425 }
426 }
427
428 return str;
429 }
430
431 // ----------------------------------------------------------------------------
432 // client data
433 // ----------------------------------------------------------------------------
434
435 void wxChoice::DoSetItemClientData( int n, void* clientData )
436 {
437 if ( ::SendMessage(GetHwnd(), CB_SETITEMDATA,
438 n, (LPARAM)clientData) == CB_ERR )
439 {
440 wxLogLastError(wxT("CB_SETITEMDATA"));
441 }
442 }
443
444 void* wxChoice::DoGetItemClientData( int n ) const
445 {
446 LPARAM rc = SendMessage(GetHwnd(), CB_GETITEMDATA, n, 0);
447 if ( rc == CB_ERR )
448 {
449 wxLogLastError(wxT("CB_GETITEMDATA"));
450
451 // unfortunately, there is no way to return an error code to the user
452 rc = (LPARAM) NULL;
453 }
454
455 return (void *)rc;
456 }
457
458 void wxChoice::DoSetItemClientObject( int n, wxClientData* clientData )
459 {
460 DoSetItemClientData(n, clientData);
461 }
462
463 wxClientData* wxChoice::DoGetItemClientObject( int n ) const
464 {
465 return (wxClientData *)DoGetItemClientData(n);
466 }
467
468 // ----------------------------------------------------------------------------
469 // wxMSW specific helpers
470 // ----------------------------------------------------------------------------
471
472 void wxChoice::UpdateVisibleHeight()
473 {
474 // be careful to not change the width here
475 DoSetSize(wxDefaultCoord, wxDefaultCoord, wxDefaultCoord, GetSize().y, wxSIZE_USE_EXISTING);
476 }
477
478 void wxChoice::DoMoveWindow(int x, int y, int width, int height)
479 {
480 // here is why this is necessary: if the width is negative, the combobox
481 // window proc makes the window of the size width*height instead of
482 // interpreting height in the usual manner (meaning the height of the drop
483 // down list - usually the height specified in the call to MoveWindow()
484 // will not change the height of combo box per se)
485 //
486 // this behaviour is not documented anywhere, but this is just how it is
487 // here (NT 4.4) and, anyhow, the check shouldn't hurt - however without
488 // the check, constraints/sizers using combos may break the height
489 // constraint will have not at all the same value as expected
490 if ( width < 0 )
491 return;
492
493 wxControl::DoMoveWindow(x, y, width, height);
494 }
495
496 void wxChoice::DoGetSize(int *w, int *h) const
497 {
498 // this is weird: sometimes, the height returned by Windows is clearly the
499 // total height of the control including the drop down list -- but only
500 // sometimes, and normally it isn't... I have no idea about what to do with
501 // this
502 wxControl::DoGetSize(w, h);
503 }
504
505 void wxChoice::DoSetSize(int x, int y,
506 int width, int height,
507 int sizeFlags)
508 {
509 int heightOrig = height;
510
511 // the height which we must pass to Windows should be the total height of
512 // the control including the drop down list while the height given to us
513 // is, of course, just the height of the permanently visible part of it
514 if ( height != wxDefaultCoord )
515 {
516 // don't make the drop down list too tall, arbitrarily limit it to 40
517 // items max and also don't leave it empty
518 size_t nItems = GetCount();
519 if ( !nItems )
520 nItems = 9;
521 else if ( nItems > 24 )
522 nItems = 24;
523
524 // add space for the drop down list
525 const int hItem = SendMessage(GetHwnd(), CB_GETITEMHEIGHT, 0, 0);
526 height += hItem*(nItems + 1);
527 }
528 else
529 {
530 // We cannot pass wxDefaultCoord as height to wxControl. wxControl uses
531 // wxGetWindowRect() to determine the current height of the combobox,
532 // and then again sets the combobox's height to that value. Unfortunately,
533 // wxGetWindowRect doesn't include the dropdown list's height (at least
534 // on Win2K), so this would result in a combobox with dropdown height of
535 // 1 pixel. We have to determine the default height ourselves and call
536 // wxControl with that value instead.
537 int w, h;
538 RECT r;
539 DoGetSize(&w, &h);
540 if (::SendMessage(GetHwnd(), CB_GETDROPPEDCONTROLRECT, 0, (LPARAM) &r) != 0)
541 {
542 height = h + r.bottom - r.top;
543 }
544 }
545
546 wxControl::DoSetSize(x, y, width, height, sizeFlags);
547
548 // I'm commenting this out since the code appears to make choices
549 // and comboxes too high when they have associated sizers. I'm sure this
550 // is not the end of the story, which is why I'm leaving it #if'ed out for
551 // now. JACS.
552 #if 0
553 // if the height specified for the visible part of the control is
554 // different from the current one, we need to change it separately
555 // as it is not affected by normal WM_SETSIZE
556 if ( height != wxDefaultCoord )
557 {
558 const int delta = heightOrig - GetSize().y;
559 if ( delta )
560 {
561 int h = ::SendMessage(GetHwnd(), CB_GETITEMHEIGHT, (WPARAM)-1, 0);
562 SendMessage(GetHwnd(), CB_SETITEMHEIGHT, (WPARAM)-1, h + delta);
563 }
564 }
565 #else
566 wxUnusedVar(heightOrig);
567 #endif
568 }
569
570 wxSize wxChoice::DoGetBestSize() const
571 {
572 // find the widest string
573 int wChoice = 0;
574 const size_t nItems = GetCount();
575 for ( size_t i = 0; i < nItems; i++ )
576 {
577 int wLine;
578 GetTextExtent(GetString(i), &wLine, NULL);
579 if ( wLine > wChoice )
580 wChoice = wLine;
581 }
582
583 // give it some reasonable default value if there are no strings in the
584 // list
585 if ( wChoice == 0 )
586 wChoice = 100;
587
588 // the combobox should be slightly larger than the widest string
589 wChoice += 5*GetCharWidth();
590
591 wxSize best(wChoice, EDIT_HEIGHT_FROM_CHAR_HEIGHT(GetCharHeight()));
592 CacheBestSize(best);
593 return best;
594 }
595
596 WXLRESULT wxChoice::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
597 {
598 switch ( nMsg )
599 {
600 case WM_LBUTTONUP:
601 {
602 int x = (int)LOWORD(lParam);
603 int y = (int)HIWORD(lParam);
604
605 // Ok, this is truly weird, but if a panel with a wxChoice
606 // loses the focus, then you get a *fake* WM_LBUTTONUP message
607 // with x = 65535 and y = 65535. Filter out this nonsense.
608 //
609 // VZ: I'd like to know how to reproduce this please...
610 if ( x == 65535 && y == 65535 )
611 return 0;
612 }
613 break;
614
615 // we have to handle both: one for the normal case and the other
616 // for readonly
617 case WM_CTLCOLOREDIT:
618 case WM_CTLCOLORLISTBOX:
619 case WM_CTLCOLORSTATIC:
620 {
621 WXHDC hdc;
622 WXHWND hwnd;
623 UnpackCtlColor(wParam, lParam, &hdc, &hwnd);
624
625 WXHBRUSH hbr = MSWControlColor((WXHDC)hdc, hwnd);
626 if ( hbr )
627 return (WXLRESULT)hbr;
628 //else: fall through to default window proc
629 }
630 }
631
632 return wxWindow::MSWWindowProc(nMsg, wParam, lParam);
633 }
634
635 bool wxChoice::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
636 {
637 switch ( param )
638 {
639 case CBN_DROPDOWN:
640 // we don't want to track selection using CB_GETCURSEL while the
641 // dropdown is opened
642 m_lastAcceptedSelection = GetCurrentSelection();
643 break;
644
645 case CBN_CLOSEUP:
646 // it should be safe to use CB_GETCURSEL again
647 m_lastAcceptedSelection = wxID_NONE;
648 break;
649
650 case CBN_SELCHANGE:
651 {
652 const int n = GetSelection();
653
654 wxCommandEvent event(wxEVT_COMMAND_CHOICE_SELECTED, m_windowId);
655 event.SetInt(n);
656 event.SetEventObject(this);
657
658 if ( n > -1 )
659 {
660 event.SetString(GetStringSelection());
661 if ( HasClientObjectData() )
662 event.SetClientObject( GetClientObject(n) );
663 else if ( HasClientUntypedData() )
664 event.SetClientData( GetClientData(n) );
665 }
666
667 ProcessCommand(event);
668 }
669 return true;
670 }
671
672 return false;
673 }
674
675 WXHBRUSH wxChoice::MSWControlColor(WXHDC hDC, WXHWND hWnd)
676 {
677 if ( !IsEnabled() )
678 return MSWControlColorDisabled(hDC);
679
680 return wxChoiceBase::MSWControlColor(hDC, hWnd);
681 }
682
683 #endif // wxUSE_CHOICE && !(__SMARTPHONE__ && __WXWINCE__)
684