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
22using std::nothrow;
23using std::swap;
24
25
26enum {
27	MAX_COLUMN_ROW_COUNT	= 1024,
28};
29
30
31namespace {
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
46struct 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
59class BGridLayout::RowInfoArray {
60public:
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
115private:
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
154BGridLayout::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
168BGridLayout::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
218BGridLayout::~BGridLayout()
219{
220	delete fRowInfos;
221	delete fColumnInfos;
222}
223
224
225int32
226BGridLayout::CountColumns() const
227{
228	return fColumnCount;
229}
230
231
232int32
233BGridLayout::CountRows() const
234{
235	return fRowCount;
236}
237
238
239float
240BGridLayout::HorizontalSpacing() const
241{
242	return fHSpacing;
243}
244
245
246float
247BGridLayout::VerticalSpacing() const
248{
249	return fVSpacing;
250}
251
252
253void
254BGridLayout::SetHorizontalSpacing(float spacing)
255{
256	spacing = BControlLook::ComposeSpacing(spacing);
257	if (spacing != fHSpacing) {
258		fHSpacing = spacing;
259
260		InvalidateLayout();
261	}
262}
263
264
265void
266BGridLayout::SetVerticalSpacing(float spacing)
267{
268	spacing = BControlLook::ComposeSpacing(spacing);
269	if (spacing != fVSpacing) {
270		fVSpacing = spacing;
271
272		InvalidateLayout();
273	}
274}
275
276
277void
278BGridLayout::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
291float
292BGridLayout::ColumnWeight(int32 column) const
293{
294	return fColumnInfos->Weight(column);
295}
296
297
298void
299BGridLayout::SetColumnWeight(int32 column, float weight)
300{
301	fColumnInfos->SetWeight(column, weight);
302}
303
304
305float
306BGridLayout::MinColumnWidth(int32 column) const
307{
308	return fColumnInfos->MinSize(column);
309}
310
311
312void
313BGridLayout::SetMinColumnWidth(int32 column, float width)
314{
315	fColumnInfos->SetMinSize(column, width);
316}
317
318
319float
320BGridLayout::MaxColumnWidth(int32 column) const
321{
322	return fColumnInfos->MaxSize(column);
323}
324
325
326void
327BGridLayout::SetMaxColumnWidth(int32 column, float width)
328{
329	fColumnInfos->SetMaxSize(column, width);
330}
331
332
333float
334BGridLayout::RowWeight(int32 row) const
335{
336	return fRowInfos->Weight(row);
337}
338
339
340void
341BGridLayout::SetRowWeight(int32 row, float weight)
342{
343	fRowInfos->SetWeight(row, weight);
344}
345
346
347float
348BGridLayout::MinRowHeight(int row) const
349{
350	return fRowInfos->MinSize(row);
351}
352
353
354void
355BGridLayout::SetMinRowHeight(int32 row, float height)
356{
357	fRowInfos->SetMinSize(row, height);
358}
359
360
361float
362BGridLayout::MaxRowHeight(int32 row) const
363{
364	return fRowInfos->MaxSize(row);
365}
366
367
368void
369BGridLayout::SetMaxRowHeight(int32 row, float height)
370{
371	fRowInfos->SetMaxSize(row, height);
372}
373
374
375BLayoutItem*
376BGridLayout::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
386BLayoutItem*
387BGridLayout::AddView(BView* child)
388{
389	return BTwoDimensionalLayout::AddView(child);
390}
391
392
393BLayoutItem*
394BGridLayout::AddView(int32 index, BView* child)
395{
396	return BTwoDimensionalLayout::AddView(index, child);
397}
398
399
400BLayoutItem*
401BGridLayout::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
417bool
418BGridLayout::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
433bool
434BGridLayout::AddItem(int32 index, BLayoutItem* item)
435{
436	return AddItem(item);
437}
438
439
440bool
441BGridLayout::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
473status_t
474BGridLayout::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
499status_t
500BGridLayout::AllArchived(BMessage* into) const
501{
502	return BTwoDimensionalLayout::AllArchived(into);
503}
504
505
506status_t
507BGridLayout::AllUnarchived(const BMessage* from)
508{
509	return BTwoDimensionalLayout::AllUnarchived(from);
510}
511
512
513BArchivable*
514BGridLayout::Instantiate(BMessage* from)
515{
516	if (validate_instantiation(from, "BGridLayout"))
517		return new BGridLayout(from);
518	return NULL;
519}
520
521
522status_t
523BGridLayout::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
539status_t
540BGridLayout::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
579bool
580BGridLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
581{
582	item->SetLayoutData(new(nothrow) ItemLayoutData);
583	return item->LayoutData() != NULL;
584}
585
586
587void
588BGridLayout::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
639bool
640BGridLayout::HasMultiColumnItems()
641{
642	return (fMultiColumnItems > 0);
643}
644
645
646bool
647BGridLayout::HasMultiRowItems()
648{
649	return (fMultiRowItems > 0);
650}
651
652
653int32
654BGridLayout::InternalCountColumns()
655{
656	return fColumnCount;
657}
658
659
660int32
661BGridLayout::InternalCountRows()
662{
663	return fRowCount;
664}
665
666
667void
668BGridLayout::GetColumnRowConstraints(enum 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
683void
684BGridLayout::GetItemDimensions(BLayoutItem* item, Dimensions* dimensions)
685{
686	if (ItemLayoutData* data = _LayoutDataForItem(item))
687		*dimensions = data->dimensions;
688}
689
690
691bool
692BGridLayout::_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
703bool
704BGridLayout::_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
723bool
724BGridLayout::_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
753bool
754BGridLayout::_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
797BGridLayout::ItemLayoutData*
798BGridLayout::_LayoutDataForItem(BLayoutItem* item) const
799{
800	if (!item)
801		return NULL;
802	return (ItemLayoutData*)item->LayoutData();
803}
804
805
806status_t
807BGridLayout::Perform(perform_code d, void* arg)
808{
809	return BTwoDimensionalLayout::Perform(d, arg);
810}
811
812
813void BGridLayout::_ReservedGridLayout1() {}
814void BGridLayout::_ReservedGridLayout2() {}
815void BGridLayout::_ReservedGridLayout3() {}
816void BGridLayout::_ReservedGridLayout4() {}
817void BGridLayout::_ReservedGridLayout5() {}
818void BGridLayout::_ReservedGridLayout6() {}
819void BGridLayout::_ReservedGridLayout7() {}
820void BGridLayout::_ReservedGridLayout8() {}
821void BGridLayout::_ReservedGridLayout9() {}
822void BGridLayout::_ReservedGridLayout10() {}
823
824