1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/slider.cpp
3 // Purpose: wxSlider, using the Win95 (and later) trackbar control
4 // Author: Julian Smart
8 // Copyright: (c) Julian Smart 1998
10 // Licence: wxWindows licence
11 /////////////////////////////////////////////////////////////////////////////
13 // ============================================================================
15 // ============================================================================
17 // ----------------------------------------------------------------------------
19 // ----------------------------------------------------------------------------
21 // For compilers that support precompilation, includes "wx.h".
22 #include "wx/wxprec.h"
30 #include "wx/slider.h"
33 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
37 #include "wx/msw/subwin.h"
39 // ----------------------------------------------------------------------------
41 // ----------------------------------------------------------------------------
46 // indices of labels in wxSlider::m_labels
55 // the gaps between the slider and the labels, in pixels
58 // the width of the borders including white space
59 const int BORDERPAD
= 8;
60 // these 2 values are arbitrary:
64 } // anonymous namespace
66 // ============================================================================
67 // wxSlider implementation
68 // ============================================================================
70 // ----------------------------------------------------------------------------
72 // ----------------------------------------------------------------------------
87 bool wxSlider::Create(wxWindow
*parent
,
95 const wxValidator
& validator
,
98 wxCHECK_MSG( minValue
< maxValue
, false,
99 wxT("Slider minimum must be strictly less than the maximum.") );
101 // our styles are redundant: wxSL_LEFT/RIGHT imply wxSL_VERTICAL and
102 // wxSL_TOP/BOTTOM imply wxSL_HORIZONTAL, but for backwards compatibility
103 // reasons we can't really change it, instead try to infer the orientation
104 // from the flags given to us here
105 switch ( style
& (wxSL_LEFT
| wxSL_RIGHT
| wxSL_TOP
| wxSL_BOTTOM
) )
109 style
|= wxSL_VERTICAL
;
114 style
|= wxSL_HORIZONTAL
;
118 // no specific direction, do we have at least the orientation?
119 if ( !(style
& (wxSL_HORIZONTAL
| wxSL_VERTICAL
)) )
121 // no, choose default
122 style
|= wxSL_BOTTOM
| wxSL_HORIZONTAL
;
126 wxASSERT_MSG( !(style
& wxSL_VERTICAL
) || !(style
& wxSL_HORIZONTAL
),
127 wxT("incompatible slider direction and orientation") );
130 // initialize everything
131 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
134 // ensure that we have correct values for GetLabelsSize()
135 m_rangeMin
= minValue
;
136 m_rangeMax
= maxValue
;
138 // create the labels first, so that our DoGetBestSize() could take them
141 // note that we could simply create 3 wxStaticTexts here but it could
142 // result in some observable side effects at wx level (e.g. the parent of
143 // wxSlider would have 3 more children than expected) and so we prefer not
144 // to do it like this
145 if ( m_windowStyle
& wxSL_LABELS
)
147 m_labels
= new wxSubwindows(SliderLabel_Last
);
149 HWND hwndParent
= GetHwndOf(parent
);
150 for ( size_t n
= 0; n
< SliderLabel_Last
; n
++ )
152 wxWindowIDRef lblid
= NewControlId();
154 HWND wnd
= ::CreateWindow
158 WS_CHILD
| WS_VISIBLE
| SS_CENTER
,
161 (HMENU
)wxUIntToPtr(lblid
.GetValue()),
166 m_labels
->Set(n
, wnd
, lblid
);
168 m_labels
->SetFont(GetFont());
171 // now create the main control too
172 if ( !MSWCreateControl(TRACKBAR_CLASS
, wxEmptyString
, pos
, size
) )
175 // and initialize everything
176 SetRange(minValue
, maxValue
);
178 SetPageSize((maxValue
- minValue
)/10);
180 // we need to position the labels correctly if we have them and if
181 // SetSize() hadn't been called before (when best size was determined by
182 // MSWCreateControl()) as in this case they haven't been put in place yet
183 if ( m_labels
&& size
.x
!= wxDefaultCoord
&& size
.y
!= wxDefaultCoord
)
191 WXDWORD
wxSlider::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
193 WXDWORD msStyle
= wxControl::MSWGetStyle(style
, exstyle
);
195 // TBS_HORZ, TBS_RIGHT and TBS_BOTTOM are 0 but do include them for clarity
196 msStyle
|= style
& wxSL_VERTICAL
? TBS_VERT
: TBS_HORZ
;
198 if ( style
& wxSL_BOTH
)
200 // this fully specifies the style combined with TBS_VERT/HORZ above
203 else // choose one direction
205 if ( style
& wxSL_LEFT
)
207 else if ( style
& wxSL_RIGHT
)
208 msStyle
|= TBS_RIGHT
;
209 else if ( style
& wxSL_TOP
)
211 else if ( style
& wxSL_BOTTOM
)
212 msStyle
|= TBS_BOTTOM
;
215 if ( style
& wxSL_AUTOTICKS
)
216 msStyle
|= TBS_AUTOTICKS
;
218 msStyle
|= TBS_NOTICKS
;
220 if ( style
& wxSL_SELRANGE
)
221 msStyle
|= TBS_ENABLESELRANGE
;
226 wxSlider::~wxSlider()
231 // ----------------------------------------------------------------------------
233 // ----------------------------------------------------------------------------
235 bool wxSlider::MSWOnScroll(int WXUNUSED(orientation
),
237 WXWORD
WXUNUSED(pos
),
240 wxEventType scrollEvent
;
244 scrollEvent
= wxEVT_SCROLL_TOP
;
248 scrollEvent
= wxEVT_SCROLL_BOTTOM
;
252 scrollEvent
= wxEVT_SCROLL_LINEUP
;
256 scrollEvent
= wxEVT_SCROLL_LINEDOWN
;
260 scrollEvent
= wxEVT_SCROLL_PAGEUP
;
264 scrollEvent
= wxEVT_SCROLL_PAGEDOWN
;
268 scrollEvent
= wxEVT_SCROLL_THUMBTRACK
;
272 case SB_THUMBPOSITION
:
275 scrollEvent
= wxEVT_SCROLL_THUMBRELEASE
;
276 m_isDragging
= false;
280 // this seems to only happen when the mouse wheel is used: in
281 // this case, as it might be unexpected to get THUMBRELEASE
282 // without preceding THUMBTRACKs, we don't generate it at all
283 // but generate CHANGED event because the control itself does
284 // not send us SB_ENDSCROLL for whatever reason when mouse
286 scrollEvent
= wxEVT_SCROLL_CHANGED
;
291 scrollEvent
= wxEVT_SCROLL_CHANGED
;
295 // unknown scroll event?
299 int newPos
= ValueInvertOrNot((int) ::SendMessage((HWND
) control
, TBM_GETPOS
, 0, 0));
300 if ( (newPos
< GetMin()) || (newPos
> GetMax()) )
302 // out of range - but we did process it
308 wxScrollEvent
event(scrollEvent
, m_windowId
);
309 event
.SetPosition(newPos
);
310 event
.SetEventObject( this );
311 HandleWindowEvent(event
);
313 wxCommandEvent
cevent( wxEVT_COMMAND_SLIDER_UPDATED
, GetId() );
314 cevent
.SetInt( newPos
);
315 cevent
.SetEventObject( this );
317 return HandleWindowEvent( cevent
);
320 void wxSlider::Command (wxCommandEvent
& event
)
322 SetValue (event
.GetInt());
323 ProcessCommand (event
);
326 // ----------------------------------------------------------------------------
328 // ----------------------------------------------------------------------------
330 wxRect
wxSlider::GetBoundingBox() const
332 // take care not to call our own functions which would call us recursively
334 wxSliderBase::DoGetPosition(&x
, &y
);
335 wxSliderBase::DoGetSize(&w
, &h
);
337 wxRect
rect(x
, y
, w
, h
);
340 wxRect lrect
= m_labels
->GetBoundingBox();
341 GetParent()->ScreenToClient(&lrect
.x
, &lrect
.y
);
348 void wxSlider::DoGetSize(int *width
, int *height
) const
350 wxRect rect
= GetBoundingBox();
355 *height
= rect
.height
;
358 void wxSlider::DoGetPosition(int *x
, int *y
) const
360 wxRect rect
= GetBoundingBox();
368 int wxSlider::GetLabelsSize(int *widthMin
, int *widthMax
) const
370 if ( widthMin
&& widthMax
)
372 *widthMin
= GetTextExtent(Format(m_rangeMin
)).x
;
373 *widthMax
= GetTextExtent(Format(m_rangeMax
)).x
;
376 return HasFlag(wxSL_LABELS
) ? GetCharHeight() : 0;
379 void wxSlider::DoMoveWindow(int x
, int y
, int width
, int height
)
381 // all complications below are because we need to position the labels,
382 // without them everything is easy
385 wxSliderBase::DoMoveWindow(x
, y
, width
, height
);
391 const int labelHeight
= GetLabelsSize(&minLabelWidth
, &maxLabelWidth
);
392 const int longestLabelWidth
= wxMax(minLabelWidth
, maxLabelWidth
);
393 if ( !HasFlag(wxSL_MIN_MAX_LABELS
) )
401 if ( HasFlag(wxSL_TICKS
))
403 if ( HasFlag(wxSL_BOTH
))
406 // be careful to position the slider itself after moving the labels as
407 // otherwise our GetBoundingBox(), which is called from WM_SIZE handler,
408 // would return a wrong result and wrong size would be cached internally
409 if ( HasFlag(wxSL_VERTICAL
) )
415 int xLabel
= (wxMax((THUMB
+ (BORDERPAD
* 2)), longestLabelWidth
) / 2) -
416 (longestLabelWidth
/ 2) + x
;
417 if ( HasFlag(wxSL_LEFT
) )
419 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
422 holdTopWidth
= minLabelWidth
;
423 holdBottomX
= xLabel
- ((maxLabelWidth
- minLabelWidth
) / 2);
424 holdBottomWidth
= maxLabelWidth
;
425 if ( HasFlag(wxSL_INVERSE
) )
427 wxSwap(holdTopWidth
, holdBottomWidth
);
428 wxSwap(holdTopX
, holdBottomX
);
430 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Min
],
433 holdTopWidth
, labelHeight
);
434 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Max
],
436 y
+ height
- labelHeight
,
437 holdBottomWidth
, labelHeight
);
439 if ( HasFlag(wxSL_VALUE_LABEL
) )
440 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Value
],
441 x
+ THUMB
+ tickOffset
+ HGAP
,
442 y
+ (height
- labelHeight
)/2,
443 longestLabelWidth
, labelHeight
);
447 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
449 holdTopX
= xLabel
+ longestLabelWidth
+ ((maxLabelWidth
- minLabelWidth
) / 2);
450 holdTopWidth
= minLabelWidth
;
451 holdBottomX
= xLabel
+ longestLabelWidth
;
452 holdBottomWidth
= maxLabelWidth
;
453 if ( HasFlag(wxSL_INVERSE
) )
455 wxSwap(holdTopWidth
, holdBottomWidth
);
456 wxSwap(holdTopX
, holdBottomX
);
458 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Min
],
461 holdTopWidth
, labelHeight
);
462 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Max
],
464 y
+ height
- labelHeight
,
465 holdBottomWidth
, labelHeight
);
467 if ( HasFlag(wxSL_VALUE_LABEL
) )
468 labelOffset
= longestLabelWidth
+ HGAP
;
469 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Value
],
471 y
+ (height
- labelHeight
)/2,
472 longestLabelWidth
, labelHeight
);
475 // position the slider itself along the left/right edge
476 wxSliderBase::DoMoveWindow(
479 THUMB
+ tickOffset
+ HGAP
,
480 height
- (labelHeight
* 2));
489 (y
+ ((THUMB
+ tickOffset
) / 2)) - (labelHeight
/ 2);
492 ((width
- (minLabelWidth
+ maxLabelWidth
)) / 2) -
493 (longestLabelWidth
/ 2);
497 if ( HasFlag(wxSL_BOTTOM
) )
499 if ( HasFlag(wxSL_VALUE_LABEL
) )
501 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Value
],
504 longestLabelWidth
, labelHeight
);
506 ySlider
+= labelHeight
;
507 yLabelMinMax
+= labelHeight
;
510 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
513 holdLeftWidth
= minLabelWidth
;
514 holdRightX
= x
+ width
- maxLabelWidth
;
515 holdRightWidth
= maxLabelWidth
;
516 if ( HasFlag(wxSL_INVERSE
) )
518 wxSwap(holdLeftWidth
, holdRightWidth
);
519 wxSwap(holdLeftX
, holdRightX
);
521 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Min
],
524 holdLeftWidth
, labelHeight
);
525 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Max
],
528 holdRightWidth
, labelHeight
);
533 if ( HasFlag(wxSL_VALUE_LABEL
) )
535 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Value
],
537 y
+ THUMB
+ tickOffset
,
538 longestLabelWidth
, labelHeight
);
541 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
544 holdLeftWidth
= minLabelWidth
;
545 holdRightX
= x
+ width
- maxLabelWidth
;
546 holdRightWidth
= maxLabelWidth
;
547 if ( HasFlag(wxSL_INVERSE
) )
549 wxSwap(holdLeftWidth
, holdRightWidth
);
550 wxSwap(holdLeftX
, holdRightX
);
552 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Min
],
555 holdLeftWidth
, labelHeight
);
556 DoMoveSibling((HWND
)(*m_labels
)[SliderLabel_Max
],
559 holdRightWidth
, labelHeight
);
563 // position the slider itself along the top/bottom edge
564 if ( HasFlag(wxSL_MIN_MAX_LABELS
) || HasFlag(wxSL_VALUE_LABEL
) )
565 labelOffset
= labelHeight
;
566 wxSliderBase::DoMoveWindow(
567 x
+ minLabelWidth
+ VGAP
,
569 width
- (minLabelWidth
+ maxLabelWidth
+ (VGAP
*2)),
574 wxSize
wxSlider::DoGetBestSize() const
576 // this value is arbitrary:
577 static const int length
= 100;
581 if ( HasFlag(wxSL_VERTICAL
) )
591 int hLabel
= GetLabelsSize(&widthMin
, &widthMax
);
593 // account for the labels
594 if ( HasFlag(wxSL_MIN_MAX_LABELS
) )
595 size
.x
+= HGAP
+ wxMax(widthMin
, widthMax
);
597 // labels are indented relative to the slider itself
609 int labelSize
= GetLabelsSize();
611 // Min/max labels are compensated by the ticks so we don't need
612 // extra space for them if we're also showing ticks.
613 if ( HasFlag(wxSL_MIN_MAX_LABELS
) && !HasFlag(wxSL_TICKS
) )
616 // The value label is always on top of the control and so does need
617 // extra space in any case.
618 if ( HasFlag(wxSL_VALUE_LABEL
) )
623 // need extra space to show ticks
624 if ( HasFlag(wxSL_TICKS
) )
627 // and maybe twice as much if we show them on both sides
628 if ( HasFlag(wxSL_BOTH
) )
634 // ----------------------------------------------------------------------------
635 // slider-specific methods
636 // ----------------------------------------------------------------------------
638 int wxSlider::GetValue() const
640 return ValueInvertOrNot(::SendMessage(GetHwnd(), TBM_GETPOS
, 0, 0));
643 void wxSlider::SetValue(int value
)
645 ::SendMessage(GetHwnd(), TBM_SETPOS
, (WPARAM
)TRUE
, (LPARAM
)ValueInvertOrNot(value
));
649 ::SetWindowText((*m_labels
)[SliderLabel_Value
], Format(value
).wx_str());
653 void wxSlider::SetRange(int minValue
, int maxValue
)
655 // Remember the old logical value if we need to update the physical control
656 // value after changing its range in wxSL_INVERSE case (and avoid an
657 // unnecessary call to GetValue() otherwise as it's just not needed).
658 const int valueOld
= HasFlag(wxSL_INVERSE
) ? GetValue() : 0;
660 m_rangeMin
= minValue
;
661 m_rangeMax
= maxValue
;
663 ::SendMessage(GetHwnd(), TBM_SETRANGEMIN
, TRUE
, m_rangeMin
);
664 ::SendMessage(GetHwnd(), TBM_SETRANGEMAX
, TRUE
, m_rangeMax
);
668 ::SetWindowText((*m_labels
)[SliderLabel_Min
],
669 Format(ValueInvertOrNot(m_rangeMin
)).wx_str());
670 ::SetWindowText((*m_labels
)[SliderLabel_Max
],
671 Format(ValueInvertOrNot(m_rangeMax
)).wx_str());
674 // When emulating wxSL_INVERSE style in wxWidgets, we need to update the
675 // value after changing the range to ensure that the value seen by the user
676 // code, i.e. the one returned by GetValue(), does not change.
677 if ( HasFlag(wxSL_INVERSE
) )
679 ::SendMessage(GetHwnd(), TBM_SETPOS
, TRUE
, ValueInvertOrNot(valueOld
));
683 void wxSlider::DoSetTickFreq(int n
)
686 ::SendMessage( GetHwnd(), TBM_SETTICFREQ
, (WPARAM
) n
, (LPARAM
) 0 );
689 void wxSlider::SetPageSize(int pageSize
)
691 ::SendMessage( GetHwnd(), TBM_SETPAGESIZE
, (WPARAM
) 0, (LPARAM
) pageSize
);
692 m_pageSize
= pageSize
;
695 int wxSlider::GetPageSize() const
700 void wxSlider::ClearSel()
702 ::SendMessage(GetHwnd(), TBM_CLEARSEL
, (WPARAM
) TRUE
, (LPARAM
) 0);
705 void wxSlider::ClearTicks()
707 ::SendMessage(GetHwnd(), TBM_CLEARTICS
, (WPARAM
) TRUE
, (LPARAM
) 0);
710 void wxSlider::SetLineSize(int lineSize
)
712 m_lineSize
= lineSize
;
713 ::SendMessage(GetHwnd(), TBM_SETLINESIZE
, (WPARAM
) 0, (LPARAM
) lineSize
);
716 int wxSlider::GetLineSize() const
718 return (int)::SendMessage(GetHwnd(), TBM_GETLINESIZE
, 0, 0);
721 int wxSlider::GetSelEnd() const
723 return (int)::SendMessage(GetHwnd(), TBM_GETSELEND
, 0, 0);
726 int wxSlider::GetSelStart() const
728 return (int)::SendMessage(GetHwnd(), TBM_GETSELSTART
, 0, 0);
731 void wxSlider::SetSelection(int minPos
, int maxPos
)
733 ::SendMessage(GetHwnd(), TBM_SETSEL
,
734 (WPARAM
) TRUE
/* redraw */,
735 (LPARAM
) MAKELONG( minPos
, maxPos
) );
738 void wxSlider::SetThumbLength(int len
)
740 ::SendMessage(GetHwnd(), TBM_SETTHUMBLENGTH
, (WPARAM
) len
, (LPARAM
) 0);
743 int wxSlider::GetThumbLength() const
745 return (int)::SendMessage( GetHwnd(), TBM_GETTHUMBLENGTH
, 0, 0);
748 void wxSlider::SetTick(int tickPos
)
750 ::SendMessage( GetHwnd(), TBM_SETTIC
, (WPARAM
) 0, (LPARAM
) tickPos
);
753 // ----------------------------------------------------------------------------
754 // composite control methods
755 // ----------------------------------------------------------------------------
757 WXHWND
wxSlider::GetStaticMin() const
759 return m_labels
? (WXHWND
)(*m_labels
)[SliderLabel_Min
] : NULL
;
762 WXHWND
wxSlider::GetStaticMax() const
764 return m_labels
? (WXHWND
)(*m_labels
)[SliderLabel_Max
] : NULL
;
767 WXHWND
wxSlider::GetEditValue() const
769 return m_labels
? (WXHWND
)(*m_labels
)[SliderLabel_Value
] : NULL
;
772 WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxSlider
, wxSliderBase
, m_labels
)
774 #endif // wxUSE_SLIDER