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