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