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