Box.cpp revision 2f86ba45
1/*
2 * Copyright (c) 2001-2007, Haiku, Inc.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Marc Flerackers (mflerackers@androme.be)
7 *		Stephan A��mus <superstippi@gmx.de>
8 *		DarkWyrm <bpmagic@columbus.rr.com>
9 *		Axel D��rfler, axeld@pinc-software.de
10 */
11
12
13#include <Box.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
28struct BBox::LayoutData {
29	LayoutData()
30		: valid(false)
31	{
32	}
33
34	BRect	label_box;		// label box (label string or label view); in case
35							// of a label string not including descent
36	BRect	insets;			// insets induced by border and label
37	BSize	min;
38	BSize	max;
39	BSize	preferred;
40	bool	valid;			// validity the other fields
41};
42
43
44BBox::BBox(BRect frame, const char *name, uint32 resizingMode, uint32 flags,
45		border_style border)
46	: BView(frame, name, resizingMode, flags  | B_WILL_DRAW | B_FRAME_EVENTS),
47	  fStyle(border)
48{
49	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
50	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
51
52	_InitObject();
53}
54
55
56BBox::BBox(const char* name, uint32 flags, border_style border, BView* child)
57	: BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS),
58	  fStyle(border)
59{
60	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
61	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
62
63	_InitObject();
64
65	if (child)
66		AddChild(child);
67}
68
69
70BBox::BBox(border_style border, BView* child)
71	: BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP),
72	  fStyle(border)
73{
74	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
75	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
76
77	_InitObject();
78
79	if (child)
80		AddChild(child);
81}
82
83
84BBox::BBox(BMessage *archive)
85	: BView(archive),
86	  fStyle(B_FANCY_BORDER)
87{
88	_InitObject(archive);
89}
90
91
92BBox::~BBox()
93{
94	_ClearLabel();
95
96	delete fLayoutData;
97}
98
99
100BArchivable *
101BBox::Instantiate(BMessage *archive)
102{
103	if (validate_instantiation(archive, "BBox"))
104		return new BBox(archive);
105
106	return NULL;
107}
108
109
110status_t
111BBox::Archive(BMessage *archive, bool deep) const
112{
113	status_t ret = BView::Archive(archive, deep);
114
115	if (fLabel && ret == B_OK)
116		 ret = archive->AddString("_label", fLabel);
117
118	if (fLabelView && ret == B_OK)
119		 ret = archive->AddBool("_lblview", true);
120
121	if (fStyle != B_FANCY_BORDER && ret == B_OK)
122		ret = archive->AddInt32("_style", fStyle);
123
124	return ret;
125}
126
127
128void
129BBox::SetBorder(border_style border)
130{
131	if (border == fStyle)
132		return;
133
134	fStyle = border;
135
136	InvalidateLayout();
137
138	if (Window() != NULL && LockLooper()) {
139		Invalidate();
140		UnlockLooper();
141	}
142}
143
144
145border_style
146BBox::Border() const
147{
148	return fStyle;
149}
150
151
152//! This function is not part of the R5 API and is not yet finalized yet
153float
154BBox::TopBorderOffset()
155{
156	_ValidateLayoutData();
157
158	if (fLabel != NULL || fLabelView != NULL)
159		return fLayoutData->label_box.Height() / 2;
160
161	return 0;
162}
163
164
165//! This function is not part of the R5 API and is not yet finalized yet
166BRect
167BBox::InnerFrame()
168{
169	_ValidateLayoutData();
170
171	BRect frame(Bounds());
172	frame.left += fLayoutData->insets.left;
173	frame.top += fLayoutData->insets.top;
174	frame.right -= fLayoutData->insets.right;
175	frame.bottom -= fLayoutData->insets.bottom;
176
177	return frame;
178}
179
180
181void
182BBox::SetLabel(const char *string)
183{
184	_ClearLabel();
185
186	if (string)
187		fLabel = strdup(string);
188
189	InvalidateLayout();
190
191	if (Window())
192		Invalidate();
193}
194
195
196status_t
197BBox::SetLabel(BView *viewLabel)
198{
199	_ClearLabel();
200
201	if (viewLabel) {
202		fLabelView = viewLabel;
203		fLabelView->MoveTo(10.0f, 0.0f);
204		AddChild(fLabelView, ChildAt(0));
205	}
206
207	InvalidateLayout();
208
209	if (Window())
210		Invalidate();
211
212	return B_OK;
213}
214
215
216const char *
217BBox::Label() const
218{
219	return fLabel;
220}
221
222
223BView *
224BBox::LabelView() const
225{
226	return fLabelView;
227}
228
229
230void
231BBox::Draw(BRect updateRect)
232{
233	_ValidateLayoutData();
234
235	PushState();
236
237	BRect labelBox = BRect(0, 0, 0, 0);
238	if (fLabel != NULL) {
239		labelBox = fLayoutData->label_box;
240		BRegion update(updateRect);
241		update.Exclude(labelBox);
242
243		ConstrainClippingRegion(&update);
244	} else if (fLabelView != NULL)
245		labelBox = fLabelView->Bounds();
246
247	switch (fStyle) {
248		case B_FANCY_BORDER:
249			_DrawFancy(labelBox);
250			break;
251
252		case B_PLAIN_BORDER:
253			_DrawPlain(labelBox);
254			break;
255
256		default:
257			break;
258	}
259
260	if (fLabel) {
261		ConstrainClippingRegion(NULL);
262
263		font_height fontHeight;
264		GetFontHeight(&fontHeight);
265
266		SetHighColor(0, 0, 0);
267		DrawString(fLabel, BPoint(10.0f, ceilf(fontHeight.ascent)));
268	}
269
270	PopState();
271}
272
273
274void
275BBox::AttachedToWindow()
276{
277	BView* parent = Parent();
278	if (parent != NULL) {
279		// inherit the color from parent
280		rgb_color color = parent->ViewColor();
281		if (color == B_TRANSPARENT_COLOR)
282			color = ui_color(B_PANEL_BACKGROUND_COLOR);
283
284		SetViewColor(color);
285		SetLowColor(color);
286	}
287
288	// The box could have been resized in the mean time
289	fBounds = Bounds();
290}
291
292
293void
294BBox::DetachedFromWindow()
295{
296	BView::DetachedFromWindow();
297}
298
299
300void
301BBox::AllAttached()
302{
303	BView::AllAttached();
304}
305
306
307void
308BBox::AllDetached()
309{
310	BView::AllDetached();
311}
312
313
314void
315BBox::FrameResized(float width, float height)
316{
317	BRect bounds(Bounds());
318
319	// invalidate the regions that the app_server did not
320	// (for removing the previous or drawing the new border)
321	if (fStyle != B_NO_BORDER) {
322
323		int32 borderSize = fStyle == B_PLAIN_BORDER ? 0 : 1;
324
325		BRect invalid(bounds);
326		if (fBounds.right < bounds.right) {
327			// enlarging
328			invalid.left = fBounds.right - borderSize;
329			invalid.right = fBounds.right;
330
331			Invalidate(invalid);
332		} else if (fBounds.right > bounds.right) {
333			// shrinking
334			invalid.left = bounds.right - borderSize;
335
336			Invalidate(invalid);
337		}
338
339		invalid = bounds;
340		if (fBounds.bottom < bounds.bottom) {
341			// enlarging
342			invalid.top = fBounds.bottom - borderSize;
343			invalid.bottom = fBounds.bottom;
344
345			Invalidate(invalid);
346		} else if (fBounds.bottom > bounds.bottom) {
347			// shrinking
348			invalid.top = bounds.bottom - borderSize;
349
350			Invalidate(invalid);
351		}
352	}
353
354	fBounds.right = bounds.right;
355	fBounds.bottom = bounds.bottom;
356}
357
358
359void
360BBox::MessageReceived(BMessage *message)
361{
362	BView::MessageReceived(message);
363}
364
365
366void
367BBox::MouseDown(BPoint point)
368{
369	BView::MouseDown(point);
370}
371
372
373void
374BBox::MouseUp(BPoint point)
375{
376	BView::MouseUp(point);
377}
378
379
380void
381BBox::WindowActivated(bool active)
382{
383	BView::WindowActivated(active);
384}
385
386
387void
388BBox::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
389{
390	BView::MouseMoved(point, transit, message);
391}
392
393
394void
395BBox::FrameMoved(BPoint newLocation)
396{
397	BView::FrameMoved(newLocation);
398}
399
400
401BHandler *
402BBox::ResolveSpecifier(BMessage *message, int32 index,
403	BMessage *specifier, int32 what,
404	const char *property)
405{
406	return BView::ResolveSpecifier(message, index, specifier, what, property);
407}
408
409
410void
411BBox::ResizeToPreferred()
412{
413	float width, height;
414	GetPreferredSize(&width, &height);
415
416	// make sure the box don't get smaller than it already is
417	if (width < Bounds().Width())
418		width = Bounds().Width();
419	if (height < Bounds().Height())
420		height = Bounds().Height();
421
422	BView::ResizeTo(width, height);
423}
424
425
426void
427BBox::GetPreferredSize(float *_width, float *_height)
428{
429	_ValidateLayoutData();
430
431	if (_width)
432		*_width = fLayoutData->preferred.width;
433	if (_height)
434		*_height = fLayoutData->preferred.height;
435}
436
437
438void
439BBox::MakeFocus(bool focused)
440{
441	BView::MakeFocus(focused);
442}
443
444
445status_t
446BBox::GetSupportedSuites(BMessage *message)
447{
448	return BView::GetSupportedSuites(message);
449}
450
451
452status_t
453BBox::Perform(perform_code code, void* _data)
454{
455	switch (code) {
456		case PERFORM_CODE_MIN_SIZE:
457			((perform_data_min_size*)_data)->return_value
458				= BBox::MinSize();
459			return B_OK;
460		case PERFORM_CODE_MAX_SIZE:
461			((perform_data_max_size*)_data)->return_value
462				= BBox::MaxSize();
463			return B_OK;
464		case PERFORM_CODE_PREFERRED_SIZE:
465			((perform_data_preferred_size*)_data)->return_value
466				= BBox::PreferredSize();
467			return B_OK;
468		case PERFORM_CODE_LAYOUT_ALIGNMENT:
469			((perform_data_layout_alignment*)_data)->return_value
470				= BBox::LayoutAlignment();
471			return B_OK;
472		case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
473			((perform_data_has_height_for_width*)_data)->return_value
474				= BBox::HasHeightForWidth();
475			return B_OK;
476		case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
477		{
478			perform_data_get_height_for_width* data
479				= (perform_data_get_height_for_width*)_data;
480			BBox::GetHeightForWidth(data->width, &data->min, &data->max,
481				&data->preferred);
482			return B_OK;
483}
484		case PERFORM_CODE_SET_LAYOUT:
485		{
486			perform_data_set_layout* data = (perform_data_set_layout*)_data;
487			BBox::SetLayout(data->layout);
488			return B_OK;
489		}
490		case PERFORM_CODE_INVALIDATE_LAYOUT:
491		{
492			perform_data_invalidate_layout* data
493				= (perform_data_invalidate_layout*)_data;
494			BBox::InvalidateLayout(data->descendants);
495			return B_OK;
496		}
497		case PERFORM_CODE_DO_LAYOUT:
498		{
499			BBox::DoLayout();
500			return B_OK;
501		}
502	}
503
504	return BView::Perform(code, _data);
505}
506
507
508BSize
509BBox::MinSize()
510{
511	_ValidateLayoutData();
512
513	BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min);
514	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
515}
516
517
518BSize
519BBox::MaxSize()
520{
521	_ValidateLayoutData();
522
523	BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max);
524	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
525}
526
527
528BSize
529BBox::PreferredSize()
530{
531	_ValidateLayoutData();
532
533	BSize size = (GetLayout() ? GetLayout()->PreferredSize()
534		: fLayoutData->preferred);
535	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
536}
537
538
539void
540BBox::InvalidateLayout(bool descendants)
541{
542	fLayoutData->valid = false;
543	BView::InvalidateLayout(descendants);
544}
545
546
547void
548BBox::DoLayout()
549{
550	// Bail out, if we shan't do layout.
551	if (!(Flags() & B_SUPPORTS_LAYOUT))
552		return;
553
554	// If the user set a layout, we let the base class version call its
555	// hook.
556	if (GetLayout()) {
557		BView::DoLayout();
558		return;
559	}
560
561	_ValidateLayoutData();
562
563	// layout the label view
564	if (fLabelView) {
565		fLabelView->MoveTo(fLayoutData->label_box.LeftTop());
566		fLabelView->ResizeTo(fLayoutData->label_box.Size());
567	}
568
569	// layout the child
570	if (BView* child = _Child()) {
571		BRect frame(Bounds());
572		frame.left += fLayoutData->insets.left;
573		frame.top += fLayoutData->insets.top;
574		frame.right -= fLayoutData->insets.right;
575		frame.bottom -= fLayoutData->insets.bottom;
576
577		BLayoutUtils::AlignInFrame(child, frame);
578	}
579}
580
581
582void BBox::_ReservedBox1() {}
583void BBox::_ReservedBox2() {}
584
585
586BBox &
587BBox::operator=(const BBox &)
588{
589	return *this;
590}
591
592
593void
594BBox::_InitObject(BMessage* archive)
595{
596	fBounds = Bounds();
597
598	fLabel = NULL;
599	fLabelView = NULL;
600	fLayoutData = new LayoutData;
601
602	int32 flags = 0;
603
604	BFont font(be_bold_font);
605
606	if (!archive || !archive->HasString("_fname"))
607		flags = B_FONT_FAMILY_AND_STYLE;
608
609	if (!archive || !archive->HasFloat("_fflt"))
610		flags |= B_FONT_SIZE;
611
612	if (flags != 0)
613		SetFont(&font, flags);
614
615	if (archive != NULL) {
616		const char *string;
617		if (archive->FindString("_label", &string) == B_OK)
618			SetLabel(string);
619
620		bool fancy;
621		int32 style;
622
623		if (archive->FindBool("_style", &fancy) == B_OK)
624			fStyle = fancy ? B_FANCY_BORDER : B_PLAIN_BORDER;
625		else if (archive->FindInt32("_style", &style) == B_OK)
626			fStyle = (border_style)style;
627
628		bool hasLabelView;
629		if (archive->FindBool("_lblview", &hasLabelView) == B_OK)
630			fLabelView = ChildAt(0);
631	}
632}
633
634
635void
636BBox::_DrawPlain(BRect labelBox)
637{
638	BRect rect = Bounds();
639	rect.top += TopBorderOffset();
640
641	float lightTint;
642	float shadowTint;
643	if (be_control_look != NULL) {
644		lightTint = B_LIGHTEN_1_TINT;
645		shadowTint = B_DARKEN_1_TINT;
646	} else {
647		lightTint = B_LIGHTEN_MAX_TINT;
648		shadowTint = B_DARKEN_3_TINT;
649	}
650
651	if (rect.Height() == 0.0 || rect.Width() == 0.0) {
652		// used as separator
653		rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT);
654
655		SetHighColor(shadow);
656		StrokeLine(rect.LeftTop(),rect.RightBottom());
657	} else {
658		// used as box
659		rgb_color light = tint_color(ViewColor(), lightTint);
660		rgb_color shadow = tint_color(ViewColor(), shadowTint);
661
662		BeginLineArray(4);
663			AddLine(BPoint(rect.left, rect.bottom),
664					BPoint(rect.left, rect.top), light);
665			AddLine(BPoint(rect.left + 1.0f, rect.top),
666					BPoint(rect.right, rect.top), light);
667			AddLine(BPoint(rect.left + 1.0f, rect.bottom),
668					BPoint(rect.right, rect.bottom), shadow);
669			AddLine(BPoint(rect.right, rect.bottom - 1.0f),
670					BPoint(rect.right, rect.top + 1.0f), shadow);
671		EndLineArray();
672	}
673}
674
675
676void
677BBox::_DrawFancy(BRect labelBox)
678{
679	BRect rect = Bounds();
680	rect.top += TopBorderOffset();
681
682	if (be_control_look != NULL) {
683		rgb_color base = ViewColor();
684		if (rect.Height() == 1.0) {
685			// used as horizontal separator
686			be_control_look->DrawGroupFrame(this, rect, rect, base,
687				BControlLook::B_TOP_BORDER);
688		} else if (rect.Width() == 1.0) {
689			// used as vertical separator
690			be_control_look->DrawGroupFrame(this, rect, rect, base,
691				BControlLook::B_LEFT_BORDER);
692		} else {
693			// used as box
694			be_control_look->DrawGroupFrame(this, rect, rect, base);
695		}
696		return;
697	}
698
699	rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT);
700	rgb_color shadow = tint_color(ViewColor(), B_DARKEN_3_TINT);
701
702	if (rect.Height() == 1.0) {
703		// used as horizontal separator
704		BeginLineArray(2);
705			AddLine(BPoint(rect.left, rect.top),
706					BPoint(rect.right, rect.top), shadow);
707			AddLine(BPoint(rect.left, rect.bottom),
708					BPoint(rect.right, rect.bottom), light);
709		EndLineArray();
710	} else if (rect.Width() == 1.0) {
711		// used as vertical separator
712		BeginLineArray(2);
713			AddLine(BPoint(rect.left, rect.top),
714					BPoint(rect.left, rect.bottom), shadow);
715			AddLine(BPoint(rect.right, rect.top),
716					BPoint(rect.right, rect.bottom), light);
717		EndLineArray();
718	} else {
719		// used as box
720		BeginLineArray(8);
721			AddLine(BPoint(rect.left, rect.bottom - 1.0),
722					BPoint(rect.left, rect.top), shadow);
723			AddLine(BPoint(rect.left + 1.0, rect.top),
724					BPoint(rect.right - 1.0, rect.top), shadow);
725			AddLine(BPoint(rect.left, rect.bottom),
726					BPoint(rect.right, rect.bottom), light);
727			AddLine(BPoint(rect.right, rect.bottom - 1.0),
728					BPoint(rect.right, rect.top), light);
729
730			rect.InsetBy(1.0, 1.0);
731
732			AddLine(BPoint(rect.left, rect.bottom - 1.0),
733					BPoint(rect.left, rect.top), light);
734			AddLine(BPoint(rect.left + 1.0, rect.top),
735					BPoint(rect.right - 1.0, rect.top), light);
736			AddLine(BPoint(rect.left, rect.bottom),
737					BPoint(rect.right, rect.bottom), shadow);
738			AddLine(BPoint(rect.right, rect.bottom - 1.0),
739					BPoint(rect.right, rect.top), shadow);
740		EndLineArray();
741	}
742}
743
744
745void
746BBox::_ClearLabel()
747{
748	fBounds.top = 0;
749
750	if (fLabel) {
751		free(fLabel);
752		fLabel = NULL;
753	} else if (fLabelView) {
754		fLabelView->RemoveSelf();
755		delete fLabelView;
756		fLabelView = NULL;
757	}
758}
759
760
761BView*
762BBox::_Child() const
763{
764	for (int32 i = 0; BView* view = ChildAt(i); i++) {
765		if (view != fLabelView)
766			return view;
767	}
768
769	return NULL;
770}
771
772
773void
774BBox::_ValidateLayoutData()
775{
776	if (fLayoutData->valid)
777		return;
778
779	// compute the label box, width and height
780	bool label = true;
781	float labelHeight = 0;	// height of the label (pixel count)
782	if (fLabel) {
783		// leave 6 pixels of the frame, and have a gap of 4 pixels between
784		// the frame and the text on either side
785		font_height fontHeight;
786		GetFontHeight(&fontHeight);
787		fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel),
788			ceilf(fontHeight.ascent));
789		labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1;
790	} else if (fLabelView) {
791		// the label view is placed at (0, 10) at its preferred size
792		BSize size = fLabelView->PreferredSize();
793		fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height);
794		labelHeight = size.height + 1;
795	} else {
796		label = false;
797	}
798
799	// border
800	switch (fStyle) {
801		case B_PLAIN_BORDER:
802			fLayoutData->insets.Set(1, 1, 1, 1);
803			break;
804		case B_FANCY_BORDER:
805			fLayoutData->insets.Set(2, 2, 2, 2);
806			break;
807		case B_NO_BORDER:
808		default:
809			fLayoutData->insets.Set(0, 0, 0, 0);
810			break;
811	}
812
813	// if there's a label, the top inset will be dictated by the label
814	if (label && labelHeight > fLayoutData->insets.top)
815		fLayoutData->insets.top = labelHeight;
816
817	// total number of pixel the border adds
818	float addWidth = fLayoutData->insets.left + fLayoutData->insets.right;
819	float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom;
820
821	// compute the minimal width induced by the label
822	float minWidth;
823	if (label)
824		minWidth = fLayoutData->label_box.right + fLayoutData->insets.right;
825	else
826		minWidth = addWidth - 1;
827
828	// finally consider the child constraints, if we shall support layout
829	BView* child = _Child();
830	if (child && (Flags() & B_SUPPORTS_LAYOUT)) {
831		BSize min = child->MinSize();
832		BSize max = child->MaxSize();
833		BSize preferred = child->PreferredSize();
834
835		min.width += addWidth;
836		min.height += addHeight;
837		preferred.width += addWidth;
838		preferred.height += addHeight;
839		max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1);
840		max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1);
841
842		if (min.width < minWidth)
843			min.width = minWidth;
844		BLayoutUtils::FixSizeConstraints(min, max, preferred);
845
846		fLayoutData->min = min;
847		fLayoutData->max = max;
848		fLayoutData->preferred = preferred;
849	} else {
850		fLayoutData->min.Set(minWidth, addHeight - 1);
851		fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
852		fLayoutData->preferred = fLayoutData->min;
853	}
854
855	fLayoutData->valid = true;
856}
857
858