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