xref: /haiku/src/kits/interface/TabView.cpp (revision 151a4f7f9354583f183b5fe1ec5f4fe83c6d2262)
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 tabFrame(bounds);
872 	uint32 borders = 0;
873 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
874 
875 	// set tabFrame to area around tabs
876 	if (fTabSide == kTopSide || fTabSide == kBottomSide) {
877 		if (fTabSide == kTopSide)
878 			tabFrame.bottom = fTabHeight;
879 		else
880 			tabFrame.top = tabFrame.bottom - fTabHeight;
881 	} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
882 		if (fTabSide == kLeftSide)
883 			tabFrame.right = fTabHeight;
884 		else
885 			tabFrame.left = tabFrame.right - fTabHeight;
886 	}
887 
888 	// draw frame behind tabs
889 	be_control_look->DrawTabFrame(this, tabFrame, bounds, base, 0,
890 		borders, fBorderStyle, fTabSide);
891 
892 	// draw the tabs on top of the tab frame
893 	BRect activeTabFrame;
894 	int32 tabCount = CountTabs();
895 	for (int32 i = 0; i < tabCount; i++) {
896 		BRect tabFrame = TabFrame(i);
897 		if (i == fSelection)
898 			activeTabFrame = tabFrame;
899 
900 		TabAt(i)->DrawTab(this, tabFrame,
901 			i == fSelection ? B_TAB_FRONT :
902 				(i == 0) ? B_TAB_FIRST : B_TAB_ANY,
903 			i + 1 != fSelection);
904 	}
905 
906 	BRect tabsBounds;
907 	float last = 0.0f;
908 	float lastTab = 0.0f;
909 	if (fTabSide == kTopSide || fTabSide == kBottomSide) {
910 		lastTab = TabFrame(tabCount - 1).right;
911 		last = tabFrame.right;
912 		tabsBounds.left = tabsBounds.right = lastTab;
913 		borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
914 	} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
915 		lastTab = TabFrame(tabCount - 1).bottom;
916 		last = tabFrame.bottom;
917 		tabsBounds.top = tabsBounds.bottom = lastTab;
918 		borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
919 	}
920 
921 	if (lastTab < last) {
922 		// draw a 1px right border on the last tab
923 		be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0,
924 			borders, fTabSide);
925 	}
926 
927 	return fSelection < CountTabs() ? TabFrame(fSelection) : BRect();
928 }
929 
930 
931 void
932 BTabView::DrawBox(BRect selectedTabRect)
933 {
934 	BRect rect(Bounds());
935 	uint32 bordersToDraw = BControlLook::B_ALL_BORDERS;
936 	switch (fTabSide) {
937 		case kTopSide:
938 			bordersToDraw &= ~BControlLook::B_TOP_BORDER;
939 			rect.top = fTabHeight;
940 			break;
941 		case kBottomSide:
942 			bordersToDraw &= ~BControlLook::B_BOTTOM_BORDER;
943 			rect.bottom -= fTabHeight;
944 			break;
945 		case kLeftSide:
946 			bordersToDraw &= ~BControlLook::B_LEFT_BORDER;
947 			rect.left = fTabHeight;
948 			break;
949 		case kRightSide:
950 			bordersToDraw &= ~BControlLook::B_RIGHT_BORDER;
951 			rect.right -= fTabHeight;
952 			break;
953 	}
954 
955 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
956 	if (fBorderStyle == B_FANCY_BORDER)
957 		be_control_look->DrawGroupFrame(this, rect, rect, base, bordersToDraw);
958 	else if (fBorderStyle == B_PLAIN_BORDER) {
959 		be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER,
960 			0, bordersToDraw);
961 	} else
962 		; // B_NO_BORDER draws no box
963 }
964 
965 
966 BRect
967 BTabView::TabFrame(int32 index) const
968 {
969 	if (index >= CountTabs() || index < 0)
970 		return BRect();
971 
972 	float width = 100.0f;
973 	float height = fTabHeight;
974 	float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
975 	BRect bounds(Bounds());
976 
977 	switch (fTabWidthSetting) {
978 		case B_WIDTH_FROM_LABEL:
979 		{
980 			float x = 0.0f;
981 			for (int32 i = 0; i < index; i++){
982 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
983 			}
984 
985 			switch (fTabSide) {
986 				case kTopSide:
987 					return BRect(offset + x, 0.0f,
988 						offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
989 						height);
990 				case kBottomSide:
991 					return BRect(offset + x, bounds.bottom - height,
992 						offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
993 						bounds.bottom);
994 				case kLeftSide:
995 					return BRect(0.0f, offset + x, height, offset + x
996 						+ StringWidth(TabAt(index)->Label()) + 20.0f);
997 				case kRightSide:
998 					return BRect(bounds.right - height, offset + x,
999 						bounds.right, offset + x
1000 							+ StringWidth(TabAt(index)->Label()) + 20.0f);
1001 				default:
1002 					return BRect();
1003 			}
1004 		}
1005 
1006 		case B_WIDTH_FROM_WIDEST:
1007 			width = 0.0;
1008 			for (int32 i = 0; i < CountTabs(); i++) {
1009 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1010 				if (tabWidth > width)
1011 					width = tabWidth;
1012 			}
1013 			// fall through
1014 
1015 		case B_WIDTH_AS_USUAL:
1016 		default:
1017 			switch (fTabSide) {
1018 				case kTopSide:
1019 					return BRect(offset + index * width, 0.0f,
1020 						offset + index * width + width, height);
1021 				case kBottomSide:
1022 					return BRect(offset + index * width, bounds.bottom - height,
1023 						offset + index * width + width, bounds.bottom);
1024 				case kLeftSide:
1025 					return BRect(0.0f, offset + index * width, height,
1026 						offset + index * width + width);
1027 				case kRightSide:
1028 					return BRect(bounds.right - height, offset + index * width,
1029 						bounds.right, offset + index * width + width);
1030 				default:
1031 					return BRect();
1032 			}
1033 	}
1034 }
1035 
1036 
1037 void
1038 BTabView::SetFlags(uint32 flags)
1039 {
1040 	BView::SetFlags(flags);
1041 }
1042 
1043 
1044 void
1045 BTabView::SetResizingMode(uint32 mode)
1046 {
1047 	BView::SetResizingMode(mode);
1048 }
1049 
1050 
1051 // #pragma mark -
1052 
1053 
1054 void
1055 BTabView::ResizeToPreferred()
1056 {
1057 	BView::ResizeToPreferred();
1058 }
1059 
1060 
1061 void
1062 BTabView::GetPreferredSize(float* _width, float* _height)
1063 {
1064 	BView::GetPreferredSize(_width, _height);
1065 }
1066 
1067 
1068 BSize
1069 BTabView::MinSize()
1070 {
1071 	BSize size;
1072 	if (GetLayout())
1073 		size = GetLayout()->MinSize();
1074 	else {
1075 		size = _TabsMinSize();
1076 		BSize containerSize = fContainerView->MinSize();
1077 		containerSize.width += 2 * _BorderWidth();
1078 		containerSize.height += 2 * _BorderWidth();
1079 		if (containerSize.width > size.width)
1080 			size.width = containerSize.width;
1081 		size.height += containerSize.height;
1082 	}
1083 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1084 }
1085 
1086 
1087 BSize
1088 BTabView::MaxSize()
1089 {
1090 	BSize size;
1091 	if (GetLayout())
1092 		size = GetLayout()->MaxSize();
1093 	else {
1094 		size = _TabsMinSize();
1095 		BSize containerSize = fContainerView->MaxSize();
1096 		containerSize.width += 2 * _BorderWidth();
1097 		containerSize.height += 2 * _BorderWidth();
1098 		if (containerSize.width > size.width)
1099 			size.width = containerSize.width;
1100 		size.height += containerSize.height;
1101 	}
1102 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1103 }
1104 
1105 
1106 BSize
1107 BTabView::PreferredSize()
1108 {
1109 	BSize size;
1110 	if (GetLayout() != NULL)
1111 		size = GetLayout()->PreferredSize();
1112 	else {
1113 		size = _TabsMinSize();
1114 		BSize containerSize = fContainerView->PreferredSize();
1115 		containerSize.width += 2 * _BorderWidth();
1116 		containerSize.height += 2 * _BorderWidth();
1117 		if (containerSize.width > size.width)
1118 			size.width = containerSize.width;
1119 		size.height += containerSize.height;
1120 	}
1121 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1122 }
1123 
1124 
1125 void
1126 BTabView::FrameMoved(BPoint newPosition)
1127 {
1128 	BView::FrameMoved(newPosition);
1129 }
1130 
1131 
1132 void
1133 BTabView::FrameResized(float newWidth, float newHeight)
1134 {
1135 	BView::FrameResized(newWidth, newHeight);
1136 }
1137 
1138 
1139 // #pragma mark -
1140 
1141 
1142 BHandler*
1143 BTabView::ResolveSpecifier(BMessage* message, int32 index,
1144 	BMessage* specifier, int32 what, const char* property)
1145 {
1146 	BPropertyInfo propInfo(sPropertyList);
1147 
1148 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1149 		return this;
1150 
1151 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1152 }
1153 
1154 
1155 status_t
1156 BTabView::GetSupportedSuites(BMessage* message)
1157 {
1158 	message->AddString("suites", "suite/vnd.Be-tab-view");
1159 
1160 	BPropertyInfo propInfo(sPropertyList);
1161 	message->AddFlat("messages", &propInfo);
1162 
1163 	return BView::GetSupportedSuites(message);
1164 }
1165 
1166 
1167 // #pragma mark -
1168 
1169 
1170 void
1171 BTabView::AddTab(BView* target, BTab* tab)
1172 {
1173 	if (tab == NULL)
1174 		tab = new BTab(target);
1175 	else
1176 		tab->SetView(target);
1177 
1178 	if (fContainerView->GetLayout())
1179 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1180 
1181 	fTabList->AddItem(tab);
1182 	BTab::Private(tab).SetTabView(this);
1183 
1184 	// When we haven't had a any tabs before, but are already attached to the
1185 	// window, select this one.
1186 	if (CountTabs() == 1 && Window() != NULL)
1187 		Select(0);
1188 }
1189 
1190 
1191 BTab*
1192 BTabView::RemoveTab(int32 index)
1193 {
1194 	if (index < 0 || index >= CountTabs())
1195 		return NULL;
1196 
1197 	BTab* tab = (BTab*)fTabList->RemoveItem(index);
1198 	if (tab == NULL)
1199 		return NULL;
1200 
1201 	tab->Deselect();
1202 	BTab::Private(tab).SetTabView(NULL);
1203 
1204 	if (fContainerView->GetLayout())
1205 		fContainerView->GetLayout()->RemoveItem(index);
1206 
1207 	if (CountTabs() == 0)
1208 		fFocus = -1;
1209 	else if (index <= fSelection)
1210 		Select(fSelection - 1);
1211 
1212 	if (fFocus >= 0) {
1213 		if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1214 			SetFocusTab(fFocus, false);
1215 		else
1216 			SetFocusTab(fFocus, true);
1217 	}
1218 
1219 	return tab;
1220 }
1221 
1222 
1223 BTab*
1224 BTabView::TabAt(int32 index) const
1225 {
1226 	return (BTab*)fTabList->ItemAt(index);
1227 }
1228 
1229 
1230 void
1231 BTabView::SetTabWidth(button_width width)
1232 {
1233 	fTabWidthSetting = width;
1234 
1235 	Invalidate();
1236 }
1237 
1238 
1239 button_width
1240 BTabView::TabWidth() const
1241 {
1242 	return fTabWidthSetting;
1243 }
1244 
1245 
1246 void
1247 BTabView::SetTabHeight(float height)
1248 {
1249 	if (fTabHeight == height)
1250 		return;
1251 
1252 	fTabHeight = height;
1253 	_LayoutContainerView(GetLayout() != NULL);
1254 
1255 	Invalidate();
1256 }
1257 
1258 
1259 float
1260 BTabView::TabHeight() const
1261 {
1262 	return fTabHeight;
1263 }
1264 
1265 
1266 void
1267 BTabView::SetBorder(border_style borderStyle)
1268 {
1269 	if (fBorderStyle == borderStyle)
1270 		return;
1271 
1272 	fBorderStyle = borderStyle;
1273 
1274 	_LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0);
1275 }
1276 
1277 
1278 border_style
1279 BTabView::Border() const
1280 {
1281 	return fBorderStyle;
1282 }
1283 
1284 
1285 void
1286 BTabView::SetTabSide(tab_side tabSide)
1287 {
1288 	if (fTabSide == tabSide)
1289 		return;
1290 
1291 	fTabSide = tabSide;
1292 	_LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
1293 }
1294 
1295 
1296 BTabView::tab_side
1297 BTabView::TabSide() const
1298 {
1299 	return fTabSide;
1300 }
1301 
1302 
1303 BView*
1304 BTabView::ContainerView() const
1305 {
1306 	return fContainerView;
1307 }
1308 
1309 
1310 int32
1311 BTabView::CountTabs() const
1312 {
1313 	return fTabList->CountItems();
1314 }
1315 
1316 
1317 BView*
1318 BTabView::ViewForTab(int32 tabIndex) const
1319 {
1320 	BTab* tab = TabAt(tabIndex);
1321 	if (tab != NULL)
1322 		return tab->View();
1323 
1324 	return NULL;
1325 }
1326 
1327 
1328 void
1329 BTabView::_InitObject(bool layouted, button_width width)
1330 {
1331 	fTabList = new BList;
1332 
1333 	fTabWidthSetting = width;
1334 	fSelection = -1;
1335 	fFocus = -1;
1336 	fTabOffset = 0.0f;
1337 	fBorderStyle = B_FANCY_BORDER;
1338 	fTabSide = kTopSide;
1339 
1340 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1341 	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1342 
1343 	font_height fh;
1344 	GetFontHeight(&fh);
1345 	fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
1346 
1347 	fContainerView = NULL;
1348 	_InitContainerView(layouted);
1349 }
1350 
1351 
1352 void
1353 BTabView::_InitContainerView(bool layouted)
1354 {
1355 	bool needsLayout = false;
1356 	bool createdContainer = false;
1357 	if (layouted) {
1358 		if (GetLayout() == NULL) {
1359 			SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL));
1360 			needsLayout = true;
1361 		}
1362 
1363 		if (fContainerView == NULL) {
1364 			fContainerView = new BView("view container", B_WILL_DRAW);
1365 			fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1366 			createdContainer = true;
1367 		}
1368 	} else if (fContainerView == NULL) {
1369 		fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL,
1370 			B_WILL_DRAW);
1371 		createdContainer = true;
1372 	}
1373 
1374 	if (needsLayout || createdContainer)
1375 		_LayoutContainerView(layouted);
1376 
1377 	if (createdContainer) {
1378 		fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1379 		fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1380 		AddChild(fContainerView);
1381 	}
1382 }
1383 
1384 
1385 BSize
1386 BTabView::_TabsMinSize() const
1387 {
1388 	BSize size(0.0f, TabHeight());
1389 	int32 count = min_c(2, CountTabs());
1390 	for (int32 i = 0; i < count; i++) {
1391 		BRect frame = TabFrame(i);
1392 		size.width += frame.Width();
1393 	}
1394 
1395 	if (count < CountTabs()) {
1396 		// TODO: Add size for yet to be implemented buttons that allow
1397 		// "scrolling" the displayed tabs left/right.
1398 	}
1399 
1400 	return size;
1401 }
1402 
1403 
1404 float
1405 BTabView::_BorderWidth() const
1406 {
1407 	switch (fBorderStyle) {
1408 		default:
1409 		case B_FANCY_BORDER:
1410 			return 3.0f;
1411 
1412 		case B_PLAIN_BORDER:
1413 			return 1.0f;
1414 
1415 		case B_NO_BORDER:
1416 			return 0.0f;
1417 	}
1418 }
1419 
1420 
1421 void
1422 BTabView::_LayoutContainerView(bool layouted)
1423 {
1424 	float borderWidth = _BorderWidth();
1425 	if (layouted) {
1426 		float topBorderOffset;
1427 		switch (fBorderStyle) {
1428 			default:
1429 			case B_FANCY_BORDER:
1430 				topBorderOffset = 1.0f;
1431 				break;
1432 
1433 			case B_PLAIN_BORDER:
1434 				topBorderOffset = 0.0f;
1435 				break;
1436 
1437 			case B_NO_BORDER:
1438 				topBorderOffset = -1.0f;
1439 				break;
1440 		}
1441 		BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
1442 		if (layout != NULL) {
1443 			float inset = borderWidth + TabHeight() - topBorderOffset;
1444 			switch (fTabSide) {
1445 				case kTopSide:
1446 					layout->SetInsets(borderWidth, inset, borderWidth,
1447 						borderWidth);
1448 					break;
1449 				case kBottomSide:
1450 					layout->SetInsets(borderWidth, borderWidth, borderWidth,
1451 						inset);
1452 					break;
1453 				case kLeftSide:
1454 					layout->SetInsets(inset, borderWidth, borderWidth,
1455 						borderWidth);
1456 					break;
1457 				case kRightSide:
1458 					layout->SetInsets(borderWidth, borderWidth, inset,
1459 						borderWidth);
1460 					break;
1461 			}
1462 		}
1463 	} else {
1464 		BRect bounds = Bounds();
1465 		switch (fTabSide) {
1466 			case kTopSide:
1467 				bounds.top += TabHeight();
1468 				break;
1469 			case kBottomSide:
1470 				bounds.bottom -= TabHeight();
1471 				break;
1472 			case kLeftSide:
1473 				bounds.left += TabHeight();
1474 				break;
1475 			case kRightSide:
1476 				bounds.right -= TabHeight();
1477 				break;
1478 		}
1479 		bounds.InsetBy(borderWidth, borderWidth);
1480 
1481 		fContainerView->MoveTo(bounds.left, bounds.top);
1482 		fContainerView->ResizeTo(bounds.Width(), bounds.Height());
1483 	}
1484 }
1485 
1486 
1487 // #pragma mark - FBC and forbidden
1488 
1489 
1490 void BTabView::_ReservedTabView3() {}
1491 void BTabView::_ReservedTabView4() {}
1492 void BTabView::_ReservedTabView5() {}
1493 void BTabView::_ReservedTabView6() {}
1494 void BTabView::_ReservedTabView7() {}
1495 void BTabView::_ReservedTabView8() {}
1496 void BTabView::_ReservedTabView9() {}
1497 void BTabView::_ReservedTabView10() {}
1498 void BTabView::_ReservedTabView11() {}
1499 void BTabView::_ReservedTabView12() {}
1500 
1501 
1502 BTabView::BTabView(const BTabView& tabView)
1503 	: BView(tabView)
1504 {
1505 	// this is private and not functional, but exported
1506 }
1507 
1508 
1509 BTabView&
1510 BTabView::operator=(const BTabView&)
1511 {
1512 	// this is private and not functional, but exported
1513 	return *this;
1514 }
1515 
1516 //	#pragma mark - binary compatibility
1517 
1518 
1519 extern "C" void
1520 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
1521 	BTabView* tabView, border_style borderStyle)
1522 {
1523 	tabView->BTabView::SetBorder(borderStyle);
1524 }
1525 
1526 extern "C" void
1527 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)(
1528 	BTabView* tabView, BTabView::tab_side tabSide)
1529 {
1530 	tabView->BTabView::SetTabSide(tabSide);
1531 }
1532