1/*
2 * Copyright 2007-2008, Christof Lutteroth, lutteroth@cs.auckland.ac.nz
3 * Copyright 2007-2008, James Kim, jkim202@ec.auckland.ac.nz
4 * Copyright 2010, Clemens Zeidler <haiku@clemens-zeidler.de>
5 * Distributed under the terms of the MIT License.
6 */
7
8
9#include "Area.h"
10
11#include <Alignment.h>
12#include <ControlLook.h>
13#include <View.h>
14
15#include "ALMLayout.h"
16#include "RowColumnManager.h"
17#include "Row.h"
18#include "Column.h"
19
20
21using namespace LinearProgramming;
22
23
24BLayoutItem*
25Area::Item()
26{
27	return fLayoutItem;
28}
29
30
31/**
32 * Gets the left tab of the area.
33 *
34 * @return the left tab of the area
35 */
36XTab*
37Area::Left() const
38{
39	return fLeft;
40}
41
42
43/**
44 * Gets the right tab of the area.
45 *
46 * @return the right tab of the area
47 */
48XTab*
49Area::Right() const
50{
51	return fRight;
52}
53
54
55/**
56 * Gets the top tab of the area.
57 */
58YTab*
59Area::Top() const
60{
61	return fTop;
62}
63
64
65/**
66 * Gets the bottom tab of the area.
67 */
68YTab*
69Area::Bottom() const
70{
71	return fBottom;
72}
73
74
75/**
76 * Sets the left tab of the area.
77 *
78 * @param left	the left tab of the area
79 */
80void
81Area::SetLeft(BReference<XTab> left)
82{
83	fLeft = left;
84
85	fMinContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
86	if (fMaxContentWidth != NULL)
87		fMaxContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
88	fRowColumnManager->TabsChanged(this);
89
90	fLayoutItem->Layout()->InvalidateLayout();
91}
92
93
94/**
95 * Sets the right tab of the area.
96 *
97 * @param right	the right tab of the area
98 */
99void
100Area::SetRight(BReference<XTab> right)
101{
102	fRight = right;
103
104	fMinContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
105	if (fMaxContentWidth != NULL)
106		fMaxContentWidth->SetLeftSide(-1.0, fLeft, 1.0, fRight);
107	fRowColumnManager->TabsChanged(this);
108
109	fLayoutItem->Layout()->InvalidateLayout();
110}
111
112
113/**
114 * Sets the top tab of the area.
115 */
116void
117Area::SetTop(BReference<YTab> top)
118{
119	fTop = top;
120
121	fMinContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
122	if (fMaxContentHeight != NULL)
123		fMaxContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
124	fRowColumnManager->TabsChanged(this);
125
126	fLayoutItem->Layout()->InvalidateLayout();
127}
128
129
130/**
131 * Sets the bottom tab of the area.
132 */
133void
134Area::SetBottom(BReference<YTab> bottom)
135{
136	fBottom = bottom;
137
138	fMinContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
139	if (fMaxContentHeight != NULL)
140		fMaxContentHeight->SetLeftSide(-1.0, fTop, 1.0, fBottom);
141	fRowColumnManager->TabsChanged(this);
142
143	fLayoutItem->Layout()->InvalidateLayout();
144}
145
146
147/**
148 * Gets the row that defines the top and bottom tabs.
149 */
150Row*
151Area::GetRow() const
152{
153	return fRow;
154}
155
156
157/**
158 * Gets the column that defines the left and right tabs.
159 */
160Column*
161Area::GetColumn() const
162{
163	return fColumn;
164}
165
166
167/**
168 * The reluctance with which the area's content shrinks below its preferred size.
169 * The bigger the less likely is such shrinking.
170 */
171BSize
172Area::ShrinkPenalties() const
173{
174	return fShrinkPenalties;
175}
176
177
178/**
179 * The reluctance with which the area's content grows over its preferred size.
180 * The bigger the less likely is such growth.
181 */
182BSize
183Area::GrowPenalties() const
184{
185	return fGrowPenalties;
186}
187
188
189void
190Area::SetShrinkPenalties(BSize shrink) {
191	fShrinkPenalties = shrink;
192
193	fLayoutItem->Layout()->InvalidateLayout();
194}
195
196
197void
198Area::SetGrowPenalties(BSize grow)
199{
200	fGrowPenalties = grow;
201
202	fLayoutItem->Layout()->InvalidateLayout();
203}
204
205
206/**
207 * Gets aspect ratio of the area's content.
208 */
209double
210Area::ContentAspectRatio() const
211{
212	return fContentAspectRatio;
213}
214
215
216/**
217 * Sets aspect ratio of the area's content.
218 * May be different from the aspect ratio of the area.
219 */
220void
221Area::SetContentAspectRatio(double ratio)
222{
223	fContentAspectRatio = ratio;
224	if (fContentAspectRatio <= 0) {
225		delete fContentAspectRatioC;
226		fContentAspectRatioC = NULL;
227	} else if (fContentAspectRatioC == NULL) {
228		fContentAspectRatioC = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight,
229			ratio, fTop, -ratio, fBottom, kEQ, 0.0);
230	} else {
231		fContentAspectRatioC->SetLeftSide(-1.0, fLeft, 1.0, fRight, ratio,
232			fTop, -ratio, fBottom);
233	}
234	/* called during BALMLayout::ItemUnarchived */
235	if (BLayout* layout = fLayoutItem->Layout())
236		layout->InvalidateLayout();
237}
238
239
240void
241Area::GetInsets(float* left, float* top, float* right, float* bottom) const
242{
243	if (left)
244		*left = fLeftTopInset.Width();
245	if (top)
246		*top = fLeftTopInset.Height();
247	if (right)
248		*right = fRightBottomInset.Width();
249	if (bottom)
250		*bottom = fRightBottomInset.Height();
251}
252
253
254/**
255 * Gets left inset between area and its content.
256 */
257float
258Area::LeftInset() const
259{
260	if (fLeftTopInset.IsWidthSet())
261		return fLeftTopInset.Width();
262
263	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
264	return layout->InsetForTab(fLeft.Get());
265}
266
267
268/**
269 * Gets top inset between area and its content.
270 */
271float
272Area::TopInset() const
273{
274	if (fLeftTopInset.IsHeightSet())
275		return fLeftTopInset.Height();
276
277	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
278	return layout->InsetForTab(fTop.Get());
279}
280
281
282/**
283 * Gets right inset between area and its content.
284 */
285float
286Area::RightInset() const
287{
288	if (fRightBottomInset.IsWidthSet())
289		return fRightBottomInset.Width();
290
291	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
292	return layout->InsetForTab(fRight.Get());
293}
294
295
296/**
297 * Gets bottom inset between area and its content.
298 */
299float
300Area::BottomInset() const
301{
302	if (fRightBottomInset.IsHeightSet())
303		return fRightBottomInset.Height();
304
305	BALMLayout* layout = static_cast<BALMLayout*>(fLayoutItem->Layout());
306	return layout->InsetForTab(fBottom.Get());
307}
308
309
310void
311Area::SetInsets(float insets)
312{
313	if (insets != B_SIZE_UNSET)
314		insets = BControlLook::ComposeSpacing(insets);
315
316	fLeftTopInset.Set(insets, insets);
317	fRightBottomInset.Set(insets, insets);
318	fLayoutItem->Layout()->InvalidateLayout();
319}
320
321
322void
323Area::SetInsets(float horizontal, float vertical)
324{
325	if (horizontal != B_SIZE_UNSET)
326		horizontal = BControlLook::ComposeSpacing(horizontal);
327	if (vertical != B_SIZE_UNSET)
328		vertical = BControlLook::ComposeSpacing(vertical);
329
330	fLeftTopInset.Set(horizontal, horizontal);
331	fRightBottomInset.Set(vertical, vertical);
332	fLayoutItem->Layout()->InvalidateLayout();
333}
334
335
336void
337Area::SetInsets(float left, float top, float right, float bottom)
338{
339	if (left != B_SIZE_UNSET)
340		left = BControlLook::ComposeSpacing(left);
341	if (right != B_SIZE_UNSET)
342		right = BControlLook::ComposeSpacing(right);
343	if (top != B_SIZE_UNSET)
344		top = BControlLook::ComposeSpacing(top);
345	if (bottom != B_SIZE_UNSET)
346		bottom = BControlLook::ComposeSpacing(bottom);
347
348	fLeftTopInset.Set(left, top);
349	fRightBottomInset.Set(right, bottom);
350	fLayoutItem->Layout()->InvalidateLayout();
351}
352
353
354/**
355 * Sets left inset between area and its content.
356 */
357void
358Area::SetLeftInset(float left)
359{
360	fLeftTopInset.width = left;
361	fLayoutItem->Layout()->InvalidateLayout();
362}
363
364
365/**
366 * Sets top inset between area and its content.
367 */
368void
369Area::SetTopInset(float top)
370{
371	fLeftTopInset.height = top;
372	fLayoutItem->Layout()->InvalidateLayout();
373}
374
375
376/**
377 * Sets right inset between area and its content.
378 */
379void
380Area::SetRightInset(float right)
381{
382	fRightBottomInset.width = right;
383	fLayoutItem->Layout()->InvalidateLayout();
384}
385
386
387/**
388 * Sets bottom inset between area and its content.
389 */
390void
391Area::SetBottomInset(float bottom)
392{
393	fRightBottomInset.height = bottom;
394	fLayoutItem->Layout()->InvalidateLayout();
395}
396
397
398BString
399Area::ToString() const
400{
401	BString string = "Area(";
402	string += fLeft->ToString();
403	string << ", ";
404	string += fTop->ToString();
405	string << ", ";
406	string += fRight->ToString();
407	string << ", ";
408	string += fBottom->ToString();
409	string << ")";
410	return string;
411}
412
413
414/*!
415 * Sets the width of the area to be the same as the width of the given area
416 * times factor.
417 *
418 * @param area	the area that should have the same width
419 * @return the same-width constraint
420 */
421Constraint*
422Area::SetWidthAs(Area* area, float factor)
423{
424	return fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, factor, area->Left(),
425		-factor, area->Right(), kEQ, 0.0);
426}
427
428
429/*!
430 * Sets the height of the area to be the same as the height of the given area
431 * times factor.
432 *
433 * @param area	the area that should have the same height
434 * @return the same-height constraint
435 */
436Constraint*
437Area::SetHeightAs(Area* area, float factor)
438{
439	return fLS->AddConstraint(-1.0, fTop, 1.0, fBottom, factor, area->Top(),
440		-factor, area->Bottom(), kEQ, 0.0);
441}
442
443
444void
445Area::InvalidateSizeConstraints()
446{
447	// check if if we are initialized
448	if (!fLeft)
449		return;
450
451	BSize minSize = fLayoutItem->MinSize();
452	BSize maxSize = fLayoutItem->MaxSize();
453
454	_UpdateMinSizeConstraint(minSize);
455	_UpdateMaxSizeConstraint(maxSize);
456}
457
458
459BRect
460Area::Frame() const
461{
462	return BRect(round(fLeft->Value()), round(fTop->Value()),
463		round(fRight->Value()), round(fBottom->Value()));
464}
465
466
467/**
468 * Destructor.
469 * Removes the area from its specification.
470 */
471Area::~Area()
472{
473	delete fMinContentWidth;
474	delete fMaxContentWidth;
475	delete fMinContentHeight;
476	delete fMaxContentHeight;
477	delete fContentAspectRatioC;
478}
479
480
481static int32 sAreaID = 0;
482
483static int32
484new_area_id()
485{
486	return sAreaID++;
487}
488
489
490/**
491 * Constructor.
492 * Uses XTabs and YTabs.
493 */
494Area::Area(BLayoutItem* item)
495	:
496	fLayoutItem(item),
497	fLS(NULL),
498	fLeft(NULL),
499	fRight(NULL),
500	fTop(NULL),
501	fBottom(NULL),
502	fRow(NULL),
503	fColumn(NULL),
504	fShrinkPenalties(5, 5),
505	fGrowPenalties(5, 5),
506	fContentAspectRatio(-1),
507	fRowColumnManager(NULL),
508	fMinContentWidth(NULL),
509	fMaxContentWidth(NULL),
510	fMinContentHeight(NULL),
511	fMaxContentHeight(NULL),
512	fContentAspectRatioC(NULL)
513{
514	fID = new_area_id();
515}
516
517
518int32
519Area::ID() const
520{
521	return fID;
522}
523
524
525void
526Area::SetID(int32 id)
527{
528	fID = id;
529}
530
531
532/**
533 * Initialize variables.
534 */
535void
536Area::_Init(LinearSpec* ls, XTab* left, YTab* top, XTab* right, YTab* bottom,
537	RowColumnManager* manager)
538{
539	fLS = ls;
540	fLeft = left;
541	fRight = right;
542	fTop = top;
543	fBottom = bottom;
544
545	fRowColumnManager = manager;
546
547	// adds the two essential constraints of the area that make sure that the
548	// left x-tab is really to the left of the right x-tab, and the top y-tab
549	// really above the bottom y-tab
550	fMinContentWidth = ls->AddConstraint(-1.0, fLeft, 1.0, fRight, kGE, 0);
551	fMinContentHeight = ls->AddConstraint(-1.0, fTop, 1.0, fBottom, kGE, 0);
552
553	InvalidateSizeConstraints();
554}
555
556
557void
558Area::_Init(LinearSpec* ls, Row* row, Column* column, RowColumnManager* manager)
559{
560	_Init(ls, column->Left(), row->Top(), column->Right(),
561		row->Bottom(), manager);
562
563	fRow = row;
564	fColumn = column;
565}
566
567
568/**
569 * Perform layout on the area.
570 */
571void
572Area::_DoLayout(const BPoint& offset)
573{
574	// check if if we are initialized
575	if (!fLeft)
576		return;
577
578	if (!fLayoutItem->IsVisible())
579		fLayoutItem->AlignInFrame(BRect(0, 0, -1, -1));
580
581	BRect areaFrame(Frame());
582	areaFrame.left += LeftInset();
583	areaFrame.right -= RightInset();
584	areaFrame.top += TopInset();
585	areaFrame.bottom -= BottomInset();
586
587	fLayoutItem->AlignInFrame(areaFrame.OffsetBySelf(offset));
588}
589
590
591void
592Area::_UpdateMinSizeConstraint(BSize min)
593{
594	if (!fLayoutItem->IsVisible()) {
595		fMinContentHeight->SetRightSide(-1);
596		fMinContentWidth->SetRightSide(-1);
597		return;
598	}
599
600	float width = 0.;
601	float height = 0.;
602	if (min.width > 0)
603		width = min.Width() + LeftInset() + RightInset();
604	if (min.height > 0)
605		height = min.Height() + TopInset() + BottomInset();
606
607	fMinContentWidth->SetRightSide(width);
608	fMinContentHeight->SetRightSide(height);
609}
610
611
612void
613Area::_UpdateMaxSizeConstraint(BSize max)
614{
615	if (!fLayoutItem->IsVisible()) {
616		if (fMaxContentHeight != NULL)
617			fMaxContentHeight->SetRightSide(B_SIZE_UNLIMITED);
618		if (fMaxContentWidth != NULL)
619			fMaxContentWidth->SetRightSide(B_SIZE_UNLIMITED);
620		return;
621	}
622
623	max.width += LeftInset() + RightInset();
624	max.height += TopInset() + BottomInset();
625
626	const double kPriority = 100;
627	// we only need max constraints if the alignment is full height/width
628	// otherwise we can just align the item in the free space
629	BAlignment alignment = fLayoutItem->Alignment();
630	double priority = kPriority;
631	if (alignment.Vertical() == B_ALIGN_USE_FULL_HEIGHT)
632		priority = -1;
633
634	if (max.Height() < 20000) {
635		if (fMaxContentHeight == NULL) {
636			fMaxContentHeight = fLS->AddConstraint(-1.0, fTop, 1.0, fBottom,
637				kLE, max.Height(), priority, priority);
638		} else {
639			fMaxContentHeight->SetRightSide(max.Height());
640			fMaxContentHeight->SetPenaltyNeg(priority);
641			fMaxContentHeight->SetPenaltyPos(priority);
642		}
643	} else {
644		delete fMaxContentHeight;
645		fMaxContentHeight = NULL;
646	}
647
648	priority = kPriority;
649	if (alignment.Horizontal() == B_ALIGN_USE_FULL_WIDTH)
650		priority = -1;
651
652	if (max.Width() < 20000) {
653		if (fMaxContentWidth == NULL) {
654			fMaxContentWidth = fLS->AddConstraint(-1.0, fLeft, 1.0, fRight, kLE,
655				max.Width(), priority, priority);
656		} else {
657			fMaxContentWidth->SetRightSide(max.Width());
658			fMaxContentWidth->SetPenaltyNeg(priority);
659			fMaxContentWidth->SetPenaltyPos(priority);
660		}
661	} else {
662		delete fMaxContentWidth;
663		fMaxContentWidth = NULL;
664	}
665}
666