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