1 /* 2 * Copyright 2001-2009, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Marc Flerackers (mflerackers@androme.be) 7 * Rene Gollent (rene@gollent.com) 8 * Alexandre Deckner (alex@zappotek.com) 9 */ 10 11 //! BDragger represents a replicant "handle". 12 13 14 #include <pthread.h> 15 #include <stdio.h> 16 #include <stdlib.h> 17 18 #include <Alert.h> 19 #include <Beep.h> 20 #include <Bitmap.h> 21 #include <Dragger.h> 22 #include <MenuItem.h> 23 #include <Message.h> 24 #include <PopUpMenu.h> 25 #include <Shelf.h> 26 #include <Window.h> 27 28 #include <AutoLocker.h> 29 30 #include <AppServerLink.h> 31 #include <DragTrackingFilter.h> 32 #include <binary_compatibility/Interface.h> 33 #include <ServerProtocol.h> 34 #include <ViewPrivate.h> 35 36 #include "ZombieReplicantView.h" 37 38 39 const uint32 kMsgDragStarted = 'Drgs'; 40 41 const unsigned char 42 kHandBitmap[] = { 43 255, 255, 0, 0, 0, 255, 255, 255, 44 255, 255, 0, 131, 131, 0, 255, 255, 45 0, 0, 0, 0, 131, 131, 0, 0, 46 0, 131, 0, 0, 131, 131, 0, 0, 47 0, 131, 131, 131, 131, 131, 0, 0, 48 255, 0, 131, 131, 131, 131, 0, 0, 49 255, 255, 0, 0, 0, 0, 0, 0, 50 255, 255, 255, 255, 255, 255, 0, 0 51 }; 52 53 54 namespace { 55 56 struct DraggerManager { 57 bool visible; 58 bool visibleInitialized; 59 BList list; 60 61 DraggerManager() 62 : 63 visible(false), 64 visibleInitialized(false), 65 fLock("BDragger static") 66 { 67 } 68 69 bool Lock() 70 { 71 return fLock.Lock(); 72 } 73 74 void Unlock() 75 { 76 fLock.Unlock(); 77 } 78 79 static DraggerManager* Default() 80 { 81 if (sDefaultInstance == NULL) 82 pthread_once(&sDefaultInitOnce, &_InitSingleton); 83 84 return sDefaultInstance; 85 } 86 87 private: 88 static void _InitSingleton() 89 { 90 sDefaultInstance = new DraggerManager; 91 } 92 93 private: 94 BLocker fLock; 95 96 static pthread_once_t sDefaultInitOnce; 97 static DraggerManager* sDefaultInstance; 98 }; 99 100 pthread_once_t DraggerManager::sDefaultInitOnce = PTHREAD_ONCE_INIT; 101 DraggerManager* DraggerManager::sDefaultInstance = NULL; 102 103 } // unnamed namespace 104 105 106 BDragger::BDragger(BRect bounds, BView *target, uint32 rmask, uint32 flags) 107 : BView(bounds, "_dragger_", rmask, flags), 108 fTarget(target), 109 fRelation(TARGET_UNKNOWN), 110 fShelf(NULL), 111 fTransition(false), 112 fIsZombie(false), 113 fErrCount(0), 114 fPopUpIsCustom(false), 115 fPopUp(NULL) 116 { 117 fBitmap = new BBitmap(BRect(0.0f, 0.0f, 7.0f, 7.0f), B_CMAP8, false, false); 118 fBitmap->SetBits(kHandBitmap, fBitmap->BitsLength(), 0, B_CMAP8); 119 } 120 121 122 BDragger::BDragger(BMessage *data) 123 : BView(data), 124 fTarget(NULL), 125 fRelation(TARGET_UNKNOWN), 126 fShelf(NULL), 127 fTransition(false), 128 fIsZombie(false), 129 fErrCount(0), 130 fPopUpIsCustom(false), 131 fPopUp(NULL) 132 { 133 data->FindInt32("_rel", (int32 *)&fRelation); 134 135 fBitmap = new BBitmap(BRect(0.0f, 0.0f, 7.0f, 7.0f), B_CMAP8, false, false); 136 fBitmap->SetBits(kHandBitmap, fBitmap->BitsLength(), 0, B_CMAP8); 137 138 BMessage popupMsg; 139 if (data->FindMessage("_popup", &popupMsg) == B_OK) { 140 BArchivable *archivable = instantiate_object(&popupMsg); 141 142 if (archivable) { 143 fPopUp = dynamic_cast<BPopUpMenu *>(archivable); 144 fPopUpIsCustom = true; 145 } 146 } 147 } 148 149 150 BDragger::~BDragger() 151 { 152 delete fPopUp; 153 delete fBitmap; 154 } 155 156 157 BArchivable * 158 BDragger::Instantiate(BMessage *data) 159 { 160 if (validate_instantiation(data, "BDragger")) 161 return new BDragger(data); 162 return NULL; 163 } 164 165 166 status_t 167 BDragger::Archive(BMessage *data, bool deep) const 168 { 169 status_t ret = BView::Archive(data, deep); 170 if (ret != B_OK) 171 return ret; 172 173 BMessage popupMsg; 174 175 if (fPopUp && fPopUpIsCustom) { 176 bool windowLocked = fPopUp->Window()->Lock(); 177 178 ret = fPopUp->Archive(&popupMsg, deep); 179 180 if (windowLocked) 181 fPopUp->Window()->Unlock(); 182 // TODO: Investigate, in some (rare) occasions the menu window 183 // has already been unlocked 184 185 if (ret == B_OK) 186 ret = data->AddMessage("_popup", &popupMsg); 187 } 188 189 if (ret == B_OK) 190 ret = data->AddInt32("_rel", fRelation); 191 return ret; 192 } 193 194 195 void 196 BDragger::AttachedToWindow() 197 { 198 if (fIsZombie) { 199 SetLowColor(kZombieColor); 200 SetViewColor(kZombieColor); 201 } else { 202 SetLowColor(B_TRANSPARENT_COLOR); 203 SetViewColor(B_TRANSPARENT_COLOR); 204 } 205 206 _DetermineRelationship(); 207 _AddToList(); 208 209 AddFilter(new DragTrackingFilter(this, kMsgDragStarted)); 210 } 211 212 213 void 214 BDragger::DetachedFromWindow() 215 { 216 _RemoveFromList(); 217 } 218 219 220 void 221 BDragger::Draw(BRect update) 222 { 223 BRect bounds(Bounds()); 224 225 if (AreDraggersDrawn() && (!fShelf || fShelf->AllowsDragging())) { 226 if (Parent() && (Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0) { 227 uint32 flags = Parent()->Flags(); 228 Parent()->SetFlags(flags | B_DRAW_ON_CHILDREN); 229 Parent()->Draw(Frame() & ConvertToParent(update)); 230 Parent()->Flush(); 231 Parent()->SetFlags(flags); 232 } 233 234 BPoint where = bounds.RightBottom() - BPoint(fBitmap->Bounds().Width(), 235 fBitmap->Bounds().Height()); 236 SetDrawingMode(B_OP_OVER); 237 DrawBitmap(fBitmap, where); 238 SetDrawingMode(B_OP_COPY); 239 240 if (fIsZombie) { 241 // TODO: should draw it differently ? 242 } 243 } else if (IsVisibilityChanging()) { 244 if (Parent()) { 245 if ((Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0) { 246 uint32 flags = Parent()->Flags(); 247 Parent()->SetFlags(flags | B_DRAW_ON_CHILDREN); 248 Parent()->Invalidate(Frame() & ConvertToParent(update)); 249 Parent()->SetFlags(flags); 250 } 251 } else { 252 SetHighColor(255, 255, 255); 253 FillRect(bounds); 254 } 255 } 256 } 257 258 259 void 260 BDragger::MouseDown(BPoint where) 261 { 262 if (!fTarget || !AreDraggersDrawn()) 263 return; 264 265 uint32 buttons; 266 Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); 267 268 if (fShelf != NULL && (buttons & B_SECONDARY_MOUSE_BUTTON)) 269 _ShowPopUp(fTarget, where); 270 } 271 272 273 void 274 BDragger::MouseUp(BPoint point) 275 { 276 BView::MouseUp(point); 277 } 278 279 280 void 281 BDragger::MouseMoved(BPoint point, uint32 code, const BMessage *msg) 282 { 283 BView::MouseMoved(point, code, msg); 284 } 285 286 287 void 288 BDragger::MessageReceived(BMessage *msg) 289 { 290 switch (msg->what) { 291 case B_TRASH_TARGET: 292 if (fShelf != NULL) 293 Window()->PostMessage(kDeleteReplicant, fTarget, NULL); 294 else { 295 (new BAlert("??", 296 "Can't delete this replicant from its original " 297 "application. Life goes on.", 298 "OK", NULL, NULL, B_WIDTH_FROM_WIDEST, 299 B_WARNING_ALERT))->Go(NULL); 300 } 301 break; 302 303 case _SHOW_DRAG_HANDLES_: 304 // This code is used whenever the "are draggers drawn" option is 305 // changed. 306 if (fRelation == TARGET_IS_CHILD) { 307 fTransition = true; 308 Draw(Bounds()); 309 Flush(); 310 fTransition = false; 311 } else { 312 if ((fShelf && (fShelf->AllowsDragging() && AreDraggersDrawn())) 313 || AreDraggersDrawn()) 314 Show(); 315 else 316 Hide(); 317 } 318 break; 319 320 case kMsgDragStarted: 321 if (fTarget != NULL) { 322 BMessage archive(B_ARCHIVED_OBJECT); 323 324 if (fRelation == TARGET_IS_PARENT) 325 fTarget->Archive(&archive); 326 else if (fRelation == TARGET_IS_CHILD) 327 Archive(&archive); 328 else { 329 if (fTarget->Archive(&archive)) { 330 BMessage archivedSelf(B_ARCHIVED_OBJECT); 331 332 if (Archive(&archivedSelf)) 333 archive.AddMessage("__widget", &archivedSelf); 334 } 335 } 336 337 archive.AddInt32("be:actions", B_TRASH_TARGET); 338 BPoint offset; 339 drawing_mode mode; 340 BBitmap *bitmap = DragBitmap(&offset, &mode); 341 if (bitmap != NULL) 342 DragMessage(&archive, bitmap, mode, offset, this); 343 else { 344 DragMessage(&archive, 345 ConvertFromScreen(fTarget->ConvertToScreen(fTarget->Bounds())), 346 this); 347 } 348 } 349 break; 350 351 default: 352 BView::MessageReceived(msg); 353 break; 354 } 355 } 356 357 358 void 359 BDragger::FrameMoved(BPoint newPosition) 360 { 361 BView::FrameMoved(newPosition); 362 } 363 364 365 void 366 BDragger::FrameResized(float newWidth, float newHeight) 367 { 368 BView::FrameResized(newWidth, newHeight); 369 } 370 371 372 status_t 373 BDragger::ShowAllDraggers() 374 { 375 BPrivate::AppServerLink link; 376 link.StartMessage(AS_SET_SHOW_ALL_DRAGGERS); 377 link.Attach<bool>(true); 378 379 status_t status = link.Flush(); 380 if (status == B_OK) { 381 DraggerManager* manager = DraggerManager::Default(); 382 AutoLocker<DraggerManager> locker(manager); 383 manager->visible = true; 384 manager->visibleInitialized = true; 385 } 386 387 return status; 388 } 389 390 391 status_t 392 BDragger::HideAllDraggers() 393 { 394 BPrivate::AppServerLink link; 395 link.StartMessage(AS_SET_SHOW_ALL_DRAGGERS); 396 link.Attach<bool>(false); 397 398 status_t status = link.Flush(); 399 if (status == B_OK) { 400 DraggerManager* manager = DraggerManager::Default(); 401 AutoLocker<DraggerManager> locker(manager); 402 manager->visible = false; 403 manager->visibleInitialized = true; 404 } 405 406 return status; 407 } 408 409 410 bool 411 BDragger::AreDraggersDrawn() 412 { 413 DraggerManager* manager = DraggerManager::Default(); 414 AutoLocker<DraggerManager> locker(manager); 415 416 if (!manager->visibleInitialized) { 417 BPrivate::AppServerLink link; 418 link.StartMessage(AS_GET_SHOW_ALL_DRAGGERS); 419 420 status_t status; 421 if (link.FlushWithReply(status) == B_OK && status == B_OK) { 422 link.Read<bool>(&manager->visible); 423 manager->visibleInitialized = true; 424 } else 425 return false; 426 } 427 428 return manager->visible; 429 } 430 431 432 BHandler* 433 BDragger::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 434 int32 form, const char* property) 435 { 436 return BView::ResolveSpecifier(message, index, specifier, form, property); 437 } 438 439 440 status_t 441 BDragger::GetSupportedSuites(BMessage* data) 442 { 443 return BView::GetSupportedSuites(data); 444 } 445 446 447 status_t 448 BDragger::Perform(perform_code code, void* _data) 449 { 450 switch (code) { 451 case PERFORM_CODE_MIN_SIZE: 452 ((perform_data_min_size*)_data)->return_value 453 = BDragger::MinSize(); 454 return B_OK; 455 case PERFORM_CODE_MAX_SIZE: 456 ((perform_data_max_size*)_data)->return_value 457 = BDragger::MaxSize(); 458 return B_OK; 459 case PERFORM_CODE_PREFERRED_SIZE: 460 ((perform_data_preferred_size*)_data)->return_value 461 = BDragger::PreferredSize(); 462 return B_OK; 463 case PERFORM_CODE_LAYOUT_ALIGNMENT: 464 ((perform_data_layout_alignment*)_data)->return_value 465 = BDragger::LayoutAlignment(); 466 return B_OK; 467 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 468 ((perform_data_has_height_for_width*)_data)->return_value 469 = BDragger::HasHeightForWidth(); 470 return B_OK; 471 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 472 { 473 perform_data_get_height_for_width* data 474 = (perform_data_get_height_for_width*)_data; 475 BDragger::GetHeightForWidth(data->width, &data->min, &data->max, 476 &data->preferred); 477 return B_OK; 478 } 479 case PERFORM_CODE_SET_LAYOUT: 480 { 481 perform_data_set_layout* data = (perform_data_set_layout*)_data; 482 BDragger::SetLayout(data->layout); 483 return B_OK; 484 } 485 case PERFORM_CODE_INVALIDATE_LAYOUT: 486 { 487 perform_data_invalidate_layout* data 488 = (perform_data_invalidate_layout*)_data; 489 BDragger::InvalidateLayout(data->descendants); 490 return B_OK; 491 } 492 case PERFORM_CODE_DO_LAYOUT: 493 { 494 BDragger::DoLayout(); 495 return B_OK; 496 } 497 } 498 499 return BView::Perform(code, _data); 500 } 501 502 503 void 504 BDragger::ResizeToPreferred() 505 { 506 BView::ResizeToPreferred(); 507 } 508 509 510 void 511 BDragger::GetPreferredSize(float* _width, float* _height) 512 { 513 BView::GetPreferredSize(_width, _height); 514 } 515 516 517 void 518 BDragger::MakeFocus(bool state) 519 { 520 BView::MakeFocus(state); 521 } 522 523 524 void 525 BDragger::AllAttached() 526 { 527 BView::AllAttached(); 528 } 529 530 531 void 532 BDragger::AllDetached() 533 { 534 BView::AllDetached(); 535 } 536 537 538 status_t 539 BDragger::SetPopUp(BPopUpMenu *menu) 540 { 541 if (menu != NULL && menu != fPopUp) { 542 delete fPopUp; 543 fPopUp = menu; 544 fPopUpIsCustom = true; 545 return B_OK; 546 } 547 return B_ERROR; 548 } 549 550 551 BPopUpMenu * 552 BDragger::PopUp() const 553 { 554 if (fPopUp == NULL && fTarget) 555 const_cast<BDragger *>(this)->_BuildDefaultPopUp(); 556 557 return fPopUp; 558 } 559 560 561 bool 562 BDragger::InShelf() const 563 { 564 return fShelf != NULL; 565 } 566 567 568 BView * 569 BDragger::Target() const 570 { 571 return fTarget; 572 } 573 574 575 BBitmap * 576 BDragger::DragBitmap(BPoint *offset, drawing_mode *mode) 577 { 578 return NULL; 579 } 580 581 582 bool 583 BDragger::IsVisibilityChanging() const 584 { 585 return fTransition; 586 } 587 588 589 void BDragger::_ReservedDragger2() {} 590 void BDragger::_ReservedDragger3() {} 591 void BDragger::_ReservedDragger4() {} 592 593 594 BDragger & 595 BDragger::operator=(const BDragger &) 596 { 597 return *this; 598 } 599 600 601 /*static*/ void 602 BDragger::_UpdateShowAllDraggers(bool visible) 603 { 604 DraggerManager* manager = DraggerManager::Default(); 605 AutoLocker<DraggerManager> locker(manager); 606 607 manager->visibleInitialized = true; 608 manager->visible = visible; 609 610 for (int32 i = manager->list.CountItems(); i-- > 0;) { 611 BDragger* dragger = (BDragger*)manager->list.ItemAt(i); 612 BMessenger target(dragger); 613 target.SendMessage(_SHOW_DRAG_HANDLES_); 614 } 615 } 616 617 618 void 619 BDragger::_AddToList() 620 { 621 DraggerManager* manager = DraggerManager::Default(); 622 AutoLocker<DraggerManager> locker(manager); 623 manager->list.AddItem(this); 624 625 bool allowsDragging = true; 626 if (fShelf) 627 allowsDragging = fShelf->AllowsDragging(); 628 629 if (!AreDraggersDrawn() || !allowsDragging) { 630 // The dragger is not shown - but we can't hide us in case we're the 631 // parent of the actual target view (because then you couldn't see 632 // it anymore). 633 if (fRelation != TARGET_IS_CHILD) 634 Hide(); 635 } 636 } 637 638 639 void 640 BDragger::_RemoveFromList() 641 { 642 DraggerManager* manager = DraggerManager::Default(); 643 AutoLocker<DraggerManager> locker(manager); 644 manager->list.RemoveItem(this); 645 } 646 647 648 status_t 649 BDragger::_DetermineRelationship() 650 { 651 if (fTarget) { 652 if (fTarget == Parent()) 653 fRelation = TARGET_IS_PARENT; 654 else if (fTarget == ChildAt(0)) 655 fRelation = TARGET_IS_CHILD; 656 else 657 fRelation = TARGET_IS_SIBLING; 658 } else { 659 if (fRelation == TARGET_IS_PARENT) 660 fTarget = Parent(); 661 else if (fRelation == TARGET_IS_CHILD) 662 fTarget = ChildAt(0); 663 else 664 return B_ERROR; 665 } 666 667 if (fRelation == TARGET_IS_PARENT) { 668 BRect bounds (Frame()); 669 BRect parentBounds (Parent()->Bounds()); 670 if (!parentBounds.Contains(bounds)) 671 MoveTo(parentBounds.right - bounds.Width(), 672 parentBounds.bottom - bounds.Height()); 673 } 674 675 return B_OK; 676 } 677 678 679 status_t 680 BDragger::_SetViewToDrag(BView *target) 681 { 682 if (target->Window() != Window()) 683 return B_ERROR; 684 685 fTarget = target; 686 687 if (Window()) 688 _DetermineRelationship(); 689 690 return B_OK; 691 } 692 693 694 void 695 BDragger::_SetShelf(BShelf *shelf) 696 { 697 fShelf = shelf; 698 } 699 700 701 void 702 BDragger::_SetZombied(bool state) 703 { 704 fIsZombie = state; 705 706 if (state) { 707 SetLowColor(kZombieColor); 708 SetViewColor(kZombieColor); 709 } 710 } 711 712 713 void 714 BDragger::_BuildDefaultPopUp() 715 { 716 fPopUp = new BPopUpMenu("Shelf", false, false, B_ITEMS_IN_COLUMN); 717 718 // About 719 BMessage *msg = new BMessage(B_ABOUT_REQUESTED); 720 721 const char *name = fTarget->Name(); 722 if (name) 723 msg->AddString("target", name); 724 725 char about[B_OS_NAME_LENGTH]; 726 snprintf(about, B_OS_NAME_LENGTH, "About %s" B_UTF8_ELLIPSIS, name); 727 728 fPopUp->AddItem(new BMenuItem(about, msg)); 729 fPopUp->AddSeparatorItem(); 730 fPopUp->AddItem(new BMenuItem("Remove replicant", 731 new BMessage(kDeleteReplicant))); 732 } 733 734 735 void 736 BDragger::_ShowPopUp(BView *target, BPoint where) 737 { 738 BPoint point = ConvertToScreen(where); 739 740 if (!fPopUp && fTarget) 741 _BuildDefaultPopUp(); 742 743 fPopUp->SetTargetForItems(fTarget); 744 745 float menuWidth, menuHeight; 746 fPopUp->GetPreferredSize(&menuWidth, &menuHeight); 747 BRect rect(0, 0, menuWidth, menuHeight); 748 rect.InsetBy(-0.5, -0.5); 749 rect.OffsetTo(point); 750 751 fPopUp->Go(point, true, false, rect, true); 752 } 753 754 755 #if __GNUC__ < 3 756 757 extern "C" BBitmap* 758 _ReservedDragger1__8BDragger(BDragger* dragger, BPoint* offset, 759 drawing_mode* mode) 760 { 761 return dragger->BDragger::DragBitmap(offset, mode); 762 } 763 764 #endif 765