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