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	// removes and deletes all items
98	if (fTarget)
99		SetTarget(NULL);
100}
101
102
103BView*
104BLayout::Owner() const
105{
106	return fOwner;
107}
108
109
110BView*
111BLayout::TargetView() const
112{
113	return fTarget;
114}
115
116
117BView*
118BLayout::View()
119{
120	return fOwner;
121}
122
123
124BLayoutItem*
125BLayout::AddView(BView* child)
126{
127	return AddView(-1, child);
128}
129
130
131BLayoutItem*
132BLayout::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
150bool
151BLayout::AddItem(BLayoutItem* item)
152{
153	return AddItem(-1, item);
154}
155
156
157bool
158BLayout::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
197bool
198BLayout::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
221bool
222BLayout::RemoveItem(BLayoutItem* item)
223{
224	int32 index = IndexOfItem(item);
225	return (index >= 0 ? RemoveItem(index) != NULL : false);
226}
227
228
229BLayoutItem*
230BLayout::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
251BLayoutItem*
252BLayout::ItemAt(int32 index) const
253{
254	return (BLayoutItem*)fItems.ItemAt(index);
255}
256
257
258int32
259BLayout::CountItems() const
260{
261	return fItems.CountItems();
262}
263
264
265int32
266BLayout::IndexOfItem(const BLayoutItem* item) const
267{
268	return fItems.IndexOf(item);
269}
270
271
272int32
273BLayout::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
291bool
292BLayout::AncestorsVisible() const
293{
294	return fAncestorsVisible;
295}
296
297
298void
299BLayout::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 (fTarget && fTarget->IsLayoutInvalidationDisabled())
305		return;
306	if (fInvalidationDisabled > 0
307		|| (fState & B_LAYOUT_INVALIDATION_ILLEGAL) != 0) {
308		return;
309	}
310
311	fState |= B_LAYOUT_NECESSARY;
312	LayoutInvalidated(children);
313
314	if (children) {
315		for (int32 i = CountItems() - 1; i >= 0; i--)
316			ItemAt(i)->InvalidateLayout(children);
317	}
318
319	if (fOwner)
320		fOwner->InvalidateLayout(children);
321
322	if (BLayout* nestedIn = Layout()) {
323		nestedIn->InvalidateLayout();
324	} else if (fOwner) {
325		// If we weren't added as a BLayoutItem, we still have to invalidate
326		// whatever layout our owner is in.
327		fOwner->_InvalidateParentLayout();
328	}
329}
330
331
332void
333BLayout::RequireLayout()
334{
335	fState |= B_LAYOUT_REQUIRED;
336}
337
338
339bool
340BLayout::IsValid()
341{
342	return (fState & B_LAYOUT_INVALID) == 0;
343}
344
345
346void
347BLayout::DisableLayoutInvalidation()
348{
349	fInvalidationDisabled++;
350}
351
352
353void
354BLayout::EnableLayoutInvalidation()
355{
356	if (fInvalidationDisabled > 0)
357		fInvalidationDisabled--;
358}
359
360
361void
362BLayout::LayoutItems(bool force)
363{
364	if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
365		return;
366
367	if (Layout() && (Layout()->fState & B_LAYOUT_IN_PROGRESS) != 0)
368		return; // wait for parent layout to lay us out.
369
370	if (fTarget && fTarget->LayoutContext())
371		return;
372
373	BLayoutContext context;
374	_LayoutWithinContext(force, &context);
375}
376
377
378void
379BLayout::Relayout(bool immediate)
380{
381	if ((fState & B_RELAYOUT_NOT_OK) == 0 || immediate) {
382		fState |= B_LAYOUT_REQUIRED;
383		LayoutItems(false);
384	}
385}
386
387
388void
389BLayout::_LayoutWithinContext(bool force, BLayoutContext* context)
390{
391// printf("BLayout(%p)::_LayoutWithinContext(%i, %p), state %x, fContext %p\n",
392// this, force, context, (unsigned int)fState, fContext);
393
394	if ((fState & B_LAYOUT_NECESSARY) == 0 && !force)
395		return;
396
397	BLayoutContext* oldContext = fContext;
398	fContext = context;
399
400	if (fOwner && BView::Private(fOwner).WillLayout()) {
401		// in this case, let our owner decide whether or not to have us
402		// do our layout, if they do, we won't end up here again.
403		fOwner->_Layout(force, context);
404	} else {
405		fState |= B_LAYOUT_IN_PROGRESS;
406		DoLayout();
407		// we must ensure that all items are laid out, layouts with a view will
408		// have their layout process triggered by their view, but nested
409		// view-less layouts must have their layout triggered here (if it hasn't
410		// already been triggered).
411		int32 nestedLayoutCount = fNestedLayouts.CountItems();
412		for (int32 i = 0; i < nestedLayoutCount; i++) {
413			BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i);
414			if ((layout->fState & B_LAYOUT_NECESSARY) != 0)
415				layout->_LayoutWithinContext(force, context);
416		}
417		fState = B_LAYOUT_ALL_CLEAR;
418	}
419
420	fContext = oldContext;
421}
422
423
424BRect
425BLayout::LayoutArea()
426{
427	BRect area(Frame());
428	if (fOwner)
429		area.OffsetTo(B_ORIGIN);
430	return area;
431}
432
433
434status_t
435BLayout::Archive(BMessage* into, bool deep) const
436{
437	BArchiver archiver(into);
438	status_t err = BLayoutItem::Archive(into, deep);
439
440	if (deep) {
441		int32 count = CountItems();
442		for (int32 i = 0; i < count && err == B_OK; i++) {
443			BLayoutItem* item = ItemAt(i);
444			err = archiver.AddArchivable(kLayoutItemField, item, deep);
445
446			if (err == B_OK) {
447				err = ItemArchived(into, item, i);
448				if (err != B_OK)
449					syslog(LOG_ERR, "ItemArchived() failed at index: %d.", i);
450			}
451		}
452	}
453
454	return archiver.Finish(err);
455}
456
457
458status_t
459BLayout::AllArchived(BMessage* archive) const
460{
461	return BLayoutItem::AllArchived(archive);
462}
463
464
465status_t
466BLayout::AllUnarchived(const BMessage* from)
467{
468	BUnarchiver unarchiver(from);
469	status_t err = BLayoutItem::AllUnarchived(from);
470	if (err != B_OK)
471		return err;
472
473	int32 itemCount = 0;
474	unarchiver.ArchiveMessage()->GetInfo(kLayoutItemField, NULL, &itemCount);
475	for (int32 i = 0; i < itemCount && err == B_OK; i++) {
476		BLayoutItem* item;
477		err = unarchiver.FindObject(kLayoutItemField,
478			i, BUnarchiver::B_DONT_ASSUME_OWNERSHIP, item);
479		if (err != B_OK)
480			return err;
481
482		if (!fItems.AddItem(item, i) || !ItemAdded(item, i)) {
483			fItems.RemoveItem(i);
484			return B_ERROR;
485		}
486
487		err = ItemUnarchived(from, item, i);
488		if (err != B_OK) {
489			fItems.RemoveItem(i);
490			ItemRemoved(item, i);
491			return err;
492		}
493
494		item->SetLayout(this);
495		unarchiver.AssumeOwnership(item);
496	}
497
498	InvalidateLayout();
499	return err;
500}
501
502
503status_t
504BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
505{
506	return B_OK;
507}
508
509
510status_t
511BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index)
512{
513	return B_OK;
514}
515
516
517bool
518BLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
519{
520	return true;
521}
522
523
524void
525BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
526{
527}
528
529
530void
531BLayout::LayoutInvalidated(bool children)
532{
533}
534
535
536void
537BLayout::OwnerChanged(BView* was)
538{
539}
540
541
542void
543BLayout::AttachedToLayout()
544{
545	if (!fOwner) {
546		Layout()->fNestedLayouts.AddItem(this);
547		SetTarget(Layout()->TargetView());
548	}
549}
550
551
552void
553BLayout::DetachedFromLayout(BLayout* from)
554{
555	if (!fOwner) {
556		from->fNestedLayouts.RemoveItem(this);
557		SetTarget(NULL);
558	}
559}
560
561
562void
563BLayout::AncestorVisibilityChanged(bool shown)
564{
565	if (fAncestorsVisible == shown)
566		return;
567
568	fAncestorsVisible = shown;
569	VisibilityChanged(shown);
570}
571
572
573void
574BLayout::VisibilityChanged(bool show)
575{
576	if (fOwner)
577		return;
578
579	for (int32 i = CountItems() - 1; i >= 0; i--)
580		ItemAt(i)->AncestorVisibilityChanged(show);
581}
582
583
584void
585BLayout::ResetLayoutInvalidation()
586{
587	fState &= ~B_LAYOUT_CACHE_INVALID;
588}
589
590
591BLayoutContext*
592BLayout::LayoutContext() const
593{
594	return fContext;
595}
596
597
598void
599BLayout::SetOwner(BView* owner)
600{
601	if (fOwner == owner)
602		return;
603
604	SetTarget(owner);
605	swap(fOwner, owner);
606
607	OwnerChanged(owner);
608		// call hook
609}
610
611
612void
613BLayout::SetTarget(BView* target)
614{
615	if (fTarget != target) {
616		/* With fTarget NULL, RemoveItem() will not remove the views from their
617		 * parent. This ensures that the views are not lost to the void.
618		 */
619		fTarget = NULL;
620
621		// remove and delete all items
622		for (int32 i = CountItems() - 1; i >= 0; i--)
623			delete RemoveItem(i);
624
625		fTarget = target;
626
627		InvalidateLayout();
628	}
629}
630
631
632// Binary compatibility stuff
633
634
635status_t
636BLayout::Perform(perform_code code, void* _data)
637{
638	return BLayoutItem::Perform(code, _data);
639}
640
641
642void BLayout::_ReservedLayout1() {}
643void BLayout::_ReservedLayout2() {}
644void BLayout::_ReservedLayout3() {}
645void BLayout::_ReservedLayout4() {}
646void BLayout::_ReservedLayout5() {}
647void BLayout::_ReservedLayout6() {}
648void BLayout::_ReservedLayout7() {}
649void BLayout::_ReservedLayout8() {}
650void BLayout::_ReservedLayout9() {}
651void BLayout::_ReservedLayout10() {}
652
653