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