xref: /haiku/src/libs/alm/ALMLayout.cpp (revision 70e207613508fb5b562405c4edc33ddfe77fbfdf)
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 BLayoutItem*
252 BALMLayout::AddView(BView* child)
253 {
254 	return AddView(-1, child);
255 }
256 
257 
258 BLayoutItem*
259 BALMLayout::AddView(int32 index, BView* child)
260 {
261 	return BAbstractLayout::AddView(index, child);
262 }
263 
264 
265 /**
266  * Adds a new area to the specification, automatically setting preferred size constraints.
267  *
268  * @param left			left border
269  * @param top			top border
270  * @param right		right border
271  * @param bottom		bottom border
272  * @param content		the control which is the area content
273  * @return the new area
274  */
275 Area*
276 BALMLayout::AddView(BView* view, XTab* left, YTab* top, XTab* right,
277 	YTab* bottom)
278 {
279 	BLayoutItem* item = _CreateLayoutItem(view);
280 	Area* area = AddItem(item, left, top, right, bottom);
281 	if (!area) {
282 		delete item;
283 		return NULL;
284 	}
285 	return area;
286 }
287 
288 
289 /**
290  * Adds a new area to the specification, automatically setting preferred size constraints.
291  *
292  * @param row			the row that defines the top and bottom border
293  * @param column		the column that defines the left and right border
294  * @param content		the control which is the area content
295  * @return the new area
296  */
297 Area*
298 BALMLayout::AddView(BView* view, Row* row, Column* column)
299 {
300 	BLayoutItem* item = _CreateLayoutItem(view);
301 	Area* area = AddItem(item, row, column);
302 	if (!area) {
303 		delete item;
304 		return NULL;
305 	}
306 	return area;
307 }
308 
309 
310 Area*
311 BALMLayout::AddViewToRight(BView* view, XTab* right, YTab* top, YTab* bottom)
312 {
313 	BLayoutItem* item = _CreateLayoutItem(view);
314 	Area* area = AddItemToRight(item, right, top, bottom);
315 	if (!area) {
316 		delete item;
317 		return NULL;
318 	}
319 	return area;
320 }
321 
322 
323 Area*
324 BALMLayout::AddViewToLeft(BView* view, XTab* left, YTab* top, YTab* bottom)
325 {
326 	BLayoutItem* item = _CreateLayoutItem(view);
327 	Area* area = AddItemToLeft(item, left, top, bottom);
328 	if (!area) {
329 		delete item;
330 		return NULL;
331 	}
332 	return area;
333 }
334 
335 
336 Area*
337 BALMLayout::AddViewToTop(BView* view, YTab* top, XTab* left, XTab* right)
338 {
339 	BLayoutItem* item = _CreateLayoutItem(view);
340 	Area* area = AddItemToTop(item, top, left, right);
341 	if (!area) {
342 		delete item;
343 		return NULL;
344 	}
345 	return area;
346 }
347 
348 
349 Area*
350 BALMLayout::AddViewToBottom(BView* view, YTab* bottom, XTab* left, XTab* right)
351 {
352 	BLayoutItem* item = _CreateLayoutItem(view);
353 	Area* area = AddItemToBottom(item, bottom, left, right);
354 	if (!area) {
355 		delete item;
356 		return NULL;
357 	}
358 	return area;
359 }
360 
361 
362 bool
363 BALMLayout::AddItem(BLayoutItem* item)
364 {
365 	return AddItem(-1, item);
366 }
367 
368 
369 bool
370 BALMLayout::AddItem(int32 index, BLayoutItem* item)
371 {
372 	if (!item)
373 		return NULL;
374 
375 	// simply add the item at the upper right corner of the previous item
376 	// TODO maybe find a more elegant solution
377 	XTab* left = Left();
378 	YTab* top = Top();
379 
380 	// check range
381 	if (index < 0 || index > CountItems())
382 		index = CountItems();
383 
384 	// for index = 0 we already have set the right tabs
385 	if (index != 0) {
386 		BLayoutItem* prevItem = ItemAt(index - 1);
387 		Area* area = AreaFor(prevItem);
388 		if (area) {
389 			left = area->Right();
390 			top = area->Top();
391 		}
392 	}
393 	Area* area = AddItem(item, left, top);
394 	return area ? true : false;
395 }
396 
397 
398 Area*
399 BALMLayout::AddItem(BLayoutItem* item, XTab* left, YTab* top, XTab* right,
400 	YTab* bottom)
401 {
402 	if (!right)
403 		right = AddXTab();
404 	if (!bottom)
405 		bottom = AddYTab();
406 
407 	if (!BAbstractLayout::AddItem(-1, item))
408 		return NULL;
409 	Area* area = AreaFor(item);
410 	if (!area)
411 		return NULL;
412 	fCurrentArea = area;
413 
414 	area->_Init(&fSolver, left, top, right, bottom);
415 	return area;
416 }
417 
418 
419 Area*
420 BALMLayout::AddItem(BLayoutItem* item, Row* row, Column* column)
421 {
422 	if (!BAbstractLayout::AddItem(-1, item))
423 		return NULL;
424 	Area* area = AreaFor(item);
425 	if (!area)
426 		return NULL;
427 	fCurrentArea = area;
428 
429 	area->_Init(&fSolver, row, column);
430 	return area;
431 }
432 
433 
434 Area*
435 BALMLayout::AddItemToRight(BLayoutItem* item, XTab* right, YTab* top,
436 	YTab* bottom)
437 {
438 	XTab* left = fCurrentArea->Right();
439 	if (!right)
440 		right = AddXTab();
441 	if (!top)
442 		top = fCurrentArea->Top();
443 	if (!bottom)
444 		bottom = fCurrentArea->Bottom();
445 
446 	return AddItem(item, left, top, right, bottom);
447 }
448 
449 
450 Area*
451 BALMLayout::AddItemToLeft(BLayoutItem* item, XTab* left, YTab* top,
452 	YTab* bottom)
453 {
454 	if (!left)
455 		left = AddXTab();
456 	XTab* right = fCurrentArea->Left();
457 	if (!top)
458 		top = fCurrentArea->Top();
459 	if (!bottom)
460 		bottom = fCurrentArea->Bottom();
461 
462 	return AddItem(item, left, top, right, bottom);
463 }
464 
465 
466 Area*
467 BALMLayout::AddItemToTop(BLayoutItem* item, YTab* top, XTab* left, XTab* right)
468 {
469 	if (!left)
470 		left = fCurrentArea->Left();
471 	if (!right)
472 		right = fCurrentArea->Right();
473 	if (!top)
474 		top = AddYTab();
475 	YTab* bottom = fCurrentArea->Top();
476 
477 	return AddItem(item, left, top, right, bottom);
478 }
479 
480 
481 Area*
482 BALMLayout::AddItemToBottom(BLayoutItem* item, YTab* bottom, XTab* left,
483 	XTab* right)
484 {
485 	if (!left)
486 		left = fCurrentArea->Left();
487 	if (!right)
488 		right = fCurrentArea->Right();
489 	YTab* top = fCurrentArea->Bottom();
490 	if (!bottom)
491 		bottom = AddYTab();
492 
493 	return AddItem(item, left, top, right, bottom);
494 }
495 
496 
497 /**
498  * Gets the left variable.
499  */
500 XTab*
501 BALMLayout::Left() const
502 {
503 	return fLeft;
504 }
505 
506 
507 /**
508  * Gets the right variable.
509  */
510 XTab*
511 BALMLayout::Right() const
512 {
513 	return fRight;
514 }
515 
516 
517 /**
518  * Gets the top variable.
519  */
520 YTab*
521 BALMLayout::Top() const
522 {
523 	return fTop;
524 }
525 
526 
527 /**
528  * Gets the bottom variable.
529  */
530 YTab*
531 BALMLayout::Bottom() const
532 {
533 	return fBottom;
534 }
535 
536 
537 /**
538  * Gets minimum size.
539  */
540 BSize
541 BALMLayout::BaseMinSize() {
542 	if (fMinSize == kUnsetSize)
543 		fMinSize = _CalculateMinSize();
544 	return fMinSize;
545 }
546 
547 
548 /**
549  * Gets maximum size.
550  */
551 BSize
552 BALMLayout::BaseMaxSize()
553 {
554 	if (fMaxSize == kUnsetSize)
555 		fMaxSize = _CalculateMaxSize();
556 	return fMaxSize;
557 }
558 
559 
560 /**
561  * Gets preferred size.
562  */
563 BSize
564 BALMLayout::BasePreferredSize()
565 {
566 	if (fPreferredSize == kUnsetSize)
567 		fPreferredSize = _CalculatePreferredSize();
568 	return fPreferredSize;
569 }
570 
571 
572 /**
573  * Gets the alignment.
574  */
575 BAlignment
576 BALMLayout::BaseAlignment()
577 {
578 	BAlignment alignment;
579 	alignment.SetHorizontal(B_ALIGN_HORIZONTAL_CENTER);
580 	alignment.SetVertical(B_ALIGN_VERTICAL_CENTER);
581 	return alignment;
582 }
583 
584 
585 /**
586  * Invalidates the layout.
587  * Resets minimum/maximum/preferred size.
588  */
589 void
590 BALMLayout::InvalidateLayout(bool children)
591 {
592 	BLayout::InvalidateLayout(children);
593 	fMinSize = kUnsetSize;
594 	fMaxSize = kUnsetSize;
595 	fPreferredSize = kUnsetSize;
596 }
597 
598 
599 bool
600 BALMLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
601 {
602 	item->SetLayoutData(new(std::nothrow) Area(item));
603 	return item->LayoutData() != NULL;
604 }
605 
606 
607 void
608 BALMLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
609 {
610 	if (Area* area = AreaFor(item)) {
611 		item->SetLayoutData(NULL);
612 		delete area;
613 	}
614 }
615 
616 
617 /**
618  * Calculate and set the layout.
619  * If no layout specification is given, a specification is reverse engineered automatically.
620  */
621 void
622 BALMLayout::DerivedLayoutItems()
623 {
624 	_UpdateAreaConstraints();
625 
626 	// Enforced absolute positions of Right and Bottom
627 	BRect area(LayoutArea());
628 	Right()->SetRange(area.right, area.right);
629 	Bottom()->SetRange(area.bottom, area.bottom);
630 
631 	_SolveLayout();
632 
633 	// if new layout is infasible, use previous layout
634 	if (fSolver.Result() == INFEASIBLE)
635 		return;
636 
637 	if (fSolver.Result() != OPTIMAL) {
638 		fSolver.Save("failed-layout.txt");
639 		printf("Could not solve the layout specification (%d). ",
640 			fSolver.Result());
641 		printf("Saved specification in file failed-layout.txt\n");
642 	}
643 
644 	// set the calculated positions and sizes for every area
645 	for (int32 i = 0; i < CountItems(); i++)
646 		AreaFor(ItemAt(i))->_DoLayout();
647 }
648 
649 
650 /**
651  * Gets the path of the performance log file.
652  *
653  * @return the path of the performance log file
654  */
655 char*
656 BALMLayout::PerformancePath() const
657 {
658 	return fPerformancePath;
659 }
660 
661 
662 /**
663  * Sets the path of the performance log file.
664  *
665  * @param path	the path of the performance log file
666  */
667 void
668 BALMLayout::SetPerformancePath(char* path)
669 {
670 	fPerformancePath = path;
671 }
672 
673 
674 LinearSpec*
675 BALMLayout::Solver() const
676 {
677 	return const_cast<LinearSpec*>(&fSolver);
678 }
679 
680 
681 void
682 BALMLayout::SetInset(float inset)
683 {
684 	fInset = inset;
685 }
686 
687 
688 float
689 BALMLayout::Inset() const
690 {
691 	return fInset;
692 }
693 
694 
695 void
696 BALMLayout::SetSpacing(float spacing)
697 {
698 	fSpacing = spacing;
699 }
700 
701 
702 float
703 BALMLayout::Spacing() const
704 {
705 	return fSpacing;
706 }
707 
708 
709 BLayoutItem*
710 BALMLayout::_CreateLayoutItem(BView* view)
711 {
712 	return new(std::nothrow) BViewLayoutItem(view);
713 }
714 
715 
716 void
717 BALMLayout::_SolveLayout()
718 {
719 	// Try to solve the layout until the result is OPTIMAL or INFEASIBLE,
720 	// maximally 15 tries sometimes the solving algorithm encounters numerical
721 	// problems (NUMFAILURE), and repeating the solving often helps to overcome
722 	// them.
723 	BFile* file = NULL;
724 	if (fPerformancePath != NULL) {
725 		file = new BFile(fPerformancePath,
726 			B_READ_WRITE | B_CREATE_FILE | B_OPEN_AT_END);
727 	}
728 
729 	ResultType result;
730 	for (int32 tries = 0; tries < 15; tries++) {
731 		result = fSolver.Solve();
732 		if (fPerformancePath != NULL) {
733 			/*char buffer [100];
734 			file->Write(buffer, sprintf(buffer, "%d\t%fms\t#vars=%ld\t"
735 				"#constraints=%ld\n", result, fSolver.SolvingTime(),
736 				fSolver.Variables()->CountItems(),
737 				fSolver.Constraints()->CountItems()));*/
738 		}
739 		if (result == OPTIMAL || result == INFEASIBLE)
740 			break;
741 	}
742 	delete file;
743 }
744 
745 
746 /**
747  * Caculates the miminum size.
748  */
749 BSize
750 BALMLayout::_CalculateMinSize()
751 {
752 	_UpdateAreaConstraints();
753 
754 	SummandList* oldObjFunction = fSolver.ObjFunction();
755 	SummandList* newObjFunction = new SummandList(2);
756 	newObjFunction->AddItem(new Summand(1.0, fRight));
757 	newObjFunction->AddItem(new Summand(1.0, fBottom));
758 	fSolver.SetObjFunction(newObjFunction);
759 	_SolveLayout();
760 	fSolver.SetObjFunction(oldObjFunction);
761 	fSolver.UpdateObjFunction();
762 	delete newObjFunction->ItemAt(0);
763 	delete newObjFunction->ItemAt(1);
764 	delete newObjFunction;
765 
766 	if (fSolver.Result() == UNBOUNDED)
767 		return kMinSize;
768 	if (fSolver.Result() != OPTIMAL) {
769 		fSolver.Save("failed-layout.txt");
770 		printf("Could not solve the layout specification (%d). "
771 			"Saved specification in file failed-layout.txt", fSolver.Result());
772 	}
773 
774 	return BSize(Right()->Value() - Left()->Value(),
775 		Bottom()->Value() - Top()->Value());
776 }
777 
778 
779 /**
780  * Caculates the maximum size.
781  */
782 BSize
783 BALMLayout::_CalculateMaxSize()
784 {
785 	_UpdateAreaConstraints();
786 
787 	SummandList* oldObjFunction = fSolver.ObjFunction();
788 	SummandList* newObjFunction = new SummandList(2);
789 	newObjFunction->AddItem(new Summand(-1.0, fRight));
790 	newObjFunction->AddItem(new Summand(-1.0, fBottom));
791 	fSolver.SetObjFunction(newObjFunction);
792 	_SolveLayout();
793 	fSolver.SetObjFunction(oldObjFunction);
794 	fSolver.UpdateObjFunction();
795 	delete newObjFunction->ItemAt(0);
796 	delete newObjFunction->ItemAt(1);
797 	delete newObjFunction;
798 
799 	if (fSolver.Result() == UNBOUNDED)
800 		return kMaxSize;
801 	if (fSolver.Result() != OPTIMAL) {
802 		fSolver.Save("failed-layout.txt");
803 		printf("Could not solve the layout specification (%d). "
804 			"Saved specification in file failed-layout.txt", fSolver.Result());
805 	}
806 
807 	return BSize(Right()->Value() - Left()->Value(),
808 		Bottom()->Value() - Top()->Value());
809 }
810 
811 
812 /**
813  * Caculates the preferred size.
814  */
815 BSize
816 BALMLayout::_CalculatePreferredSize()
817 {
818 	_UpdateAreaConstraints();
819 
820 	_SolveLayout();
821 	if (fSolver.Result() != OPTIMAL) {
822 		fSolver.Save("failed-layout.txt");
823 		printf("Could not solve the layout specification (%d). "
824 			"Saved specification in file failed-layout.txt", fSolver.Result());
825 	}
826 
827 	return BSize(Right()->Value() - Left()->Value(),
828 		Bottom()->Value() - Top()->Value());
829 }
830 
831 
832 void
833 BALMLayout::_UpdateAreaConstraints()
834 {
835 	for (int i = 0; i < CountItems(); i++)
836 		AreaFor(ItemAt(i))->InvalidateSizeConstraints();
837 }
838