xref: /haiku/src/kits/interface/StatusBar.cpp (revision b671e9bbdbd10268a042b4f4cc4317ccd03d105e)
1 /*
2  * Copyright 2001-2008, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Stephan Aßmus <superstippi@gmx.de>
9  */
10 
11 /*! BStatusBar displays a "percentage-of-completion" gauge. */
12 #include <StatusBar.h>
13 
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 
18 #include <ControlLook.h>
19 #include <Layout.h>
20 #include <LayoutUtils.h>
21 #include <Message.h>
22 #include <Region.h>
23 
24 #include <binary_compatibility/Interface.h>
25 
26 
27 static const rgb_color kDefaultBarColor = {50, 150, 255, 255};
28 
29 
30 BStatusBar::BStatusBar(BRect frame, const char *name, const char *label,
31 		const char *trailingLabel)
32 	:
33 	BView(frame, name, B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW),
34 	fLabel(label),
35 	fTrailingLabel(trailingLabel)
36 {
37 	_InitObject();
38 }
39 
40 
41 BStatusBar::BStatusBar(const char *name, const char *label,
42 		const char *trailingLabel)
43 	:
44 	BView(BRect(0, 0, -1, -1), name, B_FOLLOW_LEFT | B_FOLLOW_TOP,
45 		B_WILL_DRAW | B_SUPPORTS_LAYOUT),
46 	fLabel(label),
47 	fTrailingLabel(trailingLabel)
48 {
49 	_InitObject();
50 }
51 
52 
53 BStatusBar::BStatusBar(BMessage *archive)
54 	:
55 	BView(archive)
56 {
57 	_InitObject();
58 
59 	archive->FindString("_label", &fLabel);
60 	archive->FindString("_tlabel", &fTrailingLabel);
61 
62 	archive->FindString("_text", &fText);
63 	archive->FindString("_ttext", &fTrailingText);
64 
65 	float floatValue;
66 	if (archive->FindFloat("_high", &floatValue) == B_OK) {
67 		fBarHeight = floatValue;
68 		fCustomBarHeight = true;
69 	}
70 
71 	int32 color;
72 	if (archive->FindInt32("_bcolor", (int32 *)&color) == B_OK)
73 		fBarColor = *(rgb_color *)&color;
74 
75 	if (archive->FindFloat("_val", &floatValue) == B_OK)
76 		fCurrent = floatValue;
77 	if (archive->FindFloat("_max", &floatValue) == B_OK)
78 		fMax = floatValue;
79 }
80 
81 
82 BStatusBar::~BStatusBar()
83 {
84 }
85 
86 
87 BArchivable *
88 BStatusBar::Instantiate(BMessage *archive)
89 {
90 	if (validate_instantiation(archive, "BStatusBar"))
91 		return new BStatusBar(archive);
92 
93 	return NULL;
94 }
95 
96 
97 status_t
98 BStatusBar::Archive(BMessage *archive, bool deep) const
99 {
100 	status_t err = BView::Archive(archive, deep);
101 	if (err < B_OK)
102 		return err;
103 
104 	if (fCustomBarHeight)
105 		err = archive->AddFloat("_high", fBarHeight);
106 
107 	if (err == B_OK && fBarColor != kDefaultBarColor)
108 		err = archive->AddInt32("_bcolor", (const uint32 &)fBarColor);
109 
110 	if (err == B_OK && fCurrent != 0)
111 		err = archive->AddFloat("_val", fCurrent);
112 	if (err == B_OK && fMax != 100 )
113 		err = archive->AddFloat("_max", fMax);
114 
115 	if (err == B_OK && fText.Length())
116 		err = archive->AddString("_text", fText);
117 	if (err == B_OK && fTrailingText.Length())
118 		err = archive->AddString("_ttext", fTrailingText);
119 
120 	if (err == B_OK && fLabel.Length())
121 		err = archive->AddString("_label", fLabel);
122 	if (err == B_OK && fTrailingLabel.Length())
123 		err = archive->AddString ("_tlabel", fTrailingLabel);
124 
125 	return err;
126 }
127 
128 
129 // #pragma mark -
130 
131 
132 void
133 BStatusBar::AttachedToWindow()
134 {
135 	// resize so that the height fits
136 	float width, height;
137 	GetPreferredSize(&width, &height);
138 	ResizeTo(Bounds().Width(), height);
139 
140 	SetViewColor(B_TRANSPARENT_COLOR);
141 	rgb_color lowColor = B_TRANSPARENT_COLOR;
142 
143 	BView* parent = Parent();
144 	if (parent != NULL)
145 		lowColor = parent->ViewColor();
146 
147 	if (lowColor == B_TRANSPARENT_COLOR)
148 		lowColor = ui_color(B_PANEL_BACKGROUND_COLOR);
149 
150 	SetLowColor(lowColor);
151 
152 	fTextDivider = Bounds().Width();
153 }
154 
155 
156 void
157 BStatusBar::DetachedFromWindow()
158 {
159 	BView::DetachedFromWindow();
160 }
161 
162 
163 void
164 BStatusBar::AllAttached()
165 {
166 	BView::AllAttached();
167 }
168 
169 
170 void
171 BStatusBar::AllDetached()
172 {
173 	BView::AllDetached();
174 }
175 
176 
177 // #pragma mark -
178 
179 
180 void
181 BStatusBar::WindowActivated(bool state)
182 {
183 	BView::WindowActivated(state);
184 }
185 
186 
187 void
188 BStatusBar::MakeFocus(bool state)
189 {
190 	BView::MakeFocus(state);
191 }
192 
193 
194 // #pragma mark -
195 
196 
197 void
198 BStatusBar::GetPreferredSize(float* _width, float* _height)
199 {
200 	if (_width) {
201 		// AttachedToWindow() might not have been called yet
202 		*_width = ceilf(StringWidth(fLabel.String()))
203 			+ ceilf(StringWidth(fTrailingLabel.String()))
204 			+ ceilf(StringWidth(fText.String()))
205 			+ ceilf(StringWidth(fTrailingText.String()))
206 			+ 5;
207 	}
208 
209 	if (_height) {
210 		font_height fontHeight;
211 		GetFontHeight(&fontHeight);
212 
213 		*_height = ceilf(fontHeight.ascent + fontHeight.descent) + 6
214 			+ BarHeight();
215 	}
216 }
217 
218 
219 BSize
220 BStatusBar::MinSize()
221 {
222 	float width, height;
223 	GetPreferredSize(&width, &height);
224 
225 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), BSize(width, height));
226 }
227 
228 
229 BSize
230 BStatusBar::MaxSize()
231 {
232 	float width, height;
233 	GetPreferredSize(&width, &height);
234 
235 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
236 			BSize(B_SIZE_UNLIMITED, height));
237 }
238 
239 
240 BSize
241 BStatusBar::PreferredSize()
242 {
243 	float width, height;
244 	GetPreferredSize(&width, &height);
245 
246 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), BSize(width, height));
247 }
248 
249 
250 void
251 BStatusBar::ResizeToPreferred()
252 {
253 	BView::ResizeToPreferred();
254 }
255 
256 
257 void
258 BStatusBar::FrameMoved(BPoint newPosition)
259 {
260 	BView::FrameMoved(newPosition);
261 }
262 
263 
264 void
265 BStatusBar::FrameResized(float newWidth, float newHeight)
266 {
267 	BView::FrameResized(newWidth, newHeight);
268 	Invalidate();
269 }
270 
271 
272 // #pragma mark -
273 
274 
275 void
276 BStatusBar::Draw(BRect updateRect)
277 {
278 	rgb_color backgroundColor;
279 	if (Parent())
280 		backgroundColor = Parent()->ViewColor();
281 	else
282 		backgroundColor = ui_color(B_PANEL_BACKGROUND_COLOR);
283 
284 	font_height fontHeight;
285 	GetFontHeight(&fontHeight);
286 	BRect barFrame = _BarFrame(&fontHeight);
287 	BRect outerFrame = barFrame.InsetByCopy(-2, -2);
288 
289 	BRegion background(updateRect);
290 	background.Exclude(outerFrame);
291 	FillRegion(&background, B_SOLID_LOW);
292 
293 	// Draw labels/texts
294 
295 	BRect rect = outerFrame;
296 	rect.top = 0;
297 	rect.bottom = outerFrame.top - 1;
298 
299 	if (updateRect.Intersects(rect)) {
300 		// update labels
301 		BString leftText;
302 		leftText << fLabel << fText;
303 
304 		BString rightText;
305 		rightText << fTrailingText << fTrailingLabel;
306 
307 		float baseLine = ceilf(fontHeight.ascent) + 1;
308 		fTextDivider = rect.right;
309 
310 		BFont font;
311 		GetFont(&font);
312 
313 		if (rightText.Length()) {
314 			font.TruncateString(&rightText, B_TRUNCATE_BEGINNING, rect.Width());
315 			fTextDivider -= StringWidth(rightText.String());
316 		}
317 
318 		if (leftText.Length()) {
319 			float width = max_c(0.0, fTextDivider - rect.left);
320 			font.TruncateString(&leftText, B_TRUNCATE_END, width);
321 		}
322 
323 		SetHighColor(ui_color(B_CONTROL_TEXT_COLOR));
324 
325 		if (leftText.Length())
326 			DrawString(leftText.String(), BPoint(rect.left, baseLine));
327 
328 		if (rightText.Length())
329 			DrawString(rightText.String(), BPoint(fTextDivider, baseLine));
330 
331 	}
332 
333 	// Draw bar
334 
335 	if (!updateRect.Intersects(outerFrame))
336 		return;
337 
338 	rect = outerFrame;
339 
340 	if (be_control_look != NULL) {
341 		be_control_look->DrawStatusBar(this, rect, updateRect,
342 			backgroundColor, fBarColor, _BarPosition(barFrame));
343 		return;
344 	}
345 
346 	// First bevel
347 	SetHighColor(tint_color(ui_color ( B_PANEL_BACKGROUND_COLOR ), B_DARKEN_1_TINT));
348 	StrokeLine(rect.LeftBottom(), rect.LeftTop());
349 	StrokeLine(rect.RightTop());
350 
351 	SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_LIGHTEN_2_TINT));
352 	StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom());
353 	StrokeLine(BPoint(rect.right, rect.top + 1));
354 
355 	rect.InsetBy(1, 1);
356 
357 	// Second bevel
358 	SetHighColor(tint_color(ui_color ( B_PANEL_BACKGROUND_COLOR ), B_DARKEN_4_TINT));
359 	StrokeLine(rect.LeftBottom(), rect.LeftTop());
360 	StrokeLine(rect.RightTop());
361 
362 	SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
363 	StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom());
364 	StrokeLine(BPoint(rect.right, rect.top + 1));
365 
366 	rect = barFrame;
367 	rect.right = _BarPosition(barFrame);
368 
369 	// draw bar itself
370 
371 	if (rect.right >= rect.left) {
372 		// Bevel
373 		SetHighColor(tint_color(fBarColor, B_LIGHTEN_2_TINT));
374 		StrokeLine(rect.LeftBottom(), rect.LeftTop());
375 		StrokeLine(rect.RightTop());
376 
377 		SetHighColor(tint_color(fBarColor, B_DARKEN_2_TINT));
378 		StrokeLine(BPoint(rect.left + 1, rect.bottom), rect.RightBottom());
379 		StrokeLine(BPoint(rect.right, rect.top + 1));
380 
381 		// filling
382 		SetHighColor(fBarColor);
383 		FillRect(rect.InsetByCopy(1, 1));
384 	}
385 
386 	if (rect.right < barFrame.right) {
387 		// empty space
388 		rect.left = rect.right + 1;
389 		rect.right = barFrame.right;
390 		SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_LIGHTEN_MAX_TINT));
391 		FillRect(rect);
392 	}
393 }
394 
395 
396 void
397 BStatusBar::MessageReceived(BMessage *message)
398 {
399 	switch(message->what) {
400 		case B_UPDATE_STATUS_BAR:
401 		{
402 			float delta;
403 			const char *text = NULL, *trailing_text = NULL;
404 
405 			message->FindFloat("delta", &delta);
406 			message->FindString("text", &text);
407 			message->FindString("trailing_text", &trailing_text);
408 
409 			Update(delta, text, trailing_text);
410 
411 			break;
412 		}
413 
414 		case B_RESET_STATUS_BAR:
415 		{
416 			const char *label = NULL, *trailing_label = NULL;
417 
418 			message->FindString("label", &label);
419 			message->FindString("trailing_label", &trailing_label);
420 
421 			Reset(label, trailing_label);
422 
423 			break;
424 		}
425 
426 		default:
427 			BView::MessageReceived(message);
428 			break;
429 	}
430 }
431 
432 
433 void
434 BStatusBar::MouseDown(BPoint point)
435 {
436 	BView::MouseDown(point);
437 }
438 
439 
440 void
441 BStatusBar::MouseUp(BPoint point)
442 {
443 	BView::MouseUp(point);
444 }
445 
446 
447 void
448 BStatusBar::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
449 {
450 	BView::MouseMoved(point, transit, message);
451 }
452 
453 
454 // #pragma mark -
455 
456 
457 void
458 BStatusBar::SetBarColor(rgb_color color)
459 {
460 	fBarColor = color;
461 
462 	Invalidate();
463 }
464 
465 
466 void
467 BStatusBar::SetBarHeight(float barHeight)
468 {
469 	float oldHeight = BarHeight();
470 
471 	fCustomBarHeight = true;
472 	fBarHeight = barHeight;
473 
474 	if (barHeight == oldHeight)
475 		return;
476 
477 	// resize so that the height fits
478 	float width, height;
479 	GetPreferredSize(&width, &height);
480 	ResizeTo(Bounds().Width(), height);
481 }
482 
483 
484 void
485 BStatusBar::SetText(const char* string)
486 {
487 	_SetTextData(fText, string, fLabel, false);
488 }
489 
490 
491 void
492 BStatusBar::SetTrailingText(const char* string)
493 {
494 	_SetTextData(fTrailingText, string, fTrailingLabel, true);
495 }
496 
497 
498 void
499 BStatusBar::SetMaxValue(float max)
500 {
501 	// R5 and/or Zeta's SetMaxValue does not trigger an invalidate here.
502 	// this is probably not ideal behavior, but it does break apps in some cases
503 	// as observed with SpaceMonitor.
504 	// TODO: revisit this when we break binary compatibility
505 	fMax = max;
506 }
507 
508 
509 void
510 BStatusBar::Update(float delta, const char* text, const char* trailingText)
511 {
512 	// If any of these are NULL, the existing text remains (BeBook)
513 	if (text == NULL)
514 		text = fText.String();
515 	if (trailingText == NULL)
516 		trailingText = fTrailingText.String();
517 	BStatusBar::SetTo(fCurrent + delta, text, trailingText);
518 }
519 
520 
521 void
522 BStatusBar::Reset(const char *label, const char *trailingLabel)
523 {
524 	// Reset replaces the label and trailing label with copies of the
525 	// strings passed as arguments. If either argument is NULL, the
526 	// label or trailing label will be deleted and erased.
527 	fLabel = label ? label : "";
528 	fTrailingLabel = trailingLabel ? trailingLabel : "";
529 
530 	// Reset deletes and erases any text or trailing text
531 	fText = "";
532 	fTrailingText = "";
533 
534 	fCurrent = 0;
535 	fMax = 100;
536 
537 	Invalidate();
538 }
539 
540 
541 void
542 BStatusBar::SetTo(float value, const char* text, const char* trailingText)
543 {
544 	SetText(text);
545 	SetTrailingText(trailingText);
546 
547 	if (value > fMax)
548 		value = fMax;
549 	else if (value < 0)
550 		value = 0;
551 	if (value == fCurrent)
552 		return;
553 
554 	BRect barFrame = _BarFrame();
555 	float oldPosition = _BarPosition(barFrame);
556 
557 	fCurrent = value;
558 
559 	float newPosition = _BarPosition(barFrame);
560 	if (oldPosition == newPosition)
561 		return;
562 
563 	// update only the part of the status bar with actual changes
564 	BRect update = barFrame;
565 	if (oldPosition < newPosition) {
566 		update.left = floorf(max_c(oldPosition - 1, update.left));
567 		update.right = ceilf(newPosition);
568 	} else {
569 		update.left = floorf(max_c(newPosition - 1, update.left));
570 		update.right = ceilf(oldPosition);
571 	}
572 
573 	// TODO: Ask the BControlLook in the first place about dirty rect.
574 	if (be_control_look)
575 		update.InsetBy(-1, -1);
576 
577 	Invalidate(update);
578 }
579 
580 
581 float
582 BStatusBar::CurrentValue() const
583 {
584 	return fCurrent;
585 }
586 
587 
588 float
589 BStatusBar::MaxValue() const
590 {
591 	return fMax;
592 }
593 
594 
595 rgb_color
596 BStatusBar::BarColor() const
597 {
598 	return fBarColor;
599 }
600 
601 
602 float
603 BStatusBar::BarHeight() const
604 {
605 	if (!fCustomBarHeight && fBarHeight == -1) {
606 		// the default bar height is as height as the label
607 		font_height fontHeight;
608 		GetFontHeight(&fontHeight);
609 		const_cast<BStatusBar *>(this)->fBarHeight = fontHeight.ascent
610 			+ fontHeight.descent + 5;
611 	}
612 
613 	return ceilf(fBarHeight);
614 }
615 
616 
617 const char *
618 BStatusBar::Text() const
619 {
620 	return fText.String();
621 }
622 
623 
624 const char *
625 BStatusBar::TrailingText() const
626 {
627 	return fTrailingText.String();
628 }
629 
630 
631 const char *
632 BStatusBar::Label() const
633 {
634 	return fLabel.String();
635 }
636 
637 
638 const char *
639 BStatusBar::TrailingLabel() const
640 {
641 	return fTrailingLabel.String();
642 }
643 
644 
645 // #pragma mark -
646 
647 
648 BHandler *
649 BStatusBar::ResolveSpecifier(BMessage* message, int32 index,
650 	BMessage* specifier, int32 what, const char *property)
651 {
652 	return BView::ResolveSpecifier(message, index, specifier, what, property);
653 }
654 
655 
656 status_t
657 BStatusBar::GetSupportedSuites(BMessage* data)
658 {
659 	return BView::GetSupportedSuites(data);
660 }
661 
662 
663 status_t
664 BStatusBar::Perform(perform_code code, void* _data)
665 {
666 	switch (code) {
667 		case PERFORM_CODE_MIN_SIZE:
668 			((perform_data_min_size*)_data)->return_value
669 				= BStatusBar::MinSize();
670 			return B_OK;
671 		case PERFORM_CODE_MAX_SIZE:
672 			((perform_data_max_size*)_data)->return_value
673 				= BStatusBar::MaxSize();
674 			return B_OK;
675 		case PERFORM_CODE_PREFERRED_SIZE:
676 			((perform_data_preferred_size*)_data)->return_value
677 				= BStatusBar::PreferredSize();
678 			return B_OK;
679 		case PERFORM_CODE_LAYOUT_ALIGNMENT:
680 			((perform_data_layout_alignment*)_data)->return_value
681 				= BStatusBar::LayoutAlignment();
682 			return B_OK;
683 		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
684 			((perform_data_has_height_for_width*)_data)->return_value
685 				= BStatusBar::HasHeightForWidth();
686 			return B_OK;
687 		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
688 		{
689 			perform_data_get_height_for_width* data
690 				= (perform_data_get_height_for_width*)_data;
691 			BStatusBar::GetHeightForWidth(data->width, &data->min, &data->max,
692 				&data->preferred);
693 			return B_OK;
694 		}
695 		case PERFORM_CODE_SET_LAYOUT:
696 		{
697 			perform_data_set_layout* data = (perform_data_set_layout*)_data;
698 			BStatusBar::SetLayout(data->layout);
699 			return B_OK;
700 		}
701 		case PERFORM_CODE_INVALIDATE_LAYOUT:
702 		{
703 			perform_data_invalidate_layout* data
704 				= (perform_data_invalidate_layout*)_data;
705 			BStatusBar::InvalidateLayout(data->descendants);
706 			return B_OK;
707 		}
708 		case PERFORM_CODE_DO_LAYOUT:
709 		{
710 			BStatusBar::DoLayout();
711 			return B_OK;
712 		}
713 	}
714 
715 	return BView::Perform(code, _data);
716 }
717 
718 
719 // #pragma mark -
720 
721 
722 extern "C" void
723 _ReservedStatusBar1__10BStatusBar(BStatusBar* self, float value,
724 	const char* text, const char* trailingText)
725 {
726 	self->BStatusBar::SetTo(value, text, trailingText);
727 }
728 
729 
730 void BStatusBar::_ReservedStatusBar2() {}
731 void BStatusBar::_ReservedStatusBar3() {}
732 void BStatusBar::_ReservedStatusBar4() {}
733 
734 
735 BStatusBar &
736 BStatusBar::operator=(const BStatusBar& other)
737 {
738 	return *this;
739 }
740 
741 
742 // #pragma mark -
743 
744 
745 void
746 BStatusBar::_InitObject()
747 {
748 	fMax = 100.0;
749 	fCurrent = 0.0;
750 
751 	fBarHeight = -1.0;
752 	fTextDivider = Bounds().Width();
753 
754 	fBarColor = kDefaultBarColor;
755 	fCustomBarHeight = false;
756 
757 	SetFlags(Flags() | B_FRAME_EVENTS);
758 }
759 
760 
761 void
762 BStatusBar::_SetTextData(BString& text, const char* source,
763 	const BString& combineWith, bool rightAligned)
764 {
765 	if (source == NULL)
766 		source = "";
767 
768 	// If there were no changes, we don't have to do anything
769 	if (text == source)
770 		return;
771 
772 	float oldWidth;
773 	if (rightAligned)
774 		oldWidth = Bounds().right - fTextDivider;
775 	else
776 		oldWidth = fTextDivider;
777 
778 	text = source;
779 
780 	BString newString;
781 	if (rightAligned)
782 		newString << text << combineWith;
783 	else
784 		newString << combineWith << text;
785 
786 	float newWidth = ceilf(StringWidth(newString.String()));
787 		// might still be smaller in Draw(), but we use it
788 		// only for the invalidation rect here
789 
790 	// determine which part of the view needs an update
791 	float invalidateWidth = max_c(newWidth, oldWidth);
792 
793 	float position = 0.0;
794 	if (rightAligned)
795 		position = Bounds().right - invalidateWidth;
796 
797 	font_height fontHeight;
798 	GetFontHeight(&fontHeight);
799 
800 //	Invalidate(BRect(position, 0, position + invalidateWidth,
801 //		ceilf(fontHeight.ascent) + ceilf(fontHeight.descent)));
802 // TODO: redrawing the entire area takes care of the edge case
803 // where the left side string changes because of truncation and
804 // part of it needs to be redrawn as well.
805 	Invalidate(BRect(0, 0, Bounds().right,
806 		ceilf(fontHeight.ascent) + ceilf(fontHeight.descent)));
807 }
808 
809 
810 /*!
811 	Returns the inner bar frame without the surrounding bevel.
812 */
813 BRect
814 BStatusBar::_BarFrame(const font_height* fontHeight) const
815 {
816 	float top;
817 	if (fontHeight == NULL) {
818 		font_height height;
819 		GetFontHeight(&height);
820 		top = ceilf(height.ascent + height.descent) + 6;
821 	} else
822 		top = ceilf(fontHeight->ascent + fontHeight->descent) + 6;
823 
824 	return BRect(2, top, Bounds().right - 2, top + BarHeight() - 4);
825 }
826 
827 
828 float
829 BStatusBar::_BarPosition(const BRect& barFrame) const
830 {
831 	if (fCurrent == 0)
832 		return barFrame.left - 1;
833 
834 	return roundf(barFrame.left - 1
835 		+ (fCurrent * (barFrame.Width() + 3) / fMax));
836 }
837 
838