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 const float padding = ceilf(be_control_look->DefaultLabelSpacing() * 3.3f); 1020 const float height = fTabHeight; 1021 const float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING); 1022 const BRect bounds(Bounds()); 1023 1024 float width = padding * 5.0f; 1025 switch (fTabWidthSetting) { 1026 case B_WIDTH_FROM_LABEL: 1027 { 1028 float x = 0.0f; 1029 for (int32 i = 0; i < index; i++){ 1030 x += StringWidth(TabAt(i)->Label()) + padding; 1031 } 1032 1033 switch (fTabSide) { 1034 case kTopSide: 1035 return BRect(offset + x, 0.0f, 1036 offset + x + StringWidth(TabAt(index)->Label()) + padding, 1037 height); 1038 case kBottomSide: 1039 return BRect(offset + x, bounds.bottom - height, 1040 offset + x + StringWidth(TabAt(index)->Label()) + padding, 1041 bounds.bottom); 1042 case kLeftSide: 1043 return BRect(0.0f, offset + x, height, offset + x 1044 + StringWidth(TabAt(index)->Label()) + padding); 1045 case kRightSide: 1046 return BRect(bounds.right - height, offset + x, 1047 bounds.right, offset + x 1048 + StringWidth(TabAt(index)->Label()) + padding); 1049 default: 1050 return BRect(); 1051 } 1052 } 1053 1054 case B_WIDTH_FROM_WIDEST: 1055 width = 0.0; 1056 for (int32 i = 0; i < CountTabs(); i++) { 1057 float tabWidth = StringWidth(TabAt(i)->Label()) + padding; 1058 if (tabWidth > width) 1059 width = tabWidth; 1060 } 1061 // fall through 1062 1063 case B_WIDTH_AS_USUAL: 1064 default: 1065 switch (fTabSide) { 1066 case kTopSide: 1067 return BRect(offset + index * width, 0.0f, 1068 offset + index * width + width, height); 1069 case kBottomSide: 1070 return BRect(offset + index * width, bounds.bottom - height, 1071 offset + index * width + width, bounds.bottom); 1072 case kLeftSide: 1073 return BRect(0.0f, offset + index * width, height, 1074 offset + index * width + width); 1075 case kRightSide: 1076 return BRect(bounds.right - height, offset + index * width, 1077 bounds.right, offset + index * width + width); 1078 default: 1079 return BRect(); 1080 } 1081 } 1082 } 1083 1084 1085 void 1086 BTabView::SetFlags(uint32 flags) 1087 { 1088 BView::SetFlags(flags); 1089 } 1090 1091 1092 void 1093 BTabView::SetResizingMode(uint32 mode) 1094 { 1095 BView::SetResizingMode(mode); 1096 } 1097 1098 1099 // #pragma mark - 1100 1101 1102 void 1103 BTabView::ResizeToPreferred() 1104 { 1105 BView::ResizeToPreferred(); 1106 } 1107 1108 1109 void 1110 BTabView::GetPreferredSize(float* _width, float* _height) 1111 { 1112 BView::GetPreferredSize(_width, _height); 1113 } 1114 1115 1116 BSize 1117 BTabView::MinSize() 1118 { 1119 BSize size; 1120 if (GetLayout()) 1121 size = GetLayout()->MinSize(); 1122 else { 1123 size = _TabsMinSize(); 1124 BSize containerSize = fContainerView->MinSize(); 1125 containerSize.width += 2 * _BorderWidth(); 1126 containerSize.height += 2 * _BorderWidth(); 1127 if (containerSize.width > size.width) 1128 size.width = containerSize.width; 1129 size.height += containerSize.height; 1130 } 1131 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 1132 } 1133 1134 1135 BSize 1136 BTabView::MaxSize() 1137 { 1138 BSize size; 1139 if (GetLayout()) 1140 size = GetLayout()->MaxSize(); 1141 else { 1142 size = _TabsMinSize(); 1143 BSize containerSize = fContainerView->MaxSize(); 1144 containerSize.width += 2 * _BorderWidth(); 1145 containerSize.height += 2 * _BorderWidth(); 1146 if (containerSize.width > size.width) 1147 size.width = containerSize.width; 1148 size.height += containerSize.height; 1149 } 1150 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 1151 } 1152 1153 1154 BSize 1155 BTabView::PreferredSize() 1156 { 1157 BSize size; 1158 if (GetLayout() != NULL) 1159 size = GetLayout()->PreferredSize(); 1160 else { 1161 size = _TabsMinSize(); 1162 BSize containerSize = fContainerView->PreferredSize(); 1163 containerSize.width += 2 * _BorderWidth(); 1164 containerSize.height += 2 * _BorderWidth(); 1165 if (containerSize.width > size.width) 1166 size.width = containerSize.width; 1167 size.height += containerSize.height; 1168 } 1169 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 1170 } 1171 1172 1173 void 1174 BTabView::FrameMoved(BPoint newPosition) 1175 { 1176 BView::FrameMoved(newPosition); 1177 } 1178 1179 1180 void 1181 BTabView::FrameResized(float newWidth, float newHeight) 1182 { 1183 BView::FrameResized(newWidth, newHeight); 1184 } 1185 1186 1187 // #pragma mark - 1188 1189 1190 BHandler* 1191 BTabView::ResolveSpecifier(BMessage* message, int32 index, 1192 BMessage* specifier, int32 what, const char* property) 1193 { 1194 BPropertyInfo propInfo(sPropertyList); 1195 1196 if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK) 1197 return this; 1198 1199 return BView::ResolveSpecifier(message, index, specifier, what, property); 1200 } 1201 1202 1203 status_t 1204 BTabView::GetSupportedSuites(BMessage* message) 1205 { 1206 message->AddString("suites", "suite/vnd.Be-tab-view"); 1207 1208 BPropertyInfo propInfo(sPropertyList); 1209 message->AddFlat("messages", &propInfo); 1210 1211 return BView::GetSupportedSuites(message); 1212 } 1213 1214 1215 // #pragma mark - 1216 1217 1218 void 1219 BTabView::AddTab(BView* target, BTab* tab) 1220 { 1221 if (tab == NULL) 1222 tab = new BTab(target); 1223 else 1224 tab->SetView(target); 1225 1226 if (fContainerView->GetLayout()) 1227 fContainerView->GetLayout()->AddView(CountTabs(), target); 1228 1229 fTabList->AddItem(tab); 1230 BTab::Private(tab).SetTabView(this); 1231 1232 // When we haven't had a any tabs before, but are already attached to the 1233 // window, select this one. 1234 if (CountTabs() == 1 && Window() != NULL) 1235 Select(0); 1236 } 1237 1238 1239 BTab* 1240 BTabView::RemoveTab(int32 index) 1241 { 1242 if (index < 0 || index >= CountTabs()) 1243 return NULL; 1244 1245 BTab* tab = (BTab*)fTabList->RemoveItem(index); 1246 if (tab == NULL) 1247 return NULL; 1248 1249 tab->Deselect(); 1250 BTab::Private(tab).SetTabView(NULL); 1251 1252 if (fContainerView->GetLayout()) 1253 fContainerView->GetLayout()->RemoveItem(index); 1254 1255 if (CountTabs() == 0) 1256 fFocus = -1; 1257 else if (index <= fSelection) 1258 Select(fSelection - 1); 1259 1260 if (fFocus >= 0) { 1261 if (fFocus == CountTabs() - 1 || CountTabs() == 0) 1262 SetFocusTab(fFocus, false); 1263 else 1264 SetFocusTab(fFocus, true); 1265 } 1266 1267 return tab; 1268 } 1269 1270 1271 BTab* 1272 BTabView::TabAt(int32 index) const 1273 { 1274 return (BTab*)fTabList->ItemAt(index); 1275 } 1276 1277 1278 void 1279 BTabView::SetTabWidth(button_width width) 1280 { 1281 fTabWidthSetting = width; 1282 1283 Invalidate(); 1284 } 1285 1286 1287 button_width 1288 BTabView::TabWidth() const 1289 { 1290 return fTabWidthSetting; 1291 } 1292 1293 1294 void 1295 BTabView::SetTabHeight(float height) 1296 { 1297 if (fTabHeight == height) 1298 return; 1299 1300 fTabHeight = height; 1301 _LayoutContainerView(GetLayout() != NULL); 1302 1303 Invalidate(); 1304 } 1305 1306 1307 float 1308 BTabView::TabHeight() const 1309 { 1310 return fTabHeight; 1311 } 1312 1313 1314 void 1315 BTabView::SetBorder(border_style borderStyle) 1316 { 1317 if (fBorderStyle == borderStyle) 1318 return; 1319 1320 fBorderStyle = borderStyle; 1321 1322 _LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0); 1323 } 1324 1325 1326 border_style 1327 BTabView::Border() const 1328 { 1329 return fBorderStyle; 1330 } 1331 1332 1333 void 1334 BTabView::SetTabSide(tab_side tabSide) 1335 { 1336 if (fTabSide == tabSide) 1337 return; 1338 1339 fTabSide = tabSide; 1340 _LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT); 1341 } 1342 1343 1344 BTabView::tab_side 1345 BTabView::TabSide() const 1346 { 1347 return fTabSide; 1348 } 1349 1350 1351 BView* 1352 BTabView::ContainerView() const 1353 { 1354 return fContainerView; 1355 } 1356 1357 1358 int32 1359 BTabView::CountTabs() const 1360 { 1361 return fTabList->CountItems(); 1362 } 1363 1364 1365 BView* 1366 BTabView::ViewForTab(int32 tabIndex) const 1367 { 1368 BTab* tab = TabAt(tabIndex); 1369 if (tab != NULL) 1370 return tab->View(); 1371 1372 return NULL; 1373 } 1374 1375 1376 int32 1377 BTabView::IndexOf(BTab* tab) const 1378 { 1379 if (tab != NULL) { 1380 int32 tabCount = CountTabs(); 1381 for (int32 index = 0; index < tabCount; index++) { 1382 if (TabAt(index) == tab) 1383 return index; 1384 } 1385 } 1386 1387 return -1; 1388 } 1389 1390 1391 void 1392 BTabView::_InitObject(bool layouted, button_width width) 1393 { 1394 fTabList = new BList; 1395 1396 fTabWidthSetting = width; 1397 fSelection = -1; 1398 fFocus = -1; 1399 fTabOffset = 0.0f; 1400 fBorderStyle = B_FANCY_BORDER; 1401 fTabSide = kTopSide; 1402 1403 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1404 SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1405 1406 font_height fh; 1407 GetFontHeight(&fh); 1408 fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 1409 (be_control_look->DefaultLabelSpacing() * 1.3f)); 1410 1411 fContainerView = NULL; 1412 _InitContainerView(layouted); 1413 } 1414 1415 1416 void 1417 BTabView::_InitContainerView(bool layouted) 1418 { 1419 bool needsLayout = false; 1420 bool createdContainer = false; 1421 if (layouted) { 1422 if (GetLayout() == NULL) { 1423 SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL)); 1424 needsLayout = true; 1425 } 1426 1427 if (fContainerView == NULL) { 1428 fContainerView = new BView("view container", B_WILL_DRAW); 1429 fContainerView->SetLayout(new(std::nothrow) BCardLayout()); 1430 createdContainer = true; 1431 } 1432 } else if (fContainerView == NULL) { 1433 fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL, 1434 B_WILL_DRAW); 1435 createdContainer = true; 1436 } 1437 1438 if (needsLayout || createdContainer) 1439 _LayoutContainerView(layouted); 1440 1441 if (createdContainer) { 1442 fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1443 fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1444 AddChild(fContainerView); 1445 } 1446 } 1447 1448 1449 BSize 1450 BTabView::_TabsMinSize() const 1451 { 1452 BSize size(0.0f, TabHeight()); 1453 int32 count = min_c(2, CountTabs()); 1454 for (int32 i = 0; i < count; i++) { 1455 BRect frame = TabFrame(i); 1456 size.width += frame.Width(); 1457 } 1458 1459 if (count < CountTabs()) { 1460 // TODO: Add size for yet to be implemented buttons that allow 1461 // "scrolling" the displayed tabs left/right. 1462 } 1463 1464 return size; 1465 } 1466 1467 1468 float 1469 BTabView::_BorderWidth() const 1470 { 1471 switch (fBorderStyle) { 1472 default: 1473 case B_FANCY_BORDER: 1474 return 3.0f; 1475 1476 case B_PLAIN_BORDER: 1477 return 1.0f; 1478 1479 case B_NO_BORDER: 1480 return 0.0f; 1481 } 1482 } 1483 1484 1485 void 1486 BTabView::_LayoutContainerView(bool layouted) 1487 { 1488 float borderWidth = _BorderWidth(); 1489 if (layouted) { 1490 float topBorderOffset; 1491 switch (fBorderStyle) { 1492 default: 1493 case B_FANCY_BORDER: 1494 topBorderOffset = 1.0f; 1495 break; 1496 1497 case B_PLAIN_BORDER: 1498 topBorderOffset = 0.0f; 1499 break; 1500 1501 case B_NO_BORDER: 1502 topBorderOffset = -1.0f; 1503 break; 1504 } 1505 BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout()); 1506 if (layout != NULL) { 1507 float inset = borderWidth + TabHeight() - topBorderOffset; 1508 switch (fTabSide) { 1509 case kTopSide: 1510 layout->SetInsets(borderWidth, inset, borderWidth, 1511 borderWidth); 1512 break; 1513 case kBottomSide: 1514 layout->SetInsets(borderWidth, borderWidth, borderWidth, 1515 inset); 1516 break; 1517 case kLeftSide: 1518 layout->SetInsets(inset, borderWidth, borderWidth, 1519 borderWidth); 1520 break; 1521 case kRightSide: 1522 layout->SetInsets(borderWidth, borderWidth, inset, 1523 borderWidth); 1524 break; 1525 } 1526 } 1527 } else { 1528 BRect bounds = Bounds(); 1529 switch (fTabSide) { 1530 case kTopSide: 1531 bounds.top += TabHeight(); 1532 break; 1533 case kBottomSide: 1534 bounds.bottom -= TabHeight(); 1535 break; 1536 case kLeftSide: 1537 bounds.left += TabHeight(); 1538 break; 1539 case kRightSide: 1540 bounds.right -= TabHeight(); 1541 break; 1542 } 1543 bounds.InsetBy(borderWidth, borderWidth); 1544 1545 fContainerView->MoveTo(bounds.left, bounds.top); 1546 fContainerView->ResizeTo(bounds.Width(), bounds.Height()); 1547 } 1548 } 1549 1550 1551 // #pragma mark - FBC and forbidden 1552 1553 1554 void BTabView::_ReservedTabView3() {} 1555 void BTabView::_ReservedTabView4() {} 1556 void BTabView::_ReservedTabView5() {} 1557 void BTabView::_ReservedTabView6() {} 1558 void BTabView::_ReservedTabView7() {} 1559 void BTabView::_ReservedTabView8() {} 1560 void BTabView::_ReservedTabView9() {} 1561 void BTabView::_ReservedTabView10() {} 1562 void BTabView::_ReservedTabView11() {} 1563 void BTabView::_ReservedTabView12() {} 1564 1565 1566 BTabView::BTabView(const BTabView& tabView) 1567 : BView(tabView) 1568 { 1569 // this is private and not functional, but exported 1570 } 1571 1572 1573 BTabView& 1574 BTabView::operator=(const BTabView&) 1575 { 1576 // this is private and not functional, but exported 1577 return *this; 1578 } 1579 1580 // #pragma mark - binary compatibility 1581 1582 1583 extern "C" void 1584 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)( 1585 BTabView* tabView, border_style borderStyle) 1586 { 1587 tabView->BTabView::SetBorder(borderStyle); 1588 } 1589 1590 extern "C" void 1591 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)( 1592 BTabView* tabView, BTabView::tab_side tabSide) 1593 { 1594 tabView->BTabView::SetTabSide(tabSide); 1595 } 1596