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 -1; 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_UNLIMITED; 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 = 0; 142 info->maxSize = B_SIZE_UNLIMITED; 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::ComposeItemSpacing(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::ComposeItemSpacing(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::ComposeItemSpacing(horizontal); 281 vertical = BControlLook::ComposeItemSpacing(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 BArchivable* 500 BGridLayout::Instantiate(BMessage* from) 501 { 502 if (validate_instantiation(from, "BGridLayout")) 503 return new BGridLayout(from); 504 return NULL; 505 } 506 507 508 status_t 509 BGridLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const 510 { 511 ItemLayoutData* data = _LayoutDataForItem(item); 512 513 status_t err = into->AddInt32(kItemDimensionsField, data->dimensions.x); 514 if (err == B_OK) 515 err = into->AddInt32(kItemDimensionsField, data->dimensions.y); 516 if (err == B_OK) 517 err = into->AddInt32(kItemDimensionsField, data->dimensions.width); 518 if (err == B_OK) 519 err = into->AddInt32(kItemDimensionsField, data->dimensions.height); 520 521 return err; 522 } 523 524 525 status_t 526 BGridLayout::ItemUnarchived(const BMessage* from, 527 BLayoutItem* item, int32 index) 528 { 529 ItemLayoutData* data = _LayoutDataForItem(item); 530 Dimensions& dimensions = data->dimensions; 531 532 index *= 4; 533 // each item stores 4 int32s into kItemDimensionsField 534 status_t err = from->FindInt32(kItemDimensionsField, index, &dimensions.x); 535 if (err == B_OK) 536 err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.y); 537 538 if (err == B_OK) 539 err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.width); 540 541 if (err == B_OK) { 542 err = from->FindInt32(kItemDimensionsField, 543 ++index, &dimensions.height); 544 } 545 546 if (err != B_OK) 547 return err; 548 549 if (!_AreGridCellsEmpty(dimensions.x, dimensions.y, 550 dimensions.width, dimensions.height)) 551 return B_BAD_DATA; 552 553 if (!_InsertItemIntoGrid(item)) 554 return B_NO_MEMORY; 555 556 if (dimensions.width > 1) 557 fMultiColumnItems++; 558 if (dimensions.height > 1) 559 fMultiRowItems++; 560 561 return err; 562 } 563 564 565 bool 566 BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 567 { 568 item->SetLayoutData(new(nothrow) ItemLayoutData); 569 return item->LayoutData() != NULL; 570 } 571 572 573 void 574 BGridLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 575 { 576 ItemLayoutData* data = _LayoutDataForItem(item); 577 Dimensions itemDimensions = data->dimensions; 578 item->SetLayoutData(NULL); 579 delete data; 580 581 if (itemDimensions.width > 1) 582 fMultiColumnItems--; 583 if (itemDimensions.height > 1) 584 fMultiRowItems--; 585 586 // remove the item from the grid 587 for (int x = 0; x < itemDimensions.width; x++) { 588 for (int y = 0; y < itemDimensions.height; y++) 589 fGrid[itemDimensions.x + x][itemDimensions.y + y] = NULL; 590 } 591 592 // check whether we can shrink the grid 593 if (itemDimensions.x + itemDimensions.width == fColumnCount 594 || itemDimensions.y + itemDimensions.height == fRowCount) { 595 int32 columnCount = fColumnCount; 596 int32 rowCount = fRowCount; 597 598 // check for empty columns 599 bool empty = true; 600 for (; columnCount > 0; columnCount--) { 601 for (int32 row = 0; empty && row < rowCount; row++) 602 empty &= (fGrid[columnCount - 1][row] == NULL); 603 604 if (!empty) 605 break; 606 } 607 608 // check for empty rows 609 empty = true; 610 for (; rowCount > 0; rowCount--) { 611 for (int32 column = 0; empty && column < columnCount; column++) 612 empty &= (fGrid[column][rowCount - 1] == NULL); 613 614 if (!empty) 615 break; 616 } 617 618 // resize the grid 619 if (columnCount != fColumnCount || rowCount != fRowCount) 620 _ResizeGrid(columnCount, rowCount); 621 } 622 } 623 624 625 bool 626 BGridLayout::HasMultiColumnItems() 627 { 628 return (fMultiColumnItems > 0); 629 } 630 631 632 bool 633 BGridLayout::HasMultiRowItems() 634 { 635 return (fMultiRowItems > 0); 636 } 637 638 639 int32 640 BGridLayout::InternalCountColumns() 641 { 642 return fColumnCount; 643 } 644 645 646 int32 647 BGridLayout::InternalCountRows() 648 { 649 return fRowCount; 650 } 651 652 653 void 654 BGridLayout::GetColumnRowConstraints(enum orientation orientation, int32 index, 655 ColumnRowConstraints* constraints) 656 { 657 if (orientation == B_HORIZONTAL) { 658 constraints->min = MinColumnWidth(index); 659 constraints->max = MaxColumnWidth(index); 660 constraints->weight = ColumnWeight(index); 661 } else { 662 constraints->min = MinRowHeight(index); 663 constraints->max = MaxRowHeight(index); 664 constraints->weight = RowWeight(index); 665 } 666 } 667 668 669 void 670 BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions) 671 { 672 if (ItemLayoutData* data = _LayoutDataForItem(item)) 673 *dimensions = data->dimensions; 674 } 675 676 677 bool 678 BGridLayout::_IsGridCellEmpty(int32 column, int32 row) 679 { 680 if (column < 0 || row < 0) 681 return false; 682 if (column >= fColumnCount || row >= fRowCount) 683 return true; 684 685 return (fGrid[column][row] == NULL); 686 } 687 688 689 bool 690 BGridLayout::_AreGridCellsEmpty(int32 column, int32 row, int32 columnCount, 691 int32 rowCount) 692 { 693 if (column < 0 || row < 0) 694 return false; 695 int32 toColumn = min_c(column + columnCount, fColumnCount); 696 int32 toRow = min_c(row + rowCount, fRowCount); 697 698 for (int32 x = column; x < toColumn; x++) { 699 for (int32 y = row; y < toRow; y++) { 700 if (fGrid[x][y] != NULL) 701 return false; 702 } 703 } 704 705 return true; 706 } 707 708 709 bool 710 BGridLayout::_InsertItemIntoGrid(BLayoutItem* item) 711 { 712 BGridLayout::ItemLayoutData* data = _LayoutDataForItem(item); 713 int32 column = data->dimensions.x; 714 int32 columnCount = data->dimensions.width; 715 int32 row = data->dimensions.y; 716 int32 rowCount = data->dimensions.height; 717 718 // resize the grid, if necessary 719 int32 newColumnCount = max_c(fColumnCount, column + columnCount); 720 int32 newRowCount = max_c(fRowCount, row + rowCount); 721 if (newColumnCount > fColumnCount || newRowCount > fRowCount) { 722 if (!_ResizeGrid(newColumnCount, newRowCount)) 723 return false; 724 } 725 726 // enter the item in the grid 727 for (int32 x = 0; x < columnCount; x++) { 728 for (int32 y = 0; y < rowCount; y++) { 729 if (x == 0 && y == 0) 730 fGrid[column + x][row + y] = item; 731 else 732 fGrid[column + x][row + y] = OCCUPIED_GRID_CELL; 733 } 734 } 735 return true; 736 } 737 738 739 bool 740 BGridLayout::_ResizeGrid(int32 columnCount, int32 rowCount) 741 { 742 if (columnCount == fColumnCount && rowCount == fRowCount) 743 return true; 744 745 int32 rowsToKeep = min_c(rowCount, fRowCount); 746 747 // allocate new grid 748 BLayoutItem*** grid = new(nothrow) BLayoutItem**[columnCount]; 749 if (!grid) 750 return false; 751 memset(grid, 0, sizeof(BLayoutItem**) * columnCount); 752 753 bool success = true; 754 for (int32 i = 0; i < columnCount; i++) { 755 BLayoutItem** column = new(nothrow) BLayoutItem*[rowCount]; 756 if (!column) { 757 success = false; 758 break; 759 } 760 grid[i] = column; 761 762 memset(column, 0, sizeof(BLayoutItem*) * rowCount); 763 if (i < fColumnCount && rowsToKeep > 0) 764 memcpy(column, fGrid[i], sizeof(BLayoutItem*) * rowsToKeep); 765 } 766 767 // if everything went fine, set the new grid 768 if (success) { 769 swap(grid, fGrid); 770 swap(columnCount, fColumnCount); 771 swap(rowCount, fRowCount); 772 } 773 774 // delete the old, respectively on error the partially created grid 775 for (int32 i = 0; i < columnCount; i++) 776 delete grid[i]; 777 delete[] grid; 778 779 return success; 780 } 781 782 783 BGridLayout::ItemLayoutData* 784 BGridLayout::_LayoutDataForItem(BLayoutItem* item) const 785 { 786 if (!item) 787 return NULL; 788 return (ItemLayoutData*)item->LayoutData(); 789 } 790