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