xref: /haiku/src/kits/interface/Box.cpp (revision f75a7bf508f3156d63a14f8fd77c5e0ca4d08c42)
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 	  fStyle(B_FANCY_BORDER)
86 {
87 	_InitObject(archive);
88 }
89 
90 
91 BBox::~BBox()
92 {
93 	_ClearLabel();
94 
95 	delete fLayoutData;
96 }
97 
98 
99 BArchivable *
100 BBox::Instantiate(BMessage *archive)
101 {
102 	if (validate_instantiation(archive, "BBox"))
103 		return new BBox(archive);
104 
105 	return NULL;
106 }
107 
108 
109 status_t
110 BBox::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 
127 void
128 BBox::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 
144 border_style
145 BBox::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
152 float
153 BBox::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
165 BRect
166 BBox::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 
180 void
181 BBox::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 
195 status_t
196 BBox::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 
215 const char *
216 BBox::Label() const
217 {
218 	return fLabel;
219 }
220 
221 
222 BView *
223 BBox::LabelView() const
224 {
225 	return fLabelView;
226 }
227 
228 
229 void
230 BBox::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 
273 void
274 BBox::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 
292 void
293 BBox::DetachedFromWindow()
294 {
295 	BView::DetachedFromWindow();
296 }
297 
298 
299 void
300 BBox::AllAttached()
301 {
302 	BView::AllAttached();
303 }
304 
305 
306 void
307 BBox::AllDetached()
308 {
309 	BView::AllDetached();
310 }
311 
312 
313 void
314 BBox::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 
358 void
359 BBox::MessageReceived(BMessage *message)
360 {
361 	BView::MessageReceived(message);
362 }
363 
364 
365 void
366 BBox::MouseDown(BPoint point)
367 {
368 	BView::MouseDown(point);
369 }
370 
371 
372 void
373 BBox::MouseUp(BPoint point)
374 {
375 	BView::MouseUp(point);
376 }
377 
378 
379 void
380 BBox::WindowActivated(bool active)
381 {
382 	BView::WindowActivated(active);
383 }
384 
385 
386 void
387 BBox::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
388 {
389 	BView::MouseMoved(point, transit, message);
390 }
391 
392 
393 void
394 BBox::FrameMoved(BPoint newLocation)
395 {
396 	BView::FrameMoved(newLocation);
397 }
398 
399 
400 BHandler *
401 BBox::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 
409 void
410 BBox::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 
425 void
426 BBox::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 
437 void
438 BBox::MakeFocus(bool focused)
439 {
440 	BView::MakeFocus(focused);
441 }
442 
443 
444 status_t
445 BBox::GetSupportedSuites(BMessage *message)
446 {
447 	return BView::GetSupportedSuites(message);
448 }
449 
450 
451 status_t
452 BBox::Perform(perform_code d, void *arg)
453 {
454 	return BView::Perform(d, arg);
455 }
456 
457 
458 BSize
459 BBox::MinSize()
460 {
461 	_ValidateLayoutData();
462 
463 	BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min);
464 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
465 }
466 
467 
468 BSize
469 BBox::MaxSize()
470 {
471 	_ValidateLayoutData();
472 
473 	BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max);
474 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
475 }
476 
477 
478 BSize
479 BBox::PreferredSize()
480 {
481 	_ValidateLayoutData();
482 
483 	BSize size = (GetLayout() ? GetLayout()->PreferredSize()
484 		: fLayoutData->preferred);
485 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
486 }
487 
488 
489 void
490 BBox::InvalidateLayout(bool descendants)
491 {
492 	fLayoutData->valid = false;
493 	BView::InvalidateLayout(descendants);
494 }
495 
496 
497 void
498 BBox::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 
532 void BBox::_ReservedBox1() {}
533 void BBox::_ReservedBox2() {}
534 
535 
536 BBox &
537 BBox::operator=(const BBox &)
538 {
539 	return *this;
540 }
541 
542 
543 void
544 BBox::_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 
585 void
586 BBox::_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 
614 void
615 BBox::_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 
666 void
667 BBox::_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 
682 BView*
683 BBox::_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 
694 void
695 BBox::_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