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 
wxBuddyChoiceWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)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 
GetChoiceForListBox(WXHWND hwndBuddy)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 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],long style,const wxValidator & validator,const wxString & name)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 
CreateAndInit(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,int n,const wxString choices[],long style,const wxValidator & validator,const wxString & name)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 
Create(wxWindow * parent,wxWindowID id,const wxPoint & pos,const wxSize & size,const wxArrayString & choices,long style,const wxValidator & validator,const wxString & name)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 
MSWGetStyle(long style,WXDWORD * exstyle) const274 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 
MSWCommand(WXUINT param,WXWORD WXUNUSED (id))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 
~wxChoice()319 wxChoice::~wxChoice()
320 {
321     Clear();
322 }
323 
324 // ----------------------------------------------------------------------------
325 // adding/deleting items to/from the list
326 // ----------------------------------------------------------------------------
327 
DoInsertItems(const wxArrayStringsAdapter & items,unsigned int pos,void ** clientData,wxClientDataType type)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 
DoDeleteOneItem(unsigned int n)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 
DoClear()362 void wxChoice::DoClear()
363 {
364     ::SendMessage(GetBuddyHwnd(), LB_RESETCONTENT, 0, 0);
365 }
366 
367 // ----------------------------------------------------------------------------
368 // selection
369 // ----------------------------------------------------------------------------
370 
GetSelection() const371 int wxChoice::GetSelection() const
372 {
373     return (int)::SendMessage(GetBuddyHwnd(), LB_GETCURSEL, 0, 0);
374 }
375 
SetSelection(int n)376 void wxChoice::SetSelection(int n)
377 {
378     ::SendMessage(GetBuddyHwnd(), LB_SETCURSEL, n, 0);
379 }
380 
381 // ----------------------------------------------------------------------------
382 // string list functions
383 // ----------------------------------------------------------------------------
384 
GetCount() const385 unsigned int wxChoice::GetCount() const
386 {
387     return (unsigned int)::SendMessage(GetBuddyHwnd(), LB_GETCOUNT, 0, 0);
388 }
389 
FindString(const wxString & s,bool bCase) const390 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 
SetString(unsigned int n,const wxString & s)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 
GetString(unsigned int n) const431 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 
DoSetItemClientData(unsigned int n,void * clientData)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 
DoGetItemClientData(unsigned int n) const466 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 
DoGetBestSize() const484 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 
DoMoveWindow(int x,int y,int width,int height)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
DoGetSize(int * x,int * y) const529 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 
DoGetPosition(int * x,int * y) const542 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__
554