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