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 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* 105 BLayout::Owner() const 106 { 107 return fOwner; 108 } 109 110 111 BView* 112 BLayout::TargetView() const 113 { 114 return fTarget; 115 } 116 117 118 BView* 119 BLayout::View() 120 { 121 return fOwner; 122 } 123 124 125 BLayoutItem* 126 BLayout::AddView(BView* child) 127 { 128 return AddView(-1, child); 129 } 130 131 132 BLayoutItem* 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 152 BLayout::AddItem(BLayoutItem* item) 153 { 154 return AddItem(-1, item); 155 } 156 157 158 bool 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 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 224 BLayout::RemoveItem(BLayoutItem* item) 225 { 226 int32 index = IndexOfItem(item); 227 return (index >= 0 ? RemoveItem(index) != NULL : false); 228 } 229 230 231 BLayoutItem* 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* 254 BLayout::ItemAt(int32 index) const 255 { 256 return (BLayoutItem*)fItems.ItemAt(index); 257 } 258 259 260 int32 261 BLayout::CountItems() const 262 { 263 return fItems.CountItems(); 264 } 265 266 267 int32 268 BLayout::IndexOfItem(const BLayoutItem* item) const 269 { 270 return fItems.IndexOf(item); 271 } 272 273 274 int32 275 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 294 BLayout::AncestorsVisible() const 295 { 296 return fAncestorsVisible; 297 } 298 299 300 void 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 335 BLayout::RequireLayout() 336 { 337 fState |= B_LAYOUT_REQUIRED; 338 } 339 340 341 bool 342 BLayout::IsValid() 343 { 344 return (fState & B_LAYOUT_INVALID) == 0; 345 } 346 347 348 void 349 BLayout::DisableLayoutInvalidation() 350 { 351 fInvalidationDisabled++; 352 } 353 354 355 void 356 BLayout::EnableLayoutInvalidation() 357 { 358 if (fInvalidationDisabled > 0) 359 fInvalidationDisabled--; 360 } 361 362 363 void 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 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 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 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 437 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 461 BLayout::AllArchived(BMessage* archive) const 462 { 463 return BLayoutItem::AllArchived(archive); 464 } 465 466 467 status_t 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 506 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const 507 { 508 return B_OK; 509 } 510 511 512 status_t 513 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index) 514 { 515 return B_OK; 516 } 517 518 519 bool 520 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 521 { 522 return true; 523 } 524 525 526 void 527 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 528 { 529 } 530 531 532 void 533 BLayout::LayoutInvalidated(bool children) 534 { 535 } 536 537 538 void 539 BLayout::OwnerChanged(BView* was) 540 { 541 } 542 543 544 void 545 BLayout::AttachedToLayout() 546 { 547 if (!fOwner) { 548 Layout()->fNestedLayouts.AddItem(this); 549 SetTarget(Layout()->TargetView()); 550 } 551 } 552 553 554 void 555 BLayout::DetachedFromLayout(BLayout* from) 556 { 557 if (!fOwner) { 558 from->fNestedLayouts.RemoveItem(this); 559 SetTarget(NULL); 560 } 561 } 562 563 564 void 565 BLayout::AncestorVisibilityChanged(bool shown) 566 { 567 if (fAncestorsVisible == shown) 568 return; 569 570 fAncestorsVisible = shown; 571 VisibilityChanged(shown); 572 } 573 574 575 void 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 587 BLayout::ResetLayoutInvalidation() 588 { 589 fState &= ~B_LAYOUT_CACHE_INVALID; 590 } 591 592 593 BLayoutContext* 594 BLayout::LayoutContext() const 595 { 596 return fContext; 597 } 598 599 600 void 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 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 638 BLayout::Perform(perform_code code, void* _data) 639 { 640 return BLayoutItem::Perform(code, _data); 641 } 642 643 644 void BLayout::_ReservedLayout1() {} 645 void BLayout::_ReservedLayout2() {} 646 void BLayout::_ReservedLayout3() {} 647 void BLayout::_ReservedLayout4() {} 648 void BLayout::_ReservedLayout5() {} 649 void BLayout::_ReservedLayout6() {} 650 void BLayout::_ReservedLayout7() {} 651 void BLayout::_ReservedLayout8() {} 652 void BLayout::_ReservedLayout9() {} 653 void BLayout::_ReservedLayout10() {} 654 655