xref: /haiku/src/kits/interface/Box.cpp (revision 1214ef1b2100f2b3299fc9d8d6142e46f70a4c3f)
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 
25 struct 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 
41 BBox::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 
53 BBox::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 
68 BBox::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 
83 BBox::BBox(BMessage *archive)
84 	: BView(archive)
85 {
86 	_InitObject(archive);
87 }
88 
89 
90 BBox::~BBox()
91 {
92 	_ClearLabel();
93 
94 	delete fLayoutData;
95 }
96 
97 
98 BArchivable *
99 BBox::Instantiate(BMessage *archive)
100 {
101 	if (validate_instantiation(archive, "BBox"))
102 		return new BBox(archive);
103 
104 	return NULL;
105 }
106 
107 
108 status_t
109 BBox::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 
126 void
127 BBox::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 
143 border_style
144 BBox::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
151 float
152 BBox::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
164 BRect
165 BBox::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 
179 void
180 BBox::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 
194 status_t
195 BBox::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 
214 const char *
215 BBox::Label() const
216 {
217 	return fLabel;
218 }
219 
220 
221 BView *
222 BBox::LabelView() const
223 {
224 	return fLabelView;
225 }
226 
227 
228 void
229 BBox::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 
272 void
273 BBox::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 
285 void
286 BBox::DetachedFromWindow()
287 {
288 	BView::DetachedFromWindow();
289 }
290 
291 
292 void
293 BBox::AllAttached()
294 {
295 	BView::AllAttached();
296 }
297 
298 
299 void
300 BBox::AllDetached()
301 {
302 	BView::AllDetached();
303 }
304 
305 
306 void
307 BBox::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 
351 void
352 BBox::MessageReceived(BMessage *message)
353 {
354 	BView::MessageReceived(message);
355 }
356 
357 
358 void
359 BBox::MouseDown(BPoint point)
360 {
361 	BView::MouseDown(point);
362 }
363 
364 
365 void
366 BBox::MouseUp(BPoint point)
367 {
368 	BView::MouseUp(point);
369 }
370 
371 
372 void
373 BBox::WindowActivated(bool active)
374 {
375 	BView::WindowActivated(active);
376 }
377 
378 
379 void
380 BBox::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
381 {
382 	BView::MouseMoved(point, transit, message);
383 }
384 
385 
386 void
387 BBox::FrameMoved(BPoint newLocation)
388 {
389 	BView::FrameMoved(newLocation);
390 }
391 
392 
393 BHandler *
394 BBox::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 
402 void
403 BBox::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 
418 void
419 BBox::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 
430 void
431 BBox::MakeFocus(bool focused)
432 {
433 	BView::MakeFocus(focused);
434 }
435 
436 
437 status_t
438 BBox::GetSupportedSuites(BMessage *message)
439 {
440 	return BView::GetSupportedSuites(message);
441 }
442 
443 
444 status_t
445 BBox::Perform(perform_code d, void *arg)
446 {
447 	return BView::Perform(d, arg);
448 }
449 
450 
451 BSize
452 BBox::MinSize()
453 {
454 	_ValidateLayoutData();
455 
456 	BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min);
457 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
458 }
459 
460 
461 BSize
462 BBox::MaxSize()
463 {
464 	_ValidateLayoutData();
465 
466 	BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max);
467 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
468 }
469 
470 
471 BSize
472 BBox::PreferredSize()
473 {
474 	_ValidateLayoutData();
475 
476 	BSize size = (GetLayout() ? GetLayout()->PreferredSize()
477 		: fLayoutData->preferred);
478 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
479 }
480 
481 
482 void
483 BBox::InvalidateLayout(bool descendants)
484 {
485 	fLayoutData->valid = false;
486 	BView::InvalidateLayout(descendants);
487 }
488 
489 
490 void
491 BBox::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 
525 void BBox::_ReservedBox1() {}
526 void BBox::_ReservedBox2() {}
527 
528 
529 BBox &
530 BBox::operator=(const BBox &)
531 {
532 	return *this;
533 }
534 
535 
536 void
537 BBox::_InitObject(BMessage* archive)
538 {
539 	fBounds = Bounds();
540 
541 	fLabel = NULL;
542 	fLabelView = NULL;
543 	fLayoutData = new LayoutData;
544 
545 	int32 flags = 0;
546 
547 	BFont font(be_bold_font);
548 
549 	if (!archive || !archive->HasString("_fname"))
550 		flags = B_FONT_FAMILY_AND_STYLE;
551 
552 	if (!archive || !archive->HasFloat("_fflt"))
553 		flags |= B_FONT_SIZE;
554 
555 	if (flags != 0)
556 		SetFont(&font, flags);
557 
558 	if (archive != NULL) {
559 		const char *string;
560 		if (archive->FindString("_label", &string) == B_OK)
561 			SetLabel(string);
562 
563 		bool fancy;
564 		int32 style;
565 
566 		if (archive->FindBool("_style", &fancy) == B_OK)
567 			fStyle = fancy ? B_FANCY_BORDER : B_PLAIN_BORDER;
568 		else if (archive->FindInt32("_style", &style) == B_OK)
569 			fStyle = (border_style)style;
570 
571 		bool hasLabelView;
572 		if (archive->FindBool("_lblview", &hasLabelView) == B_OK)
573 			fLabelView = ChildAt(0);
574 	}
575 }
576 
577 
578 void
579 BBox::_DrawPlain(BRect labelBox)
580 {
581 	BRect rect = Bounds();
582 	rect.top += TopBorderOffset();
583 
584 	rgb_color shadow = tint_color(ViewColor(), B_DARKEN_3_TINT);
585 
586 	if (rect.Height() == 0.0 || rect.Width() == 0.0) {
587 		// used as separator
588 		SetHighColor(shadow);
589 		StrokeLine(rect.LeftTop(),rect.RightBottom());
590 	} else {
591 		// used as box
592 		rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT);
593 		BeginLineArray(4);
594 			AddLine(BPoint(rect.left, rect.bottom),
595 					BPoint(rect.left, rect.top), light);
596 			AddLine(BPoint(rect.left + 1.0f, rect.top),
597 					BPoint(rect.right, rect.top), light);
598 			AddLine(BPoint(rect.left + 1.0f, rect.bottom),
599 					BPoint(rect.right, rect.bottom), shadow);
600 			AddLine(BPoint(rect.right, rect.bottom - 1.0f),
601 					BPoint(rect.right, rect.top + 1.0f), shadow);
602 		EndLineArray();
603 	}
604 }
605 
606 
607 void
608 BBox::_DrawFancy(BRect labelBox)
609 {
610 	BRect rect = Bounds();
611 	rect.top += TopBorderOffset();
612 
613 	rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT);
614 	rgb_color shadow = tint_color(ViewColor(), B_DARKEN_3_TINT);
615 
616 	if (rect.Height() == 1.0) {
617 		// used as horizontal separator
618 		BeginLineArray(2);
619 			AddLine(BPoint(rect.left, rect.top),
620 					BPoint(rect.right, rect.top), shadow);
621 			AddLine(BPoint(rect.left, rect.bottom),
622 					BPoint(rect.right, rect.bottom), light);
623 		EndLineArray();
624 	} else if (rect.Width() == 1.0) {
625 		// used as vertical separator
626 		BeginLineArray(2);
627 			AddLine(BPoint(rect.left, rect.top),
628 					BPoint(rect.left, rect.bottom), shadow);
629 			AddLine(BPoint(rect.right, rect.top),
630 					BPoint(rect.right, rect.bottom), light);
631 		EndLineArray();
632 	} else {
633 		// used as box
634 		BeginLineArray(8);
635 			AddLine(BPoint(rect.left, rect.bottom - 1.0),
636 					BPoint(rect.left, rect.top), shadow);
637 			AddLine(BPoint(rect.left + 1.0, rect.top),
638 					BPoint(rect.right - 1.0, rect.top), shadow);
639 			AddLine(BPoint(rect.left, rect.bottom),
640 					BPoint(rect.right, rect.bottom), light);
641 			AddLine(BPoint(rect.right, rect.bottom - 1.0),
642 					BPoint(rect.right, rect.top), light);
643 
644 			rect.InsetBy(1.0, 1.0);
645 
646 			AddLine(BPoint(rect.left, rect.bottom - 1.0),
647 					BPoint(rect.left, rect.top), light);
648 			AddLine(BPoint(rect.left + 1.0, rect.top),
649 					BPoint(rect.right - 1.0, rect.top), light);
650 			AddLine(BPoint(rect.left, rect.bottom),
651 					BPoint(rect.right, rect.bottom), shadow);
652 			AddLine(BPoint(rect.right, rect.bottom - 1.0),
653 					BPoint(rect.right, rect.top), shadow);
654 		EndLineArray();
655 	}
656 }
657 
658 
659 void
660 BBox::_ClearLabel()
661 {
662 	fBounds.top = 0;
663 
664 	if (fLabel) {
665 		free(fLabel);
666 		fLabel = NULL;
667 	} else if (fLabelView) {
668 		fLabelView->RemoveSelf();
669 		delete fLabelView;
670 		fLabelView = NULL;
671 	}
672 }
673 
674 
675 BView*
676 BBox::_Child() const
677 {
678 	for (int32 i = 0; BView* view = ChildAt(i); i++) {
679 		if (view != fLabelView)
680 			return view;
681 	}
682 
683 	return NULL;
684 }
685 
686 
687 void
688 BBox::_ValidateLayoutData()
689 {
690 	if (fLayoutData->valid)
691 		return;
692 
693 	// compute the label box, width and height
694 	bool label = true;
695 	float labelHeight = 0;	// height of the label (pixel count)
696 	if (fLabel) {
697 		// leave 6 pixels of the frame, and have a gap of 4 pixels between
698 		// the frame and the text on either side
699 		font_height fontHeight;
700 		GetFontHeight(&fontHeight);
701 		fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel),
702 			ceilf(fontHeight.ascent));
703 		labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1;
704 	} else if (fLabelView) {
705 		// the label view is placed at (0, 10) at its preferred size
706 		BSize size = fLabelView->PreferredSize();
707 		fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height);
708 		labelHeight = size.height + 1;
709 	} else {
710 		label = false;
711 	}
712 
713 	// border
714 	switch (fStyle) {
715 		case B_PLAIN_BORDER:
716 			fLayoutData->insets.Set(1, 1, 1, 1);
717 			break;
718 		case B_FANCY_BORDER:
719 			fLayoutData->insets.Set(2, 2, 2, 2);
720 			break;
721 		case B_NO_BORDER:
722 		default:
723 			fLayoutData->insets.Set(0, 0, 0, 0);
724 			break;
725 	}
726 
727 	// if there's a label, the top inset will be dictated by the label
728 	if (label && labelHeight > fLayoutData->insets.top)
729 		fLayoutData->insets.top = labelHeight;
730 
731 	// total number of pixel the border adds
732 	float addWidth = fLayoutData->insets.left + fLayoutData->insets.right;
733 	float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom;
734 
735 	// compute the minimal width induced by the label
736 	float minWidth;
737 	if (label)
738 		minWidth = fLayoutData->label_box.right + fLayoutData->insets.right;
739 	else
740 		minWidth = addWidth - 1;
741 
742 	// finally consider the child constraints, if we shall support layout
743 	BView* child = _Child();
744 	if (child && (Flags() & B_SUPPORTS_LAYOUT)) {
745 		BSize min = child->MinSize();
746 		BSize max = child->MaxSize();
747 		BSize preferred = child->PreferredSize();
748 
749 		min.width += addWidth;
750 		min.height += addHeight;
751 		preferred.width += addWidth;
752 		preferred.height += addHeight;
753 		max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1);
754 		max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1);
755 
756 		if (min.width < minWidth)
757 			min.width = minWidth;
758 		BLayoutUtils::FixSizeConstraints(min, max, preferred);
759 
760 		fLayoutData->min = min;
761 		fLayoutData->max = max;
762 		fLayoutData->preferred = preferred;
763 	} else {
764 		fLayoutData->min.Set(minWidth, addHeight - 1);
765 		fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
766 		fLayoutData->preferred = fLayoutData->min;
767 	}
768 
769 	fLayoutData->valid = true;
770 }
771 
772