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, 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 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 382 void BTab::_ReservedTab1() {} 383 void BTab::_ReservedTab2() {} 384 void BTab::_ReservedTab3() {} 385 void BTab::_ReservedTab4() {} 386 void BTab::_ReservedTab5() {} 387 void BTab::_ReservedTab6() {} 388 void BTab::_ReservedTab7() {} 389 void BTab::_ReservedTab8() {} 390 void BTab::_ReservedTab9() {} 391 void BTab::_ReservedTab10() {} 392 void BTab::_ReservedTab11() {} 393 void BTab::_ReservedTab12() {} 394 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 405 BTabView::BTabView(const char* name, button_width width, uint32 flags) 406 : 407 BView(name, flags) 408 { 409 _InitObject(true, width); 410 } 411 412 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 422 BTabView::~BTabView() 423 { 424 for (int32 i = 0; i < CountTabs(); i++) 425 delete TabAt(i); 426 427 delete fTabList; 428 } 429 430 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* 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 508 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 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 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 595 BTabView::AttachedToWindow() 596 { 597 BView::AttachedToWindow(); 598 599 if (fSelection < 0 && CountTabs() > 0) 600 Select(0); 601 } 602 603 604 void 605 BTabView::DetachedFromWindow() 606 { 607 BView::DetachedFromWindow(); 608 } 609 610 611 void 612 BTabView::AllAttached() 613 { 614 BView::AllAttached(); 615 } 616 617 618 void 619 BTabView::AllDetached() 620 { 621 BView::AllDetached(); 622 } 623 624 625 // #pragma mark - 626 627 628 void 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 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 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 775 BTabView::MouseUp(BPoint where) 776 { 777 BView::MouseUp(where); 778 } 779 780 781 void 782 BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 783 { 784 BView::MouseMoved(where, transit, dragMessage); 785 } 786 787 788 void 789 BTabView::Pulse() 790 { 791 BView::Pulse(); 792 } 793 794 795 void 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 840 BTabView::Selection() const 841 { 842 return fSelection; 843 } 844 845 846 void 847 BTabView::WindowActivated(bool active) 848 { 849 BView::WindowActivated(active); 850 851 if (IsFocus()) 852 Invalidate(); 853 } 854 855 856 void 857 BTabView::MakeFocus(bool focus) 858 { 859 BView::MakeFocus(focus); 860 861 SetFocusTab(Selection(), focus); 862 } 863 864 865 void 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 897 BTabView::FocusTab() const 898 { 899 return fFocus; 900 } 901 902 903 void 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 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 BRect activeTabFrame; 941 int32 tabCount = CountTabs(); 942 for (int32 i = 0; i < tabCount; i++) { 943 BRect tabFrame = TabFrame(i); 944 if (i == fSelection) 945 activeTabFrame = tabFrame; 946 947 TabAt(i)->DrawTab(this, tabFrame, 948 i == fSelection ? B_TAB_FRONT 949 : (i == 0) ? B_TAB_FIRST : B_TAB_ANY, 950 i != fSelection - 1); 951 } 952 953 BRect tabsBounds; 954 float last = 0.0f; 955 float lastTab = 0.0f; 956 if (fTabSide == kTopSide || fTabSide == kBottomSide) { 957 lastTab = TabFrame(tabCount - 1).right; 958 last = tabFrame.right; 959 tabsBounds.left = tabsBounds.right = lastTab; 960 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER; 961 } else if (fTabSide == kLeftSide || fTabSide == kRightSide) { 962 lastTab = TabFrame(tabCount - 1).bottom; 963 last = tabFrame.bottom; 964 tabsBounds.top = tabsBounds.bottom = lastTab; 965 borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER; 966 } 967 968 if (lastTab < last) { 969 // draw a 1px right border on the last tab 970 be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0, 971 borders, fTabSide); 972 } 973 974 return fSelection < CountTabs() ? TabFrame(fSelection) : BRect(); 975 } 976 977 978 void 979 BTabView::DrawBox(BRect selectedTabRect) 980 { 981 BRect rect(Bounds()); 982 uint32 bordersToDraw = BControlLook::B_ALL_BORDERS; 983 switch (fTabSide) { 984 case kTopSide: 985 bordersToDraw &= ~BControlLook::B_TOP_BORDER; 986 rect.top = fTabHeight; 987 break; 988 case kBottomSide: 989 bordersToDraw &= ~BControlLook::B_BOTTOM_BORDER; 990 rect.bottom -= fTabHeight; 991 break; 992 case kLeftSide: 993 bordersToDraw &= ~BControlLook::B_LEFT_BORDER; 994 rect.left = fTabHeight; 995 break; 996 case kRightSide: 997 bordersToDraw &= ~BControlLook::B_RIGHT_BORDER; 998 rect.right -= fTabHeight; 999 break; 1000 } 1001 1002 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 1003 if (fBorderStyle == B_FANCY_BORDER) 1004 be_control_look->DrawGroupFrame(this, rect, rect, base, bordersToDraw); 1005 else if (fBorderStyle == B_PLAIN_BORDER) { 1006 be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER, 1007 0, bordersToDraw); 1008 } else 1009 ; // B_NO_BORDER draws no box 1010 } 1011 1012 1013 BRect 1014 BTabView::TabFrame(int32 index) const 1015 { 1016 if (index >= CountTabs() || index < 0) 1017 return BRect(); 1018 1019 float width = 100.0f; 1020 float height = fTabHeight; 1021 float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING); 1022 BRect bounds(Bounds()); 1023 1024 switch (fTabWidthSetting) { 1025 case B_WIDTH_FROM_LABEL: 1026 { 1027 float x = 0.0f; 1028 for (int32 i = 0; i < index; i++){ 1029 x += StringWidth(TabAt(i)->Label()) + 20.0f; 1030 } 1031 1032 switch (fTabSide) { 1033 case kTopSide: 1034 return BRect(offset + x, 0.0f, 1035 offset + x + StringWidth(TabAt(index)->Label()) + 20.0f, 1036 height); 1037 case kBottomSide: 1038 return BRect(offset + x, bounds.bottom - height, 1039 offset + x + StringWidth(TabAt(index)->Label()) + 20.0f, 1040 bounds.bottom); 1041 case kLeftSide: 1042 return BRect(0.0f, offset + x, height, offset + x 1043 + StringWidth(TabAt(index)->Label()) + 20.0f); 1044 case kRightSide: 1045 return BRect(bounds.right - height, offset + x, 1046 bounds.right, offset + x 1047 + StringWidth(TabAt(index)->Label()) + 20.0f); 1048 default: 1049 return BRect(); 1050 } 1051 } 1052 1053 case B_WIDTH_FROM_WIDEST: 1054 width = 0.0; 1055 for (int32 i = 0; i < CountTabs(); i++) { 1056 float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f; 1057 if (tabWidth > width) 1058 width = tabWidth; 1059 } 1060 // fall through 1061 1062 case B_WIDTH_AS_USUAL: 1063 default: 1064 switch (fTabSide) { 1065 case kTopSide: 1066 return BRect(offset + index * width, 0.0f, 1067 offset + index * width + width, height); 1068 case kBottomSide: 1069 return BRect(offset + index * width, bounds.bottom - height, 1070 offset + index * width + width, bounds.bottom); 1071 case kLeftSide: 1072 return BRect(0.0f, offset + index * width, height, 1073 offset + index * width + width); 1074 case kRightSide: 1075 return BRect(bounds.right - height, offset + index * width, 1076 bounds.right, offset + index * width + width); 1077 default: 1078 return BRect(); 1079 } 1080 } 1081 } 1082 1083 1084 void 1085 BTabView::SetFlags(uint32 flags) 1086 { 1087 BView::SetFlags(flags); 1088 } 1089 1090 1091 void 1092 BTabView::SetResizingMode(uint32 mode) 1093 { 1094 BView::SetResizingMode(mode); 1095 } 1096 1097 1098 // #pragma mark - 1099 1100 1101 void 1102 BTabView::ResizeToPreferred() 1103 { 1104 BView::ResizeToPreferred(); 1105 } 1106 1107 1108 void 1109 BTabView::GetPreferredSize(float* _width, float* _height) 1110 { 1111 BView::GetPreferredSize(_width, _height); 1112 } 1113 1114 1115 BSize 1116 BTabView::MinSize() 1117 { 1118 BSize size; 1119 if (GetLayout()) 1120 size = GetLayout()->MinSize(); 1121 else { 1122 size = _TabsMinSize(); 1123 BSize containerSize = fContainerView->MinSize(); 1124 containerSize.width += 2 * _BorderWidth(); 1125 containerSize.height += 2 * _BorderWidth(); 1126 if (containerSize.width > size.width) 1127 size.width = containerSize.width; 1128 size.height += containerSize.height; 1129 } 1130 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 1131 } 1132 1133 1134 BSize 1135 BTabView::MaxSize() 1136 { 1137 BSize size; 1138 if (GetLayout()) 1139 size = GetLayout()->MaxSize(); 1140 else { 1141 size = _TabsMinSize(); 1142 BSize containerSize = fContainerView->MaxSize(); 1143 containerSize.width += 2 * _BorderWidth(); 1144 containerSize.height += 2 * _BorderWidth(); 1145 if (containerSize.width > size.width) 1146 size.width = containerSize.width; 1147 size.height += containerSize.height; 1148 } 1149 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 1150 } 1151 1152 1153 BSize 1154 BTabView::PreferredSize() 1155 { 1156 BSize size; 1157 if (GetLayout() != NULL) 1158 size = GetLayout()->PreferredSize(); 1159 else { 1160 size = _TabsMinSize(); 1161 BSize containerSize = fContainerView->PreferredSize(); 1162 containerSize.width += 2 * _BorderWidth(); 1163 containerSize.height += 2 * _BorderWidth(); 1164 if (containerSize.width > size.width) 1165 size.width = containerSize.width; 1166 size.height += containerSize.height; 1167 } 1168 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 1169 } 1170 1171 1172 void 1173 BTabView::FrameMoved(BPoint newPosition) 1174 { 1175 BView::FrameMoved(newPosition); 1176 } 1177 1178 1179 void 1180 BTabView::FrameResized(float newWidth, float newHeight) 1181 { 1182 BView::FrameResized(newWidth, newHeight); 1183 } 1184 1185 1186 // #pragma mark - 1187 1188 1189 BHandler* 1190 BTabView::ResolveSpecifier(BMessage* message, int32 index, 1191 BMessage* specifier, int32 what, const char* property) 1192 { 1193 BPropertyInfo propInfo(sPropertyList); 1194 1195 if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK) 1196 return this; 1197 1198 return BView::ResolveSpecifier(message, index, specifier, what, property); 1199 } 1200 1201 1202 status_t 1203 BTabView::GetSupportedSuites(BMessage* message) 1204 { 1205 message->AddString("suites", "suite/vnd.Be-tab-view"); 1206 1207 BPropertyInfo propInfo(sPropertyList); 1208 message->AddFlat("messages", &propInfo); 1209 1210 return BView::GetSupportedSuites(message); 1211 } 1212 1213 1214 // #pragma mark - 1215 1216 1217 void 1218 BTabView::AddTab(BView* target, BTab* tab) 1219 { 1220 if (tab == NULL) 1221 tab = new BTab(target); 1222 else 1223 tab->SetView(target); 1224 1225 if (fContainerView->GetLayout()) 1226 fContainerView->GetLayout()->AddView(CountTabs(), target); 1227 1228 fTabList->AddItem(tab); 1229 BTab::Private(tab).SetTabView(this); 1230 1231 // When we haven't had a any tabs before, but are already attached to the 1232 // window, select this one. 1233 if (CountTabs() == 1 && Window() != NULL) 1234 Select(0); 1235 } 1236 1237 1238 BTab* 1239 BTabView::RemoveTab(int32 index) 1240 { 1241 if (index < 0 || index >= CountTabs()) 1242 return NULL; 1243 1244 BTab* tab = (BTab*)fTabList->RemoveItem(index); 1245 if (tab == NULL) 1246 return NULL; 1247 1248 tab->Deselect(); 1249 BTab::Private(tab).SetTabView(NULL); 1250 1251 if (fContainerView->GetLayout()) 1252 fContainerView->GetLayout()->RemoveItem(index); 1253 1254 if (CountTabs() == 0) 1255 fFocus = -1; 1256 else if (index <= fSelection) 1257 Select(fSelection - 1); 1258 1259 if (fFocus >= 0) { 1260 if (fFocus == CountTabs() - 1 || CountTabs() == 0) 1261 SetFocusTab(fFocus, false); 1262 else 1263 SetFocusTab(fFocus, true); 1264 } 1265 1266 return tab; 1267 } 1268 1269 1270 BTab* 1271 BTabView::TabAt(int32 index) const 1272 { 1273 return (BTab*)fTabList->ItemAt(index); 1274 } 1275 1276 1277 void 1278 BTabView::SetTabWidth(button_width width) 1279 { 1280 fTabWidthSetting = width; 1281 1282 Invalidate(); 1283 } 1284 1285 1286 button_width 1287 BTabView::TabWidth() const 1288 { 1289 return fTabWidthSetting; 1290 } 1291 1292 1293 void 1294 BTabView::SetTabHeight(float height) 1295 { 1296 if (fTabHeight == height) 1297 return; 1298 1299 fTabHeight = height; 1300 _LayoutContainerView(GetLayout() != NULL); 1301 1302 Invalidate(); 1303 } 1304 1305 1306 float 1307 BTabView::TabHeight() const 1308 { 1309 return fTabHeight; 1310 } 1311 1312 1313 void 1314 BTabView::SetBorder(border_style borderStyle) 1315 { 1316 if (fBorderStyle == borderStyle) 1317 return; 1318 1319 fBorderStyle = borderStyle; 1320 1321 _LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0); 1322 } 1323 1324 1325 border_style 1326 BTabView::Border() const 1327 { 1328 return fBorderStyle; 1329 } 1330 1331 1332 void 1333 BTabView::SetTabSide(tab_side tabSide) 1334 { 1335 if (fTabSide == tabSide) 1336 return; 1337 1338 fTabSide = tabSide; 1339 _LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT); 1340 } 1341 1342 1343 BTabView::tab_side 1344 BTabView::TabSide() const 1345 { 1346 return fTabSide; 1347 } 1348 1349 1350 BView* 1351 BTabView::ContainerView() const 1352 { 1353 return fContainerView; 1354 } 1355 1356 1357 int32 1358 BTabView::CountTabs() const 1359 { 1360 return fTabList->CountItems(); 1361 } 1362 1363 1364 BView* 1365 BTabView::ViewForTab(int32 tabIndex) const 1366 { 1367 BTab* tab = TabAt(tabIndex); 1368 if (tab != NULL) 1369 return tab->View(); 1370 1371 return NULL; 1372 } 1373 1374 1375 int32 1376 BTabView::IndexOf(BTab* tab) const 1377 { 1378 if (tab != NULL) { 1379 int32 tabCount = CountTabs(); 1380 for (int32 index = 0; index < tabCount; index++) { 1381 if (TabAt(index) == tab) 1382 return index; 1383 } 1384 } 1385 1386 return -1; 1387 } 1388 1389 1390 void 1391 BTabView::_InitObject(bool layouted, button_width width) 1392 { 1393 fTabList = new BList; 1394 1395 fTabWidthSetting = width; 1396 fSelection = -1; 1397 fFocus = -1; 1398 fTabOffset = 0.0f; 1399 fBorderStyle = B_FANCY_BORDER; 1400 fTabSide = kTopSide; 1401 1402 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1403 SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1404 1405 font_height fh; 1406 GetFontHeight(&fh); 1407 fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f); 1408 1409 fContainerView = NULL; 1410 _InitContainerView(layouted); 1411 } 1412 1413 1414 void 1415 BTabView::_InitContainerView(bool layouted) 1416 { 1417 bool needsLayout = false; 1418 bool createdContainer = false; 1419 if (layouted) { 1420 if (GetLayout() == NULL) { 1421 SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL)); 1422 needsLayout = true; 1423 } 1424 1425 if (fContainerView == NULL) { 1426 fContainerView = new BView("view container", B_WILL_DRAW); 1427 fContainerView->SetLayout(new(std::nothrow) BCardLayout()); 1428 createdContainer = true; 1429 } 1430 } else if (fContainerView == NULL) { 1431 fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL, 1432 B_WILL_DRAW); 1433 createdContainer = true; 1434 } 1435 1436 if (needsLayout || createdContainer) 1437 _LayoutContainerView(layouted); 1438 1439 if (createdContainer) { 1440 fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1441 fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1442 AddChild(fContainerView); 1443 } 1444 } 1445 1446 1447 BSize 1448 BTabView::_TabsMinSize() const 1449 { 1450 BSize size(0.0f, TabHeight()); 1451 int32 count = min_c(2, CountTabs()); 1452 for (int32 i = 0; i < count; i++) { 1453 BRect frame = TabFrame(i); 1454 size.width += frame.Width(); 1455 } 1456 1457 if (count < CountTabs()) { 1458 // TODO: Add size for yet to be implemented buttons that allow 1459 // "scrolling" the displayed tabs left/right. 1460 } 1461 1462 return size; 1463 } 1464 1465 1466 float 1467 BTabView::_BorderWidth() const 1468 { 1469 switch (fBorderStyle) { 1470 default: 1471 case B_FANCY_BORDER: 1472 return 3.0f; 1473 1474 case B_PLAIN_BORDER: 1475 return 1.0f; 1476 1477 case B_NO_BORDER: 1478 return 0.0f; 1479 } 1480 } 1481 1482 1483 void 1484 BTabView::_LayoutContainerView(bool layouted) 1485 { 1486 float borderWidth = _BorderWidth(); 1487 if (layouted) { 1488 float topBorderOffset; 1489 switch (fBorderStyle) { 1490 default: 1491 case B_FANCY_BORDER: 1492 topBorderOffset = 1.0f; 1493 break; 1494 1495 case B_PLAIN_BORDER: 1496 topBorderOffset = 0.0f; 1497 break; 1498 1499 case B_NO_BORDER: 1500 topBorderOffset = -1.0f; 1501 break; 1502 } 1503 BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout()); 1504 if (layout != NULL) { 1505 float inset = borderWidth + TabHeight() - topBorderOffset; 1506 switch (fTabSide) { 1507 case kTopSide: 1508 layout->SetInsets(borderWidth, inset, borderWidth, 1509 borderWidth); 1510 break; 1511 case kBottomSide: 1512 layout->SetInsets(borderWidth, borderWidth, borderWidth, 1513 inset); 1514 break; 1515 case kLeftSide: 1516 layout->SetInsets(inset, borderWidth, borderWidth, 1517 borderWidth); 1518 break; 1519 case kRightSide: 1520 layout->SetInsets(borderWidth, borderWidth, inset, 1521 borderWidth); 1522 break; 1523 } 1524 } 1525 } else { 1526 BRect bounds = Bounds(); 1527 switch (fTabSide) { 1528 case kTopSide: 1529 bounds.top += TabHeight(); 1530 break; 1531 case kBottomSide: 1532 bounds.bottom -= TabHeight(); 1533 break; 1534 case kLeftSide: 1535 bounds.left += TabHeight(); 1536 break; 1537 case kRightSide: 1538 bounds.right -= TabHeight(); 1539 break; 1540 } 1541 bounds.InsetBy(borderWidth, borderWidth); 1542 1543 fContainerView->MoveTo(bounds.left, bounds.top); 1544 fContainerView->ResizeTo(bounds.Width(), bounds.Height()); 1545 } 1546 } 1547 1548 1549 // #pragma mark - FBC and forbidden 1550 1551 1552 void BTabView::_ReservedTabView3() {} 1553 void BTabView::_ReservedTabView4() {} 1554 void BTabView::_ReservedTabView5() {} 1555 void BTabView::_ReservedTabView6() {} 1556 void BTabView::_ReservedTabView7() {} 1557 void BTabView::_ReservedTabView8() {} 1558 void BTabView::_ReservedTabView9() {} 1559 void BTabView::_ReservedTabView10() {} 1560 void BTabView::_ReservedTabView11() {} 1561 void BTabView::_ReservedTabView12() {} 1562 1563 1564 BTabView::BTabView(const BTabView& tabView) 1565 : BView(tabView) 1566 { 1567 // this is private and not functional, but exported 1568 } 1569 1570 1571 BTabView& 1572 BTabView::operator=(const BTabView&) 1573 { 1574 // this is private and not functional, but exported 1575 return *this; 1576 } 1577 1578 // #pragma mark - binary compatibility 1579 1580 1581 extern "C" void 1582 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)( 1583 BTabView* tabView, border_style borderStyle) 1584 { 1585 tabView->BTabView::SetBorder(borderStyle); 1586 } 1587 1588 extern "C" void 1589 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)( 1590 BTabView* tabView, BTabView::tab_side tabSide) 1591 { 1592 tabView->BTabView::SetTabSide(tabSide); 1593 } 1594