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