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