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