wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / msw / slider.cpp
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
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
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
190 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
225 wxSlider::~wxSlider()
226 {
227 delete m_labels;
228 }
229
230 // ----------------------------------------------------------------------------
231 // event handling
232 // ----------------------------------------------------------------------------
233
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
319 void wxSlider::Command (wxCommandEvent & event)
320 {
321 SetValue (event.GetInt());
322 ProcessCommand (event);
323 }
324
325 // ----------------------------------------------------------------------------
326 // geometry stuff
327 // ----------------------------------------------------------------------------
328
329 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
347 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
357 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
367 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
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
510 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 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
574 int wxSlider::GetValue() const
575 {
576 return ValueInvertOrNot(::SendMessage(GetHwnd(), TBM_GETPOS, 0, 0));
577 }
578
579 void 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
589 void 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
619 void wxSlider::DoSetTickFreq(int n)
620 {
621 m_tickFreq = n;
622 ::SendMessage( GetHwnd(), TBM_SETTICFREQ, (WPARAM) n, (LPARAM) 0 );
623 }
624
625 void wxSlider::SetPageSize(int pageSize)
626 {
627 ::SendMessage( GetHwnd(), TBM_SETPAGESIZE, (WPARAM) 0, (LPARAM) pageSize );
628 m_pageSize = pageSize;
629 }
630
631 int wxSlider::GetPageSize() const
632 {
633 return m_pageSize;
634 }
635
636 void wxSlider::ClearSel()
637 {
638 ::SendMessage(GetHwnd(), TBM_CLEARSEL, (WPARAM) TRUE, (LPARAM) 0);
639 }
640
641 void wxSlider::ClearTicks()
642 {
643 ::SendMessage(GetHwnd(), TBM_CLEARTICS, (WPARAM) TRUE, (LPARAM) 0);
644 }
645
646 void wxSlider::SetLineSize(int lineSize)
647 {
648 m_lineSize = lineSize;
649 ::SendMessage(GetHwnd(), TBM_SETLINESIZE, (WPARAM) 0, (LPARAM) lineSize);
650 }
651
652 int wxSlider::GetLineSize() const
653 {
654 return (int)::SendMessage(GetHwnd(), TBM_GETLINESIZE, 0, 0);
655 }
656
657 int wxSlider::GetSelEnd() const
658 {
659 return (int)::SendMessage(GetHwnd(), TBM_GETSELEND, 0, 0);
660 }
661
662 int wxSlider::GetSelStart() const
663 {
664 return (int)::SendMessage(GetHwnd(), TBM_GETSELSTART, 0, 0);
665 }
666
667 void wxSlider::SetSelection(int minPos, int maxPos)
668 {
669 ::SendMessage(GetHwnd(), TBM_SETSEL,
670 (WPARAM) TRUE /* redraw */,
671 (LPARAM) MAKELONG( minPos, maxPos) );
672 }
673
674 void wxSlider::SetThumbLength(int len)
675 {
676 ::SendMessage(GetHwnd(), TBM_SETTHUMBLENGTH, (WPARAM) len, (LPARAM) 0);
677 }
678
679 int wxSlider::GetThumbLength() const
680 {
681 return (int)::SendMessage( GetHwnd(), TBM_GETTHUMBLENGTH, 0, 0);
682 }
683
684 void wxSlider::SetTick(int tickPos)
685 {
686 ::SendMessage( GetHwnd(), TBM_SETTIC, (WPARAM) 0, (LPARAM) tickPos );
687 }
688
689 // ----------------------------------------------------------------------------
690 // composite control methods
691 // ----------------------------------------------------------------------------
692
693 WXHWND wxSlider::GetStaticMin() const
694 {
695 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Min] : NULL;
696 }
697
698 WXHWND wxSlider::GetStaticMax() const
699 {
700 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Max] : NULL;
701 }
702
703 WXHWND wxSlider::GetEditValue() const
704 {
705 return m_labels ? (WXHWND)(*m_labels)[SliderLabel_Value] : NULL;
706 }
707
708 WX_FORWARD_STD_METHODS_TO_SUBWINDOWS(wxSlider, wxSliderBase, m_labels)
709
710 #endif // wxUSE_SLIDER