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