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