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