1/*
2 * Copyright 2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
3 * All rights reserved. Distributed under the terms of the MIT License.
4 */
5
6#include "GroupView.h"
7
8#include <stdio.h>
9
10#include <LayoutUtils.h>
11
12
13// #pragma mark - GroupView
14
15
16struct GroupView::LayoutInfo {
17	int32	min;
18	int32	max;
19	int32	preferred;
20	int32	size;
21
22	LayoutInfo()
23		: min(0),
24		  max(B_SIZE_UNLIMITED),
25		  preferred(0)
26	{
27	}
28
29	void AddConstraints(float addMin, float addMax, float addPreferred)
30	{
31		if (addMin >= min)
32			min = (int32)addMin + 1;
33		if (addMax <= max)
34			max = (int32)addMax + 1;
35		if (addPreferred >= preferred)
36			preferred = (int32)addPreferred + 1;
37	}
38
39	void Normalize()
40	{
41		if (max < min)
42			max = min;
43		if (preferred < min)
44			preferred = min;
45		if (preferred > max)
46			preferred = max;
47	}
48};
49
50
51GroupView::GroupView(enum orientation orientation, int32 lineCount)
52	: View(BRect(0, 0, 0, 0)),
53	  fOrientation(orientation),
54	  fLineCount(lineCount),
55	  fColumnSpacing(0),
56	  fRowSpacing(0),
57	  fInsets(0, 0, 0, 0),
58	  fMinMaxValid(false),
59	  fColumnInfos(NULL),
60	  fRowInfos(NULL)
61{
62	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
63
64	if (fLineCount < 1)
65		fLineCount = 1;
66}
67
68
69GroupView::~GroupView()
70{
71	delete fColumnInfos;
72	delete fRowInfos;
73}
74
75
76void
77GroupView::SetSpacing(float horizontal, float vertical)
78{
79	if (horizontal != fColumnSpacing || vertical != fRowSpacing) {
80		fColumnSpacing = horizontal;
81		fRowSpacing = vertical;
82
83		InvalidateLayout();
84	}
85}
86
87
88void
89GroupView::SetInsets(float left, float top, float right, float bottom)
90{
91	BRect newInsets(left, top, right, bottom);
92	if (newInsets != fInsets) {
93		fInsets = newInsets;
94		InvalidateLayout();
95	}
96}
97
98
99BSize
100GroupView::MinSize()
101{
102	_ValidateMinMax();
103	return _AddInsetsAndSpacing(BSize(fMinWidth - 1, fMinHeight - 1));
104}
105
106
107BSize
108GroupView::MaxSize()
109{
110	_ValidateMinMax();
111	return _AddInsetsAndSpacing(BSize(fMaxWidth - 1, fMaxHeight - 1));
112}
113
114
115BSize
116GroupView::PreferredSize()
117{
118	_ValidateMinMax();
119	return _AddInsetsAndSpacing(BSize(fPreferredWidth - 1,
120		fPreferredHeight - 1));
121}
122
123
124BAlignment
125GroupView::Alignment()
126{
127	return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
128}
129
130
131void
132GroupView::InvalidateLayout()
133{
134	fMinMaxValid = false;
135	View::InvalidateLayout();
136}
137
138
139void
140GroupView::Layout()
141{
142//printf("%p->GroupView::Layout()\n", this);
143	_ValidateMinMax();
144		// actually a little late already
145
146	BSize size = _SubtractInsetsAndSpacing(Size());
147	_LayoutLine(size.IntegerWidth() + 1, fColumnInfos, fColumnCount);
148	_LayoutLine(size.IntegerHeight() + 1, fRowInfos, fRowCount);
149
150	// layout children
151	BPoint location = fInsets.LeftTop();
152	for (int32 column = 0; column < fColumnCount; column++) {
153		LayoutInfo& columnInfo = fColumnInfos[column];
154		location.y = fInsets.top;
155		for (int32 row = 0; row < fRowCount; row++) {
156			View* child = _ChildAt(column, row);
157			if (!child)
158				continue;
159
160			// get the grid cell frame
161			BRect cellFrame(location,
162				BSize(columnInfo.size - 1, fRowInfos[row].size - 1));
163
164			// align the child frame in the grid cell
165			BRect childFrame = BLayoutUtils::AlignInFrame(cellFrame,
166				child->MaxSize(), child->Alignment());
167
168			// layout child
169			child->SetFrame(childFrame);
170
171			location.y += fRowInfos[row].size + fRowSpacing;
172		}
173
174		location.x += columnInfo.size + fColumnSpacing;
175	}
176//printf("%p->GroupView::Layout() done\n", this);
177}
178
179
180void
181GroupView::_ValidateMinMax()
182{
183	if (fMinMaxValid)
184		return;
185
186//printf("%p->GroupView::_ValidateMinMax()\n", this);
187	delete fColumnInfos;
188	delete fRowInfos;
189
190	fColumnCount = _ColumnCount();
191	fRowCount = _RowCount();
192
193	fColumnInfos = new LayoutInfo[fColumnCount];
194	fRowInfos = new LayoutInfo[fRowCount];
195
196	// collect the children's min/max constraints
197	for (int32 column = 0; column < fColumnCount; column++) {
198		for (int32 row = 0; row < fRowCount; row++) {
199			View* child = _ChildAt(column, row);
200			if (!child)
201				continue;
202
203			BSize min = child->MinSize();
204			BSize max = child->MaxSize();
205			BSize preferred = child->PreferredSize();
206
207			// apply constraints to column/row info
208			fColumnInfos[column].AddConstraints(min.width, max.width,
209				preferred.width);
210			fRowInfos[row].AddConstraints(min.height, max.height,
211				preferred.height);
212		}
213	}
214
215	// normalize the column/row constraints and compute sum min/max
216	fMinWidth = 0;
217	fMinHeight = 0;
218	fMaxWidth = 0;
219	fMaxHeight = 0;
220	fPreferredWidth = 0;
221	fPreferredHeight = 0;
222
223	for (int32 column = 0; column < fColumnCount; column++) {
224		fColumnInfos[column].Normalize();
225		fMinWidth = BLayoutUtils::AddSizesInt32(fMinWidth,
226			fColumnInfos[column].min);
227		fMaxWidth = BLayoutUtils::AddSizesInt32(fMaxWidth,
228			fColumnInfos[column].max);
229		fPreferredWidth = BLayoutUtils::AddSizesInt32(fPreferredWidth,
230			fColumnInfos[column].preferred);
231//printf("  column %ld: min: %ld, max: %ld, preferred: %ld\n", column, fColumnInfos[column].min, fColumnInfos[column].max, fColumnInfos[column].preferred);
232	}
233
234	for (int32 row = 0; row < fRowCount; row++) {
235		fRowInfos[row].Normalize();
236		fMinHeight = BLayoutUtils::AddSizesInt32(fMinHeight,
237			fRowInfos[row].min);
238		fMaxHeight = BLayoutUtils::AddSizesInt32(fMaxHeight,
239			fRowInfos[row].max);
240		fPreferredHeight = BLayoutUtils::AddSizesInt32(fPreferredHeight,
241			fRowInfos[row].preferred);
242//printf("  row %ld: min: %ld, max: %ld, preferred: %ld\n", row, fRowInfos[row].min, fRowInfos[row].max, fRowInfos[row].preferred);
243	}
244
245	fMinMaxValid = true;
246//printf("%p->GroupView::_ValidateMinMax() done\n", this);
247}
248
249
250void
251GroupView::_LayoutLine(int32 size, LayoutInfo* infos, int32 infoCount)
252{
253	BList infosToLayout;
254	for (int32 i = 0; i < infoCount; i++) {
255		infos[i].size = 0;
256		infosToLayout.AddItem(infos + i);
257	}
258
259	// Distribute the available space over the infos. Each iteration we
260	// try to distribute the remaining space evenly. We respect min and
261	// max constraints, though, add up the space we failed to assign
262	// due to the constraints, and use the sum as remaining space for the
263	// next iteration (can be negative). Then we ignore infos that can't
264	// shrink or grow anymore (depending on what would be needed).
265	while (!infosToLayout.IsEmpty()) {
266		BList canShrinkInfos;
267		BList canGrowInfos;
268
269		int32 sizeDiff = 0;
270		int32 infosToLayoutCount = infosToLayout.CountItems();
271		for (int32 i = 0; i < infosToLayoutCount; i++) {
272			LayoutInfo* info = (LayoutInfo*)infosToLayout.ItemAt(i);
273			info->size += (i + 1) * size / infosToLayoutCount
274				- i * size / infosToLayoutCount;
275			if (info->size < info->min) {
276				sizeDiff -= info->min - info->size;
277				info->size = info->min;
278			} else if (info->size > info->max) {
279				sizeDiff += info->size - info->max;
280				info->size = info->max;
281			}
282
283			if (info->size > info->min)
284				canShrinkInfos.AddItem(info);
285			if (info->size < info->max)
286				canGrowInfos.AddItem(info);
287		}
288
289		size = sizeDiff;
290		if (size == 0)
291			break;
292
293		if (size > 0)
294			infosToLayout = canGrowInfos;
295		else
296			infosToLayout = canShrinkInfos;
297	}
298
299	// If unassigned space is remaining, that means, that the group has
300	// been resized beyond its max size. We distribute the excess space
301	// evenly.
302	if (size > 0) {
303		for (int32 i = 0; i < infoCount; i++) {
304			infos[i].size += (i + 1) * size / infoCount
305				- i * size / infoCount;
306		}
307	}
308}
309
310
311BSize
312GroupView::_AddInsetsAndSpacing(BSize size)
313{
314	size.width = BLayoutUtils::AddDistances(size.width,
315		fInsets.left + fInsets.right - 1
316		+ (fColumnCount - 1) * fColumnSpacing);
317	size.height = BLayoutUtils::AddDistances(size.height,
318		fInsets.top + fInsets.bottom - 1
319		+ (fRowCount - 1) * fRowSpacing);
320	return size;
321}
322
323
324BSize
325GroupView::_SubtractInsetsAndSpacing(BSize size)
326{
327	size.width = BLayoutUtils::SubtractDistances(size.width,
328		fInsets.left + fInsets.right - 1
329		+ (fColumnCount - 1) * fColumnSpacing);
330	size.height = BLayoutUtils::SubtractDistances(size.height,
331		fInsets.top + fInsets.bottom - 1
332		+ (fRowCount - 1) * fRowSpacing);
333	return size;
334}
335
336int32
337GroupView::_RowCount() const
338{
339	int32 childCount = CountChildren();
340	int32 count;
341	if (fOrientation == B_HORIZONTAL)
342		count = min_c(fLineCount, childCount);
343	else
344		count = (childCount + fLineCount - 1) / fLineCount;
345
346	return max_c(count, 1);
347}
348
349
350int32
351GroupView::_ColumnCount() const
352{
353	int32 childCount = CountChildren();
354	int32 count;
355	if (fOrientation == B_HORIZONTAL)
356		count = (childCount + fLineCount - 1) / fLineCount;
357	else
358		count = min_c(fLineCount, childCount);
359
360	return max_c(count, 1);
361}
362
363
364View*
365GroupView::_ChildAt(int32 column, int32 row) const
366{
367	if (fOrientation == B_HORIZONTAL)
368		return ChildAt(column * fLineCount + row);
369	else
370		return ChildAt(row * fLineCount + column);
371}
372
373
374// #pragma mark - Glue
375
376
377Glue::Glue()
378	: View()
379{
380	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
381}
382
383
384// #pragma mark -  Strut
385
386
387Strut::Strut(float pixelWidth, float pixelHeight)
388	: View(),
389	  fSize(pixelWidth >= 0 ? pixelWidth - 1 : B_SIZE_UNSET,
390	  	pixelHeight >= 0 ? pixelHeight - 1 : B_SIZE_UNSET)
391{
392	SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
393}
394
395
396BSize
397Strut::MinSize()
398{
399	return BLayoutUtils::ComposeSize(fSize, BSize(-1, -1));
400}
401
402
403BSize
404Strut::MaxSize()
405{
406	return BLayoutUtils::ComposeSize(fSize,
407		BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
408}
409
410
411// #pragma mark - HStrut
412
413
414HStrut::HStrut(float width)
415	: Strut(width, -1)
416{
417}
418
419
420// #pragma mark - VStrut
421
422
423VStrut::VStrut(float height)
424	: Strut(-1, height)
425{
426}
427