xref: /haiku/src/kits/interface/TabView.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 	{ 0 }
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 	switch (fTabView->TabSide()) {
247 		case BTabView::kTopSide:
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 			break;
253 		case BTabView::kBottomSide:
254 			owner->StrokeLine(BPoint((frame.left + frame.right - width) / 2.0,
255 					frame.top + offset),
256 				BPoint((frame.left + frame.right + width) / 2.0,
257 					frame.top + offset));
258 			break;
259 		case BTabView::kLeftSide:
260 			owner->StrokeLine(BPoint(frame.right - offset,
261 					(frame.top + frame.bottom - width) / 2.0),
262 				BPoint(frame.right - offset,
263 					(frame.top + frame.bottom + width) / 2.0));
264 			break;
265 		case BTabView::kRightSide:
266 			owner->StrokeLine(BPoint(frame.left + offset,
267 					(frame.top + frame.bottom - width) / 2.0),
268 				BPoint(frame.left + offset,
269 					(frame.top + frame.bottom + width) / 2.0));
270 			break;
271 	}
272 }
273 
274 
275 void
276 BTab::DrawLabel(BView* owner, BRect frame)
277 {
278 	float rotation = 0.0f;
279 	BPoint center(frame.left + frame.Width() / 2,
280 		frame.top + frame.Height() / 2);
281 	switch (fTabView->TabSide()) {
282 		case BTabView::kTopSide:
283 		case BTabView::kBottomSide:
284 			rotation = 0.0f;
285 			break;
286 		case BTabView::kLeftSide:
287 			rotation = 270.0f;
288 			break;
289 		case BTabView::kRightSide:
290 			rotation = 90.0f;
291 			break;
292 	}
293 
294 	if (rotation != 0.0f) {
295 		// DrawLabel doesn't allow rendering rotated text
296 		// rotate frame first and BAffineTransform will handle the rotation
297 		// we can't give "unrotated" frame because it comes from
298 		// BTabView::TabFrame and it is also used to handle mouse clicks
299 		BRect originalFrame(frame);
300 		frame.top = center.y - originalFrame.Width() / 2;
301 		frame.bottom = center.y + originalFrame.Width() / 2;
302 		frame.left = center.x - originalFrame.Height() / 2;
303 		frame.right = center.x + originalFrame.Height() / 2;
304 	}
305 
306 	BAffineTransform transform;
307 	transform.RotateBy(center, rotation * M_PI / 180.0f);
308 	owner->SetTransform(transform);
309 	be_control_look->DrawLabel(owner, Label(), frame, frame,
310 		ui_color(B_PANEL_BACKGROUND_COLOR),
311 		IsEnabled() ? 0 : BControlLook::B_DISABLED,
312 		BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
313 	owner->SetTransform(BAffineTransform());
314 }
315 
316 
317 void
318 BTab::DrawTab(BView* owner, BRect frame, tab_position position, bool full)
319 {
320 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
321 	uint32 borders = 0;
322 	if (fTabView->TabSide() == BTabView::kTopSide
323 		|| fTabView->TabSide() == BTabView::kBottomSide) {
324 		borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
325 
326 		if (frame.left == owner->Bounds().left)
327 			borders |= BControlLook::B_LEFT_BORDER;
328 
329 		if (frame.right == owner->Bounds().right)
330 			borders |= BControlLook::B_RIGHT_BORDER;
331 	} else if (fTabView->TabSide() == BTabView::kLeftSide
332 		|| fTabView->TabSide() == BTabView::kRightSide) {
333 		borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
334 
335 		if (frame.top == owner->Bounds().top)
336 			borders |= BControlLook::B_TOP_BORDER;
337 
338 		if (frame.bottom == owner->Bounds().bottom)
339 			borders |= BControlLook::B_BOTTOM_BORDER;
340 	}
341 
342 	if (position == B_TAB_FRONT) {
343 		be_control_look->DrawActiveTab(owner, frame, frame, no_tint, 0,
344 			borders, fTabView->TabSide());
345 	} else {
346 		be_control_look->DrawInactiveTab(owner, frame, frame, no_tint, 0,
347 			borders, fTabView->TabSide());
348 	}
349 
350 	DrawLabel(owner, frame);
351 }
352 
353 
354 //	#pragma mark - FBC padding and private methods
355 
356 
357 void BTab::_ReservedTab1() {}
358 void BTab::_ReservedTab2() {}
359 void BTab::_ReservedTab3() {}
360 void BTab::_ReservedTab4() {}
361 void BTab::_ReservedTab5() {}
362 void BTab::_ReservedTab6() {}
363 void BTab::_ReservedTab7() {}
364 void BTab::_ReservedTab8() {}
365 void BTab::_ReservedTab9() {}
366 void BTab::_ReservedTab10() {}
367 void BTab::_ReservedTab11() {}
368 void BTab::_ReservedTab12() {}
369 
370 BTab &BTab::operator=(const BTab &)
371 {
372 	// this is private and not functional, but exported
373 	return *this;
374 }
375 
376 
377 //	#pragma mark - BTabView
378 
379 
380 BTabView::BTabView(const char* name, button_width width, uint32 flags)
381 	:
382 	BView(name, flags)
383 {
384 	_InitObject(true, width);
385 }
386 
387 
388 BTabView::BTabView(BRect frame, const char* name, button_width width,
389 	uint32 resizeMask, uint32 flags)
390 	:
391 	BView(frame, name, resizeMask, flags)
392 {
393 	_InitObject(false, width);
394 }
395 
396 
397 BTabView::~BTabView()
398 {
399 	for (int32 i = 0; i < CountTabs(); i++)
400 		delete TabAt(i);
401 
402 	delete fTabList;
403 }
404 
405 
406 BTabView::BTabView(BMessage* archive)
407 	:
408 	BView(BUnarchiver::PrepareArchive(archive)),
409 	fTabList(new BList),
410 	fContainerView(NULL),
411 	fFocus(-1)
412 {
413 	BUnarchiver unarchiver(archive);
414 
415 	int16 width;
416 	if (archive->FindInt16("_but_width", &width) == B_OK)
417 		fTabWidthSetting = (button_width)width;
418 	else
419 		fTabWidthSetting = B_WIDTH_AS_USUAL;
420 
421 	if (archive->FindFloat("_high", &fTabHeight) != B_OK) {
422 		font_height fh;
423 		GetFontHeight(&fh);
424 		fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
425 	}
426 
427 	if (archive->FindInt32("_sel", &fSelection) != B_OK)
428 		fSelection = -1;
429 
430 	if (archive->FindInt32("_border_style", (int32*)&fBorderStyle) != B_OK)
431 		fBorderStyle = B_FANCY_BORDER;
432 
433 	if (archive->FindInt32("_TabSide", (int32*)&fTabSide) != B_OK)
434 		fTabSide = kTopSide;
435 
436 	int32 i = 0;
437 	BMessage tabMsg;
438 
439 	if (BUnarchiver::IsArchiveManaged(archive)) {
440 		int32 tabCount;
441 		archive->GetInfo("_l_items", NULL, &tabCount);
442 		for (int32 i = 0; i < tabCount; i++) {
443 			unarchiver.EnsureUnarchived("_l_items", i);
444 			unarchiver.EnsureUnarchived("_view_list", i);
445 		}
446 		return;
447 	}
448 
449 	fContainerView = ChildAt(0);
450 	_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
451 
452 	while (archive->FindMessage("_l_items", i, &tabMsg) == B_OK) {
453 		BArchivable* archivedTab = instantiate_object(&tabMsg);
454 
455 		if (archivedTab) {
456 			BTab* tab = dynamic_cast<BTab*>(archivedTab);
457 
458 			BMessage viewMsg;
459 			if (archive->FindMessage("_view_list", i, &viewMsg) == B_OK) {
460 				BArchivable* archivedView = instantiate_object(&viewMsg);
461 				if (archivedView)
462 					AddTab(dynamic_cast<BView*>(archivedView), tab);
463 			}
464 		}
465 
466 		tabMsg.MakeEmpty();
467 		i++;
468 	}
469 }
470 
471 
472 BArchivable*
473 BTabView::Instantiate(BMessage* archive)
474 {
475 	if ( validate_instantiation(archive, "BTabView"))
476 		return new BTabView(archive);
477 
478 	return NULL;
479 }
480 
481 
482 status_t
483 BTabView::Archive(BMessage* archive, bool deep) const
484 {
485 	BArchiver archiver(archive);
486 
487 	status_t result = BView::Archive(archive, deep);
488 
489 	if (result == B_OK)
490 		result = archive->AddInt16("_but_width", fTabWidthSetting);
491 	if (result == B_OK)
492 		result = archive->AddFloat("_high", fTabHeight);
493 	if (result == B_OK)
494 		result = archive->AddInt32("_sel", fSelection);
495 	if (result == B_OK && fBorderStyle != B_FANCY_BORDER)
496 		result = archive->AddInt32("_border_style", fBorderStyle);
497 	if (result == B_OK && fTabSide != kTopSide)
498 		result = archive->AddInt32("_TabSide", fTabSide);
499 
500 	if (result == B_OK && deep) {
501 		for (int32 i = 0; i < CountTabs(); i++) {
502 			BTab* tab = TabAt(i);
503 
504 			if ((result = archiver.AddArchivable("_l_items", tab, deep))
505 					!= B_OK) {
506 				break;
507 			}
508 			result = archiver.AddArchivable("_view_list", tab->View(), deep);
509 		}
510 	}
511 
512 	return archiver.Finish(result);
513 }
514 
515 
516 status_t
517 BTabView::AllUnarchived(const BMessage* archive)
518 {
519 	status_t err = BView::AllUnarchived(archive);
520 	if (err != B_OK)
521 		return err;
522 
523 	fContainerView = ChildAt(0);
524 	_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
525 
526 	BUnarchiver unarchiver(archive);
527 
528 	int32 tabCount;
529 	archive->GetInfo("_l_items", NULL, &tabCount);
530 	for (int32 i = 0; i < tabCount && err == B_OK; i++) {
531 		BTab* tab;
532 		err = unarchiver.FindObject("_l_items", i, tab);
533 		if (err == B_OK && tab) {
534 			BView* view;
535 			if ((err = unarchiver.FindObject("_view_list", i,
536 				BUnarchiver::B_DONT_ASSUME_OWNERSHIP, view)) != B_OK)
537 				break;
538 
539 			tab->SetView(view);
540 			fTabList->AddItem(tab);
541 		}
542 	}
543 
544 	if (err == B_OK)
545 		Select(fSelection);
546 
547 	return err;
548 }
549 
550 
551 status_t
552 BTabView::Perform(perform_code code, void* _data)
553 {
554 	switch (code) {
555 		case PERFORM_CODE_ALL_UNARCHIVED:
556 		{
557 			perform_data_all_unarchived* data
558 				= (perform_data_all_unarchived*)_data;
559 
560 			data->return_value = BTabView::AllUnarchived(data->archive);
561 			return B_OK;
562 		}
563 	}
564 
565 	return BView::Perform(code, _data);
566 }
567 
568 
569 void
570 BTabView::AttachedToWindow()
571 {
572 	BView::AttachedToWindow();
573 
574 	if (fSelection < 0 && CountTabs() > 0)
575 		Select(0);
576 }
577 
578 
579 void
580 BTabView::DetachedFromWindow()
581 {
582 	BView::DetachedFromWindow();
583 }
584 
585 
586 void
587 BTabView::AllAttached()
588 {
589 	BView::AllAttached();
590 }
591 
592 
593 void
594 BTabView::AllDetached()
595 {
596 	BView::AllDetached();
597 }
598 
599 
600 // #pragma mark -
601 
602 
603 void
604 BTabView::MessageReceived(BMessage* message)
605 {
606 	switch (message->what) {
607 		case B_GET_PROPERTY:
608 		case B_SET_PROPERTY:
609 		{
610 			BMessage reply(B_REPLY);
611 			bool handled = false;
612 
613 			BMessage specifier;
614 			int32 index;
615 			int32 form;
616 			const char* property;
617 			if (message->GetCurrentSpecifier(&index, &specifier, &form,
618 					&property) == B_OK) {
619 				if (strcmp(property, "Selection") == 0) {
620 					if (message->what == B_GET_PROPERTY) {
621 						reply.AddInt32("result", fSelection);
622 						handled = true;
623 					} else {
624 						// B_GET_PROPERTY
625 						int32 selection;
626 						if (message->FindInt32("data", &selection) == B_OK) {
627 							Select(selection);
628 							reply.AddInt32("error", B_OK);
629 							handled = true;
630 						}
631 					}
632 				}
633 			}
634 
635 			if (handled)
636 				message->SendReply(&reply);
637 			else
638 				BView::MessageReceived(message);
639 			break;
640 		}
641 
642 #if 0
643 		case B_MOUSE_WHEEL_CHANGED:
644 		{
645 			float deltaX = 0.0f;
646 			float deltaY = 0.0f;
647 			message->FindFloat("be:wheel_delta_x", &deltaX);
648 			message->FindFloat("be:wheel_delta_y", &deltaY);
649 
650 			if (deltaX == 0.0f && deltaY == 0.0f)
651 				return;
652 
653 			if (deltaY == 0.0f)
654 				deltaY = deltaX;
655 
656 			int32 selection = Selection();
657 			int32 numTabs = CountTabs();
658 			if (deltaY > 0  && selection < numTabs - 1) {
659 				// move to the right tab.
660 				Select(Selection() + 1);
661 			} else if (deltaY < 0 && selection > 0 && numTabs > 1) {
662 				// move to the left tab.
663 				Select(selection - 1);
664 			}
665 			break;
666 		}
667 #endif
668 
669 		default:
670 			BView::MessageReceived(message);
671 			break;
672 	}
673 }
674 
675 
676 void
677 BTabView::KeyDown(const char* bytes, int32 numBytes)
678 {
679 	if (IsHidden())
680 		return;
681 
682 	switch (bytes[0]) {
683 		case B_DOWN_ARROW:
684 		case B_LEFT_ARROW: {
685 			int32 focus = fFocus - 1;
686 			if (focus < 0)
687 				focus = CountTabs() - 1;
688 			SetFocusTab(focus, true);
689 			break;
690 		}
691 
692 		case B_UP_ARROW:
693 		case B_RIGHT_ARROW: {
694 			int32 focus = fFocus + 1;
695 			if (focus >= CountTabs())
696 				focus = 0;
697 			SetFocusTab(focus, true);
698 			break;
699 		}
700 
701 		case B_RETURN:
702 		case B_SPACE:
703 			Select(FocusTab());
704 			break;
705 
706 		default:
707 			BView::KeyDown(bytes, numBytes);
708 	}
709 }
710 
711 
712 void
713 BTabView::MouseDown(BPoint where)
714 {
715 	for (int32 i = 0; i < CountTabs(); i++) {
716 		if (TabFrame(i).Contains(where)
717 			&& i != Selection()) {
718 			Select(i);
719 			return;
720 		}
721 	}
722 
723 	BView::MouseDown(where);
724 }
725 
726 
727 void
728 BTabView::MouseUp(BPoint where)
729 {
730 	BView::MouseUp(where);
731 }
732 
733 
734 void
735 BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
736 {
737 	BView::MouseMoved(where, transit, dragMessage);
738 }
739 
740 
741 void
742 BTabView::Pulse()
743 {
744 	BView::Pulse();
745 }
746 
747 
748 void
749 BTabView::Select(int32 index)
750 {
751 	if (index == Selection())
752 		return;
753 
754 	if (index < 0 || index >= CountTabs())
755 		index = Selection();
756 
757 	BTab* tab = TabAt(Selection());
758 
759 	if (tab)
760 		tab->Deselect();
761 
762 	tab = TabAt(index);
763 	if (tab != NULL && fContainerView != NULL) {
764 		if (index == 0)
765 			fTabOffset = 0.0f;
766 
767 		tab->Select(fContainerView);
768 		fSelection = index;
769 
770 		// make the view visible through the layout if there is one
771 		BCardLayout* layout
772 			= dynamic_cast<BCardLayout*>(fContainerView->GetLayout());
773 		if (layout != NULL)
774 			layout->SetVisibleItem(index);
775 	}
776 
777 	Invalidate();
778 
779 	if (index != 0 && !Bounds().Contains(TabFrame(index))){
780 		if (!Bounds().Contains(TabFrame(index).LeftTop()))
781 			fTabOffset += TabFrame(index).left - Bounds().left - 20.0f;
782 		else
783 			fTabOffset += TabFrame(index).right - Bounds().right + 20.0f;
784 
785 		Invalidate();
786 	}
787 
788 	SetFocusTab(index, true);
789 }
790 
791 
792 int32
793 BTabView::Selection() const
794 {
795 	return fSelection;
796 }
797 
798 
799 void
800 BTabView::WindowActivated(bool active)
801 {
802 	BView::WindowActivated(active);
803 
804 	if (IsFocus())
805 		Invalidate();
806 }
807 
808 
809 void
810 BTabView::MakeFocus(bool focus)
811 {
812 	BView::MakeFocus(focus);
813 
814 	SetFocusTab(Selection(), focus);
815 }
816 
817 
818 void
819 BTabView::SetFocusTab(int32 tab, bool focus)
820 {
821 	if (tab >= CountTabs())
822 		tab = 0;
823 
824 	if (tab < 0)
825 		tab = CountTabs() - 1;
826 
827 	if (focus) {
828 		if (tab == fFocus)
829 			return;
830 
831 		if (fFocus != -1){
832 			if (TabAt (fFocus) != NULL)
833 				TabAt(fFocus)->MakeFocus(false);
834 			Invalidate(TabFrame(fFocus));
835 		}
836 		if (TabAt(tab) != NULL){
837 			TabAt(tab)->MakeFocus(true);
838 			Invalidate(TabFrame(tab));
839 			fFocus = tab;
840 		}
841 	} else if (fFocus != -1) {
842 		TabAt(fFocus)->MakeFocus(false);
843 		Invalidate(TabFrame(fFocus));
844 		fFocus = -1;
845 	}
846 }
847 
848 
849 int32
850 BTabView::FocusTab() const
851 {
852 	return fFocus;
853 }
854 
855 
856 void
857 BTabView::Draw(BRect updateRect)
858 {
859 	DrawTabs();
860 	DrawBox(TabFrame(fSelection));
861 
862 	if (IsFocus() && fFocus != -1)
863 		TabAt(fFocus)->DrawFocusMark(this, TabFrame(fFocus));
864 }
865 
866 
867 BRect
868 BTabView::DrawTabs()
869 {
870 	BRect bounds(Bounds());
871 	BRect tabsBounds;
872 	uint32 borders = 0;
873 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
874 	if (fTabSide == kTopSide || fTabSide == kBottomSide) {
875 		if (fTabSide == kTopSide)
876 			bounds.bottom = fTabHeight;
877 		else
878 			bounds.top = bounds.bottom - fTabHeight;
879 		tabsBounds = bounds;
880 			// make a copy for later
881 
882 		// draw an inactive tab frame behind all tabs
883 		borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
884 		if (fBorderStyle == B_NO_BORDER) {
885 			// removes left border that is an artifact of DrawInactiveTab()
886 			bounds.left -= 1;
887 		} else {
888 			borders |= BControlLook::B_LEFT_BORDER
889 				| BControlLook::B_RIGHT_BORDER;
890 		}
891 
892 		// DrawInactiveTab draws 2px border
893 		// draw a little wider tab frame to align B_PLAIN_BORDER with it
894 		if (fBorderStyle == B_PLAIN_BORDER) {
895 			bounds.left -= 1;
896 			bounds.right += 1;
897 		}
898 	} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
899 		if (fTabSide == kLeftSide)
900 			bounds.right = fTabHeight;
901 		else
902 			bounds.left = bounds.right - fTabHeight;
903 		tabsBounds = bounds;
904 			// make a copy for later
905 
906 		// draw an inactive tab frame behind all tabs
907 		borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
908 		if (fBorderStyle == B_NO_BORDER) {
909 			// removes top border that is an artifact of DrawInactiveTab()
910 			bounds.top -= 1;
911 		} else {
912 			borders |= BControlLook::B_TOP_BORDER
913 				| BControlLook::B_BOTTOM_BORDER;
914 		}
915 
916 		// DrawInactiveTab draws 2px border
917 		// draw a little wider tab frame to align B_PLAIN_BORDER with it
918 		if (fBorderStyle == B_PLAIN_BORDER) {
919 			bounds.top -= 1;
920 			bounds.bottom += 1;
921 		}
922 	}
923 
924 	be_control_look->DrawInactiveTab(this, bounds, bounds, base, 0,
925 		borders, fTabSide);
926 
927 	// draw the tabs on top of the inactive tab bounds
928 	BRect activeTabFrame;
929 	int32 tabCount = CountTabs();
930 	for (int32 i = 0; i < tabCount; i++) {
931 		BRect tabFrame = TabFrame(i);
932 		if (i == fSelection)
933 			activeTabFrame = tabFrame;
934 
935 		TabAt(i)->DrawTab(this, tabFrame,
936 			i == fSelection ? B_TAB_FRONT :
937 				(i == 0) ? B_TAB_FIRST : B_TAB_ANY,
938 			i + 1 != fSelection);
939 	}
940 
941 	float last = 0.0f;
942 	float lastTab = 0.0f;
943 	if (fTabSide == kTopSide || fTabSide == kBottomSide) {
944 		lastTab = TabFrame(tabCount - 1).right;
945 		last = bounds.right;
946 		tabsBounds.left = tabsBounds.right = lastTab;
947 		borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
948 	} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
949 		lastTab = TabFrame(tabCount - 1).bottom;
950 		last = bounds.bottom;
951 		tabsBounds.top = tabsBounds.bottom = lastTab;
952 		borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
953 	}
954 
955 	if (lastTab < last) {
956 		// draw a 1px right border on the last tab
957 		be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0,
958 			borders, fTabSide);
959 	}
960 
961 	return fSelection < CountTabs() ? TabFrame(fSelection) : BRect();
962 }
963 
964 
965 void
966 BTabView::DrawBox(BRect selectedTabRect)
967 {
968 	BRect rect(Bounds());
969 	uint32 bordersToDraw = BControlLook::B_ALL_BORDERS;
970 	switch (fTabSide) {
971 		case kTopSide:
972 			bordersToDraw &= ~BControlLook::B_TOP_BORDER;
973 			rect.top = fTabHeight;
974 			break;
975 		case kBottomSide:
976 			bordersToDraw &= ~BControlLook::B_BOTTOM_BORDER;
977 			rect.bottom -= fTabHeight;
978 			break;
979 		case kLeftSide:
980 			bordersToDraw &= ~BControlLook::B_LEFT_BORDER;
981 			rect.left = fTabHeight;
982 			break;
983 		case kRightSide:
984 			bordersToDraw &= ~BControlLook::B_RIGHT_BORDER;
985 			rect.right -= fTabHeight;
986 			break;
987 	}
988 
989 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
990 	if (fBorderStyle == B_FANCY_BORDER)
991 		be_control_look->DrawGroupFrame(this, rect, rect, base, bordersToDraw);
992 	else if (fBorderStyle == B_PLAIN_BORDER) {
993 		be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER,
994 			0, bordersToDraw);
995 	} else
996 		; // B_NO_BORDER draws no box
997 }
998 
999 
1000 BRect
1001 BTabView::TabFrame(int32 index) const
1002 {
1003 	if (index >= CountTabs() || index < 0)
1004 		return BRect();
1005 
1006 	float width = 100.0f;
1007 	float height = fTabHeight;
1008 	float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
1009 	BRect bounds(Bounds());
1010 
1011 	switch (fTabWidthSetting) {
1012 		case B_WIDTH_FROM_LABEL:
1013 		{
1014 			float x = 0.0f;
1015 			for (int32 i = 0; i < index; i++){
1016 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
1017 			}
1018 
1019 			switch (fTabSide) {
1020 				case kTopSide:
1021 					return BRect(offset + x, 0.0f,
1022 						offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
1023 						height);
1024 				case kBottomSide:
1025 					return BRect(offset + x, bounds.bottom - height,
1026 						offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
1027 						bounds.bottom);
1028 				case kLeftSide:
1029 					return BRect(0.0f, offset + x, height, offset + x
1030 						+ StringWidth(TabAt(index)->Label()) + 20.0f);
1031 				case kRightSide:
1032 					return BRect(bounds.right - height, offset + x,
1033 						bounds.right, offset + x
1034 							+ StringWidth(TabAt(index)->Label()) + 20.0f);
1035 				default:
1036 					return BRect();
1037 			}
1038 		}
1039 
1040 		case B_WIDTH_FROM_WIDEST:
1041 			width = 0.0;
1042 			for (int32 i = 0; i < CountTabs(); i++) {
1043 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1044 				if (tabWidth > width)
1045 					width = tabWidth;
1046 			}
1047 			// fall through
1048 
1049 		case B_WIDTH_AS_USUAL:
1050 		default:
1051 			switch (fTabSide) {
1052 				case kTopSide:
1053 					return BRect(offset + index * width, 0.0f,
1054 						offset + index * width + width, height);
1055 				case kBottomSide:
1056 					return BRect(offset + index * width, bounds.bottom - height,
1057 						offset + index * width + width, bounds.bottom);
1058 				case kLeftSide:
1059 					return BRect(0.0f, offset + index * width, height,
1060 						offset + index * width + width);
1061 				case kRightSide:
1062 					return BRect(bounds.right - height, offset + index * width,
1063 						bounds.right, offset + index * width + width);
1064 				default:
1065 					return BRect();
1066 			}
1067 	}
1068 }
1069 
1070 
1071 void
1072 BTabView::SetFlags(uint32 flags)
1073 {
1074 	BView::SetFlags(flags);
1075 }
1076 
1077 
1078 void
1079 BTabView::SetResizingMode(uint32 mode)
1080 {
1081 	BView::SetResizingMode(mode);
1082 }
1083 
1084 
1085 // #pragma mark -
1086 
1087 
1088 void
1089 BTabView::ResizeToPreferred()
1090 {
1091 	BView::ResizeToPreferred();
1092 }
1093 
1094 
1095 void
1096 BTabView::GetPreferredSize(float* _width, float* _height)
1097 {
1098 	BView::GetPreferredSize(_width, _height);
1099 }
1100 
1101 
1102 BSize
1103 BTabView::MinSize()
1104 {
1105 	BSize size;
1106 	if (GetLayout())
1107 		size = GetLayout()->MinSize();
1108 	else {
1109 		size = _TabsMinSize();
1110 		BSize containerSize = fContainerView->MinSize();
1111 		containerSize.width += 2 * _BorderWidth();
1112 		containerSize.height += 2 * _BorderWidth();
1113 		if (containerSize.width > size.width)
1114 			size.width = containerSize.width;
1115 		size.height += containerSize.height;
1116 	}
1117 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1118 }
1119 
1120 
1121 BSize
1122 BTabView::MaxSize()
1123 {
1124 	BSize size;
1125 	if (GetLayout())
1126 		size = GetLayout()->MaxSize();
1127 	else {
1128 		size = _TabsMinSize();
1129 		BSize containerSize = fContainerView->MaxSize();
1130 		containerSize.width += 2 * _BorderWidth();
1131 		containerSize.height += 2 * _BorderWidth();
1132 		if (containerSize.width > size.width)
1133 			size.width = containerSize.width;
1134 		size.height += containerSize.height;
1135 	}
1136 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1137 }
1138 
1139 
1140 BSize
1141 BTabView::PreferredSize()
1142 {
1143 	BSize size;
1144 	if (GetLayout() != NULL)
1145 		size = GetLayout()->PreferredSize();
1146 	else {
1147 		size = _TabsMinSize();
1148 		BSize containerSize = fContainerView->PreferredSize();
1149 		containerSize.width += 2 * _BorderWidth();
1150 		containerSize.height += 2 * _BorderWidth();
1151 		if (containerSize.width > size.width)
1152 			size.width = containerSize.width;
1153 		size.height += containerSize.height;
1154 	}
1155 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1156 }
1157 
1158 
1159 void
1160 BTabView::FrameMoved(BPoint newPosition)
1161 {
1162 	BView::FrameMoved(newPosition);
1163 }
1164 
1165 
1166 void
1167 BTabView::FrameResized(float newWidth, float newHeight)
1168 {
1169 	BView::FrameResized(newWidth, newHeight);
1170 }
1171 
1172 
1173 // #pragma mark -
1174 
1175 
1176 BHandler*
1177 BTabView::ResolveSpecifier(BMessage* message, int32 index,
1178 	BMessage* specifier, int32 what, const char* property)
1179 {
1180 	BPropertyInfo propInfo(sPropertyList);
1181 
1182 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1183 		return this;
1184 
1185 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1186 }
1187 
1188 
1189 status_t
1190 BTabView::GetSupportedSuites(BMessage* message)
1191 {
1192 	message->AddString("suites", "suite/vnd.Be-tab-view");
1193 
1194 	BPropertyInfo propInfo(sPropertyList);
1195 	message->AddFlat("messages", &propInfo);
1196 
1197 	return BView::GetSupportedSuites(message);
1198 }
1199 
1200 
1201 // #pragma mark -
1202 
1203 
1204 void
1205 BTabView::AddTab(BView* target, BTab* tab)
1206 {
1207 	if (tab == NULL)
1208 		tab = new BTab(target);
1209 	else
1210 		tab->SetView(target);
1211 
1212 	if (fContainerView->GetLayout())
1213 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1214 
1215 	fTabList->AddItem(tab);
1216 	BTab::Private(tab).SetTabView(this);
1217 
1218 	// When we haven't had a any tabs before, but are already attached to the
1219 	// window, select this one.
1220 	if (CountTabs() == 1 && Window() != NULL)
1221 		Select(0);
1222 }
1223 
1224 
1225 BTab*
1226 BTabView::RemoveTab(int32 index)
1227 {
1228 	if (index < 0 || index >= CountTabs())
1229 		return NULL;
1230 
1231 	BTab* tab = (BTab*)fTabList->RemoveItem(index);
1232 	if (tab == NULL)
1233 		return NULL;
1234 
1235 	tab->Deselect();
1236 	BTab::Private(tab).SetTabView(NULL);
1237 
1238 	if (fContainerView->GetLayout())
1239 		fContainerView->GetLayout()->RemoveItem(index);
1240 
1241 	if (CountTabs() == 0)
1242 		fFocus = -1;
1243 	else if (index <= fSelection)
1244 		Select(fSelection - 1);
1245 
1246 	if (fFocus >= 0) {
1247 		if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1248 			SetFocusTab(fFocus, false);
1249 		else
1250 			SetFocusTab(fFocus, true);
1251 	}
1252 
1253 	return tab;
1254 }
1255 
1256 
1257 BTab*
1258 BTabView::TabAt(int32 index) const
1259 {
1260 	return (BTab*)fTabList->ItemAt(index);
1261 }
1262 
1263 
1264 void
1265 BTabView::SetTabWidth(button_width width)
1266 {
1267 	fTabWidthSetting = width;
1268 
1269 	Invalidate();
1270 }
1271 
1272 
1273 button_width
1274 BTabView::TabWidth() const
1275 {
1276 	return fTabWidthSetting;
1277 }
1278 
1279 
1280 void
1281 BTabView::SetTabHeight(float height)
1282 {
1283 	if (fTabHeight == height)
1284 		return;
1285 
1286 	fTabHeight = height;
1287 	_LayoutContainerView(GetLayout() != NULL);
1288 
1289 	Invalidate();
1290 }
1291 
1292 
1293 float
1294 BTabView::TabHeight() const
1295 {
1296 	return fTabHeight;
1297 }
1298 
1299 
1300 void
1301 BTabView::SetBorder(border_style borderStyle)
1302 {
1303 	if (fBorderStyle == borderStyle)
1304 		return;
1305 
1306 	fBorderStyle = borderStyle;
1307 
1308 	_LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0);
1309 }
1310 
1311 
1312 border_style
1313 BTabView::Border() const
1314 {
1315 	return fBorderStyle;
1316 }
1317 
1318 
1319 void
1320 BTabView::SetTabSide(tab_side tabSide)
1321 {
1322 	if (fTabSide == tabSide)
1323 		return;
1324 
1325 	fTabSide = tabSide;
1326 	_LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
1327 }
1328 
1329 
1330 BTabView::tab_side
1331 BTabView::TabSide() const
1332 {
1333 	return fTabSide;
1334 }
1335 
1336 
1337 BView*
1338 BTabView::ContainerView() const
1339 {
1340 	return fContainerView;
1341 }
1342 
1343 
1344 int32
1345 BTabView::CountTabs() const
1346 {
1347 	return fTabList->CountItems();
1348 }
1349 
1350 
1351 BView*
1352 BTabView::ViewForTab(int32 tabIndex) const
1353 {
1354 	BTab* tab = TabAt(tabIndex);
1355 	if (tab != NULL)
1356 		return tab->View();
1357 
1358 	return NULL;
1359 }
1360 
1361 
1362 void
1363 BTabView::_InitObject(bool layouted, button_width width)
1364 {
1365 	fTabList = new BList;
1366 
1367 	fTabWidthSetting = width;
1368 	fSelection = -1;
1369 	fFocus = -1;
1370 	fTabOffset = 0.0f;
1371 	fBorderStyle = B_FANCY_BORDER;
1372 	fTabSide = kTopSide;
1373 
1374 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1375 	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1376 
1377 	font_height fh;
1378 	GetFontHeight(&fh);
1379 	fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
1380 
1381 	fContainerView = NULL;
1382 	_InitContainerView(layouted);
1383 }
1384 
1385 
1386 void
1387 BTabView::_InitContainerView(bool layouted)
1388 {
1389 	bool needsLayout = false;
1390 	bool createdContainer = false;
1391 	if (layouted) {
1392 		if (GetLayout() == NULL) {
1393 			SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL));
1394 			needsLayout = true;
1395 		}
1396 
1397 		if (fContainerView == NULL) {
1398 			fContainerView = new BView("view container", B_WILL_DRAW);
1399 			fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1400 			createdContainer = true;
1401 		}
1402 	} else if (fContainerView == NULL) {
1403 		fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL,
1404 			B_WILL_DRAW);
1405 		createdContainer = true;
1406 	}
1407 
1408 	if (needsLayout || createdContainer)
1409 		_LayoutContainerView(layouted);
1410 
1411 	if (createdContainer) {
1412 		fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1413 		fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1414 		AddChild(fContainerView);
1415 	}
1416 }
1417 
1418 
1419 BSize
1420 BTabView::_TabsMinSize() const
1421 {
1422 	BSize size(0.0f, TabHeight());
1423 	int32 count = min_c(2, CountTabs());
1424 	for (int32 i = 0; i < count; i++) {
1425 		BRect frame = TabFrame(i);
1426 		size.width += frame.Width();
1427 	}
1428 
1429 	if (count < CountTabs()) {
1430 		// TODO: Add size for yet to be implemented buttons that allow
1431 		// "scrolling" the displayed tabs left/right.
1432 	}
1433 
1434 	return size;
1435 }
1436 
1437 
1438 float
1439 BTabView::_BorderWidth() const
1440 {
1441 	switch (fBorderStyle) {
1442 		default:
1443 		case B_FANCY_BORDER:
1444 			return 3.0f;
1445 
1446 		case B_PLAIN_BORDER:
1447 			return 1.0f;
1448 
1449 		case B_NO_BORDER:
1450 			return 0.0f;
1451 	}
1452 }
1453 
1454 
1455 void
1456 BTabView::_LayoutContainerView(bool layouted)
1457 {
1458 	float borderWidth = _BorderWidth();
1459 	if (layouted) {
1460 		float topBorderOffset;
1461 		switch (fBorderStyle) {
1462 			default:
1463 			case B_FANCY_BORDER:
1464 				topBorderOffset = 1.0f;
1465 				break;
1466 
1467 			case B_PLAIN_BORDER:
1468 				topBorderOffset = 0.0f;
1469 				break;
1470 
1471 			case B_NO_BORDER:
1472 				topBorderOffset = -1.0f;
1473 				break;
1474 		}
1475 		BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
1476 		if (layout != NULL) {
1477 			float inset = borderWidth + TabHeight() - topBorderOffset;
1478 			switch (fTabSide) {
1479 				case kTopSide:
1480 					layout->SetInsets(borderWidth, inset, borderWidth,
1481 						borderWidth);
1482 					break;
1483 				case kBottomSide:
1484 					layout->SetInsets(borderWidth, borderWidth, borderWidth,
1485 						inset);
1486 					break;
1487 				case kLeftSide:
1488 					layout->SetInsets(inset, borderWidth, borderWidth,
1489 						borderWidth);
1490 					break;
1491 				case kRightSide:
1492 					layout->SetInsets(borderWidth, borderWidth, inset,
1493 						borderWidth);
1494 					break;
1495 			}
1496 		}
1497 	} else {
1498 		BRect bounds = Bounds();
1499 		switch (fTabSide) {
1500 			case kTopSide:
1501 				bounds.top += TabHeight();
1502 				break;
1503 			case kBottomSide:
1504 				bounds.bottom -= TabHeight();
1505 				break;
1506 			case kLeftSide:
1507 				bounds.left += TabHeight();
1508 				break;
1509 			case kRightSide:
1510 				bounds.right -= TabHeight();
1511 				break;
1512 		}
1513 		bounds.InsetBy(borderWidth, borderWidth);
1514 
1515 		fContainerView->MoveTo(bounds.left, bounds.top);
1516 		fContainerView->ResizeTo(bounds.Width(), bounds.Height());
1517 	}
1518 }
1519 
1520 
1521 // #pragma mark - FBC and forbidden
1522 
1523 
1524 void BTabView::_ReservedTabView3() {}
1525 void BTabView::_ReservedTabView4() {}
1526 void BTabView::_ReservedTabView5() {}
1527 void BTabView::_ReservedTabView6() {}
1528 void BTabView::_ReservedTabView7() {}
1529 void BTabView::_ReservedTabView8() {}
1530 void BTabView::_ReservedTabView9() {}
1531 void BTabView::_ReservedTabView10() {}
1532 void BTabView::_ReservedTabView11() {}
1533 void BTabView::_ReservedTabView12() {}
1534 
1535 
1536 BTabView::BTabView(const BTabView& tabView)
1537 	: BView(tabView)
1538 {
1539 	// this is private and not functional, but exported
1540 }
1541 
1542 
1543 BTabView&
1544 BTabView::operator=(const BTabView&)
1545 {
1546 	// this is private and not functional, but exported
1547 	return *this;
1548 }
1549 
1550 //	#pragma mark - binary compatibility
1551 
1552 
1553 extern "C" void
1554 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
1555 	BTabView* tabView, border_style borderStyle)
1556 {
1557 	tabView->BTabView::SetBorder(borderStyle);
1558 }
1559 
1560 extern "C" void
1561 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)(
1562 	BTabView* tabView, BTabView::tab_side tabSide)
1563 {
1564 	tabView->BTabView::SetTabSide(tabSide);
1565 }
1566