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