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