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