xref: /haiku/src/kits/interface/TabView.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
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 	const float padding = ceilf(be_control_look->DefaultLabelSpacing() * 3.3f);
1020 	const float height = fTabHeight;
1021 	const float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
1022 	const BRect bounds(Bounds());
1023 
1024 	float width = padding * 5.0f;
1025 	switch (fTabWidthSetting) {
1026 		case B_WIDTH_FROM_LABEL:
1027 		{
1028 			float x = 0.0f;
1029 			for (int32 i = 0; i < index; i++){
1030 				x += StringWidth(TabAt(i)->Label()) + padding;
1031 			}
1032 
1033 			switch (fTabSide) {
1034 				case kTopSide:
1035 					return BRect(offset + x, 0.0f,
1036 						offset + x + StringWidth(TabAt(index)->Label()) + padding,
1037 						height);
1038 				case kBottomSide:
1039 					return BRect(offset + x, bounds.bottom - height,
1040 						offset + x + StringWidth(TabAt(index)->Label()) + padding,
1041 						bounds.bottom);
1042 				case kLeftSide:
1043 					return BRect(0.0f, offset + x, height, offset + x
1044 						+ StringWidth(TabAt(index)->Label()) + padding);
1045 				case kRightSide:
1046 					return BRect(bounds.right - height, offset + x,
1047 						bounds.right, offset + x
1048 							+ StringWidth(TabAt(index)->Label()) + padding);
1049 				default:
1050 					return BRect();
1051 			}
1052 		}
1053 
1054 		case B_WIDTH_FROM_WIDEST:
1055 			width = 0.0;
1056 			for (int32 i = 0; i < CountTabs(); i++) {
1057 				float tabWidth = StringWidth(TabAt(i)->Label()) + padding;
1058 				if (tabWidth > width)
1059 					width = tabWidth;
1060 			}
1061 			// fall through
1062 
1063 		case B_WIDTH_AS_USUAL:
1064 		default:
1065 			switch (fTabSide) {
1066 				case kTopSide:
1067 					return BRect(offset + index * width, 0.0f,
1068 						offset + index * width + width, height);
1069 				case kBottomSide:
1070 					return BRect(offset + index * width, bounds.bottom - height,
1071 						offset + index * width + width, bounds.bottom);
1072 				case kLeftSide:
1073 					return BRect(0.0f, offset + index * width, height,
1074 						offset + index * width + width);
1075 				case kRightSide:
1076 					return BRect(bounds.right - height, offset + index * width,
1077 						bounds.right, offset + index * width + width);
1078 				default:
1079 					return BRect();
1080 			}
1081 	}
1082 }
1083 
1084 
1085 void
1086 BTabView::SetFlags(uint32 flags)
1087 {
1088 	BView::SetFlags(flags);
1089 }
1090 
1091 
1092 void
1093 BTabView::SetResizingMode(uint32 mode)
1094 {
1095 	BView::SetResizingMode(mode);
1096 }
1097 
1098 
1099 // #pragma mark -
1100 
1101 
1102 void
1103 BTabView::ResizeToPreferred()
1104 {
1105 	BView::ResizeToPreferred();
1106 }
1107 
1108 
1109 void
1110 BTabView::GetPreferredSize(float* _width, float* _height)
1111 {
1112 	BView::GetPreferredSize(_width, _height);
1113 }
1114 
1115 
1116 BSize
1117 BTabView::MinSize()
1118 {
1119 	BSize size;
1120 	if (GetLayout())
1121 		size = GetLayout()->MinSize();
1122 	else {
1123 		size = _TabsMinSize();
1124 		BSize containerSize = fContainerView->MinSize();
1125 		containerSize.width += 2 * _BorderWidth();
1126 		containerSize.height += 2 * _BorderWidth();
1127 		if (containerSize.width > size.width)
1128 			size.width = containerSize.width;
1129 		size.height += containerSize.height;
1130 	}
1131 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1132 }
1133 
1134 
1135 BSize
1136 BTabView::MaxSize()
1137 {
1138 	BSize size;
1139 	if (GetLayout())
1140 		size = GetLayout()->MaxSize();
1141 	else {
1142 		size = _TabsMinSize();
1143 		BSize containerSize = fContainerView->MaxSize();
1144 		containerSize.width += 2 * _BorderWidth();
1145 		containerSize.height += 2 * _BorderWidth();
1146 		if (containerSize.width > size.width)
1147 			size.width = containerSize.width;
1148 		size.height += containerSize.height;
1149 	}
1150 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1151 }
1152 
1153 
1154 BSize
1155 BTabView::PreferredSize()
1156 {
1157 	BSize size;
1158 	if (GetLayout() != NULL)
1159 		size = GetLayout()->PreferredSize();
1160 	else {
1161 		size = _TabsMinSize();
1162 		BSize containerSize = fContainerView->PreferredSize();
1163 		containerSize.width += 2 * _BorderWidth();
1164 		containerSize.height += 2 * _BorderWidth();
1165 		if (containerSize.width > size.width)
1166 			size.width = containerSize.width;
1167 		size.height += containerSize.height;
1168 	}
1169 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1170 }
1171 
1172 
1173 void
1174 BTabView::FrameMoved(BPoint newPosition)
1175 {
1176 	BView::FrameMoved(newPosition);
1177 }
1178 
1179 
1180 void
1181 BTabView::FrameResized(float newWidth, float newHeight)
1182 {
1183 	BView::FrameResized(newWidth, newHeight);
1184 }
1185 
1186 
1187 // #pragma mark -
1188 
1189 
1190 BHandler*
1191 BTabView::ResolveSpecifier(BMessage* message, int32 index,
1192 	BMessage* specifier, int32 what, const char* property)
1193 {
1194 	BPropertyInfo propInfo(sPropertyList);
1195 
1196 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1197 		return this;
1198 
1199 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1200 }
1201 
1202 
1203 status_t
1204 BTabView::GetSupportedSuites(BMessage* message)
1205 {
1206 	message->AddString("suites", "suite/vnd.Be-tab-view");
1207 
1208 	BPropertyInfo propInfo(sPropertyList);
1209 	message->AddFlat("messages", &propInfo);
1210 
1211 	return BView::GetSupportedSuites(message);
1212 }
1213 
1214 
1215 // #pragma mark -
1216 
1217 
1218 void
1219 BTabView::AddTab(BView* target, BTab* tab)
1220 {
1221 	if (tab == NULL)
1222 		tab = new BTab(target);
1223 	else
1224 		tab->SetView(target);
1225 
1226 	if (fContainerView->GetLayout())
1227 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1228 
1229 	fTabList->AddItem(tab);
1230 	BTab::Private(tab).SetTabView(this);
1231 
1232 	// When we haven't had a any tabs before, but are already attached to the
1233 	// window, select this one.
1234 	if (CountTabs() == 1 && Window() != NULL)
1235 		Select(0);
1236 }
1237 
1238 
1239 BTab*
1240 BTabView::RemoveTab(int32 index)
1241 {
1242 	if (index < 0 || index >= CountTabs())
1243 		return NULL;
1244 
1245 	BTab* tab = (BTab*)fTabList->RemoveItem(index);
1246 	if (tab == NULL)
1247 		return NULL;
1248 
1249 	tab->Deselect();
1250 	BTab::Private(tab).SetTabView(NULL);
1251 
1252 	if (fContainerView->GetLayout())
1253 		fContainerView->GetLayout()->RemoveItem(index);
1254 
1255 	if (CountTabs() == 0)
1256 		fFocus = -1;
1257 	else if (index <= fSelection)
1258 		Select(fSelection - 1);
1259 
1260 	if (fFocus >= 0) {
1261 		if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1262 			SetFocusTab(fFocus, false);
1263 		else
1264 			SetFocusTab(fFocus, true);
1265 	}
1266 
1267 	return tab;
1268 }
1269 
1270 
1271 BTab*
1272 BTabView::TabAt(int32 index) const
1273 {
1274 	return (BTab*)fTabList->ItemAt(index);
1275 }
1276 
1277 
1278 void
1279 BTabView::SetTabWidth(button_width width)
1280 {
1281 	fTabWidthSetting = width;
1282 
1283 	Invalidate();
1284 }
1285 
1286 
1287 button_width
1288 BTabView::TabWidth() const
1289 {
1290 	return fTabWidthSetting;
1291 }
1292 
1293 
1294 void
1295 BTabView::SetTabHeight(float height)
1296 {
1297 	if (fTabHeight == height)
1298 		return;
1299 
1300 	fTabHeight = height;
1301 	_LayoutContainerView(GetLayout() != NULL);
1302 
1303 	Invalidate();
1304 }
1305 
1306 
1307 float
1308 BTabView::TabHeight() const
1309 {
1310 	return fTabHeight;
1311 }
1312 
1313 
1314 void
1315 BTabView::SetBorder(border_style borderStyle)
1316 {
1317 	if (fBorderStyle == borderStyle)
1318 		return;
1319 
1320 	fBorderStyle = borderStyle;
1321 
1322 	_LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0);
1323 }
1324 
1325 
1326 border_style
1327 BTabView::Border() const
1328 {
1329 	return fBorderStyle;
1330 }
1331 
1332 
1333 void
1334 BTabView::SetTabSide(tab_side tabSide)
1335 {
1336 	if (fTabSide == tabSide)
1337 		return;
1338 
1339 	fTabSide = tabSide;
1340 	_LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
1341 }
1342 
1343 
1344 BTabView::tab_side
1345 BTabView::TabSide() const
1346 {
1347 	return fTabSide;
1348 }
1349 
1350 
1351 BView*
1352 BTabView::ContainerView() const
1353 {
1354 	return fContainerView;
1355 }
1356 
1357 
1358 int32
1359 BTabView::CountTabs() const
1360 {
1361 	return fTabList->CountItems();
1362 }
1363 
1364 
1365 BView*
1366 BTabView::ViewForTab(int32 tabIndex) const
1367 {
1368 	BTab* tab = TabAt(tabIndex);
1369 	if (tab != NULL)
1370 		return tab->View();
1371 
1372 	return NULL;
1373 }
1374 
1375 
1376 int32
1377 BTabView::IndexOf(BTab* tab) const
1378 {
1379 	if (tab != NULL) {
1380 		int32 tabCount = CountTabs();
1381 		for (int32 index = 0; index < tabCount; index++) {
1382 			if (TabAt(index) == tab)
1383 				return index;
1384 		}
1385 	}
1386 
1387 	return -1;
1388 }
1389 
1390 
1391 void
1392 BTabView::_InitObject(bool layouted, button_width width)
1393 {
1394 	fTabList = new BList;
1395 
1396 	fTabWidthSetting = width;
1397 	fSelection = -1;
1398 	fFocus = -1;
1399 	fTabOffset = 0.0f;
1400 	fBorderStyle = B_FANCY_BORDER;
1401 	fTabSide = kTopSide;
1402 
1403 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1404 	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1405 
1406 	font_height fh;
1407 	GetFontHeight(&fh);
1408 	fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading +
1409 		(be_control_look->DefaultLabelSpacing() * 1.3f));
1410 
1411 	fContainerView = NULL;
1412 	_InitContainerView(layouted);
1413 }
1414 
1415 
1416 void
1417 BTabView::_InitContainerView(bool layouted)
1418 {
1419 	bool needsLayout = false;
1420 	bool createdContainer = false;
1421 	if (layouted) {
1422 		if (GetLayout() == NULL) {
1423 			SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL));
1424 			needsLayout = true;
1425 		}
1426 
1427 		if (fContainerView == NULL) {
1428 			fContainerView = new BView("view container", B_WILL_DRAW);
1429 			fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1430 			createdContainer = true;
1431 		}
1432 	} else if (fContainerView == NULL) {
1433 		fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL,
1434 			B_WILL_DRAW);
1435 		createdContainer = true;
1436 	}
1437 
1438 	if (needsLayout || createdContainer)
1439 		_LayoutContainerView(layouted);
1440 
1441 	if (createdContainer) {
1442 		fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1443 		fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1444 		AddChild(fContainerView);
1445 	}
1446 }
1447 
1448 
1449 BSize
1450 BTabView::_TabsMinSize() const
1451 {
1452 	BSize size(0.0f, TabHeight());
1453 	int32 count = min_c(2, CountTabs());
1454 	for (int32 i = 0; i < count; i++) {
1455 		BRect frame = TabFrame(i);
1456 		size.width += frame.Width();
1457 	}
1458 
1459 	if (count < CountTabs()) {
1460 		// TODO: Add size for yet to be implemented buttons that allow
1461 		// "scrolling" the displayed tabs left/right.
1462 	}
1463 
1464 	return size;
1465 }
1466 
1467 
1468 float
1469 BTabView::_BorderWidth() const
1470 {
1471 	switch (fBorderStyle) {
1472 		default:
1473 		case B_FANCY_BORDER:
1474 			return 3.0f;
1475 
1476 		case B_PLAIN_BORDER:
1477 			return 1.0f;
1478 
1479 		case B_NO_BORDER:
1480 			return 0.0f;
1481 	}
1482 }
1483 
1484 
1485 void
1486 BTabView::_LayoutContainerView(bool layouted)
1487 {
1488 	float borderWidth = _BorderWidth();
1489 	if (layouted) {
1490 		float topBorderOffset;
1491 		switch (fBorderStyle) {
1492 			default:
1493 			case B_FANCY_BORDER:
1494 				topBorderOffset = 1.0f;
1495 				break;
1496 
1497 			case B_PLAIN_BORDER:
1498 				topBorderOffset = 0.0f;
1499 				break;
1500 
1501 			case B_NO_BORDER:
1502 				topBorderOffset = -1.0f;
1503 				break;
1504 		}
1505 		BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
1506 		if (layout != NULL) {
1507 			float inset = borderWidth + TabHeight() - topBorderOffset;
1508 			switch (fTabSide) {
1509 				case kTopSide:
1510 					layout->SetInsets(borderWidth, inset, borderWidth,
1511 						borderWidth);
1512 					break;
1513 				case kBottomSide:
1514 					layout->SetInsets(borderWidth, borderWidth, borderWidth,
1515 						inset);
1516 					break;
1517 				case kLeftSide:
1518 					layout->SetInsets(inset, borderWidth, borderWidth,
1519 						borderWidth);
1520 					break;
1521 				case kRightSide:
1522 					layout->SetInsets(borderWidth, borderWidth, inset,
1523 						borderWidth);
1524 					break;
1525 			}
1526 		}
1527 	} else {
1528 		BRect bounds = Bounds();
1529 		switch (fTabSide) {
1530 			case kTopSide:
1531 				bounds.top += TabHeight();
1532 				break;
1533 			case kBottomSide:
1534 				bounds.bottom -= TabHeight();
1535 				break;
1536 			case kLeftSide:
1537 				bounds.left += TabHeight();
1538 				break;
1539 			case kRightSide:
1540 				bounds.right -= TabHeight();
1541 				break;
1542 		}
1543 		bounds.InsetBy(borderWidth, borderWidth);
1544 
1545 		fContainerView->MoveTo(bounds.left, bounds.top);
1546 		fContainerView->ResizeTo(bounds.Width(), bounds.Height());
1547 	}
1548 }
1549 
1550 
1551 // #pragma mark - FBC and forbidden
1552 
1553 
1554 void BTabView::_ReservedTabView3() {}
1555 void BTabView::_ReservedTabView4() {}
1556 void BTabView::_ReservedTabView5() {}
1557 void BTabView::_ReservedTabView6() {}
1558 void BTabView::_ReservedTabView7() {}
1559 void BTabView::_ReservedTabView8() {}
1560 void BTabView::_ReservedTabView9() {}
1561 void BTabView::_ReservedTabView10() {}
1562 void BTabView::_ReservedTabView11() {}
1563 void BTabView::_ReservedTabView12() {}
1564 
1565 
1566 BTabView::BTabView(const BTabView& tabView)
1567 	: BView(tabView)
1568 {
1569 	// this is private and not functional, but exported
1570 }
1571 
1572 
1573 BTabView&
1574 BTabView::operator=(const BTabView&)
1575 {
1576 	// this is private and not functional, but exported
1577 	return *this;
1578 }
1579 
1580 //	#pragma mark - binary compatibility
1581 
1582 
1583 extern "C" void
1584 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
1585 	BTabView* tabView, border_style borderStyle)
1586 {
1587 	tabView->BTabView::SetBorder(borderStyle);
1588 }
1589 
1590 extern "C" void
1591 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)(
1592 	BTabView* tabView, BTabView::tab_side tabSide)
1593 {
1594 	tabView->BTabView::SetTabSide(tabSide);
1595 }
1596