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 position, bool full) 320 { 321 rgb_color no_tint = ui_color(B_PANEL_BACKGROUND_COLOR); 322 uint32 borders = 0; 323 if (fTabView->TabSide() == BTabView::kTopSide 324 || fTabView->TabSide() == BTabView::kBottomSide) { 325 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER; 326 327 if (frame.left == owner->Bounds().left) 328 borders |= BControlLook::B_LEFT_BORDER; 329 330 if (frame.right == owner->Bounds().right) 331 borders |= BControlLook::B_RIGHT_BORDER; 332 } else if (fTabView->TabSide() == BTabView::kLeftSide 333 || fTabView->TabSide() == BTabView::kRightSide) { 334 borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER; 335 336 if (frame.top == owner->Bounds().top) 337 borders |= BControlLook::B_TOP_BORDER; 338 339 if (frame.bottom == owner->Bounds().bottom) 340 borders |= BControlLook::B_BOTTOM_BORDER; 341 } 342 343 if (position == B_TAB_FRONT) { 344 be_control_look->DrawActiveTab(owner, frame, frame, no_tint, 0, 345 borders, fTabView->TabSide()); 346 } else { 347 be_control_look->DrawInactiveTab(owner, frame, frame, no_tint, 0, 348 borders, fTabView->TabSide()); 349 } 350 351 DrawLabel(owner, frame); 352 } 353 354 355 // #pragma mark - FBC padding and private methods 356 357 358 void BTab::_ReservedTab1() {} 359 void BTab::_ReservedTab2() {} 360 void BTab::_ReservedTab3() {} 361 void BTab::_ReservedTab4() {} 362 void BTab::_ReservedTab5() {} 363 void BTab::_ReservedTab6() {} 364 void BTab::_ReservedTab7() {} 365 void BTab::_ReservedTab8() {} 366 void BTab::_ReservedTab9() {} 367 void BTab::_ReservedTab10() {} 368 void BTab::_ReservedTab11() {} 369 void BTab::_ReservedTab12() {} 370 371 BTab &BTab::operator=(const BTab &) 372 { 373 // this is private and not functional, but exported 374 return *this; 375 } 376 377 378 // #pragma mark - BTabView 379 380 381 BTabView::BTabView(const char* name, button_width width, uint32 flags) 382 : 383 BView(name, flags) 384 { 385 _InitObject(true, width); 386 } 387 388 389 BTabView::BTabView(BRect frame, const char* name, button_width width, 390 uint32 resizeMask, uint32 flags) 391 : 392 BView(frame, name, resizeMask, flags) 393 { 394 _InitObject(false, width); 395 } 396 397 398 BTabView::~BTabView() 399 { 400 for (int32 i = 0; i < CountTabs(); i++) 401 delete TabAt(i); 402 403 delete fTabList; 404 } 405 406 407 BTabView::BTabView(BMessage* archive) 408 : 409 BView(BUnarchiver::PrepareArchive(archive)), 410 fTabList(new BList), 411 fContainerView(NULL), 412 fFocus(-1) 413 { 414 BUnarchiver unarchiver(archive); 415 416 int16 width; 417 if (archive->FindInt16("_but_width", &width) == B_OK) 418 fTabWidthSetting = (button_width)width; 419 else 420 fTabWidthSetting = B_WIDTH_AS_USUAL; 421 422 if (archive->FindFloat("_high", &fTabHeight) != B_OK) { 423 font_height fh; 424 GetFontHeight(&fh); 425 fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f); 426 } 427 428 if (archive->FindInt32("_sel", &fSelection) != B_OK) 429 fSelection = -1; 430 431 if (archive->FindInt32("_border_style", (int32*)&fBorderStyle) != B_OK) 432 fBorderStyle = B_FANCY_BORDER; 433 434 if (archive->FindInt32("_TabSide", (int32*)&fTabSide) != B_OK) 435 fTabSide = kTopSide; 436 437 int32 i = 0; 438 BMessage tabMsg; 439 440 if (BUnarchiver::IsArchiveManaged(archive)) { 441 int32 tabCount; 442 archive->GetInfo("_l_items", NULL, &tabCount); 443 for (int32 i = 0; i < tabCount; i++) { 444 unarchiver.EnsureUnarchived("_l_items", i); 445 unarchiver.EnsureUnarchived("_view_list", i); 446 } 447 return; 448 } 449 450 fContainerView = ChildAt(0); 451 _InitContainerView(Flags() & B_SUPPORTS_LAYOUT); 452 453 while (archive->FindMessage("_l_items", i, &tabMsg) == B_OK) { 454 BArchivable* archivedTab = instantiate_object(&tabMsg); 455 456 if (archivedTab) { 457 BTab* tab = dynamic_cast<BTab*>(archivedTab); 458 459 BMessage viewMsg; 460 if (archive->FindMessage("_view_list", i, &viewMsg) == B_OK) { 461 BArchivable* archivedView = instantiate_object(&viewMsg); 462 if (archivedView) 463 AddTab(dynamic_cast<BView*>(archivedView), tab); 464 } 465 } 466 467 tabMsg.MakeEmpty(); 468 i++; 469 } 470 } 471 472 473 BArchivable* 474 BTabView::Instantiate(BMessage* archive) 475 { 476 if ( validate_instantiation(archive, "BTabView")) 477 return new BTabView(archive); 478 479 return NULL; 480 } 481 482 483 status_t 484 BTabView::Archive(BMessage* archive, bool deep) const 485 { 486 BArchiver archiver(archive); 487 488 status_t result = BView::Archive(archive, deep); 489 490 if (result == B_OK) 491 result = archive->AddInt16("_but_width", fTabWidthSetting); 492 if (result == B_OK) 493 result = archive->AddFloat("_high", fTabHeight); 494 if (result == B_OK) 495 result = archive->AddInt32("_sel", fSelection); 496 if (result == B_OK && fBorderStyle != B_FANCY_BORDER) 497 result = archive->AddInt32("_border_style", fBorderStyle); 498 if (result == B_OK && fTabSide != kTopSide) 499 result = archive->AddInt32("_TabSide", fTabSide); 500 501 if (result == B_OK && deep) { 502 for (int32 i = 0; i < CountTabs(); i++) { 503 BTab* tab = TabAt(i); 504 505 if ((result = archiver.AddArchivable("_l_items", tab, deep)) 506 != B_OK) { 507 break; 508 } 509 result = archiver.AddArchivable("_view_list", tab->View(), deep); 510 } 511 } 512 513 return archiver.Finish(result); 514 } 515 516 517 status_t 518 BTabView::AllUnarchived(const BMessage* archive) 519 { 520 status_t err = BView::AllUnarchived(archive); 521 if (err != B_OK) 522 return err; 523 524 fContainerView = ChildAt(0); 525 _InitContainerView(Flags() & B_SUPPORTS_LAYOUT); 526 527 BUnarchiver unarchiver(archive); 528 529 int32 tabCount; 530 archive->GetInfo("_l_items", NULL, &tabCount); 531 for (int32 i = 0; i < tabCount && err == B_OK; i++) { 532 BTab* tab; 533 err = unarchiver.FindObject("_l_items", i, tab); 534 if (err == B_OK && tab) { 535 BView* view; 536 if ((err = unarchiver.FindObject("_view_list", i, 537 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, view)) != B_OK) 538 break; 539 540 tab->SetView(view); 541 fTabList->AddItem(tab); 542 } 543 } 544 545 if (err == B_OK) 546 Select(fSelection); 547 548 return err; 549 } 550 551 552 status_t 553 BTabView::Perform(perform_code code, void* _data) 554 { 555 switch (code) { 556 case PERFORM_CODE_ALL_UNARCHIVED: 557 { 558 perform_data_all_unarchived* data 559 = (perform_data_all_unarchived*)_data; 560 561 data->return_value = BTabView::AllUnarchived(data->archive); 562 return B_OK; 563 } 564 } 565 566 return BView::Perform(code, _data); 567 } 568 569 570 void 571 BTabView::AttachedToWindow() 572 { 573 BView::AttachedToWindow(); 574 575 if (fSelection < 0 && CountTabs() > 0) 576 Select(0); 577 } 578 579 580 void 581 BTabView::DetachedFromWindow() 582 { 583 BView::DetachedFromWindow(); 584 } 585 586 587 void 588 BTabView::AllAttached() 589 { 590 BView::AllAttached(); 591 } 592 593 594 void 595 BTabView::AllDetached() 596 { 597 BView::AllDetached(); 598 } 599 600 601 // #pragma mark - 602 603 604 void 605 BTabView::MessageReceived(BMessage* message) 606 { 607 switch (message->what) { 608 case B_GET_PROPERTY: 609 case B_SET_PROPERTY: 610 { 611 BMessage reply(B_REPLY); 612 bool handled = false; 613 614 BMessage specifier; 615 int32 index; 616 int32 form; 617 const char* property; 618 if (message->GetCurrentSpecifier(&index, &specifier, &form, 619 &property) == B_OK) { 620 if (strcmp(property, "Selection") == 0) { 621 if (message->what == B_GET_PROPERTY) { 622 reply.AddInt32("result", fSelection); 623 handled = true; 624 } else { 625 // B_GET_PROPERTY 626 int32 selection; 627 if (message->FindInt32("data", &selection) == B_OK) { 628 Select(selection); 629 reply.AddInt32("error", B_OK); 630 handled = true; 631 } 632 } 633 } 634 } 635 636 if (handled) 637 message->SendReply(&reply); 638 else 639 BView::MessageReceived(message); 640 break; 641 } 642 643 #if 0 644 // TODO this would be annoying as-is, but maybe it makes sense with 645 // a modifier or using only deltaX (not the main mouse wheel) 646 case B_MOUSE_WHEEL_CHANGED: 647 { 648 float deltaX = 0.0f; 649 float deltaY = 0.0f; 650 message->FindFloat("be:wheel_delta_x", &deltaX); 651 message->FindFloat("be:wheel_delta_y", &deltaY); 652 653 if (deltaX == 0.0f && deltaY == 0.0f) 654 return; 655 656 if (deltaY == 0.0f) 657 deltaY = deltaX; 658 659 int32 selection = Selection(); 660 int32 numTabs = CountTabs(); 661 if (deltaY > 0 && selection < numTabs - 1) { 662 // move to the right tab. 663 Select(Selection() + 1); 664 } else if (deltaY < 0 && selection > 0 && numTabs > 1) { 665 // move to the left tab. 666 Select(selection - 1); 667 } 668 break; 669 } 670 #endif 671 672 default: 673 BView::MessageReceived(message); 674 break; 675 } 676 } 677 678 679 void 680 BTabView::KeyDown(const char* bytes, int32 numBytes) 681 { 682 if (IsHidden()) 683 return; 684 685 switch (bytes[0]) { 686 case B_DOWN_ARROW: 687 case B_LEFT_ARROW: { 688 int32 focus = fFocus - 1; 689 if (focus < 0) 690 focus = CountTabs() - 1; 691 SetFocusTab(focus, true); 692 break; 693 } 694 695 case B_UP_ARROW: 696 case B_RIGHT_ARROW: { 697 int32 focus = fFocus + 1; 698 if (focus >= CountTabs()) 699 focus = 0; 700 SetFocusTab(focus, true); 701 break; 702 } 703 704 case B_RETURN: 705 case B_SPACE: 706 Select(FocusTab()); 707 break; 708 709 default: 710 BView::KeyDown(bytes, numBytes); 711 } 712 } 713 714 715 void 716 BTabView::MouseDown(BPoint where) 717 { 718 // Which button is pressed? 719 uint32 buttons = 0; 720 BMessage* currentMessage = Window()->CurrentMessage(); 721 if (currentMessage != NULL) { 722 currentMessage->FindInt32("buttons", (int32*)&buttons); 723 } 724 725 int32 selection = Selection(); 726 int32 numTabs = CountTabs(); 727 if (buttons & B_MOUSE_BUTTON(4)) { 728 // The "back" mouse button moves to previous tab 729 if (selection > 0 && numTabs > 1) 730 Select(Selection() - 1); 731 } else if (buttons & B_MOUSE_BUTTON(5)) { 732 // The "forward" mouse button moves to next tab 733 if (selection < numTabs - 1) 734 Select(Selection() + 1); 735 } else { 736 // Other buttons are used to select a tab by clicking directly on it 737 for (int32 i = 0; i < CountTabs(); i++) { 738 if (TabFrame(i).Contains(where) 739 && i != Selection()) { 740 Select(i); 741 return; 742 } 743 } 744 } 745 746 BView::MouseDown(where); 747 } 748 749 750 void 751 BTabView::MouseUp(BPoint where) 752 { 753 BView::MouseUp(where); 754 } 755 756 757 void 758 BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 759 { 760 BView::MouseMoved(where, transit, dragMessage); 761 } 762 763 764 void 765 BTabView::Pulse() 766 { 767 BView::Pulse(); 768 } 769 770 771 void 772 BTabView::Select(int32 index) 773 { 774 if (index == Selection()) 775 return; 776 777 if (index < 0 || index >= CountTabs()) 778 index = Selection(); 779 780 BTab* tab = TabAt(Selection()); 781 782 if (tab) 783 tab->Deselect(); 784 785 tab = TabAt(index); 786 if (tab != NULL && fContainerView != NULL) { 787 if (index == 0) 788 fTabOffset = 0.0f; 789 790 tab->Select(fContainerView); 791 fSelection = index; 792 793 // make the view visible through the layout if there is one 794 BCardLayout* layout 795 = dynamic_cast<BCardLayout*>(fContainerView->GetLayout()); 796 if (layout != NULL) 797 layout->SetVisibleItem(index); 798 } 799 800 Invalidate(); 801 802 if (index != 0 && !Bounds().Contains(TabFrame(index))){ 803 if (!Bounds().Contains(TabFrame(index).LeftTop())) 804 fTabOffset += TabFrame(index).left - Bounds().left - 20.0f; 805 else 806 fTabOffset += TabFrame(index).right - Bounds().right + 20.0f; 807 808 Invalidate(); 809 } 810 811 SetFocusTab(index, true); 812 } 813 814 815 int32 816 BTabView::Selection() const 817 { 818 return fSelection; 819 } 820 821 822 void 823 BTabView::WindowActivated(bool active) 824 { 825 BView::WindowActivated(active); 826 827 if (IsFocus()) 828 Invalidate(); 829 } 830 831 832 void 833 BTabView::MakeFocus(bool focus) 834 { 835 BView::MakeFocus(focus); 836 837 SetFocusTab(Selection(), focus); 838 } 839 840 841 void 842 BTabView::SetFocusTab(int32 tab, bool focus) 843 { 844 if (tab >= CountTabs()) 845 tab = 0; 846 847 if (tab < 0) 848 tab = CountTabs() - 1; 849 850 if (focus) { 851 if (tab == fFocus) 852 return; 853 854 if (fFocus != -1){ 855 if (TabAt (fFocus) != NULL) 856 TabAt(fFocus)->MakeFocus(false); 857 Invalidate(TabFrame(fFocus)); 858 } 859 if (TabAt(tab) != NULL){ 860 TabAt(tab)->MakeFocus(true); 861 Invalidate(TabFrame(tab)); 862 fFocus = tab; 863 } 864 } else if (fFocus != -1) { 865 TabAt(fFocus)->MakeFocus(false); 866 Invalidate(TabFrame(fFocus)); 867 fFocus = -1; 868 } 869 } 870 871 872 int32 873 BTabView::FocusTab() const 874 { 875 return fFocus; 876 } 877 878 879 void 880 BTabView::Draw(BRect updateRect) 881 { 882 DrawTabs(); 883 DrawBox(TabFrame(fSelection)); 884 885 if (IsFocus() && fFocus != -1) 886 TabAt(fFocus)->DrawFocusMark(this, TabFrame(fFocus)); 887 } 888 889 890 BRect 891 BTabView::DrawTabs() 892 { 893 BRect bounds(Bounds()); 894 BRect tabFrame(bounds); 895 uint32 borders = 0; 896 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 897 898 // set tabFrame to area around tabs 899 if (fTabSide == kTopSide || fTabSide == kBottomSide) { 900 if (fTabSide == kTopSide) 901 tabFrame.bottom = fTabHeight; 902 else 903 tabFrame.top = tabFrame.bottom - fTabHeight; 904 } else if (fTabSide == kLeftSide || fTabSide == kRightSide) { 905 if (fTabSide == kLeftSide) 906 tabFrame.right = fTabHeight; 907 else 908 tabFrame.left = tabFrame.right - fTabHeight; 909 } 910 911 // draw frame behind tabs 912 be_control_look->DrawTabFrame(this, tabFrame, bounds, base, 0, 913 borders, fBorderStyle, fTabSide); 914 915 // draw the tabs on top of the tab frame 916 BRect activeTabFrame; 917 int32 tabCount = CountTabs(); 918 for (int32 i = 0; i < tabCount; i++) { 919 BRect tabFrame = TabFrame(i); 920 if (i == fSelection) 921 activeTabFrame = tabFrame; 922 923 TabAt(i)->DrawTab(this, tabFrame, 924 i == fSelection ? B_TAB_FRONT : 925 (i == 0) ? B_TAB_FIRST : B_TAB_ANY, 926 i + 1 != fSelection); 927 } 928 929 BRect tabsBounds; 930 float last = 0.0f; 931 float lastTab = 0.0f; 932 if (fTabSide == kTopSide || fTabSide == kBottomSide) { 933 lastTab = TabFrame(tabCount - 1).right; 934 last = tabFrame.right; 935 tabsBounds.left = tabsBounds.right = lastTab; 936 borders = BControlLook::B_TOP_BORDER | BControlLook::B_BOTTOM_BORDER; 937 } else if (fTabSide == kLeftSide || fTabSide == kRightSide) { 938 lastTab = TabFrame(tabCount - 1).bottom; 939 last = tabFrame.bottom; 940 tabsBounds.top = tabsBounds.bottom = lastTab; 941 borders = BControlLook::B_LEFT_BORDER | BControlLook::B_RIGHT_BORDER; 942 } 943 944 if (lastTab < last) { 945 // draw a 1px right border on the last tab 946 be_control_look->DrawInactiveTab(this, tabsBounds, tabsBounds, base, 0, 947 borders, fTabSide); 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 float width = 100.0f; 996 float height = fTabHeight; 997 float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING); 998 BRect bounds(Bounds()); 999 1000 switch (fTabWidthSetting) { 1001 case B_WIDTH_FROM_LABEL: 1002 { 1003 float x = 0.0f; 1004 for (int32 i = 0; i < index; i++){ 1005 x += StringWidth(TabAt(i)->Label()) + 20.0f; 1006 } 1007 1008 switch (fTabSide) { 1009 case kTopSide: 1010 return BRect(offset + x, 0.0f, 1011 offset + x + StringWidth(TabAt(index)->Label()) + 20.0f, 1012 height); 1013 case kBottomSide: 1014 return BRect(offset + x, bounds.bottom - height, 1015 offset + x + StringWidth(TabAt(index)->Label()) + 20.0f, 1016 bounds.bottom); 1017 case kLeftSide: 1018 return BRect(0.0f, offset + x, height, offset + x 1019 + StringWidth(TabAt(index)->Label()) + 20.0f); 1020 case kRightSide: 1021 return BRect(bounds.right - height, offset + x, 1022 bounds.right, offset + x 1023 + StringWidth(TabAt(index)->Label()) + 20.0f); 1024 default: 1025 return BRect(); 1026 } 1027 } 1028 1029 case B_WIDTH_FROM_WIDEST: 1030 width = 0.0; 1031 for (int32 i = 0; i < CountTabs(); i++) { 1032 float tabWidth = StringWidth(TabAt(i)->Label()) + 20.0f; 1033 if (tabWidth > width) 1034 width = tabWidth; 1035 } 1036 // fall through 1037 1038 case B_WIDTH_AS_USUAL: 1039 default: 1040 switch (fTabSide) { 1041 case kTopSide: 1042 return BRect(offset + index * width, 0.0f, 1043 offset + index * width + width, height); 1044 case kBottomSide: 1045 return BRect(offset + index * width, bounds.bottom - height, 1046 offset + index * width + width, bounds.bottom); 1047 case kLeftSide: 1048 return BRect(0.0f, offset + index * width, height, 1049 offset + index * width + width); 1050 case kRightSide: 1051 return BRect(bounds.right - height, offset + index * width, 1052 bounds.right, offset + index * width + width); 1053 default: 1054 return BRect(); 1055 } 1056 } 1057 } 1058 1059 1060 void 1061 BTabView::SetFlags(uint32 flags) 1062 { 1063 BView::SetFlags(flags); 1064 } 1065 1066 1067 void 1068 BTabView::SetResizingMode(uint32 mode) 1069 { 1070 BView::SetResizingMode(mode); 1071 } 1072 1073 1074 // #pragma mark - 1075 1076 1077 void 1078 BTabView::ResizeToPreferred() 1079 { 1080 BView::ResizeToPreferred(); 1081 } 1082 1083 1084 void 1085 BTabView::GetPreferredSize(float* _width, float* _height) 1086 { 1087 BView::GetPreferredSize(_width, _height); 1088 } 1089 1090 1091 BSize 1092 BTabView::MinSize() 1093 { 1094 BSize size; 1095 if (GetLayout()) 1096 size = GetLayout()->MinSize(); 1097 else { 1098 size = _TabsMinSize(); 1099 BSize containerSize = fContainerView->MinSize(); 1100 containerSize.width += 2 * _BorderWidth(); 1101 containerSize.height += 2 * _BorderWidth(); 1102 if (containerSize.width > size.width) 1103 size.width = containerSize.width; 1104 size.height += containerSize.height; 1105 } 1106 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 1107 } 1108 1109 1110 BSize 1111 BTabView::MaxSize() 1112 { 1113 BSize size; 1114 if (GetLayout()) 1115 size = GetLayout()->MaxSize(); 1116 else { 1117 size = _TabsMinSize(); 1118 BSize containerSize = fContainerView->MaxSize(); 1119 containerSize.width += 2 * _BorderWidth(); 1120 containerSize.height += 2 * _BorderWidth(); 1121 if (containerSize.width > size.width) 1122 size.width = containerSize.width; 1123 size.height += containerSize.height; 1124 } 1125 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 1126 } 1127 1128 1129 BSize 1130 BTabView::PreferredSize() 1131 { 1132 BSize size; 1133 if (GetLayout() != NULL) 1134 size = GetLayout()->PreferredSize(); 1135 else { 1136 size = _TabsMinSize(); 1137 BSize containerSize = fContainerView->PreferredSize(); 1138 containerSize.width += 2 * _BorderWidth(); 1139 containerSize.height += 2 * _BorderWidth(); 1140 if (containerSize.width > size.width) 1141 size.width = containerSize.width; 1142 size.height += containerSize.height; 1143 } 1144 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 1145 } 1146 1147 1148 void 1149 BTabView::FrameMoved(BPoint newPosition) 1150 { 1151 BView::FrameMoved(newPosition); 1152 } 1153 1154 1155 void 1156 BTabView::FrameResized(float newWidth, float newHeight) 1157 { 1158 BView::FrameResized(newWidth, newHeight); 1159 } 1160 1161 1162 // #pragma mark - 1163 1164 1165 BHandler* 1166 BTabView::ResolveSpecifier(BMessage* message, int32 index, 1167 BMessage* specifier, int32 what, const char* property) 1168 { 1169 BPropertyInfo propInfo(sPropertyList); 1170 1171 if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK) 1172 return this; 1173 1174 return BView::ResolveSpecifier(message, index, specifier, what, property); 1175 } 1176 1177 1178 status_t 1179 BTabView::GetSupportedSuites(BMessage* message) 1180 { 1181 message->AddString("suites", "suite/vnd.Be-tab-view"); 1182 1183 BPropertyInfo propInfo(sPropertyList); 1184 message->AddFlat("messages", &propInfo); 1185 1186 return BView::GetSupportedSuites(message); 1187 } 1188 1189 1190 // #pragma mark - 1191 1192 1193 void 1194 BTabView::AddTab(BView* target, BTab* tab) 1195 { 1196 if (tab == NULL) 1197 tab = new BTab(target); 1198 else 1199 tab->SetView(target); 1200 1201 if (fContainerView->GetLayout()) 1202 fContainerView->GetLayout()->AddView(CountTabs(), target); 1203 1204 fTabList->AddItem(tab); 1205 BTab::Private(tab).SetTabView(this); 1206 1207 // When we haven't had a any tabs before, but are already attached to the 1208 // window, select this one. 1209 if (CountTabs() == 1 && Window() != NULL) 1210 Select(0); 1211 } 1212 1213 1214 BTab* 1215 BTabView::RemoveTab(int32 index) 1216 { 1217 if (index < 0 || index >= CountTabs()) 1218 return NULL; 1219 1220 BTab* tab = (BTab*)fTabList->RemoveItem(index); 1221 if (tab == NULL) 1222 return NULL; 1223 1224 tab->Deselect(); 1225 BTab::Private(tab).SetTabView(NULL); 1226 1227 if (fContainerView->GetLayout()) 1228 fContainerView->GetLayout()->RemoveItem(index); 1229 1230 if (CountTabs() == 0) 1231 fFocus = -1; 1232 else if (index <= fSelection) 1233 Select(fSelection - 1); 1234 1235 if (fFocus >= 0) { 1236 if (fFocus == CountTabs() - 1 || CountTabs() == 0) 1237 SetFocusTab(fFocus, false); 1238 else 1239 SetFocusTab(fFocus, true); 1240 } 1241 1242 return tab; 1243 } 1244 1245 1246 BTab* 1247 BTabView::TabAt(int32 index) const 1248 { 1249 return (BTab*)fTabList->ItemAt(index); 1250 } 1251 1252 1253 void 1254 BTabView::SetTabWidth(button_width width) 1255 { 1256 fTabWidthSetting = width; 1257 1258 Invalidate(); 1259 } 1260 1261 1262 button_width 1263 BTabView::TabWidth() const 1264 { 1265 return fTabWidthSetting; 1266 } 1267 1268 1269 void 1270 BTabView::SetTabHeight(float height) 1271 { 1272 if (fTabHeight == height) 1273 return; 1274 1275 fTabHeight = height; 1276 _LayoutContainerView(GetLayout() != NULL); 1277 1278 Invalidate(); 1279 } 1280 1281 1282 float 1283 BTabView::TabHeight() const 1284 { 1285 return fTabHeight; 1286 } 1287 1288 1289 void 1290 BTabView::SetBorder(border_style borderStyle) 1291 { 1292 if (fBorderStyle == borderStyle) 1293 return; 1294 1295 fBorderStyle = borderStyle; 1296 1297 _LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0); 1298 } 1299 1300 1301 border_style 1302 BTabView::Border() const 1303 { 1304 return fBorderStyle; 1305 } 1306 1307 1308 void 1309 BTabView::SetTabSide(tab_side tabSide) 1310 { 1311 if (fTabSide == tabSide) 1312 return; 1313 1314 fTabSide = tabSide; 1315 _LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT); 1316 } 1317 1318 1319 BTabView::tab_side 1320 BTabView::TabSide() const 1321 { 1322 return fTabSide; 1323 } 1324 1325 1326 BView* 1327 BTabView::ContainerView() const 1328 { 1329 return fContainerView; 1330 } 1331 1332 1333 int32 1334 BTabView::CountTabs() const 1335 { 1336 return fTabList->CountItems(); 1337 } 1338 1339 1340 BView* 1341 BTabView::ViewForTab(int32 tabIndex) const 1342 { 1343 BTab* tab = TabAt(tabIndex); 1344 if (tab != NULL) 1345 return tab->View(); 1346 1347 return NULL; 1348 } 1349 1350 1351 void 1352 BTabView::_InitObject(bool layouted, button_width width) 1353 { 1354 fTabList = new BList; 1355 1356 fTabWidthSetting = width; 1357 fSelection = -1; 1358 fFocus = -1; 1359 fTabOffset = 0.0f; 1360 fBorderStyle = B_FANCY_BORDER; 1361 fTabSide = kTopSide; 1362 1363 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1364 SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1365 1366 font_height fh; 1367 GetFontHeight(&fh); 1368 fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 8.0f); 1369 1370 fContainerView = NULL; 1371 _InitContainerView(layouted); 1372 } 1373 1374 1375 void 1376 BTabView::_InitContainerView(bool layouted) 1377 { 1378 bool needsLayout = false; 1379 bool createdContainer = false; 1380 if (layouted) { 1381 if (GetLayout() == NULL) { 1382 SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL)); 1383 needsLayout = true; 1384 } 1385 1386 if (fContainerView == NULL) { 1387 fContainerView = new BView("view container", B_WILL_DRAW); 1388 fContainerView->SetLayout(new(std::nothrow) BCardLayout()); 1389 createdContainer = true; 1390 } 1391 } else if (fContainerView == NULL) { 1392 fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL, 1393 B_WILL_DRAW); 1394 createdContainer = true; 1395 } 1396 1397 if (needsLayout || createdContainer) 1398 _LayoutContainerView(layouted); 1399 1400 if (createdContainer) { 1401 fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1402 fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1403 AddChild(fContainerView); 1404 } 1405 } 1406 1407 1408 BSize 1409 BTabView::_TabsMinSize() const 1410 { 1411 BSize size(0.0f, TabHeight()); 1412 int32 count = min_c(2, CountTabs()); 1413 for (int32 i = 0; i < count; i++) { 1414 BRect frame = TabFrame(i); 1415 size.width += frame.Width(); 1416 } 1417 1418 if (count < CountTabs()) { 1419 // TODO: Add size for yet to be implemented buttons that allow 1420 // "scrolling" the displayed tabs left/right. 1421 } 1422 1423 return size; 1424 } 1425 1426 1427 float 1428 BTabView::_BorderWidth() const 1429 { 1430 switch (fBorderStyle) { 1431 default: 1432 case B_FANCY_BORDER: 1433 return 3.0f; 1434 1435 case B_PLAIN_BORDER: 1436 return 1.0f; 1437 1438 case B_NO_BORDER: 1439 return 0.0f; 1440 } 1441 } 1442 1443 1444 void 1445 BTabView::_LayoutContainerView(bool layouted) 1446 { 1447 float borderWidth = _BorderWidth(); 1448 if (layouted) { 1449 float topBorderOffset; 1450 switch (fBorderStyle) { 1451 default: 1452 case B_FANCY_BORDER: 1453 topBorderOffset = 1.0f; 1454 break; 1455 1456 case B_PLAIN_BORDER: 1457 topBorderOffset = 0.0f; 1458 break; 1459 1460 case B_NO_BORDER: 1461 topBorderOffset = -1.0f; 1462 break; 1463 } 1464 BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout()); 1465 if (layout != NULL) { 1466 float inset = borderWidth + TabHeight() - topBorderOffset; 1467 switch (fTabSide) { 1468 case kTopSide: 1469 layout->SetInsets(borderWidth, inset, borderWidth, 1470 borderWidth); 1471 break; 1472 case kBottomSide: 1473 layout->SetInsets(borderWidth, borderWidth, borderWidth, 1474 inset); 1475 break; 1476 case kLeftSide: 1477 layout->SetInsets(inset, borderWidth, borderWidth, 1478 borderWidth); 1479 break; 1480 case kRightSide: 1481 layout->SetInsets(borderWidth, borderWidth, inset, 1482 borderWidth); 1483 break; 1484 } 1485 } 1486 } else { 1487 BRect bounds = Bounds(); 1488 switch (fTabSide) { 1489 case kTopSide: 1490 bounds.top += TabHeight(); 1491 break; 1492 case kBottomSide: 1493 bounds.bottom -= TabHeight(); 1494 break; 1495 case kLeftSide: 1496 bounds.left += TabHeight(); 1497 break; 1498 case kRightSide: 1499 bounds.right -= TabHeight(); 1500 break; 1501 } 1502 bounds.InsetBy(borderWidth, borderWidth); 1503 1504 fContainerView->MoveTo(bounds.left, bounds.top); 1505 fContainerView->ResizeTo(bounds.Width(), bounds.Height()); 1506 } 1507 } 1508 1509 1510 // #pragma mark - FBC and forbidden 1511 1512 1513 void BTabView::_ReservedTabView3() {} 1514 void BTabView::_ReservedTabView4() {} 1515 void BTabView::_ReservedTabView5() {} 1516 void BTabView::_ReservedTabView6() {} 1517 void BTabView::_ReservedTabView7() {} 1518 void BTabView::_ReservedTabView8() {} 1519 void BTabView::_ReservedTabView9() {} 1520 void BTabView::_ReservedTabView10() {} 1521 void BTabView::_ReservedTabView11() {} 1522 void BTabView::_ReservedTabView12() {} 1523 1524 1525 BTabView::BTabView(const BTabView& tabView) 1526 : BView(tabView) 1527 { 1528 // this is private and not functional, but exported 1529 } 1530 1531 1532 BTabView& 1533 BTabView::operator=(const BTabView&) 1534 { 1535 // this is private and not functional, but exported 1536 return *this; 1537 } 1538 1539 // #pragma mark - binary compatibility 1540 1541 1542 extern "C" void 1543 B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)( 1544 BTabView* tabView, border_style borderStyle) 1545 { 1546 tabView->BTabView::SetBorder(borderStyle); 1547 } 1548 1549 extern "C" void 1550 B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)( 1551 BTabView* tabView, BTabView::tab_side tabSide) 1552 { 1553 tabView->BTabView::SetTabSide(tabSide); 1554 } 1555