1 /* 2 * Copyright 2006-2010, Ingo Weinhold <ingo_weinhold@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 7 #include <TwoDimensionalLayout.h> 8 9 #include <stdio.h> 10 11 #include <ControlLook.h> 12 #include <LayoutContext.h> 13 #include <LayoutItem.h> 14 #include <LayoutUtils.h> 15 #include <List.h> 16 #include <Message.h> 17 #include <View.h> 18 19 #include <Referenceable.h> 20 21 #include "CollapsingLayouter.h" 22 23 24 25 26 // Some words of explanation: 27 // 28 // This class is the base class for BLayouts that organize their items 29 // on a grid, with each item covering one or more grid cells (always a 30 // rectangular area). The derived classes only need to implement the 31 // hooks reporting the constraints for the items and additional constraints 32 // for the rows and columns. This class does all the layouting. 33 // 34 // The basic idea of the layout process is simple. The horizontal and the 35 // vertical dimensions are laid out independently and the items are set to the 36 // resulting locations and sizes. The "height for width" feature makes the 37 // height depend on the width, which makes things a bit more complicated. 38 // The horizontal dimension must be laid out first and and the results are 39 // fed into the vertical layout process. 40 // 41 // The AlignLayoutWith() feature, which allows to align layouts for different 42 // views with each other, causes the need for the three inner *Layouter classes. 43 // For each set of layouts aligned with each other with respect to one 44 // dimension that dimension must be laid out together. The class responsible 45 // is CompoundLayouter; one instance exists per such set. The derived class 46 // VerticalCompoundLayouter is a specialization for the vertical dimension 47 // which additionally takes care of the "height for width" feature. Per layout 48 // a single LocalLayouter exists, which comprises the required glue layout 49 // code and serves as a proxy for the layout, providing service methods 50 // needed by the CompoundLayouter. 51 52 // TODO: Check for memory leaks! 53 54 //#define DEBUG_LAYOUT 55 56 // CompoundLayouter 57 class BTwoDimensionalLayout::CompoundLayouter : public BReferenceable { 58 public: 59 CompoundLayouter(enum orientation orientation); 60 virtual ~CompoundLayouter(); 61 62 orientation Orientation(); 63 64 virtual Layouter* GetLayouter(bool minMax); 65 66 LayoutInfo* GetLayoutInfo(); 67 68 void AddLocalLayouter(LocalLayouter* localLayouter); 69 void RemoveLocalLayouter( 70 LocalLayouter* localLayouter); 71 72 status_t AddAlignedLayoutsToArchive(BArchiver* archiver, 73 LocalLayouter* requestedBy); 74 75 void AbsorbCompoundLayouter(CompoundLayouter* other); 76 77 virtual void InvalidateLayout(); 78 bool IsMinMaxValid(); 79 void ValidateMinMax(); 80 void Layout(float size, LocalLayouter* localLayouter, 81 BLayoutContext* context); 82 83 protected: 84 virtual void DoLayout(float size, 85 LocalLayouter* localLayouter, 86 BLayoutContext* context); 87 88 Layouter* fLayouter; 89 LayoutInfo* fLayoutInfo; 90 orientation fOrientation; 91 BList fLocalLayouters; 92 BLayoutContext* fLayoutContext; 93 float fLastLayoutSize; 94 95 void _PrepareItems(); 96 97 int32 _CountElements(); 98 bool _HasMultiElementItems(); 99 100 void _AddConstraints(Layouter* layouter); 101 102 float _Spacing(); 103 }; 104 105 // VerticalCompoundLayouter 106 class BTwoDimensionalLayout::VerticalCompoundLayouter 107 : public CompoundLayouter, private BLayoutContextListener { 108 public: 109 VerticalCompoundLayouter(); 110 111 virtual Layouter* GetLayouter(bool minMax); 112 113 virtual void InvalidateLayout(); 114 115 void InvalidateHeightForWidth(); 116 117 void InternalGetHeightForWidth( 118 LocalLayouter* localLayouter, 119 BLayoutContext* context, 120 bool realLayout, float* minHeight, 121 float* maxHeight, float* preferredHeight); 122 123 protected: 124 virtual void DoLayout(float size, 125 LocalLayouter* localLayouter, 126 BLayoutContext* context); 127 128 private: 129 Layouter* fHeightForWidthLayouter; 130 float fCachedMinHeightForWidth; 131 float fCachedMaxHeightForWidth; 132 float fCachedPreferredHeightForWidth; 133 BLayoutContext* fHeightForWidthLayoutContext; 134 135 bool _HasHeightForWidth(); 136 137 bool _SetHeightForWidthLayoutContext( 138 BLayoutContext* context); 139 140 // BLayoutContextListener 141 virtual void LayoutContextLeft(BLayoutContext* context); 142 }; 143 144 // LocalLayouter 145 class BTwoDimensionalLayout::LocalLayouter : private BLayoutContextListener { 146 public: 147 LocalLayouter(BTwoDimensionalLayout* layout); 148 ~LocalLayouter(); 149 150 // interface for the BTwoDimensionalLayout class 151 152 BSize MinSize(); 153 BSize MaxSize(); 154 BSize PreferredSize(); 155 156 void InvalidateLayout(); 157 void Layout(BSize size); 158 159 BRect ItemFrame(Dimensions itemDimensions); 160 161 void ValidateMinMax(); 162 163 void DoHorizontalLayout(float width); 164 165 void InternalGetHeightForWidth(float width, 166 float* minHeight, float* maxHeight, 167 float* preferredHeight); 168 169 void AlignWith(LocalLayouter* other, 170 enum orientation orientation); 171 172 // Archiving stuff 173 status_t AddAlignedLayoutsToArchive(BArchiver* archiver); 174 status_t AddOwnerToArchive(BArchiver* archiver, 175 CompoundLayouter* requestedBy, 176 bool& _wasAvailable); 177 status_t AlignLayoutsFromArchive(BUnarchiver* unarchiver, 178 orientation posture); 179 180 181 // interface for the compound layout context 182 183 void PrepareItems( 184 CompoundLayouter* compoundLayouter); 185 int32 CountElements( 186 CompoundLayouter* compoundLayouter); 187 bool HasMultiElementItems( 188 CompoundLayouter* compoundLayouter); 189 190 void AddConstraints( 191 CompoundLayouter* compoundLayouter, 192 Layouter* layouter); 193 194 float Spacing(CompoundLayouter* compoundLayouter); 195 196 bool HasHeightForWidth(); 197 198 bool AddHeightForWidthConstraints( 199 VerticalCompoundLayouter* compoundLayouter, 200 Layouter* layouter, 201 BLayoutContext* context); 202 void SetHeightForWidthConstraintsAdded(bool added); 203 204 void SetCompoundLayouter( 205 CompoundLayouter* compoundLayouter, 206 enum orientation orientation); 207 208 void InternalInvalidateLayout( 209 CompoundLayouter* compoundLayouter); 210 211 // implementation private 212 private: 213 BTwoDimensionalLayout* fLayout; 214 CompoundLayouter* fHLayouter; 215 VerticalCompoundLayouter* fVLayouter; 216 BList fHeightForWidthItems; 217 218 // active layout context when doing last horizontal layout 219 BLayoutContext* fHorizontalLayoutContext; 220 float fHorizontalLayoutWidth; 221 bool fHeightForWidthConstraintsAdded; 222 223 void _SetHorizontalLayoutContext( 224 BLayoutContext* context, float width); 225 226 // BLayoutContextListener 227 virtual void LayoutContextLeft(BLayoutContext* context); 228 }; 229 230 231 // #pragma mark - 232 233 // archiving constants 234 namespace { 235 const char* const kHAlignedLayoutField = "BTwoDimensionalLayout:" 236 "halignedlayout"; 237 const char* const kVAlignedLayoutField = "BTwoDimensionalLayout:" 238 "valignedlayout"; 239 const char* const kInsetsField = "BTwoDimensionalLayout:insets"; 240 const char* const kSpacingField = "BTwoDimensionalLayout:spacing"; 241 // kSpacingField = {fHSpacing, fVSpacing} 242 } 243 244 245 BTwoDimensionalLayout::BTwoDimensionalLayout() 246 : 247 fLeftInset(0), 248 fRightInset(0), 249 fTopInset(0), 250 fBottomInset(0), 251 fHSpacing(0), 252 fVSpacing(0), 253 fLocalLayouter(new LocalLayouter(this)) 254 { 255 } 256 257 258 BTwoDimensionalLayout::BTwoDimensionalLayout(BMessage* from) 259 : 260 BAbstractLayout(from), 261 fLeftInset(0), 262 fRightInset(0), 263 fTopInset(0), 264 fBottomInset(0), 265 fHSpacing(0), 266 fVSpacing(0), 267 fLocalLayouter(new LocalLayouter(this)) 268 { 269 BRect insets; 270 from->FindRect(kInsetsField, &insets); 271 SetInsets(insets.left, insets.top, insets.right, insets.bottom); 272 273 from->FindFloat(kSpacingField, 0, &fHSpacing); 274 from->FindFloat(kSpacingField, 1, &fVSpacing); 275 } 276 277 278 BTwoDimensionalLayout::~BTwoDimensionalLayout() 279 { 280 delete fLocalLayouter; 281 } 282 283 284 void 285 BTwoDimensionalLayout::SetInsets(float left, float top, float right, 286 float bottom) 287 { 288 fLeftInset = BControlLook::ComposeSpacing(left); 289 fTopInset = BControlLook::ComposeSpacing(top); 290 fRightInset = BControlLook::ComposeSpacing(right); 291 fBottomInset = BControlLook::ComposeSpacing(bottom); 292 293 InvalidateLayout(); 294 } 295 296 297 void 298 BTwoDimensionalLayout::SetInsets(float horizontal, float vertical) 299 { 300 fLeftInset = BControlLook::ComposeSpacing(horizontal); 301 fRightInset = fLeftInset; 302 303 fTopInset = BControlLook::ComposeSpacing(vertical); 304 fBottomInset = fTopInset; 305 306 InvalidateLayout(); 307 } 308 309 310 void 311 BTwoDimensionalLayout::SetInsets(float insets) 312 { 313 fLeftInset = BControlLook::ComposeSpacing(insets); 314 fRightInset = fLeftInset; 315 fTopInset = fLeftInset; 316 fBottomInset = fLeftInset; 317 318 InvalidateLayout(); 319 } 320 321 322 void 323 BTwoDimensionalLayout::GetInsets(float* left, float* top, float* right, 324 float* bottom) const 325 { 326 if (left) 327 *left = fLeftInset; 328 if (top) 329 *top = fTopInset; 330 if (right) 331 *right = fRightInset; 332 if (bottom) 333 *bottom = fBottomInset; 334 } 335 336 337 void 338 BTwoDimensionalLayout::AlignLayoutWith(BTwoDimensionalLayout* other, 339 enum orientation orientation) 340 { 341 if (!other || other == this) 342 return; 343 344 fLocalLayouter->AlignWith(other->fLocalLayouter, orientation); 345 346 InvalidateLayout(); 347 } 348 349 350 BSize 351 BTwoDimensionalLayout::BaseMinSize() 352 { 353 _ValidateMinMax(); 354 return AddInsets(fLocalLayouter->MinSize()); 355 } 356 357 358 BSize 359 BTwoDimensionalLayout::BaseMaxSize() 360 { 361 _ValidateMinMax(); 362 return AddInsets(fLocalLayouter->MaxSize()); 363 } 364 365 366 BSize 367 BTwoDimensionalLayout::BasePreferredSize() 368 { 369 _ValidateMinMax(); 370 return AddInsets(fLocalLayouter->PreferredSize()); 371 } 372 373 374 BAlignment 375 BTwoDimensionalLayout::BaseAlignment() 376 { 377 return BAbstractLayout::BaseAlignment(); 378 } 379 380 381 bool 382 BTwoDimensionalLayout::HasHeightForWidth() 383 { 384 _ValidateMinMax(); 385 return fLocalLayouter->HasHeightForWidth(); 386 } 387 388 389 void 390 BTwoDimensionalLayout::GetHeightForWidth(float width, float* min, float* max, 391 float* preferred) 392 { 393 if (!HasHeightForWidth()) 394 return; 395 396 float outerSpacing = fLeftInset + fRightInset - 1; 397 fLocalLayouter->InternalGetHeightForWidth(BLayoutUtils::SubtractDistances( 398 width, outerSpacing), min, max, preferred); 399 AddInsets(min, max, preferred); 400 } 401 402 403 void 404 BTwoDimensionalLayout::SetFrame(BRect frame) 405 { 406 BAbstractLayout::SetFrame(frame); 407 } 408 409 410 status_t 411 BTwoDimensionalLayout::Archive(BMessage* into, bool deep) const 412 { 413 BArchiver archiver(into); 414 status_t err = BAbstractLayout::Archive(into, deep); 415 416 if (err == B_OK) { 417 BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset); 418 err = into->AddRect(kInsetsField, insets); 419 } 420 421 if (err == B_OK) 422 err = into->AddFloat(kSpacingField, fHSpacing); 423 424 if (err == B_OK) 425 err = into->AddFloat(kSpacingField, fVSpacing); 426 427 return archiver.Finish(err); 428 } 429 430 431 status_t 432 BTwoDimensionalLayout::AllArchived(BMessage* into) const 433 { 434 BArchiver archiver(into); 435 436 status_t err = BLayout::AllArchived(into); 437 if (err == B_OK) 438 err = fLocalLayouter->AddAlignedLayoutsToArchive(&archiver); 439 return err; 440 } 441 442 443 status_t 444 BTwoDimensionalLayout::AllUnarchived(const BMessage* from) 445 { 446 status_t err = BLayout::AllUnarchived(from); 447 if (err != B_OK) 448 return err; 449 450 BUnarchiver unarchiver(from); 451 err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_HORIZONTAL); 452 if (err == B_OK) 453 err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_VERTICAL); 454 455 return err; 456 } 457 458 459 status_t 460 BTwoDimensionalLayout::ItemArchived(BMessage* into, BLayoutItem* item, 461 int32 index) const 462 { 463 return BAbstractLayout::ItemArchived(into, item, index); 464 } 465 466 467 status_t 468 BTwoDimensionalLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, 469 int32 index) 470 { 471 return BAbstractLayout::ItemUnarchived(from, item, index); 472 } 473 474 475 476 477 void 478 BTwoDimensionalLayout::LayoutInvalidated(bool children) 479 { 480 fLocalLayouter->InvalidateLayout(); 481 } 482 483 484 void 485 BTwoDimensionalLayout::DoLayout() 486 { 487 _ValidateMinMax(); 488 489 // layout the horizontal/vertical elements 490 BSize size(SubtractInsets(LayoutArea().Size())); 491 492 #ifdef DEBUG_LAYOUT 493 printf("BTwoDimensionalLayout::DerivedLayoutItems(): view: %p" 494 " size: (%.1f, %.1f)\n", View(), size.Width(), size.Height()); 495 #endif 496 497 fLocalLayouter->Layout(size); 498 499 // layout the items 500 BPoint itemOffset(LayoutArea().LeftTop()); 501 int itemCount = CountItems(); 502 for (int i = 0; i < itemCount; i++) { 503 BLayoutItem* item = ItemAt(i); 504 if (item->IsVisible()) { 505 Dimensions itemDimensions; 506 GetItemDimensions(item, &itemDimensions); 507 BRect frame = fLocalLayouter->ItemFrame(itemDimensions); 508 frame.left += fLeftInset; 509 frame.top += fTopInset; 510 frame.right += fLeftInset; 511 frame.bottom += fTopInset; 512 frame.OffsetBy(itemOffset); 513 { 514 #ifdef DEBUG_LAYOUT 515 printf(" frame for item %2d (view: %p): ", i, item->View()); 516 frame.PrintToStream(); 517 #endif 518 //BSize min(item->MinSize()); 519 //BSize max(item->MaxSize()); 520 //printf(" min: (%.1f, %.1f), max: (%.1f, %.1f)\n", min.width, min.height, 521 // max.width, max.height); 522 //if (item->HasHeightForWidth()) { 523 //float minHeight, maxHeight, preferredHeight; 524 //item->GetHeightForWidth(frame.Width(), &minHeight, &maxHeight, 525 // &preferredHeight); 526 //printf(" hfw: min: %.1f, max: %.1f, pref: %.1f\n", minHeight, maxHeight, 527 // preferredHeight); 528 //} 529 } 530 531 item->AlignInFrame(frame); 532 } 533 //else 534 //printf(" item %2d not visible", i); 535 } 536 } 537 538 539 BSize 540 BTwoDimensionalLayout::AddInsets(BSize size) 541 { 542 size.width = BLayoutUtils::AddDistances(size.width, 543 fLeftInset + fRightInset - 1); 544 size.height = BLayoutUtils::AddDistances(size.height, 545 fTopInset + fBottomInset - 1); 546 return size; 547 } 548 549 550 void 551 BTwoDimensionalLayout::AddInsets(float* minHeight, float* maxHeight, 552 float* preferredHeight) 553 { 554 float insets = fTopInset + fBottomInset - 1; 555 if (minHeight) 556 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets); 557 if (maxHeight) 558 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets); 559 if (preferredHeight) 560 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets); 561 } 562 563 564 BSize 565 BTwoDimensionalLayout::SubtractInsets(BSize size) 566 { 567 size.width = BLayoutUtils::SubtractDistances(size.width, 568 fLeftInset + fRightInset - 1); 569 size.height = BLayoutUtils::SubtractDistances(size.height, 570 fTopInset + fBottomInset - 1); 571 return size; 572 } 573 574 575 void 576 BTwoDimensionalLayout::PrepareItems(enum orientation orientation) 577 { 578 } 579 580 581 bool 582 BTwoDimensionalLayout::HasMultiColumnItems() 583 { 584 return false; 585 } 586 587 588 bool 589 BTwoDimensionalLayout::HasMultiRowItems() 590 { 591 return false; 592 } 593 594 595 void 596 BTwoDimensionalLayout::_ValidateMinMax() 597 { 598 fLocalLayouter->ValidateMinMax(); 599 } 600 601 602 // #pragma mark - CompoundLayouter 603 604 605 BTwoDimensionalLayout::CompoundLayouter::CompoundLayouter( 606 enum orientation orientation) 607 : 608 fLayouter(NULL), 609 fLayoutInfo(NULL), 610 fOrientation(orientation), 611 fLocalLayouters(10), 612 fLayoutContext(NULL), 613 fLastLayoutSize(-1) 614 { 615 } 616 617 618 BTwoDimensionalLayout::CompoundLayouter::~CompoundLayouter() 619 { 620 } 621 622 623 orientation 624 BTwoDimensionalLayout::CompoundLayouter::Orientation() 625 { 626 return fOrientation; 627 } 628 629 630 Layouter* 631 BTwoDimensionalLayout::CompoundLayouter::GetLayouter(bool minMax) 632 { 633 return fLayouter; 634 } 635 636 637 LayoutInfo* 638 BTwoDimensionalLayout::CompoundLayouter::GetLayoutInfo() 639 { 640 return fLayoutInfo; 641 } 642 643 644 void 645 BTwoDimensionalLayout::CompoundLayouter::AddLocalLayouter( 646 LocalLayouter* localLayouter) 647 { 648 if (localLayouter) { 649 if (!fLocalLayouters.HasItem(localLayouter)) { 650 fLocalLayouters.AddItem(localLayouter); 651 InvalidateLayout(); 652 } 653 } 654 } 655 656 657 void 658 BTwoDimensionalLayout::CompoundLayouter::RemoveLocalLayouter( 659 LocalLayouter* localLayouter) 660 { 661 if (fLocalLayouters.RemoveItem(localLayouter)) 662 InvalidateLayout(); 663 } 664 665 666 status_t 667 BTwoDimensionalLayout::CompoundLayouter::AddAlignedLayoutsToArchive( 668 BArchiver* archiver, LocalLayouter* requestedBy) 669 { 670 // The LocalLayouter* that really owns us is at index 0, layouts 671 // at other indices are aligned to this one. 672 if (requestedBy != fLocalLayouters.ItemAt(0)) 673 return B_OK; 674 675 status_t err; 676 for (int32 i = fLocalLayouters.CountItems() - 1; i > 0; i--) { 677 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 678 679 bool wasAvailable; 680 err = layouter->AddOwnerToArchive(archiver, this, wasAvailable); 681 if (err != B_OK && wasAvailable) 682 return err; 683 } 684 return B_OK; 685 } 686 687 688 void 689 BTwoDimensionalLayout::CompoundLayouter::AbsorbCompoundLayouter( 690 CompoundLayouter* other) 691 { 692 if (other == this) 693 return; 694 695 int32 count = other->fLocalLayouters.CountItems(); 696 for (int32 i = count - 1; i >= 0; i--) { 697 LocalLayouter* layouter 698 = (LocalLayouter*)other->fLocalLayouters.ItemAt(i); 699 AddLocalLayouter(layouter); 700 layouter->SetCompoundLayouter(this, fOrientation); 701 } 702 703 InvalidateLayout(); 704 } 705 706 707 void 708 BTwoDimensionalLayout::CompoundLayouter::InvalidateLayout() 709 { 710 if (!fLayouter) 711 return; 712 713 delete fLayouter; 714 delete fLayoutInfo; 715 716 fLayouter = NULL; 717 fLayoutInfo = NULL; 718 fLayoutContext = NULL; 719 720 // notify all local layouters to invalidate the respective views 721 int32 count = fLocalLayouters.CountItems(); 722 for (int32 i = 0; i < count; i++) { 723 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 724 layouter->InternalInvalidateLayout(this); 725 } 726 } 727 728 729 bool 730 BTwoDimensionalLayout::CompoundLayouter::IsMinMaxValid() 731 { 732 return (fLayouter != NULL); 733 } 734 735 736 void 737 BTwoDimensionalLayout::CompoundLayouter::ValidateMinMax() 738 { 739 if (IsMinMaxValid()) 740 return; 741 742 fLastLayoutSize = -1; 743 744 // create the layouter 745 _PrepareItems(); 746 747 int elementCount = _CountElements(); 748 749 fLayouter = new CollapsingLayouter(elementCount, _Spacing()); 750 751 // tell the layouter about our constraints 752 // TODO: We should probably ignore local layouters whose view is hidden. 753 // It's a bit tricky to find out, whether the view is hidden, though, since 754 // this doesn't necessarily mean only hidden relative to the parent, but 755 // hidden relative to a common parent. 756 _AddConstraints(fLayouter); 757 758 fLayoutInfo = fLayouter->CreateLayoutInfo(); 759 } 760 761 762 void 763 BTwoDimensionalLayout::CompoundLayouter::Layout(float size, 764 LocalLayouter* localLayouter, BLayoutContext* context) 765 { 766 ValidateMinMax(); 767 768 if (context != fLayoutContext || fLastLayoutSize != size) { 769 DoLayout(size, localLayouter, context); 770 fLayoutContext = context; 771 fLastLayoutSize = size; 772 } 773 } 774 775 776 void 777 BTwoDimensionalLayout::CompoundLayouter::DoLayout(float size, 778 LocalLayouter* localLayouter, BLayoutContext* context) 779 { 780 fLayouter->Layout(fLayoutInfo, size); 781 } 782 783 784 void 785 BTwoDimensionalLayout::CompoundLayouter::_PrepareItems() 786 { 787 int32 count = fLocalLayouters.CountItems(); 788 for (int32 i = 0; i < count; i++) { 789 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 790 layouter->PrepareItems(this); 791 } 792 } 793 794 795 int32 796 BTwoDimensionalLayout::CompoundLayouter::_CountElements() 797 { 798 int32 elementCount = 0; 799 int32 count = fLocalLayouters.CountItems(); 800 for (int32 i = 0; i < count; i++) { 801 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 802 int32 layouterCount = layouter->CountElements(this); 803 elementCount = max_c(elementCount, layouterCount); 804 } 805 806 return elementCount; 807 } 808 809 810 bool 811 BTwoDimensionalLayout::CompoundLayouter::_HasMultiElementItems() 812 { 813 int32 count = fLocalLayouters.CountItems(); 814 for (int32 i = 0; i < count; i++) { 815 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 816 if (layouter->HasMultiElementItems(this)) 817 return true; 818 } 819 820 return false; 821 } 822 823 824 void 825 BTwoDimensionalLayout::CompoundLayouter::_AddConstraints(Layouter* layouter) 826 { 827 int32 count = fLocalLayouters.CountItems(); 828 for (int32 i = 0; i < count; i++) { 829 LocalLayouter* localLayouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 830 localLayouter->AddConstraints(this, layouter); 831 } 832 } 833 834 835 float 836 BTwoDimensionalLayout::CompoundLayouter::_Spacing() 837 { 838 if (!fLocalLayouters.IsEmpty()) 839 return ((LocalLayouter*)fLocalLayouters.ItemAt(0))->Spacing(this); 840 return 0; 841 } 842 843 844 // #pragma mark - VerticalCompoundLayouter 845 846 847 BTwoDimensionalLayout::VerticalCompoundLayouter::VerticalCompoundLayouter() 848 : 849 CompoundLayouter(B_VERTICAL), 850 fHeightForWidthLayouter(NULL), 851 fCachedMinHeightForWidth(0), 852 fCachedMaxHeightForWidth(0), 853 fCachedPreferredHeightForWidth(0), 854 fHeightForWidthLayoutContext(NULL) 855 { 856 } 857 858 859 Layouter* 860 BTwoDimensionalLayout::VerticalCompoundLayouter::GetLayouter(bool minMax) 861 { 862 return (minMax || !_HasHeightForWidth() 863 ? fLayouter : fHeightForWidthLayouter); 864 } 865 866 867 void 868 BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateLayout() 869 { 870 CompoundLayouter::InvalidateLayout(); 871 872 InvalidateHeightForWidth(); 873 } 874 875 876 void 877 BTwoDimensionalLayout::VerticalCompoundLayouter::InvalidateHeightForWidth() 878 { 879 if (fHeightForWidthLayouter != NULL) { 880 delete fHeightForWidthLayouter; 881 fHeightForWidthLayouter = NULL; 882 883 // also make sure we're not reusing the old layout info 884 fLastLayoutSize = -1; 885 886 int32 count = fLocalLayouters.CountItems(); 887 for (int32 i = 0; i < count; i++) { 888 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 889 layouter->SetHeightForWidthConstraintsAdded(false); 890 } 891 } 892 } 893 894 895 void 896 BTwoDimensionalLayout::VerticalCompoundLayouter::InternalGetHeightForWidth( 897 LocalLayouter* localLayouter, BLayoutContext* context, bool realLayout, 898 float* minHeight, float* maxHeight, float* preferredHeight) 899 { 900 bool updateCachedInfo = false; 901 902 if (_SetHeightForWidthLayoutContext(context) 903 || fHeightForWidthLayouter == NULL) { 904 // Either the layout context changed or we haven't initialized the 905 // height for width layouter yet. We create it and init it now. 906 907 // clone the vertical layouter 908 delete fHeightForWidthLayouter; 909 delete fLayoutInfo; 910 fHeightForWidthLayouter = fLayouter->CloneLayouter(); 911 fLayoutInfo = fHeightForWidthLayouter->CreateLayoutInfo(); 912 913 // add the children's height for width constraints 914 int32 count = fLocalLayouters.CountItems(); 915 for (int32 i = 0; i < count; i++) { 916 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 917 if (layouter->HasHeightForWidth()) { 918 layouter->AddHeightForWidthConstraints(this, 919 fHeightForWidthLayouter, context); 920 } 921 } 922 923 updateCachedInfo = true; 924 925 // get the height for width info 926 fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize(); 927 fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize(); 928 fCachedPreferredHeightForWidth 929 = fHeightForWidthLayouter->PreferredSize(); 930 931 } else if (localLayouter->HasHeightForWidth()) { 932 // There is a height for width layouter and it has been initialized 933 // in the current layout context. So we just add the height for width 934 // constraints of the calling local layouter, if they haven't been 935 // added yet. 936 updateCachedInfo = localLayouter->AddHeightForWidthConstraints(this, 937 fHeightForWidthLayouter, context); 938 } 939 940 // update cached height for width info, if something changed 941 if (updateCachedInfo) { 942 // get the height for width info 943 fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize(); 944 fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize(); 945 fCachedPreferredHeightForWidth 946 = fHeightForWidthLayouter->PreferredSize(); 947 } 948 949 if (minHeight) 950 *minHeight = fCachedMinHeightForWidth; 951 if (maxHeight) 952 *maxHeight = fCachedMaxHeightForWidth; 953 if (preferredHeight) 954 *preferredHeight = fCachedPreferredHeightForWidth; 955 } 956 957 958 void 959 BTwoDimensionalLayout::VerticalCompoundLayouter::DoLayout(float size, 960 LocalLayouter* localLayouter, BLayoutContext* context) 961 { 962 Layouter* layouter; 963 if (_HasHeightForWidth()) { 964 float minHeight, maxHeight, preferredHeight; 965 InternalGetHeightForWidth(localLayouter, context, true, &minHeight, 966 &maxHeight, &preferredHeight); 967 size = max_c(size, minHeight); 968 layouter = fHeightForWidthLayouter; 969 } else 970 layouter = fLayouter; 971 972 layouter->Layout(fLayoutInfo, size); 973 } 974 975 976 bool 977 BTwoDimensionalLayout::VerticalCompoundLayouter::_HasHeightForWidth() 978 { 979 int32 count = fLocalLayouters.CountItems(); 980 for (int32 i = 0; i < count; i++) { 981 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 982 if (layouter->HasHeightForWidth()) 983 return true; 984 } 985 986 return false; 987 } 988 989 990 bool 991 BTwoDimensionalLayout::VerticalCompoundLayouter 992 ::_SetHeightForWidthLayoutContext(BLayoutContext* context) 993 { 994 if (context == fHeightForWidthLayoutContext) 995 return false; 996 997 if (fHeightForWidthLayoutContext != NULL) { 998 fHeightForWidthLayoutContext->RemoveListener(this); 999 fHeightForWidthLayoutContext = NULL; 1000 } 1001 1002 // We can ignore the whole context business, if we have no more than one 1003 // local layouter. We use the layout context only to recognize when calls 1004 // of different local layouters belong to the same context. 1005 if (fLocalLayouters.CountItems() <= 1) 1006 return false; 1007 1008 fHeightForWidthLayoutContext = context; 1009 1010 if (fHeightForWidthLayoutContext != NULL) 1011 fHeightForWidthLayoutContext->AddListener(this); 1012 1013 InvalidateHeightForWidth(); 1014 1015 return true; 1016 } 1017 1018 1019 void 1020 BTwoDimensionalLayout::VerticalCompoundLayouter::LayoutContextLeft( 1021 BLayoutContext* context) 1022 { 1023 fHeightForWidthLayoutContext = NULL; 1024 } 1025 1026 1027 // #pragma mark - LocalLayouter 1028 1029 1030 BTwoDimensionalLayout::LocalLayouter::LocalLayouter( 1031 BTwoDimensionalLayout* layout) 1032 : 1033 fLayout(layout), 1034 fHLayouter(new CompoundLayouter(B_HORIZONTAL)), 1035 fVLayouter(new VerticalCompoundLayouter), 1036 fHeightForWidthItems(), 1037 fHorizontalLayoutContext(NULL), 1038 fHorizontalLayoutWidth(0), 1039 fHeightForWidthConstraintsAdded(false) 1040 { 1041 fHLayouter->AddLocalLayouter(this); 1042 fVLayouter->AddLocalLayouter(this); 1043 } 1044 1045 1046 BTwoDimensionalLayout::LocalLayouter::~LocalLayouter() 1047 { 1048 if (fHLayouter != NULL) { 1049 fHLayouter->RemoveLocalLayouter(this); 1050 fHLayouter->ReleaseReference(); 1051 } 1052 1053 if (fVLayouter != NULL) { 1054 fVLayouter->RemoveLocalLayouter(this); 1055 fVLayouter->ReleaseReference(); 1056 } 1057 } 1058 1059 1060 BSize 1061 BTwoDimensionalLayout::LocalLayouter::MinSize() 1062 { 1063 return BSize(fHLayouter->GetLayouter(true)->MinSize(), 1064 fVLayouter->GetLayouter(true)->MinSize()); 1065 } 1066 1067 1068 BSize 1069 BTwoDimensionalLayout::LocalLayouter::MaxSize() 1070 { 1071 return BSize(fHLayouter->GetLayouter(true)->MaxSize(), 1072 fVLayouter->GetLayouter(true)->MaxSize()); 1073 } 1074 1075 1076 BSize 1077 BTwoDimensionalLayout::LocalLayouter::PreferredSize() 1078 { 1079 return BSize(fHLayouter->GetLayouter(true)->PreferredSize(), 1080 fVLayouter->GetLayouter(true)->PreferredSize()); 1081 } 1082 1083 1084 void 1085 BTwoDimensionalLayout::LocalLayouter::InvalidateLayout() 1086 { 1087 fHLayouter->InvalidateLayout(); 1088 fVLayouter->InvalidateLayout(); 1089 } 1090 1091 1092 void 1093 BTwoDimensionalLayout::LocalLayouter::Layout(BSize size) 1094 { 1095 DoHorizontalLayout(size.width); 1096 fVLayouter->Layout(size.height, this, fLayout->LayoutContext()); 1097 } 1098 1099 1100 BRect 1101 BTwoDimensionalLayout::LocalLayouter::ItemFrame(Dimensions itemDimensions) 1102 { 1103 LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo(); 1104 LayoutInfo* vLayoutInfo = fVLayouter->GetLayoutInfo(); 1105 float x = hLayoutInfo->ElementLocation(itemDimensions.x); 1106 float y = vLayoutInfo->ElementLocation(itemDimensions.y); 1107 float width = hLayoutInfo->ElementRangeSize(itemDimensions.x, 1108 itemDimensions.width); 1109 float height = vLayoutInfo->ElementRangeSize(itemDimensions.y, 1110 itemDimensions.height); 1111 return BRect(x, y, x + width, y + height); 1112 } 1113 1114 1115 void 1116 BTwoDimensionalLayout::LocalLayouter::ValidateMinMax() 1117 { 1118 if (fHLayouter->IsMinMaxValid() && fVLayouter->IsMinMaxValid()) 1119 return; 1120 1121 if (!fHLayouter->IsMinMaxValid()) 1122 fHeightForWidthItems.MakeEmpty(); 1123 1124 _SetHorizontalLayoutContext(NULL, -1); 1125 1126 fHLayouter->ValidateMinMax(); 1127 fVLayouter->ValidateMinMax(); 1128 fLayout->ResetLayoutInvalidation(); 1129 } 1130 1131 1132 void 1133 BTwoDimensionalLayout::LocalLayouter::DoHorizontalLayout(float width) 1134 { 1135 BLayoutContext* context = fLayout->LayoutContext(); 1136 if (fHorizontalLayoutContext != context 1137 || width != fHorizontalLayoutWidth) { 1138 _SetHorizontalLayoutContext(context, width); 1139 fHLayouter->Layout(width, this, context); 1140 fVLayouter->InvalidateHeightForWidth(); 1141 } 1142 } 1143 1144 1145 void 1146 BTwoDimensionalLayout::LocalLayouter::InternalGetHeightForWidth(float width, 1147 float* minHeight, float* maxHeight, float* preferredHeight) 1148 { 1149 DoHorizontalLayout(width); 1150 fVLayouter->InternalGetHeightForWidth(this, fHorizontalLayoutContext, false, 1151 minHeight, maxHeight, preferredHeight); 1152 } 1153 1154 1155 void 1156 BTwoDimensionalLayout::LocalLayouter::AlignWith(LocalLayouter* other, 1157 enum orientation orientation) 1158 { 1159 if (orientation == B_HORIZONTAL) 1160 other->fHLayouter->AbsorbCompoundLayouter(fHLayouter); 1161 else 1162 other->fVLayouter->AbsorbCompoundLayouter(fVLayouter); 1163 } 1164 1165 1166 status_t 1167 BTwoDimensionalLayout::LocalLayouter::AddAlignedLayoutsToArchive( 1168 BArchiver* archiver) 1169 { 1170 status_t err = fHLayouter->AddAlignedLayoutsToArchive(archiver, this); 1171 1172 if (err == B_OK) 1173 err = fVLayouter->AddAlignedLayoutsToArchive(archiver, this); 1174 1175 return err; 1176 } 1177 1178 1179 status_t 1180 BTwoDimensionalLayout::LocalLayouter::AddOwnerToArchive(BArchiver* archiver, 1181 CompoundLayouter* requestedBy, bool& _wasAvailable) 1182 { 1183 const char* field = kHAlignedLayoutField; 1184 if (requestedBy == fVLayouter) 1185 field = kVAlignedLayoutField; 1186 1187 if ((_wasAvailable = archiver->IsArchived(fLayout))) 1188 return archiver->AddArchivable(field, fLayout); 1189 1190 return B_NAME_NOT_FOUND; 1191 } 1192 1193 1194 status_t 1195 BTwoDimensionalLayout::LocalLayouter::AlignLayoutsFromArchive( 1196 BUnarchiver* unarchiver, orientation posture) 1197 { 1198 const char* field = kHAlignedLayoutField; 1199 if (posture == B_VERTICAL) 1200 field = kVAlignedLayoutField; 1201 1202 int32 count; 1203 status_t err = unarchiver->ArchiveMessage()->GetInfo(field, NULL, &count); 1204 if (err == B_NAME_NOT_FOUND) 1205 return B_OK; 1206 1207 BTwoDimensionalLayout* retriever; 1208 for (int32 i = 0; i < count && err == B_OK; i++) { 1209 err = unarchiver->FindObject(field, i, 1210 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, retriever); 1211 1212 if (err == B_OK) 1213 retriever->AlignLayoutWith(fLayout, posture); 1214 } 1215 1216 return err; 1217 } 1218 1219 1220 void 1221 BTwoDimensionalLayout::LocalLayouter::PrepareItems( 1222 CompoundLayouter* compoundLayouter) 1223 { 1224 fLayout->PrepareItems(compoundLayouter->Orientation()); 1225 } 1226 1227 1228 int32 1229 BTwoDimensionalLayout::LocalLayouter::CountElements( 1230 CompoundLayouter* compoundLayouter) 1231 { 1232 if (compoundLayouter->Orientation() == B_HORIZONTAL) 1233 return fLayout->InternalCountColumns(); 1234 else 1235 return fLayout->InternalCountRows(); 1236 } 1237 1238 1239 bool 1240 BTwoDimensionalLayout::LocalLayouter::HasMultiElementItems( 1241 CompoundLayouter* compoundLayouter) 1242 { 1243 if (compoundLayouter->Orientation() == B_HORIZONTAL) 1244 return fLayout->HasMultiColumnItems(); 1245 else 1246 return fLayout->HasMultiRowItems(); 1247 } 1248 1249 1250 void 1251 BTwoDimensionalLayout::LocalLayouter::AddConstraints( 1252 CompoundLayouter* compoundLayouter, Layouter* layouter) 1253 { 1254 enum orientation orientation = compoundLayouter->Orientation(); 1255 int itemCount = fLayout->CountItems(); 1256 if (itemCount > 0) { 1257 for (int i = 0; i < itemCount; i++) { 1258 BLayoutItem* item = fLayout->ItemAt(i); 1259 if (item->IsVisible()) { 1260 Dimensions itemDimensions; 1261 fLayout->GetItemDimensions(item, &itemDimensions); 1262 1263 BSize min = item->MinSize(); 1264 BSize max = item->MaxSize(); 1265 BSize preferred = item->PreferredSize(); 1266 1267 if (orientation == B_HORIZONTAL) { 1268 layouter->AddConstraints( 1269 itemDimensions.x, 1270 itemDimensions.width, 1271 min.width, 1272 max.width, 1273 preferred.width); 1274 1275 if (item->HasHeightForWidth()) 1276 fHeightForWidthItems.AddItem(item); 1277 1278 } else { 1279 layouter->AddConstraints( 1280 itemDimensions.y, 1281 itemDimensions.height, 1282 min.height, 1283 max.height, 1284 preferred.height); 1285 } 1286 } 1287 } 1288 1289 // add column/row constraints 1290 ColumnRowConstraints constraints; 1291 int elementCount = CountElements(compoundLayouter); 1292 for (int element = 0; element < elementCount; element++) { 1293 fLayout->GetColumnRowConstraints(orientation, element, 1294 &constraints); 1295 layouter->SetWeight(element, constraints.weight); 1296 layouter->AddConstraints(element, 1, constraints.min, 1297 constraints.max, constraints.min); 1298 } 1299 } 1300 } 1301 1302 1303 float 1304 BTwoDimensionalLayout::LocalLayouter::Spacing( 1305 CompoundLayouter* compoundLayouter) 1306 { 1307 return (compoundLayouter->Orientation() == B_HORIZONTAL 1308 ? fLayout->fHSpacing : fLayout->fVSpacing); 1309 } 1310 1311 1312 bool 1313 BTwoDimensionalLayout::LocalLayouter::HasHeightForWidth() 1314 { 1315 return !fHeightForWidthItems.IsEmpty(); 1316 } 1317 1318 1319 bool 1320 BTwoDimensionalLayout::LocalLayouter::AddHeightForWidthConstraints( 1321 VerticalCompoundLayouter* compoundLayouter, Layouter* layouter, 1322 BLayoutContext* context) 1323 { 1324 if (context != fHorizontalLayoutContext) 1325 return false; 1326 1327 if (fHeightForWidthConstraintsAdded) 1328 return false; 1329 1330 LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo(); 1331 1332 // add the children's height for width constraints 1333 int32 itemCount = fHeightForWidthItems.CountItems(); 1334 for (int32 i = 0; i < itemCount; i++) { 1335 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i); 1336 Dimensions itemDimensions; 1337 fLayout->GetItemDimensions(item, &itemDimensions); 1338 1339 float minHeight, maxHeight, preferredHeight; 1340 item->GetHeightForWidth( 1341 hLayoutInfo->ElementRangeSize(itemDimensions.x, 1342 itemDimensions.width), 1343 &minHeight, &maxHeight, &preferredHeight); 1344 layouter->AddConstraints( 1345 itemDimensions.y, 1346 itemDimensions.height, 1347 minHeight, 1348 maxHeight, 1349 preferredHeight); 1350 } 1351 1352 SetHeightForWidthConstraintsAdded(true); 1353 1354 return true; 1355 } 1356 1357 1358 void 1359 BTwoDimensionalLayout::LocalLayouter::SetHeightForWidthConstraintsAdded( 1360 bool added) 1361 { 1362 fHeightForWidthConstraintsAdded = added; 1363 } 1364 1365 1366 void 1367 BTwoDimensionalLayout::LocalLayouter::SetCompoundLayouter( 1368 CompoundLayouter* compoundLayouter, enum orientation orientation) 1369 { 1370 CompoundLayouter* oldCompoundLayouter; 1371 if (orientation == B_HORIZONTAL) { 1372 oldCompoundLayouter = fHLayouter; 1373 fHLayouter = compoundLayouter; 1374 } else { 1375 oldCompoundLayouter = fVLayouter; 1376 fVLayouter = static_cast<VerticalCompoundLayouter*>(compoundLayouter); 1377 } 1378 1379 if (compoundLayouter == oldCompoundLayouter) 1380 return; 1381 1382 if (oldCompoundLayouter != NULL) { 1383 oldCompoundLayouter->RemoveLocalLayouter(this); 1384 oldCompoundLayouter->ReleaseReference(); 1385 } 1386 1387 if (compoundLayouter != NULL) 1388 compoundLayouter->AcquireReference(); 1389 1390 InternalInvalidateLayout(compoundLayouter); 1391 } 1392 1393 1394 void 1395 BTwoDimensionalLayout::LocalLayouter::InternalInvalidateLayout( 1396 CompoundLayouter* compoundLayouter) 1397 { 1398 _SetHorizontalLayoutContext(NULL, -1); 1399 1400 fLayout->BLayout::InvalidateLayout(); 1401 } 1402 1403 1404 void 1405 BTwoDimensionalLayout::LocalLayouter::_SetHorizontalLayoutContext( 1406 BLayoutContext* context, float width) 1407 { 1408 if (context != fHorizontalLayoutContext) { 1409 if (fHorizontalLayoutContext != NULL) 1410 fHorizontalLayoutContext->RemoveListener(this); 1411 1412 fHorizontalLayoutContext = context; 1413 1414 if (fHorizontalLayoutContext != NULL) 1415 fHorizontalLayoutContext->AddListener(this); 1416 } 1417 1418 fHorizontalLayoutWidth = width; 1419 } 1420 1421 1422 void 1423 BTwoDimensionalLayout::LocalLayouter::LayoutContextLeft(BLayoutContext* context) 1424 { 1425 fHorizontalLayoutContext = NULL; 1426 fHorizontalLayoutWidth = -1; 1427 } 1428 1429 1430 status_t 1431 BTwoDimensionalLayout::Perform(perform_code code, void* _data) 1432 { 1433 return BAbstractLayout::Perform(code, _data); 1434 } 1435 1436 1437 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout1() {} 1438 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout2() {} 1439 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout3() {} 1440 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout4() {} 1441 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout5() {} 1442 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout6() {} 1443 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout7() {} 1444 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout8() {} 1445 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout9() {} 1446 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout10() {} 1447 1448