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