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 #if USE_SCALE_VARIABLE 630 fPreferredContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, -1.0, 631 fScaleWidth, kEQ, 0, fShrinkPenalties.Width(), 632 fGrowPenalties.Width()); 633 634 fPreferredContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, -1.0, 635 fScaleHeight, kEQ, 0, fShrinkPenalties.Height(), 636 fGrowPenalties.Height()); 637 #else 638 fPreferredContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, kEQ, 639 0, fShrinkPenalties.Width(), fGrowPenalties.Width()); 640 fPreferredContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, kEQ, 641 0, fShrinkPenalties.Height(), fGrowPenalties.Height()); 642 #endif 643 644 BSize preferredSize = fLayoutItem->PreferredSize(); 645 _UpdatePreferredWidthConstraint(preferredSize); 646 _UpdatePreferredHeightConstraint(preferredSize); 647 648 fConstraints.AddItem(fPreferredContentWidth); 649 fConstraints.AddItem(fPreferredContentHeight); 650 } 651 652 653 #if USE_SCALE_VARIABLE 654 void 655 Area::_Init(LinearSpec* ls, Row* row, Column* column, Variable* scaleWidth, 656 Variable* scaleHeight) 657 { 658 _Init(ls, column->Left(), row->Top(), column->Right(), row->Bottom(), 659 scaleWidth, scaleHeight); 660 #else 661 void 662 Area::_Init(LinearSpec* ls, Row* row, Column* column) 663 { 664 _Init(ls, column->Left(), row->Top(), column->Right(), row->Bottom()); 665 #endif 666 fRow = row; 667 fColumn = column; 668 } 669 670 671 /** 672 * Perform layout on the area. 673 */ 674 void 675 Area::_DoLayout() 676 { 677 // check if if we are initialized 678 if (!fLeft) 679 return; 680 681 BRect areaFrame(round(fLeft->Value()), round(fTop->Value()), 682 round(fRight->Value()), round(fBottom->Value())); 683 areaFrame.left += LeftInset(); 684 areaFrame.right -= RightInset(); 685 areaFrame.top += TopInset(); 686 areaFrame.bottom -= BottomInset(); 687 688 fLayoutItem->AlignInFrame(areaFrame); 689 } 690 691 692 void 693 Area::_UpdateMinSizeConstraint(BSize min) 694 { 695 float width = 0.; 696 float height = 0.; 697 if (min.width > 0) 698 width = min.Width() + LeftInset() + RightInset(); 699 if (min.height > 0) 700 height = min.Height() + TopInset() + BottomInset(); 701 702 fMinContentWidth->SetRightSide(width); 703 fMinContentHeight->SetRightSide(height); 704 } 705 706 707 void 708 Area::_UpdateMaxSizeConstraint(BSize max) 709 { 710 max.width += LeftInset() + RightInset(); 711 max.height += TopInset() + BottomInset(); 712 713 // we only need max constraints if the alignment is full height/width 714 // otherwise we can just align the item in the free space 715 BAlignment alignment = fLayoutItem->Alignment(); 716 if (alignment.Vertical() == B_ALIGN_USE_FULL_HEIGHT) { 717 if (fMaxContentHeight == NULL) { 718 fMaxContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, 719 kLE, max.Height()); 720 fConstraints.AddItem(fMaxContentHeight); 721 } else 722 fMaxContentHeight->SetRightSide(max.Height()); 723 } 724 else { 725 fConstraints.RemoveItem(fMaxContentHeight); 726 delete fMaxContentHeight; 727 fMaxContentHeight = NULL; 728 } 729 730 if (alignment.Horizontal() == B_ALIGN_USE_FULL_WIDTH) { 731 if (fMaxContentWidth == NULL) { 732 fMaxContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, kLE, 733 max.Width()); 734 fConstraints.AddItem(fMaxContentWidth); 735 } else 736 fMaxContentWidth->SetRightSide(max.Width()); 737 } 738 else { 739 fConstraints.RemoveItem(fMaxContentWidth); 740 delete fMaxContentWidth; 741 fMaxContentWidth = NULL; 742 } 743 } 744 745 746 void 747 Area::_UpdatePreferredWidthConstraint(BSize& preferred) 748 { 749 float width = 0; 750 if (preferred.width > 0) 751 width = preferred.Width() + LeftInset() + RightInset(); 752 #if USE_SCALE_VARIABLE 753 fPreferredContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight, -width, 754 fScaleWidth); 755 #else 756 fPreferredContentWidth->SetRightSide(width); 757 #endif 758 } 759 760 761 void 762 Area::_UpdatePreferredHeightConstraint(BSize& preferred) 763 { 764 float height = 0; 765 if (preferred.height > 0) 766 height = preferred.Height() + TopInset() + BottomInset(); 767 #if USE_SCALE_VARIABLE 768 fPreferredContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom, -height, 769 fScaleHeight); 770 #else 771 fPreferredContentHeight->SetRightSide(height); 772 #endif 773 } 774