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