xref: /haiku/src/libs/alm/ALMLayout.cpp (revision 7583db5a1ec81636829331e5fc7cae81ca7a7ba7)
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 "ALMLayout.h"
10 
11 #include <math.h>		// for floor
12 #include <new>
13 #include <iostream>
14 
15 #include "ViewLayoutItem.h"
16 
17 
18 using namespace LinearProgramming;
19 
20 
21 const BSize kUnsetSize(B_SIZE_UNSET, B_SIZE_UNSET);
22 
23 
24 /*!
25  * Constructor.
26  * Creates new layout engine.
27  *
28  * If friendLayout is not NULL the solver of the friend layout is used.
29  */
30 BALMLayout::BALMLayout(float spacing, BALMLayout* friendLayout)
31 	:
32 	fInset(0.0f),
33 	fSpacing(spacing),
34 	fCurrentArea(NULL)
35 {
36 	fSolver = friendLayout ? friendLayout->Solver() : &fOwnSolver;
37 
38 	fLeft = AddXTab();
39 	fRight = AddXTab();
40 	fTop = AddYTab();
41 	fBottom = AddYTab();
42 
43 	// the Left tab is always at x-position 0, and the Top tab is always at
44 	// y-position 0
45 	fLeft->SetRange(0, 0);
46 	fTop->SetRange(0, 0);
47 
48 	// cached layout values
49 	// need to be invalidated whenever the layout specification is changed
50 	fMinSize = kUnsetSize;
51 	fMaxSize = kUnsetSize;
52 	fPreferredSize = kUnsetSize;
53 
54 	fPerformancePath = NULL;
55 
56 #if USE_SCALE_VARIABLE
57 	fScaleWidth = fSolver->AddVariable();
58 	fScaleHeight = fSolver->AddVariable();
59 #endif
60 }
61 
62 
63 BALMLayout::~BALMLayout()
64 {
65 #if USE_SCALE_VARIABLE
66 	delete fScaleWidth;
67 	delete fScaleHeight;
68 #endif
69 }
70 
71 
72 /**
73  * Adds a new x-tab to the specification.
74  *
75  * @return the new x-tab
76  */
77 XTab*
78 BALMLayout::AddXTab()
79 {
80 	XTab* tab = new(std::nothrow) XTab(fSolver);
81 	if (!tab)
82 		return NULL;
83 	if (!fSolver->AddVariable(tab)) {
84 		delete tab;
85 		return NULL;
86 	}
87 
88 	return tab;
89 }
90 
91 
92 /**
93  * Adds a new y-tab to the specification.
94  *
95  * @return the new y-tab
96  */
97 YTab*
98 BALMLayout::AddYTab()
99 {
100 	YTab* tab = new(std::nothrow) YTab(fSolver);
101 	if (!tab)
102 		return NULL;
103 	if (!fSolver->AddVariable(tab)) {
104 		delete tab;
105 		return NULL;
106 	}
107 
108 	return tab;
109 }
110 
111 
112 /**
113  * Adds a new row to the specification.
114  *
115  * @return the new row
116  */
117 Row*
118 BALMLayout::AddRow()
119 {
120 	return new(std::nothrow) Row(this);
121 }
122 
123 
124 /**
125  * Adds a new row to the specification that is glued to the given y-tabs.
126  *
127  * @param top
128  * @param bottom
129  * @return the new row
130  */
131 Row*
132 BALMLayout::AddRow(YTab* top, YTab* bottom)
133 {
134 	Row* row = new(std::nothrow) Row(this);
135 	if (top != NULL)
136 		row->Constraints()->AddItem(row->Top()->IsEqual(top));
137 	if (bottom != NULL)
138 		row->Constraints()->AddItem(row->Bottom()->IsEqual(bottom));
139 	return row;
140 }
141 
142 
143 /**
144  * Adds a new column to the specification.
145  *
146  * @return the new column
147  */
148 Column*
149 BALMLayout::AddColumn()
150 {
151 	return new(std::nothrow) Column(this);
152 }
153 
154 
155 /**
156  * Adds a new column to the specification that is glued to the given x-tabs.
157  *
158  * @param left
159  * @param right
160  * @return the new column
161  */
162 Column*
163 BALMLayout::AddColumn(XTab* left, XTab* right)
164 {
165 	Column* column = new(std::nothrow) Column(this);
166 	if (left != NULL)
167 		column->Constraints()->AddItem(column->Left()->IsEqual(left));
168 	if (right != NULL)
169 		column->Constraints()->AddItem(column->Right()->IsEqual(right));
170 	return column;
171 }
172 
173 
174 /**
175  * Finds the area that contains the given control.
176  *
177  * @param control	the control to look for
178  * @return the area that contains the control
179  */
180 Area*
181 BALMLayout::AreaFor(const BView* control) const
182 {
183 	return AreaFor(ItemAt(IndexOfView(const_cast<BView*>(control))));
184 }
185 
186 
187 Area*
188 BALMLayout::AreaFor(const BLayoutItem* item) const
189 {
190 	if (!item)
191 		return NULL;
192 	return static_cast<Area*>(item->LayoutData());
193 }
194 
195 
196 Area*
197 BALMLayout::CurrentArea() const
198 {
199 	return fCurrentArea;
200 }
201 
202 
203 void
204 BALMLayout::SetCurrentArea(const Area* area)
205 {
206 	fCurrentArea = const_cast<Area*>(area);
207 }
208 
209 
210 void
211 BALMLayout::SetCurrentArea(const BView* view)
212 {
213 	fCurrentArea = AreaFor(view);
214 }
215 
216 
217 void
218 BALMLayout::SetCurrentArea(const BLayoutItem* item)
219 {
220 	fCurrentArea = AreaFor(item);
221 }
222 
223 
224 XTab*
225 BALMLayout::LeftOf(const BView* view) const
226 {
227 	return AreaFor(view)->Left();
228 }
229 
230 
231 XTab*
232 BALMLayout::LeftOf(const BLayoutItem* item) const
233 {
234 	return AreaFor(item)->Left();
235 }
236 
237 
238 XTab*
239 BALMLayout::RightOf(const BView* view) const
240 {
241 	return AreaFor(view)->Right();
242 }
243 
244 
245 XTab*
246 BALMLayout::RightOf(const BLayoutItem* item) const
247 {
248 	return AreaFor(item)->Right();
249 }
250 
251 
252 YTab*
253 BALMLayout::TopOf(const BView* view) const
254 {
255 	return AreaFor(view)->Top();
256 }
257 
258 
259 YTab*
260 BALMLayout::TopOf(const BLayoutItem* item) const
261 {
262 	return AreaFor(item)->Top();
263 }
264 
265 
266 YTab*
267 BALMLayout::BottomOf(const BView* view) const
268 {
269 	return AreaFor(view)->Bottom();
270 }
271 
272 
273 YTab*
274 BALMLayout::BottomOf(const BLayoutItem* item) const
275 {
276 	return AreaFor(item)->Bottom();
277 }
278 
279 
280 void
281 BALMLayout::BuildLayout(GroupItem& item, XTab* left, YTab* top, XTab* right,
282 	YTab* bottom)
283 {
284 	if (!left)
285 		left = Left();
286 	if (!top)
287 		top = Top();
288 	if (!right)
289 		right = Right();
290 	if (!bottom)
291 		bottom = Bottom();
292 
293 	_ParseGroupItem(item, left, top, right, bottom);
294 }
295 
296 
297 void
298 BALMLayout::_ParseGroupItem(GroupItem& item, XTab* left, YTab* top, XTab* right,
299 	YTab* bottom)
300 {
301 	if (item.LayoutItem())
302 		AddItem(item.LayoutItem(), left, top, right, bottom);
303 	else if (item.View()) {
304 		AddView(item.View(), left, top, right, bottom);
305 	}
306 	else {
307 		for (unsigned int i = 0; i < item.GroupItems().size(); i++) {
308 			GroupItem& current = const_cast<GroupItem&>(
309 				item.GroupItems()[i]);
310 			if (item.Orientation() == B_HORIZONTAL) {
311 				XTab* r = (i == item.GroupItems().size() - 1) ? right
312 					: AddXTab();
313 				_ParseGroupItem(current, left, top, r, bottom);
314 				left = r;
315 			}
316 			else {
317 				YTab* b = (i == item.GroupItems().size() - 1) ? bottom
318 					: AddYTab();
319 				_ParseGroupItem(current, left, top, right, b);
320 				top = b;
321 			}
322 		}
323 	}
324 }
325 
326 
327 BLayoutItem*
328 BALMLayout::AddView(BView* child)
329 {
330 	return AddView(-1, child);
331 }
332 
333 
334 BLayoutItem*
335 BALMLayout::AddView(int32 index, BView* child)
336 {
337 	return BAbstractLayout::AddView(index, child);
338 }
339 
340 
341 /**
342  * Adds a new area to the specification, automatically setting preferred size constraints.
343  *
344  * @param left			left border
345  * @param top			top border
346  * @param right		right border
347  * @param bottom		bottom border
348  * @param content		the control which is the area content
349  * @return the new area
350  */
351 Area*
352 BALMLayout::AddView(BView* view, XTab* left, YTab* top, XTab* right,
353 	YTab* bottom)
354 {
355 	BLayoutItem* item = _CreateLayoutItem(view);
356 	Area* area = AddItem(item, left, top, right, bottom);
357 	if (!area) {
358 		delete item;
359 		return NULL;
360 	}
361 	return area;
362 }
363 
364 
365 /**
366  * Adds a new area to the specification, automatically setting preferred size constraints.
367  *
368  * @param row			the row that defines the top and bottom border
369  * @param column		the column that defines the left and right border
370  * @param content		the control which is the area content
371  * @return the new area
372  */
373 Area*
374 BALMLayout::AddView(BView* view, Row* row, Column* column)
375 {
376 	BLayoutItem* item = _CreateLayoutItem(view);
377 	Area* area = AddItem(item, row, column);
378 	if (!area) {
379 		delete item;
380 		return NULL;
381 	}
382 	return area;
383 }
384 
385 
386 Area*
387 BALMLayout::AddViewToRight(BView* view, XTab* right, YTab* top, YTab* bottom)
388 {
389 	BLayoutItem* item = _CreateLayoutItem(view);
390 	Area* area = AddItemToRight(item, right, top, bottom);
391 	if (!area) {
392 		delete item;
393 		return NULL;
394 	}
395 	return area;
396 }
397 
398 
399 Area*
400 BALMLayout::AddViewToLeft(BView* view, XTab* left, YTab* top, YTab* bottom)
401 {
402 	BLayoutItem* item = _CreateLayoutItem(view);
403 	Area* area = AddItemToLeft(item, left, top, bottom);
404 	if (!area) {
405 		delete item;
406 		return NULL;
407 	}
408 	return area;
409 }
410 
411 
412 Area*
413 BALMLayout::AddViewToTop(BView* view, YTab* top, XTab* left, XTab* right)
414 {
415 	BLayoutItem* item = _CreateLayoutItem(view);
416 	Area* area = AddItemToTop(item, top, left, right);
417 	if (!area) {
418 		delete item;
419 		return NULL;
420 	}
421 	return area;
422 }
423 
424 
425 Area*
426 BALMLayout::AddViewToBottom(BView* view, YTab* bottom, XTab* left, XTab* right)
427 {
428 	BLayoutItem* item = _CreateLayoutItem(view);
429 	Area* area = AddItemToBottom(item, bottom, left, right);
430 	if (!area) {
431 		delete item;
432 		return NULL;
433 	}
434 	return area;
435 }
436 
437 
438 bool
439 BALMLayout::AddItem(BLayoutItem* item)
440 {
441 	return AddItem(-1, item);
442 }
443 
444 
445 bool
446 BALMLayout::AddItem(int32 index, BLayoutItem* item)
447 {
448 	if (!item)
449 		return false;
450 
451 	// simply add the item at the upper right corner of the previous item
452 	// TODO maybe find a more elegant solution
453 	XTab* left = Left();
454 	YTab* top = Top();
455 
456 	// check range
457 	if (index < 0 || index > CountItems())
458 		index = CountItems();
459 
460 	// for index = 0 we already have set the right tabs
461 	if (index != 0) {
462 		BLayoutItem* prevItem = ItemAt(index - 1);
463 		Area* area = AreaFor(prevItem);
464 		if (area) {
465 			left = area->Right();
466 			top = area->Top();
467 		}
468 	}
469 	Area* area = AddItem(item, left, top);
470 	return area ? true : false;
471 }
472 
473 
474 Area*
475 BALMLayout::AddItem(BLayoutItem* item, XTab* left, YTab* top, XTab* right,
476 	YTab* bottom)
477 {
478 	if (!right)
479 		right = AddXTab();
480 	if (!bottom)
481 		bottom = AddYTab();
482 
483 	if (!BAbstractLayout::AddItem(-1, item))
484 		return NULL;
485 	Area* area = AreaFor(item);
486 	if (!area)
487 		return NULL;
488 	fCurrentArea = area;
489 
490 #if USE_SCALE_VARIABLE
491 	area->_Init(fSolver, left, top, right, bottom, fScaleWidth, fScaleHeight);
492 #else
493 	area->_Init(fSolver, left, top, right, bottom);
494 #endif
495 	return area;
496 }
497 
498 
499 Area*
500 BALMLayout::AddItem(BLayoutItem* item, Row* row, Column* column)
501 {
502 	if (!BAbstractLayout::AddItem(-1, item))
503 		return NULL;
504 	Area* area = AreaFor(item);
505 	if (!area)
506 		return NULL;
507 	fCurrentArea = area;
508 
509 #if USE_SCALE_VARIABLE
510 	area->_Init(fSolver, row, column, fScaleWidth, fScaleHeight);
511 #else
512 	area->_Init(fSolver, row, column);
513 #endif
514 	return area;
515 }
516 
517 
518 Area*
519 BALMLayout::AddItemToRight(BLayoutItem* item, XTab* right, YTab* top,
520 	YTab* bottom)
521 {
522 	XTab* left = fCurrentArea->Right();
523 	if (!right)
524 		right = AddXTab();
525 	if (!top)
526 		top = fCurrentArea->Top();
527 	if (!bottom)
528 		bottom = fCurrentArea->Bottom();
529 
530 	return AddItem(item, left, top, right, bottom);
531 }
532 
533 
534 Area*
535 BALMLayout::AddItemToLeft(BLayoutItem* item, XTab* left, YTab* top,
536 	YTab* bottom)
537 {
538 	if (!left)
539 		left = AddXTab();
540 	XTab* right = fCurrentArea->Left();
541 	if (!top)
542 		top = fCurrentArea->Top();
543 	if (!bottom)
544 		bottom = fCurrentArea->Bottom();
545 
546 	return AddItem(item, left, top, right, bottom);
547 }
548 
549 
550 Area*
551 BALMLayout::AddItemToTop(BLayoutItem* item, YTab* top, XTab* left, XTab* right)
552 {
553 	if (!left)
554 		left = fCurrentArea->Left();
555 	if (!right)
556 		right = fCurrentArea->Right();
557 	if (!top)
558 		top = AddYTab();
559 	YTab* bottom = fCurrentArea->Top();
560 
561 	return AddItem(item, left, top, right, bottom);
562 }
563 
564 
565 Area*
566 BALMLayout::AddItemToBottom(BLayoutItem* item, YTab* bottom, XTab* left,
567 	XTab* right)
568 {
569 	if (!left)
570 		left = fCurrentArea->Left();
571 	if (!right)
572 		right = fCurrentArea->Right();
573 	YTab* top = fCurrentArea->Bottom();
574 	if (!bottom)
575 		bottom = AddYTab();
576 
577 	return AddItem(item, left, top, right, bottom);
578 }
579 
580 
581 /**
582  * Gets the left variable.
583  */
584 XTab*
585 BALMLayout::Left() const
586 {
587 	return fLeft;
588 }
589 
590 
591 /**
592  * Gets the right variable.
593  */
594 XTab*
595 BALMLayout::Right() const
596 {
597 	return fRight;
598 }
599 
600 
601 /**
602  * Gets the top variable.
603  */
604 YTab*
605 BALMLayout::Top() const
606 {
607 	return fTop;
608 }
609 
610 
611 /**
612  * Gets the bottom variable.
613  */
614 YTab*
615 BALMLayout::Bottom() const
616 {
617 	return fBottom;
618 }
619 
620 
621 /**
622  * Gets minimum size.
623  */
624 BSize
625 BALMLayout::BaseMinSize() {
626 	if (fMinSize == kUnsetSize)
627 		fMinSize = _CalculateMinSize();
628 	return fMinSize;
629 }
630 
631 
632 /**
633  * Gets maximum size.
634  */
635 BSize
636 BALMLayout::BaseMaxSize()
637 {
638 	if (fMaxSize == kUnsetSize)
639 		fMaxSize = _CalculateMaxSize();
640 	return fMaxSize;
641 }
642 
643 
644 /**
645  * Gets preferred size.
646  */
647 BSize
648 BALMLayout::BasePreferredSize()
649 {
650 	if (fPreferredSize == kUnsetSize)
651 		fPreferredSize = _CalculatePreferredSize();
652 	return fPreferredSize;
653 }
654 
655 
656 /**
657  * Gets the alignment.
658  */
659 BAlignment
660 BALMLayout::BaseAlignment()
661 {
662 	BAlignment alignment;
663 	alignment.SetHorizontal(B_ALIGN_HORIZONTAL_CENTER);
664 	alignment.SetVertical(B_ALIGN_VERTICAL_CENTER);
665 	return alignment;
666 }
667 
668 
669 /**
670  * Invalidates the layout.
671  * Resets minimum/maximum/preferred size.
672  */
673 void
674 BALMLayout::InvalidateLayout(bool children)
675 {
676 	BLayout::InvalidateLayout(children);
677 	fMinSize = kUnsetSize;
678 	fMaxSize = kUnsetSize;
679 	fPreferredSize = kUnsetSize;
680 }
681 
682 
683 bool
684 BALMLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
685 {
686 	item->SetLayoutData(new(std::nothrow) Area(item));
687 	return item->LayoutData() != NULL;
688 }
689 
690 
691 void
692 BALMLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
693 {
694 	if (Area* area = AreaFor(item)) {
695 		item->SetLayoutData(NULL);
696 		delete area;
697 	}
698 }
699 
700 
701 /**
702  * Calculate and set the layout.
703  * If no layout specification is given, a specification is reverse engineered automatically.
704  */
705 void
706 BALMLayout::DerivedLayoutItems()
707 {
708 	_UpdateAreaConstraints();
709 
710 	// Enforced absolute positions of Right and Bottom
711 	BRect area(LayoutArea());
712 	Right()->SetRange(area.right, area.right);
713 	Bottom()->SetRange(area.bottom, area.bottom);
714 
715 	fSolver->Solve();
716 
717 	// if new layout is infeasible, use previous layout
718 	if (fSolver->Result() == kInfeasible)
719 		return;
720 
721 	if (fSolver->Result() != kOptimal) {
722 		fSolver->Save("failed-layout.txt");
723 		printf("Could not solve the layout specification (%d). ",
724 			fSolver->Result());
725 		printf("Saved specification in file failed-layout.txt\n");
726 	}
727 
728 	// set the calculated positions and sizes for every area
729 	for (int32 i = 0; i < CountItems(); i++)
730 		AreaFor(ItemAt(i))->_DoLayout();
731 }
732 
733 
734 /**
735  * Gets the path of the performance log file.
736  *
737  * @return the path of the performance log file
738  */
739 char*
740 BALMLayout::PerformancePath() const
741 {
742 	return fPerformancePath;
743 }
744 
745 
746 /**
747  * Sets the path of the performance log file.
748  *
749  * @param path	the path of the performance log file
750  */
751 void
752 BALMLayout::SetPerformancePath(char* path)
753 {
754 	fPerformancePath = path;
755 }
756 
757 
758 LinearSpec*
759 BALMLayout::Solver() const
760 {
761 	return const_cast<LinearSpec*>(fSolver);
762 }
763 
764 
765 void
766 BALMLayout::SetInset(float inset)
767 {
768 	fInset = inset;
769 }
770 
771 
772 float
773 BALMLayout::Inset() const
774 {
775 	return fInset;
776 }
777 
778 
779 void
780 BALMLayout::SetSpacing(float spacing)
781 {
782 	fSpacing = spacing;
783 }
784 
785 
786 float
787 BALMLayout::Spacing() const
788 {
789 	return fSpacing;
790 }
791 
792 
793 BLayoutItem*
794 BALMLayout::_CreateLayoutItem(BView* view)
795 {
796 	return new(std::nothrow) BViewLayoutItem(view);
797 }
798 
799 
800 /**
801  * Caculates the miminum size.
802  */
803 BSize
804 BALMLayout::_CalculateMinSize()
805 {
806 	_UpdateAreaConstraints();
807 
808 	return fSolver->MinSize(Right(), Bottom());
809 }
810 
811 
812 /**
813  * Caculates the maximum size.
814  */
815 BSize
816 BALMLayout::_CalculateMaxSize()
817 {
818 	_UpdateAreaConstraints();
819 
820 	return fSolver->MaxSize(Right(), Bottom());
821 }
822 
823 
824 /**
825  * Caculates the preferred size.
826  */
827 BSize
828 BALMLayout::_CalculatePreferredSize()
829 {
830 	_UpdateAreaConstraints();
831 
832 	fSolver->Solve();
833 	if (fSolver->Result() != kOptimal) {
834 		fSolver->Save("failed-layout.txt");
835 		printf("Could not solve the layout specification (%d). "
836 			"Saved specification in file failed-layout.txt", fSolver->Result());
837 	}
838 
839 	return BSize(Right()->Value() - Left()->Value(),
840 		Bottom()->Value() - Top()->Value());
841 }
842 
843 
844 void
845 BALMLayout::_UpdateAreaConstraints()
846 {
847 	for (int i = 0; i < CountItems(); i++)
848 		AreaFor(ItemAt(i))->InvalidateSizeConstraints();
849 }
850