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 22using std::nothrow; 23using std::swap; 24 25 26enum { 27 MAX_COLUMN_ROW_COUNT = 1024, 28}; 29 30 31namespace { 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 46struct 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 59class BGridLayout::RowInfoArray { 60public: 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 115private: 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 154BGridLayout::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 168BGridLayout::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 218BGridLayout::~BGridLayout() 219{ 220 delete fRowInfos; 221 delete fColumnInfos; 222} 223 224 225int32 226BGridLayout::CountColumns() const 227{ 228 return fColumnCount; 229} 230 231 232int32 233BGridLayout::CountRows() const 234{ 235 return fRowCount; 236} 237 238 239float 240BGridLayout::HorizontalSpacing() const 241{ 242 return fHSpacing; 243} 244 245 246float 247BGridLayout::VerticalSpacing() const 248{ 249 return fVSpacing; 250} 251 252 253void 254BGridLayout::SetHorizontalSpacing(float spacing) 255{ 256 spacing = BControlLook::ComposeSpacing(spacing); 257 if (spacing != fHSpacing) { 258 fHSpacing = spacing; 259 260 InvalidateLayout(); 261 } 262} 263 264 265void 266BGridLayout::SetVerticalSpacing(float spacing) 267{ 268 spacing = BControlLook::ComposeSpacing(spacing); 269 if (spacing != fVSpacing) { 270 fVSpacing = spacing; 271 272 InvalidateLayout(); 273 } 274} 275 276 277void 278BGridLayout::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 291float 292BGridLayout::ColumnWeight(int32 column) const 293{ 294 return fColumnInfos->Weight(column); 295} 296 297 298void 299BGridLayout::SetColumnWeight(int32 column, float weight) 300{ 301 fColumnInfos->SetWeight(column, weight); 302} 303 304 305float 306BGridLayout::MinColumnWidth(int32 column) const 307{ 308 return fColumnInfos->MinSize(column); 309} 310 311 312void 313BGridLayout::SetMinColumnWidth(int32 column, float width) 314{ 315 fColumnInfos->SetMinSize(column, width); 316} 317 318 319float 320BGridLayout::MaxColumnWidth(int32 column) const 321{ 322 return fColumnInfos->MaxSize(column); 323} 324 325 326void 327BGridLayout::SetMaxColumnWidth(int32 column, float width) 328{ 329 fColumnInfos->SetMaxSize(column, width); 330} 331 332 333float 334BGridLayout::RowWeight(int32 row) const 335{ 336 return fRowInfos->Weight(row); 337} 338 339 340void 341BGridLayout::SetRowWeight(int32 row, float weight) 342{ 343 fRowInfos->SetWeight(row, weight); 344} 345 346 347float 348BGridLayout::MinRowHeight(int row) const 349{ 350 return fRowInfos->MinSize(row); 351} 352 353 354void 355BGridLayout::SetMinRowHeight(int32 row, float height) 356{ 357 fRowInfos->SetMinSize(row, height); 358} 359 360 361float 362BGridLayout::MaxRowHeight(int32 row) const 363{ 364 return fRowInfos->MaxSize(row); 365} 366 367 368void 369BGridLayout::SetMaxRowHeight(int32 row, float height) 370{ 371 fRowInfos->SetMaxSize(row, height); 372} 373 374 375BLayoutItem* 376BGridLayout::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 386BLayoutItem* 387BGridLayout::AddView(BView* child) 388{ 389 return BTwoDimensionalLayout::AddView(child); 390} 391 392 393BLayoutItem* 394BGridLayout::AddView(int32 index, BView* child) 395{ 396 return BTwoDimensionalLayout::AddView(index, child); 397} 398 399 400BLayoutItem* 401BGridLayout::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 417bool 418BGridLayout::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 433bool 434BGridLayout::AddItem(int32 index, BLayoutItem* item) 435{ 436 return AddItem(item); 437} 438 439 440bool 441BGridLayout::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 473status_t 474BGridLayout::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 499status_t 500BGridLayout::AllArchived(BMessage* into) const 501{ 502 return BTwoDimensionalLayout::AllArchived(into); 503} 504 505 506status_t 507BGridLayout::AllUnarchived(const BMessage* from) 508{ 509 return BTwoDimensionalLayout::AllUnarchived(from); 510} 511 512 513BArchivable* 514BGridLayout::Instantiate(BMessage* from) 515{ 516 if (validate_instantiation(from, "BGridLayout")) 517 return new BGridLayout(from); 518 return NULL; 519} 520 521 522status_t 523BGridLayout::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 539status_t 540BGridLayout::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 579bool 580BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 581{ 582 item->SetLayoutData(new(nothrow) ItemLayoutData); 583 return item->LayoutData() != NULL; 584} 585 586 587void 588BGridLayout::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 639bool 640BGridLayout::HasMultiColumnItems() 641{ 642 return (fMultiColumnItems > 0); 643} 644 645 646bool 647BGridLayout::HasMultiRowItems() 648{ 649 return (fMultiRowItems > 0); 650} 651 652 653int32 654BGridLayout::InternalCountColumns() 655{ 656 return fColumnCount; 657} 658 659 660int32 661BGridLayout::InternalCountRows() 662{ 663 return fRowCount; 664} 665 666 667void 668BGridLayout::GetColumnRowConstraints(enum 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 683void 684BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions) 685{ 686 if (ItemLayoutData* data = _LayoutDataForItem(item)) 687 *dimensions = data->dimensions; 688} 689 690 691bool 692BGridLayout::_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 703bool 704BGridLayout::_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 723bool 724BGridLayout::_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 753bool 754BGridLayout::_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 797BGridLayout::ItemLayoutData* 798BGridLayout::_LayoutDataForItem(BLayoutItem* item) const 799{ 800 if (!item) 801 return NULL; 802 return (ItemLayoutData*)item->LayoutData(); 803} 804 805 806status_t 807BGridLayout::Perform(perform_code d, void* arg) 808{ 809 return BTwoDimensionalLayout::Perform(d, arg); 810} 811 812 813void BGridLayout::_ReservedGridLayout1() {} 814void BGridLayout::_ReservedGridLayout2() {} 815void BGridLayout::_ReservedGridLayout3() {} 816void BGridLayout::_ReservedGridLayout4() {} 817void BGridLayout::_ReservedGridLayout5() {} 818void BGridLayout::_ReservedGridLayout6() {} 819void BGridLayout::_ReservedGridLayout7() {} 820void BGridLayout::_ReservedGridLayout8() {} 821void BGridLayout::_ReservedGridLayout9() {} 822void BGridLayout::_ReservedGridLayout10() {} 823 824