xref: /haiku/src/kits/interface/GridLayout.cpp (revision 9d6d3fcf5fe8308cd020cecf89dede440346f8c4)
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