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