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