xref: /haiku/src/kits/interface/GridLayout.cpp (revision 04a0e9c7b68cbe3a43d38e2bca8e860fd80936fb)
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 B_SIZE_UNSET;
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_UNSET;
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 = B_SIZE_UNSET;
142 				info->maxSize = B_SIZE_UNSET;
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::ComposeSpacing(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::ComposeSpacing(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::ComposeSpacing(horizontal);
281 	vertical = BControlLook::ComposeSpacing(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 status_t
500 BGridLayout::AllArchived(BMessage* into) const
501 {
502 	return BTwoDimensionalLayout::AllArchived(into);
503 }
504 
505 
506 status_t
507 BGridLayout::AllUnarchived(const BMessage* from)
508 {
509 	return BTwoDimensionalLayout::AllUnarchived(from);
510 }
511 
512 
513 BArchivable*
514 BGridLayout::Instantiate(BMessage* from)
515 {
516 	if (validate_instantiation(from, "BGridLayout"))
517 		return new BGridLayout(from);
518 	return NULL;
519 }
520 
521 
522 status_t
523 BGridLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
524 {
525 	ItemLayoutData* data =	_LayoutDataForItem(item);
526 
527 	status_t err = into->AddInt32(kItemDimensionsField, data->dimensions.x);
528 	if (err == B_OK)
529 		err = into->AddInt32(kItemDimensionsField, data->dimensions.y);
530 	if (err == B_OK)
531 		err = into->AddInt32(kItemDimensionsField, data->dimensions.width);
532 	if (err == B_OK)
533 		err = into->AddInt32(kItemDimensionsField, data->dimensions.height);
534 
535 	return err;
536 }
537 
538 
539 status_t
540 BGridLayout::ItemUnarchived(const BMessage* from,
541 	BLayoutItem* item, int32 index)
542 {
543 	ItemLayoutData* data = _LayoutDataForItem(item);
544 	Dimensions& dimensions = data->dimensions;
545 
546 	index *= 4;
547 		// each item stores 4 int32s into kItemDimensionsField
548 	status_t err = from->FindInt32(kItemDimensionsField, index, &dimensions.x);
549 	if (err == B_OK)
550 		err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.y);
551 
552 	if (err == B_OK)
553 		err = from->FindInt32(kItemDimensionsField, ++index, &dimensions.width);
554 
555 	if (err == B_OK) {
556 		err = from->FindInt32(kItemDimensionsField,
557 			++index, &dimensions.height);
558 	}
559 
560 	if (err != B_OK)
561 		return err;
562 
563 	if (!_AreGridCellsEmpty(dimensions.x, dimensions.y,
564 		dimensions.width, dimensions.height))
565 		return B_BAD_DATA;
566 
567 	if (!_InsertItemIntoGrid(item))
568 		return B_NO_MEMORY;
569 
570 	if (dimensions.width > 1)
571 		fMultiColumnItems++;
572 	if (dimensions.height > 1)
573 		fMultiRowItems++;
574 
575 	return err;
576 }
577 
578 
579 bool
580 BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
581 {
582 	item->SetLayoutData(new(nothrow) ItemLayoutData);
583 	return item->LayoutData() != NULL;
584 }
585 
586 
587 void
588 BGridLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
589 {
590 	ItemLayoutData* data = _LayoutDataForItem(item);
591 	Dimensions itemDimensions = data->dimensions;
592 	item->SetLayoutData(NULL);
593 	delete data;
594 
595 	if (itemDimensions.width > 1)
596 		fMultiColumnItems--;
597 	if (itemDimensions.height > 1)
598 		fMultiRowItems--;
599 
600 	// remove the item from the grid
601 	for (int x = 0; x < itemDimensions.width; x++) {
602 		for (int y = 0; y < itemDimensions.height; y++)
603 			fGrid[itemDimensions.x + x][itemDimensions.y + y] = NULL;
604 	}
605 
606 	// check whether we can shrink the grid
607 	if (itemDimensions.x + itemDimensions.width == fColumnCount
608 		|| itemDimensions.y + itemDimensions.height == fRowCount) {
609 		int32 columnCount = fColumnCount;
610 		int32 rowCount = fRowCount;
611 
612 		// check for empty columns
613 		bool empty = true;
614 		for (; columnCount > 0; columnCount--) {
615 			for (int32 row = 0; empty && row < rowCount; row++)
616 				empty &= (fGrid[columnCount - 1][row] == NULL);
617 
618 			if (!empty)
619 				break;
620 		}
621 
622 		// check for empty rows
623 		empty = true;
624 		for (; rowCount > 0; rowCount--) {
625 			for (int32 column = 0; empty && column < columnCount; column++)
626 				empty &= (fGrid[column][rowCount - 1] == NULL);
627 
628 			if (!empty)
629 				break;
630 		}
631 
632 		// resize the grid
633 		if (columnCount != fColumnCount || rowCount != fRowCount)
634 			_ResizeGrid(columnCount, rowCount);
635 	}
636 }
637 
638 
639 bool
640 BGridLayout::HasMultiColumnItems()
641 {
642 	return (fMultiColumnItems > 0);
643 }
644 
645 
646 bool
647 BGridLayout::HasMultiRowItems()
648 {
649 	return (fMultiRowItems > 0);
650 }
651 
652 
653 int32
654 BGridLayout::InternalCountColumns()
655 {
656 	return fColumnCount;
657 }
658 
659 
660 int32
661 BGridLayout::InternalCountRows()
662 {
663 	return fRowCount;
664 }
665 
666 
667 void
668 BGridLayout::GetColumnRowConstraints(orientation orientation, int32 index,
669 	ColumnRowConstraints* constraints)
670 {
671 	if (orientation == B_HORIZONTAL) {
672 		constraints->min = MinColumnWidth(index);
673 		constraints->max = MaxColumnWidth(index);
674 		constraints->weight = ColumnWeight(index);
675 	} else {
676 		constraints->min = MinRowHeight(index);
677 		constraints->max = MaxRowHeight(index);
678 		constraints->weight = RowWeight(index);
679 	}
680 }
681 
682 
683 void
684 BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions)
685 {
686 	if (ItemLayoutData* data = _LayoutDataForItem(item))
687 		*dimensions = data->dimensions;
688 }
689 
690 
691 bool
692 BGridLayout::_IsGridCellEmpty(int32 column, int32 row)
693 {
694 	if (column < 0 || row < 0)
695 		return false;
696 	if (column >= fColumnCount || row >= fRowCount)
697 		return true;
698 
699 	return (fGrid[column][row] == NULL);
700 }
701 
702 
703 bool
704 BGridLayout::_AreGridCellsEmpty(int32 column, int32 row, int32 columnCount,
705 	int32 rowCount)
706 {
707 	if (column < 0 || row < 0)
708 		return false;
709 	int32 toColumn = min_c(column + columnCount, fColumnCount);
710 	int32 toRow = min_c(row + rowCount, fRowCount);
711 
712 	for (int32 x = column; x < toColumn; x++) {
713 		for (int32 y = row; y < toRow; y++) {
714 			if (fGrid[x][y] != NULL)
715 				return false;
716 		}
717 	}
718 
719 	return true;
720 }
721 
722 
723 bool
724 BGridLayout::_InsertItemIntoGrid(BLayoutItem* item)
725 {
726 	BGridLayout::ItemLayoutData* data = _LayoutDataForItem(item);
727 	int32 column = data->dimensions.x;
728 	int32 columnCount = data->dimensions.width;
729 	int32 row = data->dimensions.y;
730 	int32 rowCount = data->dimensions.height;
731 
732 	// resize the grid, if necessary
733 	int32 newColumnCount = max_c(fColumnCount, column + columnCount);
734 	int32 newRowCount = max_c(fRowCount, row + rowCount);
735 	if (newColumnCount > fColumnCount || newRowCount > fRowCount) {
736 		if (!_ResizeGrid(newColumnCount, newRowCount))
737 			return false;
738 	}
739 
740 	// enter the item in the grid
741 	for (int32 x = 0; x < columnCount; x++) {
742 		for (int32 y = 0; y < rowCount; y++) {
743 			if (x == 0 && y == 0)
744 				fGrid[column + x][row + y] = item;
745 			else
746 				fGrid[column + x][row + y] = OCCUPIED_GRID_CELL;
747 		}
748 	}
749 	return true;
750 }
751 
752 
753 bool
754 BGridLayout::_ResizeGrid(int32 columnCount, int32 rowCount)
755 {
756 	if (columnCount == fColumnCount && rowCount == fRowCount)
757 		return true;
758 
759 	int32 rowsToKeep = min_c(rowCount, fRowCount);
760 
761 	// allocate new grid
762 	BLayoutItem*** grid = new(nothrow) BLayoutItem**[columnCount];
763 	if (!grid)
764 		return false;
765 	memset(grid, 0, sizeof(BLayoutItem**) * columnCount);
766 
767 	bool success = true;
768 	for (int32 i = 0; i < columnCount; i++) {
769 		BLayoutItem** column = new(nothrow) BLayoutItem*[rowCount];
770 		if (!column) {
771 			success = false;
772 			break;
773 		}
774 		grid[i] = column;
775 
776 		memset(column, 0, sizeof(BLayoutItem*) * rowCount);
777 		if (i < fColumnCount && rowsToKeep > 0)
778 			memcpy(column, fGrid[i], sizeof(BLayoutItem*) * rowsToKeep);
779 	}
780 
781 	// if everything went fine, set the new grid
782 	if (success) {
783 		swap(grid, fGrid);
784 		swap(columnCount, fColumnCount);
785 		swap(rowCount, fRowCount);
786 	}
787 
788 	// delete the old, respectively on error the partially created grid
789 	for (int32 i = 0; i < columnCount; i++)
790 		delete[] grid[i];
791 	delete[] grid;
792 
793 	return success;
794 }
795 
796 
797 BGridLayout::ItemLayoutData*
798 BGridLayout::_LayoutDataForItem(BLayoutItem* item) const
799 {
800 	if (!item)
801 		return NULL;
802 	return (ItemLayoutData*)item->LayoutData();
803 }
804 
805 
806 status_t
807 BGridLayout::Perform(perform_code d, void* arg)
808 {
809 	return BTwoDimensionalLayout::Perform(d, arg);
810 }
811 
812 
813 void BGridLayout::_ReservedGridLayout1() {}
814 void BGridLayout::_ReservedGridLayout2() {}
815 void BGridLayout::_ReservedGridLayout3() {}
816 void BGridLayout::_ReservedGridLayout4() {}
817 void BGridLayout::_ReservedGridLayout5() {}
818 void BGridLayout::_ReservedGridLayout6() {}
819 void BGridLayout::_ReservedGridLayout7() {}
820 void BGridLayout::_ReservedGridLayout8() {}
821 void BGridLayout::_ReservedGridLayout9() {}
822 void BGridLayout::_ReservedGridLayout10() {}
823 
824