]> git.saurik.com Git - wxWidgets.git/blame_incremental - src/msw/slider.cpp
wxRTC: save and load the 'shown' status in case there's a situation where layout...
[wxWidgets.git] / src / msw / slider.cpp
... / ...
CommitLineData
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
42namespace
43{
44
45// indices of labels in wxSlider::m_labels
46enum
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
55const int HGAP = 5;
56const int VGAP = 4;
57// the width of the borders including white space
58const int BORDERPAD = 8;
59// these 2 values are arbitrary:
60const int THUMB = 24;
61const int TICK = 8;
62
63} // anonymous namespace
64
65// ============================================================================
66// wxSlider implementation
67// ============================================================================
68
69// ----------------------------------------------------------------------------
70// construction
71// ----------------------------------------------------------------------------
72
73void 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
86bool 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
190WXDWORD 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
225wxSlider::~wxSlider()
226{
227 delete m_labels;
228}
229
230// ----------------------------------------------------------------------------
231// event handling
232// ----------------------------------------------------------------------------
233
234bool 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
319void wxSlider::Command (wxCommandEvent & event)
320{
321 SetValue (event.GetInt());
322 ProcessCommand (event);
323}
324
325// ----------------------------------------------------------------------------
326// geometry stuff
327// ----------------------------------------------------------------------------
328
329wxRect 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
347void 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
357void 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
367int 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
383void 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
510wxSize 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 ticks so we don't need
548 // extra space for them if we're also showing ticks.
549 if ( HasFlag(wxSL_MIN_MAX_LABELS) && !HasFlag(wxSL_TICKS) )
550 size.y += labelSize;
551
552 // The value label is always on top of the control and so does need
553 // extra space in any case.
554 if ( HasFlag(wxSL_VALUE_LABEL) )
555 size.y += labelSize;
556 }
557 }
558
559 // need extra space to show ticks
560 if ( HasFlag(wxSL_TICKS) )
561 {
562 *width += TICK;
563 // and maybe twice as much if we show them on both sides
564 if ( HasFlag(wxSL_BOTH) )
565 *width += TICK;
566 }
567 return size;
568}
569
570// ----------------------------------------------------------------------------
571// slider-specific methods
572// ----------------------------------------------------------------------------
573
574int wxSlider::GetValue() const
575{
576 return ValueInvertOrNot(::SendMessage(GetHwnd(), TBM_GETPOS, 0, 0));
577}
578
579void wxSlider::SetValue(int value)
580{
581 ::SendMessage(GetHwnd(), TBM_SETPOS, (WPARAM)TRUE, (LPARAM)ValueInvertOrNot(value));
582
583 if ( m_labels )
584 {
585 ::SetWindowText((*m_labels)[SliderLabel_Value], Format(value).t_str());
586 }
587}
588
589void wxSlider::SetRange(int minValue, int maxValue)
590{
591 // Remember the old logical value if we need to update the physical control
592 // value after changing its range in wxSL_INVERSE case (and avoid an
593 // unnecessary call to GetValue() otherwise as it's just not needed).
594 const int valueOld = HasFlag(wxSL_INVERSE) ? GetValue() : 0;
595
596 m_rangeMin = minValue;
597 m_rangeMax = maxValue;
598
599 ::SendMessage(GetHwnd(), TBM_SETRANGEMIN, TRUE, m_rangeMin);
600 ::SendMessage(GetHwnd(), TBM_SETRANGEMAX, TRUE, m_rangeMax);
601
602 if ( m_labels )
603 {
604 ::SetWindowText((*m_labels)[SliderLabel_Min],
605 Format(ValueInvertOrNot(m_rangeMin)).t_str());
606 ::SetWindowText((*m_labels)[SliderLabel_Max],
607 Format(ValueInvertOrNot(m_rangeMax)).t_str());
608 }
609
610 // When emulating wxSL_INVERSE style in wxWidgets, we need to update the
611 // value after changing the range to ensure that the value seen by the user
612 // code, i.e. the one returned by GetValue(), does not change.
613 if ( HasFlag(wxSL_INVERSE) )
614 {
615 ::SendMessage(GetHwnd(), TBM_SETPOS, TRUE, ValueInvertOrNot(valueOld));
616 }
617}
618
619void wxSlider::DoSetTickFreq(int n)
620{
621 m_tickFreq = n;
622 ::SendMessage( GetHwnd(), TBM_SETTICFREQ, (WPARAM) n, (LPARAM) 0 );
623}
624
625void wxSlider::SetPageSize(int pageSize)
626{
627 ::SendMessage( GetHwnd(), TBM_SETPAGESIZE, (WPARAM) 0, (LPARAM) pageSize );
628 m_pageSize = pageSize;
629}
630
631int wxSlider::GetPageSize() const
632{
633 return m_pageSize;
634}
635
636void wxSlider::ClearSel()
637{
638 ::SendMessage(GetHwnd(), TBM_CLEARSEL, (WPARAM) TRUE, (LPARAM) 0);
639}
640
641void wxSlider::ClearTicks()
642{
643 ::SendMessage(GetHwnd(), TBM_CLEARTICS, (WPARAM) TRUE, (LPARAM) 0);
644}
645
646void wxSlider::SetLineSize(int lineSize)
647{
648 m_lineSize = lineSize;
649 ::SendMessage(GetHwnd(), TBM_SETLINESIZE, (WPARAM) 0, (LPARAM) lineSize);
650}
651
652int wxSlider::GetLineSize() const
653{
654 return (int)::SendMessage(GetHwnd(), TBM_GETLINESIZE, 0, 0);
655}
656
657int wxSlider::GetSelEnd() const
658{
659 return (int)::SendMessage(GetHwnd(), TBM_GETSELEND, 0, 0);
660}
661
662int wxSlider::GetSelStart() const
663{
664 return (int)::SendMessage(GetHwnd(), TBM_GETSELSTART, 0, 0);
665}
666
667void wxSlider::SetSelection(int minPos, int maxPos)
668{
669 ::SendMessage(GetHwnd(), TBM_SETSEL,
670 (WPARAM) TRUE /* redraw */,
671 (LPARAM) MAKELONG( minPos, maxPos) );
672}
673
674void wxSlider::SetThumbLength(int len)
675{
676 ::SendMessage(GetHwnd(), TBM_SETTHUMBLENGTH, (WPARAM) len, (LPARAM) 0);
677}
678
679int wxSlider::GetThumbLength() const
680{
681 return (int)::SendMessage( GetHwnd(), TBM_GETTHUMBLENGTH, 0, 0);
682}
683
684void wxSlider::SetTick(int tickPos)
685{
686 ::SendMessage( GetHwnd(), TBM_SETTIC, (WPARAM) 0, (LPARAM) tickPos );
687}
688
689// ----------------------------------------------------------------------------
690// composite control methods
691// ----------------------------------------------------------------------------
692
693WXHWND wxSlider::GetStaticMin() const
694{
695 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Min] : NULL;
696}
697
698WXHWND wxSlider::GetStaticMax() const
699{
700 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Max] : NULL;
701}
702
703WXHWND wxSlider::GetEditValue() const
704{
705 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Value] : NULL;
706}
707
708WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxSlider, wxSliderBase, m_labels)
709
710#endif // wxUSE_SLIDER