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 // Some words of explanation: 25 // 26 // This class is the base class for BLayouts that organize their items 27 // on a grid, with each item covering one or more grid cells (always a 28 // rectangular area). The derived classes only need to implement the 29 // hooks reporting the constraints for the items and additional constraints 30 // for the rows and columns. This class does all the layouting. 31 // 32 // The basic idea of the layout process is simple. The horizontal and the 33 // vertical dimensions are laid out independently and the items are set to the 34 // resulting locations and sizes. The "height for width" feature makes the 35 // height depend on the width, which makes things a bit more complicated. 36 // The horizontal dimension must be laid out first and and the results are 37 // fed into the vertical layout process. 38 // 39 // The AlignLayoutWith() feature, which allows to align layouts for different 40 // views with each other, causes the need for the three inner *Layouter classes. 41 // For each set of layouts aligned with each other with respect to one 42 // dimension that dimension must be laid out together. The class responsible 43 // is CompoundLayouter; one instance exists per such set. The derived class 44 // VerticalCompoundLayouter is a specialization for the vertical dimension 45 // which additionally takes care of the "height for width" feature. Per layout 46 // a single LocalLayouter exists, which comprises the required glue layout 47 // code and serves as a proxy for the layout, providing service methods 48 // needed by the CompoundLayouter. 49 50 // TODO: Check for memory leaks! 51 52 //#define DEBUG_LAYOUT 53 54 // CompoundLayouter 55 class BTwoDimensionalLayout::CompoundLayouter : public BReferenceable { 56 public: 57 CompoundLayouter(orientation orientation); 58 virtual ~CompoundLayouter(); 59 60 orientation Orientation(); 61 62 virtual Layouter* GetLayouter(bool minMax); 63 64 LayoutInfo* GetLayoutInfo(); 65 66 void AddLocalLayouter(LocalLayouter* localLayouter); 67 void RemoveLocalLayouter( 68 LocalLayouter* localLayouter); 69 70 status_t AddAlignedLayoutsToArchive(BArchiver* archiver, 71 LocalLayouter* requestedBy); 72 73 void AbsorbCompoundLayouter(CompoundLayouter* other); 74 75 virtual void InvalidateLayout(); 76 bool IsMinMaxValid(); 77 void ValidateMinMax(); 78 void Layout(float size, LocalLayouter* localLayouter, 79 BLayoutContext* context); 80 81 protected: 82 virtual void DoLayout(float size, 83 LocalLayouter* localLayouter, 84 BLayoutContext* context); 85 86 Layouter* fLayouter; 87 LayoutInfo* fLayoutInfo; 88 orientation fOrientation; 89 BList fLocalLayouters; 90 BLayoutContext* fLayoutContext; 91 float fLastLayoutSize; 92 93 void _PrepareItems(); 94 95 int32 _CountElements(); 96 bool _HasMultiElementItems(); 97 98 void _AddConstraints(Layouter* layouter); 99 100 float _Spacing(); 101 }; 102 103 // VerticalCompoundLayouter 104 class BTwoDimensionalLayout::VerticalCompoundLayouter 105 : public CompoundLayouter, private BLayoutContextListener { 106 public: 107 VerticalCompoundLayouter(); 108 109 virtual Layouter* GetLayouter(bool minMax); 110 111 virtual void InvalidateLayout(); 112 113 void InvalidateHeightForWidth(); 114 115 void InternalGetHeightForWidth( 116 LocalLayouter* localLayouter, 117 BLayoutContext* context, 118 bool realLayout, float* minHeight, 119 float* maxHeight, float* preferredHeight); 120 121 protected: 122 virtual void DoLayout(float size, 123 LocalLayouter* localLayouter, 124 BLayoutContext* context); 125 126 private: 127 Layouter* fHeightForWidthLayouter; 128 float fCachedMinHeightForWidth; 129 float fCachedMaxHeightForWidth; 130 float fCachedPreferredHeightForWidth; 131 BLayoutContext* fHeightForWidthLayoutContext; 132 133 bool _HasHeightForWidth(); 134 135 bool _SetHeightForWidthLayoutContext( 136 BLayoutContext* context); 137 138 // BLayoutContextListener 139 virtual void LayoutContextLeft(BLayoutContext* context); 140 }; 141 142 // LocalLayouter 143 class BTwoDimensionalLayout::LocalLayouter : private BLayoutContextListener { 144 public: 145 LocalLayouter(BTwoDimensionalLayout* layout); 146 ~LocalLayouter(); 147 148 // interface for the BTwoDimensionalLayout class 149 150 BSize MinSize(); 151 BSize MaxSize(); 152 BSize PreferredSize(); 153 154 void InvalidateLayout(); 155 void Layout(BSize size); 156 157 BRect ItemFrame(Dimensions itemDimensions); 158 159 void ValidateMinMax(); 160 161 void DoHorizontalLayout(float width); 162 163 void InternalGetHeightForWidth(float width, 164 float* minHeight, float* maxHeight, 165 float* preferredHeight); 166 167 void AlignWith(LocalLayouter* other, 168 orientation orientation); 169 170 // Archiving stuff 171 status_t AddAlignedLayoutsToArchive(BArchiver* archiver); 172 status_t AddOwnerToArchive(BArchiver* archiver, 173 CompoundLayouter* requestedBy, 174 bool& _wasAvailable); 175 status_t AlignLayoutsFromArchive(BUnarchiver* unarchiver, 176 orientation posture); 177 178 179 // interface for the compound layout context 180 181 void PrepareItems( 182 CompoundLayouter* compoundLayouter); 183 int32 CountElements( 184 CompoundLayouter* compoundLayouter); 185 bool HasMultiElementItems( 186 CompoundLayouter* compoundLayouter); 187 188 void AddConstraints( 189 CompoundLayouter* compoundLayouter, 190 Layouter* layouter); 191 192 float Spacing(CompoundLayouter* compoundLayouter); 193 194 bool HasHeightForWidth(); 195 196 bool AddHeightForWidthConstraints( 197 VerticalCompoundLayouter* compoundLayouter, 198 Layouter* layouter, 199 BLayoutContext* context); 200 void SetHeightForWidthConstraintsAdded(bool added); 201 202 void SetCompoundLayouter( 203 CompoundLayouter* compoundLayouter, 204 orientation orientation); 205 206 void InternalInvalidateLayout( 207 CompoundLayouter* compoundLayouter); 208 209 // implementation private 210 private: 211 BTwoDimensionalLayout* fLayout; 212 CompoundLayouter* fHLayouter; 213 VerticalCompoundLayouter* fVLayouter; 214 BList fHeightForWidthItems; 215 216 // active layout context when doing last horizontal layout 217 BLayoutContext* fHorizontalLayoutContext; 218 float fHorizontalLayoutWidth; 219 bool fHeightForWidthConstraintsAdded; 220 221 void _SetHorizontalLayoutContext( 222 BLayoutContext* context, float width); 223 224 // BLayoutContextListener 225 virtual void LayoutContextLeft(BLayoutContext* context); 226 }; 227 228 229 // #pragma mark - 230 231 // archiving constants 232 namespace { 233 const char* const kHAlignedLayoutField = "BTwoDimensionalLayout:" 234 "halignedlayout"; 235 const char* const kVAlignedLayoutField = "BTwoDimensionalLayout:" 236 "valignedlayout"; 237 const char* const kInsetsField = "BTwoDimensionalLayout:insets"; 238 const char* const kSpacingField = "BTwoDimensionalLayout:spacing"; 239 // kSpacingField = {fHSpacing, fVSpacing} 240 } 241 242 243 BTwoDimensionalLayout::BTwoDimensionalLayout() 244 : 245 fLeftInset(0), 246 fRightInset(0), 247 fTopInset(0), 248 fBottomInset(0), 249 fHSpacing(0), 250 fVSpacing(0), 251 fLocalLayouter(new LocalLayouter(this)) 252 { 253 } 254 255 256 BTwoDimensionalLayout::BTwoDimensionalLayout(BMessage* from) 257 : 258 BAbstractLayout(from), 259 fLeftInset(0), 260 fRightInset(0), 261 fTopInset(0), 262 fBottomInset(0), 263 fHSpacing(0), 264 fVSpacing(0), 265 fLocalLayouter(new LocalLayouter(this)) 266 { 267 BRect insets; 268 from->FindRect(kInsetsField, &insets); 269 SetInsets(insets.left, insets.top, insets.right, insets.bottom); 270 271 from->FindFloat(kSpacingField, 0, &fHSpacing); 272 from->FindFloat(kSpacingField, 1, &fVSpacing); 273 } 274 275 276 BTwoDimensionalLayout::~BTwoDimensionalLayout() 277 { 278 delete fLocalLayouter; 279 } 280 281 282 void 283 BTwoDimensionalLayout::SetInsets(float left, float top, float right, 284 float bottom) 285 { 286 fLeftInset = BControlLook::ComposeSpacing(left); 287 fTopInset = BControlLook::ComposeSpacing(top); 288 fRightInset = BControlLook::ComposeSpacing(right); 289 fBottomInset = BControlLook::ComposeSpacing(bottom); 290 291 InvalidateLayout(); 292 } 293 294 295 void 296 BTwoDimensionalLayout::SetInsets(float horizontal, float vertical) 297 { 298 fLeftInset = BControlLook::ComposeSpacing(horizontal); 299 fRightInset = fLeftInset; 300 301 fTopInset = BControlLook::ComposeSpacing(vertical); 302 fBottomInset = fTopInset; 303 304 InvalidateLayout(); 305 } 306 307 308 void 309 BTwoDimensionalLayout::SetInsets(float insets) 310 { 311 fLeftInset = BControlLook::ComposeSpacing(insets); 312 fRightInset = fLeftInset; 313 fTopInset = fLeftInset; 314 fBottomInset = fLeftInset; 315 316 InvalidateLayout(); 317 } 318 319 320 void 321 BTwoDimensionalLayout::GetInsets(float* left, float* top, float* right, 322 float* bottom) const 323 { 324 if (left) 325 *left = fLeftInset; 326 if (top) 327 *top = fTopInset; 328 if (right) 329 *right = fRightInset; 330 if (bottom) 331 *bottom = fBottomInset; 332 } 333 334 335 void 336 BTwoDimensionalLayout::AlignLayoutWith(BTwoDimensionalLayout* other, 337 orientation orientation) 338 { 339 if (!other || other == this) 340 return; 341 342 fLocalLayouter->AlignWith(other->fLocalLayouter, orientation); 343 344 InvalidateLayout(); 345 } 346 347 348 BSize 349 BTwoDimensionalLayout::BaseMinSize() 350 { 351 _ValidateMinMax(); 352 return AddInsets(fLocalLayouter->MinSize()); 353 } 354 355 356 BSize 357 BTwoDimensionalLayout::BaseMaxSize() 358 { 359 _ValidateMinMax(); 360 return AddInsets(fLocalLayouter->MaxSize()); 361 } 362 363 364 BSize 365 BTwoDimensionalLayout::BasePreferredSize() 366 { 367 _ValidateMinMax(); 368 return AddInsets(fLocalLayouter->PreferredSize()); 369 } 370 371 372 BAlignment 373 BTwoDimensionalLayout::BaseAlignment() 374 { 375 return BAbstractLayout::BaseAlignment(); 376 } 377 378 379 bool 380 BTwoDimensionalLayout::HasHeightForWidth() 381 { 382 _ValidateMinMax(); 383 return fLocalLayouter->HasHeightForWidth(); 384 } 385 386 387 void 388 BTwoDimensionalLayout::GetHeightForWidth(float width, float* min, float* max, 389 float* preferred) 390 { 391 if (!HasHeightForWidth()) 392 return; 393 394 float outerSpacing = fLeftInset + fRightInset - 1; 395 fLocalLayouter->InternalGetHeightForWidth(BLayoutUtils::SubtractDistances( 396 width, outerSpacing), min, max, preferred); 397 AddInsets(min, max, preferred); 398 } 399 400 401 void 402 BTwoDimensionalLayout::SetFrame(BRect frame) 403 { 404 BAbstractLayout::SetFrame(frame); 405 } 406 407 408 status_t 409 BTwoDimensionalLayout::Archive(BMessage* into, bool deep) const 410 { 411 BArchiver archiver(into); 412 status_t err = BAbstractLayout::Archive(into, deep); 413 414 if (err == B_OK) { 415 BRect insets(fLeftInset, fTopInset, fRightInset, fBottomInset); 416 err = into->AddRect(kInsetsField, insets); 417 } 418 419 if (err == B_OK) 420 err = into->AddFloat(kSpacingField, fHSpacing); 421 422 if (err == B_OK) 423 err = into->AddFloat(kSpacingField, fVSpacing); 424 425 return archiver.Finish(err); 426 } 427 428 429 status_t 430 BTwoDimensionalLayout::AllArchived(BMessage* into) const 431 { 432 BArchiver archiver(into); 433 434 status_t err = BLayout::AllArchived(into); 435 if (err == B_OK) 436 err = fLocalLayouter->AddAlignedLayoutsToArchive(&archiver); 437 return err; 438 } 439 440 441 status_t 442 BTwoDimensionalLayout::AllUnarchived(const BMessage* from) 443 { 444 status_t err = BLayout::AllUnarchived(from); 445 if (err != B_OK) 446 return err; 447 448 BUnarchiver unarchiver(from); 449 err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_HORIZONTAL); 450 if (err == B_OK) 451 err = fLocalLayouter->AlignLayoutsFromArchive(&unarchiver, B_VERTICAL); 452 453 return err; 454 } 455 456 457 status_t 458 BTwoDimensionalLayout::ItemArchived(BMessage* into, BLayoutItem* item, 459 int32 index) const 460 { 461 return BAbstractLayout::ItemArchived(into, item, index); 462 } 463 464 465 status_t 466 BTwoDimensionalLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, 467 int32 index) 468 { 469 return BAbstractLayout::ItemUnarchived(from, item, index); 470 } 471 472 473 474 475 void 476 BTwoDimensionalLayout::LayoutInvalidated(bool children) 477 { 478 fLocalLayouter->InvalidateLayout(); 479 } 480 481 482 void 483 BTwoDimensionalLayout::DoLayout() 484 { 485 _ValidateMinMax(); 486 487 // layout the horizontal/vertical elements 488 BSize size(SubtractInsets(LayoutArea().Size())); 489 490 #ifdef DEBUG_LAYOUT 491 printf("BTwoDimensionalLayout::DerivedLayoutItems(): view: %p" 492 " size: (%.1f, %.1f)\n", View(), size.Width(), size.Height()); 493 #endif 494 495 fLocalLayouter->Layout(size); 496 497 // layout the items 498 BPoint itemOffset(LayoutArea().LeftTop()); 499 int itemCount = CountItems(); 500 for (int i = 0; i < itemCount; i++) { 501 BLayoutItem* item = ItemAt(i); 502 if (item->IsVisible()) { 503 Dimensions itemDimensions; 504 GetItemDimensions(item, &itemDimensions); 505 BRect frame = fLocalLayouter->ItemFrame(itemDimensions); 506 frame.left += fLeftInset; 507 frame.top += fTopInset; 508 frame.right += fLeftInset; 509 frame.bottom += fTopInset; 510 frame.OffsetBy(itemOffset); 511 { 512 #ifdef DEBUG_LAYOUT 513 printf(" frame for item %2d (view: %p): ", i, item->View()); 514 frame.PrintToStream(); 515 #endif 516 //BSize min(item->MinSize()); 517 //BSize max(item->MaxSize()); 518 //printf(" min: (%.1f, %.1f), max: (%.1f, %.1f)\n", min.width, min.height, 519 // max.width, max.height); 520 //if (item->HasHeightForWidth()) { 521 //float minHeight, maxHeight, preferredHeight; 522 //item->GetHeightForWidth(frame.Width(), &minHeight, &maxHeight, 523 // &preferredHeight); 524 //printf(" hfw: min: %.1f, max: %.1f, pref: %.1f\n", minHeight, maxHeight, 525 // preferredHeight); 526 //} 527 } 528 529 item->AlignInFrame(frame); 530 } 531 //else 532 //printf(" item %2d not visible", i); 533 } 534 } 535 536 537 BSize 538 BTwoDimensionalLayout::AddInsets(BSize size) 539 { 540 size.width = BLayoutUtils::AddDistances(size.width, 541 fLeftInset + fRightInset - 1); 542 size.height = BLayoutUtils::AddDistances(size.height, 543 fTopInset + fBottomInset - 1); 544 return size; 545 } 546 547 548 void 549 BTwoDimensionalLayout::AddInsets(float* minHeight, float* maxHeight, 550 float* preferredHeight) 551 { 552 float insets = fTopInset + fBottomInset - 1; 553 if (minHeight) 554 *minHeight = BLayoutUtils::AddDistances(*minHeight, insets); 555 if (maxHeight) 556 *maxHeight = BLayoutUtils::AddDistances(*maxHeight, insets); 557 if (preferredHeight) 558 *preferredHeight = BLayoutUtils::AddDistances(*preferredHeight, insets); 559 } 560 561 562 BSize 563 BTwoDimensionalLayout::SubtractInsets(BSize size) 564 { 565 size.width = BLayoutUtils::SubtractDistances(size.width, 566 fLeftInset + fRightInset - 1); 567 size.height = BLayoutUtils::SubtractDistances(size.height, 568 fTopInset + fBottomInset - 1); 569 return size; 570 } 571 572 573 void 574 BTwoDimensionalLayout::PrepareItems(orientation orientation) 575 { 576 } 577 578 579 bool 580 BTwoDimensionalLayout::HasMultiColumnItems() 581 { 582 return false; 583 } 584 585 586 bool 587 BTwoDimensionalLayout::HasMultiRowItems() 588 { 589 return false; 590 } 591 592 593 void 594 BTwoDimensionalLayout::_ValidateMinMax() 595 { 596 fLocalLayouter->ValidateMinMax(); 597 } 598 599 600 // #pragma mark - CompoundLayouter 601 602 603 BTwoDimensionalLayout::CompoundLayouter::CompoundLayouter( 604 orientation orientation) 605 : 606 fLayouter(NULL), 607 fLayoutInfo(NULL), 608 fOrientation(orientation), 609 fLocalLayouters(10), 610 fLayoutContext(NULL), 611 fLastLayoutSize(-1) 612 { 613 } 614 615 616 BTwoDimensionalLayout::CompoundLayouter::~CompoundLayouter() 617 { 618 delete fLayouter; 619 delete fLayoutInfo; 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 } else if (localLayouter->HasHeightForWidth()) { 925 // There is a height for width layouter and it has been initialized 926 // in the current layout context. So we just add the height for width 927 // constraints of the calling local layouter, if they haven't been 928 // added yet. 929 updateCachedInfo = localLayouter->AddHeightForWidthConstraints(this, 930 fHeightForWidthLayouter, context); 931 } 932 933 // update cached height for width info, if something changed 934 if (updateCachedInfo) { 935 // get the height for width info 936 fCachedMinHeightForWidth = fHeightForWidthLayouter->MinSize(); 937 fCachedMaxHeightForWidth = fHeightForWidthLayouter->MaxSize(); 938 fCachedPreferredHeightForWidth 939 = fHeightForWidthLayouter->PreferredSize(); 940 } 941 942 if (minHeight) 943 *minHeight = fCachedMinHeightForWidth; 944 if (maxHeight) 945 *maxHeight = fCachedMaxHeightForWidth; 946 if (preferredHeight) 947 *preferredHeight = fCachedPreferredHeightForWidth; 948 } 949 950 951 void 952 BTwoDimensionalLayout::VerticalCompoundLayouter::DoLayout(float size, 953 LocalLayouter* localLayouter, BLayoutContext* context) 954 { 955 Layouter* layouter; 956 if (_HasHeightForWidth()) { 957 float minHeight, maxHeight, preferredHeight; 958 InternalGetHeightForWidth(localLayouter, context, true, &minHeight, 959 &maxHeight, &preferredHeight); 960 size = max_c(size, minHeight); 961 layouter = fHeightForWidthLayouter; 962 } else 963 layouter = fLayouter; 964 965 layouter->Layout(fLayoutInfo, size); 966 } 967 968 969 bool 970 BTwoDimensionalLayout::VerticalCompoundLayouter::_HasHeightForWidth() 971 { 972 int32 count = fLocalLayouters.CountItems(); 973 for (int32 i = 0; i < count; i++) { 974 LocalLayouter* layouter = (LocalLayouter*)fLocalLayouters.ItemAt(i); 975 if (layouter->HasHeightForWidth()) 976 return true; 977 } 978 979 return false; 980 } 981 982 983 bool 984 BTwoDimensionalLayout::VerticalCompoundLayouter 985 ::_SetHeightForWidthLayoutContext(BLayoutContext* context) 986 { 987 if (context == fHeightForWidthLayoutContext) 988 return false; 989 990 if (fHeightForWidthLayoutContext != NULL) { 991 fHeightForWidthLayoutContext->RemoveListener(this); 992 fHeightForWidthLayoutContext = NULL; 993 } 994 995 // We can ignore the whole context business, if we have no more than one 996 // local layouter. We use the layout context only to recognize when calls 997 // of different local layouters belong to the same context. 998 if (fLocalLayouters.CountItems() <= 1) 999 return false; 1000 1001 fHeightForWidthLayoutContext = context; 1002 1003 if (fHeightForWidthLayoutContext != NULL) 1004 fHeightForWidthLayoutContext->AddListener(this); 1005 1006 InvalidateHeightForWidth(); 1007 1008 return true; 1009 } 1010 1011 1012 void 1013 BTwoDimensionalLayout::VerticalCompoundLayouter::LayoutContextLeft( 1014 BLayoutContext* context) 1015 { 1016 fHeightForWidthLayoutContext = NULL; 1017 } 1018 1019 1020 // #pragma mark - LocalLayouter 1021 1022 1023 BTwoDimensionalLayout::LocalLayouter::LocalLayouter( 1024 BTwoDimensionalLayout* layout) 1025 : 1026 fLayout(layout), 1027 fHLayouter(new CompoundLayouter(B_HORIZONTAL)), 1028 fVLayouter(new VerticalCompoundLayouter), 1029 fHeightForWidthItems(), 1030 fHorizontalLayoutContext(NULL), 1031 fHorizontalLayoutWidth(0), 1032 fHeightForWidthConstraintsAdded(false) 1033 { 1034 fHLayouter->AddLocalLayouter(this); 1035 fVLayouter->AddLocalLayouter(this); 1036 } 1037 1038 1039 BTwoDimensionalLayout::LocalLayouter::~LocalLayouter() 1040 { 1041 if (fHLayouter != NULL) { 1042 fHLayouter->RemoveLocalLayouter(this); 1043 fHLayouter->ReleaseReference(); 1044 } 1045 1046 if (fVLayouter != NULL) { 1047 fVLayouter->RemoveLocalLayouter(this); 1048 fVLayouter->ReleaseReference(); 1049 } 1050 } 1051 1052 1053 BSize 1054 BTwoDimensionalLayout::LocalLayouter::MinSize() 1055 { 1056 return BSize(fHLayouter->GetLayouter(true)->MinSize(), 1057 fVLayouter->GetLayouter(true)->MinSize()); 1058 } 1059 1060 1061 BSize 1062 BTwoDimensionalLayout::LocalLayouter::MaxSize() 1063 { 1064 return BSize(fHLayouter->GetLayouter(true)->MaxSize(), 1065 fVLayouter->GetLayouter(true)->MaxSize()); 1066 } 1067 1068 1069 BSize 1070 BTwoDimensionalLayout::LocalLayouter::PreferredSize() 1071 { 1072 return BSize(fHLayouter->GetLayouter(true)->PreferredSize(), 1073 fVLayouter->GetLayouter(true)->PreferredSize()); 1074 } 1075 1076 1077 void 1078 BTwoDimensionalLayout::LocalLayouter::InvalidateLayout() 1079 { 1080 fHLayouter->InvalidateLayout(); 1081 fVLayouter->InvalidateLayout(); 1082 } 1083 1084 1085 void 1086 BTwoDimensionalLayout::LocalLayouter::Layout(BSize size) 1087 { 1088 DoHorizontalLayout(size.width); 1089 fVLayouter->Layout(size.height, this, fLayout->LayoutContext()); 1090 } 1091 1092 1093 BRect 1094 BTwoDimensionalLayout::LocalLayouter::ItemFrame(Dimensions itemDimensions) 1095 { 1096 LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo(); 1097 LayoutInfo* vLayoutInfo = fVLayouter->GetLayoutInfo(); 1098 float x = hLayoutInfo->ElementLocation(itemDimensions.x); 1099 float y = vLayoutInfo->ElementLocation(itemDimensions.y); 1100 float width = hLayoutInfo->ElementRangeSize(itemDimensions.x, 1101 itemDimensions.width); 1102 float height = vLayoutInfo->ElementRangeSize(itemDimensions.y, 1103 itemDimensions.height); 1104 return BRect(x, y, x + width, y + height); 1105 } 1106 1107 1108 void 1109 BTwoDimensionalLayout::LocalLayouter::ValidateMinMax() 1110 { 1111 if (fHLayouter->IsMinMaxValid() && fVLayouter->IsMinMaxValid()) 1112 return; 1113 1114 if (!fHLayouter->IsMinMaxValid()) 1115 fHeightForWidthItems.MakeEmpty(); 1116 1117 _SetHorizontalLayoutContext(NULL, -1); 1118 1119 fHLayouter->ValidateMinMax(); 1120 fVLayouter->ValidateMinMax(); 1121 fLayout->ResetLayoutInvalidation(); 1122 } 1123 1124 1125 void 1126 BTwoDimensionalLayout::LocalLayouter::DoHorizontalLayout(float width) 1127 { 1128 BLayoutContext* context = fLayout->LayoutContext(); 1129 if (fHorizontalLayoutContext != context 1130 || width != fHorizontalLayoutWidth) { 1131 _SetHorizontalLayoutContext(context, width); 1132 fHLayouter->Layout(width, this, context); 1133 fVLayouter->InvalidateHeightForWidth(); 1134 } 1135 } 1136 1137 1138 void 1139 BTwoDimensionalLayout::LocalLayouter::InternalGetHeightForWidth(float width, 1140 float* minHeight, float* maxHeight, float* preferredHeight) 1141 { 1142 DoHorizontalLayout(width); 1143 fVLayouter->InternalGetHeightForWidth(this, fHorizontalLayoutContext, false, 1144 minHeight, maxHeight, preferredHeight); 1145 } 1146 1147 1148 void 1149 BTwoDimensionalLayout::LocalLayouter::AlignWith(LocalLayouter* other, 1150 orientation orientation) 1151 { 1152 if (orientation == B_HORIZONTAL) 1153 other->fHLayouter->AbsorbCompoundLayouter(fHLayouter); 1154 else 1155 other->fVLayouter->AbsorbCompoundLayouter(fVLayouter); 1156 } 1157 1158 1159 status_t 1160 BTwoDimensionalLayout::LocalLayouter::AddAlignedLayoutsToArchive( 1161 BArchiver* archiver) 1162 { 1163 status_t err = fHLayouter->AddAlignedLayoutsToArchive(archiver, this); 1164 1165 if (err == B_OK) 1166 err = fVLayouter->AddAlignedLayoutsToArchive(archiver, this); 1167 1168 return err; 1169 } 1170 1171 1172 status_t 1173 BTwoDimensionalLayout::LocalLayouter::AddOwnerToArchive(BArchiver* archiver, 1174 CompoundLayouter* requestedBy, bool& _wasAvailable) 1175 { 1176 const char* field = kHAlignedLayoutField; 1177 if (requestedBy == fVLayouter) 1178 field = kVAlignedLayoutField; 1179 1180 if ((_wasAvailable = archiver->IsArchived(fLayout))) 1181 return archiver->AddArchivable(field, fLayout); 1182 1183 return B_NAME_NOT_FOUND; 1184 } 1185 1186 1187 status_t 1188 BTwoDimensionalLayout::LocalLayouter::AlignLayoutsFromArchive( 1189 BUnarchiver* unarchiver, orientation posture) 1190 { 1191 const char* field = kHAlignedLayoutField; 1192 if (posture == B_VERTICAL) 1193 field = kVAlignedLayoutField; 1194 1195 int32 count; 1196 status_t err = unarchiver->ArchiveMessage()->GetInfo(field, NULL, &count); 1197 if (err == B_NAME_NOT_FOUND) 1198 return B_OK; 1199 1200 BTwoDimensionalLayout* retriever; 1201 for (int32 i = 0; i < count && err == B_OK; i++) { 1202 err = unarchiver->FindObject(field, i, 1203 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, retriever); 1204 1205 if (err == B_OK) 1206 retriever->AlignLayoutWith(fLayout, posture); 1207 } 1208 1209 return err; 1210 } 1211 1212 1213 void 1214 BTwoDimensionalLayout::LocalLayouter::PrepareItems( 1215 CompoundLayouter* compoundLayouter) 1216 { 1217 fLayout->PrepareItems(compoundLayouter->Orientation()); 1218 } 1219 1220 1221 int32 1222 BTwoDimensionalLayout::LocalLayouter::CountElements( 1223 CompoundLayouter* compoundLayouter) 1224 { 1225 if (compoundLayouter->Orientation() == B_HORIZONTAL) 1226 return fLayout->InternalCountColumns(); 1227 else 1228 return fLayout->InternalCountRows(); 1229 } 1230 1231 1232 bool 1233 BTwoDimensionalLayout::LocalLayouter::HasMultiElementItems( 1234 CompoundLayouter* compoundLayouter) 1235 { 1236 if (compoundLayouter->Orientation() == B_HORIZONTAL) 1237 return fLayout->HasMultiColumnItems(); 1238 else 1239 return fLayout->HasMultiRowItems(); 1240 } 1241 1242 1243 void 1244 BTwoDimensionalLayout::LocalLayouter::AddConstraints( 1245 CompoundLayouter* compoundLayouter, Layouter* layouter) 1246 { 1247 enum orientation orientation = compoundLayouter->Orientation(); 1248 int itemCount = fLayout->CountItems(); 1249 if (itemCount > 0) { 1250 for (int i = 0; i < itemCount; i++) { 1251 BLayoutItem* item = fLayout->ItemAt(i); 1252 if (item->IsVisible()) { 1253 Dimensions itemDimensions; 1254 fLayout->GetItemDimensions(item, &itemDimensions); 1255 1256 BSize min = item->MinSize(); 1257 BSize max = item->MaxSize(); 1258 BSize preferred = item->PreferredSize(); 1259 1260 if (orientation == B_HORIZONTAL) { 1261 layouter->AddConstraints( 1262 itemDimensions.x, 1263 itemDimensions.width, 1264 min.width, 1265 max.width, 1266 preferred.width); 1267 1268 if (item->HasHeightForWidth()) 1269 fHeightForWidthItems.AddItem(item); 1270 1271 } else { 1272 layouter->AddConstraints( 1273 itemDimensions.y, 1274 itemDimensions.height, 1275 min.height, 1276 max.height, 1277 preferred.height); 1278 } 1279 } 1280 } 1281 1282 // add column/row constraints 1283 ColumnRowConstraints constraints; 1284 int elementCount = CountElements(compoundLayouter); 1285 for (int element = 0; element < elementCount; element++) { 1286 fLayout->GetColumnRowConstraints(orientation, element, 1287 &constraints); 1288 layouter->SetWeight(element, constraints.weight); 1289 layouter->AddConstraints(element, 1, constraints.min, 1290 constraints.max, constraints.min); 1291 } 1292 } 1293 } 1294 1295 1296 float 1297 BTwoDimensionalLayout::LocalLayouter::Spacing( 1298 CompoundLayouter* compoundLayouter) 1299 { 1300 return (compoundLayouter->Orientation() == B_HORIZONTAL 1301 ? fLayout->fHSpacing : fLayout->fVSpacing); 1302 } 1303 1304 1305 bool 1306 BTwoDimensionalLayout::LocalLayouter::HasHeightForWidth() 1307 { 1308 return !fHeightForWidthItems.IsEmpty(); 1309 } 1310 1311 1312 bool 1313 BTwoDimensionalLayout::LocalLayouter::AddHeightForWidthConstraints( 1314 VerticalCompoundLayouter* compoundLayouter, Layouter* layouter, 1315 BLayoutContext* context) 1316 { 1317 if (context != fHorizontalLayoutContext) 1318 return false; 1319 1320 if (fHeightForWidthConstraintsAdded) 1321 return false; 1322 1323 LayoutInfo* hLayoutInfo = fHLayouter->GetLayoutInfo(); 1324 1325 // add the children's height for width constraints 1326 int32 itemCount = fHeightForWidthItems.CountItems(); 1327 for (int32 i = 0; i < itemCount; i++) { 1328 BLayoutItem* item = (BLayoutItem*)fHeightForWidthItems.ItemAt(i); 1329 Dimensions itemDimensions; 1330 fLayout->GetItemDimensions(item, &itemDimensions); 1331 1332 float minHeight, maxHeight, preferredHeight; 1333 item->GetHeightForWidth( 1334 hLayoutInfo->ElementRangeSize(itemDimensions.x, 1335 itemDimensions.width), 1336 &minHeight, &maxHeight, &preferredHeight); 1337 layouter->AddConstraints( 1338 itemDimensions.y, 1339 itemDimensions.height, 1340 minHeight, 1341 maxHeight, 1342 preferredHeight); 1343 } 1344 1345 SetHeightForWidthConstraintsAdded(true); 1346 1347 return true; 1348 } 1349 1350 1351 void 1352 BTwoDimensionalLayout::LocalLayouter::SetHeightForWidthConstraintsAdded( 1353 bool added) 1354 { 1355 fHeightForWidthConstraintsAdded = added; 1356 } 1357 1358 1359 void 1360 BTwoDimensionalLayout::LocalLayouter::SetCompoundLayouter( 1361 CompoundLayouter* compoundLayouter, orientation orientation) 1362 { 1363 CompoundLayouter* oldCompoundLayouter; 1364 if (orientation == B_HORIZONTAL) { 1365 oldCompoundLayouter = fHLayouter; 1366 fHLayouter = compoundLayouter; 1367 } else { 1368 oldCompoundLayouter = fVLayouter; 1369 fVLayouter = static_cast<VerticalCompoundLayouter*>(compoundLayouter); 1370 } 1371 1372 if (compoundLayouter == oldCompoundLayouter) 1373 return; 1374 1375 if (oldCompoundLayouter != NULL) { 1376 oldCompoundLayouter->RemoveLocalLayouter(this); 1377 oldCompoundLayouter->ReleaseReference(); 1378 } 1379 1380 if (compoundLayouter != NULL) 1381 compoundLayouter->AcquireReference(); 1382 1383 InternalInvalidateLayout(compoundLayouter); 1384 } 1385 1386 1387 void 1388 BTwoDimensionalLayout::LocalLayouter::InternalInvalidateLayout( 1389 CompoundLayouter* compoundLayouter) 1390 { 1391 _SetHorizontalLayoutContext(NULL, -1); 1392 1393 fLayout->BLayout::InvalidateLayout(); 1394 } 1395 1396 1397 void 1398 BTwoDimensionalLayout::LocalLayouter::_SetHorizontalLayoutContext( 1399 BLayoutContext* context, float width) 1400 { 1401 if (context != fHorizontalLayoutContext) { 1402 if (fHorizontalLayoutContext != NULL) 1403 fHorizontalLayoutContext->RemoveListener(this); 1404 1405 fHorizontalLayoutContext = context; 1406 1407 if (fHorizontalLayoutContext != NULL) 1408 fHorizontalLayoutContext->AddListener(this); 1409 } 1410 1411 fHorizontalLayoutWidth = width; 1412 } 1413 1414 1415 void 1416 BTwoDimensionalLayout::LocalLayouter::LayoutContextLeft(BLayoutContext* context) 1417 { 1418 fHorizontalLayoutContext = NULL; 1419 fHorizontalLayoutWidth = -1; 1420 } 1421 1422 1423 status_t 1424 BTwoDimensionalLayout::Perform(perform_code code, void* _data) 1425 { 1426 return BAbstractLayout::Perform(code, _data); 1427 } 1428 1429 1430 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout1() {} 1431 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout2() {} 1432 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout3() {} 1433 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout4() {} 1434 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout5() {} 1435 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout6() {} 1436 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout7() {} 1437 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout8() {} 1438 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout9() {} 1439 void BTwoDimensionalLayout::_ReservedTwoDimensionalLayout10() {} 1440 1441