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
27enum internalFlags {
28	kCustomBarColor = 1
29};
30
31
32BStatusBar::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
43BStatusBar::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
55BStatusBar::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
86BStatusBar::~BStatusBar()
87{
88}
89
90
91BArchivable *
92BStatusBar::Instantiate(BMessage *archive)
93{
94	if (validate_instantiation(archive, "BStatusBar"))
95		return new BStatusBar(archive);
96
97	return NULL;
98}
99
100
101status_t
102BStatusBar::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
136void
137BStatusBar::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
155void
156BStatusBar::DetachedFromWindow()
157{
158	BView::DetachedFromWindow();
159}
160
161
162void
163BStatusBar::AllAttached()
164{
165	BView::AllAttached();
166}
167
168
169void
170BStatusBar::AllDetached()
171{
172	BView::AllDetached();
173}
174
175
176// #pragma mark -
177
178
179void
180BStatusBar::WindowActivated(bool state)
181{
182	BView::WindowActivated(state);
183}
184
185
186void
187BStatusBar::MakeFocus(bool state)
188{
189	BView::MakeFocus(state);
190}
191
192
193// #pragma mark -
194
195
196void
197BStatusBar::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
221BSize
222BStatusBar::MinSize()
223{
224	float width, height;
225	GetPreferredSize(&width, &height);
226
227	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height));
228}
229
230
231BSize
232BStatusBar::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
242BSize
243BStatusBar::PreferredSize()
244{
245	float width, height;
246	GetPreferredSize(&width, &height);
247
248	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
249		BSize(width, height));
250}
251
252
253void
254BStatusBar::ResizeToPreferred()
255{
256	BView::ResizeToPreferred();
257}
258
259
260void
261BStatusBar::FrameMoved(BPoint newPosition)
262{
263	BView::FrameMoved(newPosition);
264}
265
266
267void
268BStatusBar::FrameResized(float newWidth, float newHeight)
269{
270	BView::FrameResized(newWidth, newHeight);
271	Invalidate();
272}
273
274
275// #pragma mark -
276
277
278void
279BStatusBar::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
353void
354BStatusBar::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
401void
402BStatusBar::MouseDown(BPoint point)
403{
404	BView::MouseDown(point);
405}
406
407
408void
409BStatusBar::MouseUp(BPoint point)
410{
411	BView::MouseUp(point);
412}
413
414
415void
416BStatusBar::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
417{
418	BView::MouseMoved(point, transit, message);
419}
420
421
422// #pragma mark -
423
424
425void
426BStatusBar::SetBarColor(rgb_color color)
427{
428	fInternalFlags |= kCustomBarColor;
429	fBarColor = color;
430
431	Invalidate();
432}
433
434
435void
436BStatusBar::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
457void
458BStatusBar::SetText(const char* string)
459{
460	_SetTextData(fText, string, fLabel, false);
461}
462
463
464void
465BStatusBar::SetTrailingText(const char* string)
466{
467	_SetTextData(fTrailingText, string, fTrailingLabel, true);
468}
469
470
471void
472BStatusBar::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
482void
483BStatusBar::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
494void
495BStatusBar::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
514void
515BStatusBar::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
553float
554BStatusBar::CurrentValue() const
555{
556	return fCurrent;
557}
558
559
560float
561BStatusBar::MaxValue() const
562{
563	return fMax;
564}
565
566
567rgb_color
568BStatusBar::BarColor() const
569{
570	return fBarColor;
571}
572
573
574float
575BStatusBar::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
589const char *
590BStatusBar::Text() const
591{
592	return fText.String();
593}
594
595
596const char *
597BStatusBar::TrailingText() const
598{
599	return fTrailingText.String();
600}
601
602
603const char *
604BStatusBar::Label() const
605{
606	return fLabel.String();
607}
608
609
610const char *
611BStatusBar::TrailingLabel() const
612{
613	return fTrailingLabel.String();
614}
615
616
617// #pragma mark -
618
619
620BHandler *
621BStatusBar::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
628status_t
629BStatusBar::GetSupportedSuites(BMessage* data)
630{
631	return BView::GetSupportedSuites(data);
632}
633
634
635status_t
636BStatusBar::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
694extern "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
702void BStatusBar::_ReservedStatusBar2() {}
703void BStatusBar::_ReservedStatusBar3() {}
704void BStatusBar::_ReservedStatusBar4() {}
705
706
707BStatusBar &
708BStatusBar::operator=(const BStatusBar& other)
709{
710	return *this;
711}
712
713
714// #pragma mark -
715
716
717void
718BStatusBar::_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
733void
734BStatusBar::_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*/
772BRect
773BStatusBar::_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
789float
790BStatusBar::_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
800bool
801BStatusBar::_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