1 /* 2 * Copyright 2010-2011, Haiku Inc. 3 * Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>. 4 * All rights reserved. Distributed under the terms of the MIT License. 5 */ 6 7 8 #include <GridLayout.h> 9 10 #include <algorithm> 11 #include <new> 12 #include <string.h> 13 14 #include <ControlLook.h> 15 #include <LayoutItem.h> 16 #include <List.h> 17 #include <Message.h> 18 19 #include "ViewLayoutItem.h" 20 21 22 using std::nothrow; 23 using std::swap; 24 25 26 enum { 27 MAX_COLUMN_ROW_COUNT = 1024, 28 }; 29 30 31 namespace { 32 // a placeholder we put in our grid array to make a cell occupied 33 BLayoutItem* const OCCUPIED_GRID_CELL = (BLayoutItem*)0x1; 34 35 const char* const kRowSizesField = "BGridLayout:rowsizes"; 36 // kRowSizesField = {min, max} 37 const char* const kRowWeightField = "BGridLayout:rowweight"; 38 const char* const kColumnSizesField = "BGridLayout:columnsizes"; 39 // kColumnSizesField = {min, max} 40 const char* const kColumnWeightField = "BGridLayout:columnweight"; 41 const char* const kItemDimensionsField = "BGridLayout:item:dimensions"; 42 // kItemDimensionsField = {x, y, width, height} 43 } 44 45 46 struct BGridLayout::ItemLayoutData { 47 Dimensions dimensions; 48 49 ItemLayoutData() 50 { 51 dimensions.x = 0; 52 dimensions.y = 0; 53 dimensions.width = 1; 54 dimensions.height = 1; 55 } 56 }; 57 58 59 class BGridLayout::RowInfoArray { 60 public: 61 RowInfoArray() 62 { 63 } 64 65 ~RowInfoArray() 66 { 67 for (int32 i = 0; Info* info = (Info*)fInfos.ItemAt(i); i++) 68 delete info; 69 } 70 71 int32 Count() const 72 { 73 return fInfos.CountItems(); 74 } 75 76 float Weight(int32 index) const 77 { 78 if (Info* info = _InfoAt(index)) 79 return info->weight; 80 return 1; 81 } 82 83 void SetWeight(int32 index, float weight) 84 { 85 if (Info* info = _InfoAt(index, true)) 86 info->weight = weight; 87 } 88 89 float MinSize(int32 index) const 90 { 91 if (Info* info = _InfoAt(index)) 92 return info->minSize; 93 return B_SIZE_UNSET; 94 } 95 96 void SetMinSize(int32 index, float size) 97 { 98 if (Info* info = _InfoAt(index, true)) 99 info->minSize = size; 100 } 101 102 float MaxSize(int32 index) const 103 { 104 if (Info* info = _InfoAt(index)) 105 return info->maxSize; 106 return B_SIZE_UNSET; 107 } 108 109 void SetMaxSize(int32 index, float size) 110 { 111 if (Info* info = _InfoAt(index, true)) 112 info->maxSize = size; 113 } 114 115 private: 116 struct Info { 117 float weight; 118 float minSize; 119 float maxSize; 120 }; 121 122 Info* _InfoAt(int32 index) const 123 { 124 return (Info*)fInfos.ItemAt(index); 125 } 126 127 Info* _InfoAt(int32 index, bool resize) 128 { 129 if (index < 0 || index >= MAX_COLUMN_ROW_COUNT) 130 return NULL; 131 132 // resize, if necessary and desired 133 int32 count = Count(); 134 if (index >= count) { 135 if (!resize) 136 return NULL; 137 138 for (int32 i = count; i <= index; i++) { 139 Info* info = new Info; 140 info->weight = 1; 141 info->minSize = B_SIZE_UNSET; 142 info->maxSize = B_SIZE_UNSET; 143 fInfos.AddItem(info); 144 } 145 } 146 147 return _InfoAt(index); 148 } 149 150 BList fInfos; 151 }; 152 153 154 BGridLayout::BGridLayout(float horizontal, float vertical) 155 : 156 fGrid(NULL), 157 fColumnCount(0), 158 fRowCount(0), 159 fRowInfos(new RowInfoArray), 160 fColumnInfos(new RowInfoArray), 161 fMultiColumnItems(0), 162 fMultiRowItems(0) 163 { 164 SetSpacing(horizontal, vertical); 165 } 166 167 168 BGridLayout::BGridLayout(BMessage* from) 169 : 170 BTwoDimensionalLayout(BUnarchiver::PrepareArchive(from)), 171 fGrid(NULL), 172 fColumnCount(0), 173 fRowCount(0), 174 fRowInfos(new RowInfoArray), 175 fColumnInfos(new RowInfoArray), 176 fMultiColumnItems(0), 177 fMultiRowItems(0) 178 { 179 BUnarchiver unarchiver(from); 180 int32 columns; 181 from->GetInfo(kColumnWeightField, NULL, &columns); 182 183 int32 rows; 184 from->GetInfo(kRowWeightField, NULL, &rows); 185 186 // sets fColumnCount && fRowCount on success 187 if (!_ResizeGrid(columns, rows)) { 188 unarchiver.Finish(B_NO_MEMORY); 189 return; 190 } 191 192 for (int32 i = 0; i < fRowCount; i++) { 193 float getter; 194 if (from->FindFloat(kRowWeightField, i, &getter) == B_OK) 195 fRowInfos->SetWeight(i, getter); 196 197 if (from->FindFloat(kRowSizesField, i * 2, &getter) == B_OK) 198 fRowInfos->SetMinSize(i, getter); 199 200 if (from->FindFloat(kRowSizesField, i * 2 + 1, &getter) == B_OK) 201 fRowInfos->SetMaxSize(i, getter); 202 } 203 204 for (int32 i = 0; i < fColumnCount; i++) { 205 float getter; 206 if (from->FindFloat(kColumnWeightField, i, &getter) == B_OK) 207 fColumnInfos->SetWeight(i, getter); 208 209 if (from->FindFloat(kColumnSizesField, i * 2, &getter) == B_OK) 210 fColumnInfos->SetMinSize(i, getter); 211 212 if (from->FindFloat(kColumnSizesField, i * 2 + 1, &getter) == B_OK) 213 fColumnInfos->SetMaxSize(i, getter); 214 } 215 } 216 217 218 BGridLayout::~BGridLayout() 219 { 220 delete fRowInfos; 221 delete fColumnInfos; 222 } 223 224 225 int32 226 BGridLayout::CountColumns() const 227 { 228 return fColumnCount; 229 } 230 231 232 int32 233 BGridLayout::CountRows() const 234 { 235 return fRowCount; 236 } 237 238 239 float 240 BGridLayout::HorizontalSpacing() const 241 { 242 return fHSpacing; 243 } 244 245 246 float 247 BGridLayout::VerticalSpacing() const 248 { 249 return fVSpacing; 250 } 251 252 253 void 254 BGridLayout::SetHorizontalSpacing(float spacing) 255 { 256 spacing = BControlLook::ComposeSpacing(spacing); 257 if (spacing != fHSpacing) { 258 fHSpacing = spacing; 259 260 InvalidateLayout(); 261 } 262 } 263 264 265 void 266 BGridLayout::SetVerticalSpacing(float spacing) 267 { 268 spacing = BControlLook::ComposeSpacing(spacing); 269 if (spacing != fVSpacing) { 270 fVSpacing = spacing; 271 272 InvalidateLayout(); 273 } 274 } 275 276 277 void 278 BGridLayout::SetSpacing(float horizontal, float vertical) 279 { 280 horizontal = BControlLook::ComposeSpacing(horizontal); 281 vertical = BControlLook::ComposeSpacing(vertical); 282 if (horizontal != fHSpacing || vertical != fVSpacing) { 283 fHSpacing = horizontal; 284 fVSpacing = vertical; 285 286 InvalidateLayout(); 287 } 288 } 289 290 291 float 292 BGridLayout::ColumnWeight(int32 column) const 293 { 294 return fColumnInfos->Weight(column); 295 } 296 297 298 void 299 BGridLayout::SetColumnWeight(int32 column, float weight) 300 { 301 fColumnInfos->SetWeight(column, weight); 302 } 303 304 305 float 306 BGridLayout::MinColumnWidth(int32 column) const 307 { 308 return fColumnInfos->MinSize(column); 309 } 310 311 312 void 313 BGridLayout::SetMinColumnWidth(int32 column, float width) 314 { 315 fColumnInfos->SetMinSize(column, width); 316 } 317 318 319 float 320 BGridLayout::MaxColumnWidth(int32 column) const 321 { 322 return fColumnInfos->MaxSize(column); 323 } 324 325 326 void 327 BGridLayout::SetMaxColumnWidth(int32 column, float width) 328 { 329 fColumnInfos->SetMaxSize(column, width); 330 } 331 332 333 float 334 BGridLayout::RowWeight(int32 row) const 335 { 336 return fRowInfos->Weight(row); 337 } 338 339 340 void 341 BGridLayout::SetRowWeight(int32 row, float weight) 342 { 343 fRowInfos->SetWeight(row, weight); 344 } 345 346 347 float 348 BGridLayout::MinRowHeight(int row) const 349 { 350 return fRowInfos->MinSize(row); 351 } 352 353 354 void 355 BGridLayout::SetMinRowHeight(int32 row, float height) 356 { 357 fRowInfos->SetMinSize(row, height); 358 } 359 360 361 float 362 BGridLayout::MaxRowHeight(int32 row) const 363 { 364 return fRowInfos->MaxSize(row); 365 } 366 367 368 void 369 BGridLayout::SetMaxRowHeight(int32 row, float height) 370 { 371 fRowInfos->SetMaxSize(row, height); 372 } 373 374 375 BLayoutItem* 376 BGridLayout::ItemAt(int32 column, int32 row) const 377 { 378 if (column < 0 || column >= CountColumns() 379 || row < 0 || row >= CountRows()) 380 return NULL; 381 382 return fGrid[column][row]; 383 } 384 385 386 BLayoutItem* 387 BGridLayout::AddView(BView* child) 388 { 389 return BTwoDimensionalLayout::AddView(child); 390 } 391 392 393 BLayoutItem* 394 BGridLayout::AddView(int32 index, BView* child) 395 { 396 return BTwoDimensionalLayout::AddView(index, child); 397 } 398 399 400 BLayoutItem* 401 BGridLayout::AddView(BView* child, int32 column, int32 row, int32 columnCount, 402 int32 rowCount) 403 { 404 if (!child) 405 return NULL; 406 407 BLayoutItem* item = new BViewLayoutItem(child); 408 if (!AddItem(item, column, row, columnCount, rowCount)) { 409 delete item; 410 return NULL; 411 } 412 413 return item; 414 } 415 416 417 bool 418 BGridLayout::AddItem(BLayoutItem* item) 419 { 420 // find a free spot 421 for (int32 row = 0; row < fRowCount; row++) { 422 for (int32 column = 0; column < fColumnCount; column++) { 423 if (_IsGridCellEmpty(row, column)) 424 return AddItem(item, column, row, 1, 1); 425 } 426 } 427 428 // no free spot, start a new column 429 return AddItem(item, fColumnCount, 0, 1, 1); 430 } 431 432 433 bool 434 BGridLayout::AddItem(int32 index, BLayoutItem* item) 435 { 436 return AddItem(item); 437 } 438 439 440 bool 441 BGridLayout::AddItem(BLayoutItem* item, int32 column, int32 row, 442 int32 columnCount, int32 rowCount) 443 { 444 if (!_AreGridCellsEmpty(column, row, columnCount, rowCount)) 445 return false; 446 447 bool success = BTwoDimensionalLayout::AddItem(-1, item); 448 if (!success) 449 return false; 450 451 // set item dimensions 452 if (ItemLayoutData* data = _LayoutDataForItem(item)) { 453 data->dimensions.x = column; 454 data->dimensions.y = row; 455 data->dimensions.width = columnCount; 456 data->dimensions.height = rowCount; 457 } 458 459 if (!_InsertItemIntoGrid(item)) { 460 RemoveItem(item); 461 return false; 462 } 463 464 if (columnCount > 1) 465 fMultiColumnItems++; 466 if (rowCount > 1) 467 fMultiRowItems++; 468 469 return success; 470 } 471 472 473 status_t 474 BGridLayout::Archive(BMessage* into, bool deep) const 475 { 476 BArchiver archiver(into); 477 status_t err = BTwoDimensionalLayout::Archive(into, deep); 478 479 for (int32 i = 0; i < fRowCount && err == B_OK; i++) { 480 err = into->AddFloat(kRowWeightField, fRowInfos->Weight(i)); 481 if (err == B_OK) 482 err = into->AddFloat(kRowSizesField, fRowInfos->MinSize(i)); 483 if (err == B_OK) 484 err = into->AddFloat(kRowSizesField, fRowInfos->MaxSize(i)); 485 } 486 487 for (int32 i = 0; i < fColumnCount && err == B_OK; i++) { 488 err = into->AddFloat(kColumnWeightField, fColumnInfos->Weight(i)); 489 if (err == B_OK) 490 err = into->AddFloat(kColumnSizesField, fColumnInfos->MinSize(i)); 491 if (err == B_OK) 492 err = into->AddFloat(kColumnSizesField, fColumnInfos->MaxSize(i)); 493 } 494 495 return archiver.Finish(err); 496 } 497 498 499 status_t 500 BGridLayout::AllArchived(BMessage* into) const 501 { 502 return BTwoDimensionalLayout::AllArchived(into); 503 } 504 505 506 status_t 507 BGridLayout::AllUnarchived(const BMessage* from) 508 { 509 return BTwoDimensionalLayout::AllUnarchived(from); 510 } 511 512 513 BArchivable* 514 BGridLayout::Instantiate(BMessage* from) 515 { 516 if (validate_instantiation(from, "BGridLayout")) 517 return new BGridLayout(from); 518 return NULL; 519 } 520 521 522 status_t 523 BGridLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const 524 { 525 ItemLayoutData* data = _LayoutDataForItem(item); 526 527 status_t err = into->AddInt32(kItemDimensionsField, data->dimensions.x); 528 if (err == B_OK) 529 err = into->AddInt32(kItemDimensionsField, data->dimensions.y); 530 if (err == B_OK) 531 err = into->AddInt32(kItemDimensionsField, data->dimensions.width); 532 if (err == B_OK) 533 err = into->AddInt32(kItemDimensionsField, data->dimensions.height); 534 535 return err; 536 } 537 538 539 status_t 540 BGridLayout::ItemUnarchived(const BMessage* from, 541 BLayoutItem* item, int32 index) 542 { 543 ItemLayoutData* data = _LayoutDataForItem(item); 544 Dimensions& dimensions = data->dimensions; 545 546 index *= 4; 547 // each item stores 4 int32s into kItemDimensionsField 548 status_t err = from->FindInt32(kItemDimensionsField, index, &dimensions.x); 549 if (err == B_OK) 550 err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.y); 551 552 if (err == B_OK) 553 err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.width); 554 555 if (err == B_OK) { 556 err = from->FindInt32(kItemDimensionsField, 557 ++index, &dimensions.height); 558 } 559 560 if (err != B_OK) 561 return err; 562 563 if (!_AreGridCellsEmpty(dimensions.x, dimensions.y, 564 dimensions.width, dimensions.height)) 565 return B_BAD_DATA; 566 567 if (!_InsertItemIntoGrid(item)) 568 return B_NO_MEMORY; 569 570 if (dimensions.width > 1) 571 fMultiColumnItems++; 572 if (dimensions.height > 1) 573 fMultiRowItems++; 574 575 return err; 576 } 577 578 579 bool 580 BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 581 { 582 item->SetLayoutData(new(nothrow) ItemLayoutData); 583 return item->LayoutData() != NULL; 584 } 585 586 587 void 588 BGridLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 589 { 590 ItemLayoutData* data = _LayoutDataForItem(item); 591 Dimensions itemDimensions = data->dimensions; 592 item->SetLayoutData(NULL); 593 delete data; 594 595 if (itemDimensions.width > 1) 596 fMultiColumnItems--; 597 if (itemDimensions.height > 1) 598 fMultiRowItems--; 599 600 // remove the item from the grid 601 for (int x = 0; x < itemDimensions.width; x++) { 602 for (int y = 0; y < itemDimensions.height; y++) 603 fGrid[itemDimensions.x + x][itemDimensions.y + y] = NULL; 604 } 605 606 // check whether we can shrink the grid 607 if (itemDimensions.x + itemDimensions.width == fColumnCount 608 || itemDimensions.y + itemDimensions.height == fRowCount) { 609 int32 columnCount = fColumnCount; 610 int32 rowCount = fRowCount; 611 612 // check for empty columns 613 bool empty = true; 614 for (; columnCount > 0; columnCount--) { 615 for (int32 row = 0; empty && row < rowCount; row++) 616 empty &= (fGrid[columnCount - 1][row] == NULL); 617 618 if (!empty) 619 break; 620 } 621 622 // check for empty rows 623 empty = true; 624 for (; rowCount > 0; rowCount--) { 625 for (int32 column = 0; empty && column < columnCount; column++) 626 empty &= (fGrid[column][rowCount - 1] == NULL); 627 628 if (!empty) 629 break; 630 } 631 632 // resize the grid 633 if (columnCount != fColumnCount || rowCount != fRowCount) 634 _ResizeGrid(columnCount, rowCount); 635 } 636 } 637 638 639 bool 640 BGridLayout::HasMultiColumnItems() 641 { 642 return (fMultiColumnItems > 0); 643 } 644 645 646 bool 647 BGridLayout::HasMultiRowItems() 648 { 649 return (fMultiRowItems > 0); 650 } 651 652 653 int32 654 BGridLayout::InternalCountColumns() 655 { 656 return fColumnCount; 657 } 658 659 660 int32 661 BGridLayout::InternalCountRows() 662 { 663 return fRowCount; 664 } 665 666 667 void 668 BGridLayout::GetColumnRowConstraints(orientation orientation, int32 index, 669 ColumnRowConstraints* constraints) 670 { 671 if (orientation == B_HORIZONTAL) { 672 constraints->min = MinColumnWidth(index); 673 constraints->max = MaxColumnWidth(index); 674 constraints->weight = ColumnWeight(index); 675 } else { 676 constraints->min = MinRowHeight(index); 677 constraints->max = MaxRowHeight(index); 678 constraints->weight = RowWeight(index); 679 } 680 } 681 682 683 void 684 BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions) 685 { 686 if (ItemLayoutData* data = _LayoutDataForItem(item)) 687 *dimensions = data->dimensions; 688 } 689 690 691 bool 692 BGridLayout::_IsGridCellEmpty(int32 column, int32 row) 693 { 694 if (column < 0 || row < 0) 695 return false; 696 if (column >= fColumnCount || row >= fRowCount) 697 return true; 698 699 return (fGrid[column][row] == NULL); 700 } 701 702 703 bool 704 BGridLayout::_AreGridCellsEmpty(int32 column, int32 row, int32 columnCount, 705 int32 rowCount) 706 { 707 if (column < 0 || row < 0) 708 return false; 709 int32 toColumn = min_c(column + columnCount, fColumnCount); 710 int32 toRow = min_c(row + rowCount, fRowCount); 711 712 for (int32 x = column; x < toColumn; x++) { 713 for (int32 y = row; y < toRow; y++) { 714 if (fGrid[x][y] != NULL) 715 return false; 716 } 717 } 718 719 return true; 720 } 721 722 723 bool 724 BGridLayout::_InsertItemIntoGrid(BLayoutItem* item) 725 { 726 BGridLayout::ItemLayoutData* data = _LayoutDataForItem(item); 727 int32 column = data->dimensions.x; 728 int32 columnCount = data->dimensions.width; 729 int32 row = data->dimensions.y; 730 int32 rowCount = data->dimensions.height; 731 732 // resize the grid, if necessary 733 int32 newColumnCount = max_c(fColumnCount, column + columnCount); 734 int32 newRowCount = max_c(fRowCount, row + rowCount); 735 if (newColumnCount > fColumnCount || newRowCount > fRowCount) { 736 if (!_ResizeGrid(newColumnCount, newRowCount)) 737 return false; 738 } 739 740 // enter the item in the grid 741 for (int32 x = 0; x < columnCount; x++) { 742 for (int32 y = 0; y < rowCount; y++) { 743 if (x == 0 && y == 0) 744 fGrid[column + x][row + y] = item; 745 else 746 fGrid[column + x][row + y] = OCCUPIED_GRID_CELL; 747 } 748 } 749 return true; 750 } 751 752 753 bool 754 BGridLayout::_ResizeGrid(int32 columnCount, int32 rowCount) 755 { 756 if (columnCount == fColumnCount && rowCount == fRowCount) 757 return true; 758 759 int32 rowsToKeep = min_c(rowCount, fRowCount); 760 761 // allocate new grid 762 BLayoutItem*** grid = new(nothrow) BLayoutItem**[columnCount]; 763 if (!grid) 764 return false; 765 memset(grid, 0, sizeof(BLayoutItem**) * columnCount); 766 767 bool success = true; 768 for (int32 i = 0; i < columnCount; i++) { 769 BLayoutItem** column = new(nothrow) BLayoutItem*[rowCount]; 770 if (!column) { 771 success = false; 772 break; 773 } 774 grid[i] = column; 775 776 memset(column, 0, sizeof(BLayoutItem*) * rowCount); 777 if (i < fColumnCount && rowsToKeep > 0) 778 memcpy(column, fGrid[i], sizeof(BLayoutItem*) * rowsToKeep); 779 } 780 781 // if everything went fine, set the new grid 782 if (success) { 783 swap(grid, fGrid); 784 swap(columnCount, fColumnCount); 785 swap(rowCount, fRowCount); 786 } 787 788 // delete the old, respectively on error the partially created grid 789 for (int32 i = 0; i < columnCount; i++) 790 delete[] grid[i]; 791 delete[] grid; 792 793 return success; 794 } 795 796 797 BGridLayout::ItemLayoutData* 798 BGridLayout::_LayoutDataForItem(BLayoutItem* item) const 799 { 800 if (!item) 801 return NULL; 802 return (ItemLayoutData*)item->LayoutData(); 803 } 804 805 806 status_t 807 BGridLayout::Perform(perform_code d, void* arg) 808 { 809 return BTwoDimensionalLayout::Perform(d, arg); 810 } 811 812 813 void BGridLayout::_ReservedGridLayout1() {} 814 void BGridLayout::_ReservedGridLayout2() {} 815 void BGridLayout::_ReservedGridLayout3() {} 816 void BGridLayout::_ReservedGridLayout4() {} 817 void BGridLayout::_ReservedGridLayout5() {} 818 void BGridLayout::_ReservedGridLayout6() {} 819 void BGridLayout::_ReservedGridLayout7() {} 820 void BGridLayout::_ReservedGridLayout8() {} 821 void BGridLayout::_ReservedGridLayout9() {} 822 void BGridLayout::_ReservedGridLayout10() {} 823 824