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