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