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