xref: /haiku/src/kits/interface/Layout.cpp (revision 0d452c8f34013b611a54c746a71c05e28796eae2)
1 /*
2  * Copyright 2010, Haiku Inc.
3  * Copyright 2006, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
4  * All rights reserved. Distributed under the terms of the MIT License.
5  */
6 
7 
8 #include <Layout.h>
9 
10 #include <algorithm>
11 #include <new>
12 #include <syslog.h>
13 
14 #include <LayoutContext.h>
15 #include <Message.h>
16 #include <View.h>
17 #include <ViewPrivate.h>
18 
19 #include "ViewLayoutItem.h"
20 
21 
22 using std::nothrow;
23 using std::swap;
24 
25 
26 namespace {
27 	// flags for our state
28 	const uint32 B_LAYOUT_INVALID = 0x80000000UL; // needs layout
29 	const uint32 B_LAYOUT_CACHE_INVALID = 0x40000000UL; // needs recalculation
30 	const uint32 B_LAYOUT_REQUIRED = 0x20000000UL; // needs layout
31 	const uint32 B_LAYOUT_IN_PROGRESS = 0x10000000UL;
32 	const uint32 B_LAYOUT_ALL_CLEAR = 0UL;
33 
34 	// handy masks to check various states
35 	const uint32 B_LAYOUT_INVALIDATION_ILLEGAL
36 		= B_LAYOUT_CACHE_INVALID | B_LAYOUT_IN_PROGRESS;
37 	const uint32 B_LAYOUT_NECESSARY
38 		= B_LAYOUT_INVALID | B_LAYOUT_REQUIRED | B_LAYOUT_CACHE_INVALID;
39 	const uint32 B_RELAYOUT_NOT_OK
40 		= B_LAYOUT_INVALID | B_LAYOUT_IN_PROGRESS;
41 
42 	const char* const kLayoutItemField = "BLayout:items";
43 }
44 
45 
46 BLayout::BLayout()
47 	:
48 	fState(B_LAYOUT_ALL_CLEAR),
49 	fAncestorsVisible(true),
50 	fInvalidationDisabled(0),
51 	fContext(NULL),
52 	fOwner(NULL),
53 	fTarget(NULL),
54 	fItems(20)
55 {
56 }
57 
58 
59 BLayout::BLayout(BMessage* from)
60 	:
61 	BLayoutItem(BUnarchiver::PrepareArchive(from)),
62 	fState(B_LAYOUT_ALL_CLEAR),
63 	fAncestorsVisible(true),
64 	fInvalidationDisabled(0),
65 	fContext(NULL),
66 	fOwner(NULL),
67 	fTarget(NULL),
68 	fItems(20)
69 {
70 	BUnarchiver unarchiver(from);
71 
72 	int32 i = 0;
73 	while (unarchiver.EnsureUnarchived(kLayoutItemField, i++) == B_OK)
74 		;
75 }
76 
77 
78 BLayout::~BLayout()
79 {
80 	// in case we have a view, but have been added to a layout as a BLayoutItem
81 	// we will get deleted before our view, so we should tell it that we're
82 	// going, so that we aren't double-freed.
83 	if (fOwner && this == fOwner->GetLayout())
84 		fOwner->_LayoutLeft(this);
85 
86 	// removes and deletes all items
87 	if (fTarget)
88 		SetTarget(NULL);
89 }
90 
91 
92 BView*
93 BLayout::Owner() const
94 {
95 	return fOwner;
96 }
97 
98 
99 BView*
100 BLayout::TargetView() const
101 {
102 	return fTarget;
103 }
104 
105 
106 BView*
107 BLayout::View()
108 {
109 	return fOwner;
110 }
111 
112 
113 BLayoutItem*
114 BLayout::AddView(BView* child)
115 {
116 	return AddView(-1, child);
117 }
118 
119 
120 BLayoutItem*
121 BLayout::AddView(int32 index, BView* child)
122 {
123 	BLayoutItem* item = child->GetLayout();
124 	if (!item)
125 		item = new(nothrow) BViewLayoutItem(child);
126 
127 	if (item && AddItem(index, item))
128 		return item;
129 
130 	if (!child->GetLayout())
131 		delete item;
132 	return NULL;
133 }
134 
135 
136 bool
137 BLayout::AddItem(BLayoutItem* item)
138 {
139 	return AddItem(-1, item);
140 }
141 
142 
143 bool
144 BLayout::AddItem(int32 index, BLayoutItem* item)
145 {
146 	if (!fTarget || !item || fItems.HasItem(item))
147 		return false;
148 
149 	// if the item refers to a BView, we make sure it is added to the parent
150 	// view
151 	bool addedView = false;
152 	BView* view = item->View();
153 	if (view && view->fParent != fTarget
154 		&& !(addedView = fTarget->_AddChild(view, NULL)))
155 		return false;
156 
157 	// validate the index
158 	if (index < 0 || index > fItems.CountItems())
159 		index = fItems.CountItems();
160 
161 	if (fItems.AddItem(item, index) && ItemAdded(item, index)) {
162 		item->SetLayout(this);
163 		if (!fAncestorsVisible)
164 			item->AncestorVisibilityChanged(fAncestorsVisible);
165 		InvalidateLayout();
166 		return true;
167 	} else {
168 		// this check is necessary so that if an addition somewhere other
169 		// than the end of the list fails, we don't remove the wrong item
170 		if (fItems.ItemAt(index) == item)
171 			fItems.RemoveItem(index);
172 		if (addedView)
173 			view->_RemoveSelf();
174 		return false;
175 	}
176 }
177 
178 
179 bool
180 BLayout::RemoveView(BView* child)
181 {
182 	bool removed = false;
183 
184 	// a view can have any number of layout items - we need to remove them all
185 	for (int32 i = fItems.CountItems(); i-- > 0;) {
186 		BLayoutItem* item = ItemAt(i);
187 
188 		if (item->View() != child)
189 			continue;
190 
191 		RemoveItem(i);
192 		removed = true;
193 		delete item;
194 	}
195 
196 	return removed;
197 }
198 
199 
200 bool
201 BLayout::RemoveItem(BLayoutItem* item)
202 {
203 	int32 index = IndexOfItem(item);
204 	return (index >= 0 ? RemoveItem(index) : false);
205 }
206 
207 
208 BLayoutItem*
209 BLayout::RemoveItem(int32 index)
210 {
211 	if (index < 0 || index >= fItems.CountItems())
212 		return NULL;
213 
214 	BLayoutItem* item = (BLayoutItem*)fItems.RemoveItem(index);
215 
216 	// if the item refers to a BView, we make sure, it is removed from the
217 	// parent view
218 	BView* view = item->View();
219 	if (view && view->fParent == fTarget)
220 		view->_RemoveSelf();
221 
222 	ItemRemoved(item, index);
223 	item->SetLayout(NULL);
224 	InvalidateLayout();
225 
226 	return item;
227 }
228 
229 
230 BLayoutItem*
231 BLayout::ItemAt(int32 index) const
232 {
233 	return (BLayoutItem*)fItems.ItemAt(index);
234 }
235 
236 
237 int32
238 BLayout::CountItems() const
239 {
240 	return fItems.CountItems();
241 }
242 
243 
244 int32
245 BLayout::IndexOfItem(const BLayoutItem* item) const
246 {
247 	return fItems.IndexOf(item);
248 }
249 
250 
251 int32
252 BLayout::IndexOfView(BView* child) const
253 {
254 	if (child == NULL)
255 		return -1;
256 
257 	int itemCount = fItems.CountItems();
258 	for (int32 i = 0; i < itemCount; i++) {
259 		BLayoutItem* item = (BLayoutItem*)fItems.ItemAt(i);
260 		if (item->View() == child && (dynamic_cast<BViewLayoutItem*>(item)
261 				|| dynamic_cast<BLayout*>(item)))
262 			return i;
263 	}
264 
265 	return -1;
266 }
267 
268 
269 bool
270 BLayout::AncestorsVisible() const
271 {
272 	return fAncestorsVisible;
273 }
274 
275 
276 void
277 BLayout::InvalidateLayout(bool children)
278 {
279 	// printf("BLayout(%p)::InvalidateLayout(%i) : state %x, disabled %li\n",
280 	// this, children, (unsigned int)fState, fInvalidationDisabled);
281 
282 	if (!InvalidationLegal())
283 		return;
284 
285 	fState |= B_LAYOUT_NECESSARY;
286 
287 	if (children) {
288 		for (int32 i = CountItems() - 1; i >= 0; i--)
289 			ItemAt(i)->InvalidateLayout(children);
290 	}
291 
292 	if (fOwner && BView::Private(fOwner).MinMaxValid())
293 		fOwner->InvalidateLayout(children);
294 
295 	if (BLayout* nestedIn = Layout()) {
296 		if (nestedIn->InvalidationLegal())
297 			nestedIn->InvalidateLayout();
298 	} else if (fOwner) {
299 		// If we weren't added as a BLayoutItem, we still have to invalidate
300 		// whatever layout our owner is in.
301 		BView* ownerParent = fOwner->fParent;
302 		if (ownerParent) {
303 			BLayout* layout = ownerParent->GetLayout();
304 			if (layout && layout->fNestedLayouts.CountItems() > 0)
305 				layout->InvalidateLayoutsForView(fOwner);
306 			else if (BView::Private(ownerParent).MinMaxValid())
307 				ownerParent->InvalidateLayout(false);
308 		}
309 	}
310 }
311 
312 
313 void
314 BLayout::RequireLayout()
315 {
316 	fState |= B_LAYOUT_REQUIRED;
317 }
318 
319 
320 bool
321 BLayout::IsValid()
322 {
323 	return (fState & B_LAYOUT_INVALID) == 0;
324 }
325 
326 
327 void
328 BLayout::DisableLayoutInvalidation()
329 {
330 	fInvalidationDisabled++;
331 }
332 
333 
334 void
335 BLayout::EnableLayoutInvalidation()
336 {
337 	if (fInvalidationDisabled > 0)
338 		fInvalidationDisabled--;
339 }
340 
341 
342 void
343 BLayout::LayoutItems(bool force)
344 {
345 	if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
346 		return;
347 
348 	if (Layout() && (Layout()->fState & B_LAYOUT_IN_PROGRESS) != 0)
349 		return; // wait for parent layout to lay us out.
350 
351 	if (fTarget && fTarget->LayoutContext())
352 		return;
353 
354 	BLayoutContext context;
355 	_LayoutWithinContext(force, &context);
356 }
357 
358 
359 void
360 BLayout::Relayout(bool immediate)
361 {
362 	if ((fState & B_RELAYOUT_NOT_OK) == 0 || immediate) {
363 		fState |= B_LAYOUT_REQUIRED;
364 		LayoutItems(false);
365 	}
366 }
367 
368 
369 
370 void
371 BLayout::_LayoutWithinContext(bool force, BLayoutContext* context)
372 {
373 // printf("BLayout(%p)::_LayoutWithinContext(%i, %p), state %x, fContext %p\n",
374 // this, force, context, (unsigned int)fState, fContext);
375 
376 	if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
377 		return;
378 
379 	BLayoutContext* oldContext = fContext;
380 	fContext = context;
381 
382 	if (fOwner && BView::Private(fOwner).WillLayout()) {
383 		// in this case, let our owner decide whether or not to have us
384 		// do our layout, if they do, we won't end up here again.
385 		fOwner->_Layout(force, context);
386 	} else {
387 		fState |= B_LAYOUT_IN_PROGRESS;
388 		DerivedLayoutItems();
389 		// we must ensure that all items are laid out, layouts with a view will
390 		// have their layout process triggered by their view, but nested
391 		// view-less layouts must have their layout triggered here (if it hasn't
392 		// already been triggered).
393 		int32 nestedLayoutCount = fNestedLayouts.CountItems();
394 		for (int32 i = 0; i < nestedLayoutCount; i++) {
395 			BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i);
396 			if ((layout->fState & B_LAYOUT_NECESSARY) != 0)
397 				layout->_LayoutWithinContext(force, context);
398 		}
399 		fState = B_LAYOUT_ALL_CLEAR;
400 	}
401 
402 	fContext = oldContext;
403 }
404 
405 
406 BRect
407 BLayout::LayoutArea()
408 {
409 	BRect area(Frame());
410 	if (fOwner)
411 		area.OffsetTo(B_ORIGIN);
412 	return area;
413 }
414 
415 
416 status_t
417 BLayout::Archive(BMessage* into, bool deep) const
418 {
419 	BArchiver archiver(into);
420 	status_t err = BLayoutItem::Archive(into, deep);
421 
422 	if (deep) {
423 		int32 count = CountItems();
424 		for (int32 i = 0; i < count && err == B_OK; i++) {
425 			BLayoutItem* item = ItemAt(i);
426 			err = archiver.AddArchivable(kLayoutItemField, item, deep);
427 
428 			if (err == B_OK) {
429 				err = ItemArchived(into, item, i);
430 				if (err != B_OK)
431 					syslog(LOG_ERR, "ItemArchived() failed at index: %d.", i);
432 			}
433 		}
434 	}
435 
436 	return archiver.Finish(err);
437 }
438 
439 
440 status_t
441 BLayout::AllUnarchived(const BMessage* from)
442 {
443 	BUnarchiver unarchiver(from);
444 	status_t err = BLayoutItem::AllUnarchived(from);
445 	if (err != B_OK)
446 		return err;
447 
448 	int32 itemCount;
449 	unarchiver.ArchiveMessage()->GetInfo(kLayoutItemField, NULL, &itemCount);
450 	for (int32 i = 0; i < itemCount && err == B_OK; i++) {
451 		BLayoutItem* item;
452 		err = unarchiver.FindObject(kLayoutItemField,
453 			i, BUnarchiver::B_DONT_ASSUME_OWNERSHIP, item);
454 		if (err != B_OK)
455 			return err;
456 
457 		if (!fItems.AddItem(item, i) || !ItemAdded(item, i)) {
458 			fItems.RemoveItem(i);
459 			return B_ERROR;
460 		}
461 
462 		err = ItemUnarchived(from, item, i);
463 		if (err != B_OK) {
464 			fItems.RemoveItem(i);
465 			ItemRemoved(item, i);
466 			return err;
467 		}
468 
469 		item->SetLayout(this);
470 		unarchiver.AssumeOwnership(item);
471 	}
472 
473 	InvalidateLayout();
474 	return err;
475 }
476 
477 
478 status_t
479 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
480 {
481 	return B_OK;
482 }
483 
484 
485 status_t
486 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index)
487 {
488 	return B_OK;
489 }
490 
491 
492 bool
493 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
494 {
495 	return true;
496 }
497 
498 
499 void
500 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
501 {
502 }
503 
504 
505 void
506 BLayout::OwnerChanged(BView* was)
507 {
508 }
509 
510 
511 void
512 BLayout::AttachedToLayout()
513 {
514 	if (!fOwner) {
515 		Layout()->fNestedLayouts.AddItem(this);
516 		SetTarget(Layout()->TargetView());
517 	}
518 }
519 
520 
521 void
522 BLayout::DetachedFromLayout(BLayout* from)
523 {
524 	if (!fOwner) {
525 		from->fNestedLayouts.RemoveItem(this);
526 		SetTarget(NULL);
527 	}
528 }
529 
530 
531 void
532 BLayout::AncestorVisibilityChanged(bool shown)
533 {
534 	if (fAncestorsVisible == shown)
535 		return;
536 
537 	fAncestorsVisible = shown;
538 	VisibilityChanged(shown);
539 }
540 
541 
542 void
543 BLayout::VisibilityChanged(bool show)
544 {
545 	if (fOwner)
546 		return;
547 
548 	for (int32 i = CountItems() - 1; i >= 0; i--)
549 		ItemAt(i)->AncestorVisibilityChanged(show);
550 }
551 
552 
553 void
554 BLayout::ResetLayoutInvalidation()
555 {
556 	fState &= ~B_LAYOUT_CACHE_INVALID;
557 }
558 
559 
560 BLayoutContext*
561 BLayout::LayoutContext() const
562 {
563 	return fContext;
564 }
565 
566 
567 bool
568 BLayout::RemoveViewRecursive(BView* view)
569 {
570 	bool removed = RemoveView(view);
571 	for (int32 i = fNestedLayouts.CountItems() - 1; i >= 0; i--) {
572 		BLayout* nested = (BLayout*)fNestedLayouts.ItemAt(i);
573 		removed |= nested->RemoveViewRecursive(view);
574 	}
575 	return removed;
576 }
577 
578 
579 bool
580 BLayout::InvalidateLayoutsForView(BView* view)
581 {
582 	bool found = false;
583 	for (int32 i = fNestedLayouts.CountItems() - 1; i >= 0; i--) {
584 		BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i);
585 		found |= layout->InvalidateLayoutsForView(view);
586 	}
587 
588 	if (found)
589 		return found;
590 
591 	if (!InvalidationLegal())
592 		return false;
593 
594 	for (int32 i = CountItems() - 1; i >= 0; i--) {
595 		if (ItemAt(i)->View() == view) {
596 			InvalidateLayout();
597 			return true;
598 		}
599 	}
600 	return found;
601 }
602 
603 
604 bool
605 BLayout::InvalidationLegal()
606 {
607 	return fInvalidationDisabled <= 0
608 		&& (fState & B_LAYOUT_INVALIDATION_ILLEGAL) == 0;
609 }
610 
611 
612 void
613 BLayout::SetOwner(BView* owner)
614 {
615 	if (fOwner == owner)
616 		return;
617 
618 	SetTarget(owner);
619 	swap(fOwner, owner);
620 
621 	OwnerChanged(owner);
622 		// call hook
623 }
624 
625 
626 void
627 BLayout::SetTarget(BView* target)
628 {
629 	if (fTarget != target) {
630 		fTarget = NULL;
631 			// only remove items, not views
632 
633 		// remove and delete all items
634 		for (int32 i = CountItems() - 1; i >= 0; i--)
635 			delete RemoveItem(i);
636 
637 		fTarget = target;
638 
639 		InvalidateLayout();
640 	}
641 }
642