xref: /haiku/src/kits/interface/Layout.cpp (revision ebea950b2df08711adaca9f2c0d282d48667e545)
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 {
operator ()__anond3ed7a700111::ViewRemover49 		inline void operator()(BView* view) {
50 			if (view)
51 				BView::Private(view).RemoveSelf();
52 		}
53 	};
54 }
55 
56 
BLayout()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 
BLayout(BMessage * from)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 
~BLayout()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 	if (CountItems() > 0) {
98 		debugger("Deleting a BLayout that still has items. Subclass hooks "
99 			"will not be called");
100 	}
101 }
102 
103 
104 BView*
Owner() const105 BLayout::Owner() const
106 {
107 	return fOwner;
108 }
109 
110 
111 BView*
TargetView() const112 BLayout::TargetView() const
113 {
114 	return fTarget;
115 }
116 
117 
118 BView*
View()119 BLayout::View()
120 {
121 	return fOwner;
122 }
123 
124 
125 BLayoutItem*
AddView(BView * child)126 BLayout::AddView(BView* child)
127 {
128 	return AddView(-1, child);
129 }
130 
131 
132 BLayoutItem*
AddView(int32 index,BView * child)133 BLayout::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 
151 bool
AddItem(BLayoutItem * item)152 BLayout::AddItem(BLayoutItem* item)
153 {
154 	return AddItem(-1, item);
155 }
156 
157 
158 bool
AddItem(int32 index,BLayoutItem * item)159 BLayout::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 
198 bool
RemoveView(BView * child)199 BLayout::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 
223 bool
RemoveItem(BLayoutItem * item)224 BLayout::RemoveItem(BLayoutItem* item)
225 {
226 	int32 index = IndexOfItem(item);
227 	return (index >= 0 ? RemoveItem(index) != NULL : false);
228 }
229 
230 
231 BLayoutItem*
RemoveItem(int32 index)232 BLayout::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 
253 BLayoutItem*
ItemAt(int32 index) const254 BLayout::ItemAt(int32 index) const
255 {
256 	return (BLayoutItem*)fItems.ItemAt(index);
257 }
258 
259 
260 int32
CountItems() const261 BLayout::CountItems() const
262 {
263 	return fItems.CountItems();
264 }
265 
266 
267 int32
IndexOfItem(const BLayoutItem * item) const268 BLayout::IndexOfItem(const BLayoutItem* item) const
269 {
270 	return fItems.IndexOf(item);
271 }
272 
273 
274 int32
IndexOfView(BView * child) const275 BLayout::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 
293 bool
AncestorsVisible() const294 BLayout::AncestorsVisible() const
295 {
296 	return fAncestorsVisible;
297 }
298 
299 
300 void
InvalidateLayout(bool children)301 BLayout::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 
334 void
RequireLayout()335 BLayout::RequireLayout()
336 {
337 	fState |= B_LAYOUT_REQUIRED;
338 }
339 
340 
341 bool
IsValid()342 BLayout::IsValid()
343 {
344 	return (fState & B_LAYOUT_INVALID) == 0;
345 }
346 
347 
348 void
DisableLayoutInvalidation()349 BLayout::DisableLayoutInvalidation()
350 {
351 	fInvalidationDisabled++;
352 }
353 
354 
355 void
EnableLayoutInvalidation()356 BLayout::EnableLayoutInvalidation()
357 {
358 	if (fInvalidationDisabled > 0)
359 		fInvalidationDisabled--;
360 }
361 
362 
363 void
LayoutItems(bool force)364 BLayout::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 
380 void
Relayout(bool immediate)381 BLayout::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 
390 void
_LayoutWithinContext(bool force,BLayoutContext * context)391 BLayout::_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 
426 BRect
LayoutArea()427 BLayout::LayoutArea()
428 {
429 	BRect area(Frame());
430 	if (fOwner)
431 		area.OffsetTo(B_ORIGIN);
432 	return area;
433 }
434 
435 
436 status_t
Archive(BMessage * into,bool deep) const437 BLayout::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 
460 status_t
AllArchived(BMessage * archive) const461 BLayout::AllArchived(BMessage* archive) const
462 {
463 	return BLayoutItem::AllArchived(archive);
464 }
465 
466 
467 status_t
AllUnarchived(const BMessage * from)468 BLayout::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 
505 status_t
ItemArchived(BMessage * into,BLayoutItem * item,int32 index) const506 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const
507 {
508 	return B_OK;
509 }
510 
511 
512 status_t
ItemUnarchived(const BMessage * from,BLayoutItem * item,int32 index)513 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index)
514 {
515 	return B_OK;
516 }
517 
518 
519 bool
ItemAdded(BLayoutItem * item,int32 atIndex)520 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex)
521 {
522 	return true;
523 }
524 
525 
526 void
ItemRemoved(BLayoutItem * item,int32 fromIndex)527 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex)
528 {
529 }
530 
531 
532 void
LayoutInvalidated(bool children)533 BLayout::LayoutInvalidated(bool children)
534 {
535 }
536 
537 
538 void
OwnerChanged(BView * was)539 BLayout::OwnerChanged(BView* was)
540 {
541 }
542 
543 
544 void
AttachedToLayout()545 BLayout::AttachedToLayout()
546 {
547 	if (!fOwner) {
548 		Layout()->fNestedLayouts.AddItem(this);
549 		SetTarget(Layout()->TargetView());
550 	}
551 }
552 
553 
554 void
DetachedFromLayout(BLayout * from)555 BLayout::DetachedFromLayout(BLayout* from)
556 {
557 	if (!fOwner) {
558 		from->fNestedLayouts.RemoveItem(this);
559 		SetTarget(NULL);
560 	}
561 }
562 
563 
564 void
AncestorVisibilityChanged(bool shown)565 BLayout::AncestorVisibilityChanged(bool shown)
566 {
567 	if (fAncestorsVisible == shown)
568 		return;
569 
570 	fAncestorsVisible = shown;
571 	VisibilityChanged(shown);
572 }
573 
574 
575 void
VisibilityChanged(bool show)576 BLayout::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 
586 void
ResetLayoutInvalidation()587 BLayout::ResetLayoutInvalidation()
588 {
589 	fState &= ~B_LAYOUT_CACHE_INVALID;
590 }
591 
592 
593 BLayoutContext*
LayoutContext() const594 BLayout::LayoutContext() const
595 {
596 	return fContext;
597 }
598 
599 
600 void
SetOwner(BView * owner)601 BLayout::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 
614 void
SetTarget(BView * target)615 BLayout::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 
637 status_t
Perform(perform_code code,void * _data)638 BLayout::Perform(perform_code code, void* _data)
639 {
640 	return BLayoutItem::Perform(code, _data);
641 }
642 
643 
_ReservedLayout1()644 void BLayout::_ReservedLayout1() {}
_ReservedLayout2()645 void BLayout::_ReservedLayout2() {}
_ReservedLayout3()646 void BLayout::_ReservedLayout3() {}
_ReservedLayout4()647 void BLayout::_ReservedLayout4() {}
_ReservedLayout5()648 void BLayout::_ReservedLayout5() {}
_ReservedLayout6()649 void BLayout::_ReservedLayout6() {}
_ReservedLayout7()650 void BLayout::_ReservedLayout7() {}
_ReservedLayout8()651 void BLayout::_ReservedLayout8() {}
_ReservedLayout9()652 void BLayout::_ReservedLayout9() {}
_ReservedLayout10()653 void BLayout::_ReservedLayout10() {}
654 
655