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