xref: /haiku/src/kits/interface/Layout.cpp (revision 3af608ada59d12d73608a045de66d425eedf7003)
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 (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 
332 void
333 BLayout::RequireLayout()
334 {
335 	fState |= B_LAYOUT_REQUIRED;
336 }
337 
338 
339 bool
340 BLayout::IsValid()
341 {
342 	return (fState & B_LAYOUT_INVALID) == 0;
343 }
344 
345 
346 void
347 BLayout::DisableLayoutInvalidation()
348 {
349 	fInvalidationDisabled++;
350 }
351 
352 
353 void
354 BLayout::EnableLayoutInvalidation()
355 {
356 	if (fInvalidationDisabled > 0)
357 		fInvalidationDisabled--;
358 }
359 
360 
361 void
362 BLayout::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 
378 void
379 BLayout::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 
388 void
389 BLayout::_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 
424 BRect
425 BLayout::LayoutArea()
426 {
427 	BRect area(Frame());
428 	if (fOwner)
429 		area.OffsetTo(B_ORIGIN);
430 	return area;
431 }
432 
433 
434 status_t
435 BLayout::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 
458 status_t
459 BLayout::AllArchived(BMessage* archive) const
460 {
461 	return BLayoutItem::AllArchived(archive);
462 }
463 
464 
465 status_t
466 BLayout::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 
503 status_t
504 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
505 {
506 	return B_OK;
507 }
508 
509 
510 status_t
511 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index)
512 {
513 	return B_OK;
514 }
515 
516 
517 bool
518 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
519 {
520 	return true;
521 }
522 
523 
524 void
525 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
526 {
527 }
528 
529 
530 void
531 BLayout::LayoutInvalidated(bool children)
532 {
533 }
534 
535 
536 void
537 BLayout::OwnerChanged(BView* was)
538 {
539 }
540 
541 
542 void
543 BLayout::AttachedToLayout()
544 {
545 	if (!fOwner) {
546 		Layout()->fNestedLayouts.AddItem(this);
547 		SetTarget(Layout()->TargetView());
548 	}
549 }
550 
551 
552 void
553 BLayout::DetachedFromLayout(BLayout* from)
554 {
555 	if (!fOwner) {
556 		from->fNestedLayouts.RemoveItem(this);
557 		SetTarget(NULL);
558 	}
559 }
560 
561 
562 void
563 BLayout::AncestorVisibilityChanged(bool shown)
564 {
565 	if (fAncestorsVisible == shown)
566 		return;
567 
568 	fAncestorsVisible = shown;
569 	VisibilityChanged(shown);
570 }
571 
572 
573 void
574 BLayout::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 
584 void
585 BLayout::ResetLayoutInvalidation()
586 {
587 	fState &= ~B_LAYOUT_CACHE_INVALID;
588 }
589 
590 
591 BLayoutContext*
592 BLayout::LayoutContext() const
593 {
594 	return fContext;
595 }
596 
597 
598 void
599 BLayout::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 
612 void
613 BLayout::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 
635 status_t
636 BLayout::Perform(perform_code code, void* _data)
637 {
638 	return BLayoutItem::Perform(code, _data);
639 }
640 
641 
642 void BLayout::_ReservedLayout1() {}
643 void BLayout::_ReservedLayout2() {}
644 void BLayout::_ReservedLayout3() {}
645 void BLayout::_ReservedLayout4() {}
646 void BLayout::_ReservedLayout5() {}
647 void BLayout::_ReservedLayout6() {}
648 void BLayout::_ReservedLayout7() {}
649 void BLayout::_ReservedLayout8() {}
650 void BLayout::_ReservedLayout9() {}
651 void BLayout::_ReservedLayout10() {}
652 
653