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 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 955 BTabView::DrawBox(BRect selectedTabRect) 956 { 957 BRect rect(Bounds()); 958 uint32 bordersToDraw = BControlLook::B_ALL_BORDERS; 959 switch (fTabSide) { 960 case kTopSide: 961 bordersToDraw &= ~BControlLook::B_TOP_BORDER; 962 rect.top = fTabHeight; 963 break; 964 case kBottomSide: 965 bordersToDraw &= ~BControlLook::B_BOTTOM_BORDER; 966 rect.bottom -= fTabHeight; 967 break; 968 case kLeftSide: 969 bordersToDraw &= ~BControlLook::B_LEFT_BORDER; 970 rect.left = fTabHeight; 971 break; 972 case kRightSide: 973 bordersToDraw &= ~BControlLook::B_RIGHT_BORDER; 974 rect.right -= fTabHeight; 975 break; 976 } 977 978 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 979 if (fBorderStyle == B_FANCY_BORDER) 980 be_control_look->DrawGroupFrame(this, rect, rect, base, bordersToDraw); 981 else if (fBorderStyle == B_PLAIN_BORDER) { 982 be_control_look->DrawBorder(this, rect, rect, base, B_PLAIN_BORDER, 983 0, bordersToDraw); 984 } else 985 ; // B_NO_BORDER draws no box 986 } 987 988 989 BRect 990 BTabView::TabFrame(int32 index) const 991 { 992 if (index >= CountTabs() || index < 0) 993 return BRect(); 994 995 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 1062 BTabView::SetFlags(uint32 flags) 1063 { 1064 BView::SetFlags(flags); 1065 } 1066 1067 1068 void 1069 BTabView::SetResizingMode(uint32 mode) 1070 { 1071 BView::SetResizingMode(mode); 1072 } 1073 1074 1075 // #pragma mark - 1076 1077 1078 void 1079 BTabView::ResizeToPreferred() 1080 { 1081 BView::ResizeToPreferred(); 1082 } 1083 1084 1085 void 1086 BTabView::GetPreferredSize(float* _width, float* _height) 1087 { 1088 BView::GetPreferredSize(_width, _height); 1089 } 1090 1091 1092 BSize 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 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 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 1150 BTabView::FrameMoved(BPoint newPosition) 1151 { 1152 BView::FrameMoved(newPosition); 1153 } 1154 1155 1156 void 1157 BTabView::FrameResized(float newWidth, float newHeight) 1158 { 1159 BView::FrameResized(newWidth, newHeight); 1160 } 1161 1162 1163 // #pragma mark - 1164 1165 1166 BHandler* 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 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 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* 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* 1248 BTabView::TabAt(int32 index) const 1249 { 1250 return (BTab*)fTabList->ItemAt(index); 1251 } 1252 1253 1254 void 1255 BTabView::SetTabWidth(button_width width) 1256 { 1257 fTabWidthSetting = width; 1258 1259 Invalidate(); 1260 } 1261 1262 1263 button_width 1264 BTabView::TabWidth() const 1265 { 1266 return fTabWidthSetting; 1267 } 1268 1269 1270 void 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 1284 BTabView::TabHeight() const 1285 { 1286 return fTabHeight; 1287 } 1288 1289 1290 void 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 1303 BTabView::Border() const 1304 { 1305 return fBorderStyle; 1306 } 1307 1308 1309 void 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 1321 BTabView::TabSide() const 1322 { 1323 return fTabSide; 1324 } 1325 1326 1327 BView* 1328 BTabView::ContainerView() const 1329 { 1330 return fContainerView; 1331 } 1332 1333 1334 int32 1335 BTabView::CountTabs() const 1336 { 1337 return fTabList->CountItems(); 1338 } 1339 1340 1341 BView* 1342 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 1353 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 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 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 1426 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 1445 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 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 1530 void BTabView::_ReservedTabView3() {} 1531 void BTabView::_ReservedTabView4() {} 1532 void BTabView::_ReservedTabView5() {} 1533 void BTabView::_ReservedTabView6() {} 1534 void BTabView::_ReservedTabView7() {} 1535 void BTabView::_ReservedTabView8() {} 1536 void BTabView::_ReservedTabView9() {} 1537 void BTabView::_ReservedTabView10() {} 1538 void BTabView::_ReservedTabView11() {} 1539 void BTabView::_ReservedTabView12() {} 1540 1541 1542 BTabView::BTabView(const BTabView& tabView) 1543 : BView(tabView) 1544 { 1545 // this is private and not functional, but exported 1546 } 1547 1548 1549 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 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 1567 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)( 1568 BTabView* tabView, BTabView::tab_side tabSide) 1569 { 1570 tabView->BTabView::SetTabSide(tabSide); 1571 } 1572