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