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