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