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 "Area.h" 10 11 #include <algorithm> // for max 12 13 #include <Button.h> 14 #include <CheckBox.h> 15 #include <PictureButton.h> 16 #include <RadioButton.h> 17 #include <StatusBar.h> 18 #include <StringView.h> 19 20 #include "ALMLayout.h" 21 22 23 using namespace LinearProgramming; 24 using namespace std; 25 26 27 GroupItem::GroupItem(BLayoutItem* item) 28 { 29 _Init(item, NULL); 30 } 31 32 33 GroupItem::GroupItem(BView* view) 34 { 35 _Init(NULL, view); 36 } 37 38 39 BLayoutItem* 40 GroupItem::LayoutItem() 41 { 42 return fLayoutItem; 43 } 44 45 46 BView* 47 GroupItem::View() 48 { 49 return fView; 50 } 51 52 53 const std::vector<GroupItem>& 54 GroupItem::GroupItems() 55 { 56 return fGroupItems; 57 } 58 59 60 enum orientation 61 GroupItem::Orientation() 62 { 63 return fOrientation; 64 } 65 66 67 GroupItem& 68 GroupItem::operator|(const GroupItem& right) 69 { 70 return _AddItem(right, B_HORIZONTAL); 71 } 72 73 74 GroupItem& 75 GroupItem::operator/(const GroupItem& bottom) 76 { 77 return _AddItem(bottom, B_VERTICAL); 78 } 79 80 81 GroupItem::GroupItem() 82 { 83 _Init(NULL, NULL); 84 } 85 86 87 void 88 GroupItem::_Init(BLayoutItem* item, BView* view, enum orientation orien) 89 { 90 fLayoutItem = item; 91 fView = view; 92 fOrientation = orien; 93 } 94 95 96 GroupItem& 97 GroupItem::_AddItem(const GroupItem& item, enum orientation orien) 98 { 99 if (fGroupItems.size() == 0) 100 fGroupItems.push_back(*this); 101 else if (fOrientation != orien) { 102 GroupItem clone = *this; 103 fGroupItems.clear(); 104 _Init(NULL, NULL, orien); 105 fGroupItems.push_back(clone); 106 } 107 108 _Init(NULL, NULL, orien); 109 fGroupItems.push_back(item); 110 return *this; 111 } 112 113 114 BView* 115 Area::View() 116 { 117 return fLayoutItem->View(); 118 } 119 120 121 /** 122 * Gets the left tab of the area. 123 * 124 * @return the left tab of the area 125 */ 126 XTab* 127 Area::Left() const 128 { 129 return fLeft; 130 } 131 132 133 /** 134 * Gets the right tab of the area. 135 * 136 * @return the right tab of the area 137 */ 138 XTab* 139 Area::Right() const 140 { 141 return fRight; 142 } 143 144 145 /** 146 * Gets the top tab of the area. 147 */ 148 YTab* 149 Area::Top() const 150 { 151 return fTop; 152 } 153 154 155 /** 156 * Gets the bottom tab of the area. 157 */ 158 YTab* 159 Area::Bottom() const 160 { 161 return fBottom; 162 } 163 164 165 /** 166 * Sets the left tab of the area. 167 * 168 * @param left the left tab of the area 169 */ 170 void 171 Area::SetLeft(XTab* left) 172 { 173 fLeft = left; 174 175 fColumn = NULL; 176 177 fMinContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight); 178 BSize preferredSize = fLayoutItem->PreferredSize(); 179 _UpdatePreferredWidthConstraint(preferredSize); 180 181 if (fMaxContentWidth != NULL) 182 fMaxContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight); 183 184 fLayoutItem->Layout()->InvalidateLayout(); 185 } 186 187 188 /** 189 * Sets the right tab of the area. 190 * 191 * @param right the right tab of the area 192 */ 193 void 194 Area::SetRight(XTab* right) 195 { 196 fRight = right; 197 198 fColumn = NULL; 199 200 fMinContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight); 201 BSize preferredSize = fLayoutItem->PreferredSize(); 202 _UpdatePreferredWidthConstraint(preferredSize); 203 if (fMaxContentWidth != NULL) 204 fMaxContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight); 205 206 fLayoutItem->Layout()->InvalidateLayout(); 207 } 208 209 210 /** 211 * Sets the top tab of the area. 212 */ 213 void 214 Area::SetTop(YTab* top) 215 { 216 fTop = top; 217 218 fRow = NULL; 219 220 fMinContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom); 221 BSize preferredSize = fLayoutItem->PreferredSize(); 222 _UpdatePreferredHeightConstraint(preferredSize); 223 if (fMaxContentHeight != NULL) 224 fMaxContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom); 225 226 fLayoutItem->Layout()->InvalidateLayout(); 227 } 228 229 230 /** 231 * Sets the bottom tab of the area. 232 */ 233 void 234 Area::SetBottom(YTab* bottom) 235 { 236 fBottom = bottom; 237 238 fRow = NULL; 239 240 fMinContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom); 241 BSize preferredSize = fLayoutItem->PreferredSize(); 242 _UpdatePreferredHeightConstraint(preferredSize); 243 if (fMaxContentHeight != NULL) 244 fMaxContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom); 245 246 fLayoutItem->Layout()->InvalidateLayout(); 247 } 248 249 250 /** 251 * Gets the row that defines the top and bottom tabs. 252 */ 253 Row* 254 Area::GetRow() const 255 { 256 return fRow; 257 } 258 259 260 /** 261 * Gets the column that defines the left and right tabs. 262 */ 263 Column* 264 Area::GetColumn() const 265 { 266 return fColumn; 267 } 268 269 270 /** 271 * Sets the row that defines the top and bottom tabs. 272 * May be null. 273 */ 274 void 275 Area::SetRow(Row* row) 276 { 277 SetTop(row->Top()); 278 SetBottom(row->Bottom()); 279 fRow = row; 280 fLayoutItem->Layout()->InvalidateLayout(); 281 } 282 283 284 /** 285 * Sets the column that defines the left and right tabs. 286 * May be null. 287 */ 288 void 289 Area::SetColumn(Column* column) 290 { 291 SetLeft(column->Left()); 292 SetRight(column->Right()); 293 fColumn = column; 294 fLayoutItem->Layout()->InvalidateLayout(); 295 } 296 297 298 /** 299 * The reluctance with which the area's content shrinks below its preferred size. 300 * The bigger the less likely is such shrinking. 301 */ 302 BSize 303 Area::ShrinkPenalties() const 304 { 305 return fShrinkPenalties; 306 } 307 308 309 /** 310 * The reluctance with which the area's content grows over its preferred size. 311 * The bigger the less likely is such growth. 312 */ 313 BSize 314 Area::GrowPenalties() const 315 { 316 return fGrowPenalties; 317 } 318 319 320 void Area::SetShrinkPenalties(BSize shrink) { 321 fShrinkPenalties = shrink; 322 if (fPreferredContentWidth != NULL) { 323 fPreferredContentWidth->SetPenaltyNeg(shrink.Width()); 324 fPreferredContentHeight->SetPenaltyNeg(shrink.Height()); 325 } 326 fLayoutItem->Layout()->InvalidateLayout(); 327 } 328 329 330 void 331 Area::SetGrowPenalties(BSize grow) 332 { 333 fGrowPenalties = grow; 334 if (fPreferredContentWidth != NULL) { 335 fPreferredContentWidth->SetPenaltyPos(grow.Width()); 336 fPreferredContentHeight->SetPenaltyPos(grow.Height()); 337 } 338 fLayoutItem->Layout()->InvalidateLayout(); 339 } 340 341 342 /** 343 * Gets aspect ratio of the area's content. 344 */ 345 double 346 Area::ContentAspectRatio() const 347 { 348 return fContentAspectRatio; 349 } 350 351 352 /** 353 * Sets aspect ratio of the area's content. 354 * May be different from the aspect ratio of the area. 355 */ 356 void 357 Area::SetContentAspectRatio(double ratio) 358 { 359 fContentAspectRatio = ratio; 360 if (fContentAspectRatio <= 0) { 361 delete fContentAspectRatioC; 362 fContentAspectRatioC = NULL; 363 } else if (fContentAspectRatioC == NULL) { 364 fContentAspectRatioC = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, 365 ratio, fTop, -ratio, fBottom, kEQ, 0.0); 366 fConstraints.AddItem(fContentAspectRatioC); 367 } else { 368 fContentAspectRatioC->SetLeftSide(-1.0, fLeft, 1.0, fRight, ratio, 369 fTop, -ratio, fBottom); 370 } 371 fLayoutItem->Layout()->InvalidateLayout(); 372 } 373 374 375 /** 376 * Gets left inset between area and its content. 377 */ 378 float 379 Area::LeftInset() const 380 { 381 if (fTopLeftInset.IsWidthSet()) 382 return fTopLeftInset.Width(); 383 384 BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout()); 385 if (fLeft == layout->Left()) 386 return layout->Inset(); 387 return layout->Spacing() / 2; 388 } 389 390 391 /** 392 * Gets top inset between area and its content. 393 */ 394 float 395 Area::TopInset() const 396 { 397 if (fTopLeftInset.IsHeightSet()) 398 return fTopLeftInset.Height(); 399 400 BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout()); 401 if (fTop == layout->Top()) 402 return layout->Inset(); 403 return layout->Spacing() / 2; 404 } 405 406 407 /** 408 * Gets right inset between area and its content. 409 */ 410 float 411 Area::RightInset() const 412 { 413 if (fRightBottomInset.IsWidthSet()) 414 return fRightBottomInset.Width(); 415 416 BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout()); 417 if (fRight == layout->Right()) 418 return layout->Inset(); 419 return layout->Spacing() / 2; 420 } 421 422 423 /** 424 * Gets bottom inset between area and its content. 425 */ 426 float 427 Area::BottomInset() const 428 { 429 if (fRightBottomInset.IsHeightSet()) 430 return fRightBottomInset.Height(); 431 432 BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout()); 433 if (fBottom == layout->Bottom()) 434 return layout->Inset(); 435 return layout->Spacing() / 2; 436 } 437 438 439 /** 440 * Sets left inset between area and its content. 441 */ 442 void 443 Area::SetLeftInset(float left) 444 { 445 fTopLeftInset.width = left; 446 fLayoutItem->Layout()->InvalidateLayout(); 447 } 448 449 450 /** 451 * Sets top inset between area and its content. 452 */ 453 void 454 Area::SetTopInset(float top) 455 { 456 fTopLeftInset.height = top; 457 fLayoutItem->Layout()->InvalidateLayout(); 458 } 459 460 461 /** 462 * Sets right inset between area and its content. 463 */ 464 void 465 Area::SetRightInset(float right) 466 { 467 fRightBottomInset.width = right; 468 fLayoutItem->Layout()->InvalidateLayout(); 469 } 470 471 472 /** 473 * Sets bottom inset between area and its content. 474 */ 475 void 476 Area::SetBottomInset(float bottom) 477 { 478 fRightBottomInset.height = bottom; 479 fLayoutItem->Layout()->InvalidateLayout(); 480 } 481 482 483 Area::operator BString() const 484 { 485 BString string; 486 GetString(string); 487 return string; 488 } 489 490 491 void 492 Area::GetString(BString& string) const 493 { 494 string << "Area("; 495 fLeft->GetString(string); 496 string << ", "; 497 fTop->GetString(string); 498 string << ", "; 499 fRight->GetString(string); 500 string << ", "; 501 fBottom->GetString(string); 502 string << ")"; 503 } 504 505 506 /*! 507 * Sets the width of the area to be the same as the width of the given area 508 * times factor. 509 * 510 * @param area the area that should have the same width 511 * @return the same-width constraint 512 */ 513 Constraint* 514 Area::SetWidthAs(Area* area, float factor) 515 { 516 return fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, factor, area->Left(), 517 -factor, area->Right(), kEQ, 0.0); 518 } 519 520 521 /*! 522 * Sets the height of the area to be the same as the height of the given area 523 * times factor. 524 * 525 * @param area the area that should have the same height 526 * @return the same-height constraint 527 */ 528 Constraint* 529 Area::SetHeightAs(Area* area, float factor) 530 { 531 return fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, factor, area->Top(), 532 -factor, area->Bottom(), kEQ, 0.0); 533 } 534 535 536 void 537 Area::InvalidateSizeConstraints() 538 { 539 // check if if we are initialized 540 if (!fLeft) 541 return; 542 543 BSize minSize = fLayoutItem->MinSize(); 544 BSize maxSize = fLayoutItem->MaxSize(); 545 BSize prefSize = fLayoutItem->PreferredSize(); 546 547 _UpdateMinSizeConstraint(minSize); 548 _UpdateMaxSizeConstraint(maxSize); 549 _UpdatePreferredWidthConstraint(prefSize); 550 _UpdatePreferredHeightConstraint(prefSize); 551 } 552 553 554 /** 555 * Destructor. 556 * Removes the area from its specification. 557 */ 558 Area::~Area() 559 { 560 for (int32 i = 0; i < fConstraints.CountItems(); i++) 561 delete (Constraint*)fConstraints.ItemAt(i); 562 } 563 564 565 /** 566 * Constructor. 567 * Uses XTabs and YTabs. 568 */ 569 Area::Area(BLayoutItem* item) 570 : 571 fLayoutItem(item), 572 573 fLS(NULL), 574 fLeft(NULL), 575 fRight(NULL), 576 fTop(NULL), 577 fBottom(NULL), 578 579 fRow(NULL), 580 fColumn(NULL), 581 582 fShrinkPenalties(5, 5), 583 fGrowPenalties(5, 5), 584 585 fMinContentWidth(NULL), 586 fMaxContentWidth(NULL), 587 fMinContentHeight(NULL), 588 fMaxContentHeight(NULL), 589 fPreferredContentWidth(NULL), 590 fPreferredContentHeight(NULL), 591 592 fContentAspectRatio(-1), 593 fContentAspectRatioC(NULL) 594 { 595 596 } 597 598 599 /** 600 * Initialize variables. 601 */ 602 #if USE_SCALE_VARIABLE 603 void 604 Area::_Init(LinearSpec* ls, XTab* left, YTab* top, XTab* right, YTab* bottom, 605 Variable* scaleWidth, Variable* scaleHeight) 606 { 607 fScaleWidth = scaleWidth; 608 fScaleHeight = scaleHeight; 609 #else 610 void 611 Area::_Init(LinearSpec* ls, XTab* left, YTab* top, XTab* right, YTab* bottom) 612 { 613 #endif 614 fLS = ls; 615 fLeft = left; 616 fRight = right; 617 fTop = top; 618 fBottom = bottom; 619 620 // adds the two essential constraints of the area that make sure that the 621 // left x-tab is really to the left of the right x-tab, and the top y-tab 622 // really above the bottom y-tab 623 fMinContentWidth = ls->AddConstraint(-1.0, fLeft, 1.0, fRight, kGE, 0); 624 fMinContentHeight = ls->AddConstraint(-1.0, fTop, 1.0, fBottom, kGE, 0); 625 626 fConstraints.AddItem(fMinContentWidth); 627 fConstraints.AddItem(fMinContentHeight); 628 629 _SetupPreferredConstraints(); 630 631 BSize preferredSize = fLayoutItem->PreferredSize(); 632 _UpdatePreferredWidthConstraint(preferredSize); 633 _UpdatePreferredHeightConstraint(preferredSize); 634 635 fConstraints.AddItem(fPreferredContentWidth); 636 fConstraints.AddItem(fPreferredContentHeight); 637 } 638 639 640 #if USE_SCALE_VARIABLE 641 void 642 Area::_Init(LinearSpec* ls, Row* row, Column* column, Variable* scaleWidth, 643 Variable* scaleHeight) 644 { 645 _Init(ls, column->Left(), row->Top(), column->Right(), row->Bottom(), 646 scaleWidth, scaleHeight); 647 #else 648 void 649 Area::_Init(LinearSpec* ls, Row* row, Column* column) 650 { 651 _Init(ls, column->Left(), row->Top(), column->Right(), row->Bottom()); 652 #endif 653 fRow = row; 654 fColumn = column; 655 } 656 657 658 /** 659 * Perform layout on the area. 660 */ 661 void 662 Area::_DoLayout() 663 { 664 // check if if we are initialized 665 if (!fLeft) 666 return; 667 668 BRect areaFrame(round(fLeft->Value()), round(fTop->Value()), 669 round(fRight->Value()), round(fBottom->Value())); 670 areaFrame.left += LeftInset(); 671 areaFrame.right -= RightInset(); 672 areaFrame.top += TopInset(); 673 areaFrame.bottom -= BottomInset(); 674 675 fLayoutItem->AlignInFrame(areaFrame); 676 } 677 678 679 void 680 Area::_UpdateMinSizeConstraint(BSize min) 681 { 682 float width = 0.; 683 float height = 0.; 684 if (min.width > 0) 685 width = min.Width() + LeftInset() + RightInset(); 686 if (min.height > 0) 687 height = min.Height() + TopInset() + BottomInset(); 688 689 fMinContentWidth->SetRightSide(width); 690 fMinContentHeight->SetRightSide(height); 691 } 692 693 694 void 695 Area::_UpdateMaxSizeConstraint(BSize max) 696 { 697 max.width += LeftInset() + RightInset(); 698 max.height += TopInset() + BottomInset(); 699 700 // we only need max constraints if the alignment is full height/width 701 // otherwise we can just align the item in the free space 702 BAlignment alignment = fLayoutItem->Alignment(); 703 if (alignment.Vertical() == B_ALIGN_USE_FULL_HEIGHT) { 704 if (fMaxContentHeight == NULL) { 705 fMaxContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, 706 kLE, max.Height()); 707 fConstraints.AddItem(fMaxContentHeight); 708 } else 709 fMaxContentHeight->SetRightSide(max.Height()); 710 } 711 else { 712 fConstraints.RemoveItem(fMaxContentHeight); 713 delete fMaxContentHeight; 714 fMaxContentHeight = NULL; 715 } 716 717 if (alignment.Horizontal() == B_ALIGN_USE_FULL_WIDTH) { 718 if (fMaxContentWidth == NULL) { 719 fMaxContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, kLE, 720 max.Width()); 721 fConstraints.AddItem(fMaxContentWidth); 722 } else 723 fMaxContentWidth->SetRightSide(max.Width()); 724 } 725 else { 726 fConstraints.RemoveItem(fMaxContentWidth); 727 delete fMaxContentWidth; 728 fMaxContentWidth = NULL; 729 } 730 } 731 732 733 void 734 Area::_UpdatePreferredWidthConstraint(BSize& preferred) 735 { 736 if (preferred.width == -1) { 737 delete fPreferredContentWidth; 738 fPreferredContentWidth = NULL; 739 return; 740 } 741 742 float width = 0; 743 if (preferred.width > 0) 744 width = preferred.width + LeftInset() + RightInset(); 745 746 _SetupPreferredConstraints(); 747 748 #if USE_SCALE_VARIABLE 749 fPreferredContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight, -width, 750 fScaleWidth); 751 #else 752 fPreferredContentWidth->SetRightSide(width); 753 #endif 754 } 755 756 757 void 758 Area::_UpdatePreferredHeightConstraint(BSize& preferred) 759 { 760 if (preferred.height == -1) { 761 delete fPreferredContentHeight; 762 fPreferredContentHeight = NULL; 763 return; 764 } 765 766 float height = 0; 767 if (preferred.height > 0) 768 height = preferred.height + TopInset() + BottomInset(); 769 770 _SetupPreferredConstraints(); 771 #if USE_SCALE_VARIABLE 772 fPreferredContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom, -height, 773 fScaleHeight); 774 #else 775 fPreferredContentHeight->SetRightSide(height); 776 #endif 777 } 778 779 780 void 781 Area::_SetupPreferredConstraints() 782 { 783 #if USE_SCALE_VARIABLE 784 if (!fPreferredContentWidth) { 785 fPreferredContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, 786 -1.0, fScaleWidth, kEQ, 0, fShrinkPenalties.Width(), 787 fGrowPenalties.Width()); 788 } 789 if (!fPreferredContentHeight) { 790 fPreferredContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, 791 -1.0, fScaleHeight, kEQ, 0, fShrinkPenalties.Height(), 792 fGrowPenalties.Height()); 793 } 794 #else 795 if (!fPreferredContentWidth) { 796 fPreferredContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, 797 kEQ, 0, fShrinkPenalties.Width(), fGrowPenalties.Width()); 798 } 799 if (!fPreferredContentHeight) { 800 fPreferredContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, 801 kEQ, 0, fShrinkPenalties.Height(), fGrowPenalties.Height()); 802 } 803 #endif 804 } 805