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