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