xref: /haiku/src/kits/interface/TabView.cpp (revision 5e7964b0a929555415798dea3373db9ac4611caa)
1 /*
2  * Copyright 2001-2015 Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Marc Flerackers (mflerackers@androme.be)
7  *		Jérôme Duval (korli@users.berlios.de)
8  *		Stephan Aßmus <superstippi@gmx.de>
9  *		Artur Wyszynski
10  *		Rene Gollent (rene@gollent.com)
11  */
12 
13 
14 #include <TabView.h>
15 #include <TabViewPrivate.h>
16 
17 #include <new>
18 
19 #include <math.h>
20 #include <string.h>
21 
22 #include <CardLayout.h>
23 #include <ControlLook.h>
24 #include <GroupLayout.h>
25 #include <LayoutUtils.h>
26 #include <List.h>
27 #include <Message.h>
28 #include <PropertyInfo.h>
29 #include <Rect.h>
30 #include <Region.h>
31 #include <String.h>
32 
33 #include <binary_compatibility/Support.h>
34 
35 
36 static property_info sPropertyList[] = {
37 	{
38 		"Selection",
39 		{ B_GET_PROPERTY, B_SET_PROPERTY },
40 		{ B_DIRECT_SPECIFIER },
41 		NULL, 0,
42 		{ B_INT32_TYPE }
43 	},
44 
45 	{}
46 };
47 
48 
49 BTab::BTab(BView* contentsView)
50 	:
51 	fEnabled(true),
52 	fSelected(false),
53 	fFocus(false),
54 	fView(contentsView),
55 	fTabView(NULL)
56 {
57 }
58 
59 
60 BTab::BTab(BMessage* archive)
61 	:
62 	BArchivable(archive),
63 	fSelected(false),
64 	fFocus(false),
65 	fView(NULL),
66 	fTabView(NULL)
67 {
68 	bool disable;
69 
70 	if (archive->FindBool("_disable", &disable) != B_OK)
71 		SetEnabled(true);
72 	else
73 		SetEnabled(!disable);
74 }
75 
76 
77 BTab::~BTab()
78 {
79 	if (fView == NULL)
80 		return;
81 
82 	if (fSelected)
83 		fView->RemoveSelf();
84 
85 	delete fView;
86 }
87 
88 
89 BArchivable*
90 BTab::Instantiate(BMessage* archive)
91 {
92 	if (validate_instantiation(archive, "BTab"))
93 		return new BTab(archive);
94 
95 	return NULL;
96 }
97 
98 
99 status_t
100 BTab::Archive(BMessage* data, bool deep) const
101 {
102 	status_t result = BArchivable::Archive(data, deep);
103 	if (result != B_OK)
104 		return result;
105 
106 	if (!fEnabled)
107 		result = data->AddBool("_disable", false);
108 
109 	return result;
110 }
111 
112 
113 status_t
114 BTab::Perform(uint32 d, void* arg)
115 {
116 	return BArchivable::Perform(d, arg);
117 }
118 
119 
120 const char*
121 BTab::Label() const
122 {
123 	if (fView != NULL)
124 		return fView->Name();
125 	else
126 		return NULL;
127 }
128 
129 
130 void
131 BTab::SetLabel(const char* label)
132 {
133 	if (label == NULL || fView == NULL)
134 		return;
135 
136 	fView->SetName(label);
137 
138 	if (fTabView != NULL)
139 		fTabView->Invalidate();
140 }
141 
142 
143 bool
144 BTab::IsSelected() const
145 {
146 	return fSelected;
147 }
148 
149 
150 void
151 BTab::Select(BView* owner)
152 {
153 	fSelected = true;
154 
155 	if (owner == NULL || fView == NULL)
156 		return;
157 
158 	// NOTE: Views are not added/removed, if there is layout,
159 	// they are made visible/invisible in that case.
160 	if (owner->GetLayout() == NULL && fView->Parent() == NULL)
161 		owner->AddChild(fView);
162 }
163 
164 
165 void
166 BTab::Deselect()
167 {
168 	if (fView != NULL) {
169 		// NOTE: Views are not added/removed, if there is layout,
170 		// they are made visible/invisible in that case.
171 		bool removeView = false;
172 		BView* container = fView->Parent();
173 		if (container != NULL)
174 			removeView =
175 				dynamic_cast<BCardLayout*>(container->GetLayout()) == NULL;
176 		if (removeView)
177 			fView->RemoveSelf();
178 	}
179 
180 	fSelected = false;
181 }
182 
183 
184 void
185 BTab::SetEnabled(bool enable)
186 {
187 	fEnabled = enable;
188 }
189 
190 
191 bool
192 BTab::IsEnabled() const
193 {
194 	return fEnabled;
195 }
196 
197 
198 void
199 BTab::MakeFocus(bool focus)
200 {
201 	fFocus = focus;
202 }
203 
204 
205 bool
206 BTab::IsFocus() const
207 {
208 	return fFocus;
209 }
210 
211 
212 void
213 BTab::SetView(BView* view)
214 {
215 	if (view == NULL || fView == view)
216 		return;
217 
218 	if (fView != NULL) {
219 		fView->RemoveSelf();
220 		delete fView;
221 	}
222 	fView = view;
223 
224 	if (fTabView != NULL && fSelected) {
225 		Select(fTabView->ContainerView());
226 		fTabView->Invalidate();
227 	}
228 }
229 
230 
231 BView*
232 BTab::View() const
233 {
234 	return fView;
235 }
236 
237 
238 void
239 BTab::DrawFocusMark(BView* owner, BRect frame)
240 {
241 	float width = owner->StringWidth(Label());
242 
243 	owner->SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
244 
245 	float offset = IsSelected() ? 3 : 2;
246 	owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
247 			frame.bottom - offset),
248 		BPoint((frame.left + frame.right + width) / 2.0,
249 			frame.bottom - offset));
250 }
251 
252 
253 void
254 BTab::DrawLabel(BView* owner, BRect frame)
255 {
256 	be_control_look->DrawLabel(owner, Label(), frame, frame,
257 		ui_color(B_PANEL_BACKGROUND_COLOR),
258 		IsEnabled() ? 0 : BPrivate::BControlLook::B_DISABLED,
259 		BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
260 }
261 
262 
263 void
264 BTab::DrawTab(BView* owner, BRect frame, tab_position position, bool full)
265 {
266 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
267 	uint32 borders = BControlLook::B_TOP_BORDER
268 		| BControlLook::B_BOTTOM_BORDER;
269 
270 	if (frame.left == owner->Bounds().left)
271 		borders |= BControlLook::B_LEFT_BORDER;
272 
273 	if (frame.right == owner->Bounds().right)
274 		borders |= BControlLook::B_RIGHT_BORDER;
275 
276 	if (position == B_TAB_FRONT) {
277 		frame.bottom += 1.0f;
278 		be_control_look->DrawActiveTab(owner, frame, frame, no_tint, 0,
279 			borders);
280 	} else {
281 		be_control_look->DrawInactiveTab(owner, frame, frame, no_tint, 0,
282 			borders);
283 	}
284 
285 	DrawLabel(owner, frame);
286 }
287 
288 
289 //	#pragma mark - FBC padding and private methods
290 
291 
292 void BTab::_ReservedTab1() {}
293 void BTab::_ReservedTab2() {}
294 void BTab::_ReservedTab3() {}
295 void BTab::_ReservedTab4() {}
296 void BTab::_ReservedTab5() {}
297 void BTab::_ReservedTab6() {}
298 void BTab::_ReservedTab7() {}
299 void BTab::_ReservedTab8() {}
300 void BTab::_ReservedTab9() {}
301 void BTab::_ReservedTab10() {}
302 void BTab::_ReservedTab11() {}
303 void BTab::_ReservedTab12() {}
304 
305 BTab &BTab::operator=(const BTab &)
306 {
307 	// this is private and not functional, but exported
308 	return *this;
309 }
310 
311 
312 //	#pragma mark - BTabView
313 
314 
315 BTabView::BTabView(const char* name, button_width width, uint32 flags)
316 	:
317 	BView(name, flags)
318 {
319 	_InitObject(true, width);
320 }
321 
322 
323 BTabView::BTabView(BRect frame, const char* name, button_width width,
324 	uint32 resizeMask, uint32 flags)
325 	:
326 	BView(frame, name, resizeMask, flags)
327 {
328 	_InitObject(false, width);
329 }
330 
331 
332 BTabView::~BTabView()
333 {
334 	for (int32 i = 0; i < CountTabs(); i++)
335 		delete TabAt(i);
336 
337 	delete fTabList;
338 }
339 
340 
341 BTabView::BTabView(BMessage* archive)
342 	:
343 	BView(BUnarchiver::PrepareArchive(archive)),
344 	fTabList(new BList),
345 	fContainerView(NULL),
346 	fFocus(-1)
347 {
348 	BUnarchiver unarchiver(archive);
349 
350 	int16 width;
351 	if (archive->FindInt16("_but_width", &width) == B_OK)
352 		fTabWidthSetting = (button_width)width;
353 	else
354 		fTabWidthSetting = B_WIDTH_AS_USUAL;
355 
356 	if (archive->FindFloat("_high", &fTabHeight) != B_OK) {
357 		font_height fh;
358 		GetFontHeight(&fh);
359 		fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
360 	}
361 
362 	if (archive->FindInt32("_sel", &fSelection) != B_OK)
363 		fSelection = -1;
364 
365 	if (archive->FindInt32("_border_style", (int32*)&fBorderStyle) != B_OK)
366 		fBorderStyle = B_FANCY_BORDER;
367 
368 	int32 i = 0;
369 	BMessage tabMsg;
370 
371 	if (BUnarchiver::IsArchiveManaged(archive)) {
372 		int32 tabCount;
373 		archive->GetInfo("_l_items", NULL, &tabCount);
374 		for (int32 i = 0; i < tabCount; i++) {
375 			unarchiver.EnsureUnarchived("_l_items", i);
376 			unarchiver.EnsureUnarchived("_view_list", i);
377 		}
378 		return;
379 	}
380 
381 	fContainerView = ChildAt(0);
382 	_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
383 
384 	while (archive->FindMessage("_l_items", i, &tabMsg) == B_OK) {
385 		BArchivable* archivedTab = instantiate_object(&tabMsg);
386 
387 		if (archivedTab) {
388 			BTab* tab = dynamic_cast<BTab*>(archivedTab);
389 
390 			BMessage viewMsg;
391 			if (archive->FindMessage("_view_list", i, &viewMsg) == B_OK) {
392 				BArchivable* archivedView = instantiate_object(&viewMsg);
393 				if (archivedView)
394 					AddTab(dynamic_cast<BView*>(archivedView), tab);
395 			}
396 		}
397 
398 		tabMsg.MakeEmpty();
399 		i++;
400 	}
401 }
402 
403 
404 BArchivable*
405 BTabView::Instantiate(BMessage* archive)
406 {
407 	if ( validate_instantiation(archive, "BTabView"))
408 		return new BTabView(archive);
409 
410 	return NULL;
411 }
412 
413 
414 status_t
415 BTabView::Archive(BMessage* archive, bool deep) const
416 {
417 	BArchiver archiver(archive);
418 
419 	status_t result = BView::Archive(archive, deep);
420 
421 	if (result == B_OK)
422 		result = archive->AddInt16("_but_width", fTabWidthSetting);
423 	if (result == B_OK)
424 		result = archive->AddFloat("_high", fTabHeight);
425 	if (result == B_OK)
426 		result = archive->AddInt32("_sel", fSelection);
427 	if (result == B_OK && fBorderStyle != B_FANCY_BORDER)
428 		result = archive->AddInt32("_border_style", fBorderStyle);
429 
430 	if (result == B_OK && deep) {
431 		for (int32 i = 0; i < CountTabs(); i++) {
432 			BTab* tab = TabAt(i);
433 
434 			if ((result = archiver.AddArchivable("_l_items", tab, deep))
435 					!= B_OK) {
436 				break;
437 			}
438 			result = archiver.AddArchivable("_view_list", tab->View(), deep);
439 		}
440 	}
441 
442 	return archiver.Finish(result);
443 }
444 
445 
446 status_t
447 BTabView::AllUnarchived(const BMessage* archive)
448 {
449 	status_t err = BView::AllUnarchived(archive);
450 	if (err != B_OK)
451 		return err;
452 
453 	fContainerView = ChildAt(0);
454 	_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
455 
456 	BUnarchiver unarchiver(archive);
457 
458 	int32 tabCount;
459 	archive->GetInfo("_l_items", NULL, &tabCount);
460 	for (int32 i = 0; i < tabCount && err == B_OK; i++) {
461 		BTab* tab;
462 		err = unarchiver.FindObject("_l_items", i, tab);
463 		if (err == B_OK && tab) {
464 			BView* view;
465 			if ((err = unarchiver.FindObject("_view_list", i,
466 				BUnarchiver::B_DONT_ASSUME_OWNERSHIP, view)) != B_OK)
467 				break;
468 
469 			tab->SetView(view);
470 			fTabList->AddItem(tab);
471 		}
472 	}
473 
474 	if (err == B_OK)
475 		Select(fSelection);
476 
477 	return err;
478 }
479 
480 
481 status_t
482 BTabView::Perform(perform_code code, void* _data)
483 {
484 	switch (code) {
485 		case PERFORM_CODE_ALL_UNARCHIVED:
486 		{
487 			perform_data_all_unarchived* data
488 				= (perform_data_all_unarchived*)_data;
489 
490 			data->return_value = BTabView::AllUnarchived(data->archive);
491 			return B_OK;
492 		}
493 	}
494 
495 	return BView::Perform(code, _data);
496 }
497 
498 
499 void
500 BTabView::AttachedToWindow()
501 {
502 	BView::AttachedToWindow();
503 
504 	if (fSelection < 0 && CountTabs() > 0)
505 		Select(0);
506 }
507 
508 
509 void
510 BTabView::DetachedFromWindow()
511 {
512 	BView::DetachedFromWindow();
513 }
514 
515 
516 void
517 BTabView::AllAttached()
518 {
519 	BView::AllAttached();
520 }
521 
522 
523 void
524 BTabView::AllDetached()
525 {
526 	BView::AllDetached();
527 }
528 
529 
530 // #pragma mark -
531 
532 
533 void
534 BTabView::MessageReceived(BMessage* message)
535 {
536 	switch (message->what) {
537 		case B_GET_PROPERTY:
538 		case B_SET_PROPERTY:
539 		{
540 			BMessage reply(B_REPLY);
541 			bool handled = false;
542 
543 			BMessage specifier;
544 			int32 index;
545 			int32 form;
546 			const char* property;
547 			if (message->GetCurrentSpecifier(&index, &specifier, &form,
548 					&property) == B_OK) {
549 				if (strcmp(property, "Selection") == 0) {
550 					if (message->what == B_GET_PROPERTY) {
551 						reply.AddInt32("result", fSelection);
552 						handled = true;
553 					} else {
554 						// B_GET_PROPERTY
555 						int32 selection;
556 						if (message->FindInt32("data", &selection) == B_OK) {
557 							Select(selection);
558 							reply.AddInt32("error", B_OK);
559 							handled = true;
560 						}
561 					}
562 				}
563 			}
564 
565 			if (handled)
566 				message->SendReply(&reply);
567 			else
568 				BView::MessageReceived(message);
569 			break;
570 		}
571 
572 #if 0
573 		case B_MOUSE_WHEEL_CHANGED:
574 		{
575 			float deltaX = 0.0f;
576 			float deltaY = 0.0f;
577 			message->FindFloat("be:wheel_delta_x", &deltaX);
578 			message->FindFloat("be:wheel_delta_y", &deltaY);
579 
580 			if (deltaX == 0.0f && deltaY == 0.0f)
581 				return;
582 
583 			if (deltaY == 0.0f)
584 				deltaY = deltaX;
585 
586 			int32 selection = Selection();
587 			int32 numTabs = CountTabs();
588 			if (deltaY > 0  && selection < numTabs - 1) {
589 				// move to the right tab.
590 				Select(Selection() + 1);
591 			} else if (deltaY < 0 && selection > 0 && numTabs > 1) {
592 				// move to the left tab.
593 				Select(selection - 1);
594 			}
595 			break;
596 		}
597 #endif
598 
599 		default:
600 			BView::MessageReceived(message);
601 			break;
602 	}
603 }
604 
605 
606 void
607 BTabView::KeyDown(const char* bytes, int32 numBytes)
608 {
609 	if (IsHidden())
610 		return;
611 
612 	switch (bytes[0]) {
613 		case B_DOWN_ARROW:
614 		case B_LEFT_ARROW: {
615 			int32 focus = fFocus - 1;
616 			if (focus < 0)
617 				focus = CountTabs() - 1;
618 			SetFocusTab(focus, true);
619 			break;
620 		}
621 
622 		case B_UP_ARROW:
623 		case B_RIGHT_ARROW: {
624 			int32 focus = fFocus + 1;
625 			if (focus >= CountTabs())
626 				focus = 0;
627 			SetFocusTab(focus, true);
628 			break;
629 		}
630 
631 		case B_RETURN:
632 		case B_SPACE:
633 			Select(FocusTab());
634 			break;
635 
636 		default:
637 			BView::KeyDown(bytes, numBytes);
638 	}
639 }
640 
641 
642 void
643 BTabView::MouseDown(BPoint where)
644 {
645 	if (where.y > fTabHeight)
646 		return;
647 
648 	for (int32 i = 0; i < CountTabs(); i++) {
649 		if (TabFrame(i).Contains(where)
650 			&& i != Selection()) {
651 			Select(i);
652 			return;
653 		}
654 	}
655 
656 	BView::MouseDown(where);
657 }
658 
659 
660 void
661 BTabView::MouseUp(BPoint where)
662 {
663 	BView::MouseUp(where);
664 }
665 
666 
667 void
668 BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
669 {
670 	BView::MouseMoved(where, transit, dragMessage);
671 }
672 
673 
674 void
675 BTabView::Pulse()
676 {
677 	BView::Pulse();
678 }
679 
680 
681 void
682 BTabView::Select(int32 index)
683 {
684 	if (index == Selection())
685 		return;
686 
687 	if (index < 0 || index >= CountTabs())
688 		index = Selection();
689 
690 	BTab* tab = TabAt(Selection());
691 
692 	if (tab)
693 		tab->Deselect();
694 
695 	tab = TabAt(index);
696 	if (tab != NULL && fContainerView != NULL) {
697 		if (index == 0)
698 			fTabOffset = 0.0f;
699 
700 		tab->Select(fContainerView);
701 		fSelection = index;
702 
703 		// make the view visible through the layout if there is one
704 		BCardLayout* layout
705 			= dynamic_cast<BCardLayout*>(fContainerView->GetLayout());
706 		if (layout != NULL)
707 			layout->SetVisibleItem(index);
708 	}
709 
710 	Invalidate();
711 
712 	if (index != 0 && !Bounds().Contains(TabFrame(index))){
713 		if (!Bounds().Contains(TabFrame(index).LeftTop()))
714 			fTabOffset += TabFrame(index).left - Bounds().left - 20.0f;
715 		else
716 			fTabOffset += TabFrame(index).right - Bounds().right + 20.0f;
717 
718 		Invalidate();
719 	}
720 
721 	SetFocusTab(index, true);
722 }
723 
724 
725 int32
726 BTabView::Selection() const
727 {
728 	return fSelection;
729 }
730 
731 
732 void
733 BTabView::WindowActivated(bool active)
734 {
735 	BView::WindowActivated(active);
736 
737 	if (IsFocus())
738 		Invalidate();
739 }
740 
741 
742 void
743 BTabView::MakeFocus(bool focus)
744 {
745 	BView::MakeFocus(focus);
746 
747 	SetFocusTab(Selection(), focus);
748 }
749 
750 
751 void
752 BTabView::SetFocusTab(int32 tab, bool focus)
753 {
754 	if (tab >= CountTabs())
755 		tab = 0;
756 
757 	if (tab < 0)
758 		tab = CountTabs() - 1;
759 
760 	if (focus) {
761 		if (tab == fFocus)
762 			return;
763 
764 		if (fFocus != -1){
765 			if (TabAt (fFocus) != NULL)
766 				TabAt(fFocus)->MakeFocus(false);
767 			Invalidate(TabFrame(fFocus));
768 		}
769 		if (TabAt(tab) != NULL){
770 			TabAt(tab)->MakeFocus(true);
771 			Invalidate(TabFrame(tab));
772 			fFocus = tab;
773 		}
774 	} else if (fFocus != -1) {
775 		TabAt(fFocus)->MakeFocus(false);
776 		Invalidate(TabFrame(fFocus));
777 		fFocus = -1;
778 	}
779 }
780 
781 
782 int32
783 BTabView::FocusTab() const
784 {
785 	return fFocus;
786 }
787 
788 
789 void
790 BTabView::Draw(BRect updateRect)
791 {
792 	DrawBox(TabFrame(fSelection));
793 	DrawTabs();
794 
795 	if (IsFocus() && fFocus != -1)
796 		TabAt(fFocus)->DrawFocusMark(this, TabFrame(fFocus));
797 }
798 
799 
800 BRect
801 BTabView::DrawTabs()
802 {
803 	// TODO: Rewrite this method
804 
805 	// draw an inactive tab frame behind all tabs
806 	BRect bounds(Bounds());
807 	bounds.bottom = fTabHeight;
808 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
809 	uint32 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
810 	if (fBorderStyle == B_NO_BORDER) {
811 		// removes left border that is an artifact of DrawInactiveTab()
812 		bounds.left -= 1;
813 	} else
814 		borders |= BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
815 
816 	// TODO: Why do we have to do this?
817 	if (fBorderStyle == B_PLAIN_BORDER) {
818 		bounds.left -= 1;
819 		bounds.right += 1;
820 	}
821 
822 	// TODO: Doesn't draw bottom border, why?
823 	be_control_look->DrawInactiveTab(this, bounds, bounds, base, 0, borders);
824 
825 	// draw the tabs on top of the inactive tab bounds
826 	float right = 0.0f;
827 	BRect activeTabFrame;
828 	int32 tabCount = CountTabs();
829 	for (int32 i = 0; i < tabCount; i++) {
830 		BRect tabFrame = TabFrame(i);
831 		if (i == fSelection)
832 			activeTabFrame = tabFrame;
833 
834 		TabAt(i)->DrawTab(this, tabFrame,
835 			i == fSelection ? B_TAB_FRONT : (i == 0) ? B_TAB_FIRST : B_TAB_ANY,
836 			i + 1 != fSelection);
837 		right = tabFrame.right;
838 	}
839 
840 	if (right < bounds.right) {
841 		// draw a 1px right border on the last tab
842 		bounds = Bounds();
843 		bounds.left = bounds.right = right;
844 		bounds.bottom = fTabHeight;
845 		borders = BControlLook::B_TOP_BORDER;
846 		be_control_look->DrawInactiveTab(this, bounds, bounds, base, 0,
847 			BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
848 	}
849 
850 	// TODO: Why do we have to do this?
851 	// TODO: Why don't we have to do this for B_FANCY_BORDER?
852 	// TODO: Why does this draw the wrong color (152 instead of 151)
853 	if (fBorderStyle != B_FANCY_BORDER) {
854 		// draw the bottom border of the tabs
855 		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
856 
857 		// draw the bottom border left of the active tab
858 		bounds = Bounds();
859 		bounds.top = bounds.bottom = fTabHeight;
860 		bounds.right = activeTabFrame.left;
861 		be_control_look->DrawBorder(this, bounds, bounds, base, B_PLAIN_BORDER,
862 			0, BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
863 
864 		// draw the bottom border right of the active tab
865 		bounds = Bounds();
866 		bounds.top = bounds.bottom = fTabHeight;
867 		bounds.left = activeTabFrame.right;
868 		be_control_look->DrawBorder(this, bounds, bounds, base, B_PLAIN_BORDER,
869 			0, BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER);
870 	}
871 
872 	return fSelection < CountTabs() ? TabFrame(fSelection) : BRect();
873 }
874 
875 
876 void
877 BTabView::DrawBox(BRect selectedTabRect)
878 {
879 	BRect rect(Bounds());
880 	rect.top = fTabHeight;
881 
882 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
883 	if (fBorderStyle == B_FANCY_BORDER)
884 		be_control_look->DrawGroupFrame(this, rect, rect, base);
885 	else if (fBorderStyle == B_PLAIN_BORDER) {
886 		be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER,
887 			0, BControlLook::B_ALL_BORDERS);
888 	} else
889 		; // B_NO_BORDER draws no box
890 }
891 
892 
893 BRect
894 BTabView::TabFrame(int32 index) const
895 {
896 	if (index >= CountTabs() || index < 0)
897 		return BRect();
898 
899 	float width = 100.0f;
900 	float height = fTabHeight;
901 	float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
902 
903 	switch (fTabWidthSetting) {
904 		case B_WIDTH_FROM_LABEL:
905 		{
906 			float x = 0.0f;
907 			for (int32 i = 0; i < index; i++){
908 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
909 			}
910 
911 			return BRect(offset + x, 0.0f,
912 				offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
913 				height);
914 		}
915 
916 		case B_WIDTH_FROM_WIDEST:
917 			width = 0.0;
918 			for (int32 i = 0; i < CountTabs(); i++) {
919 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
920 				if (tabWidth > width)
921 					width = tabWidth;
922 			}
923 			// fall through
924 
925 		case B_WIDTH_AS_USUAL:
926 		default:
927 			return BRect(offset + index * width, 0.0f,
928 				offset + index * width + width, height);
929 	}
930 
931 	// TODO: fix to remove "offset" in DrawTab and DrawLabel ...
932 	switch (fTabWidthSetting) {
933 		case B_WIDTH_FROM_LABEL:
934 		{
935 			float x = 6.0f;
936 			for (int32 i = 0; i < index; i++){
937 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
938 			}
939 
940 			return BRect(x - fTabOffset, 0.0f,
941 				x - fTabOffset + StringWidth(TabAt(index)->Label()) + 20.0f,
942 				fTabHeight);
943 		}
944 
945 		case B_WIDTH_FROM_WIDEST:
946 		{
947 			float width = 0.0f;
948 
949 			for (int32 i = 0; i < CountTabs(); i++) {
950 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
951 				if (tabWidth > width)
952 					width = tabWidth;
953 			}
954 			return BRect((6.0f + index * width) - fTabOffset, 0.0f,
955 				(6.0f + index * width + width) - fTabOffset, fTabHeight);
956 		}
957 
958 		case B_WIDTH_AS_USUAL:
959 		default:
960 			return BRect((6.0f + index * 100.0f) - fTabOffset, 0.0f,
961 				(6.0f + index * 100.0f + 100.0f) - fTabOffset, fTabHeight);
962 	}
963 }
964 
965 
966 void
967 BTabView::SetFlags(uint32 flags)
968 {
969 	BView::SetFlags(flags);
970 }
971 
972 
973 void
974 BTabView::SetResizingMode(uint32 mode)
975 {
976 	BView::SetResizingMode(mode);
977 }
978 
979 
980 // #pragma mark -
981 
982 
983 void
984 BTabView::ResizeToPreferred()
985 {
986 	BView::ResizeToPreferred();
987 }
988 
989 
990 void
991 BTabView::GetPreferredSize(float* _width, float* _height)
992 {
993 	BView::GetPreferredSize(_width, _height);
994 }
995 
996 
997 BSize
998 BTabView::MinSize()
999 {
1000 	BSize size;
1001 	if (GetLayout())
1002 		size = GetLayout()->MinSize();
1003 	else {
1004 		size = _TabsMinSize();
1005 		BSize containerSize = fContainerView->MinSize();
1006 		containerSize.width += 2 * _BorderWidth();
1007 		containerSize.height += 2 * _BorderWidth();
1008 		if (containerSize.width > size.width)
1009 			size.width = containerSize.width;
1010 		size.height += containerSize.height;
1011 	}
1012 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1013 }
1014 
1015 
1016 BSize
1017 BTabView::MaxSize()
1018 {
1019 	BSize size;
1020 	if (GetLayout())
1021 		size = GetLayout()->MaxSize();
1022 	else {
1023 		size = _TabsMinSize();
1024 		BSize containerSize = fContainerView->MaxSize();
1025 		containerSize.width += 2 * _BorderWidth();
1026 		containerSize.height += 2 * _BorderWidth();
1027 		if (containerSize.width > size.width)
1028 			size.width = containerSize.width;
1029 		size.height += containerSize.height;
1030 	}
1031 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1032 }
1033 
1034 
1035 BSize
1036 BTabView::PreferredSize()
1037 {
1038 	BSize size;
1039 	if (GetLayout() != NULL)
1040 		size = GetLayout()->PreferredSize();
1041 	else {
1042 		size = _TabsMinSize();
1043 		BSize containerSize = fContainerView->PreferredSize();
1044 		containerSize.width += 2 * _BorderWidth();
1045 		containerSize.height += 2 * _BorderWidth();
1046 		if (containerSize.width > size.width)
1047 			size.width = containerSize.width;
1048 		size.height += containerSize.height;
1049 	}
1050 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1051 }
1052 
1053 
1054 void
1055 BTabView::FrameMoved(BPoint newPosition)
1056 {
1057 	BView::FrameMoved(newPosition);
1058 }
1059 
1060 
1061 void
1062 BTabView::FrameResized(float newWidth, float newHeight)
1063 {
1064 	BView::FrameResized(newWidth, newHeight);
1065 }
1066 
1067 
1068 // #pragma mark -
1069 
1070 
1071 BHandler*
1072 BTabView::ResolveSpecifier(BMessage* message, int32 index,
1073 	BMessage* specifier, int32 what, const char* property)
1074 {
1075 	BPropertyInfo propInfo(sPropertyList);
1076 
1077 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1078 		return this;
1079 
1080 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1081 }
1082 
1083 
1084 status_t
1085 BTabView::GetSupportedSuites(BMessage* message)
1086 {
1087 	message->AddString("suites", "suite/vnd.Be-tab-view");
1088 
1089 	BPropertyInfo propInfo(sPropertyList);
1090 	message->AddFlat("messages", &propInfo);
1091 
1092 	return BView::GetSupportedSuites(message);
1093 }
1094 
1095 
1096 // #pragma mark -
1097 
1098 
1099 void
1100 BTabView::AddTab(BView* target, BTab* tab)
1101 {
1102 	if (tab == NULL)
1103 		tab = new BTab(target);
1104 	else
1105 		tab->SetView(target);
1106 
1107 	if (fContainerView->GetLayout())
1108 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1109 
1110 	fTabList->AddItem(tab);
1111 	BTab::Private(tab).SetTabView(this);
1112 
1113 	// When we haven't had a any tabs before, but are already attached to the
1114 	// window, select this one.
1115 	if (CountTabs() == 1 && Window() != NULL)
1116 		Select(0);
1117 }
1118 
1119 
1120 BTab*
1121 BTabView::RemoveTab(int32 index)
1122 {
1123 	if (index < 0 || index >= CountTabs())
1124 		return NULL;
1125 
1126 	BTab* tab = (BTab*)fTabList->RemoveItem(index);
1127 	if (tab == NULL)
1128 		return NULL;
1129 
1130 	tab->Deselect();
1131 	BTab::Private(tab).SetTabView(NULL);
1132 
1133 	if (fContainerView->GetLayout())
1134 		fContainerView->GetLayout()->RemoveItem(index);
1135 
1136 	if (CountTabs() == 0)
1137 		fFocus = -1;
1138 	else if (index <= fSelection)
1139 		Select(fSelection - 1);
1140 
1141 	if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1142 		SetFocusTab(fFocus, false);
1143 	else
1144 		SetFocusTab(fFocus, true);
1145 
1146 	return tab;
1147 }
1148 
1149 
1150 BTab*
1151 BTabView::TabAt(int32 index) const
1152 {
1153 	return (BTab*)fTabList->ItemAt(index);
1154 }
1155 
1156 
1157 void
1158 BTabView::SetTabWidth(button_width width)
1159 {
1160 	fTabWidthSetting = width;
1161 
1162 	Invalidate();
1163 }
1164 
1165 
1166 button_width
1167 BTabView::TabWidth() const
1168 {
1169 	return fTabWidthSetting;
1170 }
1171 
1172 
1173 void
1174 BTabView::SetTabHeight(float height)
1175 {
1176 	if (fTabHeight == height)
1177 		return;
1178 
1179 	fTabHeight = height;
1180 	_LayoutContainerView(GetLayout() != NULL);
1181 
1182 	Invalidate();
1183 }
1184 
1185 
1186 float
1187 BTabView::TabHeight() const
1188 {
1189 	return fTabHeight;
1190 }
1191 
1192 
1193 void
1194 BTabView::SetBorder(border_style borderStyle)
1195 {
1196 	if (fBorderStyle == borderStyle)
1197 		return;
1198 
1199 	fBorderStyle = borderStyle;
1200 
1201 	_LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0);
1202 }
1203 
1204 
1205 border_style
1206 BTabView::Border() const
1207 {
1208 	return fBorderStyle;
1209 }
1210 
1211 
1212 BView*
1213 BTabView::ContainerView() const
1214 {
1215 	return fContainerView;
1216 }
1217 
1218 
1219 int32
1220 BTabView::CountTabs() const
1221 {
1222 	return fTabList->CountItems();
1223 }
1224 
1225 
1226 BView*
1227 BTabView::ViewForTab(int32 tabIndex) const
1228 {
1229 	BTab* tab = TabAt(tabIndex);
1230 	if (tab != NULL)
1231 		return tab->View();
1232 
1233 	return NULL;
1234 }
1235 
1236 
1237 void
1238 BTabView::_InitObject(bool layouted, button_width width)
1239 {
1240 	fTabList = new BList;
1241 
1242 	fTabWidthSetting = width;
1243 	fSelection = -1;
1244 	fFocus = -1;
1245 	fTabOffset = 0.0f;
1246 	fBorderStyle = B_FANCY_BORDER;
1247 
1248 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1249 	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1250 
1251 	font_height fh;
1252 	GetFontHeight(&fh);
1253 	fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
1254 
1255 	fContainerView = NULL;
1256 	_InitContainerView(layouted);
1257 }
1258 
1259 
1260 void
1261 BTabView::_InitContainerView(bool layouted)
1262 {
1263 	bool needsLayout = false;
1264 	bool createdContainer = false;
1265 	if (layouted) {
1266 		if (GetLayout() == NULL) {
1267 			SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL));
1268 			needsLayout = true;
1269 		}
1270 
1271 		if (fContainerView == NULL) {
1272 			fContainerView = new BView("view container", B_WILL_DRAW);
1273 			fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1274 			createdContainer = true;
1275 		}
1276 	} else if (fContainerView == NULL) {
1277 		fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL,
1278 			B_WILL_DRAW);
1279 		createdContainer = true;
1280 	}
1281 
1282 	if (needsLayout || createdContainer)
1283 		_LayoutContainerView(layouted);
1284 
1285 	if (createdContainer) {
1286 		fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1287 		fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1288 		AddChild(fContainerView);
1289 	}
1290 }
1291 
1292 
1293 BSize
1294 BTabView::_TabsMinSize() const
1295 {
1296 	BSize size(0.0f, TabHeight());
1297 	int32 count = min_c(2, CountTabs());
1298 	for (int32 i = 0; i < count; i++) {
1299 		BRect frame = TabFrame(i);
1300 		size.width += frame.Width();
1301 	}
1302 
1303 	if (count < CountTabs()) {
1304 		// TODO: Add size for yet to be implemented buttons that allow
1305 		// "scrolling" the displayed tabs left/right.
1306 	}
1307 
1308 	return size;
1309 }
1310 
1311 
1312 float
1313 BTabView::_BorderWidth() const
1314 {
1315 	switch (fBorderStyle) {
1316 		default:
1317 		case B_FANCY_BORDER:
1318 			return 3.0f;
1319 
1320 		case B_PLAIN_BORDER:
1321 			return 1.0f;
1322 
1323 		case B_NO_BORDER:
1324 			return 0.0f;
1325 	}
1326 }
1327 
1328 
1329 void
1330 BTabView::_LayoutContainerView(bool layouted)
1331 {
1332 	float borderWidth = _BorderWidth();
1333 	if (layouted) {
1334 		float topBorderOffset;
1335 		switch (fBorderStyle) {
1336 			default:
1337 			case B_FANCY_BORDER:
1338 				topBorderOffset = 1.0f;
1339 				break;
1340 
1341 			case B_PLAIN_BORDER:
1342 				topBorderOffset = 0.0f;
1343 				break;
1344 
1345 			case B_NO_BORDER:
1346 				topBorderOffset = -1.0f;
1347 				break;
1348 		}
1349 		BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
1350 		if (layout != NULL) {
1351 			layout->SetInsets(borderWidth, borderWidth + TabHeight()
1352 				- topBorderOffset, borderWidth, borderWidth);
1353 		}
1354 	} else {
1355 		BRect bounds = Bounds();
1356 
1357 		bounds.top += TabHeight();
1358 		bounds.InsetBy(borderWidth, borderWidth);
1359 
1360 		fContainerView->MoveTo(bounds.left, bounds.top);
1361 		fContainerView->ResizeTo(bounds.Width(), bounds.Height());
1362 	}
1363 }
1364 
1365 
1366 // #pragma mark - FBC and forbidden
1367 
1368 
1369 void BTabView::_ReservedTabView2() {}
1370 void BTabView::_ReservedTabView3() {}
1371 void BTabView::_ReservedTabView4() {}
1372 void BTabView::_ReservedTabView5() {}
1373 void BTabView::_ReservedTabView6() {}
1374 void BTabView::_ReservedTabView7() {}
1375 void BTabView::_ReservedTabView8() {}
1376 void BTabView::_ReservedTabView9() {}
1377 void BTabView::_ReservedTabView10() {}
1378 void BTabView::_ReservedTabView11() {}
1379 void BTabView::_ReservedTabView12() {}
1380 
1381 
1382 BTabView::BTabView(const BTabView& tabView)
1383 	: BView(tabView)
1384 {
1385 	// this is private and not functional, but exported
1386 }
1387 
1388 
1389 BTabView&
1390 BTabView::operator=(const BTabView&)
1391 {
1392 	// this is private and not functional, but exported
1393 	return *this;
1394 }
1395 
1396 //	#pragma mark - binary compatibility
1397 
1398 
1399 extern "C" void
1400 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
1401 	BTabView* tabView, border_style borderStyle)
1402 {
1403 	tabView->BTabView::SetBorder(borderStyle);
1404 }
1405