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