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