1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: univ/slider.cpp
3 // Purpose: implementation of the universal version of wxSlider
4 // Author: Vadim Zeitlin
8 // Copyright: (c) 2001 SciTech Software, Inc. (www.scitechsoft.com)
9 // Licence: wxWindows licence
10 ///////////////////////////////////////////////////////////////////////////////
13 There is some discrepancy in wxSL_LABELS style handling between wxMSW and
14 wxGTK: the latter handles it natively and shows only the current value of
15 the slider on the side corresponding to wxSL_TOP/BOTTOM/LEFT/RIGHT style
16 given (which can be combined with wxSL_HORIZONTAL/VERTICAL) while wxMSW
17 emulates this somehow and shows the min and max values near the ends of the
18 slider and the current value in a separate static box nearby.
20 We currently follow wxGTK except that wxSL_HORIZONTAL slider can only have
21 the label displayed on top or bottom of it and wxSL_VERTICAL - to the left
24 What we really need is probably a more fine grain control on labels, i.e. we
25 should be able to select if we show nothing at all, the current value only
26 or the value and the limits - the current approach is just that of the
27 lowest common denominator.
32 1. support for all orientations
33 2. draw the slider thumb highlighted when it is dragged
34 ?3. manual ticks support?
37 // ============================================================================
39 // ============================================================================
41 // ----------------------------------------------------------------------------
43 // ----------------------------------------------------------------------------
45 #include "wx/wxprec.h"
55 #include "wx/slider.h"
59 #include "wx/univ/renderer.h"
60 #include "wx/univ/inphand.h"
61 #include "wx/univ/theme.h"
63 // ----------------------------------------------------------------------------
65 // ----------------------------------------------------------------------------
67 // the margin between the slider and the label (FIXME: hardcoded)
68 static const wxCoord SLIDER_LABEL_MARGIN
= 2;
70 // ============================================================================
71 // implementation of wxSlider
72 // ============================================================================
74 IMPLEMENT_DYNAMIC_CLASS(wxSlider
, wxControl
)
76 BEGIN_EVENT_TABLE(wxSlider
, wxControl
)
77 EVT_SIZE(wxSlider::OnSize
)
80 // ----------------------------------------------------------------------------
82 // ----------------------------------------------------------------------------
85 // warning C4355: 'this' : used in base member initializer list
86 #pragma warning(disable:4355)
95 wxSlider::wxSlider(wxWindow
*parent
,
97 int value
, int minValue
, int maxValue
,
101 const wxValidator
& validator
,
102 const wxString
& name
)
107 (void)Create(parent
, id
, value
, minValue
, maxValue
,
108 pos
, size
, style
, validator
, name
);
112 // warning C4355: 'this' : used in base member initializer list
113 #pragma warning(default:4355)
116 void wxSlider::Init()
131 bool wxSlider::Create(wxWindow
*parent
,
133 int value
, int minValue
, int maxValue
,
137 const wxValidator
& validator
,
138 const wxString
& name
)
140 if ( !wxSliderBase::Create(parent
, id
, pos
, size
, style
,
144 SetRange(minValue
, maxValue
);
147 // call this after setting the range as the best size depends (at least if
148 // we have wxSL_LABELS style) on the range
151 CreateInputHandler(wxINP_HANDLER_SLIDER
);
156 // ----------------------------------------------------------------------------
157 // wxSlider range and value
158 // ----------------------------------------------------------------------------
160 int wxSlider::GetValue() const
165 int wxSlider::NormalizeValue(int value
) const
169 else if ( value
> m_max
)
175 bool wxSlider::ChangeValueBy(int inc
)
177 return ChangeValueTo(NormalizeValue(m_value
+ inc
));
180 bool wxSlider::ChangeValueTo(int value
)
182 // check if the value is going to change at all
183 if (value
== m_value
) return false;
185 // this method is protected and we should only call it with normalized
187 wxCHECK_MSG( IsInRange(value
), false, _T("invalid slider value") );
193 // generate the event
194 wxCommandEvent
event(wxEVT_COMMAND_SLIDER_UPDATED
, GetId());
195 event
.SetInt(m_value
);
196 event
.SetEventObject(this);
198 (void)GetEventHandler()->ProcessEvent(event
);
203 void wxSlider::SetValue(int value
)
205 value
= NormalizeValue(value
);
207 if ( m_value
!= value
)
215 void wxSlider::SetRange(int minValue
, int maxValue
)
217 if ( minValue
> maxValue
)
219 // swap them, we always want min to be less than max
225 if ( m_min
!= minValue
|| m_max
!= maxValue
)
230 // reset the value to make sure it is in the new range
233 // the size of the label rect might have changed
241 //else: nothing changed
244 int wxSlider::GetMin() const
249 int wxSlider::GetMax() const
254 // ----------------------------------------------------------------------------
255 // wxSlider line/page/thumb size
256 // ----------------------------------------------------------------------------
258 void wxSlider::SetLineSize(int lineSize
)
260 wxCHECK_RET( lineSize
>= 0, _T("invalid slider line size") );
262 m_lineSize
= lineSize
;
265 void wxSlider::SetPageSize(int pageSize
)
267 wxCHECK_RET( pageSize
>= 0, _T("invalid slider page size") );
269 m_pageSize
= pageSize
;
272 int wxSlider::GetLineSize() const
276 // the default line increment is 1
277 wxConstCast(this, wxSlider
)->m_lineSize
= 1;
283 int wxSlider::GetPageSize() const
287 // the default page increment is m_tickFreq
288 wxConstCast(this, wxSlider
)->m_pageSize
= m_tickFreq
;
294 void wxSlider::SetThumbLength(int lenPixels
)
296 wxCHECK_RET( lenPixels
>= 0, _T("invalid slider thumb size") );
298 // use m_thumbSize here directly and not GetThumbLength() to avoid setting
299 // it to the default value as we don't need it
300 if ( lenPixels
!= m_thumbSize
)
302 m_thumbSize
= lenPixels
;
308 int wxSlider::GetThumbLength() const
310 wxSize sz
= GetDefaultThumbSize();
311 int len
= (IsVert() ? sz
.x
: sz
.y
);
312 if (m_thumbSize
> len
)
323 // ----------------------------------------------------------------------------
325 // ----------------------------------------------------------------------------
327 void wxSlider::SetTickFreq(int n
, int WXUNUSED(dummy
))
329 wxCHECK_RET (n
> 0, _T("invalid slider tick frequency"));
331 if ( n
!= m_tickFreq
)
339 // ----------------------------------------------------------------------------
341 // ----------------------------------------------------------------------------
343 wxSize
wxSlider::CalcLabelSize() const
347 // there is no sense in trying to calc the labels size if we haven't got
348 // any, the caller must check for it
349 wxCHECK_MSG( HasLabels(), size
, _T("shouldn't be called") );
351 wxCoord w1
, h1
, w2
, h2
;
352 GetTextExtent(FormatValue(m_min
), &w1
, &h1
);
353 GetTextExtent(FormatValue(m_max
), &w2
, &h2
);
355 size
.x
= wxMax(w1
, w2
);
356 size
.y
= wxMax(h1
, h2
);
361 wxSize
wxSlider::DoGetBestClientSize() const
363 // this dimension is completely arbitrary
364 static const wxCoord SLIDER_WIDTH
= 40;
366 long style
= GetWindowStyle();
368 // first calculate the size of the slider itself: i.e. the shaft and the
370 wxCoord height
= GetRenderer()->GetSliderDim();
376 size
.y
= SLIDER_WIDTH
;
380 size
.x
= SLIDER_WIDTH
;
384 // add space for ticks
387 wxCoord lenTick
= GetRenderer()->GetSliderTickLen();
388 if (style
& wxSL_BOTH
)
390 lenTick
= 2 * lenTick
;
399 // if we have the label, reserve enough space for it
402 wxSize sizeLabels
= CalcLabelSize();
404 if (style
& (wxSL_LEFT
|wxSL_RIGHT
))
406 size
.x
+= sizeLabels
.x
+ SLIDER_LABEL_MARGIN
;
408 else if (style
& (wxSL_TOP
|wxSL_BOTTOM
))
410 size
.y
+= sizeLabels
.y
+ SLIDER_LABEL_MARGIN
;
417 void wxSlider::OnSize(wxSizeEvent
& event
)
424 const wxRect
& wxSlider::GetSliderRect() const
426 if ( m_rectSlider
.width
< 0 )
428 wxConstCast(this, wxSlider
)->CalcGeometry();
434 void wxSlider::CalcGeometry()
437 recalc the label and slider positions, this looks like this for
438 wxSL_HORIZONTAL | wxSL_TOP slider:
441 -------------------------
442 | T | <-- this is the slider rect
443 | HHHHHHHHHHHHHHHTHHHHH |
446 -------------------------
448 LLL is m_rectLabel as calculated here and lll is the real rect used for
449 label drawing in OnDraw() (TTT indicated the thumb position and *s are
452 in the wxSL_VERTICAL | wxSL_RIGHT case the picture is like this:
467 long style
= GetWindowStyle();
469 // initialize to the full client rect
470 wxRect rectTotal
= GetClientRect();
471 m_rectSlider
= rectTotal
;
472 wxSize sizeThumb
= GetThumbSize();
474 // Labels reduce the size of the slider rect
477 wxSize sizeLabels
= CalcLabelSize();
479 m_rectLabel
= wxRect(rectTotal
.GetPosition(), sizeLabels
);
481 if (style
& wxSL_TOP
)
483 // shrink and offset the slider to the bottom
484 m_rectSlider
.y
+= sizeLabels
.y
+ SLIDER_LABEL_MARGIN
;
485 m_rectSlider
.height
-= sizeLabels
.y
+ SLIDER_LABEL_MARGIN
;
487 else if (style
& wxSL_BOTTOM
)
489 // shrink the slider and move the label to the bottom
490 m_rectSlider
.height
-= sizeLabels
.y
+ SLIDER_LABEL_MARGIN
;
491 m_rectLabel
.y
+= m_rectSlider
.height
+ SLIDER_LABEL_MARGIN
;
493 else if (style
& wxSL_LEFT
)
495 // shrink and offset the slider to the right
496 m_rectSlider
.x
+= sizeLabels
.x
+ SLIDER_LABEL_MARGIN
;
497 m_rectSlider
.width
-= sizeLabels
.x
+ SLIDER_LABEL_MARGIN
;
499 else if (style
& wxSL_RIGHT
)
501 // shrink the slider and move the label to the right
502 m_rectSlider
.width
-= sizeLabels
.x
+ SLIDER_LABEL_MARGIN
;
503 m_rectLabel
.x
+= m_rectSlider
.width
+ SLIDER_LABEL_MARGIN
;
507 // calculate ticks too
510 wxCoord lenTick
= GetRenderer()->GetSliderTickLen();
513 m_rectTicks
= GetShaftRect();
517 if (style
& (wxSL_LEFT
|wxSL_BOTH
))
519 m_rectTicks
.x
= m_rectSlider
.x
;
523 m_rectTicks
.x
= m_rectSlider
.x
+ m_rectSlider
.width
- lenTick
;
525 m_rectTicks
.width
= lenTick
;
529 if (style
& (wxSL_TOP
|wxSL_BOTH
))
531 m_rectTicks
.y
= m_rectSlider
.y
;
535 m_rectTicks
.y
= m_rectSlider
.y
+ m_rectSlider
.height
- lenTick
;
537 m_rectTicks
.height
= lenTick
;
541 // slider is never smaller than thumb size unless rectTotal
544 wxCoord width
= wxMin ( rectTotal
.width
, sizeThumb
.x
);
545 m_rectSlider
.width
= wxMax ( m_rectSlider
.width
, width
);
549 wxCoord height
= wxMin ( rectTotal
.height
, sizeThumb
.y
);
550 m_rectSlider
.height
= wxMax ( m_rectSlider
.height
, height
);
554 wxSize
wxSlider::GetDefaultThumbSize() const
556 // Default size has no styles (arrows)
557 return GetRenderer()->GetSliderThumbSize(GetSliderRect(), 0, GetOrientation());
560 wxSize
wxSlider::GetThumbSize() const
562 return GetRenderer()->GetSliderThumbSize(GetSliderRect(), m_thumbSize
, GetOrientation());
565 // ----------------------------------------------------------------------------
566 // wxSlider thumb geometry
567 // ----------------------------------------------------------------------------
569 wxRect
wxSlider::GetShaftRect() const
571 return GetRenderer()->GetSliderShaftRect(m_rectSlider
, m_thumbSize
, GetOrientation(), GetWindowStyle());
574 void wxSlider::CalcThumbRect(const wxRect
*rectShaftIn
,
575 wxRect
*rectThumbOut
,
576 wxRect
*rectLabelOut
,
579 if ( value
== INVALID_THUMB_VALUE
)
581 // use the current if not specified
585 bool isVertical
= IsVert();
590 rectShaft
= *rectShaftIn
;
592 else // no shaft rect provided, calc it
594 rectShaft
= GetShaftRect();
601 wxRect
rectThumb(rectShaft
.GetPosition(), GetThumbSize());
604 rectThumb
.x
+= (rectShaft
.width
- rectThumb
.width
) / 2;
606 lenThumb
= rectThumb
.height
;
607 lenShaft
= rectShaft
.height
;
612 rectThumb
.y
+= (rectShaft
.height
- rectThumb
.height
) / 2;
614 lenThumb
= rectThumb
.width
;
615 lenShaft
= rectShaft
.width
;
619 // the thumb must always be entirely inside the shaft limits, so the max
620 // position is not at lenShaft but at lenShaft - thumbSize
621 if ( m_max
!= m_min
)
625 *p
+= ((lenShaft
- lenThumb
)*(m_max
- value
))/(m_max
- m_min
);
629 *p
+= ((lenShaft
- lenThumb
)*(value
- m_min
))/(m_max
- m_min
);
633 // calc the label rect
634 if ( HasLabels() && rectLabelOut
)
636 long style
= GetWindowStyle();
637 wxRect rectLabel
= m_rectLabel
;
639 // centre the label relatively to the thumb position
640 if (style
& (wxSL_TOP
|wxSL_BOTTOM
))
642 rectLabel
.x
= rectThumb
.x
+ (rectThumb
.width
- m_rectLabel
.width
)/2;
644 else if (style
& (wxSL_LEFT
|wxSL_RIGHT
))
646 rectLabel
.y
= rectThumb
.y
+ (rectThumb
.height
- m_rectLabel
.height
)/2;
649 *rectLabelOut
= rectLabel
;
654 *rectThumbOut
= rectThumb
;
657 // ----------------------------------------------------------------------------
659 // ----------------------------------------------------------------------------
661 wxString
wxSlider::FormatValue(int value
) const
663 return wxString::Format(_T("%d"), value
);
666 void wxSlider::DoDraw(wxControlRenderer
*renderer
)
668 wxRenderer
*rend
= GetRenderer();
669 wxDC
& dc
= renderer
->GetDC();
670 wxRect rectUpdate
= GetUpdateClientRect();
672 wxOrientation orient
= GetOrientation();
673 int flags
= GetStateFlags();
674 long style
= GetWindowStyle();
676 wxSize sz
= GetThumbSize();
677 int len
= IsVert() ? sz
.x
: sz
.y
;
679 // first draw the shaft
680 wxRect rectShaft
= rend
->GetSliderShaftRect(m_rectSlider
, len
, orient
, style
);
681 if ( rectUpdate
.Intersects(rectShaft
) )
683 rend
->DrawSliderShaft(dc
, m_rectSlider
, len
, orient
, flags
, style
);
686 // calculate the thumb position in pixels and draw it
687 wxRect rectThumb
, rectLabel
;
688 CalcThumbRect(&rectShaft
, &rectThumb
, &rectLabel
);
690 // then draw the ticks
691 if ( HasTicks() && rectUpdate
.Intersects(m_rectTicks
) )
693 rend
->DrawSliderTicks(dc
, m_rectSlider
, len
, orient
,
694 m_min
, m_max
, m_tickFreq
, flags
, style
);
697 // then draw the thumb
698 if ( rectUpdate
.Intersects(rectThumb
) )
700 rend
->DrawSliderThumb(dc
, rectThumb
, orient
, flags
| m_thumbFlags
, style
);
703 // finally, draw the label near the thumb
704 if ( HasLabels() && rectUpdate
.Intersects(rectLabel
) )
706 // align it to be close to the shaft
708 if (style
& wxSL_TOP
)
710 align
= wxALIGN_CENTRE_HORIZONTAL
|wxALIGN_TOP
;
712 else if (style
& wxSL_BOTTOM
)
714 align
= wxALIGN_CENTRE_HORIZONTAL
|wxALIGN_BOTTOM
;
716 else if (style
& wxSL_LEFT
)
718 align
= wxALIGN_CENTRE_VERTICAL
|wxALIGN_LEFT
;
720 else if (style
& wxSL_RIGHT
)
722 align
= wxALIGN_CENTRE_VERTICAL
|wxALIGN_RIGHT
;
725 dc
.SetFont(GetFont());
726 dc
.SetTextForeground(GetForegroundColour());
728 // the slider label is never drawn focused
729 rend
->DrawLabel(dc
, FormatValue(m_value
), rectLabel
,
730 flags
& ~wxCONTROL_FOCUSED
, align
);
734 // ----------------------------------------------------------------------------
735 // wxSlider input processing
736 // ----------------------------------------------------------------------------
738 bool wxSlider::PerformAction(const wxControlAction
& action
,
740 const wxString
& strArg
)
742 if ( action
== wxACTION_SLIDER_START
)
744 ChangeValueTo(m_min
);
746 else if ( action
== wxACTION_SLIDER_END
)
748 ChangeValueTo(m_max
);
750 else if ( action
== wxACTION_SLIDER_PAGE_CHANGE
)
752 ChangeValueBy(numArg
* GetPageSize());
754 else if ( action
== wxACTION_SLIDER_LINE_UP
)
756 ChangeValueBy(+GetLineSize());
758 else if ( action
== wxACTION_SLIDER_LINE_DOWN
)
760 ChangeValueBy(-GetLineSize());
762 else if ( action
== wxACTION_SLIDER_PAGE_UP
)
764 ChangeValueBy(+GetPageSize());
766 else if ( action
== wxACTION_SLIDER_PAGE_DOWN
)
768 ChangeValueBy(-GetPageSize());
770 else if ( action
== wxACTION_SLIDER_THUMB_DRAG
)
772 // no special processing for it
775 else if ( action
== wxACTION_SLIDER_THUMB_MOVE
||
776 action
== wxACTION_SLIDER_THUMB_RELEASE
)
778 ChangeValueTo((int)numArg
);
782 return wxControl::PerformAction(action
, numArg
, strArg
);
788 // ----------------------------------------------------------------------------
789 // wxSlider implementation of wxControlWithThumb interface
790 // ----------------------------------------------------------------------------
792 wxScrollThumb::Shaft
wxSlider::HitTest(const wxPoint
& pt
) const
794 wxRect rectShaft
= GetShaftRect();
796 CalcThumbRect(&rectShaft
, &rectThumb
, NULL
);
798 // check for possible shaft or thumb hit
799 if (!rectShaft
.Inside(pt
) && !rectThumb
.Inside(pt
))
801 return wxScrollThumb::Shaft_None
;
804 // the position to test and the start and end of the thumb
805 wxCoord x
, x1
, x2
, x3
, x4
;
809 x1
= rectThumb
.GetBottom();
810 x2
= rectShaft
.GetBottom();
811 x3
= rectShaft
.GetTop();
812 x4
= rectThumb
.GetTop();
817 x1
= rectShaft
.GetLeft();
818 x2
= rectThumb
.GetLeft();
819 x3
= rectThumb
.GetRight();
820 x4
= rectShaft
.GetRight();
822 if ((x1
<= x
) && (x
< x2
))
825 return wxScrollThumb::Shaft_Above
;
828 if ((x3
< x
) && (x
<= x4
)) {
830 return wxScrollThumb::Shaft_Below
;
833 // where else can it be?
834 return wxScrollThumb::Shaft_Thumb
;
837 wxCoord
wxSlider::ThumbPosToPixel() const
840 CalcThumbRect(NULL
, &rectThumb
, NULL
);
842 return IsVert() ? rectThumb
.y
: rectThumb
.x
;
845 int wxSlider::PixelToThumbPos(wxCoord x
) const
847 wxRect rectShaft
= GetShaftRect();
848 wxSize sizeThumb
= GetThumbSize();
854 len
= rectShaft
.height
- sizeThumb
.y
;
859 len
= rectShaft
.width
- sizeThumb
.x
;
867 pos
+= ((x
- x0
) * (m_max
- m_min
)) / len
;
871 //else: x <= x0, leave pos = min
877 void wxSlider::SetShaftPartState(wxScrollThumb::Shaft shaftPart
,
881 // for now we ignore the flags for the shaft as no renderer uses them
883 if ( shaftPart
== wxScrollThumb::Shaft_Thumb
)
886 m_thumbFlags
|= flag
;
888 m_thumbFlags
&= ~flag
;
894 void wxSlider::OnThumbDragStart(int pos
)
898 PerformAction(wxACTION_SLIDER_THUMB_DRAG
, m_max
- pos
);
902 PerformAction(wxACTION_SLIDER_THUMB_DRAG
, pos
);
906 void wxSlider::OnThumbDrag(int pos
)
910 PerformAction(wxACTION_SLIDER_THUMB_MOVE
, m_max
- pos
);
914 PerformAction(wxACTION_SLIDER_THUMB_MOVE
, pos
);
918 void wxSlider::OnThumbDragEnd(int pos
)
922 PerformAction(wxACTION_SLIDER_THUMB_RELEASE
, m_max
- pos
);
926 PerformAction(wxACTION_SLIDER_THUMB_RELEASE
, pos
);
930 void wxSlider::OnPageScrollStart()
932 // we do nothing here
935 bool wxSlider::OnPageScroll(int pageInc
)
937 int value
= GetValue();
938 PerformAction(wxACTION_SLIDER_PAGE_CHANGE
, pageInc
);
940 return GetValue() != value
;
943 // ----------------------------------------------------------------------------
944 // wxStdSliderButtonInputHandler
945 // ----------------------------------------------------------------------------
947 bool wxStdSliderButtonInputHandler::HandleKey(wxInputConsumer
*consumer
,
948 const wxKeyEvent
& event
,
953 int keycode
= event
.GetKeyCode();
955 wxControlAction action
;
959 action
= wxACTION_SLIDER_END
;
963 action
= wxACTION_SLIDER_START
;
968 action
= wxACTION_SLIDER_LINE_UP
;
973 action
= wxACTION_SLIDER_LINE_DOWN
;
978 action
= wxACTION_SLIDER_PAGE_UP
;
983 action
= wxACTION_SLIDER_PAGE_DOWN
;
987 if ( !action
.IsEmpty() )
989 consumer
->PerformAction(action
);
995 return wxStdInputHandler::HandleKey(consumer
, event
, pressed
);
998 bool wxStdSliderButtonInputHandler::HandleMouse(wxInputConsumer
*consumer
,
999 const wxMouseEvent
& event
)
1001 wxSlider
*slider
= wxStaticCast(consumer
->GetInputWindow(), wxSlider
);
1003 if ( slider
->GetThumb().HandleMouse(event
) )
1005 // processed by the thumb
1009 return wxStdInputHandler::HandleMouse(consumer
, event
);
1012 bool wxStdSliderButtonInputHandler::HandleMouseMove(wxInputConsumer
*consumer
,
1013 const wxMouseEvent
& event
)
1015 wxSlider
*slider
= wxStaticCast(consumer
->GetInputWindow(), wxSlider
);
1017 if ( slider
->GetThumb().HandleMouseMove(event
) )
1019 // processed by the thumb
1023 return wxStdInputHandler::HandleMouseMove(consumer
, event
);
1027 wxStdSliderButtonInputHandler::HandleFocus(wxInputConsumer
* WXUNUSED(consumer
),
1028 const wxFocusEvent
& WXUNUSED(event
))
1030 // slider's appearance changes when it gets/loses focus
1034 #endif // wxUSE_SLIDER