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