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 37static 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 50BTab::BTab(BView* contentsView) 51 : 52 fEnabled(true), 53 fSelected(false), 54 fFocus(false), 55 fView(contentsView), 56 fTabView(NULL) 57{ 58} 59 60 61BTab::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 78BTab::~BTab() 79{ 80 if (fView == NULL) 81 return; 82 83 if (fSelected) 84 fView->RemoveSelf(); 85 86 delete fView; 87} 88 89 90BArchivable* 91BTab::Instantiate(BMessage* archive) 92{ 93 if (validate_instantiation(archive, "BTab")) 94 return new BTab(archive); 95 96 return NULL; 97} 98 99 100status_t 101BTab::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 114status_t 115BTab::Perform(uint32 d, void* arg) 116{ 117 return BArchivable::Perform(d, arg); 118} 119 120 121const char* 122BTab::Label() const 123{ 124 if (fView != NULL) 125 return fView->Name(); 126 else 127 return NULL; 128} 129 130 131void 132BTab::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 144bool 145BTab::IsSelected() const 146{ 147 return fSelected; 148} 149 150 151void 152BTab::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 166void 167BTab::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 185void 186BTab::SetEnabled(bool enable) 187{ 188 fEnabled = enable; 189} 190 191 192bool 193BTab::IsEnabled() const 194{ 195 return fEnabled; 196} 197 198 199void 200BTab::MakeFocus(bool focus) 201{ 202 fFocus = focus; 203} 204 205 206bool 207BTab::IsFocus() const 208{ 209 return fFocus; 210} 211 212 213void 214BTab::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 232BView* 233BTab::View() const 234{ 235 return fView; 236} 237 238 239void 240BTab::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 276void 277BTab::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 318void 319BTab::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 348uint32 349BTab::_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 382void BTab::_ReservedTab1() {} 383void BTab::_ReservedTab2() {} 384void BTab::_ReservedTab3() {} 385void BTab::_ReservedTab4() {} 386void BTab::_ReservedTab5() {} 387void BTab::_ReservedTab6() {} 388void BTab::_ReservedTab7() {} 389void BTab::_ReservedTab8() {} 390void BTab::_ReservedTab9() {} 391void BTab::_ReservedTab10() {} 392void BTab::_ReservedTab11() {} 393void BTab::_ReservedTab12() {} 394 395BTab &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 405BTabView::BTabView(const char* name, button_width width, uint32 flags) 406 : 407 BView(name, flags) 408{ 409 _InitObject(true, width); 410} 411 412 413BTabView::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 422BTabView::~BTabView() 423{ 424 for (int32 i = 0; i < CountTabs(); i++) 425 delete TabAt(i); 426 427 delete fTabList; 428} 429 430 431BTabView::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 497BArchivable* 498BTabView::Instantiate(BMessage* archive) 499{ 500 if ( validate_instantiation(archive, "BTabView")) 501 return new BTabView(archive); 502 503 return NULL; 504} 505 506 507status_t 508BTabView::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 541status_t 542BTabView::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 576status_t 577BTabView::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 594void 595BTabView::AttachedToWindow() 596{ 597 BView::AttachedToWindow(); 598 599 if (fSelection < 0 && CountTabs() > 0) 600 Select(0); 601} 602 603 604void 605BTabView::DetachedFromWindow() 606{ 607 BView::DetachedFromWindow(); 608} 609 610 611void 612BTabView::AllAttached() 613{ 614 BView::AllAttached(); 615} 616 617 618void 619BTabView::AllDetached() 620{ 621 BView::AllDetached(); 622} 623 624 625// #pragma mark - 626 627 628void 629BTabView::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 703void 704BTabView::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 739void 740BTabView::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 774void 775BTabView::MouseUp(BPoint where) 776{ 777 BView::MouseUp(where); 778} 779 780 781void 782BTabView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 783{ 784 BView::MouseMoved(where, transit, dragMessage); 785} 786 787 788void 789BTabView::Pulse() 790{ 791 BView::Pulse(); 792} 793 794 795void 796BTabView::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 839int32 840BTabView::Selection() const 841{ 842 return fSelection; 843} 844 845 846void 847BTabView::WindowActivated(bool active) 848{ 849 BView::WindowActivated(active); 850 851 if (IsFocus()) 852 Invalidate(); 853} 854 855 856void 857BTabView::MakeFocus(bool focus) 858{ 859 BView::MakeFocus(focus); 860 861 SetFocusTab(Selection(), focus); 862} 863 864 865void 866BTabView::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 896int32 897BTabView::FocusTab() const 898{ 899 return fFocus; 900} 901 902 903void 904BTabView::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 914BRect 915BTabView::DrawTabs() 916{ 917 BRect bounds(Bounds()); 918 BRect tabFrame(bounds); 919 uint32 borders = 0; 920 rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); 921 922 // set tabFrame to area around tabs 923 if (fTabSide == kTopSide || fTabSide == kBottomSide) { 924 if (fTabSide == kTopSide) 925 tabFrame.bottom = fTabHeight; 926 else 927 tabFrame.top = tabFrame.bottom - fTabHeight; 928 } else if (fTabSide == kLeftSide || fTabSide == kRightSide) { 929 if (fTabSide == kLeftSide) 930 tabFrame.right = fTabHeight; 931 else 932 tabFrame.left = tabFrame.right - fTabHeight; 933 } 934 935 // draw frame behind tabs 936 be_control_look->DrawTabFrame(this, tabFrame, bounds, base, 0, 937 borders, fBorderStyle, fTabSide); 938 939 // draw the tabs on top of the tab frame 940 int32 tabCount = CountTabs(); 941 for (int32 i = 0; i < tabCount; i++) { 942 BRect tabFrame = TabFrame(i); 943 944 TabAt(i)->DrawTab(this, tabFrame, 945 i == fSelection ? B_TAB_FRONT 946 : (i == 0) ? B_TAB_FIRST : B_TAB_ANY, 947 i != fSelection - 1); 948 } 949 950 return fSelection < CountTabs() ? TabFrame(fSelection) : BRect(); 951} 952 953 954void 955BTabView::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 989BRect 990BTabView::TabFrame(int32 index) const 991{ 992 if (index >= CountTabs() || index < 0) 993 return BRect(); 994 995 const float padding = ceilf(be_control_look->DefaultLabelSpacing() * 3.3f); 996 const float height = fTabHeight; 997 const float offset = BControlLook::ComposeSpacing(B_USE_WINDOW_SPACING); 998 const BRect bounds(Bounds()); 999 1000 float width = padding * 5.0f; 1001 switch (fTabWidthSetting) { 1002 case B_WIDTH_FROM_LABEL: 1003 { 1004 float x = 0.0f; 1005 for (int32 i = 0; i < index; i++){ 1006 x += StringWidth(TabAt(i)->Label()) + padding; 1007 } 1008 1009 switch (fTabSide) { 1010 case kTopSide: 1011 return BRect(offset + x, 0.0f, 1012 offset + x + StringWidth(TabAt(index)->Label()) + padding, 1013 height); 1014 case kBottomSide: 1015 return BRect(offset + x, bounds.bottom - height, 1016 offset + x + StringWidth(TabAt(index)->Label()) + padding, 1017 bounds.bottom); 1018 case kLeftSide: 1019 return BRect(0.0f, offset + x, height, offset + x 1020 + StringWidth(TabAt(index)->Label()) + padding); 1021 case kRightSide: 1022 return BRect(bounds.right - height, offset + x, 1023 bounds.right, offset + x 1024 + StringWidth(TabAt(index)->Label()) + padding); 1025 default: 1026 return BRect(); 1027 } 1028 } 1029 1030 case B_WIDTH_FROM_WIDEST: 1031 width = 0.0; 1032 for (int32 i = 0; i < CountTabs(); i++) { 1033 float tabWidth = StringWidth(TabAt(i)->Label()) + padding; 1034 if (tabWidth > width) 1035 width = tabWidth; 1036 } 1037 // fall through 1038 1039 case B_WIDTH_AS_USUAL: 1040 default: 1041 switch (fTabSide) { 1042 case kTopSide: 1043 return BRect(offset + index * width, 0.0f, 1044 offset + index * width + width, height); 1045 case kBottomSide: 1046 return BRect(offset + index * width, bounds.bottom - height, 1047 offset + index * width + width, bounds.bottom); 1048 case kLeftSide: 1049 return BRect(0.0f, offset + index * width, height, 1050 offset + index * width + width); 1051 case kRightSide: 1052 return BRect(bounds.right - height, offset + index * width, 1053 bounds.right, offset + index * width + width); 1054 default: 1055 return BRect(); 1056 } 1057 } 1058} 1059 1060 1061void 1062BTabView::SetFlags(uint32 flags) 1063{ 1064 BView::SetFlags(flags); 1065} 1066 1067 1068void 1069BTabView::SetResizingMode(uint32 mode) 1070{ 1071 BView::SetResizingMode(mode); 1072} 1073 1074 1075// #pragma mark - 1076 1077 1078void 1079BTabView::ResizeToPreferred() 1080{ 1081 BView::ResizeToPreferred(); 1082} 1083 1084 1085void 1086BTabView::GetPreferredSize(float* _width, float* _height) 1087{ 1088 BView::GetPreferredSize(_width, _height); 1089} 1090 1091 1092BSize 1093BTabView::MinSize() 1094{ 1095 BSize size; 1096 if (GetLayout()) 1097 size = GetLayout()->MinSize(); 1098 else { 1099 size = _TabsMinSize(); 1100 BSize containerSize = fContainerView->MinSize(); 1101 containerSize.width += 2 * _BorderWidth(); 1102 containerSize.height += 2 * _BorderWidth(); 1103 if (containerSize.width > size.width) 1104 size.width = containerSize.width; 1105 size.height += containerSize.height; 1106 } 1107 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 1108} 1109 1110 1111BSize 1112BTabView::MaxSize() 1113{ 1114 BSize size; 1115 if (GetLayout()) 1116 size = GetLayout()->MaxSize(); 1117 else { 1118 size = _TabsMinSize(); 1119 BSize containerSize = fContainerView->MaxSize(); 1120 containerSize.width += 2 * _BorderWidth(); 1121 containerSize.height += 2 * _BorderWidth(); 1122 if (containerSize.width > size.width) 1123 size.width = containerSize.width; 1124 size.height += containerSize.height; 1125 } 1126 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 1127} 1128 1129 1130BSize 1131BTabView::PreferredSize() 1132{ 1133 BSize size; 1134 if (GetLayout() != NULL) 1135 size = GetLayout()->PreferredSize(); 1136 else { 1137 size = _TabsMinSize(); 1138 BSize containerSize = fContainerView->PreferredSize(); 1139 containerSize.width += 2 * _BorderWidth(); 1140 containerSize.height += 2 * _BorderWidth(); 1141 if (containerSize.width > size.width) 1142 size.width = containerSize.width; 1143 size.height += containerSize.height; 1144 } 1145 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 1146} 1147 1148 1149void 1150BTabView::FrameMoved(BPoint newPosition) 1151{ 1152 BView::FrameMoved(newPosition); 1153} 1154 1155 1156void 1157BTabView::FrameResized(float newWidth, float newHeight) 1158{ 1159 BView::FrameResized(newWidth, newHeight); 1160} 1161 1162 1163// #pragma mark - 1164 1165 1166BHandler* 1167BTabView::ResolveSpecifier(BMessage* message, int32 index, 1168 BMessage* specifier, int32 what, const char* property) 1169{ 1170 BPropertyInfo propInfo(sPropertyList); 1171 1172 if (propInfo.FindMatch(message, 0, specifier, what, property) >= B_OK) 1173 return this; 1174 1175 return BView::ResolveSpecifier(message, index, specifier, what, property); 1176} 1177 1178 1179status_t 1180BTabView::GetSupportedSuites(BMessage* message) 1181{ 1182 message->AddString("suites", "suite/vnd.Be-tab-view"); 1183 1184 BPropertyInfo propInfo(sPropertyList); 1185 message->AddFlat("messages", &propInfo); 1186 1187 return BView::GetSupportedSuites(message); 1188} 1189 1190 1191// #pragma mark - 1192 1193 1194void 1195BTabView::AddTab(BView* target, BTab* tab) 1196{ 1197 if (tab == NULL) 1198 tab = new BTab(target); 1199 else 1200 tab->SetView(target); 1201 1202 if (fContainerView->GetLayout()) 1203 fContainerView->GetLayout()->AddView(CountTabs(), target); 1204 1205 fTabList->AddItem(tab); 1206 BTab::Private(tab).SetTabView(this); 1207 1208 // When we haven't had a any tabs before, but are already attached to the 1209 // window, select this one. 1210 if (CountTabs() == 1 && Window() != NULL) 1211 Select(0); 1212} 1213 1214 1215BTab* 1216BTabView::RemoveTab(int32 index) 1217{ 1218 if (index < 0 || index >= CountTabs()) 1219 return NULL; 1220 1221 BTab* tab = (BTab*)fTabList->RemoveItem(index); 1222 if (tab == NULL) 1223 return NULL; 1224 1225 tab->Deselect(); 1226 BTab::Private(tab).SetTabView(NULL); 1227 1228 if (fContainerView->GetLayout()) 1229 fContainerView->GetLayout()->RemoveItem(index); 1230 1231 if (CountTabs() == 0) 1232 fFocus = -1; 1233 else if (index <= fSelection) 1234 Select(fSelection - 1); 1235 1236 if (fFocus >= 0) { 1237 if (fFocus == CountTabs() - 1 || CountTabs() == 0) 1238 SetFocusTab(fFocus, false); 1239 else 1240 SetFocusTab(fFocus, true); 1241 } 1242 1243 return tab; 1244} 1245 1246 1247BTab* 1248BTabView::TabAt(int32 index) const 1249{ 1250 return (BTab*)fTabList->ItemAt(index); 1251} 1252 1253 1254void 1255BTabView::SetTabWidth(button_width width) 1256{ 1257 fTabWidthSetting = width; 1258 1259 Invalidate(); 1260} 1261 1262 1263button_width 1264BTabView::TabWidth() const 1265{ 1266 return fTabWidthSetting; 1267} 1268 1269 1270void 1271BTabView::SetTabHeight(float height) 1272{ 1273 if (fTabHeight == height) 1274 return; 1275 1276 fTabHeight = height; 1277 _LayoutContainerView(GetLayout() != NULL); 1278 1279 Invalidate(); 1280} 1281 1282 1283float 1284BTabView::TabHeight() const 1285{ 1286 return fTabHeight; 1287} 1288 1289 1290void 1291BTabView::SetBorder(border_style borderStyle) 1292{ 1293 if (fBorderStyle == borderStyle) 1294 return; 1295 1296 fBorderStyle = borderStyle; 1297 1298 _LayoutContainerView((Flags() & B_SUPPORTS_LAYOUT) != 0); 1299} 1300 1301 1302border_style 1303BTabView::Border() const 1304{ 1305 return fBorderStyle; 1306} 1307 1308 1309void 1310BTabView::SetTabSide(tab_side tabSide) 1311{ 1312 if (fTabSide == tabSide) 1313 return; 1314 1315 fTabSide = tabSide; 1316 _LayoutContainerView(Flags() & B_SUPPORTS_LAYOUT); 1317} 1318 1319 1320BTabView::tab_side 1321BTabView::TabSide() const 1322{ 1323 return fTabSide; 1324} 1325 1326 1327BView* 1328BTabView::ContainerView() const 1329{ 1330 return fContainerView; 1331} 1332 1333 1334int32 1335BTabView::CountTabs() const 1336{ 1337 return fTabList->CountItems(); 1338} 1339 1340 1341BView* 1342BTabView::ViewForTab(int32 tabIndex) const 1343{ 1344 BTab* tab = TabAt(tabIndex); 1345 if (tab != NULL) 1346 return tab->View(); 1347 1348 return NULL; 1349} 1350 1351 1352int32 1353BTabView::IndexOf(BTab* tab) const 1354{ 1355 if (tab != NULL) { 1356 int32 tabCount = CountTabs(); 1357 for (int32 index = 0; index < tabCount; index++) { 1358 if (TabAt(index) == tab) 1359 return index; 1360 } 1361 } 1362 1363 return -1; 1364} 1365 1366 1367void 1368BTabView::_InitObject(bool layouted, button_width width) 1369{ 1370 fTabList = new BList; 1371 1372 fTabWidthSetting = width; 1373 fSelection = -1; 1374 fFocus = -1; 1375 fTabOffset = 0.0f; 1376 fBorderStyle = B_FANCY_BORDER; 1377 fTabSide = kTopSide; 1378 1379 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1380 SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1381 1382 font_height fh; 1383 GetFontHeight(&fh); 1384 fTabHeight = ceilf(fh.ascent + fh.descent + fh.leading + 1385 (be_control_look->DefaultLabelSpacing() * 1.3f)); 1386 1387 fContainerView = NULL; 1388 _InitContainerView(layouted); 1389} 1390 1391 1392void 1393BTabView::_InitContainerView(bool layouted) 1394{ 1395 bool needsLayout = false; 1396 bool createdContainer = false; 1397 if (layouted) { 1398 if (GetLayout() == NULL) { 1399 SetLayout(new(std::nothrow) BGroupLayout(B_HORIZONTAL)); 1400 needsLayout = true; 1401 } 1402 1403 if (fContainerView == NULL) { 1404 fContainerView = new BView("view container", B_WILL_DRAW); 1405 fContainerView->SetLayout(new(std::nothrow) BCardLayout()); 1406 createdContainer = true; 1407 } 1408 } else if (fContainerView == NULL) { 1409 fContainerView = new BView(Bounds(), "view container", B_FOLLOW_ALL, 1410 B_WILL_DRAW); 1411 createdContainer = true; 1412 } 1413 1414 if (needsLayout || createdContainer) 1415 _LayoutContainerView(layouted); 1416 1417 if (createdContainer) { 1418 fContainerView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 1419 fContainerView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR); 1420 AddChild(fContainerView); 1421 } 1422} 1423 1424 1425BSize 1426BTabView::_TabsMinSize() const 1427{ 1428 BSize size(0.0f, TabHeight()); 1429 int32 count = min_c(2, CountTabs()); 1430 for (int32 i = 0; i < count; i++) { 1431 BRect frame = TabFrame(i); 1432 size.width += frame.Width(); 1433 } 1434 1435 if (count < CountTabs()) { 1436 // TODO: Add size for yet to be implemented buttons that allow 1437 // "scrolling" the displayed tabs left/right. 1438 } 1439 1440 return size; 1441} 1442 1443 1444float 1445BTabView::_BorderWidth() const 1446{ 1447 switch (fBorderStyle) { 1448 default: 1449 case B_FANCY_BORDER: 1450 return 3.0f; 1451 1452 case B_PLAIN_BORDER: 1453 return 1.0f; 1454 1455 case B_NO_BORDER: 1456 return 0.0f; 1457 } 1458} 1459 1460 1461void 1462BTabView::_LayoutContainerView(bool layouted) 1463{ 1464 float borderWidth = _BorderWidth(); 1465 if (layouted) { 1466 float topBorderOffset; 1467 switch (fBorderStyle) { 1468 default: 1469 case B_FANCY_BORDER: 1470 topBorderOffset = 1.0f; 1471 break; 1472 1473 case B_PLAIN_BORDER: 1474 topBorderOffset = 0.0f; 1475 break; 1476 1477 case B_NO_BORDER: 1478 topBorderOffset = -1.0f; 1479 break; 1480 } 1481 BGroupLayout* layout = dynamic_cast<BGroupLayout*>(GetLayout()); 1482 if (layout != NULL) { 1483 float inset = borderWidth + TabHeight() - topBorderOffset; 1484 switch (fTabSide) { 1485 case kTopSide: 1486 layout->SetInsets(borderWidth, inset, borderWidth, 1487 borderWidth); 1488 break; 1489 case kBottomSide: 1490 layout->SetInsets(borderWidth, borderWidth, borderWidth, 1491 inset); 1492 break; 1493 case kLeftSide: 1494 layout->SetInsets(inset, borderWidth, borderWidth, 1495 borderWidth); 1496 break; 1497 case kRightSide: 1498 layout->SetInsets(borderWidth, borderWidth, inset, 1499 borderWidth); 1500 break; 1501 } 1502 } 1503 } else { 1504 BRect bounds = Bounds(); 1505 switch (fTabSide) { 1506 case kTopSide: 1507 bounds.top += TabHeight(); 1508 break; 1509 case kBottomSide: 1510 bounds.bottom -= TabHeight(); 1511 break; 1512 case kLeftSide: 1513 bounds.left += TabHeight(); 1514 break; 1515 case kRightSide: 1516 bounds.right -= TabHeight(); 1517 break; 1518 } 1519 bounds.InsetBy(borderWidth, borderWidth); 1520 1521 fContainerView->MoveTo(bounds.left, bounds.top); 1522 fContainerView->ResizeTo(bounds.Width(), bounds.Height()); 1523 } 1524} 1525 1526 1527// #pragma mark - FBC and forbidden 1528 1529 1530void BTabView::_ReservedTabView3() {} 1531void BTabView::_ReservedTabView4() {} 1532void BTabView::_ReservedTabView5() {} 1533void BTabView::_ReservedTabView6() {} 1534void BTabView::_ReservedTabView7() {} 1535void BTabView::_ReservedTabView8() {} 1536void BTabView::_ReservedTabView9() {} 1537void BTabView::_ReservedTabView10() {} 1538void BTabView::_ReservedTabView11() {} 1539void BTabView::_ReservedTabView12() {} 1540 1541 1542BTabView::BTabView(const BTabView& tabView) 1543 : BView(tabView) 1544{ 1545 // this is private and not functional, but exported 1546} 1547 1548 1549BTabView& 1550BTabView::operator=(const BTabView&) 1551{ 1552 // this is private and not functional, but exported 1553 return *this; 1554} 1555 1556// #pragma mark - binary compatibility 1557 1558 1559extern "C" void 1560B_IF_GCC_2(_ReservedTabView1__8BTabView, _ZN8BTabView17_ReservedTabView1Ev)( 1561 BTabView* tabView, border_style borderStyle) 1562{ 1563 tabView->BTabView::SetBorder(borderStyle); 1564} 1565 1566extern "C" void 1567B_IF_GCC_2(_ReservedTabView2__8BTabView, _ZN8BTabView17_ReservedTabView2Ev)( 1568 BTabView* tabView, BTabView::tab_side tabSide) 1569{ 1570 tabView->BTabView::SetTabSide(tabSide); 1571} 1572