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