xref: /haiku/src/kits/interface/TabView.cpp (revision adb0d19d561947362090081e81d90dde59142026)
1 /*
2  * Copyright (c) 2001-2008, Haiku, Inc.
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 		frame.left = left;
855 		frame.bottom = fTabHeight;
856 		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
857 		uint32 borders = BControlLook::B_TOP_BORDER
858 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
859 		if (left == 0)
860 			borders |= BControlLook::B_LEFT_BORDER;
861 		be_control_look->DrawInactiveTab(this, frame, frame, base, 0, borders);
862 	}
863 
864 	if (fSelection < CountTabs())
865 		return TabFrame(fSelection);
866 
867 	return BRect();
868 }
869 
870 
871 void
872 BTabView::DrawBox(BRect selTabRect)
873 {
874 	if (be_control_look != NULL) {
875 		BRect rect(Bounds());
876 		rect.top = selTabRect.bottom;
877 
878 //		BRegion clipping(Bounds());
879 //		selTabRect.left += 2;
880 //		selTabRect.right -= 2;
881 //		clipping.Exclude(selTabRect);
882 //		ConstrainClippingRegion(&clipping);
883 
884 		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
885 		be_control_look->DrawGroupFrame(this, rect, rect, base);
886 
887 //		ConstrainClippingRegion(NULL);
888 
889 		return;
890 	}
891 
892 	BRect rect = Bounds();
893 	BRect lastTabRect = TabFrame(CountTabs() - 1);
894 
895 	rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR);
896 	rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT);
897 	rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT);
898 	rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT);
899 	rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT);
900 
901 	BeginLineArray(12);
902 
903 	int32 offset = (int32)ceilf(selTabRect.Height() / 2.0);
904 
905 	// outer lines
906 	AddLine(BPoint(rect.left, rect.bottom - 1),
907 			BPoint(rect.left, selTabRect.bottom), darken2);
908 	if (selTabRect.left >= rect.left + 1)
909 		AddLine(BPoint(rect.left + 1, selTabRect.bottom),
910 				BPoint(selTabRect.left, selTabRect.bottom), darken2);
911 	if (lastTabRect.right + offset + 1 <= rect.right - 1)
912 		AddLine(BPoint(lastTabRect.right + offset + 1, selTabRect.bottom),
913 				BPoint(rect.right - 1, selTabRect.bottom), darken2);
914 	AddLine(BPoint(rect.right, selTabRect.bottom + 2),
915 			BPoint(rect.right, rect.bottom), darken2);
916 	AddLine(BPoint(rect.right - 1, rect.bottom),
917 			BPoint(rect.left + 2, rect.bottom), darken2);
918 
919 	// inner lines
920 	rect.InsetBy(1, 1);
921 	selTabRect.bottom += 1;
922 
923 	AddLine(BPoint(rect.left, rect.bottom - 2),
924 			BPoint(rect.left, selTabRect.bottom), lightenMax);
925 	if (selTabRect.left >= rect.left + 1)
926 		AddLine(BPoint(rect.left + 1, selTabRect.bottom),
927 				BPoint(selTabRect.left, selTabRect.bottom), lightenMax);
928 	if (selTabRect.right + offset + 1 <= rect.right - 2)
929 		AddLine(BPoint(selTabRect.right + offset + 1, selTabRect.bottom),
930 				BPoint(rect.right - 2, selTabRect.bottom), lightenMax);
931 	AddLine(BPoint(rect.right, selTabRect.bottom),
932 			BPoint(rect.right, rect.bottom), darken4);
933 	AddLine(BPoint(rect.right - 1, rect.bottom),
934 			BPoint(rect.left, rect.bottom), darken4);
935 
936 	// soft inner bevel at right/bottom
937 	rect.right--;
938 	rect.bottom--;
939 
940 	AddLine(BPoint(rect.right, selTabRect.bottom + 1),
941 			BPoint(rect.right, rect.bottom), darken1);
942 	AddLine(BPoint(rect.right - 1, rect.bottom),
943 			BPoint(rect.left + 1, rect.bottom), darken1);
944 
945 	EndLineArray();
946 }
947 
948 #define X_OFFSET 0.0f
949 
950 BRect
951 BTabView::TabFrame(int32 tab_index) const
952 {
953 	if (be_control_look != NULL) {
954 		float width = 100.0;
955 		float height = fTabHeight;;
956 		switch (fTabWidthSetting) {
957 			case B_WIDTH_FROM_LABEL:
958 			{
959 				float x = 0.0;
960 				for (int32 i = 0; i < tab_index; i++){
961 					x += StringWidth(TabAt(i)->Label()) + 20.0;
962 				}
963 
964 				return BRect(x, 0.0,
965 					x + StringWidth(TabAt(tab_index)->Label()) + 20.0,
966 					height);
967 			}
968 
969 			case B_WIDTH_FROM_WIDEST:
970 				width = 0.0;
971 				for (int32 i = 0; i < CountTabs(); i++) {
972 					float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0;
973 					if (tabWidth > width)
974 						width = tabWidth;
975 				}
976 				// fall through
977 
978 			case B_WIDTH_AS_USUAL:
979 			default:
980 				return BRect(tab_index * width, 0.0,
981 					tab_index * width + width, height);
982 		}
983 	}
984 
985 	// TODO: fix to remove "offset" in DrawTab and DrawLabel ...
986 	switch (fTabWidthSetting) {
987 		case B_WIDTH_FROM_LABEL:
988 		{
989 			float x = 6.0f;
990 			for (int32 i = 0; i < tab_index; i++){
991 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
992 			}
993 
994 			return BRect(x - fTabOffset, 0.0f,
995 				x - fTabOffset + StringWidth(TabAt(tab_index)->Label()) + 20.0f , fTabHeight);
996 
997 
998 			/*float x = X_OFFSET;
999 			for (int32 i = 0; i < tab_index; i++)
1000 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
1001 
1002 			return BRect(x, 0.0f,
1003 				x + StringWidth(TabAt(tab_index)->Label()) + 20.0f, fTabHeight);*/
1004 		}
1005 
1006 		case B_WIDTH_FROM_WIDEST:
1007 		{
1008 			float width = 0.0f;
1009 
1010 			for (int32 i = 0; i < CountTabs(); i++) {
1011 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1012 				if (tabWidth > width)
1013 					width = tabWidth;
1014 			}
1015 			return BRect((6.0f + tab_index * width) - fTabOffset, 0.0f,
1016 				(6.0f + tab_index * width + width) - fTabOffset, fTabHeight);
1017 			/*float width = 0.0f;
1018 
1019 			for (int32 i = 0; i < CountTabs(); i++) {
1020 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1021 
1022 				if (tabWidth > width)
1023 					width = tabWidth;
1024 			}
1025 
1026 			return BRect(X_OFFSET + tab_index * width, 0.0f,
1027 				X_OFFSET + tab_index * width + width, fTabHeight);*/
1028 		}
1029 
1030 		case B_WIDTH_AS_USUAL:
1031 		default:
1032 			return BRect((6.0f + tab_index * 100.0f) - fTabOffset, 0.0f,
1033 						(6.0f + tab_index * 100.0f + 100.0f) - fTabOffset, fTabHeight);
1034 			/*return BRect(X_OFFSET + tab_index * 100.0f, 0.0f,
1035 				X_OFFSET + tab_index * 100.0f + 100.0f, fTabHeight);*/
1036 	}
1037 }
1038 
1039 
1040 void
1041 BTabView::SetFlags(uint32 flags)
1042 {
1043 	BView::SetFlags(flags);
1044 }
1045 
1046 
1047 void
1048 BTabView::SetResizingMode(uint32 mode)
1049 {
1050 	BView::SetResizingMode(mode);
1051 }
1052 
1053 
1054 void
1055 BTabView::GetPreferredSize(float *width, float *height)
1056 {
1057 	BView::GetPreferredSize(width, height);
1058 }
1059 
1060 
1061 BSize
1062 BTabView::MinSize()
1063 {
1064 	BSize size = fContainerView->MinSize();
1065 	size.height += TabHeight() + 6.0f;
1066 	size.width += 6.0f;
1067 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1068 }
1069 
1070 
1071 BSize
1072 BTabView::MaxSize()
1073 {
1074 	BSize size = fContainerView->MaxSize();
1075 	size.height += TabHeight() + 6.0f;
1076 	size.width += 6.0f;
1077 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1078 }
1079 
1080 
1081 BSize
1082 BTabView::PreferredSize()
1083 {
1084 	BSize size = fContainerView->PreferredSize();
1085 	size.height += TabHeight() + 6.0f;
1086 	size.width += 6.0f;
1087 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1088 }
1089 
1090 
1091 void
1092 BTabView::ResizeToPreferred()
1093 {
1094 	BView::ResizeToPreferred();
1095 }
1096 
1097 
1098 BHandler *
1099 BTabView::ResolveSpecifier(BMessage *message, int32 index,
1100 	BMessage *specifier, int32 what, const char *property)
1101 {
1102 	BPropertyInfo propInfo(sPropertyList);
1103 
1104 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1105 		return this;
1106 
1107 	return BView::ResolveSpecifier(message, index, specifier, what,
1108 		property);
1109 }
1110 
1111 
1112 status_t
1113 BTabView::GetSupportedSuites(BMessage *message)
1114 {
1115 	message->AddString("suites", "suite/vnd.Be-tab-view");
1116 
1117 	BPropertyInfo propInfo(sPropertyList);
1118 	message->AddFlat("messages", &propInfo);
1119 
1120 	return BView::GetSupportedSuites(message);
1121 }
1122 
1123 
1124 void
1125 BTabView::AddTab(BView *target, BTab *tab)
1126 {
1127 	if (tab == NULL)
1128 		tab = new BTab(target);
1129 	else
1130 		tab->SetView(target);
1131 
1132 	if (fContainerView->GetLayout())
1133 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1134 
1135 	fTabList->AddItem(tab);
1136 }
1137 
1138 
1139 BTab *
1140 BTabView::RemoveTab(int32 index)
1141 {
1142 	if (index < 0 || index >= CountTabs())
1143 		return NULL;
1144 
1145 	BTab *tab = (BTab *)fTabList->RemoveItem(index);
1146 	if (tab == NULL)
1147 		return NULL;
1148 
1149 	tab->Deselect();
1150 
1151 	if (index <= fSelection && fSelection != 0)
1152 		fSelection--;
1153 
1154 	if (CountTabs() == 0)
1155 		fFocus = -1;
1156 	else
1157 		Select(fSelection);
1158 
1159 	if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1160 		SetFocusTab(fFocus, false);
1161 	else
1162 		SetFocusTab(fFocus, true);
1163 
1164 	if (fContainerView->GetLayout())
1165 		fContainerView->GetLayout()->RemoveItem(index);
1166 
1167 	return tab;
1168 }
1169 
1170 
1171 BTab *
1172 BTabView::TabAt(int32 index) const
1173 {
1174 	return (BTab *)fTabList->ItemAt(index);
1175 }
1176 
1177 
1178 void
1179 BTabView::SetTabWidth(button_width width)
1180 {
1181 	fTabWidthSetting = width;
1182 
1183 	Invalidate();
1184 }
1185 
1186 
1187 button_width
1188 BTabView::TabWidth() const
1189 {
1190 	return fTabWidthSetting;
1191 }
1192 
1193 
1194 void
1195 BTabView::SetTabHeight(float height)
1196 {
1197 	if (fTabHeight == height)
1198 		return;
1199 
1200 	fContainerView->MoveBy(0.0f, height - fTabHeight);
1201 	fContainerView->ResizeBy(0.0f, height - fTabHeight);
1202 
1203 	fTabHeight = height;
1204 
1205 	Invalidate();
1206 }
1207 
1208 
1209 float
1210 BTabView::TabHeight() const
1211 {
1212 	return fTabHeight;
1213 }
1214 
1215 
1216 BView *
1217 BTabView::ContainerView() const
1218 {
1219 	return fContainerView;
1220 }
1221 
1222 
1223 int32
1224 BTabView::CountTabs() const
1225 {
1226 	return fTabList->CountItems();
1227 }
1228 
1229 
1230 BView *
1231 BTabView::ViewForTab(int32 tabIndex) const
1232 {
1233 	BTab *tab = TabAt(tabIndex);
1234 	if (tab)
1235 		return tab->View();
1236 
1237 	return NULL;
1238 }
1239 
1240 
1241 void
1242 BTabView::_InitObject(bool layouted, button_width width)
1243 {
1244 	if (!be_control_look)
1245 		SetFont(be_bold_font);
1246 
1247 	fTabList = new BList;
1248 
1249 	fTabWidthSetting = width;
1250 	fSelection = 0;
1251 	fFocus = -1;
1252 	fTabOffset = 0.0f;
1253 
1254 	rgb_color color = ui_color(B_PANEL_BACKGROUND_COLOR);
1255 
1256 	SetViewColor(color);
1257 	SetLowColor(color);
1258 
1259 	font_height fh;
1260 	GetFontHeight(&fh);
1261 	fTabHeight = fh.ascent + fh.descent + fh.leading + 8.0f;
1262 
1263 	if (layouted) {
1264 		BGroupLayout* layout = new(std::nothrow) BGroupLayout(B_HORIZONTAL);
1265 		if (layout) {
1266 			layout->SetInsets(3.0, 3.0 + TabHeight() - 1, 3.0, 3.0);
1267 			SetLayout(layout);
1268 		}
1269 
1270 		fContainerView = new BView("view container", B_WILL_DRAW);
1271 		fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1272 	} else {
1273 		BRect bounds = Bounds();
1274 
1275 		bounds.top += TabHeight();
1276 		bounds.InsetBy(3.0f, 3.0f);
1277 
1278 		fContainerView = new BView(bounds, "view container", B_FOLLOW_ALL,
1279 			B_WILL_DRAW);
1280 	}
1281 
1282 	fContainerView->SetViewColor(color);
1283 	fContainerView->SetLowColor(color);
1284 
1285 	AddChild(fContainerView);
1286 }
1287 
1288 
1289 void BTabView::_ReservedTabView1() {}
1290 void BTabView::_ReservedTabView2() {}
1291 void BTabView::_ReservedTabView3() {}
1292 void BTabView::_ReservedTabView4() {}
1293 void BTabView::_ReservedTabView5() {}
1294 void BTabView::_ReservedTabView6() {}
1295 void BTabView::_ReservedTabView7() {}
1296 void BTabView::_ReservedTabView8() {}
1297 void BTabView::_ReservedTabView9() {}
1298 void BTabView::_ReservedTabView10() {}
1299 void BTabView::_ReservedTabView11() {}
1300 void BTabView::_ReservedTabView12() {}
1301 
1302 
1303 BTabView::BTabView(const BTabView &tabView)
1304 	: BView(tabView)
1305 {
1306 	// this is private and not functional, but exported
1307 }
1308 
1309 
1310 BTabView &BTabView::operator=(const BTabView &)
1311 {
1312 	// this is private and not functional, but exported
1313 	return *this;
1314 }
1315