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