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