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