wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / msw / wince / choicece.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/wince/choicece.cpp
3 // Purpose: wxChoice implementation for smart phones driven by WinCE
4 // Author: Wlodzimierz ABX Skiba
5 // Modified by:
6 // Created: 29.07.2004
7 // Copyright: (c) Wlodzimierz Skiba
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #if wxUSE_CHOICE && defined(__SMARTPHONE__) && defined(__WXWINCE__)
27
28 #include "wx/choice.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
32 #endif
33
34 #include "wx/spinbutt.h" // for wxSpinnerBestSize
35
36 #define GetBuddyHwnd() (HWND)(m_hwndBuddy)
37
38 #define IsVertical(wxStyle) ( (wxStyle & wxSP_HORIZONTAL) != wxSP_HORIZONTAL )
39
40 // ----------------------------------------------------------------------------
41 // constants
42 // ----------------------------------------------------------------------------
43
44 // the margin between the up-down control and its buddy (can be arbitrary,
45 // choose what you like - or may be decide during run-time depending on the
46 // font size?)
47 static const int MARGIN_BETWEEN = 0;
48
49 // ============================================================================
50 // implementation
51 // ============================================================================
52
53 wxArrayChoiceSpins wxChoice::ms_allChoiceSpins;
54
55 // ----------------------------------------------------------------------------
56 // wnd proc for the buddy text ctrl
57 // ----------------------------------------------------------------------------
58
59 LRESULT APIENTRY _EXPORT wxBuddyChoiceWndProc(HWND hwnd,
60 UINT message,
61 WPARAM wParam,
62 LPARAM lParam)
63 {
64 wxChoice *spin = (wxChoice *)wxGetWindowUserData(hwnd);
65
66 // forward some messages (the key and focus ones only so far) to
67 // the spin ctrl
68 switch ( message )
69 {
70 case WM_SETFOCUS:
71 // if the focus comes from the spin control itself, don't set it
72 // back to it -- we don't want to go into an infinite loop
73 if ( (WXHWND)wParam == spin->GetHWND() )
74 break;
75 //else: fall through
76
77 case WM_KILLFOCUS:
78 case WM_CHAR:
79 case WM_DEADCHAR:
80 case WM_KEYUP:
81 case WM_KEYDOWN:
82 spin->MSWWindowProc(message, wParam, lParam);
83
84 // The control may have been deleted at this point, so check.
85 if ( !::IsWindow(hwnd) || wxGetWindowUserData(hwnd) != spin )
86 return 0;
87 break;
88
89 case WM_GETDLGCODE:
90 // we want to get WXK_RETURN in order to generate the event for it
91 return DLGC_WANTCHARS;
92 }
93
94 return ::CallWindowProc(CASTWNDPROC spin->GetBuddyWndProc(),
95 hwnd, message, wParam, lParam);
96 }
97
98 wxChoice *wxChoice::GetChoiceForListBox(WXHWND hwndBuddy)
99 {
100 wxChoice *choice = (wxChoice *)wxGetWindowUserData((HWND)hwndBuddy);
101
102 int i = ms_allChoiceSpins.Index(choice);
103
104 if ( i == wxNOT_FOUND )
105 return NULL;
106
107 // sanity check
108 wxASSERT_MSG( choice->m_hwndBuddy == hwndBuddy,
109 wxT("wxChoice has incorrect buddy HWND!") );
110
111 return choice;
112 }
113
114 // ----------------------------------------------------------------------------
115 // creation
116 // ----------------------------------------------------------------------------
117
118 bool wxChoice::Create(wxWindow *parent,
119 wxWindowID id,
120 const wxPoint& pos,
121 const wxSize& size,
122 int n, const wxString choices[],
123 long style,
124 const wxValidator& validator,
125 const wxString& name)
126 {
127 return CreateAndInit(parent, id, pos, size, n, choices, style,
128 validator, name);
129 }
130
131 bool wxChoice::CreateAndInit(wxWindow *parent,
132 wxWindowID id,
133 const wxPoint& pos,
134 const wxSize& size,
135 int n, const wxString choices[],
136 long style,
137 const wxValidator& validator,
138 const wxString& name)
139 {
140 if ( !(style & wxSP_VERTICAL) )
141 style |= wxSP_HORIZONTAL;
142
143 if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
144 style |= wxBORDER_SIMPLE;
145
146 style |= wxSP_ARROW_KEYS;
147
148 SetWindowStyle(style);
149
150 WXDWORD exStyle = 0;
151 WXDWORD msStyle = MSWGetStyle(GetWindowStyle(), & exStyle) ;
152
153 wxSize sizeText(size), sizeBtn(size);
154 sizeBtn.x = GetBestSpinnerSize(IsVertical(style)).x;
155
156 if ( sizeText.x == wxDefaultCoord )
157 {
158 // DEFAULT_ITEM_WIDTH is the default width for the text control
159 sizeText.x = DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN + sizeBtn.x;
160 }
161
162 sizeText.x -= sizeBtn.x + MARGIN_BETWEEN;
163 if ( sizeText.x <= 0 )
164 {
165 wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
166 }
167
168 wxPoint posBtn(pos);
169 posBtn.x += sizeText.x + MARGIN_BETWEEN;
170
171 // we must create the list control before the spin button for the purpose
172 // of the dialog navigation: if there is a static text just before the spin
173 // control, activating it by Alt-letter should give focus to the text
174 // control, not the spin and the dialog navigation code will give focus to
175 // the next control (at Windows level), not the one after it
176
177 // create the text window
178
179 m_hwndBuddy = (WXHWND)::CreateWindowEx
180 (
181 exStyle, // sunken border
182 wxT("LISTBOX"), // window class
183 NULL, // no window title
184 msStyle, // style (will be shown later)
185 pos.x, pos.y, // position
186 0, 0, // size (will be set later)
187 GetHwndOf(parent), // parent
188 (HMENU)-1, // control id
189 wxGetInstance(), // app instance
190 NULL // unused client data
191 );
192
193 if ( !m_hwndBuddy )
194 {
195 wxLogLastError(wxT("CreateWindow(buddy text window)"));
196
197 return false;
198 }
199
200 // initialize wxControl
201 if ( !CreateControl(parent, id, posBtn, sizeBtn, style, validator, name) )
202 return false;
203
204 // now create the real HWND
205 WXDWORD spiner_style = WS_VISIBLE |
206 UDS_ALIGNRIGHT |
207 UDS_ARROWKEYS |
208 UDS_SETBUDDYINT |
209 UDS_EXPANDABLE;
210
211 if ( !IsVertical(style) )
212 spiner_style |= UDS_HORZ;
213
214 if ( style & wxSP_WRAP )
215 spiner_style |= UDS_WRAP;
216
217 if ( !MSWCreateControl(UPDOWN_CLASS, spiner_style, posBtn, sizeBtn, wxEmptyString, 0) )
218 return false;
219
220 // subclass the text ctrl to be able to intercept some events
221 wxSetWindowUserData(GetBuddyHwnd(), this);
222 m_wndProcBuddy = (WXFARPROC)wxSetWindowProc(GetBuddyHwnd(),
223 wxBuddyChoiceWndProc);
224
225 // set up fonts and colours (This is nomally done in MSWCreateControl)
226 InheritAttributes();
227 if (!m_hasFont)
228 SetFont(GetDefaultAttributes().font);
229
230 // set the size of the text window - can do it only now, because we
231 // couldn't call DoGetBestSize() before as font wasn't set
232 if ( sizeText.y <= 0 )
233 {
234 int cx, cy;
235 wxGetCharSize(GetHWND(), &cx, &cy, GetFont());
236
237 sizeText.y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy);
238 }
239
240 SetInitialSize(size);
241
242 (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW);
243
244 // associate the list window with the spin button
245 (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)GetBuddyHwnd(), 0);
246
247 // do it after finishing with m_hwndBuddy creation to avoid generating
248 // initial wxEVT_TEXT message
249 ms_allChoiceSpins.Add(this);
250
251 // initialize the controls contents
252 for ( int i = 0; i < n; i++ )
253 {
254 Append(choices[i]);
255 }
256
257 return true;
258 }
259
260 bool wxChoice::Create(wxWindow *parent,
261 wxWindowID id,
262 const wxPoint& pos,
263 const wxSize& size,
264 const wxArrayString& choices,
265 long style,
266 const wxValidator& validator,
267 const wxString& name)
268 {
269 wxCArrayString chs(choices);
270 return Create(parent, id, pos, size, chs.GetCount(), chs.GetStrings(),
271 style, validator, name);
272 }
273
274 WXDWORD wxChoice::MSWGetStyle(long style, WXDWORD *exstyle) const
275 {
276 // we never have an external border
277 WXDWORD msStyle = wxControl::MSWGetStyle
278 (
279 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
280 );
281
282 msStyle |= WS_VISIBLE;
283
284 // wxChoice-specific styles
285 msStyle |= LBS_NOINTEGRALHEIGHT;
286 if ( style & wxCB_SORT )
287 msStyle |= LBS_SORT;
288
289 msStyle |= LBS_NOTIFY;
290
291 return msStyle;
292 }
293
294 bool wxChoice::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
295 {
296 if ( param != LBN_SELCHANGE)
297 {
298 // "selection changed" is the only event we're after
299 return false;
300 }
301
302 int n = GetSelection();
303 if (n > -1)
304 {
305 wxCommandEvent event(wxEVT_CHOICE, m_windowId);
306 event.SetInt(n);
307 event.SetEventObject(this);
308 event.SetString(GetStringSelection());
309 if ( HasClientObjectData() )
310 event.SetClientObject( GetClientObject(n) );
311 else if ( HasClientUntypedData() )
312 event.SetClientData( GetClientData(n) );
313 ProcessCommand(event);
314 }
315
316 return true;
317 }
318
319 wxChoice::~wxChoice()
320 {
321 Clear();
322 }
323
324 // ----------------------------------------------------------------------------
325 // adding/deleting items to/from the list
326 // ----------------------------------------------------------------------------
327
328 int wxChoice::DoInsertItems(const wxArrayStringsAdapter& items,
329 unsigned int pos,
330 void **clientData,
331 wxClientDataType type)
332 {
333 MSWAllocStorage(items, LB_INITSTORAGE);
334
335 const bool append = pos == GetCount();
336 const unsigned msg = append ? LB_ADDSTRING : LB_INSERTSTRING;
337 if ( append )
338 pos = 0;
339
340 int n = wxNOT_FOUND;
341
342 const unsigned int numItems = items.GetCount();
343 for ( unsigned int i = 0; i < numItems; ++i )
344 {
345 n = MSWInsertOrAppendItem(pos, items[i], msg);
346 if ( !append )
347 pos++;
348
349 AssignNewItemClientData(n, clientData, i, type);
350 }
351
352 return n;
353 }
354
355 void wxChoice::DoDeleteOneItem(unsigned int n)
356 {
357 wxCHECK_RET( IsValid(n), wxT("invalid item index in wxChoice::Delete") );
358
359 ::SendMessage(GetBuddyHwnd(), LB_DELETESTRING, n, 0);
360 }
361
362 void wxChoice::DoClear()
363 {
364 ::SendMessage(GetBuddyHwnd(), LB_RESETCONTENT, 0, 0);
365 }
366
367 // ----------------------------------------------------------------------------
368 // selection
369 // ----------------------------------------------------------------------------
370
371 int wxChoice::GetSelection() const
372 {
373 return (int)::SendMessage(GetBuddyHwnd(), LB_GETCURSEL, 0, 0);
374 }
375
376 void wxChoice::SetSelection(int n)
377 {
378 ::SendMessage(GetBuddyHwnd(), LB_SETCURSEL, n, 0);
379 }
380
381 // ----------------------------------------------------------------------------
382 // string list functions
383 // ----------------------------------------------------------------------------
384
385 unsigned int wxChoice::GetCount() const
386 {
387 return (unsigned int)::SendMessage(GetBuddyHwnd(), LB_GETCOUNT, 0, 0);
388 }
389
390 int wxChoice::FindString(const wxString& s, bool bCase) const
391 {
392 // back to base class search for not native search type
393 if (bCase)
394 return wxItemContainerImmutable::FindString( s, bCase );
395
396 int pos = (int)::SendMessage(GetBuddyHwnd(), LB_FINDSTRINGEXACT,
397 (WPARAM)-1, (LPARAM)s.c_str());
398
399 return pos == LB_ERR ? wxNOT_FOUND : pos;
400 }
401
402 void wxChoice::SetString(unsigned int n, const wxString& s)
403 {
404 wxCHECK_RET( IsValid(n),
405 wxT("invalid item index in wxChoice::SetString") );
406
407 // we have to delete and add back the string as there is no way to change a
408 // string in place
409
410 // we need to preserve the client data
411 void *data;
412 if ( m_clientDataItemsType != wxClientData_None )
413 {
414 data = DoGetItemClientData(n);
415 }
416 else // no client data
417 {
418 data = NULL;
419 }
420
421 ::SendMessage(GetBuddyHwnd(), LB_DELETESTRING, n, 0);
422 ::SendMessage(GetBuddyHwnd(), LB_INSERTSTRING, n, (LPARAM)s.c_str() );
423
424 if ( data )
425 {
426 DoSetItemClientData(n, data);
427 }
428 //else: it's already NULL by default
429 }
430
431 wxString wxChoice::GetString(unsigned int n) const
432 {
433 int len = (int)::SendMessage(GetBuddyHwnd(), LB_GETTEXTLEN, n, 0);
434
435 wxString str;
436 if ( len != LB_ERR && len > 0 )
437 {
438 if ( ::SendMessage
439 (
440 GetBuddyHwnd(),
441 LB_GETTEXT,
442 n,
443 (LPARAM)(wxChar *)wxStringBuffer(str, len)
444 ) == LB_ERR )
445 {
446 wxLogLastError(wxT("SendMessage(LB_GETLBTEXT)"));
447 }
448 }
449
450 return str;
451 }
452
453 // ----------------------------------------------------------------------------
454 // client data
455 // ----------------------------------------------------------------------------
456
457 void wxChoice::DoSetItemClientData(unsigned int n, void* clientData)
458 {
459 if ( ::SendMessage(GetHwnd(), LB_SETITEMDATA,
460 n, (LPARAM)clientData) == LB_ERR )
461 {
462 wxLogLastError(wxT("LB_SETITEMDATA"));
463 }
464 }
465
466 void* wxChoice::DoGetItemClientData(unsigned int n) const
467 {
468 LPARAM rc = ::SendMessage(GetHwnd(), LB_GETITEMDATA, n, 0);
469 if ( rc == LB_ERR )
470 {
471 wxLogLastError(wxT("LB_GETITEMDATA"));
472
473 // unfortunately, there is no way to return an error code to the user
474 rc = (LPARAM) NULL;
475 }
476
477 return (void *)rc;
478 }
479
480 // ----------------------------------------------------------------------------
481 // size calculations
482 // ----------------------------------------------------------------------------
483
484 wxSize wxChoice::DoGetBestSize() const
485 {
486 wxSize sizeBtn = GetBestSpinnerSize(IsVertical(GetWindowStyle()));
487 sizeBtn.x += DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN;
488
489 int y;
490 wxGetCharSize(GetHWND(), NULL, &y, GetFont());
491 y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(y);
492
493 // JACS: we should always use the height calculated
494 // from above, because otherwise we'll get a spin control
495 // that's too big. So never use the height calculated
496 // from wxSpinButton::DoGetBestSize().
497
498 // if ( sizeBtn.y < y )
499 {
500 // make the text tall enough
501 sizeBtn.y = y;
502 }
503
504 return sizeBtn;
505 }
506
507 void wxChoice::DoMoveWindow(int x, int y, int width, int height)
508 {
509 int widthBtn = GetBestSpinnerSize(IsVertical(GetWindowStyle())).x;
510 int widthText = width - widthBtn - MARGIN_BETWEEN;
511 if ( widthText <= 0 )
512 {
513 wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
514 }
515
516 if ( !::MoveWindow(GetBuddyHwnd(), x, y, widthText, height, TRUE) )
517 {
518 wxLogLastError(wxT("MoveWindow(buddy)"));
519 }
520
521 x += widthText + MARGIN_BETWEEN;
522 if ( !::MoveWindow(GetHwnd(), x, y, widthBtn, height, TRUE) )
523 {
524 wxLogLastError(wxT("MoveWindow"));
525 }
526 }
527
528 // get total size of the control
529 void wxChoice::DoGetSize(int *x, int *y) const
530 {
531 RECT spinrect, textrect, ctrlrect;
532 GetWindowRect(GetHwnd(), &spinrect);
533 GetWindowRect(GetBuddyHwnd(), &textrect);
534 UnionRect(&ctrlrect, &textrect, &spinrect);
535
536 if ( x )
537 *x = ctrlrect.right - ctrlrect.left;
538 if ( y )
539 *y = ctrlrect.bottom - ctrlrect.top;
540 }
541
542 void wxChoice::DoGetPosition(int *x, int *y) const
543 {
544 // hack: pretend that our HWND is the text control just for a moment
545 WXHWND hWnd = GetHWND();
546 wxConstCast(this, wxChoice)->m_hWnd = m_hwndBuddy;
547
548 wxChoiceBase::DoGetPosition(x, y);
549
550 wxConstCast(this, wxChoice)->m_hWnd = hWnd;
551 }
552
553 #endif // wxUSE_CHOICE && __SMARTPHONE__ && __WXWINCE__