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 // Copyright: (c) 1999-2005 Vadim Zeitlin
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
23 #if wxUSE_SPINCTRL
24
25 #include "wx/spinctrl.h"
26
27 #ifndef WX_PRECOMP
28 #include "wx/hashmap.h"
29 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
30 #include "wx/event.h"
31 #include "wx/textctrl.h"
32 #include "wx/wxcrtvararg.h"
33 #endif
34
35 #include "wx/private/spinctrl.h"
36
37 #include "wx/msw/private.h"
38 #include "wx/msw/private/winstyle.h"
39
40 #if wxUSE_TOOLTIPS
41 #include "wx/tooltip.h"
42 #endif // wxUSE_TOOLTIPS
43
44 #include <limits.h> // for INT_MIN
45
46 // ----------------------------------------------------------------------------
47 // macros
48 // ----------------------------------------------------------------------------
49
50 wxBEGIN_EVENT_TABLE(wxSpinCtrl, wxSpinButton)
51 EVT_CHAR(wxSpinCtrl::OnChar)
52 EVT_SET_FOCUS(wxSpinCtrl::OnSetFocus)
53 EVT_KILL_FOCUS(wxSpinCtrl::OnKillFocus)
54 wxEND_EVENT_TABLE()
55
56 #define GetBuddyHwnd() (HWND)(m_hwndBuddy)
57
58
59 // ---------------------------------------------------------------------------
60 // global vars
61 // ---------------------------------------------------------------------------
62
63 namespace
64 {
65
66 // Global hash used to find the spin control corresponding to the given buddy
67 // text control HWND.
68 WX_DECLARE_HASH_MAP(HWND, wxSpinCtrl *,
69 wxPointerHash, wxPointerEqual,
70 SpinForTextCtrl);
71
72 SpinForTextCtrl gs_spinForTextCtrl;
73
74 } // anonymous namespace
75
76 // ============================================================================
77 // implementation
78 // ============================================================================
79
80 // ----------------------------------------------------------------------------
81 // wnd proc for the buddy text ctrl
82 // ----------------------------------------------------------------------------
83
84 LRESULT APIENTRY
wxBuddyTextWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)85 wxBuddyTextWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
86 {
87 wxSpinCtrl * const spin = wxSpinCtrl::GetSpinForTextCtrl(hwnd);
88
89 // forward some messages (mostly the key and focus ones) to the spin ctrl
90 switch ( message )
91 {
92 case WM_SETFOCUS:
93 // if the focus comes from the spin control itself, don't set it
94 // back to it -- we don't want to go into an infinite loop
95 if ( (WXHWND)wParam == spin->GetHWND() )
96 break;
97 //else: fall through
98 wxFALLTHROUGH;
99
100 case WM_KILLFOCUS:
101 case WM_CHAR:
102 case WM_DEADCHAR:
103 case WM_KEYUP:
104 case WM_KEYDOWN:
105 // we need to forward WM_HELP too to ensure that the context help
106 // associated with wxSpinCtrl is shown when the text control part of it
107 // is clicked with the "?" cursor
108 case WM_HELP:
109 {
110 WXLRESULT result;
111 if ( spin->MSWHandleMessage(&result, message, wParam, lParam) )
112 {
113 // Do not let the message be processed by the window proc
114 // of the text control if it had been already handled at wx
115 // level, this is consistent with what happens for normal,
116 // non-composite controls.
117 return 0;
118 }
119
120 // The control may have been deleted at this point, so check.
121 if ( !::IsWindow(hwnd) )
122 return 0;
123 }
124 break;
125
126 case WM_GETDLGCODE:
127 if ( spin->HasFlag(wxTE_PROCESS_ENTER) )
128 {
129 long dlgCode = ::CallWindowProc
130 (
131 CASTWNDPROC spin->GetBuddyWndProc(),
132 hwnd,
133 message,
134 wParam,
135 lParam
136 );
137 dlgCode |= DLGC_WANTMESSAGE;
138 return dlgCode;
139 }
140 break;
141 }
142
143 return ::CallWindowProc(CASTWNDPROC spin->GetBuddyWndProc(),
144 hwnd, message, wParam, lParam);
145 }
146
147 /* static */
GetSpinForTextCtrl(WXHWND hwndBuddy)148 wxSpinCtrl *wxSpinCtrl::GetSpinForTextCtrl(WXHWND hwndBuddy)
149 {
150 const SpinForTextCtrl::const_iterator
151 it = gs_spinForTextCtrl.find(hwndBuddy);
152 if ( it == gs_spinForTextCtrl.end() )
153 return NULL;
154
155 wxSpinCtrl * const spin = it->second;
156
157 // sanity check
158 wxASSERT_MSG( spin->m_hwndBuddy == hwndBuddy,
159 wxT("wxSpinCtrl has incorrect buddy HWND!") );
160
161 return spin;
162 }
163
164 // process a WM_COMMAND generated by the buddy text control
ProcessTextCommand(WXWORD cmd,WXWORD WXUNUSED (id))165 bool wxSpinCtrl::ProcessTextCommand(WXWORD cmd, WXWORD WXUNUSED(id))
166 {
167 if ( (cmd == EN_CHANGE) && (!m_blockEvent ))
168 {
169 wxCommandEvent event(wxEVT_TEXT, GetId());
170 event.SetEventObject(this);
171 wxString val = wxGetWindowText(m_hwndBuddy);
172 event.SetString(val);
173 event.SetInt(GetValue());
174 return HandleWindowEvent(event);
175 }
176
177 // not processed
178 return false;
179 }
180
OnChar(wxKeyEvent & event)181 void wxSpinCtrl::OnChar(wxKeyEvent& event)
182 {
183 switch ( event.GetKeyCode() )
184 {
185 case WXK_RETURN:
186 {
187 wxCommandEvent evt(wxEVT_TEXT_ENTER, m_windowId);
188 InitCommandEvent(evt);
189 wxString val = wxGetWindowText(m_hwndBuddy);
190 evt.SetString(val);
191 evt.SetInt(GetValue());
192 if ( HandleWindowEvent(evt) )
193 return;
194 break;
195 }
196
197 case WXK_TAB:
198 // always produce navigation event - even if we process TAB
199 // ourselves the fact that we got here means that the user code
200 // decided to skip processing of this TAB - probably to let it
201 // do its default job.
202 {
203 wxNavigationKeyEvent eventNav;
204 eventNav.SetDirection(!event.ShiftDown());
205 eventNav.SetWindowChange(event.ControlDown());
206 eventNav.SetEventObject(this);
207
208 if ( GetParent()->HandleWindowEvent(eventNav) )
209 return;
210 }
211 break;
212 }
213
214 // no, we didn't process it
215 event.Skip();
216 }
217
OnKillFocus(wxFocusEvent & event)218 void wxSpinCtrl::OnKillFocus(wxFocusEvent& event)
219 {
220 // ensure that a correct value is shown by the control
221 NormalizeValue();
222 event.Skip();
223 }
224
MSWUpdateFontOnDPIChange(const wxSize & newDPI)225 void wxSpinCtrl::MSWUpdateFontOnDPIChange(const wxSize& newDPI)
226 {
227 wxSpinButton::MSWUpdateFontOnDPIChange(newDPI);
228
229 if ( m_font.IsOk() )
230 wxSetWindowFont(GetBuddyHwnd(), m_font);
231 }
232
OnSetFocus(wxFocusEvent & event)233 void wxSpinCtrl::OnSetFocus(wxFocusEvent& event)
234 {
235 // when we get focus, give it to our buddy window as it needs it more than
236 // we do
237 ::SetFocus((HWND)m_hwndBuddy);
238
239 event.Skip();
240 }
241
NormalizeValue()242 void wxSpinCtrl::NormalizeValue()
243 {
244 const int value = GetValue();
245 const bool changed = value != m_oldValue;
246
247 // notice that we have to call SetValue() even if the value didn't change
248 // because otherwise we could be left with empty buddy control when value
249 // is 0, see comment in SetValue()
250 SetValue(value);
251
252 if ( changed )
253 {
254 m_oldValue = value;
255 SendSpinUpdate(value);
256 }
257 }
258
259 // ----------------------------------------------------------------------------
260 // construction
261 // ----------------------------------------------------------------------------
262
Init()263 void wxSpinCtrl::Init()
264 {
265 m_blockEvent = false;
266 m_hwndBuddy = NULL;
267 m_wndProcBuddy = NULL;
268 m_oldValue = INT_MIN;
269 }
270
Create(wxWindow * parent,wxWindowID id,const wxString & value,const wxPoint & pos,const wxSize & size,long style,int min,int max,int initial,const wxString & name)271 bool wxSpinCtrl::Create(wxWindow *parent,
272 wxWindowID id,
273 const wxString& value,
274 const wxPoint& pos,
275 const wxSize& size,
276 long style,
277 int min, int max, int initial,
278 const wxString& name)
279 {
280 // set style for the base class
281 style |= wxSP_VERTICAL;
282
283 if ( (style & wxBORDER_MASK) == wxBORDER_DEFAULT )
284 style |= wxBORDER_SUNKEN;
285
286 SetWindowStyle(style);
287
288 WXDWORD exStyle = 0;
289 WXDWORD msStyle = MSWGetStyle(GetWindowStyle(), & exStyle) ;
290
291 // Scroll text automatically if there is not enough space to show all of
292 // it, this is better than not allowing to enter more digits at all.
293 msStyle |= ES_AUTOHSCROLL;
294
295 // propagate text alignment style to text ctrl
296 if ( style & wxALIGN_RIGHT )
297 msStyle |= ES_RIGHT;
298 else if ( style & wxALIGN_CENTER )
299 msStyle |= ES_CENTER;
300
301 // we must create the text control before the spin button for the purpose
302 // of the dialog navigation: if there is a static text just before the spin
303 // control, activating it by Alt-letter should give focus to the text
304 // control, not the spin and the dialog navigation code will give focus to
305 // the next control (at Windows level), not the one after it
306
307 // create the text window
308
309 m_hwndBuddy = MSWCreateWindowAtAnyPosition
310 (
311 exStyle, // sunken border
312 wxT("EDIT"), // window class
313 NULL, // no window title
314 msStyle, // style (will be shown later)
315 pos.x, pos.y, // position
316 0, 0, // size (will be set later)
317 GetHwndOf(parent), // parent
318 -1 // control id
319 );
320
321 if ( !m_hwndBuddy )
322 {
323 wxLogLastError(wxT("CreateWindow(buddy text window)"));
324
325 return false;
326 }
327
328
329 // create the spin button
330 if ( !wxSpinButton::Create(parent, id, pos, wxSize(0, 0), style, name) )
331 {
332 return false;
333 }
334
335 wxSpinButtonBase::SetRange(min, max);
336
337 // subclass the text ctrl to be able to intercept some events
338 gs_spinForTextCtrl[GetBuddyHwnd()] = this;
339
340 m_wndProcBuddy = wxSetWindowProc(GetBuddyHwnd(), wxBuddyTextWndProc);
341
342 // associate the text window with the spin button
343 (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)m_hwndBuddy, 0);
344
345 // set up fonts and colours (This is nomally done in MSWCreateControl)
346 InheritAttributes();
347 if (!m_hasFont)
348 SetFont(GetDefaultAttributes().font);
349
350 // If the initial text value is actually a number, it overrides the
351 // "initial" argument specified later.
352 long initialFromText;
353 if ( value.ToLong(&initialFromText) )
354 initial = initialFromText;
355
356 // Set the range in the native control: notice that we must do it before
357 // calling SetValue() to use the correct validity checks for the initial
358 // value.
359 SetRange(min, max);
360 SetValue(initial);
361
362 // Also set the text part of the control if it was specified independently
363 // but don't generate an event for this, it would be unexpected.
364 m_blockEvent = true;
365 if ( !value.empty() )
366 SetValue(value);
367 m_blockEvent = false;
368
369 // Finally deal with the size: notice that this can only be done now both
370 // windows are created and the text one is set up as buddy because
371 // UDM_SETBUDDY changes its size using some unknown algorithm, so setting
372 // the sizes earlier is useless. Do it after setting the range and the base
373 // because GetBestSize() uses them.
374 if ( size.x > 0 && size.x < GetBestSize().x )
375 {
376 wxLogDebug(wxS("wxSpinCtrl \"%s\": initial width %d is too small, ")
377 wxS("at least %d pixels needed."),
378 name, size.x, GetBestSize().x);
379 }
380
381 SetInitialSize(size);
382
383 (void)::ShowWindow(GetBuddyHwnd(), SW_SHOW);
384
385 return true;
386 }
387
~wxSpinCtrl()388 wxSpinCtrl::~wxSpinCtrl()
389 {
390 // destroy the buddy window because this pointer which wxBuddyTextWndProc
391 // uses will not soon be valid any more
392 ::DestroyWindow( GetBuddyHwnd() );
393
394 gs_spinForTextCtrl.erase(GetBuddyHwnd());
395 }
396
Refresh(bool eraseBackground,const wxRect * rect)397 void wxSpinCtrl::Refresh(bool eraseBackground, const wxRect *rect)
398 {
399 wxControl::Refresh(eraseBackground, rect);
400
401 UINT flags = RDW_INVALIDATE;
402 if ( eraseBackground )
403 flags |= RDW_ERASE;
404
405 // Don't bother computing the intersection of the given rectangle with the
406 // buddy control, just always refresh it entirely, as it's much simpler.
407 ::RedrawWindow(GetBuddyHwnd(), NULL, NULL, flags);
408 }
409
410 // ----------------------------------------------------------------------------
411 // wxSpinCtrl-specific methods
412 // ----------------------------------------------------------------------------
413
GetBase() const414 int wxSpinCtrl::GetBase() const
415 {
416 return ::SendMessage(GetHwnd(), UDM_GETBASE, 0, 0);
417 }
418
SetBase(int base)419 bool wxSpinCtrl::SetBase(int base)
420 {
421 // For negative values in the range only base == 10 is allowed
422 if ( !wxSpinCtrlImpl::IsBaseCompatibleWithRange(m_min, m_max, base) )
423 return false;
424
425 if ( !::SendMessage(GetHwnd(), UDM_SETBASE, base, 0) )
426 return false;
427
428 // DoGetBestSize uses the base.
429 InvalidateBestSize();
430
431 // Whether we need to be able enter "x" or not influences whether we should
432 // use ES_NUMBER for the buddy control.
433 UpdateBuddyStyle();
434
435 // Update the displayed text after changing the base it uses.
436 SetValue(GetValue());
437
438 return true;
439 }
440
441 // ----------------------------------------------------------------------------
442 // wxTextCtrl-like methods
443 // ----------------------------------------------------------------------------
444
SetValue(const wxString & text)445 void wxSpinCtrl::SetValue(const wxString& text)
446 {
447 if ( !::SetWindowText(GetBuddyHwnd(), text.c_str()) )
448 {
449 wxLogLastError(wxT("SetWindowText(buddy)"));
450 }
451 }
452
SetValue(int val)453 void wxSpinCtrl::SetValue(int val)
454 {
455 m_blockEvent = true;
456
457 wxSpinButton::SetValue(val);
458
459 // Normally setting the value of the spin button is enough as it updates
460 // its buddy control automatically but in a couple of situations it doesn't
461 // do it, for whatever reason, do it explicitly then:
462 const wxString text = wxGetWindowText(m_hwndBuddy);
463
464 // First case is when the text control is empty and the value is 0: the
465 // spin button just leaves it empty in this case, while we want to show 0
466 // in it.
467 if ( text.empty() && !val )
468 {
469 ::SetWindowText(GetBuddyHwnd(), wxT("0"));
470 }
471
472 // Another one is when we're using hexadecimal base but the user input
473 // doesn't start with "0x" -- we prefer to show it to avoid ambiguity
474 // between decimal and hexadecimal.
475 if ( GetBase() == 16 &&
476 (text.length() < 3 || text[0] != '0' ||
477 (text[1] != 'x' && text[1] != 'X')) )
478 {
479 ::SetWindowText(GetBuddyHwnd(),
480 wxSpinCtrlImpl::FormatAsHex(val, m_max).t_str());
481 }
482
483 m_oldValue = GetValue();
484
485 m_blockEvent = false;
486 }
487
GetValue() const488 int wxSpinCtrl::GetValue() const
489 {
490 const wxString val = wxGetWindowText(m_hwndBuddy);
491
492 long n;
493 if ( !val.ToLong(&n, GetBase()) )
494 n = INT_MIN;
495
496 if ( n < m_min )
497 n = m_min;
498 if ( n > m_max )
499 n = m_max;
500
501 return n;
502 }
503
SetSelection(long from,long to)504 void wxSpinCtrl::SetSelection(long from, long to)
505 {
506 // if from and to are both -1, it means (in wxWidgets) that all text should
507 // be selected - translate into Windows convention
508 if ( (from == -1) && (to == -1) )
509 {
510 from = 0;
511 }
512
513 ::SendMessage(GetBuddyHwnd(), EM_SETSEL, (WPARAM)from, (LPARAM)to);
514 }
515
SetLayoutDirection(wxLayoutDirection dir)516 void wxSpinCtrl::SetLayoutDirection(wxLayoutDirection dir)
517 {
518 // Buddy text field is plain EDIT control so we need to set its layout
519 // direction in a specific way.
520 wxUpdateEditLayoutDirection(GetBuddyHwnd(), dir);
521
522 wxSpinButton::SetLayoutDirection(dir);
523
524 // Reposition the child windows according to the new layout.
525 SetSize(-1, -1, -1, -1, wxSIZE_AUTO | wxSIZE_FORCE);
526 }
527
MSWGetFocusHWND() const528 WXHWND wxSpinCtrl::MSWGetFocusHWND() const
529 {
530 // Return the buddy hwnd because it shuld be focused instead of the
531 // wxSpinCtrl itself.
532 return m_hwndBuddy;
533 }
534
535 // ----------------------------------------------------------------------------
536 // wxSpinButton methods
537 // ----------------------------------------------------------------------------
538
SetRange(int minVal,int maxVal)539 void wxSpinCtrl::SetRange(int minVal, int maxVal)
540 {
541 // Negative values in the range are allowed only if base == 10
542 if ( !wxSpinCtrlImpl::IsBaseCompatibleWithRange(minVal, maxVal, GetBase()) )
543 {
544 return;
545 }
546
547 // Manually adjust the old value to avoid an event being sent from
548 // NormalizeValue() called from inside the base class SetRange() as we're
549 // not supposed to generate any events from here.
550 if ( minVal <= maxVal )
551 {
552 if ( m_oldValue < minVal )
553 m_oldValue = minVal;
554 else if ( m_oldValue > maxVal )
555 m_oldValue = maxVal;
556 }
557 else // reversed range
558 {
559 if ( m_oldValue > minVal )
560 m_oldValue = minVal;
561 else if ( m_oldValue < maxVal )
562 m_oldValue = maxVal;
563 }
564
565 wxSpinButton::SetRange(minVal, maxVal);
566
567 InvalidateBestSize();
568
569 UpdateBuddyStyle();
570 }
571
UpdateBuddyStyle()572 void wxSpinCtrl::UpdateBuddyStyle()
573 {
574 // this control is used for numeric entry so restrict the input to numeric
575 // keys only -- but only if we don't need to be able to enter "-" in it as
576 // otherwise this would become impossible and also if we don't use
577 // hexadecimal as entering "x" of the "0x" prefix wouldn't be allowed
578 // neither then
579 wxMSWWinStyleUpdater(GetBuddyHwnd())
580 .TurnOnOrOff(m_min >= 0 && GetBase() == 10, ES_NUMBER);
581 }
582
583 // ----------------------------------------------------------------------------
584 // forward some methods to subcontrols
585 // ----------------------------------------------------------------------------
586
SetFont(const wxFont & font)587 bool wxSpinCtrl::SetFont(const wxFont& font)
588 {
589 if ( !wxWindowBase::SetFont(font) )
590 {
591 // nothing to do
592 return false;
593 }
594
595 if ( m_font.IsOk() )
596 wxSetWindowFont(GetBuddyHwnd(), m_font);
597
598 return true;
599 }
600
Show(bool show)601 bool wxSpinCtrl::Show(bool show)
602 {
603 if ( !wxControl::Show(show) )
604 {
605 return false;
606 }
607
608 ::ShowWindow(GetBuddyHwnd(), show ? SW_SHOW : SW_HIDE);
609
610 return true;
611 }
612
Reparent(wxWindowBase * newParent)613 bool wxSpinCtrl::Reparent(wxWindowBase *newParent)
614 {
615 // Reparenting both the updown control and its buddy does not seem to work:
616 // they continue to be connected somehow, but visually there is no feedback
617 // on the buddy edit control. To avoid this problem, we reparent the buddy
618 // window normally, but we recreate the updown control and reassign its
619 // buddy.
620
621 // Get the position before changing the parent as it would be offset after
622 // changing it.
623 const wxRect rect = GetRect();
624
625 if ( !wxWindowBase::Reparent(newParent) )
626 return false;
627
628 newParent->GetChildren().DeleteObject(this);
629
630 // destroy the old spin button after detaching it from this wxWindow object
631 // (notice that m_hWnd will be reset by UnsubclassWin() so save it first)
632 const HWND hwndOld = GetHwnd();
633 UnsubclassWin();
634 if ( !::DestroyWindow(hwndOld) )
635 {
636 wxLogLastError(wxT("DestroyWindow"));
637 }
638
639 // create and initialize the new one
640 if ( !wxSpinButton::Create(GetParent(), GetId(),
641 wxPoint(0, 0), wxSize(0, 0), // it will have a buddy
642 GetWindowStyle(), GetName()) )
643 return false;
644
645 // reapply our values to wxSpinButton
646 wxSpinButton::SetValue(GetValue());
647 SetRange(m_min, m_max);
648
649 // associate it with the buddy control again
650 ::SetParent(GetBuddyHwnd(), GetHwndOf(GetParent()));
651 (void)::SendMessage(GetHwnd(), UDM_SETBUDDY, (WPARAM)GetBuddyHwnd(), 0);
652
653 // also set the size again with wxSIZE_ALLOW_MINUS_ONE flag: this is
654 // necessary if our original position used -1 for either x or y
655 SetSize(rect, wxSIZE_ALLOW_MINUS_ONE);
656
657 return true;
658 }
659
Enable(bool enable)660 bool wxSpinCtrl::Enable(bool enable)
661 {
662 if ( !wxControl::Enable(enable) )
663 {
664 return false;
665 }
666
667 MSWEnableHWND(GetBuddyHwnd(), enable);
668
669 return true;
670 }
671
672 #if wxUSE_TOOLTIPS
673
DoSetToolTip(wxToolTip * tip)674 void wxSpinCtrl::DoSetToolTip(wxToolTip *tip)
675 {
676 wxSpinButton::DoSetToolTip(tip);
677
678 if ( tip )
679 tip->AddOtherWindow(m_hwndBuddy);
680 }
681
682 #endif // wxUSE_TOOLTIPS
683
684 // ----------------------------------------------------------------------------
685 // events processing and generation
686 // ----------------------------------------------------------------------------
687
SendSpinUpdate(int value)688 void wxSpinCtrl::SendSpinUpdate(int value)
689 {
690 wxSpinEvent event(wxEVT_SPINCTRL, GetId());
691 event.SetEventObject(this);
692 event.SetInt(value);
693 (void)HandleWindowEvent(event);
694 }
695
MSWOnScroll(int WXUNUSED (orientation),WXWORD wParam,WXWORD WXUNUSED (pos),WXHWND control)696 bool wxSpinCtrl::MSWOnScroll(int WXUNUSED(orientation), WXWORD wParam,
697 WXWORD WXUNUSED(pos), WXHWND control)
698 {
699 wxCHECK_MSG( control, false, wxT("scrolling what?") );
700
701 if ( wParam != SB_THUMBPOSITION )
702 {
703 // probable SB_ENDSCROLL - we don't react to it
704 return false;
705 }
706
707 // Notice that we can't use "pos" from WM_VSCROLL as it is 16 bit and we
708 // might be using 32 bit range.
709 int new_value = GetValue();
710 if (m_oldValue != new_value)
711 {
712 m_oldValue = new_value;
713 SendSpinUpdate(new_value);
714 }
715
716 return true;
717 }
718
MSWOnNotify(int WXUNUSED (idCtrl),WXLPARAM lParam,WXLPARAM * result)719 bool wxSpinCtrl::MSWOnNotify(int WXUNUSED(idCtrl), WXLPARAM lParam, WXLPARAM *result)
720 {
721 NM_UPDOWN *lpnmud = (NM_UPDOWN *)lParam;
722
723 if (lpnmud->hdr.hwndFrom != GetHwnd()) // make sure it is the right control
724 return false;
725
726 *result = 0; // never reject UP and DOWN events
727
728 return TRUE;
729 }
730
731
732 // ----------------------------------------------------------------------------
733 // size calculations
734 // ----------------------------------------------------------------------------
735
GetOverlap() const736 int wxSpinCtrl::GetOverlap() const
737 {
738 // Don't use FromDIP here. The gap between the control border and the
739 // button seems to be always 1px.
740 return 2;
741 }
742
DoGetBestSize() const743 wxSize wxSpinCtrl::DoGetBestSize() const
744 {
745 return wxSpinCtrlImpl::GetBestSize(this, GetMin(), GetMax(), GetBase());
746 }
747
DoGetSizeFromTextSize(int xlen,int ylen) const748 wxSize wxSpinCtrl::DoGetSizeFromTextSize(int xlen, int ylen) const
749 {
750 wxSize sizeBtn = wxSpinButton::DoGetBestSize();
751
752 // Create a temporary wxTextCtrl wrapping our existing HWND in order to be
753 // able to reuse its GetSizeFromTextSize() implementation.
754 wxTextCtrl text;
755 TempHWNDSetter set(&text, m_hwndBuddy);
756
757 // It's unnecessary to actually change the font used by the buddy control,
758 // but we do need to ensure that the helper wxTextCtrl wxFont matches what
759 // it is used as GetSizeFromTextSize() uses the current font.
760 text.wxWindowBase::SetFont(GetFont());
761
762 // Increase the width to accommodate the button, which should fit inside
763 // the text control while taking account of the overlap.
764 return text.GetSizeFromTextSize(xlen + sizeBtn.x - GetOverlap(), ylen);
765 }
766
DoMoveWindow(int x,int y,int width,int height)767 void wxSpinCtrl::DoMoveWindow(int x, int y, int width, int height)
768 {
769 // make sure the given width will be the total of both controls
770 const int overlap = GetOverlap();
771 int widthBtn = wxSpinButton::DoGetBestSize().x;
772 int widthText = width - widthBtn + overlap;
773
774 if ( widthText < 0 )
775 {
776 // This can happen during the initial window layout when it's total
777 // size is too small to accommodate all the controls and usually is not
778 // a problem because the window will be relaid out with enough space
779 // later. Of course, if it isn't and this is our final size, then we
780 // have a real problem but as we don't know if this is going to be the
781 // case or not, just hope for the best -- we used to give a debug
782 // warning here and this was annoying as it could result in dozens of
783 // perfectly harmless warnings.
784 widthText = 0;
785 }
786
787 if ( widthBtn > width )
788 widthBtn = width;
789
790 // Because both subcontrols are positioned relatively
791 // to the parent which can have different layout direction
792 // then our control, we need to mirror their positions manually.
793 if ( GetParent()->GetLayoutDirection() == GetLayoutDirection() )
794 {
795 // Logical positions: x(Text) < x(Button)
796 // 1) The buddy window
797 DoMoveSibling(m_hwndBuddy, x, y, widthText, height);
798
799 // 2) The button window
800 wxSpinButton::DoMoveWindow(x + widthText - overlap, y, widthBtn, height);
801 }
802 else
803 {
804 // Logical positions: x(Button) < x(Text)
805 // 1) The button window
806 wxSpinButton::DoMoveWindow(x, y, widthBtn, height);
807
808 // 2) The buddy window
809 DoMoveSibling(m_hwndBuddy, x + widthBtn - overlap, y, widthText, height);
810 }
811 }
812
813 // get total size of the control
DoGetSize(int * x,int * y) const814 void wxSpinCtrl::DoGetSize(int *x, int *y) const
815 {
816 RECT spinrect, textrect, ctrlrect;
817 GetWindowRect(GetHwnd(), &spinrect);
818 GetWindowRect(GetBuddyHwnd(), &textrect);
819 UnionRect(&ctrlrect,&textrect, &spinrect);
820
821 if ( x )
822 *x = ctrlrect.right - ctrlrect.left;
823 if ( y )
824 *y = ctrlrect.bottom - ctrlrect.top;
825 }
826
DoGetClientSize(int * x,int * y) const827 void wxSpinCtrl::DoGetClientSize(int *x, int *y) const
828 {
829 RECT spinrect = wxGetClientRect(GetHwnd());
830 RECT textrect = wxGetClientRect(GetBuddyHwnd());
831 RECT ctrlrect;
832 UnionRect(&ctrlrect,&textrect, &spinrect);
833
834 if ( x )
835 *x = ctrlrect.right - ctrlrect.left;
836 if ( y )
837 *y = ctrlrect.bottom - ctrlrect.top;
838 }
839
DoGetPosition(int * x,int * y) const840 void wxSpinCtrl::DoGetPosition(int *x, int *y) const
841 {
842 // Because both subcontrols are mirrored manually
843 // (for layout direction purposes, see note)
844 // and leftmost control can be either spin or buddy text
845 // we need to get positions for both controls
846 // and return this with lower horizonal value.
847 // Note:
848 // Logical positions in manual mirroring:
849 // our layout == parent layout => x(Text) < x(Button)
850 // our layout != parent layout => x(Button) < x(Text)
851
852 // hack: pretend that our HWND is the text control just for a moment
853 int xBuddy;
854 WXHWND hWnd = GetHWND();
855 wxConstCast(this, wxSpinCtrl)->m_hWnd = m_hwndBuddy;
856 wxSpinButton::DoGetPosition(&xBuddy, y);
857
858 int xText;
859 wxConstCast(this, wxSpinCtrl)->m_hWnd = hWnd;
860 wxSpinButton::DoGetPosition(&xText, y);
861
862 *x = wxMin(xBuddy, xText);
863 }
864
DoScreenToClient(int * x,int * y) const865 void wxSpinCtrl::DoScreenToClient(int *x, int *y) const
866 {
867 wxWindow::MSWDoScreenToClient(GetBuddyHwnd(), x, y);
868 }
869
DoClientToScreen(int * x,int * y) const870 void wxSpinCtrl::DoClientToScreen(int *x, int *y) const
871 {
872 wxWindow::MSWDoClientToScreen(GetBuddyHwnd(), x, y);
873 }
874
875 #endif // wxUSE_SPINCTRL
876