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