xref: /haiku/src/kits/interface/TabView.cpp (revision 9fe35223cf4eb9714865152d62e8efc36b327fef)
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_RIGHT_BORDER
274 //			| BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
275 //		if (frame.left == owner->Bounds().left)
276 //			borders |= BControlLook::B_LEFT_BORDER;
277 //		be_control_look->DrawButtonFrame(owner, frame, frame,
278 //			no_tint, 0, borders);
279 //		if (position == B_TAB_FRONT)
280 //			no_tint = tint_color(no_tint, B_DARKEN_2_TINT);
281 //		be_control_look->DrawButtonBackground(owner, frame, frame, no_tint);
282 
283 		uint32 borders = BControlLook::B_TOP_BORDER
284 			| BControlLook::B_BOTTOM_BORDER;
285 		if (frame.left == owner->Bounds().left)
286 			borders |= BControlLook::B_LEFT_BORDER;
287 		if (frame.right == owner->Bounds().right)
288 			borders |= BControlLook::B_RIGHT_BORDER;
289 
290 		if (position == B_TAB_FRONT) {
291 			frame.bottom += 1;
292 			be_control_look->DrawActiveTab(owner, frame, frame, no_tint, 0,
293 				borders);
294 		} else {
295 			be_control_look->DrawInactiveTab(owner, frame, frame, no_tint, 0,
296 				borders);
297 		}
298 
299 		DrawLabel(owner, frame);
300 		return;
301 	}
302 
303 	rgb_color lightenmax = tint_color(no_tint, B_LIGHTEN_MAX_TINT);
304 	rgb_color darken2 = tint_color(no_tint, B_DARKEN_2_TINT);
305 	rgb_color darken3 = tint_color(no_tint, B_DARKEN_3_TINT);
306 	rgb_color darken4 = tint_color(no_tint, B_DARKEN_4_TINT);
307 	rgb_color darkenmax = tint_color(no_tint, B_DARKEN_MAX_TINT);
308 
309 	owner->SetHighColor(darkenmax);
310 	owner->SetLowColor(no_tint);
311 	// NOTE: "frame" goes from the beginning of the left slope to the beginning
312 	// of the right slope - "lableFrame" is the frame between both slopes
313 	BRect lableFrame = frame;
314 	lableFrame.left = lableFrame.left + frame.Height() / 2.0;
315 	DrawLabel(owner, lableFrame);
316 
317 	owner->SetDrawingMode(B_OP_OVER);
318 
319 	owner->BeginLineArray(12);
320 
321 	int32 slopeWidth = (int32)ceilf(frame.Height() / 2.0);
322 
323 	if (position != B_TAB_ANY) {
324 		// full height left side
325 		owner->AddLine(BPoint(frame.left, frame.bottom),
326 			BPoint(frame.left + slopeWidth, frame.top), darken3);
327 		owner->AddLine(BPoint(frame.left, frame.bottom + 1),
328 			BPoint(frame.left + slopeWidth, frame.top + 1), lightenmax);
329 	} else {
330 		// upper half of left side
331 		owner->AddLine(BPoint(frame.left + slopeWidth / 2,
332 				frame.bottom - slopeWidth),
333 			BPoint(frame.left + slopeWidth, frame.top), darken3);
334 		owner->AddLine(BPoint(frame.left + slopeWidth / 2 + 2,
335 				frame.bottom - slopeWidth - 1),
336 			BPoint(frame.left + slopeWidth, frame.top + 1), lightenmax);
337 	}
338 
339 	// lines along the top
340 	owner->AddLine(BPoint(frame.left + slopeWidth, frame.top),
341 		BPoint(frame.right, frame.top), darken3);
342 	owner->AddLine(BPoint(frame.left + slopeWidth, frame.top + 1),
343 		BPoint(frame.right, frame.top + 1), lightenmax);
344 
345 	if (full) {
346 		// full height right side
347 		owner->AddLine(BPoint(frame.right, frame.top),
348 			BPoint(frame.right + slopeWidth + 2, frame.bottom), darken2);
349 		owner->AddLine(BPoint(frame.right, frame.top + 1),
350 			BPoint(frame.right + slopeWidth + 1, frame.bottom), darken4);
351 	} else {
352 		// upper half of right side
353 		owner->AddLine(BPoint(frame.right, frame.top),
354 			BPoint(frame.right + slopeWidth / 2 + 1,
355 				frame.bottom - slopeWidth), darken2);
356 		owner->AddLine(BPoint(frame.right, frame.top + 1),
357 			BPoint(frame.right + slopeWidth / 2,
358 				frame.bottom - slopeWidth), darken4);
359 	}
360 
361 	owner->EndLineArray();
362 }
363 
364 
365 void BTab::_ReservedTab1() {}
366 void BTab::_ReservedTab2() {}
367 void BTab::_ReservedTab3() {}
368 void BTab::_ReservedTab4() {}
369 void BTab::_ReservedTab5() {}
370 void BTab::_ReservedTab6() {}
371 void BTab::_ReservedTab7() {}
372 void BTab::_ReservedTab8() {}
373 void BTab::_ReservedTab9() {}
374 void BTab::_ReservedTab10() {}
375 void BTab::_ReservedTab11() {}
376 void BTab::_ReservedTab12() {}
377 
378 BTab &BTab::operator=(const BTab &)
379 {
380 	// this is private and not functional, but exported
381 	return *this;
382 }
383 
384 
385 //	#pragma mark - BTabView
386 
387 
388 BTabView::BTabView(const char *name, button_width width, uint32 flags)
389 	: BView(name, flags)
390 {
391 	_InitObject(true, width);
392 }
393 
394 
395 BTabView::BTabView(BRect frame, const char *name, button_width width,
396 	uint32 resizingMode, uint32 flags)
397 	: BView(frame, name, resizingMode, flags)
398 {
399 	_InitObject(false, width);
400 }
401 
402 
403 BTabView::~BTabView()
404 {
405 	for (int32 i = 0; i < CountTabs(); i++) {
406 		delete TabAt(i);
407 	}
408 
409 	delete fTabList;
410 }
411 
412 
413 BTabView::BTabView(BMessage *archive)
414 	: BView(archive),
415 	fFocus(-1)
416 {
417 	fContainerView = NULL;
418 	fTabList = new BList;
419 
420 	int16 width;
421 
422 	if (archive->FindInt16("_but_width", &width) == B_OK)
423 		fTabWidthSetting = (button_width)width;
424 	else
425 		fTabWidthSetting = B_WIDTH_AS_USUAL;
426 
427 	if (archive->FindFloat("_high", &fTabHeight) != B_OK) {
428 		font_height fh;
429 		GetFontHeight(&fh);
430 		fTabHeight = fh.ascent + fh.descent + fh.leading + 8.0f;
431 	}
432 
433 	fFocus = -1;
434 
435 	if (archive->FindInt32("_sel", &fSelection) != B_OK)
436 		fSelection = 0;
437 
438 	if (fContainerView == NULL)
439 		fContainerView = ChildAt(0);
440 
441 	int32 i = 0;
442 	BMessage tabMsg;
443 
444 	while (archive->FindMessage("_l_items", i, &tabMsg) == B_OK) {
445 		BArchivable *archivedTab = instantiate_object(&tabMsg);
446 
447 		if (archivedTab) {
448 			BTab *tab = dynamic_cast<BTab *>(archivedTab);
449 
450 			BMessage viewMsg;
451 			if (archive->FindMessage("_view_list", i, &viewMsg) == B_OK) {
452 				BArchivable *archivedView = instantiate_object(&viewMsg);
453 				if (archivedView)
454 					AddTab(dynamic_cast<BView*>(archivedView), tab);
455 			}
456 		}
457 
458 		tabMsg.MakeEmpty();
459 		i++;
460 	}
461 }
462 
463 
464 BArchivable *
465 BTabView::Instantiate(BMessage *archive)
466 {
467 	if ( validate_instantiation(archive, "BTabView"))
468 		return new BTabView(archive);
469 
470 	return NULL;
471 }
472 
473 
474 status_t
475 BTabView::Archive(BMessage *archive, bool deep) const
476 {
477 	if (CountTabs() > 0)
478 		TabAt(Selection())->View()->RemoveSelf();
479 
480 	status_t ret = BView::Archive(archive, deep);
481 
482 	if (ret == B_OK)
483 		ret = archive->AddInt16("_but_width", fTabWidthSetting);
484 	if (ret == B_OK)
485 		ret = archive->AddFloat("_high", fTabHeight);
486 	if (ret == B_OK)
487 		ret = archive->AddInt32("_sel", fSelection);
488 
489 	if (ret == B_OK && deep) {
490 		for (int32 i = 0; i < CountTabs(); i++) {
491 			BMessage tabArchive;
492 			BTab *tab = TabAt(i);
493 
494 			if (!tab)
495 				continue;
496 			ret = tab->Archive(&tabArchive, true);
497 			if (ret == B_OK)
498 				ret = archive->AddMessage("_l_items", &tabArchive);
499 
500 			if (!tab->View())
501 				continue;
502 
503 			BMessage viewArchive;
504 			ret = tab->View()->Archive(&viewArchive, true);
505 			if (ret == B_OK)
506 				ret = archive->AddMessage("_view_list", &viewArchive);
507 		}
508 	}
509 
510 	if (CountTabs() > 0) {
511 		if (TabAt(Selection())->View() && ContainerView())
512 			TabAt(Selection())->Select(ContainerView());
513 	}
514 
515 	return ret;
516 }
517 
518 
519 status_t
520 BTabView::Perform(perform_code d, void *arg)
521 {
522 	return BView::Perform(d, arg);
523 }
524 
525 
526 void
527 BTabView::AttachedToWindow()
528 {
529 	BView::AttachedToWindow();
530 
531 	Select(fSelection);
532 }
533 
534 
535 void
536 BTabView::DetachedFromWindow()
537 {
538 	BView::DetachedFromWindow();
539 }
540 
541 
542 void
543 BTabView::AllAttached()
544 {
545 	BView::AllAttached();
546 }
547 
548 
549 void
550 BTabView::AllDetached()
551 {
552 	BView::AllDetached();
553 }
554 
555 
556 // #pragma mark -
557 
558 
559 void
560 BTabView::MessageReceived(BMessage *message)
561 {
562 	switch (message->what) {
563 		case B_GET_PROPERTY:
564 		case B_SET_PROPERTY:
565 		{
566 			BMessage reply(B_REPLY);
567 			bool handled = false;
568 
569 			BMessage specifier;
570 			int32 index;
571 			int32 form;
572 			const char *property;
573 			if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) {
574 				if (strcmp(property, "Selection") == 0) {
575 					if (message->what == B_GET_PROPERTY) {
576 						reply.AddInt32("result", fSelection);
577 						handled = true;
578 					} else {
579 						// B_GET_PROPERTY
580 						int32 selection;
581 						if (message->FindInt32("data", &selection) == B_OK) {
582 							Select(selection);
583 							reply.AddInt32("error", B_OK);
584 							handled = true;
585 						}
586 					}
587 				}
588 			}
589 
590 			if (handled)
591 				message->SendReply(&reply);
592 			else
593 				BView::MessageReceived(message);
594 			break;
595 		}
596 
597 #if 0
598 		case B_MOUSE_WHEEL_CHANGED:
599 		{
600 			float deltaX = 0.0f;
601 			float deltaY = 0.0f;
602 			message->FindFloat("be:wheel_delta_x", &deltaX);
603 			message->FindFloat("be:wheel_delta_y", &deltaY);
604 
605 			if (deltaX == 0.0f && deltaY == 0.0f)
606 				return;
607 
608 			if (deltaY == 0.0f)
609 				deltaY = deltaX;
610 
611 			int32 selection = Selection();
612 			int32 numTabs = CountTabs();
613 			if (deltaY > 0  && selection < numTabs - 1) {
614 				// move to the right tab.
615 				Select(Selection() + 1);
616 			} else if (deltaY < 0 && selection > 0 && numTabs > 1) {
617 				// move to the left tab.
618 				Select(selection - 1);
619 			}
620 			break;
621 		}
622 #endif
623 
624 		default:
625 			BView::MessageReceived(message);
626 			break;
627 	}
628 }
629 
630 
631 void
632 BTabView::KeyDown(const char *bytes, int32 numBytes)
633 {
634 	if (IsHidden())
635 		return;
636 
637 	switch (bytes[0]) {
638 		case B_DOWN_ARROW:
639 		case B_LEFT_ARROW: {
640 			int32 focus = fFocus - 1;
641 			if (focus < 0)
642 				focus = CountTabs() - 1;
643 			SetFocusTab(focus, true);
644 			break;
645 		}
646 
647 		case B_UP_ARROW:
648 		case B_RIGHT_ARROW: {
649 			int32 focus = fFocus + 1;
650 			if (focus >= CountTabs())
651 				focus = 0;
652 			SetFocusTab(focus, true);
653 			break;
654 		}
655 
656 		case B_RETURN:
657 		case B_SPACE:
658 			Select(FocusTab());
659 			break;
660 
661 		default:
662 			BView::KeyDown(bytes, numBytes);
663 	}
664 }
665 
666 
667 void
668 BTabView::MouseDown(BPoint point)
669 {
670 	if (point.y > fTabHeight)
671 		return;
672 
673 	for (int32 i = 0; i < CountTabs(); i++) {
674 		if (TabFrame(i).Contains(point)
675 			&& i != Selection()) {
676 			Select(i);
677 			return;
678 		}
679 	}
680 
681 	BView::MouseDown(point);
682 }
683 
684 
685 void
686 BTabView::MouseUp(BPoint point)
687 {
688 	BView::MouseUp(point);
689 }
690 
691 
692 void
693 BTabView::MouseMoved(BPoint point, uint32 transit, const BMessage *message)
694 {
695 	BView::MouseMoved(point, transit, message);
696 }
697 
698 
699 void
700 BTabView::Pulse()
701 {
702 	BView::Pulse();
703 }
704 
705 
706 void
707 BTabView::Select(int32 index)
708 {
709 	if (index < 0 || index >= CountTabs())
710 		index = Selection();
711 
712 	BTab *tab = TabAt(Selection());
713 
714 	if (tab)
715 		tab->Deselect();
716 
717 	tab = TabAt(index);
718 	if (tab && ContainerView()) {
719 		if (index == 0)
720 			fTabOffset = 0.0f;
721 		tab->Select(ContainerView());
722 		fSelection = index;
723 
724 		// make the view visible through the layout if there is one
725 		BCardLayout* layout
726 			= dynamic_cast<BCardLayout*>(fContainerView->GetLayout());
727 		if (layout)
728 			layout->SetVisibleItem(index);
729 	}
730 
731 	Invalidate();
732 
733 	if (index != 0 && !Bounds().Contains(TabFrame(index))){
734 		if (!Bounds().Contains(TabFrame(index).LeftTop()))
735 			fTabOffset += TabFrame(index).left - Bounds().left - 20.0f;
736 		else
737 			fTabOffset += TabFrame(index).right - Bounds().right + 20.0f;
738 
739 		Invalidate();
740 	}
741 
742 	/*MakeFocus();
743 	SetFocusTab(index, true);
744 	FocusTab();*/
745 }
746 
747 
748 int32
749 BTabView::Selection() const
750 {
751 	return fSelection;
752 }
753 
754 
755 void
756 BTabView::WindowActivated(bool active)
757 {
758 	BView::WindowActivated(active);
759 
760 	if (IsFocus())
761 		Invalidate();
762 }
763 
764 
765 void
766 BTabView::MakeFocus(bool focused)
767 {
768 	BView::MakeFocus(focused);
769 
770 	SetFocusTab(Selection(), focused);
771 }
772 
773 
774 void
775 BTabView::SetFocusTab(int32 tab, bool focused)
776 {
777 	if (tab >= CountTabs())
778 		tab = 0;
779 
780 	if (tab < 0)
781 		tab = CountTabs() - 1;
782 
783 	if (focused) {
784 		if (tab == fFocus)
785 			return;
786 
787 		if (fFocus != -1){
788 			if (TabAt (fFocus) != NULL)
789 				TabAt(fFocus)->MakeFocus(false);
790 			Invalidate(TabFrame(fFocus));
791 		}
792 		if (TabAt(tab) != NULL){
793 			TabAt(tab)->MakeFocus(true);
794 			Invalidate(TabFrame(tab));
795 			fFocus = tab;
796 		}
797 	} else if (fFocus != -1) {
798 		TabAt(fFocus)->MakeFocus(false);
799 		Invalidate(TabFrame(fFocus));
800 		fFocus = -1;
801 	}
802 }
803 
804 
805 int32
806 BTabView::FocusTab() const
807 {
808 	return fFocus;
809 }
810 
811 
812 void
813 BTabView::Draw(BRect updateRect)
814 {
815 	if (be_control_look != NULL) {
816 		DrawBox(TabFrame(fSelection));
817 		DrawTabs();
818 	} else
819 		DrawBox(DrawTabs());
820 
821 	if (IsFocus() && fFocus != -1)
822 		TabAt(fFocus)->DrawFocusMark(this, TabFrame(fFocus));
823 }
824 
825 
826 BRect
827 BTabView::DrawTabs()
828 {
829 	if (be_control_look != NULL) {
830 //		BRect rect(Bounds());
831 //		rect.bottom = rect.top + fTabHeight;
832 //		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
833 //		be_control_look->DrawButtonFrame(this, rect, rect, base);
834 //		be_control_look->DrawButtonBackground(this, rect, rect, base);
835 	}
836 
837 	float left = 0;
838 
839 	for (int32 i = 0; i < CountTabs(); i++) {
840 		BRect tabFrame = TabFrame(i);
841 		TabAt(i)->DrawTab(this, tabFrame,
842 			i == fSelection ? B_TAB_FRONT : (i == 0) ? B_TAB_FIRST : B_TAB_ANY,
843 			i + 1 != fSelection);
844 		left = tabFrame.right;
845 	}
846 
847 	if (be_control_look != NULL) {
848 		BRect frame(Bounds());
849 		if (left < frame.right) {
850 			frame.left = left;
851 			frame.bottom = fTabHeight;
852 			rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
853 			uint32 borders = BControlLook::B_TOP_BORDER
854 				| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
855 			if (left == 0)
856 				borders |= BControlLook::B_LEFT_BORDER;
857 			be_control_look->DrawInactiveTab(this, frame, frame, base, 0,
858 				borders);
859 		}
860 	}
861 
862 	if (fSelection < CountTabs())
863 		return TabFrame(fSelection);
864 
865 	return BRect();
866 }
867 
868 
869 void
870 BTabView::DrawBox(BRect selTabRect)
871 {
872 	if (be_control_look != NULL) {
873 		BRect rect(Bounds());
874 		rect.top = selTabRect.bottom;
875 
876 //		BRegion clipping(Bounds());
877 //		selTabRect.left += 2;
878 //		selTabRect.right -= 2;
879 //		clipping.Exclude(selTabRect);
880 //		ConstrainClippingRegion(&clipping);
881 
882 		rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
883 		be_control_look->DrawGroupFrame(this, rect, rect, base);
884 
885 //		ConstrainClippingRegion(NULL);
886 
887 		return;
888 	}
889 
890 	BRect rect = Bounds();
891 	BRect lastTabRect = TabFrame(CountTabs() - 1);
892 
893 	rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR);
894 	rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT);
895 	rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT);
896 	rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT);
897 	rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT);
898 
899 	BeginLineArray(12);
900 
901 	int32 offset = (int32)ceilf(selTabRect.Height() / 2.0);
902 
903 	// outer lines
904 	AddLine(BPoint(rect.left, rect.bottom - 1),
905 			BPoint(rect.left, selTabRect.bottom), darken2);
906 	if (selTabRect.left >= rect.left + 1)
907 		AddLine(BPoint(rect.left + 1, selTabRect.bottom),
908 				BPoint(selTabRect.left, selTabRect.bottom), darken2);
909 	if (lastTabRect.right + offset + 1 <= rect.right - 1)
910 		AddLine(BPoint(lastTabRect.right + offset + 1, selTabRect.bottom),
911 				BPoint(rect.right - 1, selTabRect.bottom), darken2);
912 	AddLine(BPoint(rect.right, selTabRect.bottom + 2),
913 			BPoint(rect.right, rect.bottom), darken2);
914 	AddLine(BPoint(rect.right - 1, rect.bottom),
915 			BPoint(rect.left + 2, rect.bottom), darken2);
916 
917 	// inner lines
918 	rect.InsetBy(1, 1);
919 	selTabRect.bottom += 1;
920 
921 	AddLine(BPoint(rect.left, rect.bottom - 2),
922 			BPoint(rect.left, selTabRect.bottom), lightenMax);
923 	if (selTabRect.left >= rect.left + 1)
924 		AddLine(BPoint(rect.left + 1, selTabRect.bottom),
925 				BPoint(selTabRect.left, selTabRect.bottom), lightenMax);
926 	if (selTabRect.right + offset + 1 <= rect.right - 2)
927 		AddLine(BPoint(selTabRect.right + offset + 1, selTabRect.bottom),
928 				BPoint(rect.right - 2, selTabRect.bottom), lightenMax);
929 	AddLine(BPoint(rect.right, selTabRect.bottom),
930 			BPoint(rect.right, rect.bottom), darken4);
931 	AddLine(BPoint(rect.right - 1, rect.bottom),
932 			BPoint(rect.left, rect.bottom), darken4);
933 
934 	// soft inner bevel at right/bottom
935 	rect.right--;
936 	rect.bottom--;
937 
938 	AddLine(BPoint(rect.right, selTabRect.bottom + 1),
939 			BPoint(rect.right, rect.bottom), darken1);
940 	AddLine(BPoint(rect.right - 1, rect.bottom),
941 			BPoint(rect.left + 1, rect.bottom), darken1);
942 
943 	EndLineArray();
944 }
945 
946 #define X_OFFSET 0.0f
947 
948 BRect
949 BTabView::TabFrame(int32 tab_index) const
950 {
951 	if (be_control_look != NULL) {
952 		float width = 100.0;
953 		float height = fTabHeight;;
954 		switch (fTabWidthSetting) {
955 			case B_WIDTH_FROM_LABEL:
956 			{
957 				float x = 0.0;
958 				for (int32 i = 0; i < tab_index; i++){
959 					x += StringWidth(TabAt(i)->Label()) + 20.0;
960 				}
961 
962 				return BRect(x, 0.0,
963 					x + StringWidth(TabAt(tab_index)->Label()) + 20.0,
964 					height);
965 			}
966 
967 			case B_WIDTH_FROM_WIDEST:
968 				width = 0.0;
969 				for (int32 i = 0; i < CountTabs(); i++) {
970 					float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0;
971 					if (tabWidth > width)
972 						width = tabWidth;
973 				}
974 				// fall through
975 
976 			case B_WIDTH_AS_USUAL:
977 			default:
978 				return BRect(tab_index * width, 0.0,
979 					tab_index * width + width, height);
980 		}
981 	}
982 
983 	// TODO: fix to remove "offset" in DrawTab and DrawLabel ...
984 	switch (fTabWidthSetting) {
985 		case B_WIDTH_FROM_LABEL:
986 		{
987 			float x = 6.0f;
988 			for (int32 i = 0; i < tab_index; i++){
989 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
990 			}
991 
992 			return BRect(x - fTabOffset, 0.0f,
993 				x - fTabOffset + StringWidth(TabAt(tab_index)->Label()) + 20.0f , fTabHeight);
994 
995 
996 			/*float x = X_OFFSET;
997 			for (int32 i = 0; i < tab_index; i++)
998 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
999 
1000 			return BRect(x, 0.0f,
1001 				x + StringWidth(TabAt(tab_index)->Label()) + 20.0f, fTabHeight);*/
1002 		}
1003 
1004 		case B_WIDTH_FROM_WIDEST:
1005 		{
1006 			float width = 0.0f;
1007 
1008 			for (int32 i = 0; i < CountTabs(); i++) {
1009 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1010 				if (tabWidth > width)
1011 					width = tabWidth;
1012 			}
1013 			return BRect((6.0f + tab_index * width) - fTabOffset, 0.0f,
1014 				(6.0f + tab_index * width + width) - fTabOffset, fTabHeight);
1015 			/*float width = 0.0f;
1016 
1017 			for (int32 i = 0; i < CountTabs(); i++) {
1018 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1019 
1020 				if (tabWidth > width)
1021 					width = tabWidth;
1022 			}
1023 
1024 			return BRect(X_OFFSET + tab_index * width, 0.0f,
1025 				X_OFFSET + tab_index * width + width, fTabHeight);*/
1026 		}
1027 
1028 		case B_WIDTH_AS_USUAL:
1029 		default:
1030 			return BRect((6.0f + tab_index * 100.0f) - fTabOffset, 0.0f,
1031 						(6.0f + tab_index * 100.0f + 100.0f) - fTabOffset, fTabHeight);
1032 			/*return BRect(X_OFFSET + tab_index * 100.0f, 0.0f,
1033 				X_OFFSET + tab_index * 100.0f + 100.0f, fTabHeight);*/
1034 	}
1035 }
1036 
1037 
1038 void
1039 BTabView::SetFlags(uint32 flags)
1040 {
1041 	BView::SetFlags(flags);
1042 }
1043 
1044 
1045 void
1046 BTabView::SetResizingMode(uint32 mode)
1047 {
1048 	BView::SetResizingMode(mode);
1049 }
1050 
1051 
1052 // #pragma mark -
1053 
1054 
1055 void
1056 BTabView::ResizeToPreferred()
1057 {
1058 	BView::ResizeToPreferred();
1059 }
1060 
1061 
1062 void
1063 BTabView::GetPreferredSize(float *width, float *height)
1064 {
1065 	BView::GetPreferredSize(width, height);
1066 }
1067 
1068 
1069 BSize
1070 BTabView::MinSize()
1071 {
1072 	BSize size(_TabsMinSize());
1073 	BSize containerSize = fContainerView->MinSize();
1074 	if (containerSize.width > size.width)
1075 		size.width = containerSize.width;
1076 	size.height += containerSize.height;
1077 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1078 }
1079 
1080 
1081 BSize
1082 BTabView::MaxSize()
1083 {
1084 	BSize size(_TabsMinSize());
1085 	BSize containerSize = fContainerView->MaxSize();
1086 	if (containerSize.width > size.width)
1087 		size.width = containerSize.width;
1088 	size.height += containerSize.height;
1089 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1090 }
1091 
1092 
1093 BSize
1094 BTabView::PreferredSize()
1095 {
1096 	BSize size(_TabsMinSize());
1097 	BSize containerSize = fContainerView->PreferredSize();
1098 	if (containerSize.width > size.width)
1099 		size.width = containerSize.width;
1100 	size.height += containerSize.height;
1101 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1102 }
1103 
1104 
1105 void
1106 BTabView::FrameMoved(BPoint newLocation)
1107 {
1108 	BView::FrameMoved(newLocation);
1109 }
1110 
1111 
1112 void
1113 BTabView::FrameResized(float width,float height)
1114 {
1115 	BView::FrameResized(width, height);
1116 }
1117 
1118 
1119 // #pragma mark -
1120 
1121 
1122 BHandler *
1123 BTabView::ResolveSpecifier(BMessage *message, int32 index,
1124 	BMessage *specifier, int32 what, const char *property)
1125 {
1126 	BPropertyInfo propInfo(sPropertyList);
1127 
1128 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1129 		return this;
1130 
1131 	return BView::ResolveSpecifier(message, index, specifier, what,
1132 		property);
1133 }
1134 
1135 
1136 status_t
1137 BTabView::GetSupportedSuites(BMessage *message)
1138 {
1139 	message->AddString("suites", "suite/vnd.Be-tab-view");
1140 
1141 	BPropertyInfo propInfo(sPropertyList);
1142 	message->AddFlat("messages", &propInfo);
1143 
1144 	return BView::GetSupportedSuites(message);
1145 }
1146 
1147 
1148 // #pragma mark -
1149 
1150 
1151 void
1152 BTabView::AddTab(BView *target, BTab *tab)
1153 {
1154 	if (tab == NULL)
1155 		tab = new BTab(target);
1156 	else
1157 		tab->SetView(target);
1158 
1159 	if (fContainerView->GetLayout())
1160 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1161 
1162 	fTabList->AddItem(tab);
1163 
1164 	// When we haven't had a any tabs before, but are already attached to the
1165 	// window, select this one.
1166 	if (CountTabs() == 1 && Window() != NULL)
1167 		Select(0);
1168 }
1169 
1170 
1171 BTab *
1172 BTabView::RemoveTab(int32 index)
1173 {
1174 	if (index < 0 || index >= CountTabs())
1175 		return NULL;
1176 
1177 	BTab *tab = (BTab *)fTabList->RemoveItem(index);
1178 	if (tab == NULL)
1179 		return NULL;
1180 
1181 	tab->Deselect();
1182 
1183 	if (index <= fSelection && fSelection != 0)
1184 		fSelection--;
1185 
1186 	if (CountTabs() == 0)
1187 		fFocus = -1;
1188 	else
1189 		Select(fSelection);
1190 
1191 	if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1192 		SetFocusTab(fFocus, false);
1193 	else
1194 		SetFocusTab(fFocus, true);
1195 
1196 	if (fContainerView->GetLayout())
1197 		fContainerView->GetLayout()->RemoveItem(index);
1198 
1199 	return tab;
1200 }
1201 
1202 
1203 BTab *
1204 BTabView::TabAt(int32 index) const
1205 {
1206 	return (BTab *)fTabList->ItemAt(index);
1207 }
1208 
1209 
1210 void
1211 BTabView::SetTabWidth(button_width width)
1212 {
1213 	fTabWidthSetting = width;
1214 
1215 	Invalidate();
1216 }
1217 
1218 
1219 button_width
1220 BTabView::TabWidth() const
1221 {
1222 	return fTabWidthSetting;
1223 }
1224 
1225 
1226 void
1227 BTabView::SetTabHeight(float height)
1228 {
1229 	if (fTabHeight == height)
1230 		return;
1231 
1232 	fContainerView->MoveBy(0.0f, height - fTabHeight);
1233 	fContainerView->ResizeBy(0.0f, height - fTabHeight);
1234 
1235 	fTabHeight = height;
1236 
1237 	Invalidate();
1238 }
1239 
1240 
1241 float
1242 BTabView::TabHeight() const
1243 {
1244 	return fTabHeight;
1245 }
1246 
1247 
1248 BView *
1249 BTabView::ContainerView() const
1250 {
1251 	return fContainerView;
1252 }
1253 
1254 
1255 int32
1256 BTabView::CountTabs() const
1257 {
1258 	return fTabList->CountItems();
1259 }
1260 
1261 
1262 BView *
1263 BTabView::ViewForTab(int32 tabIndex) const
1264 {
1265 	BTab *tab = TabAt(tabIndex);
1266 	if (tab)
1267 		return tab->View();
1268 
1269 	return NULL;
1270 }
1271 
1272 
1273 void
1274 BTabView::_InitObject(bool layouted, button_width width)
1275 {
1276 	if (!be_control_look)
1277 		SetFont(be_bold_font);
1278 
1279 	fTabList = new BList;
1280 
1281 	fTabWidthSetting = width;
1282 	fSelection = 0;
1283 	fFocus = -1;
1284 	fTabOffset = 0.0f;
1285 
1286 	rgb_color color = ui_color(B_PANEL_BACKGROUND_COLOR);
1287 
1288 	SetViewColor(color);
1289 	SetLowColor(color);
1290 
1291 	font_height fh;
1292 	GetFontHeight(&fh);
1293 	fTabHeight = fh.ascent + fh.descent + fh.leading + 8.0f;
1294 
1295 	if (layouted) {
1296 		BGroupLayout* layout = new(std::nothrow) BGroupLayout(B_HORIZONTAL);
1297 		if (layout) {
1298 			layout->SetInsets(3.0, 3.0 + TabHeight() - 1, 3.0, 3.0);
1299 			SetLayout(layout);
1300 		}
1301 
1302 		fContainerView = new BView("view container", B_WILL_DRAW);
1303 		fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1304 	} else {
1305 		BRect bounds = Bounds();
1306 
1307 		bounds.top += TabHeight();
1308 		bounds.InsetBy(3.0f, 3.0f);
1309 
1310 		fContainerView = new BView(bounds, "view container", B_FOLLOW_ALL,
1311 			B_WILL_DRAW);
1312 	}
1313 
1314 	fContainerView->SetViewColor(color);
1315 	fContainerView->SetLowColor(color);
1316 
1317 	AddChild(fContainerView);
1318 }
1319 
1320 
1321 BSize
1322 BTabView::_TabsMinSize() const
1323 {
1324 	BSize size(0.0f, TabHeight() + 6.0f);
1325 	int32 count = min_c(2, CountTabs());
1326 	for (int32 i = 0; i < count; i++) {
1327 		BRect frame = TabFrame(i);
1328 		size.width += frame.Width();
1329 	}
1330 
1331 	if (count < CountTabs()) {
1332 		// TODO: Add size for yet to be implemented buttons that allow
1333 		// "scrolling" the displayed tabs left/right.
1334 	}
1335 
1336 	return size;
1337 }
1338 
1339 
1340 // #pragma mark - FBC and forbidden
1341 
1342 
1343 void BTabView::_ReservedTabView1() {}
1344 void BTabView::_ReservedTabView2() {}
1345 void BTabView::_ReservedTabView3() {}
1346 void BTabView::_ReservedTabView4() {}
1347 void BTabView::_ReservedTabView5() {}
1348 void BTabView::_ReservedTabView6() {}
1349 void BTabView::_ReservedTabView7() {}
1350 void BTabView::_ReservedTabView8() {}
1351 void BTabView::_ReservedTabView9() {}
1352 void BTabView::_ReservedTabView10() {}
1353 void BTabView::_ReservedTabView11() {}
1354 void BTabView::_ReservedTabView12() {}
1355 
1356 
1357 BTabView::BTabView(const BTabView& tabView)
1358 	: BView(tabView)
1359 {
1360 	// this is private and not functional, but exported
1361 }
1362 
1363 
1364 BTabView&
1365 BTabView::operator=(const BTabView&)
1366 {
1367 	// this is private and not functional, but exported
1368 	return *this;
1369 }
1370