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