merging back XTI branch part 2
[wxWidgets.git] / src / msw / spinctrl.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/spinctrl.cpp
3 // Purpose: wxSpinCtrl class implementation for Win32
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 22.07.99
7 // RCS-ID: $Id$
8 // Copyright: (c) 1999-2005 Vadim Zeitlin
9 // Licence: 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_SPINCTRL
28
29 #include "wx/spinctrl.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/hashmap.h"
33 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
34 #include "wx/event.h"
35 #include "wx/textctrl.h"
36 #include "wx/wxcrtvararg.h"
37 #endif
38
39 #include "wx/msw/private.h"
40
41 #if wxUSE_TOOLTIPS
42 #include "wx/tooltip.h"
43 #endif // wxUSE_TOOLTIPS
44
45 #include <limits.h> // for INT_MIN
46
47 // ----------------------------------------------------------------------------
48 // macros
49 // ----------------------------------------------------------------------------
50
51 BEGIN_EVENT_TABLE(wxSpinCtrl, wxSpinButton)
52 EVT_CHAR(wxSpinCtrl::OnChar)
53 EVT_SET_FOCUS(wxSpinCtrl::OnSetFocus)
54 EVT_KILL_FOCUS(wxSpinCtrl::OnKillFocus)
55 END_EVENT_TABLE()
56
57 #define GetBuddyHwnd() (HWND)(m_hwndBuddy)
58
59 // ----------------------------------------------------------------------------
60 // constants
61 // ----------------------------------------------------------------------------
62
63 // the margin between the up-down control and its buddy (can be arbitrary,
64 // choose what you like - or may be decide during run-time depending on the
65 // font size?)
66 static const int MARGIN_BETWEEN = 1;
67
68
69 // ---------------------------------------------------------------------------
70 // global vars
71 // ---------------------------------------------------------------------------
72
73 namespace
74 {
75
76 // Global hash used to find the spin control corresponding to the given buddy
77 // text control HWND.
78 WX_DECLARE_HASH_MAP(HWND, wxSpinCtrl *,
79 wxPointerHash, wxPointerEqual,
80 SpinForTextCtrl);
81
82 SpinForTextCtrl gs_spinForTextCtrl;
83
84 } // anonymous namespace
85
86 // ============================================================================
87 // implementation
88 // ============================================================================
89
90 // ----------------------------------------------------------------------------
91 // wnd proc for the buddy text ctrl
92 // ----------------------------------------------------------------------------
93
94 LRESULT APIENTRY _EXPORT wxBuddyTextWndProc(HWND hwnd,
95 UINT message,
96 WPARAM wParam,
97 LPARAM lParam)
98 {
99 wxSpinCtrl * const spin = wxSpinCtrl::GetSpinForTextCtrl(hwnd);
100
101 // forward some messages (mostly the key and focus ones) to the spin ctrl
102 switch ( message )
103 {
104 case WM_SETFOCUS:
105 // if the focus comes from the spin control itself, don't set it
106 // back to it -- we don't want to go into an infinite loop
107 if ( (WXHWND)wParam == spin->GetHWND() )
108 break;
109 //else: fall through
110
111 case WM_KILLFOCUS:
112 case WM_CHAR:
113 case WM_DEADCHAR:
114 case WM_KEYUP:
115 case WM_KEYDOWN:
116 #ifdef WM_HELP
117 // we need to forward WM_HELP too to ensure that the context help
118 // associated with wxSpinCtrl is shown when the text control part of it
119 // is clicked with the "?" cursor
120 case WM_HELP:
121 #endif
122 spin->MSWWindowProc(message, wParam, lParam);
123
124 // The control may have been deleted at this point, so check.
125 if ( !::IsWindow(hwnd) )
126 return 0;
127 break;
128
129 case WM_GETDLGCODE:
130 if ( spin->HasFlag(wxTE_PROCESS_ENTER) )
131 {
132 long dlgCode = ::CallWindowProc
133 (
134 CASTWNDPROC spin->GetBuddyWndProc(),
135 hwnd,
136 message,
137 wParam,
138 lParam
139 );
140 dlgCode |= DLGC_WANTMESSAGE;
141 return dlgCode;
142 }
143 break;
144 }
145
146 return ::CallWindowProc(CASTWNDPROC spin->GetBuddyWndProc(),
147 hwnd, message, wParam, lParam);
148 }
149
150 /* static */
151 wxSpinCtrl *wxSpinCtrl::GetSpinForTextCtrl(WXHWND hwndBuddy)
152 {
153 const SpinForTextCtrl::const_iterator
154 it = gs_spinForTextCtrl.find(hwndBuddy);
155 if ( it == gs_spinForTextCtrl.end() )
156 return NULL;
157
158 wxSpinCtrl * const spin = it->second;
159
160 // sanity check
161 wxASSERT_MSG( spin->m_hwndBuddy == hwndBuddy,
162 wxT("wxSpinCtrl has incorrect buddy HWND!") );
163
164 return spin;
165 }
166
167 // process a WM_COMMAND generated by the buddy text control
168 bool wxSpinCtrl::ProcessTextCommand(WXWORD cmd, WXWORD WXUNUSED(id))
169 {
170 if ( (cmd == EN_CHANGE) && (!m_blockEvent ))
171 {
172 wxCommandEvent event(wxEVT_COMMAND_TEXT_UPDATED, GetId());
173 event.SetEventObject(this);
174 wxString val = wxGetWindowText(m_hwndBuddy);
175 event.SetString(val);
176 event.SetInt(GetValue());
177 return HandleWindowEvent(event);
178 }
179
180 // not processed
181 return false;
182 }
183
184 void wxSpinCtrl::OnChar(wxKeyEvent& event)
185 {
186 switch ( event.GetKeyCode() )
187 {
188 case WXK_RETURN:
189 {
190 wxCommandEvent event(wxEVT_COMMAND_TEXT_ENTER, m_windowId);
191 InitCommandEvent(event);
192 wxString val = wxGetWindowText(m_hwndBuddy);
193 event.SetString(val);
194 event.SetInt(GetValue());
195 if ( HandleWindowEvent(event) )
196 return;
197 break;
198 }
199
200 case WXK_TAB:
201 // always produce navigation event - even if we process TAB
202 // ourselves the fact that we got here means that the user code
203 // decided to skip processing of this TAB - probably to let it
204 // do its default job.
205 {
206 wxNavigationKeyEvent eventNav;
207 eventNav.SetDirection(!event.ShiftDown());
208 eventNav.SetWindowChange(event.ControlDown());
209 eventNav.SetEventObject(this);
210
211 if ( GetParent()->HandleWindowEvent(eventNav) )
212 return;
213 }
214 break;
215 }
216
217 // no, we didn't process it
218 event.Skip();
219 }
220
221 void wxSpinCtrl::OnKillFocus(wxFocusEvent& event)
222 {
223 // ensure that a correct value is shown by the control
224 NormalizeValue();
225 event.Skip();
226 }
227
228 void wxSpinCtrl::OnSetFocus(wxFocusEvent& event)
229 {
230 // when we get focus, give it to our buddy window as it needs it more than
231 // we do
232 ::SetFocus((HWND)m_hwndBuddy);
233
234 event.Skip();
235 }
236
237 void wxSpinCtrl::NormalizeValue()
238 {
239 const int value = GetValue();
240 const bool changed = value != m_oldValue;
241
242 // notice that we have to call SetValue() even if the value didn't change
243 // because otherwise we could be left with empty buddy control when value
244 // is 0, see comment in SetValue()
245 SetValue(value);
246
247 if ( changed )
248 {
249 SendSpinUpdate(value);
250 }
251 }
252
253 // ----------------------------------------------------------------------------
254 // construction
255 // ----------------------------------------------------------------------------
256
257 bool wxSpinCtrl::Create(wxWindow *parent,
258 wxWindowID id,
259 const wxString& value,
260 const wxPoint& pos,
261 const wxSize& size,
262 long style,
263 int min, int max, int initial,
264 const wxString& name)
265 {
266 m_blockEvent = false;
267
268 // this should be in ctor/init function but I don't want to add one to 2.8
269 // to avoid problems with default ctor which can be inlined in the user
270 // code and so might not get this fix without recompilation
271 m_oldValue = INT_MIN;
272
273 // before using DoGetBestSize(), have to set style to let the base class
274 // know whether this is a horizontal or vertical control (we're always
275 // vertical)
276 style |= wxSP_VERTICAL;
277
278 if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
279 #ifdef __WXWINCE__
280 style |= wxBORDER_SIMPLE;
281 #else
282 style |= wxBORDER_SUNKEN;
283 #endif
284
285 SetWindowStyle(style);
286
287 WXDWORD exStyle = 0;
288 WXDWORD msStyle = MSWGetStyle(GetWindowStyle(), & exStyle) ;
289
290 // propagate text alignment style to text ctrl
291 if ( style & wxALIGN_RIGHT )
292 msStyle |= ES_RIGHT;
293 else if ( style & wxALIGN_CENTER )
294 msStyle |= ES_CENTER;
295
296 // calculate the sizes: the size given is the total size for both controls
297 // and we need to fit them both in the given width (height is the same)
298 wxSize sizeText(size), sizeBtn(size);
299 sizeBtn.x = wxSpinButton::DoGetBestSize().x;
300 if ( sizeText.x <= 0 )
301 {
302 // DEFAULT_ITEM_WIDTH is the default width for the text control
303 sizeText.x = DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN + sizeBtn.x;
304 }
305
306 sizeText.x -= sizeBtn.x + MARGIN_BETWEEN;
307 if ( sizeText.x <= 0 )
308 {
309 wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
310 }
311
312 wxPoint posBtn(pos);
313 posBtn.x += sizeText.x + MARGIN_BETWEEN;
314
315 // we must create the text control before the spin button for the purpose
316 // of the dialog navigation: if there is a static text just before the spin
317 // control, activating it by Alt-letter should give focus to the text
318 // control, not the spin and the dialog navigation code will give focus to
319 // the next control (at Windows level), not the one after it
320
321 // create the text window
322
323 m_hwndBuddy = (WXHWND)::CreateWindowEx
324 (
325 exStyle, // sunken border
326 wxT("EDIT"), // window class
327 NULL, // no window title
328 msStyle, // style (will be shown later)
329 pos.x, pos.y, // position
330 0, 0, // size (will be set later)
331 GetHwndOf(parent), // parent
332 (HMENU)-1, // control id
333 wxGetInstance(), // app instance
334 NULL // unused client data
335 );
336
337 if ( !m_hwndBuddy )
338 {
339 wxLogLastError(wxT("CreateWindow(buddy text window)"));
340
341 return false;
342 }
343
344
345 // create the spin button
346 if ( !wxSpinButton::Create(parent, id, posBtn, sizeBtn, style, name) )
347 {
348 return false;
349 }
350
351 wxSpinButtonBase::SetRange(min, max);
352
353 // subclass the text ctrl to be able to intercept some events
354 gs_spinForTextCtrl[GetBuddyHwnd()] = this;
355
356 m_wndProcBuddy = (WXFARPROC)wxSetWindowProc(GetBuddyHwnd(),
357 wxBuddyTextWndProc);
358
359 // set up fonts and colours (This is nomally done in MSWCreateControl)
360 InheritAttributes();
361 if (!m_hasFont)
362 SetFont(GetDefaultAttributes().font);
363
364 // set the size of the text window - can do it only now, because we
365 // couldn't call DoGetBestSize() before as font wasn't set
366 if ( sizeText.y <= 0 )
367 {
368 int cx, cy;
369 wxGetCharSize(GetHWND(), &cx, &cy, GetFont());
370
371 sizeText.y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy);
372 }
373
374 SetInitialSize(size);
375
376 (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW);
377
378 // associate the text window with the spin button
379 (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0);
380
381 SetValue(initial);
382
383 // Set the range in the native control
384 SetRange(min, max);
385
386 if ( !value.empty() )
387 {
388 SetValue(value);
389 m_oldValue = (int) wxAtol(value);
390 }
391 else
392 {
393 SetValue(wxString::Format(wxT("%d"), initial));
394 m_oldValue = initial;
395 }
396
397 return true;
398 }
399
400 wxSpinCtrl::~wxSpinCtrl()
401 {
402 // destroy the buddy window because this pointer which wxBuddyTextWndProc
403 // uses will not soon be valid any more
404 ::DestroyWindow( GetBuddyHwnd() );
405
406 gs_spinForTextCtrl.erase(GetBuddyHwnd());
407 }
408
409 // ----------------------------------------------------------------------------
410 // wxTextCtrl-like methods
411 // ----------------------------------------------------------------------------
412
413 void wxSpinCtrl::SetValue(const wxString& text)
414 {
415 if ( !::SetWindowText(GetBuddyHwnd(), text.c_str()) )
416 {
417 wxLogLastError(wxT("SetWindowText(buddy)"));
418 }
419 }
420
421 void wxSpinCtrl::SetValue(int val)
422 {
423 m_blockEvent = true;
424
425 wxSpinButton::SetValue(val);
426
427 // normally setting the value of the spin button is enough as it updates
428 // its buddy control automatically ...
429 if ( wxGetWindowText(m_hwndBuddy).empty() )
430 {
431 // ... but sometimes it doesn't, notably when the value is 0 and the
432 // text control is currently empty, the spin button seems to be happy
433 // to leave it like this, while we really want to always show the
434 // current value in the control, so do it manually
435 ::SetWindowText(GetBuddyHwnd(),
436 wxString::Format(wxT("%d"), val).wx_str());
437 }
438
439 m_oldValue = GetValue();
440
441 m_blockEvent = false;
442 }
443
444 int wxSpinCtrl::GetValue() const
445 {
446 wxString val = wxGetWindowText(m_hwndBuddy);
447
448 long n;
449 if ( (wxSscanf(val, wxT("%ld"), &n) != 1) )
450 n = INT_MIN;
451
452 if ( n < m_min )
453 n = m_min;
454 if ( n > m_max )
455 n = m_max;
456
457 return n;
458 }
459
460 void wxSpinCtrl::SetSelection(long from, long to)
461 {
462 // if from and to are both -1, it means (in wxWidgets) that all text should
463 // be selected - translate into Windows convention
464 if ( (from == -1) && (to == -1) )
465 {
466 from = 0;
467 }
468
469 ::SendMessage(GetBuddyHwnd(), EM_SETSEL, (WPARAM)from, (LPARAM)to);
470 }
471
472 // ----------------------------------------------------------------------------
473 // wxSpinButton methods
474 // ----------------------------------------------------------------------------
475
476 void wxSpinCtrl::SetRange(int minVal, int maxVal)
477 {
478 wxSpinButton::SetRange(minVal, maxVal);
479
480 // this control is used for numeric entry so restrict the input to numeric
481 // keys only -- but only if we don't need to be able to enter "-" in it as
482 // otherwise this would become impossible
483 const DWORD styleOld = ::GetWindowLong(GetBuddyHwnd(), GWL_STYLE);
484 DWORD styleNew;
485 if ( minVal < 0 )
486 styleNew = styleOld & ~ES_NUMBER;
487 else
488 styleNew = styleOld | ES_NUMBER;
489
490 if ( styleNew != styleOld )
491 ::SetWindowLong(GetBuddyHwnd(), GWL_STYLE, styleNew);
492 }
493
494 // ----------------------------------------------------------------------------
495 // forward some methods to subcontrols
496 // ----------------------------------------------------------------------------
497
498 bool wxSpinCtrl::SetFont(const wxFont& font)
499 {
500 if ( !wxWindowBase::SetFont(font) )
501 {
502 // nothing to do
503 return false;
504 }
505
506 WXHANDLE hFont = GetFont().GetResourceHandle();
507 (void)::SendMessage(GetBuddyHwnd(), WM_SETFONT, (WPARAM)hFont, TRUE);
508
509 return true;
510 }
511
512 bool wxSpinCtrl::Show(bool show)
513 {
514 if ( !wxControl::Show(show) )
515 {
516 return false;
517 }
518
519 ::ShowWindow(GetBuddyHwnd(), show ? SW_SHOW : SW_HIDE);
520
521 return true;
522 }
523
524 bool wxSpinCtrl::Reparent(wxWindowBase *newParent)
525 {
526 // Reparenting both the updown control and its buddy does not seem to work:
527 // they continue to be connected somehow, but visually there is no feedback
528 // on the buddy edit control. To avoid this problem, we reparent the buddy
529 // window normally, but we recreate the updown control and reassign its
530 // buddy.
531
532 // Get the position before changing the parent as it would be offset after
533 // changing it.
534 const wxRect rect = GetRect();
535
536 if ( !wxWindowBase::Reparent(newParent) )
537 return false;
538
539 newParent->GetChildren().DeleteObject(this);
540
541 // destroy the old spin button after detaching it from this wxWindow object
542 // (notice that m_hWnd will be reset by UnsubclassWin() so save it first)
543 const HWND hwndOld = GetHwnd();
544 UnsubclassWin();
545 if ( !::DestroyWindow(hwndOld) )
546 {
547 wxLogLastError(wxT("DestroyWindow"));
548 }
549
550 // create and initialize the new one
551 if ( !wxSpinButton::Create(GetParent(), GetId(),
552 rect.GetPosition(), rect.GetSize(),
553 GetWindowStyle(), GetName()) )
554 return false;
555
556 // reapply our values to wxSpinButton
557 wxSpinButton::SetValue(GetValue());
558 SetRange(m_min, m_max);
559
560 // also set the size again with wxSIZE_ALLOW_MINUS_ONE flag: this is
561 // necessary if our original position used -1 for either x or y
562 SetSize(rect, wxSIZE_ALLOW_MINUS_ONE);
563
564 // associate it with the buddy control again
565 ::SetParent(GetBuddyHwnd(), GetHwndOf(GetParent()));
566 (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)GetBuddyHwnd(), 0);
567
568 return true;
569 }
570
571 bool wxSpinCtrl::Enable(bool enable)
572 {
573 if ( !wxControl::Enable(enable) )
574 {
575 return false;
576 }
577
578 MSWEnableHWND(GetBuddyHwnd(), enable);
579
580 return true;
581 }
582
583 void wxSpinCtrl::SetFocus()
584 {
585 ::SetFocus(GetBuddyHwnd());
586 }
587
588 #if wxUSE_TOOLTIPS
589
590 void wxSpinCtrl::DoSetToolTip(wxToolTip *tip)
591 {
592 wxSpinButton::DoSetToolTip(tip);
593
594 if ( tip )
595 tip->Add(m_hwndBuddy);
596 }
597
598 #endif // wxUSE_TOOLTIPS
599
600 // ----------------------------------------------------------------------------
601 // events processing and generation
602 // ----------------------------------------------------------------------------
603
604 void wxSpinCtrl::SendSpinUpdate(int value)
605 {
606 wxCommandEvent event(wxEVT_COMMAND_SPINCTRL_UPDATED, GetId());
607 event.SetEventObject(this);
608 event.SetInt(value);
609
610 (void)HandleWindowEvent(event);
611
612 m_oldValue = value;
613 }
614
615 bool wxSpinCtrl::MSWOnScroll(int WXUNUSED(orientation), WXWORD wParam,
616 WXWORD pos, WXHWND control)
617 {
618 wxCHECK_MSG( control, false, wxT("scrolling what?") );
619
620 if ( wParam != SB_THUMBPOSITION )
621 {
622 // probable SB_ENDSCROLL - we don't react to it
623 return false;
624 }
625
626 int new_value = (short) pos;
627 if (m_oldValue != new_value)
628 SendSpinUpdate( new_value );
629
630 return TRUE;
631 }
632
633 bool wxSpinCtrl::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM *result)
634 {
635 NM_UPDOWN *lpnmud = (NM_UPDOWN *)lParam;
636
637 if (lpnmud->hdr.hwndFrom != GetHwnd()) // make sure it is the right control
638 return false;
639
640 *result = 0; // never reject UP and DOWN events
641
642 return TRUE;
643 }
644
645
646 // ----------------------------------------------------------------------------
647 // size calculations
648 // ----------------------------------------------------------------------------
649
650 wxSize wxSpinCtrl::DoGetBestSize() const
651 {
652 wxSize sizeBtn = wxSpinButton::DoGetBestSize();
653 sizeBtn.x += DEFAULT_ITEM_WIDTH + MARGIN_BETWEEN;
654
655 int y;
656 wxGetCharSize(GetHWND(), NULL, &y, GetFont());
657 y = EDIT_HEIGHT_FROM_CHAR_HEIGHT(y);
658
659 // JACS: we should always use the height calculated
660 // from above, because otherwise we'll get a spin control
661 // that's too big. So never use the height calculated
662 // from wxSpinButton::DoGetBestSize().
663
664 // if ( sizeBtn.y < y )
665 {
666 // make the text tall enough
667 sizeBtn.y = y;
668 }
669
670 return sizeBtn;
671 }
672
673 void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height)
674 {
675 int widthBtn = wxSpinButton::DoGetBestSize().x;
676 int widthText = width - widthBtn - MARGIN_BETWEEN;
677 if ( widthText <= 0 )
678 {
679 wxLogDebug(wxT("not enough space for wxSpinCtrl!"));
680 }
681
682 // 1) The buddy window
683 DoMoveSibling(m_hwndBuddy, x, y, widthText, height);
684
685 // 2) The button window
686 x += widthText + MARGIN_BETWEEN;
687 wxSpinButton::DoMoveWindow(x, y, widthBtn, height);
688 }
689
690 // get total size of the control
691 void wxSpinCtrl::DoGetSize(int *x, int *y) const
692 {
693 RECT spinrect, textrect, ctrlrect;
694 GetWindowRect(GetHwnd(), &spinrect);
695 GetWindowRect(GetBuddyHwnd(), &textrect);
696 UnionRect(&ctrlrect,&textrect, &spinrect);
697
698 if ( x )
699 *x = ctrlrect.right - ctrlrect.left;
700 if ( y )
701 *y = ctrlrect.bottom - ctrlrect.top;
702 }
703
704 void wxSpinCtrl::DoGetClientSize(int *x, int *y) const
705 {
706 RECT spinrect = wxGetClientRect(GetHwnd());
707 RECT textrect = wxGetClientRect(GetBuddyHwnd());
708 RECT ctrlrect;
709 UnionRect(&ctrlrect,&textrect, &spinrect);
710
711 if ( x )
712 *x = ctrlrect.right - ctrlrect.left;
713 if ( y )
714 *y = ctrlrect.bottom - ctrlrect.top;
715 }
716
717 void wxSpinCtrl::DoGetPosition(int *x, int *y) const
718 {
719 // hack: pretend that our HWND is the text control just for a moment
720 WXHWND hWnd = GetHWND();
721 wxConstCast(this, wxSpinCtrl)->m_hWnd = m_hwndBuddy;
722
723 wxSpinButton::DoGetPosition(x, y);
724
725 wxConstCast(this, wxSpinCtrl)->m_hWnd = hWnd;
726 }
727
728 #endif // wxUSE_SPINCTRL