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