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