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