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 (fInvalidationDisabled > 0 305 || (fState & B_LAYOUT_INVALIDATION_ILLEGAL) != 0) { 306 return; 307 } 308 309 fState |= B_LAYOUT_NECESSARY; 310 LayoutInvalidated(children); 311 312 if (children) { 313 for (int32 i = CountItems() - 1; i >= 0; i--) 314 ItemAt(i)->InvalidateLayout(children); 315 } 316 317 if (fOwner) 318 fOwner->InvalidateLayout(children); 319 320 if (BLayout* nestedIn = Layout()) { 321 nestedIn->InvalidateLayout(); 322 } else if (fOwner) { 323 // If we weren't added as a BLayoutItem, we still have to invalidate 324 // whatever layout our owner is in. 325 fOwner->_InvalidateParentLayout(); 326 } 327 } 328 329 330 void 331 BLayout::RequireLayout() 332 { 333 fState |= B_LAYOUT_REQUIRED; 334 } 335 336 337 bool 338 BLayout::IsValid() 339 { 340 return (fState & B_LAYOUT_INVALID) == 0; 341 } 342 343 344 void 345 BLayout::DisableLayoutInvalidation() 346 { 347 fInvalidationDisabled++; 348 } 349 350 351 void 352 BLayout::EnableLayoutInvalidation() 353 { 354 if (fInvalidationDisabled > 0) 355 fInvalidationDisabled--; 356 } 357 358 359 void 360 BLayout::LayoutItems(bool force) 361 { 362 if ((fState & B_LAYOUT_NECESSARY) == 0 && !force) 363 return; 364 365 if (Layout() && (Layout()->fState & B_LAYOUT_IN_PROGRESS) != 0) 366 return; // wait for parent layout to lay us out. 367 368 if (fTarget && fTarget->LayoutContext()) 369 return; 370 371 BLayoutContext context; 372 _LayoutWithinContext(force, &context); 373 } 374 375 376 void 377 BLayout::Relayout(bool immediate) 378 { 379 if ((fState & B_RELAYOUT_NOT_OK) == 0 || immediate) { 380 fState |= B_LAYOUT_REQUIRED; 381 LayoutItems(false); 382 } 383 } 384 385 386 void 387 BLayout::_LayoutWithinContext(bool force, BLayoutContext* context) 388 { 389 // printf("BLayout(%p)::_LayoutWithinContext(%i, %p), state %x, fContext %p\n", 390 // this, force, context, (unsigned int)fState, fContext); 391 392 if ((fState & B_LAYOUT_NECESSARY) == 0 && !force) 393 return; 394 395 BLayoutContext* oldContext = fContext; 396 fContext = context; 397 398 if (fOwner && BView::Private(fOwner).WillLayout()) { 399 // in this case, let our owner decide whether or not to have us 400 // do our layout, if they do, we won't end up here again. 401 fOwner->_Layout(force, context); 402 } else { 403 fState |= B_LAYOUT_IN_PROGRESS; 404 DoLayout(); 405 // we must ensure that all items are laid out, layouts with a view will 406 // have their layout process triggered by their view, but nested 407 // view-less layouts must have their layout triggered here (if it hasn't 408 // already been triggered). 409 int32 nestedLayoutCount = fNestedLayouts.CountItems(); 410 for (int32 i = 0; i < nestedLayoutCount; i++) { 411 BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i); 412 if ((layout->fState & B_LAYOUT_NECESSARY) != 0) 413 layout->_LayoutWithinContext(force, context); 414 } 415 fState = B_LAYOUT_ALL_CLEAR; 416 } 417 418 fContext = oldContext; 419 } 420 421 422 BRect 423 BLayout::LayoutArea() 424 { 425 BRect area(Frame()); 426 if (fOwner) 427 area.OffsetTo(B_ORIGIN); 428 return area; 429 } 430 431 432 status_t 433 BLayout::Archive(BMessage* into, bool deep) const 434 { 435 BArchiver archiver(into); 436 status_t err = BLayoutItem::Archive(into, deep); 437 438 if (deep) { 439 int32 count = CountItems(); 440 for (int32 i = 0; i < count && err == B_OK; i++) { 441 BLayoutItem* item = ItemAt(i); 442 err = archiver.AddArchivable(kLayoutItemField, item, deep); 443 444 if (err == B_OK) { 445 err = ItemArchived(into, item, i); 446 if (err != B_OK) 447 syslog(LOG_ERR, "ItemArchived() failed at index: %d.", i); 448 } 449 } 450 } 451 452 return archiver.Finish(err); 453 } 454 455 456 status_t 457 BLayout::AllArchived(BMessage* archive) const 458 { 459 return BLayoutItem::AllArchived(archive); 460 } 461 462 463 status_t 464 BLayout::AllUnarchived(const BMessage* from) 465 { 466 BUnarchiver unarchiver(from); 467 status_t err = BLayoutItem::AllUnarchived(from); 468 if (err != B_OK) 469 return err; 470 471 int32 itemCount = 0; 472 unarchiver.ArchiveMessage()->GetInfo(kLayoutItemField, NULL, &itemCount); 473 for (int32 i = 0; i < itemCount && err == B_OK; i++) { 474 BLayoutItem* item; 475 err = unarchiver.FindObject(kLayoutItemField, 476 i, BUnarchiver::B_DONT_ASSUME_OWNERSHIP, item); 477 if (err != B_OK) 478 return err; 479 480 if (!fItems.AddItem(item, i) || !ItemAdded(item, i)) { 481 fItems.RemoveItem(i); 482 return B_ERROR; 483 } 484 485 err = ItemUnarchived(from, item, i); 486 if (err != B_OK) { 487 fItems.RemoveItem(i); 488 ItemRemoved(item, i); 489 return err; 490 } 491 492 item->SetLayout(this); 493 unarchiver.AssumeOwnership(item); 494 } 495 496 InvalidateLayout(); 497 return err; 498 } 499 500 501 status_t 502 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const 503 { 504 return B_OK; 505 } 506 507 508 status_t 509 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index) 510 { 511 return B_OK; 512 } 513 514 515 bool 516 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 517 { 518 return true; 519 } 520 521 522 void 523 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 524 { 525 } 526 527 528 void 529 BLayout::LayoutInvalidated(bool children) 530 { 531 } 532 533 534 void 535 BLayout::OwnerChanged(BView* was) 536 { 537 } 538 539 540 void 541 BLayout::AttachedToLayout() 542 { 543 if (!fOwner) { 544 Layout()->fNestedLayouts.AddItem(this); 545 SetTarget(Layout()->TargetView()); 546 } 547 } 548 549 550 void 551 BLayout::DetachedFromLayout(BLayout* from) 552 { 553 if (!fOwner) { 554 from->fNestedLayouts.RemoveItem(this); 555 SetTarget(NULL); 556 } 557 } 558 559 560 void 561 BLayout::AncestorVisibilityChanged(bool shown) 562 { 563 if (fAncestorsVisible == shown) 564 return; 565 566 fAncestorsVisible = shown; 567 VisibilityChanged(shown); 568 } 569 570 571 void 572 BLayout::VisibilityChanged(bool show) 573 { 574 if (fOwner) 575 return; 576 577 for (int32 i = CountItems() - 1; i >= 0; i--) 578 ItemAt(i)->AncestorVisibilityChanged(show); 579 } 580 581 582 void 583 BLayout::ResetLayoutInvalidation() 584 { 585 fState &= ~B_LAYOUT_CACHE_INVALID; 586 } 587 588 589 BLayoutContext* 590 BLayout::LayoutContext() const 591 { 592 return fContext; 593 } 594 595 596 void 597 BLayout::SetOwner(BView* owner) 598 { 599 if (fOwner == owner) 600 return; 601 602 SetTarget(owner); 603 swap(fOwner, owner); 604 605 OwnerChanged(owner); 606 // call hook 607 } 608 609 610 void 611 BLayout::SetTarget(BView* target) 612 { 613 if (fTarget != target) { 614 /* With fTarget NULL, RemoveItem() will not remove the views from their 615 * parent. This ensures that the views are not lost to the void. 616 */ 617 fTarget = NULL; 618 619 // remove and delete all items 620 for (int32 i = CountItems() - 1; i >= 0; i--) 621 delete RemoveItem(i); 622 623 fTarget = target; 624 625 InvalidateLayout(); 626 } 627 } 628 629 630 // Binary compatibility stuff 631 632 633 status_t 634 BLayout::Perform(perform_code code, void* _data) 635 { 636 return BLayoutItem::Perform(code, _data); 637 } 638 639 640 void BLayout::_ReservedLayout1() {} 641 void BLayout::_ReservedLayout2() {} 642 void BLayout::_ReservedLayout3() {} 643 void BLayout::_ReservedLayout4() {} 644 void BLayout::_ReservedLayout5() {} 645 void BLayout::_ReservedLayout6() {} 646 void BLayout::_ReservedLayout7() {} 647 void BLayout::_ReservedLayout8() {} 648 void BLayout::_ReservedLayout9() {} 649 void BLayout::_ReservedLayout10() {} 650 651