xref: /haiku/src/kits/interface/TabView.cpp (revision a9b301871d06c0ebe42d22b31c685abed5107acd)
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 
37 static 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 
50 BTab::BTab(BView* contentsView)
51 	:
52 	fEnabled(true),
53 	fSelected(false),
54 	fFocus(false),
55 	fView(contentsView),
56 	fTabView(NULL)
57 {
58 }
59 
60 
61 BTab::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 
78 BTab::~BTab()
79 {
80 	if (fView == NULL)
81 		return;
82 
83 	if (fSelected)
84 		fView->RemoveSelf();
85 
86 	delete fView;
87 }
88 
89 
90 BArchivable*
91 BTab::Instantiate(BMessage* archive)
92 {
93 	if (validate_instantiation(archive, "BTab"))
94 		return new BTab(archive);
95 
96 	return NULL;
97 }
98 
99 
100 status_t
101 BTab::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 
114 status_t
115 BTab::Perform(uint32 d, void* arg)
116 {
117 	return BArchivable::Perform(d, arg);
118 }
119 
120 
121 const char*
122 BTab::Label() const
123 {
124 	if (fView != NULL)
125 		return fView->Name();
126 	else
127 		return NULL;
128 }
129 
130 
131 void
132 BTab::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 
144 bool
145 BTab::IsSelected() const
146 {
147 	return fSelected;
148 }
149 
150 
151 void
152 BTab::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 
166 void
167 BTab::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 
185 void
186 BTab::SetEnabled(bool enable)
187 {
188 	fEnabled = enable;
189 }
190 
191 
192 bool
193 BTab::IsEnabled() const
194 {
195 	return fEnabled;
196 }
197 
198 
199 void
200 BTab::MakeFocus(bool focus)
201 {
202 	fFocus = focus;
203 }
204 
205 
206 bool
207 BTab::IsFocus() const
208 {
209 	return fFocus;
210 }
211 
212 
213 void
214 BTab::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 
232 BView*
233 BTab::View() const
234 {
235 	return fView;
236 }
237 
238 
239 void
240 BTab::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 
276 void
277 BTab::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 
318 void
319 BTab::DrawTab(BView* owner, BRect frame, tab_position position, bool full)
320 {
321 	rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR);
322 	uint32 borders = 0;
323 	if (fTabView->TabSide() == BTabView::kTopSide
324 		|| fTabView->TabSide() == BTabView::kBottomSide) {
325 		borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
326 
327 		if (frame.left == owner->Bounds().left)
328 			borders |= BControlLook::B_LEFT_BORDER;
329 
330 		if (frame.right == owner->Bounds().right)
331 			borders |= BControlLook::B_RIGHT_BORDER;
332 	} else if (fTabView->TabSide() == BTabView::kLeftSide
333 		|| fTabView->TabSide() == BTabView::kRightSide) {
334 		borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
335 
336 		if (frame.top == owner->Bounds().top)
337 			borders |= BControlLook::B_TOP_BORDER;
338 
339 		if (frame.bottom == owner->Bounds().bottom)
340 			borders |= BControlLook::B_BOTTOM_BORDER;
341 	}
342 
343 	if (position == B_TAB_FRONT) {
344 		be_control_look->DrawActiveTab(owner, frame, frame, no_tint, 0,
345 			borders, fTabView->TabSide());
346 	} else {
347 		be_control_look->DrawInactiveTab(owner, frame, frame, no_tint, 0,
348 			borders, fTabView->TabSide());
349 	}
350 
351 	DrawLabel(owner, frame);
352 }
353 
354 
355 //	#pragma mark - FBC padding and private methods
356 
357 
358 void BTab::_ReservedTab1() {}
359 void BTab::_ReservedTab2() {}
360 void BTab::_ReservedTab3() {}
361 void BTab::_ReservedTab4() {}
362 void BTab::_ReservedTab5() {}
363 void BTab::_ReservedTab6() {}
364 void BTab::_ReservedTab7() {}
365 void BTab::_ReservedTab8() {}
366 void BTab::_ReservedTab9() {}
367 void BTab::_ReservedTab10() {}
368 void BTab::_ReservedTab11() {}
369 void BTab::_ReservedTab12() {}
370 
371 BTab &BTab::operator=(const BTab &)
372 {
373 	// this is private and not functional, but exported
374 	return *this;
375 }
376 
377 
378 //	#pragma mark - BTabView
379 
380 
381 BTabView::BTabView(const char* name, button_width width, uint32 flags)
382 	:
383 	BView(name, flags)
384 {
385 	_InitObject(true, width);
386 }
387 
388 
389 BTabView::BTabView(BRect frame, const char* name, button_width width,
390 	uint32 resizeMask, uint32 flags)
391 	:
392 	BView(frame, name, resizeMask, flags)
393 {
394 	_InitObject(false, width);
395 }
396 
397 
398 BTabView::~BTabView()
399 {
400 	for (int32 i = 0; i < CountTabs(); i++)
401 		delete TabAt(i);
402 
403 	delete fTabList;
404 }
405 
406 
407 BTabView::BTabView(BMessage* archive)
408 	:
409 	BView(BUnarchiver::PrepareArchive(archive)),
410 	fTabList(new BList),
411 	fContainerView(NULL),
412 	fFocus(-1)
413 {
414 	BUnarchiver unarchiver(archive);
415 
416 	int16 width;
417 	if (archive->FindInt16("_but_width", &width) == B_OK)
418 		fTabWidthSetting = (button_width)width;
419 	else
420 		fTabWidthSetting = B_WIDTH_AS_USUAL;
421 
422 	if (archive->FindFloat("_high", &fTabHeight) != B_OK) {
423 		font_height fh;
424 		GetFontHeight(&fh);
425 		fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f);
426 	}
427 
428 	if (archive->FindInt32("_sel", &fSelection) != B_OK)
429 		fSelection = -1;
430 
431 	if (archive->FindInt32("_border_style", (int32*)&fBorderStyle) != B_OK)
432 		fBorderStyle = B_FANCY_BORDER;
433 
434 	if (archive->FindInt32("_TabSide", (int32*)&fTabSide) != B_OK)
435 		fTabSide = kTopSide;
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 result = BView::Archive(archive, deep);
489 
490 	if (result == B_OK)
491 		result = archive->AddInt16("_but_width", fTabWidthSetting);
492 	if (result == B_OK)
493 		result = archive->AddFloat("_high", fTabHeight);
494 	if (result == B_OK)
495 		result = archive->AddInt32("_sel", fSelection);
496 	if (result == B_OK && fBorderStyle != B_FANCY_BORDER)
497 		result = archive->AddInt32("_border_style", fBorderStyle);
498 	if (result == B_OK && fTabSide != kTopSide)
499 		result = archive->AddInt32("_TabSide", fTabSide);
500 
501 	if (result == B_OK && deep) {
502 		for (int32 i = 0; i < CountTabs(); i++) {
503 			BTab* tab = TabAt(i);
504 
505 			if ((result = archiver.AddArchivable("_l_items", tab, deep))
506 					!= B_OK) {
507 				break;
508 			}
509 			result = archiver.AddArchivable("_view_list", tab->View(), deep);
510 		}
511 	}
512 
513 	return archiver.Finish(result);
514 }
515 
516 
517 status_t
518 BTabView::AllUnarchived(const BMessage* archive)
519 {
520 	status_t err = BView::AllUnarchived(archive);
521 	if (err != B_OK)
522 		return err;
523 
524 	fContainerView = ChildAt(0);
525 	_InitContainerView(Flags() & B_SUPPORTS_LAYOUT);
526 
527 	BUnarchiver unarchiver(archive);
528 
529 	int32 tabCount;
530 	archive->GetInfo("_l_items", NULL, &tabCount);
531 	for (int32 i = 0; i < tabCount && err == B_OK; i++) {
532 		BTab* tab;
533 		err = unarchiver.FindObject("_l_items", i, tab);
534 		if (err == B_OK && tab) {
535 			BView* view;
536 			if ((err = unarchiver.FindObject("_view_list", i,
537 				BUnarchiver::B_DONT_ASSUME_OWNERSHIP, view)) != B_OK)
538 				break;
539 
540 			tab->SetView(view);
541 			fTabList->AddItem(tab);
542 		}
543 	}
544 
545 	if (err == B_OK)
546 		Select(fSelection);
547 
548 	return err;
549 }
550 
551 
552 status_t
553 BTabView::Perform(perform_code code, void* _data)
554 {
555 	switch (code) {
556 		case PERFORM_CODE_ALL_UNARCHIVED:
557 		{
558 			perform_data_all_unarchived* data
559 				= (perform_data_all_unarchived*)_data;
560 
561 			data->return_value = BTabView::AllUnarchived(data->archive);
562 			return B_OK;
563 		}
564 	}
565 
566 	return BView::Perform(code, _data);
567 }
568 
569 
570 void
571 BTabView::AttachedToWindow()
572 {
573 	BView::AttachedToWindow();
574 
575 	if (fSelection < 0 && CountTabs() > 0)
576 		Select(0);
577 }
578 
579 
580 void
581 BTabView::DetachedFromWindow()
582 {
583 	BView::DetachedFromWindow();
584 }
585 
586 
587 void
588 BTabView::AllAttached()
589 {
590 	BView::AllAttached();
591 }
592 
593 
594 void
595 BTabView::AllDetached()
596 {
597 	BView::AllDetached();
598 }
599 
600 
601 // #pragma mark -
602 
603 
604 void
605 BTabView::MessageReceived(BMessage* message)
606 {
607 	switch (message->what) {
608 		case B_GET_PROPERTY:
609 		case B_SET_PROPERTY:
610 		{
611 			BMessage reply(B_REPLY);
612 			bool handled = false;
613 
614 			BMessage specifier;
615 			int32 index;
616 			int32 form;
617 			const char* property;
618 			if (message->GetCurrentSpecifier(&index, &specifier, &form,
619 					&property) == B_OK) {
620 				if (strcmp(property, "Selection") == 0) {
621 					if (message->what == B_GET_PROPERTY) {
622 						reply.AddInt32("result", fSelection);
623 						handled = true;
624 					} else {
625 						// B_GET_PROPERTY
626 						int32 selection;
627 						if (message->FindInt32("data", &selection) == B_OK) {
628 							Select(selection);
629 							reply.AddInt32("error", B_OK);
630 							handled = true;
631 						}
632 					}
633 				}
634 			}
635 
636 			if (handled)
637 				message->SendReply(&reply);
638 			else
639 				BView::MessageReceived(message);
640 			break;
641 		}
642 
643 #if 0
644 		// TODO this would be annoying as-is, but maybe it makes sense with
645 		// a modifier or using only deltaX (not the main mouse wheel)
646 		case B_MOUSE_WHEEL_CHANGED:
647 		{
648 			float deltaX = 0.0f;
649 			float deltaY = 0.0f;
650 			message->FindFloat("be:wheel_delta_x", &deltaX);
651 			message->FindFloat("be:wheel_delta_y", &deltaY);
652 
653 			if (deltaX == 0.0f && deltaY == 0.0f)
654 				return;
655 
656 			if (deltaY == 0.0f)
657 				deltaY = deltaX;
658 
659 			int32 selection = Selection();
660 			int32 numTabs = CountTabs();
661 			if (deltaY > 0  && selection < numTabs - 1) {
662 				// move to the right tab.
663 				Select(Selection() + 1);
664 			} else if (deltaY < 0 && selection > 0 && numTabs > 1) {
665 				// move to the left tab.
666 				Select(selection - 1);
667 			}
668 			break;
669 		}
670 #endif
671 
672 		default:
673 			BView::MessageReceived(message);
674 			break;
675 	}
676 }
677 
678 
679 void
680 BTabView::KeyDown(const char* bytes, int32 numBytes)
681 {
682 	if (IsHidden())
683 		return;
684 
685 	switch (bytes[0]) {
686 		case B_DOWN_ARROW:
687 		case B_LEFT_ARROW: {
688 			int32 focus = fFocus - 1;
689 			if (focus < 0)
690 				focus = CountTabs() - 1;
691 			SetFocusTab(focus, true);
692 			break;
693 		}
694 
695 		case B_UP_ARROW:
696 		case B_RIGHT_ARROW: {
697 			int32 focus = fFocus + 1;
698 			if (focus >= CountTabs())
699 				focus = 0;
700 			SetFocusTab(focus, true);
701 			break;
702 		}
703 
704 		case B_RETURN:
705 		case B_SPACE:
706 			Select(FocusTab());
707 			break;
708 
709 		default:
710 			BView::KeyDown(bytes, numBytes);
711 	}
712 }
713 
714 
715 void
716 BTabView::MouseDown(BPoint where)
717 {
718 	// Which button is pressed?
719 	uint32 buttons = 0;
720 	BMessage* currentMessage = Window()->CurrentMessage();
721 	if (currentMessage != NULL) {
722 		currentMessage->FindInt32("buttons", (int32*)&buttons);
723 	}
724 
725 	int32 selection = Selection();
726 	int32 numTabs = CountTabs();
727 	if (buttons & B_MOUSE_BUTTON(4)) {
728 		// The "back" mouse button moves to previous tab
729 		if (selection > 0 && numTabs > 1)
730 			Select(Selection() - 1);
731 	} else if (buttons & B_MOUSE_BUTTON(5)) {
732 		// The "forward" mouse button moves to next tab
733 		if (selection < numTabs - 1)
734 			Select(Selection() + 1);
735 	} else {
736 		// Other buttons are used to select a tab by clicking directly on it
737 		for (int32 i = 0; i < CountTabs(); i++) {
738 			if (TabFrame(i).Contains(where)
739 					&& i != Selection()) {
740 				Select(i);
741 				return;
742 			}
743 		}
744 	}
745 
746 	BView::MouseDown(where);
747 }
748 
749 
750 void
751 BTabView::MouseUp(BPoint where)
752 {
753 	BView::MouseUp(where);
754 }
755 
756 
757 void
758 BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
759 {
760 	BView::MouseMoved(where, transit, dragMessage);
761 }
762 
763 
764 void
765 BTabView::Pulse()
766 {
767 	BView::Pulse();
768 }
769 
770 
771 void
772 BTabView::Select(int32 index)
773 {
774 	if (index == Selection())
775 		return;
776 
777 	if (index < 0 || index >= CountTabs())
778 		index = Selection();
779 
780 	BTab* tab = TabAt(Selection());
781 
782 	if (tab)
783 		tab->Deselect();
784 
785 	tab = TabAt(index);
786 	if (tab != NULL && fContainerView != NULL) {
787 		if (index == 0)
788 			fTabOffset = 0.0f;
789 
790 		tab->Select(fContainerView);
791 		fSelection = index;
792 
793 		// make the view visible through the layout if there is one
794 		BCardLayout* layout
795 			= dynamic_cast<BCardLayout*>(fContainerView->GetLayout());
796 		if (layout != NULL)
797 			layout->SetVisibleItem(index);
798 	}
799 
800 	Invalidate();
801 
802 	if (index != 0 && !Bounds().Contains(TabFrame(index))){
803 		if (!Bounds().Contains(TabFrame(index).LeftTop()))
804 			fTabOffset += TabFrame(index).left - Bounds().left - 20.0f;
805 		else
806 			fTabOffset += TabFrame(index).right - Bounds().right + 20.0f;
807 
808 		Invalidate();
809 	}
810 
811 	SetFocusTab(index, true);
812 }
813 
814 
815 int32
816 BTabView::Selection() const
817 {
818 	return fSelection;
819 }
820 
821 
822 void
823 BTabView::WindowActivated(bool active)
824 {
825 	BView::WindowActivated(active);
826 
827 	if (IsFocus())
828 		Invalidate();
829 }
830 
831 
832 void
833 BTabView::MakeFocus(bool focus)
834 {
835 	BView::MakeFocus(focus);
836 
837 	SetFocusTab(Selection(), focus);
838 }
839 
840 
841 void
842 BTabView::SetFocusTab(int32 tab, bool focus)
843 {
844 	if (tab >= CountTabs())
845 		tab = 0;
846 
847 	if (tab < 0)
848 		tab = CountTabs() - 1;
849 
850 	if (focus) {
851 		if (tab == fFocus)
852 			return;
853 
854 		if (fFocus != -1){
855 			if (TabAt (fFocus) != NULL)
856 				TabAt(fFocus)->MakeFocus(false);
857 			Invalidate(TabFrame(fFocus));
858 		}
859 		if (TabAt(tab) != NULL){
860 			TabAt(tab)->MakeFocus(true);
861 			Invalidate(TabFrame(tab));
862 			fFocus = tab;
863 		}
864 	} else if (fFocus != -1) {
865 		TabAt(fFocus)->MakeFocus(false);
866 		Invalidate(TabFrame(fFocus));
867 		fFocus = -1;
868 	}
869 }
870 
871 
872 int32
873 BTabView::FocusTab() const
874 {
875 	return fFocus;
876 }
877 
878 
879 void
880 BTabView::Draw(BRect updateRect)
881 {
882 	DrawTabs();
883 	DrawBox(TabFrame(fSelection));
884 
885 	if (IsFocus() && fFocus != -1)
886 		TabAt(fFocus)->DrawFocusMark(this, TabFrame(fFocus));
887 }
888 
889 
890 BRect
891 BTabView::DrawTabs()
892 {
893 	BRect bounds(Bounds());
894 	BRect tabFrame(bounds);
895 	uint32 borders = 0;
896 	rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
897 
898 	// set tabFrame to area around tabs
899 	if (fTabSide == kTopSide || fTabSide == kBottomSide) {
900 		if (fTabSide == kTopSide)
901 			tabFrame.bottom = fTabHeight;
902 		else
903 			tabFrame.top = tabFrame.bottom - fTabHeight;
904 	} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
905 		if (fTabSide == kLeftSide)
906 			tabFrame.right = fTabHeight;
907 		else
908 			tabFrame.left = tabFrame.right - fTabHeight;
909 	}
910 
911 	// draw frame behind tabs
912 	be_control_look->DrawTabFrame(this, tabFrame, bounds, base, 0,
913 		borders, fBorderStyle, fTabSide);
914 
915 	// draw the tabs on top of the tab frame
916 	BRect activeTabFrame;
917 	int32 tabCount = CountTabs();
918 	for (int32 i = 0; i < tabCount; i++) {
919 		BRect tabFrame = TabFrame(i);
920 		if (i == fSelection)
921 			activeTabFrame = tabFrame;
922 
923 		TabAt(i)->DrawTab(this, tabFrame,
924 			i == fSelection ? B_TAB_FRONT :
925 				(i == 0) ? B_TAB_FIRST : B_TAB_ANY,
926 			i + 1 != fSelection);
927 	}
928 
929 	BRect tabsBounds;
930 	float last = 0.0f;
931 	float lastTab = 0.0f;
932 	if (fTabSide == kTopSide || fTabSide == kBottomSide) {
933 		lastTab = TabFrame(tabCount - 1).right;
934 		last = tabFrame.right;
935 		tabsBounds.left = tabsBounds.right = lastTab;
936 		borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER;
937 	} else if (fTabSide == kLeftSide || fTabSide == kRightSide) {
938 		lastTab = TabFrame(tabCount - 1).bottom;
939 		last = tabFrame.bottom;
940 		tabsBounds.top = tabsBounds.bottom = lastTab;
941 		borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER;
942 	}
943 
944 	if (lastTab < last) {
945 		// draw a 1px right border on the last tab
946 		be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0,
947 			borders, fTabSide);
948 	}
949 
950 	return fSelection < CountTabs() ? TabFrame(fSelection) : BRect();
951 }
952 
953 
954 void
955 BTabView::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 
989 BRect
990 BTabView::TabFrame(int32 index) const
991 {
992 	if (index >= CountTabs() || index < 0)
993 		return BRect();
994 
995 	float width = 100.0f;
996 	float height = fTabHeight;
997 	float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING);
998 	BRect bounds(Bounds());
999 
1000 	switch (fTabWidthSetting) {
1001 		case B_WIDTH_FROM_LABEL:
1002 		{
1003 			float x = 0.0f;
1004 			for (int32 i = 0; i < index; i++){
1005 				x += StringWidth(TabAt(i)->Label()) + 20.0f;
1006 			}
1007 
1008 			switch (fTabSide) {
1009 				case kTopSide:
1010 					return BRect(offset + x, 0.0f,
1011 						offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
1012 						height);
1013 				case kBottomSide:
1014 					return BRect(offset + x, bounds.bottom - height,
1015 						offset + x + StringWidth(TabAt(index)->Label()) + 20.0f,
1016 						bounds.bottom);
1017 				case kLeftSide:
1018 					return BRect(0.0f, offset + x, height, offset + x
1019 						+ StringWidth(TabAt(index)->Label()) + 20.0f);
1020 				case kRightSide:
1021 					return BRect(bounds.right - height, offset + x,
1022 						bounds.right, offset + x
1023 							+ StringWidth(TabAt(index)->Label()) + 20.0f);
1024 				default:
1025 					return BRect();
1026 			}
1027 		}
1028 
1029 		case B_WIDTH_FROM_WIDEST:
1030 			width = 0.0;
1031 			for (int32 i = 0; i < CountTabs(); i++) {
1032 				float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f;
1033 				if (tabWidth > width)
1034 					width = tabWidth;
1035 			}
1036 			// fall through
1037 
1038 		case B_WIDTH_AS_USUAL:
1039 		default:
1040 			switch (fTabSide) {
1041 				case kTopSide:
1042 					return BRect(offset + index * width, 0.0f,
1043 						offset + index * width + width, height);
1044 				case kBottomSide:
1045 					return BRect(offset + index * width, bounds.bottom - height,
1046 						offset + index * width + width, bounds.bottom);
1047 				case kLeftSide:
1048 					return BRect(0.0f, offset + index * width, height,
1049 						offset + index * width + width);
1050 				case kRightSide:
1051 					return BRect(bounds.right - height, offset + index * width,
1052 						bounds.right, offset + index * width + width);
1053 				default:
1054 					return BRect();
1055 			}
1056 	}
1057 }
1058 
1059 
1060 void
1061 BTabView::SetFlags(uint32 flags)
1062 {
1063 	BView::SetFlags(flags);
1064 }
1065 
1066 
1067 void
1068 BTabView::SetResizingMode(uint32 mode)
1069 {
1070 	BView::SetResizingMode(mode);
1071 }
1072 
1073 
1074 // #pragma mark -
1075 
1076 
1077 void
1078 BTabView::ResizeToPreferred()
1079 {
1080 	BView::ResizeToPreferred();
1081 }
1082 
1083 
1084 void
1085 BTabView::GetPreferredSize(float* _width, float* _height)
1086 {
1087 	BView::GetPreferredSize(_width, _height);
1088 }
1089 
1090 
1091 BSize
1092 BTabView::MinSize()
1093 {
1094 	BSize size;
1095 	if (GetLayout())
1096 		size = GetLayout()->MinSize();
1097 	else {
1098 		size = _TabsMinSize();
1099 		BSize containerSize = fContainerView->MinSize();
1100 		containerSize.width += 2 * _BorderWidth();
1101 		containerSize.height += 2 * _BorderWidth();
1102 		if (containerSize.width > size.width)
1103 			size.width = containerSize.width;
1104 		size.height += containerSize.height;
1105 	}
1106 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), size);
1107 }
1108 
1109 
1110 BSize
1111 BTabView::MaxSize()
1112 {
1113 	BSize size;
1114 	if (GetLayout())
1115 		size = GetLayout()->MaxSize();
1116 	else {
1117 		size = _TabsMinSize();
1118 		BSize containerSize = fContainerView->MaxSize();
1119 		containerSize.width += 2 * _BorderWidth();
1120 		containerSize.height += 2 * _BorderWidth();
1121 		if (containerSize.width > size.width)
1122 			size.width = containerSize.width;
1123 		size.height += containerSize.height;
1124 	}
1125 	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size);
1126 }
1127 
1128 
1129 BSize
1130 BTabView::PreferredSize()
1131 {
1132 	BSize size;
1133 	if (GetLayout() != NULL)
1134 		size = GetLayout()->PreferredSize();
1135 	else {
1136 		size = _TabsMinSize();
1137 		BSize containerSize = fContainerView->PreferredSize();
1138 		containerSize.width += 2 * _BorderWidth();
1139 		containerSize.height += 2 * _BorderWidth();
1140 		if (containerSize.width > size.width)
1141 			size.width = containerSize.width;
1142 		size.height += containerSize.height;
1143 	}
1144 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size);
1145 }
1146 
1147 
1148 void
1149 BTabView::FrameMoved(BPoint newPosition)
1150 {
1151 	BView::FrameMoved(newPosition);
1152 }
1153 
1154 
1155 void
1156 BTabView::FrameResized(float newWidth, float newHeight)
1157 {
1158 	BView::FrameResized(newWidth, newHeight);
1159 }
1160 
1161 
1162 // #pragma mark -
1163 
1164 
1165 BHandler*
1166 BTabView::ResolveSpecifier(BMessage* message, int32 index,
1167 	BMessage* specifier, int32 what, const char* property)
1168 {
1169 	BPropertyInfo propInfo(sPropertyList);
1170 
1171 	if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK)
1172 		return this;
1173 
1174 	return BView::ResolveSpecifier(message, index, specifier, what, property);
1175 }
1176 
1177 
1178 status_t
1179 BTabView::GetSupportedSuites(BMessage* message)
1180 {
1181 	message->AddString("suites", "suite/vnd.Be-tab-view");
1182 
1183 	BPropertyInfo propInfo(sPropertyList);
1184 	message->AddFlat("messages", &propInfo);
1185 
1186 	return BView::GetSupportedSuites(message);
1187 }
1188 
1189 
1190 // #pragma mark -
1191 
1192 
1193 void
1194 BTabView::AddTab(BView* target, BTab* tab)
1195 {
1196 	if (tab == NULL)
1197 		tab = new BTab(target);
1198 	else
1199 		tab->SetView(target);
1200 
1201 	if (fContainerView->GetLayout())
1202 		fContainerView->GetLayout()->AddView(CountTabs(), target);
1203 
1204 	fTabList->AddItem(tab);
1205 	BTab::Private(tab).SetTabView(this);
1206 
1207 	// When we haven't had a any tabs before, but are already attached to the
1208 	// window, select this one.
1209 	if (CountTabs() == 1 && Window() != NULL)
1210 		Select(0);
1211 }
1212 
1213 
1214 BTab*
1215 BTabView::RemoveTab(int32 index)
1216 {
1217 	if (index < 0 || index >= CountTabs())
1218 		return NULL;
1219 
1220 	BTab* tab = (BTab*)fTabList->RemoveItem(index);
1221 	if (tab == NULL)
1222 		return NULL;
1223 
1224 	tab->Deselect();
1225 	BTab::Private(tab).SetTabView(NULL);
1226 
1227 	if (fContainerView->GetLayout())
1228 		fContainerView->GetLayout()->RemoveItem(index);
1229 
1230 	if (CountTabs() == 0)
1231 		fFocus = -1;
1232 	else if (index <= fSelection)
1233 		Select(fSelection - 1);
1234 
1235 	if (fFocus >= 0) {
1236 		if (fFocus == CountTabs() - 1 || CountTabs() == 0)
1237 			SetFocusTab(fFocus, false);
1238 		else
1239 			SetFocusTab(fFocus, true);
1240 	}
1241 
1242 	return tab;
1243 }
1244 
1245 
1246 BTab*
1247 BTabView::TabAt(int32 index) const
1248 {
1249 	return (BTab*)fTabList->ItemAt(index);
1250 }
1251 
1252 
1253 void
1254 BTabView::SetTabWidth(button_width width)
1255 {
1256 	fTabWidthSetting = width;
1257 
1258 	Invalidate();
1259 }
1260 
1261 
1262 button_width
1263 BTabView::TabWidth() const
1264 {
1265 	return fTabWidthSetting;
1266 }
1267 
1268 
1269 void
1270 BTabView::SetTabHeight(float height)
1271 {
1272 	if (fTabHeight == height)
1273 		return;
1274 
1275 	fTabHeight = height;
1276 	_LayoutContainerView(GetLayout() != NULL);
1277 
1278 	Invalidate();
1279 }
1280 
1281 
1282 float
1283 BTabView::TabHeight() const
1284 {
1285 	return fTabHeight;
1286 }
1287 
1288 
1289 void
1290 BTabView::SetBorder(border_style borderStyle)
1291 {
1292 	if (fBorderStyle == borderStyle)
1293 		return;
1294 
1295 	fBorderStyle = borderStyle;
1296 
1297 	_LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0);
1298 }
1299 
1300 
1301 border_style
1302 BTabView::Border() const
1303 {
1304 	return fBorderStyle;
1305 }
1306 
1307 
1308 void
1309 BTabView::SetTabSide(tab_side tabSide)
1310 {
1311 	if (fTabSide == tabSide)
1312 		return;
1313 
1314 	fTabSide = tabSide;
1315 	_LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
1316 }
1317 
1318 
1319 BTabView::tab_side
1320 BTabView::TabSide() const
1321 {
1322 	return fTabSide;
1323 }
1324 
1325 
1326 BView*
1327 BTabView::ContainerView() const
1328 {
1329 	return fContainerView;
1330 }
1331 
1332 
1333 int32
1334 BTabView::CountTabs() const
1335 {
1336 	return fTabList->CountItems();
1337 }
1338 
1339 
1340 BView*
1341 BTabView::ViewForTab(int32 tabIndex) const
1342 {
1343 	BTab* tab = TabAt(tabIndex);
1344 	if (tab != NULL)
1345 		return tab->View();
1346 
1347 	return NULL;
1348 }
1349 
1350 
1351 void
1352 BTabView::_InitObject(bool layouted, button_width width)
1353 {
1354 	fTabList = new BList;
1355 
1356 	fTabWidthSetting = width;
1357 	fSelection = -1;
1358 	fFocus = -1;
1359 	fTabOffset = 0.0f;
1360 	fBorderStyle = B_FANCY_BORDER;
1361 	fTabSide = kTopSide;
1362 
1363 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1364 	SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
1365 
1366 	font_height fh;
1367 	GetFontHeight(&fh);
1368 	fTabHeight = ceilf(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() == NULL) {
1382 			SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL));
1383 			needsLayout = true;
1384 		}
1385 
1386 		if (fContainerView == NULL) {
1387 			fContainerView = new BView("view container", B_WILL_DRAW);
1388 			fContainerView->SetLayout(new(std::nothrow) BCardLayout());
1389 			createdContainer = true;
1390 		}
1391 	} else if (fContainerView == NULL) {
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->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1402 		fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
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 
1435 		case B_PLAIN_BORDER:
1436 			return 1.0f;
1437 
1438 		case B_NO_BORDER:
1439 			return 0.0f;
1440 	}
1441 }
1442 
1443 
1444 void
1445 BTabView::_LayoutContainerView(bool layouted)
1446 {
1447 	float borderWidth = _BorderWidth();
1448 	if (layouted) {
1449 		float topBorderOffset;
1450 		switch (fBorderStyle) {
1451 			default:
1452 			case B_FANCY_BORDER:
1453 				topBorderOffset = 1.0f;
1454 				break;
1455 
1456 			case B_PLAIN_BORDER:
1457 				topBorderOffset = 0.0f;
1458 				break;
1459 
1460 			case B_NO_BORDER:
1461 				topBorderOffset = -1.0f;
1462 				break;
1463 		}
1464 		BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout());
1465 		if (layout != NULL) {
1466 			float inset = borderWidth + TabHeight() - topBorderOffset;
1467 			switch (fTabSide) {
1468 				case kTopSide:
1469 					layout->SetInsets(borderWidth, inset, borderWidth,
1470 						borderWidth);
1471 					break;
1472 				case kBottomSide:
1473 					layout->SetInsets(borderWidth, borderWidth, borderWidth,
1474 						inset);
1475 					break;
1476 				case kLeftSide:
1477 					layout->SetInsets(inset, borderWidth, borderWidth,
1478 						borderWidth);
1479 					break;
1480 				case kRightSide:
1481 					layout->SetInsets(borderWidth, borderWidth, inset,
1482 						borderWidth);
1483 					break;
1484 			}
1485 		}
1486 	} else {
1487 		BRect bounds = Bounds();
1488 		switch (fTabSide) {
1489 			case kTopSide:
1490 				bounds.top += TabHeight();
1491 				break;
1492 			case kBottomSide:
1493 				bounds.bottom -= TabHeight();
1494 				break;
1495 			case kLeftSide:
1496 				bounds.left += TabHeight();
1497 				break;
1498 			case kRightSide:
1499 				bounds.right -= TabHeight();
1500 				break;
1501 		}
1502 		bounds.InsetBy(borderWidth, borderWidth);
1503 
1504 		fContainerView->MoveTo(bounds.left, bounds.top);
1505 		fContainerView->ResizeTo(bounds.Width(), bounds.Height());
1506 	}
1507 }
1508 
1509 
1510 // #pragma mark - FBC and forbidden
1511 
1512 
1513 void BTabView::_ReservedTabView3() {}
1514 void BTabView::_ReservedTabView4() {}
1515 void BTabView::_ReservedTabView5() {}
1516 void BTabView::_ReservedTabView6() {}
1517 void BTabView::_ReservedTabView7() {}
1518 void BTabView::_ReservedTabView8() {}
1519 void BTabView::_ReservedTabView9() {}
1520 void BTabView::_ReservedTabView10() {}
1521 void BTabView::_ReservedTabView11() {}
1522 void BTabView::_ReservedTabView12() {}
1523 
1524 
1525 BTabView::BTabView(const BTabView& tabView)
1526 	: BView(tabView)
1527 {
1528 	// this is private and not functional, but exported
1529 }
1530 
1531 
1532 BTabView&
1533 BTabView::operator=(const BTabView&)
1534 {
1535 	// this is private and not functional, but exported
1536 	return *this;
1537 }
1538 
1539 //	#pragma mark - binary compatibility
1540 
1541 
1542 extern "C" void
1543 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
1544 	BTabView* tabView, border_style borderStyle)
1545 {
1546 	tabView->BTabView::SetBorder(borderStyle);
1547 }
1548 
1549 extern "C" void
1550 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)(
1551 	BTabView* tabView, BTabView::tab_side tabSide)
1552 {
1553 	tabView->BTabView::SetTabSide(tabSide);
1554 }
1555