xref: /haiku/src/kits/interface/GridLayout.cpp (revision cbed190f71b8aff814bf95539c39a1bcfb953ed8)
1 /*
2  * Copyright 2010-2011, Haiku Inc.
3  * Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include <GridLayout.h>
9 
10 #include <algorithm>
11 #include <new>
12 #include <string.h>
13 
14 #include <ControlLook.h>
15 #include <LayoutItem.h>
16 #include <List.h>
17 #include <Message.h>
18 
19 #include "ViewLayoutItem.h"
20 
21 
22 using std::nothrow;
23 using std::swap;
24 
25 
26 enum {
27 	MAX_COLUMN_ROW_COUNT	= 1024,
28 };
29 
30 
31 namespace {
32 	// a placeholder we put in our grid array to make a cell occupied
33 	BLayoutItem* const OCCUPIED_GRID_CELL = (BLayoutItem*)0x1;
34 
35 	const char* const kRowSizesField = "BGridLayout:rowsizes";
36 		// kRowSizesField = {min, max}
37 	const char* const kRowWeightField = "BGridLayout:rowweight";
38 	const char* const kColumnSizesField = "BGridLayout:columnsizes";
39 		// kColumnSizesField = {min, max}
40 	const char* const kColumnWeightField = "BGridLayout:columnweight";
41 	const char* const kItemDimensionsField = "BGridLayout:item:dimensions";
42 		// kItemDimensionsField = {x, y, width, height}
43 }
44 
45 
46 struct BGridLayout::ItemLayoutData {
47 	Dimensions	dimensions;
48 
49 	ItemLayoutData()
50 	{
51 		dimensions.x = 0;
52 		dimensions.y = 0;
53 		dimensions.width = 1;
54 		dimensions.height = 1;
55 	}
56 };
57 
58 
59 class BGridLayout::RowInfoArray {
60 public:
61 	RowInfoArray()
62 	{
63 	}
64 
65 	~RowInfoArray()
66 	{
67 		for (int32 i = 0; Info* info = (Info*)fInfos.ItemAt(i); i++)
68 			delete info;
69 	}
70 
71 	int32 Count() const
72 	{
73 		return fInfos.CountItems();
74 	}
75 
76 	float Weight(int32 index) const
77 	{
78 		if (Info* info = _InfoAt(index))
79 			return info->weight;
80 		return 1;
81 	}
82 
83 	void SetWeight(int32 index, float weight)
84 	{
85 		if (Info* info = _InfoAt(index, true))
86 			info->weight = weight;
87 	}
88 
89 	float MinSize(int32 index) const
90 	{
91 		if (Info* info = _InfoAt(index))
92 			return info->minSize;
93 		return -1;
94 	}
95 
96 	void SetMinSize(int32 index, float size)
97 	{
98 		if (Info* info = _InfoAt(index, true))
99 			info->minSize = size;
100 	}
101 
102 	float MaxSize(int32 index) const
103 	{
104 		if (Info* info = _InfoAt(index))
105 			return info->maxSize;
106 		return B_SIZE_UNLIMITED;
107 	}
108 
109 	void SetMaxSize(int32 index, float size)
110 	{
111 		if (Info* info = _InfoAt(index, true))
112 			info->maxSize = size;
113 	}
114 
115 private:
116 	struct Info {
117 		float	weight;
118 		float	minSize;
119 		float	maxSize;
120 	};
121 
122 	Info* _InfoAt(int32 index) const
123 	{
124 		return (Info*)fInfos.ItemAt(index);
125 	}
126 
127 	Info* _InfoAt(int32 index, bool resize)
128 	{
129 		if (index < 0 || index >= MAX_COLUMN_ROW_COUNT)
130 			return NULL;
131 
132 		// resize, if necessary and desired
133 		int32 count = Count();
134 		if (index >= count) {
135 			if (!resize)
136 				return NULL;
137 
138 			for (int32 i = count; i <= index; i++) {
139 				Info* info = new Info;
140 				info->weight = 1;
141 				info->minSize = 0;
142 				info->maxSize = B_SIZE_UNLIMITED;
143 				fInfos.AddItem(info);
144 			}
145 		}
146 
147 		return _InfoAt(index);
148 	}
149 
150 	BList		fInfos;
151 };
152 
153 
154 BGridLayout::BGridLayout(float horizontal, float vertical)
155 	:
156 	fGrid(NULL),
157 	fColumnCount(0),
158 	fRowCount(0),
159 	fRowInfos(new RowInfoArray),
160 	fColumnInfos(new RowInfoArray),
161 	fMultiColumnItems(0),
162 	fMultiRowItems(0)
163 {
164 	SetSpacing(horizontal, vertical);
165 }
166 
167 
168 BGridLayout::BGridLayout(BMessage* from)
169 	:
170 	BTwoDimensionalLayout(BUnarchiver::PrepareArchive(from)),
171 	fGrid(NULL),
172 	fColumnCount(0),
173 	fRowCount(0),
174 	fRowInfos(new RowInfoArray),
175 	fColumnInfos(new RowInfoArray),
176 	fMultiColumnItems(0),
177 	fMultiRowItems(0)
178 {
179 	BUnarchiver unarchiver(from);
180 	int32 columns;
181 	from->GetInfo(kColumnWeightField, NULL, &columns);
182 
183 	int32 rows;
184 	from->GetInfo(kRowWeightField, NULL, &rows);
185 
186 	// sets fColumnCount && fRowCount on success
187 	if (!_ResizeGrid(columns, rows)) {
188 		unarchiver.Finish(B_NO_MEMORY);
189 		return;
190 	}
191 
192 	for (int32 i = 0; i < fRowCount; i++) {
193 		float getter;
194 		if (from->FindFloat(kRowWeightField, i, &getter) == B_OK)
195 			fRowInfos->SetWeight(i, getter);
196 
197 		if (from->FindFloat(kRowSizesField, i * 2, &getter) == B_OK)
198 			fRowInfos->SetMinSize(i, getter);
199 
200 		if (from->FindFloat(kRowSizesField, i * 2 + 1, &getter) == B_OK)
201 			fRowInfos->SetMaxSize(i, getter);
202 	}
203 
204 	for (int32 i = 0; i < fColumnCount; i++) {
205 		float getter;
206 		if (from->FindFloat(kColumnWeightField, i, &getter) == B_OK)
207 			fColumnInfos->SetWeight(i, getter);
208 
209 		if (from->FindFloat(kColumnSizesField, i * 2, &getter) == B_OK)
210 			fColumnInfos->SetMinSize(i, getter);
211 
212 		if (from->FindFloat(kColumnSizesField, i * 2 + 1, &getter) == B_OK)
213 			fColumnInfos->SetMaxSize(i, getter);
214 	}
215 }
216 
217 
218 BGridLayout::~BGridLayout()
219 {
220 	delete fRowInfos;
221 	delete fColumnInfos;
222 }
223 
224 
225 int32
226 BGridLayout::CountColumns() const
227 {
228 	return fColumnCount;
229 }
230 
231 
232 int32
233 BGridLayout::CountRows() const
234 {
235 	return fRowCount;
236 }
237 
238 
239 float
240 BGridLayout::HorizontalSpacing() const
241 {
242 	return fHSpacing;
243 }
244 
245 
246 float
247 BGridLayout::VerticalSpacing() const
248 {
249 	return fVSpacing;
250 }
251 
252 
253 void
254 BGridLayout::SetHorizontalSpacing(float spacing)
255 {
256 	spacing = BControlLook::ComposeItemSpacing(spacing);
257 	if (spacing != fHSpacing) {
258 		fHSpacing = spacing;
259 
260 		InvalidateLayout();
261 	}
262 }
263 
264 
265 void
266 BGridLayout::SetVerticalSpacing(float spacing)
267 {
268 	spacing = BControlLook::ComposeItemSpacing(spacing);
269 	if (spacing != fVSpacing) {
270 		fVSpacing = spacing;
271 
272 		InvalidateLayout();
273 	}
274 }
275 
276 
277 void
278 BGridLayout::SetSpacing(float horizontal, float vertical)
279 {
280 	horizontal = BControlLook::ComposeItemSpacing(horizontal);
281 	vertical = BControlLook::ComposeItemSpacing(vertical);
282 	if (horizontal != fHSpacing || vertical != fVSpacing) {
283 		fHSpacing = horizontal;
284 		fVSpacing = vertical;
285 
286 		InvalidateLayout();
287 	}
288 }
289 
290 
291 float
292 BGridLayout::ColumnWeight(int32 column) const
293 {
294 	return fColumnInfos->Weight(column);
295 }
296 
297 
298 void
299 BGridLayout::SetColumnWeight(int32 column, float weight)
300 {
301 	fColumnInfos->SetWeight(column, weight);
302 }
303 
304 
305 float
306 BGridLayout::MinColumnWidth(int32 column) const
307 {
308 	return fColumnInfos->MinSize(column);
309 }
310 
311 
312 void
313 BGridLayout::SetMinColumnWidth(int32 column, float width)
314 {
315 	fColumnInfos->SetMinSize(column, width);
316 }
317 
318 
319 float
320 BGridLayout::MaxColumnWidth(int32 column) const
321 {
322 	return fColumnInfos->MaxSize(column);
323 }
324 
325 
326 void
327 BGridLayout::SetMaxColumnWidth(int32 column, float width)
328 {
329 	fColumnInfos->SetMaxSize(column, width);
330 }
331 
332 
333 float
334 BGridLayout::RowWeight(int32 row) const
335 {
336 	return fRowInfos->Weight(row);
337 }
338 
339 
340 void
341 BGridLayout::SetRowWeight(int32 row, float weight)
342 {
343 	fRowInfos->SetWeight(row, weight);
344 }
345 
346 
347 float
348 BGridLayout::MinRowHeight(int row) const
349 {
350 	return fRowInfos->MinSize(row);
351 }
352 
353 
354 void
355 BGridLayout::SetMinRowHeight(int32 row, float height)
356 {
357 	fRowInfos->SetMinSize(row, height);
358 }
359 
360 
361 float
362 BGridLayout::MaxRowHeight(int32 row) const
363 {
364 	return fRowInfos->MaxSize(row);
365 }
366 
367 
368 void
369 BGridLayout::SetMaxRowHeight(int32 row, float height)
370 {
371 	fRowInfos->SetMaxSize(row, height);
372 }
373 
374 
375 BLayoutItem*
376 BGridLayout::ItemAt(int32 column, int32 row) const
377 {
378 	if (column < 0 || column >= CountColumns()
379 		|| row < 0 || row >= CountRows())
380 		return NULL;
381 
382 	return fGrid[column][row];
383 }
384 
385 
386 BLayoutItem*
387 BGridLayout::AddView(BView* child)
388 {
389 	return BTwoDimensionalLayout::AddView(child);
390 }
391 
392 
393 BLayoutItem*
394 BGridLayout::AddView(int32 index, BView* child)
395 {
396 	return BTwoDimensionalLayout::AddView(index, child);
397 }
398 
399 
400 BLayoutItem*
401 BGridLayout::AddView(BView* child, int32 column, int32 row, int32 columnCount,
402 	int32 rowCount)
403 {
404 	if (!child)
405 		return NULL;
406 
407 	BLayoutItem* item = new BViewLayoutItem(child);
408 	if (!AddItem(item, column, row, columnCount, rowCount)) {
409 		delete item;
410 		return NULL;
411 	}
412 
413 	return item;
414 }
415 
416 
417 bool
418 BGridLayout::AddItem(BLayoutItem* item)
419 {
420 	// find a free spot
421 	for (int32 row = 0; row < fRowCount; row++) {
422 		for (int32 column = 0; column < fColumnCount; column++) {
423 			if (_IsGridCellEmpty(row, column))
424 				return AddItem(item, column, row, 1, 1);
425 		}
426 	}
427 
428 	// no free spot, start a new column
429 	return AddItem(item, fColumnCount, 0, 1, 1);
430 }
431 
432 
433 bool
434 BGridLayout::AddItem(int32 index, BLayoutItem* item)
435 {
436 	return AddItem(item);
437 }
438 
439 
440 bool
441 BGridLayout::AddItem(BLayoutItem* item, int32 column, int32 row,
442 	int32 columnCount, int32 rowCount)
443 {
444 	if (!_AreGridCellsEmpty(column, row, columnCount, rowCount))
445 		return false;
446 
447 	bool success = BTwoDimensionalLayout::AddItem(-1, item);
448 	if (!success)
449 		return false;
450 
451 	// set item dimensions
452 	if (ItemLayoutData* data = _LayoutDataForItem(item)) {
453 		data->dimensions.x = column;
454 		data->dimensions.y = row;
455 		data->dimensions.width = columnCount;
456 		data->dimensions.height = rowCount;
457 	}
458 
459 	if (!_InsertItemIntoGrid(item)) {
460 		RemoveItem(item);
461 		return false;
462 	}
463 
464 	if (columnCount > 1)
465 		fMultiColumnItems++;
466 	if (rowCount > 1)
467 		fMultiRowItems++;
468 
469 	return success;
470 }
471 
472 
473 status_t
474 BGridLayout::Archive(BMessage* into, bool deep) const
475 {
476 	BArchiver archiver(into);
477 	status_t err = BTwoDimensionalLayout::Archive(into, deep);
478 
479 	for (int32 i = 0; i < fRowCount && err == B_OK; i++) {
480 		err = into->AddFloat(kRowWeightField, fRowInfos->Weight(i));
481 		if (err == B_OK)
482 			err = into->AddFloat(kRowSizesField, fRowInfos->MinSize(i));
483 		if (err == B_OK)
484 			err = into->AddFloat(kRowSizesField, fRowInfos->MaxSize(i));
485 	}
486 
487 	for (int32 i = 0; i < fColumnCount && err == B_OK; i++) {
488 		err = into->AddFloat(kColumnWeightField, fColumnInfos->Weight(i));
489 		if (err == B_OK)
490 			err = into->AddFloat(kColumnSizesField, fColumnInfos->MinSize(i));
491 		if (err == B_OK)
492 			err = into->AddFloat(kColumnSizesField, fColumnInfos->MaxSize(i));
493 	}
494 
495 	return archiver.Finish(err);
496 }
497 
498 
499 BArchivable*
500 BGridLayout::Instantiate(BMessage* from)
501 {
502 	if (validate_instantiation(from, "BGridLayout"))
503 		return new BGridLayout(from);
504 	return NULL;
505 }
506 
507 
508 status_t
509 BGridLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
510 {
511 	ItemLayoutData* data =	_LayoutDataForItem(item);
512 
513 	status_t err = into->AddInt32(kItemDimensionsField, data->dimensions.x);
514 	if (err == B_OK)
515 		err = into->AddInt32(kItemDimensionsField, data->dimensions.y);
516 	if (err == B_OK)
517 		err = into->AddInt32(kItemDimensionsField, data->dimensions.width);
518 	if (err == B_OK)
519 		err = into->AddInt32(kItemDimensionsField, data->dimensions.height);
520 
521 	return err;
522 }
523 
524 
525 status_t
526 BGridLayout::ItemUnarchived(const BMessage* from,
527 	BLayoutItem* item, int32 index)
528 {
529 	ItemLayoutData* data = _LayoutDataForItem(item);
530 	Dimensions& dimensions = data->dimensions;
531 
532 	index *= 4;
533 		// each item stores 4 int32s into kItemDimensionsField
534 	status_t err = from->FindInt32(kItemDimensionsField, index, &dimensions.x);
535 	if (err == B_OK)
536 		err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.y);
537 
538 	if (err == B_OK)
539 		err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.width);
540 
541 	if (err == B_OK) {
542 		err = from->FindInt32(kItemDimensionsField,
543 			++index, &dimensions.height);
544 	}
545 
546 	if (err != B_OK)
547 		return err;
548 
549 	if (!_AreGridCellsEmpty(dimensions.x, dimensions.y,
550 		dimensions.width, dimensions.height))
551 		return B_BAD_DATA;
552 
553 	if (!_InsertItemIntoGrid(item))
554 		return B_NO_MEMORY;
555 
556 	if (dimensions.width > 1)
557 		fMultiColumnItems++;
558 	if (dimensions.height > 1)
559 		fMultiRowItems++;
560 
561 	return err;
562 }
563 
564 
565 bool
566 BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
567 {
568 	item->SetLayoutData(new(nothrow) ItemLayoutData);
569 	return item->LayoutData() != NULL;
570 }
571 
572 
573 void
574 BGridLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
575 {
576 	ItemLayoutData* data = _LayoutDataForItem(item);
577 	Dimensions itemDimensions = data->dimensions;
578 	item->SetLayoutData(NULL);
579 	delete data;
580 
581 	if (itemDimensions.width > 1)
582 		fMultiColumnItems--;
583 	if (itemDimensions.height > 1)
584 		fMultiRowItems--;
585 
586 	// remove the item from the grid
587 	for (int x = 0; x < itemDimensions.width; x++) {
588 		for (int y = 0; y < itemDimensions.height; y++)
589 			fGrid[itemDimensions.x + x][itemDimensions.y + y] = NULL;
590 	}
591 
592 	// check whether we can shrink the grid
593 	if (itemDimensions.x + itemDimensions.width == fColumnCount
594 		|| itemDimensions.y + itemDimensions.height == fRowCount) {
595 		int32 columnCount = fColumnCount;
596 		int32 rowCount = fRowCount;
597 
598 		// check for empty columns
599 		bool empty = true;
600 		for (; columnCount > 0; columnCount--) {
601 			for (int32 row = 0; empty && row < rowCount; row++)
602 				empty &= (fGrid[columnCount - 1][row] == NULL);
603 
604 			if (!empty)
605 				break;
606 		}
607 
608 		// check for empty rows
609 		empty = true;
610 		for (; rowCount > 0; rowCount--) {
611 			for (int32 column = 0; empty && column < columnCount; column++)
612 				empty &= (fGrid[column][rowCount - 1] == NULL);
613 
614 			if (!empty)
615 				break;
616 		}
617 
618 		// resize the grid
619 		if (columnCount != fColumnCount || rowCount != fRowCount)
620 			_ResizeGrid(columnCount, rowCount);
621 	}
622 }
623 
624 
625 bool
626 BGridLayout::HasMultiColumnItems()
627 {
628 	return (fMultiColumnItems > 0);
629 }
630 
631 
632 bool
633 BGridLayout::HasMultiRowItems()
634 {
635 	return (fMultiRowItems > 0);
636 }
637 
638 
639 int32
640 BGridLayout::InternalCountColumns()
641 {
642 	return fColumnCount;
643 }
644 
645 
646 int32
647 BGridLayout::InternalCountRows()
648 {
649 	return fRowCount;
650 }
651 
652 
653 void
654 BGridLayout::GetColumnRowConstraints(enum orientation orientation, int32 index,
655 	ColumnRowConstraints* constraints)
656 {
657 	if (orientation == B_HORIZONTAL) {
658 		constraints->min = MinColumnWidth(index);
659 		constraints->max = MaxColumnWidth(index);
660 		constraints->weight = ColumnWeight(index);
661 	} else {
662 		constraints->min = MinRowHeight(index);
663 		constraints->max = MaxRowHeight(index);
664 		constraints->weight = RowWeight(index);
665 	}
666 }
667 
668 
669 void
670 BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions)
671 {
672 	if (ItemLayoutData* data = _LayoutDataForItem(item))
673 		*dimensions = data->dimensions;
674 }
675 
676 
677 bool
678 BGridLayout::_IsGridCellEmpty(int32 column, int32 row)
679 {
680 	if (column < 0 || row < 0)
681 		return false;
682 	if (column >= fColumnCount || row >= fRowCount)
683 		return true;
684 
685 	return (fGrid[column][row] == NULL);
686 }
687 
688 
689 bool
690 BGridLayout::_AreGridCellsEmpty(int32 column, int32 row, int32 columnCount,
691 	int32 rowCount)
692 {
693 	if (column < 0 || row < 0)
694 		return false;
695 	int32 toColumn = min_c(column + columnCount, fColumnCount);
696 	int32 toRow = min_c(row + rowCount, fRowCount);
697 
698 	for (int32 x = column; x < toColumn; x++) {
699 		for (int32 y = row; y < toRow; y++) {
700 			if (fGrid[x][y] != NULL)
701 				return false;
702 		}
703 	}
704 
705 	return true;
706 }
707 
708 
709 bool
710 BGridLayout::_InsertItemIntoGrid(BLayoutItem* item)
711 {
712 	BGridLayout::ItemLayoutData* data = _LayoutDataForItem(item);
713 	int32 column = data->dimensions.x;
714 	int32 columnCount = data->dimensions.width;
715 	int32 row = data->dimensions.y;
716 	int32 rowCount = data->dimensions.height;
717 
718 	// resize the grid, if necessary
719 	int32 newColumnCount = max_c(fColumnCount, column + columnCount);
720 	int32 newRowCount = max_c(fRowCount, row + rowCount);
721 	if (newColumnCount > fColumnCount || newRowCount > fRowCount) {
722 		if (!_ResizeGrid(newColumnCount, newRowCount))
723 			return false;
724 	}
725 
726 	// enter the item in the grid
727 	for (int32 x = 0; x < columnCount; x++) {
728 		for (int32 y = 0; y < rowCount; y++) {
729 			if (x == 0 && y == 0)
730 				fGrid[column + x][row + y] = item;
731 			else
732 				fGrid[column + x][row + y] = OCCUPIED_GRID_CELL;
733 		}
734 	}
735 	return true;
736 }
737 
738 
739 bool
740 BGridLayout::_ResizeGrid(int32 columnCount, int32 rowCount)
741 {
742 	if (columnCount == fColumnCount && rowCount == fRowCount)
743 		return true;
744 
745 	int32 rowsToKeep = min_c(rowCount, fRowCount);
746 
747 	// allocate new grid
748 	BLayoutItem*** grid = new(nothrow) BLayoutItem**[columnCount];
749 	if (!grid)
750 		return false;
751 	memset(grid, 0, sizeof(BLayoutItem**) * columnCount);
752 
753 	bool success = true;
754 	for (int32 i = 0; i < columnCount; i++) {
755 		BLayoutItem** column = new(nothrow) BLayoutItem*[rowCount];
756 		if (!column) {
757 			success = false;
758 			break;
759 		}
760 		grid[i] = column;
761 
762 		memset(column, 0, sizeof(BLayoutItem*) * rowCount);
763 		if (i < fColumnCount && rowsToKeep > 0)
764 			memcpy(column, fGrid[i], sizeof(BLayoutItem*) * rowsToKeep);
765 	}
766 
767 	// if everything went fine, set the new grid
768 	if (success) {
769 		swap(grid, fGrid);
770 		swap(columnCount, fColumnCount);
771 		swap(rowCount, fRowCount);
772 	}
773 
774 	// delete the old, respectively on error the partially created grid
775 	for (int32 i = 0; i < columnCount; i++)
776 		delete grid[i];
777 	delete[] grid;
778 
779 	return success;
780 }
781 
782 
783 BGridLayout::ItemLayoutData*
784 BGridLayout::_LayoutDataForItem(BLayoutItem* item) const
785 {
786 	if (!item)
787 		return NULL;
788 	return (ItemLayoutData*)item->LayoutData();
789 }
790