1 /* 2 * Copyright 2007-2008, Christof Lutteroth, lutteroth@cs.auckland.ac.nz 3 * Copyright 2007-2008, James Kim, jkim202@ec.auckland.ac.nz 4 * Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de> 5 * Distributed under the terms of the MIT License. 6 */ 7 8 9 #include "ALMLayout.h" 10 11 #include <math.h> // for floor 12 #include <new> 13 #include <iostream> 14 15 #include "ViewLayoutItem.h" 16 17 #include "ResultType.h" 18 19 20 const BSize kUnsetSize(B_SIZE_UNSET, B_SIZE_UNSET); 21 const BSize kMinSize(0, 0); 22 const BSize kMaxSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 23 24 25 /*! 26 * Constructor. 27 * Creates new layout engine. 28 * 29 * If friendLayout is not NULL the solver of the friend layout is used. 30 */ 31 BALMLayout::BALMLayout(float spacing, BALMLayout* friendLayout) 32 : 33 fInset(0.0f), 34 fSpacing(spacing), 35 fCurrentArea(NULL) 36 { 37 fSolver = friendLayout ? friendLayout->Solver() : &fOwnSolver; 38 39 fLeft = AddXTab(); 40 fRight = AddXTab(); 41 fTop = AddYTab(); 42 fBottom = AddYTab(); 43 44 // the Left tab is always at x-position 0, and the Top tab is always at y-position 0 45 fLeft->SetRange(0, 0); 46 fTop->SetRange(0, 0); 47 48 // cached layout values 49 // need to be invalidated whenever the layout specification is changed 50 fMinSize = kUnsetSize; 51 fMaxSize = kUnsetSize; 52 fPreferredSize = kUnsetSize; 53 54 fPerformancePath = NULL; 55 56 fScaleWidth = fSolver->AddVariable(); 57 fScaleHeight = fSolver->AddVariable(); 58 } 59 60 61 BALMLayout::~BALMLayout() 62 { 63 delete fScaleWidth; 64 delete fScaleHeight; 65 } 66 67 68 /** 69 * Adds a new x-tab to the specification. 70 * 71 * @return the new x-tab 72 */ 73 XTab* 74 BALMLayout::AddXTab() 75 { 76 XTab* tab = new(std::nothrow) XTab(fSolver); 77 if (!tab) 78 return NULL; 79 if (!fSolver->AddVariable(tab)) { 80 delete tab; 81 return NULL; 82 } 83 84 return tab; 85 } 86 87 88 /** 89 * Adds a new y-tab to the specification. 90 * 91 * @return the new y-tab 92 */ 93 YTab* 94 BALMLayout::AddYTab() 95 { 96 YTab* tab = new(std::nothrow) YTab(fSolver); 97 if (!tab) 98 return NULL; 99 if (!fSolver->AddVariable(tab)) { 100 delete tab; 101 return NULL; 102 } 103 104 return tab; 105 } 106 107 108 /** 109 * Adds a new row to the specification. 110 * 111 * @return the new row 112 */ 113 Row* 114 BALMLayout::AddRow() 115 { 116 return new(std::nothrow) Row(this); 117 } 118 119 120 /** 121 * Adds a new row to the specification that is glued to the given y-tabs. 122 * 123 * @param top 124 * @param bottom 125 * @return the new row 126 */ 127 Row* 128 BALMLayout::AddRow(YTab* top, YTab* bottom) 129 { 130 Row* row = new(std::nothrow) Row(this); 131 if (top != NULL) 132 row->Constraints()->AddItem(row->Top()->IsEqual(top)); 133 if (bottom != NULL) 134 row->Constraints()->AddItem(row->Bottom()->IsEqual(bottom)); 135 return row; 136 } 137 138 139 /** 140 * Adds a new column to the specification. 141 * 142 * @return the new column 143 */ 144 Column* 145 BALMLayout::AddColumn() 146 { 147 return new(std::nothrow) Column(this); 148 } 149 150 151 /** 152 * Adds a new column to the specification that is glued to the given x-tabs. 153 * 154 * @param left 155 * @param right 156 * @return the new column 157 */ 158 Column* 159 BALMLayout::AddColumn(XTab* left, XTab* right) 160 { 161 Column* column = new(std::nothrow) Column(this); 162 if (left != NULL) 163 column->Constraints()->AddItem(column->Left()->IsEqual(left)); 164 if (right != NULL) 165 column->Constraints()->AddItem(column->Right()->IsEqual(right)); 166 return column; 167 } 168 169 170 /** 171 * Finds the area that contains the given control. 172 * 173 * @param control the control to look for 174 * @return the area that contains the control 175 */ 176 Area* 177 BALMLayout::AreaFor(const BView* control) const 178 { 179 return AreaFor(ItemAt(IndexOfView(const_cast<BView*>(control)))); 180 } 181 182 183 Area* 184 BALMLayout::AreaFor(const BLayoutItem* item) const 185 { 186 if (!item) 187 return NULL; 188 return static_cast<Area*>(item->LayoutData()); 189 } 190 191 192 Area* 193 BALMLayout::CurrentArea() const 194 { 195 return fCurrentArea; 196 } 197 198 199 void 200 BALMLayout::SetCurrentArea(const Area* area) 201 { 202 fCurrentArea = const_cast<Area*>(area); 203 } 204 205 206 void 207 BALMLayout::SetCurrentArea(const BView* view) 208 { 209 fCurrentArea = AreaFor(view); 210 } 211 212 213 void 214 BALMLayout::SetCurrentArea(const BLayoutItem* item) 215 { 216 fCurrentArea = AreaFor(item); 217 } 218 219 220 XTab* 221 BALMLayout::LeftOf(const BView* view) const 222 { 223 return AreaFor(view)->Left(); 224 } 225 226 227 XTab* 228 BALMLayout::LeftOf(const BLayoutItem* item) const 229 { 230 return AreaFor(item)->Left(); 231 } 232 233 234 XTab* 235 BALMLayout::RightOf(const BView* view) const 236 { 237 return AreaFor(view)->Right(); 238 } 239 240 241 XTab* 242 BALMLayout::RightOf(const BLayoutItem* item) const 243 { 244 return AreaFor(item)->Right(); 245 } 246 247 248 YTab* 249 BALMLayout::TopOf(const BView* view) const 250 { 251 return AreaFor(view)->Top(); 252 } 253 254 255 YTab* 256 BALMLayout::TopOf(const BLayoutItem* item) const 257 { 258 return AreaFor(item)->Top(); 259 } 260 261 262 YTab* 263 BALMLayout::BottomOf(const BView* view) const 264 { 265 return AreaFor(view)->Bottom(); 266 } 267 268 269 YTab* 270 BALMLayout::BottomOf(const BLayoutItem* item) const 271 { 272 return AreaFor(item)->Bottom(); 273 } 274 275 276 void 277 BALMLayout::BuildLayout(GroupItem& item, XTab* left, YTab* top, XTab* right, 278 YTab* bottom) 279 { 280 if (!left) 281 left = Left(); 282 if (!top) 283 top = Top(); 284 if (!right) 285 right = Right(); 286 if (!bottom) 287 bottom = Bottom(); 288 289 _ParseGroupItem(item, left, top, right, bottom); 290 } 291 292 293 void 294 BALMLayout::_ParseGroupItem(GroupItem& item, XTab* left, YTab* top, XTab* right, 295 YTab* bottom) 296 { 297 if (item.LayoutItem()) 298 AddItem(item.LayoutItem(), left, top, right, bottom); 299 else if (item.View()) { 300 AddView(item.View(), left, top, right, bottom); 301 } 302 else { 303 for (unsigned int i = 0; i < item.GroupItems().size(); i++) { 304 GroupItem& current = const_cast<GroupItem&>( 305 item.GroupItems()[i]); 306 if (item.Orientation() == B_HORIZONTAL) { 307 XTab* r = (i == item.GroupItems().size() - 1) ? right 308 : AddXTab(); 309 _ParseGroupItem(current, left, top, r, bottom); 310 left = r; 311 } 312 else { 313 YTab* b = (i == item.GroupItems().size() - 1) ? bottom 314 : AddYTab(); 315 _ParseGroupItem(current, left, top, right, b); 316 top = b; 317 } 318 } 319 } 320 } 321 322 323 BLayoutItem* 324 BALMLayout::AddView(BView* child) 325 { 326 return AddView(-1, child); 327 } 328 329 330 BLayoutItem* 331 BALMLayout::AddView(int32 index, BView* child) 332 { 333 return BAbstractLayout::AddView(index, child); 334 } 335 336 337 /** 338 * Adds a new area to the specification, automatically setting preferred size constraints. 339 * 340 * @param left left border 341 * @param top top border 342 * @param right right border 343 * @param bottom bottom border 344 * @param content the control which is the area content 345 * @return the new area 346 */ 347 Area* 348 BALMLayout::AddView(BView* view, XTab* left, YTab* top, XTab* right, 349 YTab* bottom) 350 { 351 BLayoutItem* item = _CreateLayoutItem(view); 352 Area* area = AddItem(item, left, top, right, bottom); 353 if (!area) { 354 delete item; 355 return NULL; 356 } 357 return area; 358 } 359 360 361 /** 362 * Adds a new area to the specification, automatically setting preferred size constraints. 363 * 364 * @param row the row that defines the top and bottom border 365 * @param column the column that defines the left and right border 366 * @param content the control which is the area content 367 * @return the new area 368 */ 369 Area* 370 BALMLayout::AddView(BView* view, Row* row, Column* column) 371 { 372 BLayoutItem* item = _CreateLayoutItem(view); 373 Area* area = AddItem(item, row, column); 374 if (!area) { 375 delete item; 376 return NULL; 377 } 378 return area; 379 } 380 381 382 Area* 383 BALMLayout::AddViewToRight(BView* view, XTab* right, YTab* top, YTab* bottom) 384 { 385 BLayoutItem* item = _CreateLayoutItem(view); 386 Area* area = AddItemToRight(item, right, top, bottom); 387 if (!area) { 388 delete item; 389 return NULL; 390 } 391 return area; 392 } 393 394 395 Area* 396 BALMLayout::AddViewToLeft(BView* view, XTab* left, YTab* top, YTab* bottom) 397 { 398 BLayoutItem* item = _CreateLayoutItem(view); 399 Area* area = AddItemToLeft(item, left, top, bottom); 400 if (!area) { 401 delete item; 402 return NULL; 403 } 404 return area; 405 } 406 407 408 Area* 409 BALMLayout::AddViewToTop(BView* view, YTab* top, XTab* left, XTab* right) 410 { 411 BLayoutItem* item = _CreateLayoutItem(view); 412 Area* area = AddItemToTop(item, top, left, right); 413 if (!area) { 414 delete item; 415 return NULL; 416 } 417 return area; 418 } 419 420 421 Area* 422 BALMLayout::AddViewToBottom(BView* view, YTab* bottom, XTab* left, XTab* right) 423 { 424 BLayoutItem* item = _CreateLayoutItem(view); 425 Area* area = AddItemToBottom(item, bottom, left, right); 426 if (!area) { 427 delete item; 428 return NULL; 429 } 430 return area; 431 } 432 433 434 bool 435 BALMLayout::AddItem(BLayoutItem* item) 436 { 437 return AddItem(-1, item); 438 } 439 440 441 bool 442 BALMLayout::AddItem(int32 index, BLayoutItem* item) 443 { 444 if (!item) 445 return false; 446 447 // simply add the item at the upper right corner of the previous item 448 // TODO maybe find a more elegant solution 449 XTab* left = Left(); 450 YTab* top = Top(); 451 452 // check range 453 if (index < 0 || index > CountItems()) 454 index = CountItems(); 455 456 // for index = 0 we already have set the right tabs 457 if (index != 0) { 458 BLayoutItem* prevItem = ItemAt(index - 1); 459 Area* area = AreaFor(prevItem); 460 if (area) { 461 left = area->Right(); 462 top = area->Top(); 463 } 464 } 465 Area* area = AddItem(item, left, top); 466 return area ? true : false; 467 } 468 469 470 Area* 471 BALMLayout::AddItem(BLayoutItem* item, XTab* left, YTab* top, XTab* right, 472 YTab* bottom) 473 { 474 if (!right) 475 right = AddXTab(); 476 if (!bottom) 477 bottom = AddYTab(); 478 479 if (!BAbstractLayout::AddItem(-1, item)) 480 return NULL; 481 Area* area = AreaFor(item); 482 if (!area) 483 return NULL; 484 fCurrentArea = area; 485 486 area->_Init(fSolver, left, top, right, bottom, fScaleWidth, fScaleHeight); 487 return area; 488 } 489 490 491 Area* 492 BALMLayout::AddItem(BLayoutItem* item, Row* row, Column* column) 493 { 494 if (!BAbstractLayout::AddItem(-1, item)) 495 return NULL; 496 Area* area = AreaFor(item); 497 if (!area) 498 return NULL; 499 fCurrentArea = area; 500 501 area->_Init(fSolver, row, column, fScaleWidth, fScaleHeight); 502 return area; 503 } 504 505 506 Area* 507 BALMLayout::AddItemToRight(BLayoutItem* item, XTab* right, YTab* top, 508 YTab* bottom) 509 { 510 XTab* left = fCurrentArea->Right(); 511 if (!right) 512 right = AddXTab(); 513 if (!top) 514 top = fCurrentArea->Top(); 515 if (!bottom) 516 bottom = fCurrentArea->Bottom(); 517 518 return AddItem(item, left, top, right, bottom); 519 } 520 521 522 Area* 523 BALMLayout::AddItemToLeft(BLayoutItem* item, XTab* left, YTab* top, 524 YTab* bottom) 525 { 526 if (!left) 527 left = AddXTab(); 528 XTab* right = fCurrentArea->Left(); 529 if (!top) 530 top = fCurrentArea->Top(); 531 if (!bottom) 532 bottom = fCurrentArea->Bottom(); 533 534 return AddItem(item, left, top, right, bottom); 535 } 536 537 538 Area* 539 BALMLayout::AddItemToTop(BLayoutItem* item, YTab* top, XTab* left, XTab* right) 540 { 541 if (!left) 542 left = fCurrentArea->Left(); 543 if (!right) 544 right = fCurrentArea->Right(); 545 if (!top) 546 top = AddYTab(); 547 YTab* bottom = fCurrentArea->Top(); 548 549 return AddItem(item, left, top, right, bottom); 550 } 551 552 553 Area* 554 BALMLayout::AddItemToBottom(BLayoutItem* item, YTab* bottom, XTab* left, 555 XTab* right) 556 { 557 if (!left) 558 left = fCurrentArea->Left(); 559 if (!right) 560 right = fCurrentArea->Right(); 561 YTab* top = fCurrentArea->Bottom(); 562 if (!bottom) 563 bottom = AddYTab(); 564 565 return AddItem(item, left, top, right, bottom); 566 } 567 568 569 /** 570 * Gets the left variable. 571 */ 572 XTab* 573 BALMLayout::Left() const 574 { 575 return fLeft; 576 } 577 578 579 /** 580 * Gets the right variable. 581 */ 582 XTab* 583 BALMLayout::Right() const 584 { 585 return fRight; 586 } 587 588 589 /** 590 * Gets the top variable. 591 */ 592 YTab* 593 BALMLayout::Top() const 594 { 595 return fTop; 596 } 597 598 599 /** 600 * Gets the bottom variable. 601 */ 602 YTab* 603 BALMLayout::Bottom() const 604 { 605 return fBottom; 606 } 607 608 609 /** 610 * Gets minimum size. 611 */ 612 BSize 613 BALMLayout::BaseMinSize() { 614 if (fMinSize == kUnsetSize) 615 fMinSize = _CalculateMinSize(); 616 return fMinSize; 617 } 618 619 620 /** 621 * Gets maximum size. 622 */ 623 BSize 624 BALMLayout::BaseMaxSize() 625 { 626 if (fMaxSize == kUnsetSize) 627 fMaxSize = _CalculateMaxSize(); 628 return fMaxSize; 629 } 630 631 632 /** 633 * Gets preferred size. 634 */ 635 BSize 636 BALMLayout::BasePreferredSize() 637 { 638 if (fPreferredSize == kUnsetSize) 639 fPreferredSize = _CalculatePreferredSize(); 640 return fPreferredSize; 641 } 642 643 644 /** 645 * Gets the alignment. 646 */ 647 BAlignment 648 BALMLayout::BaseAlignment() 649 { 650 BAlignment alignment; 651 alignment.SetHorizontal(B_ALIGN_HORIZONTAL_CENTER); 652 alignment.SetVertical(B_ALIGN_VERTICAL_CENTER); 653 return alignment; 654 } 655 656 657 /** 658 * Invalidates the layout. 659 * Resets minimum/maximum/preferred size. 660 */ 661 void 662 BALMLayout::InvalidateLayout(bool children) 663 { 664 BLayout::InvalidateLayout(children); 665 fMinSize = kUnsetSize; 666 fMaxSize = kUnsetSize; 667 fPreferredSize = kUnsetSize; 668 } 669 670 671 bool 672 BALMLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 673 { 674 item->SetLayoutData(new(std::nothrow) Area(item)); 675 return item->LayoutData() != NULL; 676 } 677 678 679 void 680 BALMLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 681 { 682 if (Area* area = AreaFor(item)) { 683 item->SetLayoutData(NULL); 684 delete area; 685 } 686 } 687 688 689 /** 690 * Calculate and set the layout. 691 * If no layout specification is given, a specification is reverse engineered automatically. 692 */ 693 void 694 BALMLayout::DerivedLayoutItems() 695 { 696 _UpdateAreaConstraints(); 697 698 // Enforced absolute positions of Right and Bottom 699 BRect area(LayoutArea()); 700 Right()->SetRange(area.right, area.right); 701 Bottom()->SetRange(area.bottom, area.bottom); 702 703 _SolveLayout(); 704 705 // if new layout is infeasible, use previous layout 706 if (fSolver->Result() == INFEASIBLE) 707 return; 708 709 if (fSolver->Result() != OPTIMAL) { 710 fSolver->Save("failed-layout.txt"); 711 printf("Could not solve the layout specification (%d). ", 712 fSolver->Result()); 713 printf("Saved specification in file failed-layout.txt\n"); 714 } 715 716 // set the calculated positions and sizes for every area 717 for (int32 i = 0; i < CountItems(); i++) 718 AreaFor(ItemAt(i))->_DoLayout(); 719 } 720 721 722 /** 723 * Gets the path of the performance log file. 724 * 725 * @return the path of the performance log file 726 */ 727 char* 728 BALMLayout::PerformancePath() const 729 { 730 return fPerformancePath; 731 } 732 733 734 /** 735 * Sets the path of the performance log file. 736 * 737 * @param path the path of the performance log file 738 */ 739 void 740 BALMLayout::SetPerformancePath(char* path) 741 { 742 fPerformancePath = path; 743 } 744 745 746 LinearSpec* 747 BALMLayout::Solver() const 748 { 749 return const_cast<LinearSpec*>(fSolver); 750 } 751 752 753 void 754 BALMLayout::SetInset(float inset) 755 { 756 fInset = inset; 757 } 758 759 760 float 761 BALMLayout::Inset() const 762 { 763 return fInset; 764 } 765 766 767 void 768 BALMLayout::SetSpacing(float spacing) 769 { 770 fSpacing = spacing; 771 } 772 773 774 float 775 BALMLayout::Spacing() const 776 { 777 return fSpacing; 778 } 779 780 781 BLayoutItem* 782 BALMLayout::_CreateLayoutItem(BView* view) 783 { 784 return new(std::nothrow) BViewLayoutItem(view); 785 } 786 787 788 void 789 BALMLayout::_SolveLayout() 790 { 791 // Try to solve the layout until the result is OPTIMAL or INFEASIBLE, 792 // maximally 15 tries sometimes the solving algorithm encounters numerical 793 // problems (NUMFAILURE), and repeating the solving often helps to overcome 794 // them. 795 BFile* file = NULL; 796 if (fPerformancePath != NULL) { 797 file = new(std::nothrow) BFile(fPerformancePath, 798 B_READ_WRITE | B_CREATE_FILE | B_OPEN_AT_END); 799 } 800 801 ResultType result; 802 for (int32 tries = 0; tries < 15; tries++) { 803 result = fSolver->Solve(); 804 if (fPerformancePath != NULL) { 805 /*char buffer [100]; 806 file->Write(buffer, sprintf(buffer, "%d\t%fms\t#vars=%ld\t" 807 "#constraints=%ld\n", result, fSolver->SolvingTime(), 808 fSolver->Variables()->CountItems(), 809 fSolver->Constraints()->CountItems()));*/ 810 } 811 if (result == OPTIMAL || result == INFEASIBLE) 812 break; 813 } 814 delete file; 815 } 816 817 818 /** 819 * Caculates the miminum size. 820 */ 821 BSize 822 BALMLayout::_CalculateMinSize() 823 { 824 _UpdateAreaConstraints(); 825 826 SummandList* newObjFunction = new(std::nothrow) SummandList(2); 827 newObjFunction->AddItem(new(std::nothrow) Summand(1.0, fRight)); 828 newObjFunction->AddItem(new(std::nothrow) Summand(1.0, fBottom)); 829 SummandList* oldObjFunction = fSolver->SwapObjectiveFunction( 830 newObjFunction); 831 _SolveLayout(); 832 fSolver->SetObjectiveFunction(oldObjFunction); 833 834 if (fSolver->Result() == UNBOUNDED) 835 return kMinSize; 836 if (fSolver->Result() != OPTIMAL) { 837 fSolver->Save("failed-layout.txt"); 838 printf("Could not solve the layout specification (%d). " 839 "Saved specification in file failed-layout.txt", fSolver->Result()); 840 } 841 842 return BSize(Right()->Value() - Left()->Value(), 843 Bottom()->Value() - Top()->Value()); 844 } 845 846 847 /** 848 * Caculates the maximum size. 849 */ 850 BSize 851 BALMLayout::_CalculateMaxSize() 852 { 853 _UpdateAreaConstraints(); 854 855 SummandList* newObjFunction = new(std::nothrow) SummandList(2); 856 newObjFunction->AddItem(new(std::nothrow) Summand(-1.0, fRight)); 857 newObjFunction->AddItem(new(std::nothrow) Summand(-1.0, fBottom)); 858 SummandList* oldObjFunction = fSolver->SwapObjectiveFunction( 859 newObjFunction); 860 _SolveLayout(); 861 fSolver->SetObjectiveFunction(oldObjFunction); 862 863 if (fSolver->Result() == UNBOUNDED) 864 return kMaxSize; 865 if (fSolver->Result() != OPTIMAL) { 866 fSolver->Save("failed-layout.txt"); 867 printf("Could not solve the layout specification (%d). " 868 "Saved specification in file failed-layout.txt", fSolver->Result()); 869 } 870 871 return BSize(Right()->Value() - Left()->Value(), 872 Bottom()->Value() - Top()->Value()); 873 } 874 875 876 /** 877 * Caculates the preferred size. 878 */ 879 BSize 880 BALMLayout::_CalculatePreferredSize() 881 { 882 _UpdateAreaConstraints(); 883 884 _SolveLayout(); 885 if (fSolver->Result() != OPTIMAL) { 886 fSolver->Save("failed-layout.txt"); 887 printf("Could not solve the layout specification (%d). " 888 "Saved specification in file failed-layout.txt", fSolver->Result()); 889 } 890 891 return BSize(Right()->Value() - Left()->Value(), 892 Bottom()->Value() - Top()->Value()); 893 } 894 895 896 void 897 BALMLayout::_UpdateAreaConstraints() 898 { 899 for (int i = 0; i < CountItems(); i++) 900 AreaFor(ItemAt(i))->InvalidateSizeConstraints(); 901 } 902