Box.cpp revision c944d11f
1/*
2 * Copyright (c) 2001-2009, 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		// TODO: this must be made part of the be_control_look stuff!
323		int32 borderSize = fStyle == B_PLAIN_BORDER ? 0 : 2;
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	bool layouted = GetLayout() ? true : false;
555
556	// If the user set a layout, let the base class version call its
557	// hook. In case when we have BView as a label, remove it from child list
558	// so it won't be layouted with the rest of views and add it again
559	// after that.
560	if (layouted) {
561		if (fLabelView)
562			RemoveChild(fLabelView);
563
564		BView::DoLayout();
565
566		if (fLabelView)
567			AddChild(fLabelView, ChildAt(0));
568		else
569			return;
570	}
571
572	_ValidateLayoutData();
573
574	// Even if the user set a layout, restore label view to it's
575	// desired position.
576
577	// layout the label view
578	if (fLabelView) {
579		fLabelView->MoveTo(fLayoutData->label_box.LeftTop());
580		fLabelView->ResizeTo(fLayoutData->label_box.Size());
581	}
582
583	// If we have layout return here and do not layout the child
584	if (layouted)
585		return;
586
587	// layout the child
588	if (BView* child = _Child()) {
589		BRect frame(Bounds());
590		frame.left += fLayoutData->insets.left;
591		frame.top += fLayoutData->insets.top;
592		frame.right -= fLayoutData->insets.right;
593		frame.bottom -= fLayoutData->insets.bottom;
594
595		BLayoutUtils::AlignInFrame(child, frame);
596	}
597}
598
599
600void BBox::_ReservedBox1() {}
601void BBox::_ReservedBox2() {}
602
603
604BBox &
605BBox::operator=(const BBox &)
606{
607	return *this;
608}
609
610
611void
612BBox::_InitObject(BMessage* archive)
613{
614	fBounds = Bounds();
615
616	fLabel = NULL;
617	fLabelView = NULL;
618	fLayoutData = new LayoutData;
619
620	int32 flags = 0;
621
622	BFont font(be_bold_font);
623
624	if (!archive || !archive->HasString("_fname"))
625		flags = B_FONT_FAMILY_AND_STYLE;
626
627	if (!archive || !archive->HasFloat("_fflt"))
628		flags |= B_FONT_SIZE;
629
630	if (flags != 0)
631		SetFont(&font, flags);
632
633	if (archive != NULL) {
634		const char *string;
635		if (archive->FindString("_label", &string) == B_OK)
636			SetLabel(string);
637
638		bool fancy;
639		int32 style;
640
641		if (archive->FindBool("_style", &fancy) == B_OK)
642			fStyle = fancy ? B_FANCY_BORDER : B_PLAIN_BORDER;
643		else if (archive->FindInt32("_style", &style) == B_OK)
644			fStyle = (border_style)style;
645
646		bool hasLabelView;
647		if (archive->FindBool("_lblview", &hasLabelView) == B_OK)
648			fLabelView = ChildAt(0);
649	}
650}
651
652
653void
654BBox::_DrawPlain(BRect labelBox)
655{
656	BRect rect = Bounds();
657	rect.top += TopBorderOffset();
658
659	float lightTint;
660	float shadowTint;
661	if (be_control_look != NULL) {
662		lightTint = B_LIGHTEN_1_TINT;
663		shadowTint = B_DARKEN_1_TINT;
664	} else {
665		lightTint = B_LIGHTEN_MAX_TINT;
666		shadowTint = B_DARKEN_3_TINT;
667	}
668
669	if (rect.Height() == 0.0 || rect.Width() == 0.0) {
670		// used as separator
671		rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT);
672
673		SetHighColor(shadow);
674		StrokeLine(rect.LeftTop(),rect.RightBottom());
675	} else {
676		// used as box
677		rgb_color light = tint_color(ViewColor(), lightTint);
678		rgb_color shadow = tint_color(ViewColor(), shadowTint);
679
680		BeginLineArray(4);
681			AddLine(BPoint(rect.left, rect.bottom),
682					BPoint(rect.left, rect.top), light);
683			AddLine(BPoint(rect.left + 1.0f, rect.top),
684					BPoint(rect.right, rect.top), light);
685			AddLine(BPoint(rect.left + 1.0f, rect.bottom),
686					BPoint(rect.right, rect.bottom), shadow);
687			AddLine(BPoint(rect.right, rect.bottom - 1.0f),
688					BPoint(rect.right, rect.top + 1.0f), shadow);
689		EndLineArray();
690	}
691}
692
693
694void
695BBox::_DrawFancy(BRect labelBox)
696{
697	BRect rect = Bounds();
698	rect.top += TopBorderOffset();
699
700	if (be_control_look != NULL) {
701		rgb_color base = ViewColor();
702		if (rect.Height() == 1.0) {
703			// used as horizontal separator
704			be_control_look->DrawGroupFrame(this, rect, rect, base,
705				BControlLook::B_TOP_BORDER);
706		} else if (rect.Width() == 1.0) {
707			// used as vertical separator
708			be_control_look->DrawGroupFrame(this, rect, rect, base,
709				BControlLook::B_LEFT_BORDER);
710		} else {
711			// used as box
712			be_control_look->DrawGroupFrame(this, rect, rect, base);
713		}
714		return;
715	}
716
717	rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT);
718	rgb_color shadow = tint_color(ViewColor(), B_DARKEN_3_TINT);
719
720	if (rect.Height() == 1.0) {
721		// used as horizontal separator
722		BeginLineArray(2);
723			AddLine(BPoint(rect.left, rect.top),
724					BPoint(rect.right, rect.top), shadow);
725			AddLine(BPoint(rect.left, rect.bottom),
726					BPoint(rect.right, rect.bottom), light);
727		EndLineArray();
728	} else if (rect.Width() == 1.0) {
729		// used as vertical separator
730		BeginLineArray(2);
731			AddLine(BPoint(rect.left, rect.top),
732					BPoint(rect.left, rect.bottom), shadow);
733			AddLine(BPoint(rect.right, rect.top),
734					BPoint(rect.right, rect.bottom), light);
735		EndLineArray();
736	} else {
737		// used as box
738		BeginLineArray(8);
739			AddLine(BPoint(rect.left, rect.bottom - 1.0),
740					BPoint(rect.left, rect.top), shadow);
741			AddLine(BPoint(rect.left + 1.0, rect.top),
742					BPoint(rect.right - 1.0, rect.top), shadow);
743			AddLine(BPoint(rect.left, rect.bottom),
744					BPoint(rect.right, rect.bottom), light);
745			AddLine(BPoint(rect.right, rect.bottom - 1.0),
746					BPoint(rect.right, rect.top), light);
747
748			rect.InsetBy(1.0, 1.0);
749
750			AddLine(BPoint(rect.left, rect.bottom - 1.0),
751					BPoint(rect.left, rect.top), light);
752			AddLine(BPoint(rect.left + 1.0, rect.top),
753					BPoint(rect.right - 1.0, rect.top), light);
754			AddLine(BPoint(rect.left, rect.bottom),
755					BPoint(rect.right, rect.bottom), shadow);
756			AddLine(BPoint(rect.right, rect.bottom - 1.0),
757					BPoint(rect.right, rect.top), shadow);
758		EndLineArray();
759	}
760}
761
762
763void
764BBox::_ClearLabel()
765{
766	fBounds.top = 0;
767
768	if (fLabel) {
769		free(fLabel);
770		fLabel = NULL;
771	} else if (fLabelView) {
772		fLabelView->RemoveSelf();
773		delete fLabelView;
774		fLabelView = NULL;
775	}
776}
777
778
779BView*
780BBox::_Child() const
781{
782	for (int32 i = 0; BView* view = ChildAt(i); i++) {
783		if (view != fLabelView)
784			return view;
785	}
786
787	return NULL;
788}
789
790
791void
792BBox::_ValidateLayoutData()
793{
794	if (fLayoutData->valid)
795		return;
796
797	// compute the label box, width and height
798	bool label = true;
799	float labelHeight = 0;	// height of the label (pixel count)
800	if (fLabel) {
801		// leave 6 pixels of the frame, and have a gap of 4 pixels between
802		// the frame and the text on either side
803		font_height fontHeight;
804		GetFontHeight(&fontHeight);
805		fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel),
806			ceilf(fontHeight.ascent));
807		labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1;
808	} else if (fLabelView) {
809		// the label view is placed at (0, 10) at its preferred size
810		BSize size = fLabelView->PreferredSize();
811		fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height);
812		labelHeight = size.height + 1;
813	} else {
814		label = false;
815	}
816
817	// border
818	switch (fStyle) {
819		case B_PLAIN_BORDER:
820			fLayoutData->insets.Set(1, 1, 1, 1);
821			break;
822		case B_FANCY_BORDER:
823			fLayoutData->insets.Set(3, 3, 3, 3);
824			break;
825		case B_NO_BORDER:
826		default:
827			fLayoutData->insets.Set(0, 0, 0, 0);
828			break;
829	}
830
831	// if there's a label, the top inset will be dictated by the label
832	if (label && labelHeight > fLayoutData->insets.top)
833		fLayoutData->insets.top = labelHeight;
834
835	// total number of pixel the border adds
836	float addWidth = fLayoutData->insets.left + fLayoutData->insets.right;
837	float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom;
838
839	// compute the minimal width induced by the label
840	float minWidth;
841	if (label)
842		minWidth = fLayoutData->label_box.right + fLayoutData->insets.right;
843	else
844		minWidth = addWidth - 1;
845
846	// finally consider the child constraints, if we shall support layout
847	BView* child = _Child();
848	if (child && (Flags() & B_SUPPORTS_LAYOUT)) {
849		BSize min = child->MinSize();
850		BSize max = child->MaxSize();
851		BSize preferred = child->PreferredSize();
852
853		min.width += addWidth;
854		min.height += addHeight;
855		preferred.width += addWidth;
856		preferred.height += addHeight;
857		max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1);
858		max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1);
859
860		if (min.width < minWidth)
861			min.width = minWidth;
862		BLayoutUtils::FixSizeConstraints(min, max, preferred);
863
864		fLayoutData->min = min;
865		fLayoutData->max = max;
866		fLayoutData->preferred = preferred;
867	} else {
868		fLayoutData->min.Set(minWidth, addHeight - 1);
869		fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
870		fLayoutData->preferred = fLayoutData->min;
871	}
872
873	fLayoutData->valid = true;
874	ResetLayoutInvalidation();
875}
876
877