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
BTab(BView * contentsView)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
BTab(BMessage * archive)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
~BTab()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*
Instantiate(BMessage * archive)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
Archive(BMessage * data,bool deep) const101 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
Perform(uint32 d,void * arg)115 BTab::Perform(uint32 d, void* arg)
116 {
117 return BArchivable::Perform(d, arg);
118 }
119
120
121 const char*
Label() const122 BTab::Label() const
123 {
124 if (fView != NULL)
125 return fView->Name();
126 else
127 return NULL;
128 }
129
130
131 void
SetLabel(const char * label)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
IsSelected() const145 BTab::IsSelected() const
146 {
147 return fSelected;
148 }
149
150
151 void
Select(BView * owner)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
Deselect()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
SetEnabled(bool enable)186 BTab::SetEnabled(bool enable)
187 {
188 fEnabled = enable;
189 }
190
191
192 bool
IsEnabled() const193 BTab::IsEnabled() const
194 {
195 return fEnabled;
196 }
197
198
199 void
MakeFocus(bool focus)200 BTab::MakeFocus(bool focus)
201 {
202 fFocus = focus;
203 }
204
205
206 bool
IsFocus() const207 BTab::IsFocus() const
208 {
209 return fFocus;
210 }
211
212
213 void
SetView(BView * view)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*
View() const233 BTab::View() const
234 {
235 return fView;
236 }
237
238
239 void
DrawFocusMark(BView * owner,BRect frame)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
DrawLabel(BView * owner,BRect frame)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
DrawTab(BView * owner,BRect frame,tab_position,bool)319 BTab::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
348 uint32
_Borders(BView * owner,BRect frame)349 BTab::_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
_ReservedTab1()382 void BTab::_ReservedTab1() {}
_ReservedTab2()383 void BTab::_ReservedTab2() {}
_ReservedTab3()384 void BTab::_ReservedTab3() {}
_ReservedTab4()385 void BTab::_ReservedTab4() {}
_ReservedTab5()386 void BTab::_ReservedTab5() {}
_ReservedTab6()387 void BTab::_ReservedTab6() {}
_ReservedTab7()388 void BTab::_ReservedTab7() {}
_ReservedTab8()389 void BTab::_ReservedTab8() {}
_ReservedTab9()390 void BTab::_ReservedTab9() {}
_ReservedTab10()391 void BTab::_ReservedTab10() {}
_ReservedTab11()392 void BTab::_ReservedTab11() {}
_ReservedTab12()393 void BTab::_ReservedTab12() {}
394
operator =(const BTab &)395 BTab &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
BTabView(const char * name,button_width width,uint32 flags)405 BTabView::BTabView(const char* name, button_width width, uint32 flags)
406 :
407 BView(name, flags)
408 {
409 _InitObject(true, width);
410 }
411
412
BTabView(BRect frame,const char * name,button_width width,uint32 resizeMask,uint32 flags)413 BTabView::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
~BTabView()422 BTabView::~BTabView()
423 {
424 for (int32 i = 0; i < CountTabs(); i++)
425 delete TabAt(i);
426
427 delete fTabList;
428 }
429
430
BTabView(BMessage * archive)431 BTabView::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
497 BArchivable*
Instantiate(BMessage * archive)498 BTabView::Instantiate(BMessage* archive)
499 {
500 if ( validate_instantiation(archive, "BTabView"))
501 return new BTabView(archive);
502
503 return NULL;
504 }
505
506
507 status_t
Archive(BMessage * archive,bool deep) const508 BTabView::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
541 status_t
AllUnarchived(const BMessage * archive)542 BTabView::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
576 status_t
Perform(perform_code code,void * _data)577 BTabView::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
594 void
AttachedToWindow()595 BTabView::AttachedToWindow()
596 {
597 BView::AttachedToWindow();
598
599 if (fSelection < 0 && CountTabs() > 0)
600 Select(0);
601 }
602
603
604 void
DetachedFromWindow()605 BTabView::DetachedFromWindow()
606 {
607 BView::DetachedFromWindow();
608 }
609
610
611 void
AllAttached()612 BTabView::AllAttached()
613 {
614 BView::AllAttached();
615 }
616
617
618 void
AllDetached()619 BTabView::AllDetached()
620 {
621 BView::AllDetached();
622 }
623
624
625 // #pragma mark -
626
627
628 void
MessageReceived(BMessage * message)629 BTabView::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
703 void
KeyDown(const char * bytes,int32 numBytes)704 BTabView::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
739 void
MouseDown(BPoint where)740 BTabView::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
774 void
MouseUp(BPoint where)775 BTabView::MouseUp(BPoint where)
776 {
777 BView::MouseUp(where);
778 }
779
780
781 void
MouseMoved(BPoint where,uint32 transit,const BMessage * dragMessage)782 BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
783 {
784 BView::MouseMoved(where, transit, dragMessage);
785 }
786
787
788 void
Pulse()789 BTabView::Pulse()
790 {
791 BView::Pulse();
792 }
793
794
795 void
Select(int32 index)796 BTabView::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
839 int32
Selection() const840 BTabView::Selection() const
841 {
842 return fSelection;
843 }
844
845
846 void
WindowActivated(bool active)847 BTabView::WindowActivated(bool active)
848 {
849 BView::WindowActivated(active);
850
851 if (IsFocus())
852 Invalidate();
853 }
854
855
856 void
MakeFocus(bool focus)857 BTabView::MakeFocus(bool focus)
858 {
859 BView::MakeFocus(focus);
860
861 SetFocusTab(Selection(), focus);
862 }
863
864
865 void
SetFocusTab(int32 tab,bool focus)866 BTabView::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
896 int32
FocusTab() const897 BTabView::FocusTab() const
898 {
899 return fFocus;
900 }
901
902
903 void
Draw(BRect updateRect)904 BTabView::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
914 BRect
DrawTabs()915 BTabView::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
954 void
DrawBox(BRect selectedTabRect)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
TabFrame(int32 index) const990 BTabView::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
1061 void
SetFlags(uint32 flags)1062 BTabView::SetFlags(uint32 flags)
1063 {
1064 BView::SetFlags(flags);
1065 }
1066
1067
1068 void
SetResizingMode(uint32 mode)1069 BTabView::SetResizingMode(uint32 mode)
1070 {
1071 BView::SetResizingMode(mode);
1072 }
1073
1074
1075 // #pragma mark -
1076
1077
1078 void
ResizeToPreferred()1079 BTabView::ResizeToPreferred()
1080 {
1081 BView::ResizeToPreferred();
1082 }
1083
1084
1085 void
GetPreferredSize(float * _width,float * _height)1086 BTabView::GetPreferredSize(float* _width, float* _height)
1087 {
1088 BView::GetPreferredSize(_width, _height);
1089 }
1090
1091
1092 BSize
MinSize()1093 BTabView::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
1111 BSize
MaxSize()1112 BTabView::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
1130 BSize
PreferredSize()1131 BTabView::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
1149 void
FrameMoved(BPoint newPosition)1150 BTabView::FrameMoved(BPoint newPosition)
1151 {
1152 BView::FrameMoved(newPosition);
1153 }
1154
1155
1156 void
FrameResized(float newWidth,float newHeight)1157 BTabView::FrameResized(float newWidth, float newHeight)
1158 {
1159 BView::FrameResized(newWidth, newHeight);
1160 }
1161
1162
1163 // #pragma mark -
1164
1165
1166 BHandler*
ResolveSpecifier(BMessage * message,int32 index,BMessage * specifier,int32 what,const char * property)1167 BTabView::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
1179 status_t
GetSupportedSuites(BMessage * message)1180 BTabView::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
1194 void
AddTab(BView * target,BTab * tab)1195 BTabView::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
1215 BTab*
RemoveTab(int32 index)1216 BTabView::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
1247 BTab*
TabAt(int32 index) const1248 BTabView::TabAt(int32 index) const
1249 {
1250 return (BTab*)fTabList->ItemAt(index);
1251 }
1252
1253
1254 void
SetTabWidth(button_width width)1255 BTabView::SetTabWidth(button_width width)
1256 {
1257 fTabWidthSetting = width;
1258
1259 Invalidate();
1260 }
1261
1262
1263 button_width
TabWidth() const1264 BTabView::TabWidth() const
1265 {
1266 return fTabWidthSetting;
1267 }
1268
1269
1270 void
SetTabHeight(float height)1271 BTabView::SetTabHeight(float height)
1272 {
1273 if (fTabHeight == height)
1274 return;
1275
1276 fTabHeight = height;
1277 _LayoutContainerView(GetLayout() != NULL);
1278
1279 Invalidate();
1280 }
1281
1282
1283 float
TabHeight() const1284 BTabView::TabHeight() const
1285 {
1286 return fTabHeight;
1287 }
1288
1289
1290 void
SetBorder(border_style borderStyle)1291 BTabView::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
1302 border_style
Border() const1303 BTabView::Border() const
1304 {
1305 return fBorderStyle;
1306 }
1307
1308
1309 void
SetTabSide(tab_side tabSide)1310 BTabView::SetTabSide(tab_side tabSide)
1311 {
1312 if (fTabSide == tabSide)
1313 return;
1314
1315 fTabSide = tabSide;
1316 _LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT);
1317 }
1318
1319
1320 BTabView::tab_side
TabSide() const1321 BTabView::TabSide() const
1322 {
1323 return fTabSide;
1324 }
1325
1326
1327 BView*
ContainerView() const1328 BTabView::ContainerView() const
1329 {
1330 return fContainerView;
1331 }
1332
1333
1334 int32
CountTabs() const1335 BTabView::CountTabs() const
1336 {
1337 return fTabList->CountItems();
1338 }
1339
1340
1341 BView*
ViewForTab(int32 tabIndex) const1342 BTabView::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
1352 int32
IndexOf(BTab * tab) const1353 BTabView::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
1367 void
_InitObject(bool layouted,button_width width)1368 BTabView::_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
1392 void
_InitContainerView(bool layouted)1393 BTabView::_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
1425 BSize
_TabsMinSize() const1426 BTabView::_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
1444 float
_BorderWidth() const1445 BTabView::_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
1461 void
_LayoutContainerView(bool layouted)1462 BTabView::_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
_ReservedTabView3()1530 void BTabView::_ReservedTabView3() {}
_ReservedTabView4()1531 void BTabView::_ReservedTabView4() {}
_ReservedTabView5()1532 void BTabView::_ReservedTabView5() {}
_ReservedTabView6()1533 void BTabView::_ReservedTabView6() {}
_ReservedTabView7()1534 void BTabView::_ReservedTabView7() {}
_ReservedTabView8()1535 void BTabView::_ReservedTabView8() {}
_ReservedTabView9()1536 void BTabView::_ReservedTabView9() {}
_ReservedTabView10()1537 void BTabView::_ReservedTabView10() {}
_ReservedTabView11()1538 void BTabView::_ReservedTabView11() {}
_ReservedTabView12()1539 void BTabView::_ReservedTabView12() {}
1540
1541
BTabView(const BTabView & tabView)1542 BTabView::BTabView(const BTabView& tabView)
1543 : BView(tabView)
1544 {
1545 // this is private and not functional, but exported
1546 }
1547
1548
1549 BTabView&
operator =(const BTabView &)1550 BTabView::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
1559 extern "C" void
B_IF_GCC_2(_ReservedTabView1__8BTabView,_ZN8BTabView17_ReservedTabView1Ev)1560 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)(
1561 BTabView* tabView, border_style borderStyle)
1562 {
1563 tabView->BTabView::SetBorder(borderStyle);
1564 }
1565
1566 extern "C" void
B_IF_GCC_2(_ReservedTabView2__8BTabView,_ZN8BTabView17_ReservedTabView2Ev)1567 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)(
1568 BTabView* tabView, BTabView::tab_side tabSide)
1569 {
1570 tabView->BTabView::SetTabSide(tabSide);
1571 }
1572