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