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 <LayoutContext.h> 15 #include <Message.h> 16 #include <View.h> 17 #include <ViewPrivate.h> 18 19 #include "ViewLayoutItem.h" 20 21 22 using std::nothrow; 23 using std::swap; 24 25 26 namespace { 27 // flags for our state 28 const uint32 B_LAYOUT_INVALID = 0x80000000UL; // needs layout 29 const uint32 B_LAYOUT_CACHE_INVALID = 0x40000000UL; // needs recalculation 30 const uint32 B_LAYOUT_REQUIRED = 0x20000000UL; // needs layout 31 const uint32 B_LAYOUT_IN_PROGRESS = 0x10000000UL; 32 const uint32 B_LAYOUT_ALL_CLEAR = 0UL; 33 34 // handy masks to check various states 35 const uint32 B_LAYOUT_INVALIDATION_ILLEGAL 36 = B_LAYOUT_CACHE_INVALID | B_LAYOUT_IN_PROGRESS; 37 const uint32 B_LAYOUT_NECESSARY 38 = B_LAYOUT_INVALID | B_LAYOUT_REQUIRED | B_LAYOUT_CACHE_INVALID; 39 const uint32 B_RELAYOUT_NOT_OK 40 = B_LAYOUT_INVALID | B_LAYOUT_IN_PROGRESS; 41 42 const char* const kLayoutItemField = "BLayout:items"; 43 } 44 45 46 BLayout::BLayout() 47 : 48 fState(B_LAYOUT_ALL_CLEAR), 49 fAncestorsVisible(true), 50 fInvalidationDisabled(0), 51 fContext(NULL), 52 fOwner(NULL), 53 fTarget(NULL), 54 fItems(20) 55 { 56 } 57 58 59 BLayout::BLayout(BMessage* from) 60 : 61 BLayoutItem(BUnarchiver::PrepareArchive(from)), 62 fState(B_LAYOUT_ALL_CLEAR), 63 fAncestorsVisible(true), 64 fInvalidationDisabled(0), 65 fContext(NULL), 66 fOwner(NULL), 67 fTarget(NULL), 68 fItems(20) 69 { 70 BUnarchiver unarchiver(from); 71 72 int32 i = 0; 73 while (unarchiver.EnsureUnarchived(kLayoutItemField, i++) == B_OK) 74 ; 75 } 76 77 78 BLayout::~BLayout() 79 { 80 // in case we have a view, but have been added to a layout as a BLayoutItem 81 // we will get deleted before our view, so we should tell it that we're 82 // going, so that we aren't double-freed. 83 if (fOwner && this == fOwner->GetLayout()) 84 fOwner->_LayoutLeft(this); 85 86 // removes and deletes all items 87 if (fTarget) 88 SetTarget(NULL); 89 } 90 91 92 BView* 93 BLayout::Owner() const 94 { 95 return fOwner; 96 } 97 98 99 BView* 100 BLayout::TargetView() const 101 { 102 return fTarget; 103 } 104 105 106 BView* 107 BLayout::View() 108 { 109 return fOwner; 110 } 111 112 113 BLayoutItem* 114 BLayout::AddView(BView* child) 115 { 116 return AddView(-1, child); 117 } 118 119 120 BLayoutItem* 121 BLayout::AddView(int32 index, BView* child) 122 { 123 BLayoutItem* item = child->GetLayout(); 124 if (!item) 125 item = new(nothrow) BViewLayoutItem(child); 126 127 if (item && AddItem(index, item)) 128 return item; 129 130 if (!child->GetLayout()) 131 delete item; 132 return NULL; 133 } 134 135 136 bool 137 BLayout::AddItem(BLayoutItem* item) 138 { 139 return AddItem(-1, item); 140 } 141 142 143 bool 144 BLayout::AddItem(int32 index, BLayoutItem* item) 145 { 146 if (!fTarget || !item || fItems.HasItem(item)) 147 return false; 148 149 // if the item refers to a BView, we make sure it is added to the parent 150 // view 151 bool addedView = false; 152 BView* view = item->View(); 153 if (view && view->fParent != fTarget 154 && !(addedView = fTarget->_AddChild(view, NULL))) 155 return false; 156 157 // validate the index 158 if (index < 0 || index > fItems.CountItems()) 159 index = fItems.CountItems(); 160 161 if (fItems.AddItem(item, index) && ItemAdded(item, index)) { 162 item->SetLayout(this); 163 if (!fAncestorsVisible) 164 item->AncestorVisibilityChanged(fAncestorsVisible); 165 InvalidateLayout(); 166 return true; 167 } else { 168 // this check is necessary so that if an addition somewhere other 169 // than the end of the list fails, we don't remove the wrong item 170 if (fItems.ItemAt(index) == item) 171 fItems.RemoveItem(index); 172 if (addedView) 173 view->_RemoveSelf(); 174 return false; 175 } 176 } 177 178 179 bool 180 BLayout::RemoveView(BView* child) 181 { 182 bool removed = false; 183 184 // a view can have any number of layout items - we need to remove them all 185 for (int32 i = fItems.CountItems(); i-- > 0;) { 186 BLayoutItem* item = ItemAt(i); 187 188 if (item->View() != child) 189 continue; 190 191 RemoveItem(i); 192 removed = true; 193 delete item; 194 } 195 196 return removed; 197 } 198 199 200 bool 201 BLayout::RemoveItem(BLayoutItem* item) 202 { 203 int32 index = IndexOfItem(item); 204 return (index >= 0 ? RemoveItem(index) : false); 205 } 206 207 208 BLayoutItem* 209 BLayout::RemoveItem(int32 index) 210 { 211 if (index < 0 || index >= fItems.CountItems()) 212 return NULL; 213 214 BLayoutItem* item = (BLayoutItem*)fItems.RemoveItem(index); 215 216 // if the item refers to a BView, we make sure, it is removed from the 217 // parent view 218 BView* view = item->View(); 219 if (view && view->fParent == fTarget) 220 view->_RemoveSelf(); 221 222 ItemRemoved(item, index); 223 item->SetLayout(NULL); 224 InvalidateLayout(); 225 226 return item; 227 } 228 229 230 BLayoutItem* 231 BLayout::ItemAt(int32 index) const 232 { 233 return (BLayoutItem*)fItems.ItemAt(index); 234 } 235 236 237 int32 238 BLayout::CountItems() const 239 { 240 return fItems.CountItems(); 241 } 242 243 244 int32 245 BLayout::IndexOfItem(const BLayoutItem* item) const 246 { 247 return fItems.IndexOf(item); 248 } 249 250 251 int32 252 BLayout::IndexOfView(BView* child) const 253 { 254 int itemCount = fItems.CountItems(); 255 for (int32 i = 0; i < itemCount; i++) { 256 BLayoutItem* item = (BLayoutItem*)fItems.ItemAt(i); 257 if (dynamic_cast<BViewLayoutItem*>(item) && item->View() == child) 258 return i; 259 } 260 261 return -1; 262 } 263 264 265 bool 266 BLayout::AncestorsVisible() 267 { 268 return fAncestorsVisible; 269 } 270 271 272 void 273 BLayout::InvalidateLayout(bool children) 274 { 275 // printf("BLayout(%p)::InvalidateLayout(%i) : state %x, disabled %li\n", 276 // this, children, (unsigned int)fState, fInvalidationDisabled); 277 278 if (!InvalidationLegal()) 279 return; 280 281 fState |= B_LAYOUT_NECESSARY; 282 283 if (children) { 284 for (int32 i = CountItems() - 1; i >= 0; i--) 285 ItemAt(i)->InvalidateLayout(children); 286 } 287 288 if (fOwner && BView::Private(fOwner).MinMaxValid()) 289 fOwner->InvalidateLayout(children); 290 291 if (BLayout* nestedIn = Layout()) { 292 if (nestedIn->InvalidationLegal()) 293 nestedIn->InvalidateLayout(); 294 } else if (fOwner) { 295 // If we weren't added as a BLayoutItem, we still have to invalidate 296 // whatever layout our owner is in. 297 BView* ownerParent = fOwner->fParent; 298 if (ownerParent) { 299 BLayout* layout = ownerParent->GetLayout(); 300 if (layout && layout->fNestedLayouts.CountItems() > 0) 301 layout->InvalidateLayoutsForView(fOwner); 302 else if (BView::Private(ownerParent).MinMaxValid()) 303 ownerParent->InvalidateLayout(false); 304 } 305 } 306 } 307 308 309 void 310 BLayout::RequireLayout() 311 { 312 fState |= B_LAYOUT_REQUIRED; 313 } 314 315 316 bool 317 BLayout::IsValid() 318 { 319 return (fState & B_LAYOUT_INVALID) == 0; 320 } 321 322 323 void 324 BLayout::DisableLayoutInvalidation() 325 { 326 fInvalidationDisabled++; 327 } 328 329 330 void 331 BLayout::EnableLayoutInvalidation() 332 { 333 if (fInvalidationDisabled > 0) 334 fInvalidationDisabled--; 335 } 336 337 338 void 339 BLayout::LayoutItems(bool force) 340 { 341 if ((fState & B_LAYOUT_NECESSARY) == 0 && !force) 342 return; 343 344 if (Layout() && (Layout()->fState & B_LAYOUT_IN_PROGRESS) != 0) 345 return; // wait for parent layout to lay us out. 346 347 if (fTarget && fTarget->LayoutContext()) 348 return; 349 350 BLayoutContext context; 351 _LayoutWithinContext(force, &context); 352 } 353 354 355 void 356 BLayout::Relayout(bool immediate) 357 { 358 if ((fState & B_RELAYOUT_NOT_OK) == 0 || immediate) { 359 fState |= B_LAYOUT_REQUIRED; 360 LayoutItems(false); 361 } 362 } 363 364 365 366 void 367 BLayout::_LayoutWithinContext(bool force, BLayoutContext* context) 368 { 369 // printf("BLayout(%p)::_LayoutWithinContext(%i, %p), state %x, fContext %p\n", 370 // this, force, context, (unsigned int)fState, fContext); 371 372 if ((fState & B_LAYOUT_NECESSARY) == 0 && !force) 373 return; 374 375 BLayoutContext* oldContext = fContext; 376 fContext = context; 377 378 if (fOwner && BView::Private(fOwner).WillLayout()) { 379 // in this case, let our owner decide whether or not to have us 380 // do our layout, if they do, we won't end up here again. 381 fOwner->_Layout(force, context); 382 } else { 383 fState |= B_LAYOUT_IN_PROGRESS; 384 DerivedLayoutItems(); 385 // we must ensure that all items are laid out, layouts with a view will 386 // have their layout process triggered by their view, but nested 387 // view-less layouts must have their layout triggered here (if it hasn't 388 // already been triggered). 389 int32 nestedLayoutCount = fNestedLayouts.CountItems(); 390 for (int32 i = 0; i < nestedLayoutCount; i++) { 391 BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i); 392 if ((layout->fState & B_LAYOUT_NECESSARY) != 0) 393 layout->_LayoutWithinContext(force, context); 394 } 395 fState = B_LAYOUT_ALL_CLEAR; 396 } 397 398 fContext = oldContext; 399 } 400 401 402 BRect 403 BLayout::LayoutArea() 404 { 405 BRect area(Frame()); 406 if (fOwner) 407 area.OffsetTo(B_ORIGIN); 408 return area; 409 } 410 411 412 status_t 413 BLayout::Archive(BMessage* into, bool deep) const 414 { 415 BArchiver archiver(into); 416 status_t err = BLayoutItem::Archive(into, deep); 417 418 if (deep) { 419 int32 count = CountItems(); 420 for (int32 i = 0; i < count && err == B_OK; i++) { 421 BLayoutItem* item = ItemAt(i); 422 err = archiver.AddArchivable(kLayoutItemField, item, deep); 423 424 if (err == B_OK) { 425 err = ItemArchived(into, item, i); 426 if (err != B_OK) 427 syslog(LOG_ERR, "ItemArchived() failed at index: %d.", i); 428 } 429 } 430 } 431 432 return archiver.Finish(err); 433 } 434 435 436 status_t 437 BLayout::AllUnarchived(const BMessage* from) 438 { 439 BUnarchiver unarchiver(from); 440 status_t err = BLayoutItem::AllUnarchived(from); 441 if (err != B_OK) 442 return err; 443 444 int32 itemCount; 445 unarchiver.ArchiveMessage()->GetInfo(kLayoutItemField, NULL, &itemCount); 446 for (int32 i = 0; i < itemCount && err == B_OK; i++) { 447 BLayoutItem* item; 448 err = unarchiver.FindObject(kLayoutItemField, 449 i, BUnarchiver::B_DONT_ASSUME_OWNERSHIP, item); 450 if (err != B_OK) 451 return err; 452 453 if (!fItems.AddItem(item, i) || !ItemAdded(item, i)) { 454 fItems.RemoveItem(i); 455 return B_ERROR; 456 } 457 458 err = ItemUnarchived(from, item, i); 459 if (err != B_OK) { 460 fItems.RemoveItem(i); 461 ItemRemoved(item, i); 462 return err; 463 } 464 465 item->SetLayout(this); 466 unarchiver.AssumeOwnership(item); 467 } 468 469 InvalidateLayout(); 470 return err; 471 } 472 473 474 status_t 475 BLayout::ItemArchived(BMessage* into, BLayoutItem* item, int32 index) const 476 { 477 return B_OK; 478 } 479 480 481 status_t 482 BLayout::ItemUnarchived(const BMessage* from, BLayoutItem* item, int32 index) 483 { 484 return B_OK; 485 } 486 487 488 bool 489 BLayout::ItemAdded(BLayoutItem* item, int32 atIndex) 490 { 491 return true; 492 } 493 494 495 void 496 BLayout::ItemRemoved(BLayoutItem* item, int32 fromIndex) 497 { 498 } 499 500 501 void 502 BLayout::OwnerChanged(BView* was) 503 { 504 } 505 506 507 void 508 BLayout::AttachedToLayout() 509 { 510 if (!fOwner) { 511 Layout()->fNestedLayouts.AddItem(this); 512 SetTarget(Layout()->TargetView()); 513 } 514 } 515 516 517 void 518 BLayout::DetachedFromLayout(BLayout* from) 519 { 520 if (!fOwner) { 521 from->fNestedLayouts.RemoveItem(this); 522 SetTarget(NULL); 523 } 524 } 525 526 527 void 528 BLayout::AncestorVisibilityChanged(bool shown) 529 { 530 if (fAncestorsVisible == shown) 531 return; 532 533 fAncestorsVisible = shown; 534 VisibilityChanged(shown); 535 } 536 537 538 void 539 BLayout::VisibilityChanged(bool show) 540 { 541 if (fOwner) 542 return; 543 544 for (int32 i = CountItems() - 1; i >= 0; i--) 545 ItemAt(i)->AncestorVisibilityChanged(show); 546 } 547 548 549 void 550 BLayout::ResetLayoutInvalidation() 551 { 552 fState &= ~B_LAYOUT_CACHE_INVALID; 553 } 554 555 556 BLayoutContext* 557 BLayout::LayoutContext() 558 { 559 return fContext; 560 } 561 562 563 bool 564 BLayout::RemoveViewRecursive(BView* view) 565 { 566 bool removed = RemoveView(view); 567 for (int32 i = fNestedLayouts.CountItems() - 1; i >= 0; i--) { 568 BLayout* nested = (BLayout*)fNestedLayouts.ItemAt(i); 569 removed |= nested->RemoveViewRecursive(view); 570 } 571 return removed; 572 } 573 574 575 bool 576 BLayout::InvalidateLayoutsForView(BView* view) 577 { 578 bool found = false; 579 for (int32 i = fNestedLayouts.CountItems() - 1; i >= 0; i--) { 580 BLayout* layout = (BLayout*)fNestedLayouts.ItemAt(i); 581 found |= layout->InvalidateLayoutsForView(view); 582 } 583 584 if (found) 585 return found; 586 587 if (!InvalidationLegal()) 588 return false; 589 590 for (int32 i = CountItems() - 1; i >= 0; i--) { 591 if (ItemAt(i)->View() == view) { 592 InvalidateLayout(); 593 return true; 594 } 595 } 596 return found; 597 } 598 599 600 bool 601 BLayout::InvalidationLegal() 602 { 603 return fInvalidationDisabled <= 0 604 && (fState & B_LAYOUT_INVALIDATION_ILLEGAL) == 0; 605 } 606 607 608 void 609 BLayout::SetOwner(BView* owner) 610 { 611 if (fOwner == owner) 612 return; 613 614 SetTarget(owner); 615 swap(fOwner, owner); 616 617 OwnerChanged(owner); 618 // call hook 619 } 620 621 622 void 623 BLayout::SetTarget(BView* target) 624 { 625 if (fTarget != target) { 626 fTarget = NULL; 627 // only remove items, not views 628 629 // remove and delete all items 630 for (int32 i = CountItems() - 1; i >= 0; i--) 631 delete RemoveItem(i); 632 633 fTarget = target; 634 635 InvalidateLayout(); 636 } 637 } 638