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