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