xref: /haiku/src/kits/interface/Layout.cpp (revision e0ef64750f3169cd634bb2f7a001e22488b05231)
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 	int itemCount = fItems.CountItems();
255 	for (int32 i = 0; i < itemCount; i++) {
256 		BLayoutItem* item = (BLayoutItem*)fItems.ItemAt(i);
257 		if (dynamic_cast<BViewLayoutItem*>(item) && item->View() == child)
258 			return i;
259 	}
260 
261 	return -1;
262 }
263 
264 
265 bool
266 BLayout::AncestorsVisible()
267 {
268 	return fAncestorsVisible;
269 }
270 
271 
272 void
273 BLayout::InvalidateLayout(bool children)
274 {
275 	// printf("BLayout(%p)::InvalidateLayout(%i) : state %x, disabled %li\n",
276 	// this, children, (unsigned int)fState, fInvalidationDisabled);
277 
278 	if (!InvalidationLegal())
279 		return;
280 
281 	fState |= B_LAYOUT_NECESSARY;
282 
283 	if (children) {
284 		for (int32 i = CountItems() - 1; i >= 0; i--)
285 			ItemAt(i)->InvalidateLayout(children);
286 	}
287 
288 	if (fOwner && BView::Private(fOwner).MinMaxValid())
289 		fOwner->InvalidateLayout(children);
290 
291 	if (BLayout* nestedIn = Layout()) {
292 		if (nestedIn->InvalidationLegal())
293 			nestedIn->InvalidateLayout();
294 	} else if (fOwner) {
295 		// If we weren't added as a BLayoutItem, we still have to invalidate
296 		// whatever layout our owner is in.
297 		BView* ownerParent = fOwner->fParent;
298 		if (ownerParent) {
299 			BLayout* layout = ownerParent->GetLayout();
300 			if (layout && layout->fNestedLayouts.CountItems() > 0)
301 				layout->InvalidateLayoutsForView(fOwner);
302 			else if (BView::Private(ownerParent).MinMaxValid())
303 				ownerParent->InvalidateLayout(false);
304 		}
305 	}
306 }
307 
308 
309 void
310 BLayout::RequireLayout()
311 {
312 	fState |= B_LAYOUT_REQUIRED;
313 }
314 
315 
316 bool
317 BLayout::IsValid()
318 {
319 	return (fState & B_LAYOUT_INVALID) == 0;
320 }
321 
322 
323 void
324 BLayout::DisableLayoutInvalidation()
325 {
326 	fInvalidationDisabled++;
327 }
328 
329 
330 void
331 BLayout::EnableLayoutInvalidation()
332 {
333 	if (fInvalidationDisabled > 0)
334 		fInvalidationDisabled--;
335 }
336 
337 
338 void
339 BLayout::LayoutItems(bool force)
340 {
341 	if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
342 		return;
343 
344 	if (Layout() && (Layout()->fState & B_LAYOUT_IN_PROGRESS) != 0)
345 		return; // wait for parent layout to lay us out.
346 
347 	if (fTarget && fTarget->LayoutContext())
348 		return;
349 
350 	BLayoutContext context;
351 	_LayoutWithinContext(force, &context);
352 }
353 
354 
355 void
356 BLayout::Relayout(bool immediate)
357 {
358 	if ((fState & B_RELAYOUT_NOT_OK) == 0 || immediate) {
359 		fState |= B_LAYOUT_REQUIRED;
360 		LayoutItems(false);
361 	}
362 }
363 
364 
365 
366 void
367 BLayout::_LayoutWithinContext(bool force, BLayoutContext* context)
368 {
369 // printf("BLayout(%p)::_LayoutWithinContext(%i, %p), state %x, fContext %p\n",
370 // this, force, context, (unsigned int)fState, fContext);
371 
372 	if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
373 		return;
374 
375 	BLayoutContext* oldContext = fContext;
376 	fContext = context;
377 
378 	if (fOwner && BView::Private(fOwner).WillLayout()) {
379 		// in this case, let our owner decide whether or not to have us
380 		// do our layout, if they do, we won't end up here again.
381 		fOwner->_Layout(force, context);
382 	} else {
383 		fState |= B_LAYOUT_IN_PROGRESS;
384 		DerivedLayoutItems();
385 		// we must ensure that all items are laid out, layouts with a view will
386 		// have their layout process triggered by their view, but nested
387 		// view-less layouts must have their layout triggered here (if it hasn't
388 		// already been triggered).
389 		int32 nestedLayoutCount = fNestedLayouts.CountItems();
390 		for (int32 i = 0; i < nestedLayoutCount; i++) {
391 			BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i);
392 			if ((layout->fState & B_LAYOUT_NECESSARY) != 0)
393 				layout->_LayoutWithinContext(force, context);
394 		}
395 		fState = B_LAYOUT_ALL_CLEAR;
396 	}
397 
398 	fContext = oldContext;
399 }
400 
401 
402 BRect
403 BLayout::LayoutArea()
404 {
405 	BRect area(Frame());
406 	if (fOwner)
407 		area.OffsetTo(B_ORIGIN);
408 	return area;
409 }
410 
411 
412 status_t
413 BLayout::Archive(BMessage* into, bool deep) const
414 {
415 	BArchiver archiver(into);
416 	status_t err = BLayoutItem::Archive(into, deep);
417 
418 	if (deep) {
419 		int32 count = CountItems();
420 		for (int32 i = 0; i < count && err == B_OK; i++) {
421 			BLayoutItem* item = ItemAt(i);
422 			err = archiver.AddArchivable(kLayoutItemField, item, deep);
423 
424 			if (err == B_OK) {
425 				err = ItemArchived(into, item, i);
426 				if (err != B_OK)
427 					syslog(LOG_ERR, "ItemArchived() failed at index: %d.", i);
428 			}
429 		}
430 	}
431 
432 	return archiver.Finish(err);
433 }
434 
435 
436 status_t
437 BLayout::AllUnarchived(const BMessage* from)
438 {
439 	BUnarchiver unarchiver(from);
440 	status_t err = BLayoutItem::AllUnarchived(from);
441 	if (err != B_OK)
442 		return err;
443 
444 	int32 itemCount;
445 	unarchiver.ArchiveMessage()->GetInfo(kLayoutItemField, NULL, &itemCount);
446 	for (int32 i = 0; i < itemCount && err == B_OK; i++) {
447 		BLayoutItem* item;
448 		err = unarchiver.FindObject(kLayoutItemField,
449 			i, BUnarchiver::B_DONT_ASSUME_OWNERSHIP, item);
450 		if (err != B_OK)
451 			return err;
452 
453 		if (!fItems.AddItem(item, i) || !ItemAdded(item, i)) {
454 			fItems.RemoveItem(i);
455 			return B_ERROR;
456 		}
457 
458 		err = ItemUnarchived(from, item, i);
459 		if (err != B_OK) {
460 			fItems.RemoveItem(i);
461 			ItemRemoved(item, i);
462 			return err;
463 		}
464 
465 		item->SetLayout(this);
466 		unarchiver.AssumeOwnership(item);
467 	}
468 
469 	InvalidateLayout();
470 	return err;
471 }
472 
473 
474 status_t
475 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
476 {
477 	return B_OK;
478 }
479 
480 
481 status_t
482 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index)
483 {
484 	return B_OK;
485 }
486 
487 
488 bool
489 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
490 {
491 	return true;
492 }
493 
494 
495 void
496 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
497 {
498 }
499 
500 
501 void
502 BLayout::OwnerChanged(BView* was)
503 {
504 }
505 
506 
507 void
508 BLayout::AttachedToLayout()
509 {
510 	if (!fOwner) {
511 		Layout()->fNestedLayouts.AddItem(this);
512 		SetTarget(Layout()->TargetView());
513 	}
514 }
515 
516 
517 void
518 BLayout::DetachedFromLayout(BLayout* from)
519 {
520 	if (!fOwner) {
521 		from->fNestedLayouts.RemoveItem(this);
522 		SetTarget(NULL);
523 	}
524 }
525 
526 
527 void
528 BLayout::AncestorVisibilityChanged(bool shown)
529 {
530 	if (fAncestorsVisible == shown)
531 		return;
532 
533 	fAncestorsVisible = shown;
534 	VisibilityChanged(shown);
535 }
536 
537 
538 void
539 BLayout::VisibilityChanged(bool show)
540 {
541 	if (fOwner)
542 		return;
543 
544 	for (int32 i = CountItems() - 1; i >= 0; i--)
545 		ItemAt(i)->AncestorVisibilityChanged(show);
546 }
547 
548 
549 void
550 BLayout::ResetLayoutInvalidation()
551 {
552 	fState &= ~B_LAYOUT_CACHE_INVALID;
553 }
554 
555 
556 BLayoutContext*
557 BLayout::LayoutContext()
558 {
559 	return fContext;
560 }
561 
562 
563 bool
564 BLayout::RemoveViewRecursive(BView* view)
565 {
566 	bool removed = RemoveView(view);
567 	for (int32 i = fNestedLayouts.CountItems() - 1; i >= 0; i--) {
568 		BLayout* nested = (BLayout*)fNestedLayouts.ItemAt(i);
569 		removed |= nested->RemoveViewRecursive(view);
570 	}
571 	return removed;
572 }
573 
574 
575 bool
576 BLayout::InvalidateLayoutsForView(BView* view)
577 {
578 	bool found = false;
579 	for (int32 i = fNestedLayouts.CountItems() - 1; i >= 0; i--) {
580 		BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i);
581 		found |= layout->InvalidateLayoutsForView(view);
582 	}
583 
584 	if (found)
585 		return found;
586 
587 	if (!InvalidationLegal())
588 		return false;
589 
590 	for (int32 i = CountItems() - 1; i >= 0; i--) {
591 		if (ItemAt(i)->View() == view) {
592 			InvalidateLayout();
593 			return true;
594 		}
595 	}
596 	return found;
597 }
598 
599 
600 bool
601 BLayout::InvalidationLegal()
602 {
603 	return fInvalidationDisabled <= 0
604 		&& (fState & B_LAYOUT_INVALIDATION_ILLEGAL) == 0;
605 }
606 
607 
608 void
609 BLayout::SetOwner(BView* owner)
610 {
611 	if (fOwner == owner)
612 		return;
613 
614 	SetTarget(owner);
615 	swap(fOwner, owner);
616 
617 	OwnerChanged(owner);
618 		// call hook
619 }
620 
621 
622 void
623 BLayout::SetTarget(BView* target)
624 {
625 	if (fTarget != target) {
626 		fTarget = NULL;
627 			// only remove items, not views
628 
629 		// remove and delete all items
630 		for (int32 i = CountItems() - 1; i >= 0; i--)
631 			delete RemoveItem(i);
632 
633 		fTarget = target;
634 
635 		InvalidateLayout();
636 	}
637 }
638