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 BLayoutItem* 252 BALMLayout::AddView(BView* child) 253 { 254 return AddView(-1, child); 255 } 256 257 258 BLayoutItem* 259 BALMLayout::AddView(int32 index, BView* child) 260 { 261 return BAbstractLayout::AddView(index, child); 262 } 263 264 265 /** 266 * Adds a new area to the specification, automatically setting preferred size constraints. 267 * 268 * @param left left border 269 * @param top top border 270 * @param right right border 271 * @param bottom bottom border 272 * @param content the control which is the area content 273 * @return the new area 274 */ 275 Area* 276 BALMLayout::AddView(BView* view, XTab* left, YTab* top, XTab* right, 277 YTab* bottom) 278 { 279 BLayoutItem* item = _CreateLayoutItem(view); 280 Area* area = AddItem(item, left, top, right, bottom); 281 if (!area) { 282 delete item; 283 return NULL; 284 } 285 return area; 286 } 287 288 289 /** 290 * Adds a new area to the specification, automatically setting preferred size constraints. 291 * 292 * @param row the row that defines the top and bottom border 293 * @param column the column that defines the left and right border 294 * @param content the control which is the area content 295 * @return the new area 296 */ 297 Area* 298 BALMLayout::AddView(BView* view, Row* row, Column* column) 299 { 300 BLayoutItem* item = _CreateLayoutItem(view); 301 Area* area = AddItem(item, row, column); 302 if (!area) { 303 delete item; 304 return NULL; 305 } 306 return area; 307 } 308 309 310 Area* 311 BALMLayout::AddViewToRight(BView* view, XTab* right, YTab* top, YTab* bottom) 312 { 313 BLayoutItem* item = _CreateLayoutItem(view); 314 Area* area = AddItemToRight(item, right, top, bottom); 315 if (!area) { 316 delete item; 317 return NULL; 318 } 319 return area; 320 } 321 322 323 Area* 324 BALMLayout::AddViewToLeft(BView* view, XTab* left, YTab* top, YTab* bottom) 325 { 326 BLayoutItem* item = _CreateLayoutItem(view); 327 Area* area = AddItemToLeft(item, left, top, bottom); 328 if (!area) { 329 delete item; 330 return NULL; 331 } 332 return area; 333 } 334 335 336 Area* 337 BALMLayout::AddViewToTop(BView* view, YTab* top, XTab* left, XTab* right) 338 { 339 BLayoutItem* item = _CreateLayoutItem(view); 340 Area* area = AddItemToTop(item, top, left, right); 341 if (!area) { 342 delete item; 343 return NULL; 344 } 345 return area; 346 } 347 348 349 Area* 350 BALMLayout::AddViewToBottom(BView* view, YTab* bottom, XTab* left, XTab* right) 351 { 352 BLayoutItem* item = _CreateLayoutItem(view); 353 Area* area = AddItemToBottom(item, bottom, left, right); 354 if (!area) { 355 delete item; 356 return NULL; 357 } 358 return area; 359 } 360 361 362 bool 363 BALMLayout::AddItem(BLayoutItem* item) 364 { 365 return AddItem(-1, item); 366 } 367 368 369 bool 370 BALMLayout::AddItem(int32 index, BLayoutItem* item) 371 { 372 if (!item) 373 return NULL; 374 375 // simply add the item at the upper right corner of the previous item 376 // TODO maybe find a more elegant solution 377 XTab* left = Left(); 378 YTab* top = Top(); 379 380 // check range 381 if (index < 0 || index > CountItems()) 382 index = CountItems(); 383 384 // for index = 0 we already have set the right tabs 385 if (index != 0) { 386 BLayoutItem* prevItem = ItemAt(index - 1); 387 Area* area = AreaFor(prevItem); 388 if (area) { 389 left = area->Right(); 390 top = area->Top(); 391 } 392 } 393 Area* area = AddItem(item, left, top); 394 return area ? true : false; 395 } 396 397 398 Area* 399 BALMLayout::AddItem(BLayoutItem* item, XTab* left, YTab* top, XTab* right, 400 YTab* bottom) 401 { 402 if (!right) 403 right = AddXTab(); 404 if (!bottom) 405 bottom = AddYTab(); 406 407 if (!BAbstractLayout::AddItem(-1, item)) 408 return NULL; 409 Area* area = AreaFor(item); 410 if (!area) 411 return NULL; 412 fCurrentArea = area; 413 414 area->_Init(&fSolver, left, top, right, bottom); 415 return area; 416 } 417 418 419 Area* 420 BALMLayout::AddItem(BLayoutItem* item, Row* row, Column* column) 421 { 422 if (!BAbstractLayout::AddItem(-1, item)) 423 return NULL; 424 Area* area = AreaFor(item); 425 if (!area) 426 return NULL; 427 fCurrentArea = area; 428 429 area->_Init(&fSolver, row, column); 430 return area; 431 } 432 433 434 Area* 435 BALMLayout::AddItemToRight(BLayoutItem* item, XTab* right, YTab* top, 436 YTab* bottom) 437 { 438 XTab* left = fCurrentArea->Right(); 439 if (!right) 440 right = AddXTab(); 441 if (!top) 442 top = fCurrentArea->Top(); 443 if (!bottom) 444 bottom = fCurrentArea->Bottom(); 445 446 return AddItem(item, left, top, right, bottom); 447 } 448 449 450 Area* 451 BALMLayout::AddItemToLeft(BLayoutItem* item, XTab* left, YTab* top, 452 YTab* bottom) 453 { 454 if (!left) 455 left = AddXTab(); 456 XTab* right = fCurrentArea->Left(); 457 if (!top) 458 top = fCurrentArea->Top(); 459 if (!bottom) 460 bottom = fCurrentArea->Bottom(); 461 462 return AddItem(item, left, top, right, bottom); 463 } 464 465 466 Area* 467 BALMLayout::AddItemToTop(BLayoutItem* item, YTab* top, XTab* left, XTab* right) 468 { 469 if (!left) 470 left = fCurrentArea->Left(); 471 if (!right) 472 right = fCurrentArea->Right(); 473 if (!top) 474 top = AddYTab(); 475 YTab* bottom = fCurrentArea->Top(); 476 477 return AddItem(item, left, top, right, bottom); 478 } 479 480 481 Area* 482 BALMLayout::AddItemToBottom(BLayoutItem* item, YTab* bottom, XTab* left, 483 XTab* right) 484 { 485 if (!left) 486 left = fCurrentArea->Left(); 487 if (!right) 488 right = fCurrentArea->Right(); 489 YTab* top = fCurrentArea->Bottom(); 490 if (!bottom) 491 bottom = AddYTab(); 492 493 return AddItem(item, left, top, right, bottom); 494 } 495 496 497 /** 498 * Gets the left variable. 499 */ 500 XTab* 501 BALMLayout::Left() const 502 { 503 return fLeft; 504 } 505 506 507 /** 508 * Gets the right variable. 509 */ 510 XTab* 511 BALMLayout::Right() const 512 { 513 return fRight; 514 } 515 516 517 /** 518 * Gets the top variable. 519 */ 520 YTab* 521 BALMLayout::Top() const 522 { 523 return fTop; 524 } 525 526 527 /** 528 * Gets the bottom variable. 529 */ 530 YTab* 531 BALMLayout::Bottom() const 532 { 533 return fBottom; 534 } 535 536 537 /** 538 * Gets minimum size. 539 */ 540 BSize 541 BALMLayout::BaseMinSize() { 542 if (fMinSize == kUnsetSize) 543 fMinSize = _CalculateMinSize(); 544 return fMinSize; 545 } 546 547 548 /** 549 * Gets maximum size. 550 */ 551 BSize 552 BALMLayout::BaseMaxSize() 553 { 554 if (fMaxSize == kUnsetSize) 555 fMaxSize = _CalculateMaxSize(); 556 return fMaxSize; 557 } 558 559 560 /** 561 * Gets preferred size. 562 */ 563 BSize 564 BALMLayout::BasePreferredSize() 565 { 566 if (fPreferredSize == kUnsetSize) 567 fPreferredSize = _CalculatePreferredSize(); 568 return fPreferredSize; 569 } 570 571 572 /** 573 * Gets the alignment. 574 */ 575 BAlignment 576 BALMLayout::BaseAlignment() 577 { 578 BAlignment alignment; 579 alignment.SetHorizontal(B_ALIGN_HORIZONTAL_CENTER); 580 alignment.SetVertical(B_ALIGN_VERTICAL_CENTER); 581 return alignment; 582 } 583 584 585 /** 586 * Invalidates the layout. 587 * Resets minimum/maximum/preferred size. 588 */ 589 void 590 BALMLayout::InvalidateLayout(bool children) 591 { 592 BLayout::InvalidateLayout(children); 593 fMinSize = kUnsetSize; 594 fMaxSize = kUnsetSize; 595 fPreferredSize = kUnsetSize; 596 } 597 598 599 bool 600 BALMLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 601 { 602 item->SetLayoutData(new(std::nothrow) Area(item)); 603 return item->LayoutData() != NULL; 604 } 605 606 607 void 608 BALMLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 609 { 610 if (Area* area = AreaFor(item)) { 611 item->SetLayoutData(NULL); 612 delete area; 613 } 614 } 615 616 617 /** 618 * Calculate and set the layout. 619 * If no layout specification is given, a specification is reverse engineered automatically. 620 */ 621 void 622 BALMLayout::DerivedLayoutItems() 623 { 624 _UpdateAreaConstraints(); 625 626 // Enforced absolute positions of Right and Bottom 627 BRect area(LayoutArea()); 628 Right()->SetRange(area.right, area.right); 629 Bottom()->SetRange(area.bottom, area.bottom); 630 631 _SolveLayout(); 632 633 // if new layout is infasible, use previous layout 634 if (fSolver.Result() == INFEASIBLE) 635 return; 636 637 if (fSolver.Result() != OPTIMAL) { 638 fSolver.Save("failed-layout.txt"); 639 printf("Could not solve the layout specification (%d). ", 640 fSolver.Result()); 641 printf("Saved specification in file failed-layout.txt\n"); 642 } 643 644 // set the calculated positions and sizes for every area 645 for (int32 i = 0; i < CountItems(); i++) 646 AreaFor(ItemAt(i))->_DoLayout(); 647 } 648 649 650 /** 651 * Gets the path of the performance log file. 652 * 653 * @return the path of the performance log file 654 */ 655 char* 656 BALMLayout::PerformancePath() const 657 { 658 return fPerformancePath; 659 } 660 661 662 /** 663 * Sets the path of the performance log file. 664 * 665 * @param path the path of the performance log file 666 */ 667 void 668 BALMLayout::SetPerformancePath(char* path) 669 { 670 fPerformancePath = path; 671 } 672 673 674 LinearSpec* 675 BALMLayout::Solver() const 676 { 677 return const_cast<LinearSpec*>(&fSolver); 678 } 679 680 681 void 682 BALMLayout::SetInset(float inset) 683 { 684 fInset = inset; 685 } 686 687 688 float 689 BALMLayout::Inset() const 690 { 691 return fInset; 692 } 693 694 695 void 696 BALMLayout::SetSpacing(float spacing) 697 { 698 fSpacing = spacing; 699 } 700 701 702 float 703 BALMLayout::Spacing() const 704 { 705 return fSpacing; 706 } 707 708 709 BLayoutItem* 710 BALMLayout::_CreateLayoutItem(BView* view) 711 { 712 return new(std::nothrow) BViewLayoutItem(view); 713 } 714 715 716 void 717 BALMLayout::_SolveLayout() 718 { 719 // Try to solve the layout until the result is OPTIMAL or INFEASIBLE, 720 // maximally 15 tries sometimes the solving algorithm encounters numerical 721 // problems (NUMFAILURE), and repeating the solving often helps to overcome 722 // them. 723 BFile* file = NULL; 724 if (fPerformancePath != NULL) { 725 file = new BFile(fPerformancePath, 726 B_READ_WRITE | B_CREATE_FILE | B_OPEN_AT_END); 727 } 728 729 ResultType result; 730 for (int32 tries = 0; tries < 15; tries++) { 731 result = fSolver.Solve(); 732 if (fPerformancePath != NULL) { 733 /*char buffer [100]; 734 file->Write(buffer, sprintf(buffer, "%d\t%fms\t#vars=%ld\t" 735 "#constraints=%ld\n", result, fSolver.SolvingTime(), 736 fSolver.Variables()->CountItems(), 737 fSolver.Constraints()->CountItems()));*/ 738 } 739 if (result == OPTIMAL || result == INFEASIBLE) 740 break; 741 } 742 delete file; 743 } 744 745 746 /** 747 * Caculates the miminum size. 748 */ 749 BSize 750 BALMLayout::_CalculateMinSize() 751 { 752 _UpdateAreaConstraints(); 753 754 SummandList* oldObjFunction = fSolver.ObjFunction(); 755 SummandList* newObjFunction = new SummandList(2); 756 newObjFunction->AddItem(new Summand(1.0, fRight)); 757 newObjFunction->AddItem(new Summand(1.0, fBottom)); 758 fSolver.SetObjFunction(newObjFunction); 759 _SolveLayout(); 760 fSolver.SetObjFunction(oldObjFunction); 761 fSolver.UpdateObjFunction(); 762 delete newObjFunction->ItemAt(0); 763 delete newObjFunction->ItemAt(1); 764 delete newObjFunction; 765 766 if (fSolver.Result() == UNBOUNDED) 767 return kMinSize; 768 if (fSolver.Result() != OPTIMAL) { 769 fSolver.Save("failed-layout.txt"); 770 printf("Could not solve the layout specification (%d). " 771 "Saved specification in file failed-layout.txt", fSolver.Result()); 772 } 773 774 return BSize(Right()->Value() - Left()->Value(), 775 Bottom()->Value() - Top()->Value()); 776 } 777 778 779 /** 780 * Caculates the maximum size. 781 */ 782 BSize 783 BALMLayout::_CalculateMaxSize() 784 { 785 _UpdateAreaConstraints(); 786 787 SummandList* oldObjFunction = fSolver.ObjFunction(); 788 SummandList* newObjFunction = new SummandList(2); 789 newObjFunction->AddItem(new Summand(-1.0, fRight)); 790 newObjFunction->AddItem(new Summand(-1.0, fBottom)); 791 fSolver.SetObjFunction(newObjFunction); 792 _SolveLayout(); 793 fSolver.SetObjFunction(oldObjFunction); 794 fSolver.UpdateObjFunction(); 795 delete newObjFunction->ItemAt(0); 796 delete newObjFunction->ItemAt(1); 797 delete newObjFunction; 798 799 if (fSolver.Result() == UNBOUNDED) 800 return kMaxSize; 801 if (fSolver.Result() != OPTIMAL) { 802 fSolver.Save("failed-layout.txt"); 803 printf("Could not solve the layout specification (%d). " 804 "Saved specification in file failed-layout.txt", fSolver.Result()); 805 } 806 807 return BSize(Right()->Value() - Left()->Value(), 808 Bottom()->Value() - Top()->Value()); 809 } 810 811 812 /** 813 * Caculates the preferred size. 814 */ 815 BSize 816 BALMLayout::_CalculatePreferredSize() 817 { 818 _UpdateAreaConstraints(); 819 820 _SolveLayout(); 821 if (fSolver.Result() != OPTIMAL) { 822 fSolver.Save("failed-layout.txt"); 823 printf("Could not solve the layout specification (%d). " 824 "Saved specification in file failed-layout.txt", fSolver.Result()); 825 } 826 827 return BSize(Right()->Value() - Left()->Value(), 828 Bottom()->Value() - Top()->Value()); 829 } 830 831 832 void 833 BALMLayout::_UpdateAreaConstraints() 834 { 835 for (int i = 0; i < CountItems(); i++) 836 AreaFor(ItemAt(i))->InvalidateSizeConstraints(); 837 } 838