1 /* 2 * Copyright 2006-2009, Ingo Weinhold <ingo_weinhold@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "SplitLayout.h" 8 9 #include <new> 10 #include <stdio.h> 11 12 #include <LayoutItem.h> 13 #include <LayoutUtils.h> 14 #include <Message.h> 15 #include <View.h> 16 17 #include "OneElementLayouter.h" 18 #include "SimpleLayouter.h" 19 20 21 using std::nothrow; 22 23 24 // archivng constants 25 namespace { 26 const char* const kItemCollapsibleField = "BSplitLayout:item:collapsible"; 27 const char* const kItemWeightField = "BSplitLayout:item:weight"; 28 const char* const kSpacingField = "BSplitLayout:spacing"; 29 const char* const kSplitterSizeField = "BSplitLayout:splitterSize"; 30 const char* const kIsVerticalField = "BSplitLayout:vertical"; 31 const char* const kInsetsField = "BSplitLayout:insets"; 32 } 33 34 35 class BSplitLayout::ItemLayoutInfo { 36 public: 37 float weight; 38 BRect layoutFrame; 39 BSize min; 40 BSize max; 41 bool isVisible; 42 bool isCollapsible; 43 44 ItemLayoutInfo() 45 : 46 weight(1.0f), 47 layoutFrame(0, 0, -1, -1), 48 min(), 49 max(), 50 isVisible(true), 51 isCollapsible(true) 52 { 53 } 54 }; 55 56 57 class BSplitLayout::ValueRange { 58 public: 59 int32 sumValue; // including spacing 60 int32 previousMin; 61 int32 previousMax; 62 int32 previousSize; 63 int32 nextMin; 64 int32 nextMax; 65 int32 nextSize; 66 }; 67 68 69 class BSplitLayout::SplitterItem : public BLayoutItem { 70 public: 71 SplitterItem(BSplitLayout* layout) 72 : 73 fLayout(layout), 74 fFrame() 75 { 76 } 77 78 79 virtual BSize MinSize() 80 { 81 if (fLayout->Orientation() == B_HORIZONTAL) 82 return BSize(fLayout->SplitterSize() - 1, -1); 83 else 84 return BSize(-1, fLayout->SplitterSize() - 1); 85 } 86 87 virtual BSize MaxSize() 88 { 89 if (fLayout->Orientation() == B_HORIZONTAL) 90 return BSize(fLayout->SplitterSize() - 1, B_SIZE_UNLIMITED); 91 else 92 return BSize(B_SIZE_UNLIMITED, fLayout->SplitterSize() - 1); 93 } 94 95 virtual BSize PreferredSize() 96 { 97 return MinSize(); 98 } 99 100 virtual BAlignment Alignment() 101 { 102 return BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER); 103 } 104 105 virtual void SetExplicitMinSize(BSize size) 106 { 107 // not allowed 108 } 109 110 virtual void SetExplicitMaxSize(BSize size) 111 { 112 // not allowed 113 } 114 115 virtual void SetExplicitPreferredSize(BSize size) 116 { 117 // not allowed 118 } 119 120 virtual void SetExplicitAlignment(BAlignment alignment) 121 { 122 // not allowed 123 } 124 125 virtual bool IsVisible() 126 { 127 return true; 128 } 129 130 virtual void SetVisible(bool visible) 131 { 132 // not allowed 133 } 134 135 136 virtual BRect Frame() 137 { 138 return fFrame; 139 } 140 141 virtual void SetFrame(BRect frame) 142 { 143 fFrame = frame; 144 } 145 146 private: 147 BSplitLayout* fLayout; 148 BRect fFrame; 149 }; 150 151 152 // #pragma mark - 153 154 155 BSplitLayout::BSplitLayout(enum orientation orientation, 156 float spacing) 157 : 158 fOrientation(orientation), 159 fLeftInset(0), 160 fRightInset(0), 161 fTopInset(0), 162 fBottomInset(0), 163 fSplitterSize(6), 164 fSpacing(spacing), 165 166 fSplitterItems(), 167 fVisibleItems(), 168 fMin(), 169 fMax(), 170 fPreferred(), 171 172 fHorizontalLayouter(NULL), 173 fVerticalLayouter(NULL), 174 fHorizontalLayoutInfo(NULL), 175 fVerticalLayoutInfo(NULL), 176 177 fHeightForWidthItems(), 178 fHeightForWidthVerticalLayouter(NULL), 179 fHeightForWidthHorizontalLayoutInfo(NULL), 180 181 fLayoutValid(false), 182 183 fCachedHeightForWidthWidth(-2), 184 fHeightForWidthVerticalLayouterWidth(-2), 185 fCachedMinHeightForWidth(-1), 186 fCachedMaxHeightForWidth(-1), 187 fCachedPreferredHeightForWidth(-1), 188 189 fDraggingStartPoint(), 190 fDraggingStartValue(0), 191 fDraggingCurrentValue(0), 192 fDraggingSplitterIndex(-1) 193 { 194 } 195 196 197 BSplitLayout::BSplitLayout(BMessage* from) 198 : 199 BLayout(BUnarchiver::PrepareArchive(from)), 200 fSplitterItems(), 201 fVisibleItems(), 202 fMin(), 203 fMax(), 204 fPreferred(), 205 206 fHorizontalLayouter(NULL), 207 fVerticalLayouter(NULL), 208 fHorizontalLayoutInfo(NULL), 209 fVerticalLayoutInfo(NULL), 210 211 fHeightForWidthItems(), 212 fHeightForWidthVerticalLayouter(NULL), 213 fHeightForWidthHorizontalLayoutInfo(NULL), 214 215 fLayoutValid(false), 216 217 fCachedHeightForWidthWidth(-2), 218 fHeightForWidthVerticalLayouterWidth(-2), 219 fCachedMinHeightForWidth(-1), 220 fCachedMaxHeightForWidth(-1), 221 fCachedPreferredHeightForWidth(-1), 222 223 fDraggingStartPoint(), 224 fDraggingStartValue(0), 225 fDraggingCurrentValue(0), 226 fDraggingSplitterIndex(-1) 227 { 228 BUnarchiver unarchiver(from); 229 230 bool isVertical; 231 status_t err = from->FindBool(kIsVerticalField, &isVertical); 232 if (err != B_OK) { 233 unarchiver.Finish(err); 234 return; 235 } 236 fOrientation = (isVertical) ? B_VERTICAL : B_HORIZONTAL ; 237 238 BRect insets; 239 err = from->FindRect(kInsetsField, &insets); 240 if (err != B_OK) { 241 unarchiver.Finish(err); 242 return; 243 } 244 SetInsets(insets.left, insets.top, insets.right, insets.bottom); 245 246 err = from->FindFloat(kSplitterSizeField, &fSplitterSize); 247 if (err == B_OK) 248 err = from->FindFloat(kSpacingField, &fSpacing); 249 250 unarchiver.Finish(err); 251 } 252 253 254 BSplitLayout::~BSplitLayout() 255 { 256 } 257 258 259 void 260 BSplitLayout::SetInsets(float left, float top, float right, float bottom) 261 { 262 fLeftInset = left; 263 fTopInset = top; 264 fRightInset = right; 265 fBottomInset = bottom; 266 267 InvalidateLayout(); 268 } 269 270 271 void 272 BSplitLayout::GetInsets(float* left, float* top, float* right, 273 float* bottom) const 274 { 275 if (left) 276 *left = fLeftInset; 277 if (top) 278 *top = fTopInset; 279 if (right) 280 *right = fRightInset; 281 if (bottom) 282 *bottom = fBottomInset; 283 } 284 285 286 float 287 BSplitLayout::Spacing() const 288 { 289 return fSpacing; 290 } 291 292 293 void 294 BSplitLayout::SetSpacing(float spacing) 295 { 296 if (spacing != fSpacing) { 297 fSpacing = spacing; 298 299 InvalidateLayout(); 300 } 301 } 302 303 304 orientation 305 BSplitLayout::Orientation() const 306 { 307 return fOrientation; 308 } 309 310 311 void 312 BSplitLayout::SetOrientation(enum orientation orientation) 313 { 314 if (orientation != fOrientation) { 315 fOrientation = orientation; 316 317 InvalidateLayout(); 318 } 319 } 320 321 322 float 323 BSplitLayout::SplitterSize() const 324 { 325 return fSplitterSize; 326 } 327 328 329 void 330 BSplitLayout::SetSplitterSize(float size) 331 { 332 if (size != fSplitterSize) { 333 fSplitterSize = size; 334 335 InvalidateLayout(); 336 } 337 } 338 339 340 BLayoutItem* 341 BSplitLayout::AddView(BView* child) 342 { 343 return BLayout::AddView(child); 344 } 345 346 347 BLayoutItem* 348 BSplitLayout::AddView(int32 index, BView* child) 349 { 350 return BLayout::AddView(index, child); 351 } 352 353 354 BLayoutItem* 355 BSplitLayout::AddView(BView* child, float weight) 356 { 357 return AddView(-1, child, weight); 358 } 359 360 361 BLayoutItem* 362 BSplitLayout::AddView(int32 index, BView* child, float weight) 363 { 364 BLayoutItem* item = AddView(index, child); 365 if (item) 366 SetItemWeight(item, weight); 367 368 return item; 369 } 370 371 372 bool 373 BSplitLayout::AddItem(BLayoutItem* item) 374 { 375 return BLayout::AddItem(item); 376 } 377 378 379 bool 380 BSplitLayout::AddItem(int32 index, BLayoutItem* item) 381 { 382 return BLayout::AddItem(index, item); 383 } 384 385 386 bool 387 BSplitLayout::AddItem(BLayoutItem* item, float weight) 388 { 389 return AddItem(-1, item, weight); 390 } 391 392 393 bool 394 BSplitLayout::AddItem(int32 index, BLayoutItem* item, float weight) 395 { 396 bool success = AddItem(index, item); 397 if (success) 398 SetItemWeight(item, weight); 399 400 return success; 401 } 402 403 404 float 405 BSplitLayout::ItemWeight(int32 index) const 406 { 407 if (index < 0 || index >= CountItems()) 408 return 0; 409 410 return ItemWeight(ItemAt(index)); 411 } 412 413 414 float 415 BSplitLayout::ItemWeight(BLayoutItem* item) const 416 { 417 if (ItemLayoutInfo* info = _ItemLayoutInfo(item)) 418 return info->weight; 419 return 0; 420 } 421 422 423 void 424 BSplitLayout::SetItemWeight(int32 index, float weight, bool invalidateLayout) 425 { 426 if (index < 0 || index >= CountItems()) 427 return; 428 429 BLayoutItem* item = ItemAt(index); 430 SetItemWeight(item, weight); 431 432 if (fHorizontalLayouter) { 433 int32 visibleIndex = fVisibleItems.IndexOf(item); 434 if (visibleIndex >= 0) { 435 if (fOrientation == B_HORIZONTAL) 436 fHorizontalLayouter->SetWeight(visibleIndex, weight); 437 else 438 fVerticalLayouter->SetWeight(visibleIndex, weight); 439 } 440 } 441 442 if (invalidateLayout) 443 InvalidateLayout(); 444 } 445 446 447 void 448 BSplitLayout::SetItemWeight(BLayoutItem* item, float weight) 449 { 450 if (ItemLayoutInfo* info = _ItemLayoutInfo(item)) 451 info->weight = weight; 452 } 453 454 455 void 456 BSplitLayout::SetCollapsible(bool collapsible) 457 { 458 SetCollapsible(0, CountItems() - 1, collapsible); 459 } 460 461 462 void 463 BSplitLayout::SetCollapsible(int32 index, bool collapsible) 464 { 465 SetCollapsible(index, index, collapsible); 466 } 467 468 469 void 470 BSplitLayout::SetCollapsible(int32 first, int32 last, bool collapsible) 471 { 472 if (first < 0) 473 first = 0; 474 if (last < 0 || last > CountItems()) 475 last = CountItems() - 1; 476 477 for (int32 i = first; i <= last; i++) 478 _ItemLayoutInfo(ItemAt(i))->isCollapsible = collapsible; 479 } 480 481 482 BSize 483 BSplitLayout::MinSize() 484 { 485 _ValidateMinMax(); 486 487 return _AddInsets(fMin); 488 } 489 490 491 BSize 492 BSplitLayout::MaxSize() 493 { 494 _ValidateMinMax(); 495 496 return _AddInsets(fMax); 497 } 498 499 500 BSize 501 BSplitLayout::PreferredSize() 502 { 503 _ValidateMinMax(); 504 505 return _AddInsets(fPreferred); 506 } 507 508 509 BAlignment 510 BSplitLayout::Alignment() 511 { 512 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 513 } 514 515 516 bool 517 BSplitLayout::HasHeightForWidth() 518 { 519 _ValidateMinMax(); 520 521 return !fHeightForWidthItems.IsEmpty(); 522 } 523 524 525 void 526 BSplitLayout::GetHeightForWidth(float width, float* min, float* max, 527 float* preferred) 528 { 529 if (!HasHeightForWidth()) 530 return; 531 532 float innerWidth = _SubtractInsets(BSize(width, 0)).width; 533 _InternalGetHeightForWidth(innerWidth, false, min, max, preferred); 534 _AddInsets(min, max, preferred); 535 } 536 537 538 void 539 BSplitLayout::InvalidateLayout() 540 { 541 _InvalidateLayout(true); 542 } 543 544 545 void 546 BSplitLayout::LayoutView() 547 { 548 _ValidateMinMax(); 549 550 // layout the elements 551 BSize size = _SubtractInsets(View()->Bounds().Size()); 552 fHorizontalLayouter->Layout(fHorizontalLayoutInfo, size.width); 553 554 Layouter* verticalLayouter; 555 if (HasHeightForWidth()) { 556 float minHeight, maxHeight, preferredHeight; 557 _InternalGetHeightForWidth(size.width, true, &minHeight, &maxHeight, 558 &preferredHeight); 559 size.height = max_c(size.height, minHeight); 560 verticalLayouter = fHeightForWidthVerticalLayouter; 561 } else 562 verticalLayouter = fVerticalLayouter; 563 564 verticalLayouter->Layout(fVerticalLayoutInfo, size.height); 565 566 float xOffset = fLeftInset; 567 float yOffset = fTopInset; 568 float splitterWidth = 0; // pixel counts, no distances 569 float splitterHeight = 0; // 570 float xSpacing = 0; 571 float ySpacing = 0; 572 if (fOrientation == B_HORIZONTAL) { 573 splitterWidth = fSplitterSize; 574 splitterHeight = size.height + 1; 575 xSpacing = fSpacing; 576 } else { 577 splitterWidth = size.width + 1; 578 splitterHeight = fSplitterSize; 579 ySpacing = fSpacing; 580 } 581 582 int itemCount = CountItems(); 583 for (int i = 0; i < itemCount; i++) { 584 // layout the splitter 585 if (i > 0) { 586 SplitterItem* splitterItem = _SplitterItemAt(i - 1); 587 588 _LayoutItem(splitterItem, BRect(xOffset, yOffset, 589 xOffset + splitterWidth - 1, yOffset + splitterHeight - 1), 590 true); 591 592 if (fOrientation == B_HORIZONTAL) 593 xOffset += splitterWidth + xSpacing; 594 else 595 yOffset += splitterHeight + ySpacing; 596 } 597 598 // layout the item 599 BLayoutItem* item = ItemAt(i); 600 int32 visibleIndex = fVisibleItems.IndexOf(item); 601 if (visibleIndex < 0) { 602 _LayoutItem(item, BRect(), false); 603 continue; 604 } 605 606 // get the dimensions of the item 607 float width = fHorizontalLayoutInfo->ElementSize(visibleIndex); 608 float height = fVerticalLayoutInfo->ElementSize(visibleIndex); 609 610 // place the component 611 _LayoutItem(item, BRect(xOffset, yOffset, xOffset + width, 612 yOffset + height), true); 613 614 if (fOrientation == B_HORIZONTAL) 615 xOffset += width + xSpacing + 1; 616 else 617 yOffset += height + ySpacing + 1; 618 } 619 620 fLayoutValid = true; 621 } 622 623 624 BRect 625 BSplitLayout::SplitterItemFrame(int32 index) const 626 { 627 if (SplitterItem* item = _SplitterItemAt(index)) 628 return item->Frame(); 629 return BRect(); 630 } 631 632 633 bool 634 BSplitLayout::IsAboveSplitter(const BPoint& point) const 635 { 636 return _SplitterItemAt(point) != NULL; 637 } 638 639 640 bool 641 BSplitLayout::StartDraggingSplitter(BPoint point) 642 { 643 StopDraggingSplitter(); 644 645 // Layout must be valid. Bail out, if it isn't. 646 if (!fLayoutValid) 647 return false; 648 649 // Things shouldn't be draggable, if we have a >= max layout. 650 BSize size = _SubtractInsets(View()->Frame().Size()); 651 if ((fOrientation == B_HORIZONTAL && size.width >= fMax.width) 652 || (fOrientation == B_VERTICAL && size.height >= fMax.height)) { 653 return false; 654 } 655 656 int32 index = -1; 657 if (_SplitterItemAt(point, &index) != NULL) { 658 fDraggingStartPoint = View()->ConvertToScreen(point); 659 fDraggingStartValue = _SplitterValue(index); 660 fDraggingCurrentValue = fDraggingStartValue; 661 fDraggingSplitterIndex = index; 662 663 return true; 664 } 665 666 return false; 667 } 668 669 670 bool 671 BSplitLayout::DragSplitter(BPoint point) 672 { 673 if (fDraggingSplitterIndex < 0) 674 return false; 675 676 point = View()->ConvertToScreen(point); 677 678 int32 valueDiff; 679 if (fOrientation == B_HORIZONTAL) 680 valueDiff = int32(point.x - fDraggingStartPoint.x); 681 else 682 valueDiff = int32(point.y - fDraggingStartPoint.y); 683 684 return _SetSplitterValue(fDraggingSplitterIndex, 685 fDraggingStartValue + valueDiff); 686 } 687 688 689 bool 690 BSplitLayout::StopDraggingSplitter() 691 { 692 if (fDraggingSplitterIndex < 0) 693 return false; 694 695 // update the item weights 696 _UpdateSplitterWeights(); 697 698 fDraggingSplitterIndex = -1; 699 700 return true; 701 } 702 703 704 int32 705 BSplitLayout::DraggedSplitter() const 706 { 707 return fDraggingSplitterIndex; 708 } 709 710 711 status_t 712 BSplitLayout::Archive(BMessage* into, bool deep) const 713 { 714 BArchiver archiver(into); 715 status_t err = BLayout::Archive(into, deep); 716 717 if (err == B_OK) 718 err = into->AddBool(kIsVerticalField, fOrientation == B_VERTICAL); 719 720 if (err == B_OK) { 721 BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset); 722 err = into->AddRect(kInsetsField, insets); 723 } 724 725 if (err == B_OK) 726 err = into->AddFloat(kSplitterSizeField, fSplitterSize); 727 728 if (err == B_OK) 729 err = into->AddFloat(kSpacingField, fSpacing); 730 731 return archiver.Finish(err); 732 } 733 734 735 BArchivable* 736 BSplitLayout::Instantiate(BMessage* from) 737 { 738 if (validate_instantiation(from, "BSplitLayout")) 739 return new(std::nothrow) BSplitLayout(from); 740 return NULL; 741 } 742 743 744 status_t 745 BSplitLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const 746 { 747 ItemLayoutInfo* info = _ItemLayoutInfo(item); 748 749 status_t err = into->AddFloat(kItemWeightField, info->weight); 750 if (err == B_OK) 751 err = into->AddBool(kItemCollapsibleField, info->isCollapsible); 752 753 return err; 754 } 755 756 757 status_t 758 BSplitLayout::ItemUnarchived(const BMessage* from, 759 BLayoutItem* item, int32 index) 760 { 761 ItemLayoutInfo* info = _ItemLayoutInfo(item); 762 status_t err = from->FindFloat(kItemWeightField, index, &info->weight); 763 764 if (err == B_OK) { 765 bool* collapsible = &info->isCollapsible; 766 err = from->FindBool(kItemCollapsibleField, index, collapsible); 767 } 768 return err; 769 } 770 771 772 bool 773 BSplitLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 774 { 775 ItemLayoutInfo* itemInfo = new(nothrow) ItemLayoutInfo(); 776 if (!itemInfo) 777 return false; 778 779 if (CountItems() > 1) { 780 SplitterItem* splitter = new(nothrow) SplitterItem(this); 781 ItemLayoutInfo* splitterInfo = new(nothrow) ItemLayoutInfo(); 782 if (!splitter || !splitterInfo || !fSplitterItems.AddItem(splitter)) { 783 delete itemInfo; 784 delete splitter; 785 delete splitterInfo; 786 return false; 787 } 788 splitter->SetLayoutData(splitterInfo); 789 SetItemWeight(splitter, 0); 790 } 791 792 item->SetLayoutData(itemInfo); 793 SetItemWeight(item, 1); 794 return true; 795 } 796 797 798 void 799 BSplitLayout::ItemRemoved(BLayoutItem* item, int32 atIndex) 800 { 801 if (fSplitterItems.CountItems() > 0) { 802 SplitterItem* splitterItem = (SplitterItem*)fSplitterItems.RemoveItem( 803 fSplitterItems.CountItems() - 1); 804 delete _ItemLayoutInfo(splitterItem); 805 delete splitterItem; 806 } 807 808 delete _ItemLayoutInfo(item); 809 item->SetLayoutData(NULL); 810 } 811 812 813 void 814 BSplitLayout::_InvalidateLayout(bool invalidateView) 815 { 816 if (invalidateView) 817 BLayout::InvalidateLayout(); 818 819 delete fHorizontalLayouter; 820 delete fVerticalLayouter; 821 delete fHorizontalLayoutInfo; 822 delete fVerticalLayoutInfo; 823 824 fHorizontalLayouter = NULL; 825 fVerticalLayouter = NULL; 826 fHorizontalLayoutInfo = NULL; 827 fVerticalLayoutInfo = NULL; 828 829 _InvalidateCachedHeightForWidth(); 830 831 fLayoutValid = false; 832 } 833 834 835 void 836 BSplitLayout::_InvalidateCachedHeightForWidth() 837 { 838 delete fHeightForWidthVerticalLayouter; 839 delete fHeightForWidthHorizontalLayoutInfo; 840 841 fHeightForWidthVerticalLayouter = NULL; 842 fHeightForWidthHorizontalLayoutInfo = NULL; 843 844 fCachedHeightForWidthWidth = -2; 845 fHeightForWidthVerticalLayouterWidth = -2; 846 } 847 848 849 BSplitLayout::SplitterItem* 850 BSplitLayout::_SplitterItemAt(const BPoint& point, int32* index) const 851 { 852 int32 splitterCount = fSplitterItems.CountItems(); 853 for (int32 i = 0; i < splitterCount; i++) { 854 SplitterItem* splitItem = _SplitterItemAt(i); 855 BRect frame = splitItem->Frame(); 856 if (frame.Contains(point)) { 857 if (index != NULL) 858 *index = i; 859 return splitItem; 860 } 861 } 862 return NULL; 863 } 864 865 866 BSplitLayout::SplitterItem* 867 BSplitLayout::_SplitterItemAt(int32 index) const 868 { 869 return (SplitterItem*)fSplitterItems.ItemAt(index); 870 } 871 872 873 void 874 BSplitLayout::_GetSplitterValueRange(int32 index, ValueRange& range) 875 { 876 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(ItemAt(index)); 877 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(ItemAt(index + 1)); 878 if (fOrientation == B_HORIZONTAL) { 879 range.previousMin = (int32)previousInfo->min.width + 1; 880 range.previousMax = (int32)previousInfo->max.width + 1; 881 range.previousSize = previousInfo->layoutFrame.IntegerWidth() + 1; 882 range.nextMin = (int32)nextInfo->min.width + 1; 883 range.nextMax = (int32)nextInfo->max.width + 1; 884 range.nextSize = nextInfo->layoutFrame.IntegerWidth() + 1; 885 } else { 886 range.previousMin = (int32)previousInfo->min.height + 1; 887 range.previousMax = (int32)previousInfo->max.height + 1; 888 range.previousSize = previousInfo->layoutFrame.IntegerHeight() + 1; 889 range.nextMin = (int32)nextInfo->min.height + 1; 890 range.nextMax = (int32)nextInfo->max.height + 1; 891 range.nextSize = (int32)nextInfo->layoutFrame.IntegerHeight() + 1; 892 } 893 894 range.sumValue = range.previousSize + range.nextSize; 895 if (previousInfo->isVisible) 896 range.sumValue += (int32)fSpacing; 897 if (nextInfo->isVisible) 898 range.sumValue += (int32)fSpacing; 899 } 900 901 902 int32 903 BSplitLayout::_SplitterValue(int32 index) const 904 { 905 ItemLayoutInfo* info = _ItemLayoutInfo(ItemAt(index)); 906 if (info && info->isVisible) { 907 if (fOrientation == B_HORIZONTAL) 908 return info->layoutFrame.IntegerWidth() + 1 + (int32)fSpacing; 909 else 910 return info->layoutFrame.IntegerHeight() + 1 + (int32)fSpacing; 911 } else 912 return 0; 913 } 914 915 916 void 917 BSplitLayout::_LayoutItem(BLayoutItem* item, BRect frame, bool visible) 918 { 919 // update the layout frame 920 ItemLayoutInfo* info = _ItemLayoutInfo(item); 921 info->isVisible = visible; 922 if (visible) 923 info->layoutFrame = frame; 924 else 925 info->layoutFrame = BRect(0, 0, -1, -1); 926 927 // update min/max 928 info->min = item->MinSize(); 929 info->max = item->MaxSize(); 930 931 if (item->HasHeightForWidth()) { 932 BSize size = _SubtractInsets(View()->Frame().Size()); 933 float minHeight, maxHeight; 934 item->GetHeightForWidth(size.width, &minHeight, &maxHeight, NULL); 935 info->min.height = max_c(info->min.height, minHeight); 936 info->max.height = min_c(info->max.height, maxHeight); 937 } 938 939 // layout the item 940 if (visible) 941 item->AlignInFrame(frame); 942 } 943 944 945 void 946 BSplitLayout::_LayoutItem(BLayoutItem* item, ItemLayoutInfo* info) 947 { 948 // update the visibility of the item 949 bool isVisible = item->IsVisible(); 950 bool visibilityChanged = (info->isVisible != isVisible); 951 if (visibilityChanged) 952 item->SetVisible(info->isVisible); 953 954 // nothing more to do, if the item is not visible 955 if (!info->isVisible) 956 return; 957 958 item->AlignInFrame(info->layoutFrame); 959 960 // if the item became visible, we need to update its internal layout 961 if (visibilityChanged) { 962 if (BView* itemView = item->View()) 963 itemView->Layout(false); 964 } 965 } 966 967 968 bool 969 BSplitLayout::_SetSplitterValue(int32 index, int32 value) 970 { 971 // if both items are collapsed, nothing can be dragged 972 BLayoutItem* previousItem = ItemAt(index); 973 BLayoutItem* nextItem = ItemAt(index + 1); 974 ItemLayoutInfo* previousInfo = _ItemLayoutInfo(previousItem); 975 ItemLayoutInfo* nextInfo = _ItemLayoutInfo(nextItem); 976 ItemLayoutInfo* splitterInfo = _ItemLayoutInfo(_SplitterItemAt(index)); 977 bool previousVisible = previousInfo->isVisible; 978 bool nextVisible = nextInfo->isVisible; 979 if (!previousVisible && !nextVisible) 980 return false; 981 982 ValueRange range; 983 _GetSplitterValueRange(index, range); 984 985 value = max_c(min_c(value, range.sumValue), -(int32)fSpacing); 986 987 int32 previousSize = value - (int32)fSpacing; 988 int32 nextSize = range.sumValue - value - (int32)fSpacing; 989 990 // Note: While this collapsed-check is mathmatically correct (i.e. we 991 // collapse an item, if it would become smaller than half its minimum 992 // size), we might want to change it, since for the user it looks like 993 // collapsing happens earlier. The reason being that the only visual mark 994 // the user has is the mouse cursor which indeed hasn't crossed the middle 995 // of the item yet. 996 bool previousCollapsed = (previousSize <= range.previousMin / 2) 997 && previousInfo->isCollapsible; 998 bool nextCollapsed = (nextSize <= range.nextMin / 2) 999 && nextInfo->isCollapsible; 1000 if (previousCollapsed && nextCollapsed) { 1001 // we cannot collapse both items; we have to decide for one 1002 if (previousSize < nextSize) { 1003 // collapse previous 1004 nextCollapsed = false; 1005 nextSize = range.sumValue - (int32)fSpacing; 1006 } else { 1007 // collapse next 1008 previousCollapsed = false; 1009 previousSize = range.sumValue - (int32)fSpacing; 1010 } 1011 } 1012 1013 if (previousCollapsed || nextCollapsed) { 1014 // one collapsed item -- check whether that violates the constraints 1015 // of the other one 1016 int32 availableSpace = range.sumValue - (int32)fSpacing; 1017 if (previousCollapsed) { 1018 if (availableSpace < range.nextMin 1019 || availableSpace > range.nextMax) { 1020 // we cannot collapse the previous item 1021 previousCollapsed = false; 1022 } 1023 } else { 1024 if (availableSpace < range.previousMin 1025 || availableSpace > range.previousMax) { 1026 // we cannot collapse the next item 1027 nextCollapsed = false; 1028 } 1029 } 1030 } 1031 1032 if (!(previousCollapsed || nextCollapsed)) { 1033 // no collapsed item -- check whether there is a close solution 1034 previousSize = value - (int32)fSpacing; 1035 nextSize = range.sumValue - value - (int32)fSpacing; 1036 1037 if (range.previousMin + range.nextMin + 2 * fSpacing > range.sumValue) { 1038 // we don't have enough space to uncollapse both items 1039 int32 availableSpace = range.sumValue - (int32)fSpacing; 1040 if (previousSize < nextSize && availableSpace >= range.nextMin 1041 && availableSpace <= range.nextMax 1042 && previousInfo->isCollapsible) { 1043 previousCollapsed = true; 1044 } else if (availableSpace >= range.previousMin 1045 && availableSpace <= range.previousMax 1046 && nextInfo->isCollapsible) { 1047 nextCollapsed = true; 1048 } else if (availableSpace >= range.nextMin 1049 && availableSpace <= range.nextMax 1050 && previousInfo->isCollapsible) { 1051 previousCollapsed = true; 1052 } else { 1053 if (previousSize < nextSize && previousInfo->isCollapsible) { 1054 previousCollapsed = true; 1055 } else if (nextInfo->isCollapsible) { 1056 nextCollapsed = true; 1057 } else { 1058 // Neither item is collapsible although there's not enough 1059 // space: Give them both their minimum size. 1060 previousSize = range.previousMin; 1061 nextSize = range.nextMin; 1062 } 1063 } 1064 1065 } else { 1066 // there is enough space for both items 1067 // make sure the min constraints are satisfied 1068 if (previousSize < range.previousMin) { 1069 previousSize = range.previousMin; 1070 nextSize = range.sumValue - previousSize - 2 * (int32)fSpacing; 1071 } else if (nextSize < range.nextMin) { 1072 nextSize = range.nextMin; 1073 previousSize = range.sumValue - nextSize - 2 * (int32)fSpacing; 1074 } 1075 1076 // if we can, also satisfy the max constraints 1077 if (range.previousMax + range.nextMax + 2 * (int32)fSpacing 1078 >= range.sumValue) { 1079 if (previousSize > range.previousMax) { 1080 previousSize = range.previousMax; 1081 nextSize = range.sumValue - previousSize 1082 - 2 * (int32)fSpacing; 1083 } else if (nextSize > range.nextMax) { 1084 nextSize = range.nextMax; 1085 previousSize = range.sumValue - nextSize 1086 - 2 * (int32)fSpacing; 1087 } 1088 } 1089 } 1090 } 1091 1092 // compute the size for one collapsed item; for none collapsed item we 1093 // already have correct values 1094 if (previousCollapsed || nextCollapsed) { 1095 int32 availableSpace = range.sumValue - (int32)fSpacing; 1096 if (previousCollapsed) { 1097 previousSize = 0; 1098 nextSize = availableSpace; 1099 } else { 1100 previousSize = availableSpace; 1101 nextSize = 0; 1102 } 1103 } 1104 1105 int32 newValue = previousSize + (previousCollapsed ? 0 : (int32)fSpacing); 1106 if (newValue == fDraggingCurrentValue) { 1107 // nothing changed 1108 return false; 1109 } 1110 1111 // something changed: we need to recompute the layout 1112 int32 baseOffset = -fDraggingCurrentValue; 1113 // offset to the current splitter position 1114 int32 splitterOffset = baseOffset + newValue; 1115 int32 nextOffset = splitterOffset + (int32)fSplitterSize + (int32)fSpacing; 1116 1117 BRect splitterFrame(splitterInfo->layoutFrame); 1118 if (fOrientation == B_HORIZONTAL) { 1119 // horizontal layout 1120 // previous item 1121 float left = splitterFrame.left + baseOffset; 1122 previousInfo->layoutFrame.Set( 1123 left, 1124 splitterFrame.top, 1125 left + previousSize - 1, 1126 splitterFrame.bottom); 1127 1128 // next item 1129 left = splitterFrame.left + nextOffset; 1130 nextInfo->layoutFrame.Set( 1131 left, 1132 splitterFrame.top, 1133 left + nextSize - 1, 1134 splitterFrame.bottom); 1135 1136 // splitter 1137 splitterInfo->layoutFrame.left += splitterOffset; 1138 splitterInfo->layoutFrame.right += splitterOffset; 1139 } else { 1140 // vertical layout 1141 // previous item 1142 float top = splitterFrame.top + baseOffset; 1143 previousInfo->layoutFrame.Set( 1144 splitterFrame.left, 1145 top, 1146 splitterFrame.right, 1147 top + previousSize - 1); 1148 1149 // next item 1150 top = splitterFrame.top + nextOffset; 1151 nextInfo->layoutFrame.Set( 1152 splitterFrame.left, 1153 top, 1154 splitterFrame.right, 1155 top + nextSize - 1); 1156 1157 // splitter 1158 splitterInfo->layoutFrame.top += splitterOffset; 1159 splitterInfo->layoutFrame.bottom += splitterOffset; 1160 } 1161 1162 previousInfo->isVisible = !previousCollapsed; 1163 nextInfo->isVisible = !nextCollapsed; 1164 1165 bool heightForWidth = (fOrientation == B_HORIZONTAL && HasHeightForWidth()); 1166 1167 // If the item visibility is to be changed, we need to update the splitter 1168 // values now, since the visibility change will cause an invalidation. 1169 if (previousVisible != previousInfo->isVisible 1170 || nextVisible != nextInfo->isVisible || heightForWidth) { 1171 _UpdateSplitterWeights(); 1172 } 1173 1174 // If we have height for width items, we need to invalidate the previous 1175 // and the next item. Actually we would only need to invalidate height for 1176 // width items, but since non height for width items might be aligned with 1177 // height for width items, we need to trigger a layout that creates a 1178 // context that spans all aligned items. 1179 // We invalidate already here, so that changing the items' size won't cause 1180 // an immediate relayout. 1181 if (heightForWidth) { 1182 previousItem->InvalidateLayout(); 1183 nextItem->InvalidateLayout(); 1184 } 1185 1186 // do the layout 1187 _LayoutItem(previousItem, previousInfo); 1188 _LayoutItem(_SplitterItemAt(index), splitterInfo); 1189 _LayoutItem(nextItem, nextInfo); 1190 1191 fDraggingCurrentValue = newValue; 1192 1193 return true; 1194 } 1195 1196 1197 BSplitLayout::ItemLayoutInfo* 1198 BSplitLayout::_ItemLayoutInfo(BLayoutItem* item) const 1199 { 1200 return (ItemLayoutInfo*)item->LayoutData(); 1201 } 1202 1203 1204 void 1205 BSplitLayout::_UpdateSplitterWeights() 1206 { 1207 int32 count = CountItems(); 1208 for (int32 i = 0; i < count; i++) { 1209 float weight; 1210 if (fOrientation == B_HORIZONTAL) 1211 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Width() + 1; 1212 else 1213 weight = _ItemLayoutInfo(ItemAt(i))->layoutFrame.Height() + 1; 1214 1215 SetItemWeight(i, weight, false); 1216 } 1217 1218 // Just updating the splitter weights is fine in principle. The next 1219 // LayoutView() will use the correct values. But, if our orientation is 1220 // vertical, the cached height for width info needs to be flushed, or the 1221 // obsolete cached values will be used. 1222 if (fOrientation == B_VERTICAL) 1223 _InvalidateCachedHeightForWidth(); 1224 } 1225 1226 1227 void 1228 BSplitLayout::_ValidateMinMax() 1229 { 1230 if (fHorizontalLayouter != NULL) 1231 return; 1232 1233 fLayoutValid = false; 1234 1235 fVisibleItems.MakeEmpty(); 1236 fHeightForWidthItems.MakeEmpty(); 1237 1238 _InvalidateCachedHeightForWidth(); 1239 1240 // filter the visible items 1241 int32 itemCount = CountItems(); 1242 for (int32 i = 0; i < itemCount; i++) { 1243 BLayoutItem* item = ItemAt(i); 1244 if (item->IsVisible()) 1245 fVisibleItems.AddItem(item); 1246 1247 // Add "height for width" items even, if they aren't visible. Otherwise 1248 // we may get our parent into trouble, since we could change from 1249 // "height for width" to "not height for width". 1250 if (item->HasHeightForWidth()) 1251 fHeightForWidthItems.AddItem(item); 1252 } 1253 itemCount = fVisibleItems.CountItems(); 1254 1255 // create the layouters 1256 Layouter* itemLayouter = new SimpleLayouter(itemCount, 0); 1257 1258 if (fOrientation == B_HORIZONTAL) { 1259 fHorizontalLayouter = itemLayouter; 1260 fVerticalLayouter = new OneElementLayouter(); 1261 } else { 1262 fHorizontalLayouter = new OneElementLayouter(); 1263 fVerticalLayouter = itemLayouter; 1264 } 1265 1266 // tell the layouters about our constraints 1267 if (itemCount > 0) { 1268 for (int32 i = 0; i < itemCount; i++) { 1269 BLayoutItem* item = (BLayoutItem*)fVisibleItems.ItemAt(i); 1270 BSize min = item->MinSize(); 1271 BSize max = item->MaxSize(); 1272 BSize preferred = item->PreferredSize(); 1273 1274 fHorizontalLayouter->AddConstraints(i, 1, min.width, max.width, 1275 preferred.width); 1276 fVerticalLayouter->AddConstraints(i, 1, min.height, max.height, 1277 preferred.height); 1278 1279 float weight = ItemWeight(item); 1280 fHorizontalLayouter->SetWeight(i, weight); 1281 fVerticalLayouter->SetWeight(i, weight); 1282 } 1283 } 1284 1285 fMin.width = fHorizontalLayouter->MinSize(); 1286 fMin.height = fVerticalLayouter->MinSize(); 1287 fMax.width = fHorizontalLayouter->MaxSize(); 1288 fMax.height = fVerticalLayouter->MaxSize(); 1289 fPreferred.width = fHorizontalLayouter->PreferredSize(); 1290 fPreferred.height = fVerticalLayouter->PreferredSize(); 1291 1292 fHorizontalLayoutInfo = fHorizontalLayouter->CreateLayoutInfo(); 1293 if (fHeightForWidthItems.IsEmpty()) 1294 fVerticalLayoutInfo = fVerticalLayouter->CreateLayoutInfo(); 1295 1296 if (BView* view = View()) 1297 view->ResetLayoutInvalidation(); 1298 } 1299 1300 1301 void 1302 BSplitLayout::_InternalGetHeightForWidth(float width, bool realLayout, 1303 float* minHeight, float* maxHeight, float* preferredHeight) 1304 { 1305 if ((realLayout && fHeightForWidthVerticalLayouterWidth != width) 1306 || (!realLayout && fCachedHeightForWidthWidth != width)) { 1307 // The general strategy is to clone the vertical layouter, which only 1308 // knows the general min/max constraints, do a horizontal layout for the 1309 // given width, and add the children's height for width constraints to 1310 // the cloned vertical layouter. If this method is invoked internally, 1311 // we keep the cloned vertical layouter, for it will be used for doing 1312 // the layout. Otherwise we just drop it after we've got the height for 1313 // width info. 1314 1315 // clone the vertical layouter and get the horizontal layout info to be used 1316 LayoutInfo* horizontalLayoutInfo = NULL; 1317 Layouter* verticalLayouter = fVerticalLayouter->CloneLayouter(); 1318 if (realLayout) { 1319 horizontalLayoutInfo = fHorizontalLayoutInfo; 1320 delete fHeightForWidthVerticalLayouter; 1321 fHeightForWidthVerticalLayouter = verticalLayouter; 1322 delete fVerticalLayoutInfo; 1323 fVerticalLayoutInfo = verticalLayouter->CreateLayoutInfo(); 1324 fHeightForWidthVerticalLayouterWidth = width; 1325 } else { 1326 if (fHeightForWidthHorizontalLayoutInfo == NULL) { 1327 delete fHeightForWidthHorizontalLayoutInfo; 1328 fHeightForWidthHorizontalLayoutInfo 1329 = fHorizontalLayouter->CreateLayoutInfo(); 1330 } 1331 horizontalLayoutInfo = fHeightForWidthHorizontalLayoutInfo; 1332 } 1333 1334 // do the horizontal layout (already done when doing this for the real 1335 // layout) 1336 if (!realLayout) 1337 fHorizontalLayouter->Layout(horizontalLayoutInfo, width); 1338 1339 // add the children's height for width constraints 1340 int32 count = fHeightForWidthItems.CountItems(); 1341 for (int32 i = 0; i < count; i++) { 1342 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i); 1343 int32 index = fVisibleItems.IndexOf(item); 1344 if (index >= 0) { 1345 float itemMinHeight, itemMaxHeight, itemPreferredHeight; 1346 item->GetHeightForWidth( 1347 horizontalLayoutInfo->ElementSize(index), 1348 &itemMinHeight, &itemMaxHeight, &itemPreferredHeight); 1349 verticalLayouter->AddConstraints(index, 1, itemMinHeight, 1350 itemMaxHeight, itemPreferredHeight); 1351 } 1352 } 1353 1354 // get the height for width info 1355 fCachedHeightForWidthWidth = width; 1356 fCachedMinHeightForWidth = verticalLayouter->MinSize(); 1357 fCachedMaxHeightForWidth = verticalLayouter->MaxSize(); 1358 fCachedPreferredHeightForWidth = verticalLayouter->PreferredSize(); 1359 } 1360 1361 if (minHeight) 1362 *minHeight = fCachedMinHeightForWidth; 1363 if (maxHeight) 1364 *maxHeight = fCachedMaxHeightForWidth; 1365 if (preferredHeight) 1366 *preferredHeight = fCachedPreferredHeightForWidth; 1367 } 1368 1369 1370 float 1371 BSplitLayout::_SplitterSpace() const 1372 { 1373 int32 splitters = fSplitterItems.CountItems(); 1374 float space = 0; 1375 if (splitters > 0) { 1376 space = (fVisibleItems.CountItems() + splitters - 1) * fSpacing 1377 + splitters * fSplitterSize; 1378 } 1379 1380 return space; 1381 } 1382 1383 1384 BSize 1385 BSplitLayout::_AddInsets(BSize size) 1386 { 1387 size.width = BLayoutUtils::AddDistances(size.width, 1388 fLeftInset + fRightInset - 1); 1389 size.height = BLayoutUtils::AddDistances(size.height, 1390 fTopInset + fBottomInset - 1); 1391 1392 float spacing = _SplitterSpace(); 1393 if (fOrientation == B_HORIZONTAL) 1394 size.width = BLayoutUtils::AddDistances(size.width, spacing - 1); 1395 else 1396 size.height = BLayoutUtils::AddDistances(size.height, spacing - 1); 1397 1398 return size; 1399 } 1400 1401 1402 void 1403 BSplitLayout::_AddInsets(float* minHeight, float* maxHeight, 1404 float* preferredHeight) 1405 { 1406 float insets = fTopInset + fBottomInset - 1; 1407 if (fOrientation == B_VERTICAL) 1408 insets += _SplitterSpace(); 1409 if (minHeight) 1410 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets); 1411 if (maxHeight) 1412 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets); 1413 if (preferredHeight) 1414 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets); 1415 } 1416 1417 1418 BSize 1419 BSplitLayout::_SubtractInsets(BSize size) 1420 { 1421 size.width = BLayoutUtils::SubtractDistances(size.width, 1422 fLeftInset + fRightInset - 1); 1423 size.height = BLayoutUtils::SubtractDistances(size.height, 1424 fTopInset + fBottomInset - 1); 1425 1426 float spacing = _SplitterSpace(); 1427 if (fOrientation == B_HORIZONTAL) 1428 size.width = BLayoutUtils::SubtractDistances(size.width, spacing - 1); 1429 else 1430 size.height = BLayoutUtils::SubtractDistances(size.height, spacing - 1); 1431 1432 return size; 1433 } 1434