1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/slider.cpp
3 // Purpose: wxSlider, using the Win95 (and later) trackbar control
4 // Author: Julian Smart
5 // Modified by:
6 // Created: 04/01/98
7 // Copyright: (c) Julian Smart 1998
8 // Vadim Zeitlin 2004
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_SLIDER
28
29 #include "wx/slider.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/msw/wrapcctl.h" // include <commctrl.h> "properly"
33 #include "wx/brush.h"
34 #endif
35
36 #include "wx/msw/subwin.h"
37
38 // ----------------------------------------------------------------------------
39 // constants
40 // ----------------------------------------------------------------------------
41
42 namespace
43 {
44
45 // indices of labels in wxSlider::m_labels
46 enum
47 {
48 SliderLabel_Min,
49 SliderLabel_Max,
50 SliderLabel_Value,
51 SliderLabel_Last
52 };
53
54 // the gaps between the slider and the labels, in pixels
55 const int HGAP = 5;
56 const int VGAP = 4;
57 // the width of the borders including white space
58 const int BORDERPAD = 8;
59 // these 2 values are arbitrary:
60 const int THUMB = 24;
61 const int TICK = 8;
62
63 } // anonymous namespace
64
65 // ============================================================================
66 // wxSlider implementation
67 // ============================================================================
68
69 // ----------------------------------------------------------------------------
70 // construction
71 // ----------------------------------------------------------------------------
72
Init()73 void wxSlider::Init()
74 {
75 m_labels = NULL;
76
77 m_pageSize = 1;
78 m_lineSize = 1;
79 m_rangeMax = 0;
80 m_rangeMin = 0;
81 m_tickFreq = 0;
82
83 m_isDragging = false;
84 }
85
Create(wxWindow * parent,wxWindowID id,int value,int minValue,int maxValue,const wxPoint & pos,const wxSize & size,long style,const wxValidator & validator,const wxString & name)86 bool wxSlider::Create(wxWindow *parent,
87 wxWindowID id,
88 int value,
89 int minValue,
90 int maxValue,
91 const wxPoint& pos,
92 const wxSize& size,
93 long style,
94 const wxValidator& validator,
95 const wxString& name)
96 {
97 wxCHECK_MSG( minValue < maxValue, false,
98 wxT("Slider minimum must be strictly less than the maximum.") );
99
100 // our styles are redundant: wxSL_LEFT/RIGHT imply wxSL_VERTICAL and
101 // wxSL_TOP/BOTTOM imply wxSL_HORIZONTAL, but for backwards compatibility
102 // reasons we can't really change it, instead try to infer the orientation
103 // from the flags given to us here
104 switch ( style & (wxSL_LEFT | wxSL_RIGHT | wxSL_TOP | wxSL_BOTTOM) )
105 {
106 case wxSL_LEFT:
107 case wxSL_RIGHT:
108 style |= wxSL_VERTICAL;
109 break;
110
111 case wxSL_TOP:
112 case wxSL_BOTTOM:
113 style |= wxSL_HORIZONTAL;
114 break;
115
116 case 0:
117 // no specific direction, do we have at least the orientation?
118 if ( !(style & (wxSL_HORIZONTAL | wxSL_VERTICAL)) )
119 {
120 // no, choose default
121 style |= wxSL_BOTTOM | wxSL_HORIZONTAL;
122 }
123 };
124
125 wxASSERT_MSG( !(style & wxSL_VERTICAL) || !(style & wxSL_HORIZONTAL),
126 wxT("incompatible slider direction and orientation") );
127
128
129 // initialize everything
130 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
131 return false;
132
133 // ensure that we have correct values for GetLabelsSize()
134 m_rangeMin = minValue;
135 m_rangeMax = maxValue;
136
137 // create the labels first, so that our DoGetBestSize() could take them
138 // into account
139 //
140 // note that we could simply create 3 wxStaticTexts here but it could
141 // result in some observable side effects at wx level (e.g. the parent of
142 // wxSlider would have 3 more children than expected) and so we prefer not
143 // to do it like this
144 if ( m_windowStyle & wxSL_LABELS )
145 {
146 m_labels = new wxSubwindows(SliderLabel_Last);
147
148 HWND hwndParent = GetHwndOf(parent);
149 for ( size_t n = 0; n < SliderLabel_Last; n++ )
150 {
151 wxWindowIDRef lblid = NewControlId();
152
153 HWND wnd = ::CreateWindow
154 (
155 wxT("STATIC"),
156 NULL,
157 WS_CHILD | WS_VISIBLE | SS_CENTER,
158 0, 0, 0, 0,
159 hwndParent,
160 (HMENU)wxUIntToPtr(lblid.GetValue()),
161 wxGetInstance(),
162 NULL
163 );
164
165 m_labels->Set(n, wnd, lblid);
166 }
167 m_labels->SetFont(GetFont());
168 }
169
170 // now create the main control too
171 if ( !MSWCreateControl(TRACKBAR_CLASS, wxEmptyString, pos, size) )
172 return false;
173
174 // and initialize everything
175 SetRange(minValue, maxValue);
176 SetValue(value);
177 SetPageSize( wxMax(1, (maxValue - minValue)/10) );
178
179 // we need to position the labels correctly if we have them and if
180 // SetSize() hadn't been called before (when best size was determined by
181 // MSWCreateControl()) as in this case they haven't been put in place yet
182 if ( m_labels && size.x != wxDefaultCoord && size.y != wxDefaultCoord )
183 {
184 SetSize(size);
185 }
186
187 return true;
188 }
189
MSWGetStyle(long style,WXDWORD * exstyle) const190 WXDWORD wxSlider::MSWGetStyle(long style, WXDWORD *exstyle) const
191 {
192 WXDWORD msStyle = wxControl::MSWGetStyle(style, exstyle);
193
194 // TBS_HORZ, TBS_RIGHT and TBS_BOTTOM are 0 but do include them for clarity
195 msStyle |= style & wxSL_VERTICAL ? TBS_VERT : TBS_HORZ;
196
197 if ( style & wxSL_BOTH )
198 {
199 // this fully specifies the style combined with TBS_VERT/HORZ above
200 msStyle |= TBS_BOTH;
201 }
202 else // choose one direction
203 {
204 if ( style & wxSL_LEFT )
205 msStyle |= TBS_LEFT;
206 else if ( style & wxSL_RIGHT )
207 msStyle |= TBS_RIGHT;
208 else if ( style & wxSL_TOP )
209 msStyle |= TBS_TOP;
210 else if ( style & wxSL_BOTTOM )
211 msStyle |= TBS_BOTTOM;
212 }
213
214 if ( style & wxSL_AUTOTICKS )
215 msStyle |= TBS_AUTOTICKS;
216 else
217 msStyle |= TBS_NOTICKS;
218
219 if ( style & wxSL_SELRANGE )
220 msStyle |= TBS_ENABLESELRANGE;
221
222 return msStyle;
223 }
224
~wxSlider()225 wxSlider::~wxSlider()
226 {
227 delete m_labels;
228 }
229
230 // ----------------------------------------------------------------------------
231 // event handling
232 // ----------------------------------------------------------------------------
233
MSWOnScroll(int WXUNUSED (orientation),WXWORD wParam,WXWORD WXUNUSED (pos),WXHWND control)234 bool wxSlider::MSWOnScroll(int WXUNUSED(orientation),
235 WXWORD wParam,
236 WXWORD WXUNUSED(pos),
237 WXHWND control)
238 {
239 wxEventType scrollEvent;
240 switch ( wParam )
241 {
242 case SB_TOP:
243 scrollEvent = wxEVT_SCROLL_TOP;
244 break;
245
246 case SB_BOTTOM:
247 scrollEvent = wxEVT_SCROLL_BOTTOM;
248 break;
249
250 case SB_LINEUP:
251 scrollEvent = wxEVT_SCROLL_LINEUP;
252 break;
253
254 case SB_LINEDOWN:
255 scrollEvent = wxEVT_SCROLL_LINEDOWN;
256 break;
257
258 case SB_PAGEUP:
259 scrollEvent = wxEVT_SCROLL_PAGEUP;
260 break;
261
262 case SB_PAGEDOWN:
263 scrollEvent = wxEVT_SCROLL_PAGEDOWN;
264 break;
265
266 case SB_THUMBTRACK:
267 scrollEvent = wxEVT_SCROLL_THUMBTRACK;
268 m_isDragging = true;
269 break;
270
271 case SB_THUMBPOSITION:
272 if ( m_isDragging )
273 {
274 scrollEvent = wxEVT_SCROLL_THUMBRELEASE;
275 m_isDragging = false;
276 }
277 else
278 {
279 // this seems to only happen when the mouse wheel is used: in
280 // this case, as it might be unexpected to get THUMBRELEASE
281 // without preceding THUMBTRACKs, we don't generate it at all
282 // but generate CHANGED event because the control itself does
283 // not send us SB_ENDSCROLL for whatever reason when mouse
284 // wheel is used
285 scrollEvent = wxEVT_SCROLL_CHANGED;
286 }
287 break;
288
289 case SB_ENDSCROLL:
290 scrollEvent = wxEVT_SCROLL_CHANGED;
291 break;
292
293 default:
294 // unknown scroll event?
295 return false;
296 }
297
298 int newPos = ValueInvertOrNot((int) ::SendMessage((HWND) control, TBM_GETPOS, 0, 0));
299 if ( (newPos < GetMin()) || (newPos > GetMax()) )
300 {
301 // out of range - but we did process it
302 return true;
303 }
304
305 SetValue(newPos);
306
307 wxScrollEvent event(scrollEvent, m_windowId);
308 event.SetPosition(newPos);
309 event.SetEventObject( this );
310 HandleWindowEvent(event);
311
312 wxCommandEvent cevent( wxEVT_SLIDER, GetId() );
313 cevent.SetInt( newPos );
314 cevent.SetEventObject( this );
315
316 return HandleWindowEvent( cevent );
317 }
318
Command(wxCommandEvent & event)319 void wxSlider::Command (wxCommandEvent & event)
320 {
321 SetValue (event.GetInt());
322 ProcessCommand (event);
323 }
324
325 // ----------------------------------------------------------------------------
326 // geometry stuff
327 // ----------------------------------------------------------------------------
328
GetBoundingBox() const329 wxRect wxSlider::GetBoundingBox() const
330 {
331 // take care not to call our own functions which would call us recursively
332 int x, y, w, h;
333 wxSliderBase::DoGetPosition(&x, &y);
334 wxSliderBase::DoGetSize(&w, &h);
335
336 wxRect rect(x, y, w, h);
337 if ( m_labels )
338 {
339 wxRect lrect = m_labels->GetBoundingBox();
340 GetParent()->ScreenToClient(&lrect.x, &lrect.y);
341 rect.Union(lrect);
342 }
343
344 return rect;
345 }
346
DoGetSize(int * width,int * height) const347 void wxSlider::DoGetSize(int *width, int *height) const
348 {
349 wxRect rect = GetBoundingBox();
350
351 if ( width )
352 *width = rect.width;
353 if ( height )
354 *height = rect.height;
355 }
356
DoGetPosition(int * x,int * y) const357 void wxSlider::DoGetPosition(int *x, int *y) const
358 {
359 wxRect rect = GetBoundingBox();
360
361 if ( x )
362 *x = rect.x;
363 if ( y )
364 *y = rect.y;
365 }
366
GetLabelsSize(int * widthMin,int * widthMax) const367 int wxSlider::GetLabelsSize(int *widthMin, int *widthMax) const
368 {
369 if ( widthMin && widthMax )
370 {
371 *widthMin = GetTextExtent(Format(m_rangeMin)).x;
372 *widthMax = GetTextExtent(Format(m_rangeMax)).x;
373
374 if ( HasFlag(wxSL_INVERSE) )
375 {
376 wxSwap(*widthMin, *widthMax);
377 }
378 }
379
380 return HasFlag(wxSL_LABELS) ? GetCharHeight() : 0;
381 }
382
DoMoveWindow(int x,int y,int width,int height)383 void wxSlider::DoMoveWindow(int x, int y, int width, int height)
384 {
385 // all complications below are because we need to position the labels,
386 // without them everything is easy
387 if ( !m_labels )
388 {
389 wxSliderBase::DoMoveWindow(x, y, width, height);
390 return;
391 }
392
393 int minLabelWidth,
394 maxLabelWidth;
395 const int labelHeight = GetLabelsSize(&minLabelWidth, &maxLabelWidth);
396 const int longestLabelWidth = wxMax(minLabelWidth, maxLabelWidth);
397 if ( !HasFlag(wxSL_MIN_MAX_LABELS) )
398 {
399 minLabelWidth =
400 maxLabelWidth = 0;
401 }
402
403 int tickOffset = 0;
404 if ( HasFlag(wxSL_TICKS))
405 tickOffset = TICK;
406 if ( HasFlag(wxSL_BOTH))
407 tickOffset *= 2;
408
409 // be careful to position the slider itself after moving the labels as
410 // otherwise our GetBoundingBox(), which is called from WM_SIZE handler,
411 // would return a wrong result and wrong size would be cached internally
412 if ( HasFlag(wxSL_VERTICAL) )
413 {
414 int labelOffset = 0;
415 int holdTopX;
416 int holdBottomX;
417 int xLabel = (wxMax((THUMB + (BORDERPAD * 2)), longestLabelWidth) / 2) -
418 (longestLabelWidth / 2) + x;
419 if ( HasFlag(wxSL_LEFT) )
420 {
421 holdTopX = xLabel;
422 holdBottomX = xLabel - (abs(maxLabelWidth - minLabelWidth) / 2);
423 }
424 else // wxSL_RIGHT
425 {
426 holdTopX = xLabel + longestLabelWidth + (abs(maxLabelWidth - minLabelWidth) / 2);
427 holdBottomX = xLabel + longestLabelWidth;
428
429 labelOffset = longestLabelWidth + HGAP;
430 }
431
432 if ( HasFlag(wxSL_MIN_MAX_LABELS) )
433 {
434 if ( HasFlag(wxSL_INVERSE) )
435 {
436 wxSwap(holdTopX, holdBottomX);
437 }
438
439 DoMoveSibling((HWND)(*m_labels)[SliderLabel_Min],
440 holdTopX,
441 y,
442 minLabelWidth, labelHeight);
443 DoMoveSibling((HWND)(*m_labels)[SliderLabel_Max],
444 holdBottomX,
445 y + height - labelHeight,
446 maxLabelWidth, labelHeight);
447 }
448
449 if ( HasFlag(wxSL_VALUE_LABEL) )
450 {
451 DoMoveSibling((HWND)(*m_labels)[SliderLabel_Value],
452 x + ( HasFlag(wxSL_LEFT) ? THUMB + tickOffset + HGAP : 0 ),
453 y + (height - labelHeight)/2,
454 longestLabelWidth, labelHeight);
455 }
456
457 // position the slider itself along the left/right edge
458 wxSliderBase::DoMoveWindow(
459 x + labelOffset,
460 y + labelHeight,
461 THUMB + tickOffset + HGAP,
462 height - (labelHeight * 2));
463 }
464 else // horizontal
465 {
466 int yLabelMinMax =
467 (y + ((THUMB + tickOffset) / 2)) - (labelHeight / 2);
468 int xLabelValue =
469 x + minLabelWidth +
470 ((width - (minLabelWidth + maxLabelWidth)) / 2) -
471 (longestLabelWidth / 2);
472
473 int ySlider = y;
474
475 if ( HasFlag(wxSL_VALUE_LABEL) )
476 {
477 DoMoveSibling((HWND)(*m_labels)[SliderLabel_Value],
478 xLabelValue,
479 y + (HasFlag(wxSL_BOTTOM) ? 0 : THUMB + tickOffset),
480 longestLabelWidth, labelHeight);
481
482 if ( HasFlag(wxSL_BOTTOM) )
483 {
484 ySlider += labelHeight;
485 yLabelMinMax += labelHeight;
486 }
487 }
488
489 if ( HasFlag(wxSL_MIN_MAX_LABELS) )
490 {
491 DoMoveSibling((HWND)(*m_labels)[SliderLabel_Min],
492 x,
493 yLabelMinMax,
494 minLabelWidth, labelHeight);
495 DoMoveSibling((HWND)(*m_labels)[SliderLabel_Max],
496 x + width - maxLabelWidth,
497 yLabelMinMax,
498 maxLabelWidth, labelHeight);
499 }
500
501 // position the slider itself along the top/bottom edge
502 wxSliderBase::DoMoveWindow(
503 x + minLabelWidth + VGAP,
504 ySlider,
505 width - (minLabelWidth + maxLabelWidth + (VGAP*2)),
506 THUMB + tickOffset);
507 }
508 }
509
DoGetBestSize() const510 wxSize wxSlider::DoGetBestSize() const
511 {
512 // this value is arbitrary:
513 static const int length = 100;
514
515 int *width;
516 wxSize size;
517 if ( HasFlag(wxSL_VERTICAL) )
518 {
519 size.x = THUMB;
520 size.y = length;
521 width = &size.x;
522
523 if ( m_labels )
524 {
525 int widthMin,
526 widthMax;
527 int hLabel = GetLabelsSize(&widthMin, &widthMax);
528
529 // account for the labels
530 if ( HasFlag(wxSL_MIN_MAX_LABELS) )
531 size.x += HGAP + wxMax(widthMin, widthMax);
532
533 // labels are indented relative to the slider itself
534 size.y += hLabel;
535 }
536 }
537 else // horizontal
538 {
539 size.x = length;
540 size.y = THUMB;
541 width = &size.y;
542
543 if ( m_labels )
544 {
545 int labelSize = GetLabelsSize();
546
547 // Min/max labels are compensated by the thumb so we don't need
548 // extra space for them
549
550 // The value label is always on top of the control and so does need
551 // extra space in any case.
552 if ( HasFlag(wxSL_VALUE_LABEL) )
553 size.y += labelSize;
554 }
555 }
556
557 // need extra space to show ticks
558 if ( HasFlag(wxSL_TICKS) )
559 {
560 *width += TICK;
561 // and maybe twice as much if we show them on both sides
562 if ( HasFlag(wxSL_BOTH) )
563 *width += TICK;
564 }
565 return size;
566 }
567
568 // ----------------------------------------------------------------------------
569 // slider-specific methods
570 // ----------------------------------------------------------------------------
571
GetValue() const572 int wxSlider::GetValue() const
573 {
574 return ValueInvertOrNot(::SendMessage(GetHwnd(), TBM_GETPOS, 0, 0));
575 }
576
SetValue(int value)577 void wxSlider::SetValue(int value)
578 {
579 ::SendMessage(GetHwnd(), TBM_SETPOS, (WPARAM)TRUE, (LPARAM)ValueInvertOrNot(value));
580
581 if ( m_labels )
582 {
583 ::SetWindowText((*m_labels)[SliderLabel_Value], Format(value).t_str());
584 }
585 }
586
SetRange(int minValue,int maxValue)587 void wxSlider::SetRange(int minValue, int maxValue)
588 {
589 // Remember the old logical value if we need to update the physical control
590 // value after changing its range in wxSL_INVERSE case (and avoid an
591 // unnecessary call to GetValue() otherwise as it's just not needed).
592 const int valueOld = HasFlag(wxSL_INVERSE) ? GetValue() : 0;
593
594 m_rangeMin = minValue;
595 m_rangeMax = maxValue;
596
597 ::SendMessage(GetHwnd(), TBM_SETRANGEMIN, TRUE, m_rangeMin);
598 ::SendMessage(GetHwnd(), TBM_SETRANGEMAX, TRUE, m_rangeMax);
599
600 if ( m_labels )
601 {
602 ::SetWindowText((*m_labels)[SliderLabel_Min],
603 Format(ValueInvertOrNot(m_rangeMin)).t_str());
604 ::SetWindowText((*m_labels)[SliderLabel_Max],
605 Format(ValueInvertOrNot(m_rangeMax)).t_str());
606 }
607
608 // When emulating wxSL_INVERSE style in wxWidgets, we need to update the
609 // value after changing the range to ensure that the value seen by the user
610 // code, i.e. the one returned by GetValue(), does not change.
611 if ( HasFlag(wxSL_INVERSE) )
612 {
613 ::SendMessage(GetHwnd(), TBM_SETPOS, TRUE, ValueInvertOrNot(valueOld));
614 }
615 }
616
DoSetTickFreq(int n)617 void wxSlider::DoSetTickFreq(int n)
618 {
619 m_tickFreq = n;
620 ::SendMessage( GetHwnd(), TBM_SETTICFREQ, (WPARAM) n, (LPARAM) 0 );
621 }
622
SetPageSize(int pageSize)623 void wxSlider::SetPageSize(int pageSize)
624 {
625 ::SendMessage( GetHwnd(), TBM_SETPAGESIZE, (WPARAM) 0, (LPARAM) pageSize );
626 m_pageSize = pageSize;
627 }
628
GetPageSize() const629 int wxSlider::GetPageSize() const
630 {
631 return m_pageSize;
632 }
633
ClearSel()634 void wxSlider::ClearSel()
635 {
636 ::SendMessage(GetHwnd(), TBM_CLEARSEL, (WPARAM) TRUE, (LPARAM) 0);
637 }
638
ClearTicks()639 void wxSlider::ClearTicks()
640 {
641 ::SendMessage(GetHwnd(), TBM_CLEARTICS, (WPARAM) TRUE, (LPARAM) 0);
642 }
643
SetLineSize(int lineSize)644 void wxSlider::SetLineSize(int lineSize)
645 {
646 m_lineSize = lineSize;
647 ::SendMessage(GetHwnd(), TBM_SETLINESIZE, (WPARAM) 0, (LPARAM) lineSize);
648 }
649
GetLineSize() const650 int wxSlider::GetLineSize() const
651 {
652 return (int)::SendMessage(GetHwnd(), TBM_GETLINESIZE, 0, 0);
653 }
654
GetSelEnd() const655 int wxSlider::GetSelEnd() const
656 {
657 return (int)::SendMessage(GetHwnd(), TBM_GETSELEND, 0, 0);
658 }
659
GetSelStart() const660 int wxSlider::GetSelStart() const
661 {
662 return (int)::SendMessage(GetHwnd(), TBM_GETSELSTART, 0, 0);
663 }
664
SetSelection(int minPos,int maxPos)665 void wxSlider::SetSelection(int minPos, int maxPos)
666 {
667 ::SendMessage(GetHwnd(), TBM_SETSEL,
668 (WPARAM) TRUE /* redraw */,
669 (LPARAM) MAKELONG( minPos, maxPos) );
670 }
671
SetThumbLength(int len)672 void wxSlider::SetThumbLength(int len)
673 {
674 ::SendMessage(GetHwnd(), TBM_SETTHUMBLENGTH, (WPARAM) len, (LPARAM) 0);
675 }
676
GetThumbLength() const677 int wxSlider::GetThumbLength() const
678 {
679 return (int)::SendMessage( GetHwnd(), TBM_GETTHUMBLENGTH, 0, 0);
680 }
681
SetTick(int tickPos)682 void wxSlider::SetTick(int tickPos)
683 {
684 ::SendMessage( GetHwnd(), TBM_SETTIC, (WPARAM) 0, (LPARAM) tickPos );
685 }
686
687 // ----------------------------------------------------------------------------
688 // composite control methods
689 // ----------------------------------------------------------------------------
690
GetStaticMin() const691 WXHWND wxSlider::GetStaticMin() const
692 {
693 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Min] : NULL;
694 }
695
GetStaticMax() const696 WXHWND wxSlider::GetStaticMax() const
697 {
698 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Max] : NULL;
699 }
700
GetEditValue() const701 WXHWND wxSlider::GetEditValue() const
702 {
703 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Value] : NULL;
704 }
705
706 WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxSlider, wxSliderBase, m_labels)
707
708 #endif // wxUSE_SLIDER
709