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