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