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