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