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