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