1 /* 2 * Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include <GridLayout.h> 7 8 #include <algorithm> 9 #include <new> 10 #include <string.h> 11 12 #include <LayoutItem.h> 13 #include <List.h> 14 15 #include "ViewLayoutItem.h" 16 17 using std::nothrow; 18 using std::swap; 19 20 enum { 21 MAX_COLUMN_ROW_COUNT = 1024, 22 }; 23 24 // a placeholder we put in our grid array to make a cell occupied 25 static BLayoutItem* const OCCUPIED_GRID_CELL = (BLayoutItem*)0x1; 26 27 28 // ItemLayoutData 29 struct BGridLayout::ItemLayoutData { 30 Dimensions dimensions; 31 32 ItemLayoutData() 33 { 34 dimensions.x = 0; 35 dimensions.y = 0; 36 dimensions.width = 1; 37 dimensions.height = 1; 38 } 39 }; 40 41 // RowInfoArray 42 class BGridLayout::RowInfoArray { 43 public: 44 45 RowInfoArray() 46 { 47 } 48 49 ~RowInfoArray() 50 { 51 for (int32 i = 0; Info* info = (Info*)fInfos.ItemAt(i); i++) 52 delete info; 53 } 54 55 int32 Count() const 56 { 57 return fInfos.CountItems(); 58 } 59 60 float Weight(int32 index) const 61 { 62 if (Info* info = _InfoAt(index)) 63 return info->weight; 64 return 1; 65 } 66 67 void SetWeight(int32 index, float weight) 68 { 69 if (Info* info = _InfoAt(index, true)) 70 info->weight = weight; 71 } 72 73 float MinSize(int32 index) const 74 { 75 if (Info* info = _InfoAt(index)) 76 return info->minSize; 77 return -1; 78 } 79 80 void SetMinSize(int32 index, float size) 81 { 82 if (Info* info = _InfoAt(index, true)) 83 info->minSize = size; 84 } 85 86 float MaxSize(int32 index) const 87 { 88 if (Info* info = _InfoAt(index)) 89 return info->maxSize; 90 return B_SIZE_UNLIMITED; 91 } 92 93 void SetMaxSize(int32 index, float size) 94 { 95 if (Info* info = _InfoAt(index, true)) 96 info->maxSize = size; 97 } 98 99 private: 100 struct Info { 101 float weight; 102 float minSize; 103 float maxSize; 104 }; 105 106 Info* _InfoAt(int32 index) const 107 { 108 return (Info*)fInfos.ItemAt(index); 109 } 110 111 Info* _InfoAt(int32 index, bool resize) 112 { 113 if (index < 0 || index >= MAX_COLUMN_ROW_COUNT) 114 return NULL; 115 116 // resize, if necessary and desired 117 int32 count = Count(); 118 if (index >= count) { 119 if (!resize) 120 return NULL; 121 122 for (int32 i = count; i <= index; i++) { 123 Info* info = new Info; 124 info->weight = 1; 125 info->minSize = 0; 126 info->maxSize = B_SIZE_UNLIMITED; 127 fInfos.AddItem(info); 128 } 129 } 130 131 return _InfoAt(index); 132 } 133 134 BList fInfos; 135 }; 136 137 138 // constructor 139 BGridLayout::BGridLayout(float horizontal, float vertical) 140 : fGrid(NULL), 141 fColumnCount(0), 142 fRowCount(0), 143 fRowInfos(new RowInfoArray), 144 fColumnInfos(new RowInfoArray), 145 fMultiColumnItems(0), 146 fMultiRowItems(0) 147 { 148 SetSpacing(horizontal, vertical); 149 } 150 151 // destructor 152 BGridLayout::~BGridLayout() 153 { 154 delete fRowInfos; 155 delete fColumnInfos; 156 } 157 158 159 // CountColumns 160 int32 161 BGridLayout::CountColumns() const 162 { 163 return fColumnCount; 164 } 165 166 167 // CountRows 168 int32 169 BGridLayout::CountRows() const 170 { 171 return fRowCount; 172 } 173 174 175 // HorizontalSpacing 176 float 177 BGridLayout::HorizontalSpacing() const 178 { 179 return fHSpacing; 180 } 181 182 // VerticalSpacing 183 float 184 BGridLayout::VerticalSpacing() const 185 { 186 return fVSpacing; 187 } 188 189 // SetHorizontalSpacing 190 void 191 BGridLayout::SetHorizontalSpacing(float spacing) 192 { 193 if (spacing != fHSpacing) { 194 fHSpacing = spacing; 195 196 InvalidateLayout(); 197 } 198 } 199 200 // SetVerticalSpacing 201 void 202 BGridLayout::SetVerticalSpacing(float spacing) 203 { 204 if (spacing != fVSpacing) { 205 fVSpacing = spacing; 206 207 InvalidateLayout(); 208 } 209 } 210 211 // SetSpacing 212 void 213 BGridLayout::SetSpacing(float horizontal, float vertical) 214 { 215 if (horizontal != fHSpacing || vertical != fVSpacing) { 216 fHSpacing = horizontal; 217 fVSpacing = vertical; 218 219 InvalidateLayout(); 220 } 221 } 222 223 // ColumnWeight 224 float 225 BGridLayout::ColumnWeight(int32 column) const 226 { 227 return fColumnInfos->Weight(column); 228 } 229 230 // SetColumnWeight 231 void 232 BGridLayout::SetColumnWeight(int32 column, float weight) 233 { 234 fColumnInfos->SetWeight(column, weight); 235 } 236 237 // MinColumnWidth 238 float 239 BGridLayout::MinColumnWidth(int32 column) const 240 { 241 return fColumnInfos->MinSize(column); 242 } 243 244 // SetMinColumnWidth 245 void 246 BGridLayout::SetMinColumnWidth(int32 column, float width) 247 { 248 fColumnInfos->SetMinSize(column, width); 249 } 250 251 // MaxColumnWidth 252 float 253 BGridLayout::MaxColumnWidth(int32 column) const 254 { 255 return fColumnInfos->MaxSize(column); 256 } 257 258 // SetMaxColumnWidth 259 void 260 BGridLayout::SetMaxColumnWidth(int32 column, float width) 261 { 262 fColumnInfos->SetMaxSize(column, width); 263 } 264 265 // RowWeight 266 float 267 BGridLayout::RowWeight(int32 row) const 268 { 269 return fRowInfos->Weight(row); 270 } 271 272 // SetRowWeight 273 void 274 BGridLayout::SetRowWeight(int32 row, float weight) 275 { 276 fRowInfos->SetWeight(row, weight); 277 } 278 279 // MinRowHeight 280 float 281 BGridLayout::MinRowHeight(int row) const 282 { 283 return fRowInfos->MinSize(row); 284 } 285 286 // SetMinRowHeight 287 void 288 BGridLayout::SetMinRowHeight(int32 row, float height) 289 { 290 fRowInfos->SetMinSize(row, height); 291 } 292 293 // MaxRowHeight 294 float 295 BGridLayout::MaxRowHeight(int32 row) const 296 { 297 return fRowInfos->MaxSize(row); 298 } 299 300 // SetMaxRowHeight 301 void 302 BGridLayout::SetMaxRowHeight(int32 row, float height) 303 { 304 fRowInfos->SetMaxSize(row, height); 305 } 306 307 // AddView 308 BLayoutItem* 309 BGridLayout::AddView(BView* child) 310 { 311 return BTwoDimensionalLayout::AddView(child); 312 } 313 314 // AddView 315 BLayoutItem* 316 BGridLayout::AddView(int32 index, BView* child) 317 { 318 return BTwoDimensionalLayout::AddView(index, child); 319 } 320 321 // AddView 322 BLayoutItem* 323 BGridLayout::AddView(BView* child, int32 column, int32 row, int32 columnCount, 324 int32 rowCount) 325 { 326 if (!child) 327 return NULL; 328 329 BLayoutItem* item = new BViewLayoutItem(child); 330 if (!AddItem(item, column, row, columnCount, rowCount)) { 331 delete item; 332 return NULL; 333 } 334 335 return item; 336 } 337 338 // AddItem 339 bool 340 BGridLayout::AddItem(BLayoutItem* item) 341 { 342 // find a free spot 343 for (int32 row = 0; row < fRowCount; row++) { 344 for (int32 column = 0; column < fColumnCount; column++) { 345 if (_IsGridCellEmpty(row, column)) 346 return AddItem(item, column, row, 1, 1); 347 } 348 } 349 350 // no free spot, start a new column 351 return AddItem(item, fColumnCount, 0, 1, 1); 352 } 353 354 // AddItem 355 bool 356 BGridLayout::AddItem(int32 index, BLayoutItem* item) 357 { 358 return AddItem(item); 359 } 360 361 // AddItem 362 bool 363 BGridLayout::AddItem(BLayoutItem* item, int32 column, int32 row, 364 int32 columnCount, int32 rowCount) 365 { 366 if (!_AreGridCellsEmpty(column, row, columnCount, rowCount)) 367 return false; 368 369 bool success = BTwoDimensionalLayout::AddItem(-1, item); 370 if (!success) 371 return false; 372 373 // set item dimensions 374 if (ItemLayoutData* data = _LayoutDataForItem(item)) { 375 data->dimensions.x = column; 376 data->dimensions.y = row; 377 data->dimensions.width = columnCount; 378 data->dimensions.height = rowCount; 379 } 380 381 // resize the grid, if necessary 382 int32 newColumnCount = max_c(fColumnCount, column + columnCount); 383 int32 newRowCount = max_c(fRowCount, row + rowCount); 384 if (newColumnCount > fColumnCount || newRowCount > fRowCount) { 385 if (!_ResizeGrid(newColumnCount, newRowCount)) { 386 RemoveItem(item); 387 return false; 388 } 389 } 390 391 // enter the item in the grid 392 for (int32 x = 0; x < columnCount; x++) { 393 for (int32 y = 0; y < rowCount; y++) { 394 if (x == 0 && y == 0) 395 fGrid[column + x][row + y] = item; 396 else 397 fGrid[column + x][row + y] = OCCUPIED_GRID_CELL; 398 } 399 } 400 401 if (columnCount > 1) 402 fMultiColumnItems++; 403 if (rowCount > 1) 404 fMultiRowItems++; 405 406 return success; 407 } 408 409 // ItemAdded 410 void 411 BGridLayout::ItemAdded(BLayoutItem* item) 412 { 413 item->SetLayoutData(new ItemLayoutData); 414 } 415 416 // ItemRemoved 417 void 418 BGridLayout::ItemRemoved(BLayoutItem* item) 419 { 420 ItemLayoutData* data = _LayoutDataForItem(item); 421 // TODO: Once ItemAdded() returns a bool, we can remove this check. 422 if (!data) 423 return; 424 425 Dimensions itemDimensions = data->dimensions; 426 item->SetLayoutData(NULL); 427 delete data; 428 429 if (itemDimensions.width > 1) 430 fMultiColumnItems--; 431 if (itemDimensions.height > 1) 432 fMultiRowItems--; 433 434 // remove the item from the grid 435 for (int x = 0; x < itemDimensions.width; x++) { 436 for (int y = 0; y < itemDimensions.height; y++) 437 fGrid[itemDimensions.x + x][itemDimensions.y + y] = NULL; 438 } 439 440 // check whether we can shrink the grid 441 if (itemDimensions.x + itemDimensions.width == fColumnCount 442 || itemDimensions.y + itemDimensions.height == fRowCount) { 443 int32 columnCount = fColumnCount; 444 int32 rowCount = fRowCount; 445 446 // check for empty columns 447 bool empty = false; 448 for (; columnCount > 0; columnCount--) { 449 for (int32 row = 0; empty && row < rowCount; row++) 450 empty &= (fGrid[columnCount - 1][row] == NULL); 451 452 if (!empty) 453 break; 454 } 455 456 // check for empty rows 457 empty = false; 458 for (; rowCount > 0; rowCount--) { 459 for (int32 column = 0; empty && column < columnCount; column++) 460 empty &= (fGrid[column][rowCount - 1] == NULL); 461 462 if (!empty) 463 break; 464 } 465 466 // resize the grid 467 if (columnCount != fColumnCount || rowCount != fRowCount) 468 _ResizeGrid(columnCount, rowCount); 469 } 470 } 471 472 // HasMultiColumnItems 473 bool 474 BGridLayout::HasMultiColumnItems() 475 { 476 return (fMultiColumnItems > 0); 477 } 478 479 // HasMultiRowItems 480 bool 481 BGridLayout::HasMultiRowItems() 482 { 483 return (fMultiRowItems > 0); 484 } 485 486 // InternalCountColumns 487 int32 488 BGridLayout::InternalCountColumns() 489 { 490 return fColumnCount; 491 } 492 493 // InternalCountRows 494 int32 495 BGridLayout::InternalCountRows() 496 { 497 return fRowCount; 498 } 499 500 // GetColumnRowConstraints 501 void 502 BGridLayout::GetColumnRowConstraints(enum orientation orientation, int32 index, 503 ColumnRowConstraints* constraints) 504 { 505 if (orientation == B_HORIZONTAL) { 506 constraints->min = MinColumnWidth(index); 507 constraints->max = MaxColumnWidth(index); 508 constraints->weight = ColumnWeight(index); 509 } else { 510 constraints->min = MinRowHeight(index); 511 constraints->max = MaxRowHeight(index); 512 constraints->weight = RowWeight(index); 513 } 514 } 515 516 // GetItemDimensions 517 void 518 BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions) 519 { 520 if (ItemLayoutData* data = _LayoutDataForItem(item)) 521 *dimensions = data->dimensions; 522 } 523 524 // _IsGridCellEmpty 525 bool 526 BGridLayout::_IsGridCellEmpty(int32 column, int32 row) 527 { 528 if (column < 0 || row < 0) 529 return false; 530 if (column >= fColumnCount || row >= fRowCount) 531 return true; 532 533 return (fGrid[column][row] == NULL); 534 } 535 536 // _AreGridCellsEmpty 537 bool 538 BGridLayout::_AreGridCellsEmpty(int32 column, int32 row, int32 columnCount, 539 int32 rowCount) 540 { 541 if (column < 0 || row < 0) 542 return false; 543 int32 toColumn = min_c(column + columnCount, fColumnCount); 544 int32 toRow = min_c(row + rowCount, fRowCount); 545 546 for (int32 x = column; x < toColumn; x++) { 547 for (int32 y = row; y < toRow; y++) { 548 if (fGrid[x][y] != NULL) 549 return false; 550 } 551 } 552 553 return true; 554 } 555 556 // _ResizeGrid 557 bool 558 BGridLayout::_ResizeGrid(int32 columnCount, int32 rowCount) 559 { 560 if (columnCount == fColumnCount && rowCount == fRowCount) 561 return true; 562 563 int32 rowsToKeep = min_c(rowCount, fRowCount); 564 565 // allocate new grid 566 BLayoutItem*** grid = new(nothrow) BLayoutItem**[columnCount]; 567 if (!grid) 568 return false; 569 memset(grid, 0, sizeof(BLayoutItem**) * columnCount); 570 571 bool success = true; 572 for (int32 i = 0; i < columnCount; i++) { 573 BLayoutItem** column = new(nothrow) BLayoutItem*[rowCount]; 574 if (!column) { 575 success = false; 576 break; 577 } 578 grid[i] = column; 579 580 memset(column, 0, sizeof(BLayoutItem*) * rowCount); 581 if (i < fColumnCount && rowsToKeep > 0) 582 memcpy(column, fGrid[i], sizeof(BLayoutItem*) * rowsToKeep); 583 } 584 585 // if everything went fine, set the new grid 586 if (success) { 587 swap(grid, fGrid); 588 swap(columnCount, fColumnCount); 589 swap(rowCount, fRowCount); 590 } 591 592 // delete the old, respectively on error the partially created grid 593 for (int32 i = 0; i < columnCount; i++) 594 delete grid[i]; 595 delete[] grid; 596 597 return success; 598 } 599 600 // _LayoutDataForItem 601 BGridLayout::ItemLayoutData* 602 BGridLayout::_LayoutDataForItem(BLayoutItem* item) const 603 { 604 if (!item) 605 return NULL; 606 return (ItemLayoutData*)item->LayoutData(); 607 } 608