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