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