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