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