1 /* 2 * Copyright 2005, Jérôme Duval. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Inspired by SoundCapture from Be newsletter (Media Kit Basics: Consumers 6 * and Producers) 7 */ 8 9 #include <Bitmap.h> 10 #include <Debug.h> 11 #include <MessageFilter.h> 12 #include <Window.h> 13 14 #include <map> 15 16 #include "TransportButton.h" 17 #include "DrawingTidbits.h" 18 19 using std::map; 20 21 class BitmapStash { 22 // Bitmap stash is a simple class to hold all the lazily-allocated 23 // bitmaps that the TransportButton needs when rendering itself. 24 // signature is a combination of the different enabled, pressed, playing, etc. 25 // flavors of a bitmap. If the stash does not have a particular bitmap, 26 // it turns around to ask the button to create one and stores it for next time. 27 public: 28 BitmapStash(TransportButton *); 29 ~BitmapStash(); 30 BBitmap *GetBitmap(uint32 signature); 31 32 private: 33 TransportButton *owner; 34 map<uint32, BBitmap *> stash; 35 }; 36 37 38 BitmapStash::BitmapStash(TransportButton *owner) 39 : owner(owner) 40 { 41 } 42 43 44 BBitmap * 45 BitmapStash::GetBitmap(uint32 signature) 46 { 47 if (stash.find(signature) == stash.end()) { 48 BBitmap *newBits = owner->MakeBitmap(signature); 49 ASSERT(newBits); 50 stash[signature] = newBits; 51 } 52 53 return stash[signature]; 54 } 55 56 57 BitmapStash::~BitmapStash() 58 { 59 // delete all the bitmaps 60 for (map<uint32, BBitmap *>::iterator i = stash.begin(); 61 i != stash.end(); i++) 62 delete (*i).second; 63 } 64 65 66 class PeriodicMessageSender { 67 // used to send a specified message repeatedly when holding down a button 68 public: 69 static PeriodicMessageSender *Launch(BMessenger target, 70 const BMessage *message, bigtime_t period); 71 void Quit(); 72 73 private: 74 PeriodicMessageSender(BMessenger target, const BMessage *message, 75 bigtime_t period); 76 ~PeriodicMessageSender() {} 77 // use quit 78 79 static status_t TrackBinder(void *); 80 void Run(); 81 82 BMessenger target; 83 BMessage message; 84 85 bigtime_t period; 86 87 bool requestToQuit; 88 }; 89 90 91 PeriodicMessageSender::PeriodicMessageSender(BMessenger target, 92 const BMessage *message, bigtime_t period) 93 : target(target), 94 message(*message), 95 period(period), 96 requestToQuit(false) 97 { 98 } 99 100 101 PeriodicMessageSender * 102 PeriodicMessageSender::Launch(BMessenger target, const BMessage *message, 103 bigtime_t period) 104 { 105 PeriodicMessageSender *result = new PeriodicMessageSender(target, 106 message, period); 107 thread_id thread = spawn_thread(&PeriodicMessageSender::TrackBinder, 108 "ButtonRepeatingThread", B_NORMAL_PRIORITY, result); 109 110 if (thread <= 0 || resume_thread(thread) != B_OK) { 111 // didn't start, don't leak self 112 delete result; 113 result = 0; 114 } 115 116 return result; 117 } 118 119 120 void 121 PeriodicMessageSender::Quit() 122 { 123 requestToQuit = true; 124 } 125 126 127 status_t 128 PeriodicMessageSender::TrackBinder(void *castToThis) 129 { 130 ((PeriodicMessageSender *)castToThis)->Run(); 131 return 0; 132 } 133 134 135 void 136 PeriodicMessageSender::Run() 137 { 138 for (;;) { 139 snooze(period); 140 if (requestToQuit) 141 break; 142 target.SendMessage(&message); 143 } 144 delete this; 145 } 146 147 148 class SkipButtonKeypressFilter : public BMessageFilter { 149 public: 150 SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier, 151 TransportButton *target); 152 153 protected: 154 filter_result Filter(BMessage *message, BHandler **handler); 155 156 private: 157 uint32 shortcutKey; 158 uint32 shortcutModifier; 159 TransportButton *target; 160 }; 161 162 163 SkipButtonKeypressFilter::SkipButtonKeypressFilter(uint32 shortcutKey, 164 uint32 shortcutModifier, TransportButton *target) 165 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 166 shortcutKey(shortcutKey), 167 shortcutModifier(shortcutModifier), 168 target(target) 169 { 170 } 171 172 173 filter_result 174 SkipButtonKeypressFilter::Filter(BMessage *message, BHandler **handler) 175 { 176 if (target->IsEnabled() 177 && (message->what == B_KEY_DOWN || message->what == B_KEY_UP)) { 178 uint32 modifiers; 179 uint32 rawKeyChar = 0; 180 uint8 byte = 0; 181 int32 key = 0; 182 183 if (message->FindInt32("modifiers", (int32 *)&modifiers) != B_OK 184 || message->FindInt32("raw_char", (int32 *)&rawKeyChar) != B_OK 185 || message->FindInt8("byte", (int8 *)&byte) != B_OK 186 || message->FindInt32("key", &key) != B_OK) 187 return B_DISPATCH_MESSAGE; 188 189 modifiers &= B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY 190 | B_OPTION_KEY | B_MENU_KEY; 191 // strip caps lock, etc. 192 193 if (modifiers == shortcutModifier && rawKeyChar == shortcutKey) { 194 if (message->what == B_KEY_DOWN) 195 target->ShortcutKeyDown(); 196 else 197 target->ShortcutKeyUp(); 198 199 return B_SKIP_MESSAGE; 200 } 201 } 202 203 // let others deal with this 204 return B_DISPATCH_MESSAGE; 205 } 206 207 208 TransportButton::TransportButton(BRect frame, const char *name, 209 const unsigned char *normalBits, 210 const unsigned char *pressedBits, 211 const unsigned char *disabledBits, 212 BMessage *invokeMessage, BMessage *startPressingMessage, 213 BMessage *pressingMessage, BMessage *donePressingMessage, bigtime_t period, 214 uint32 key, uint32 modifiers, uint32 resizeFlags) 215 : BControl(frame, name, "", invokeMessage, resizeFlags, 216 B_WILL_DRAW | B_NAVIGABLE), 217 bitmaps(new BitmapStash(this)), 218 normalBits(normalBits), 219 pressedBits(pressedBits), 220 disabledBits(disabledBits), 221 startPressingMessage(startPressingMessage), 222 pressingMessage(pressingMessage), 223 donePressingMessage(donePressingMessage), 224 pressingPeriod(period), 225 mouseDown(false), 226 keyDown(false), 227 messageSender(0), 228 keyPressFilter(0) 229 { 230 if (key) 231 keyPressFilter = new SkipButtonKeypressFilter(key, modifiers, this); 232 } 233 234 235 void 236 TransportButton::AttachedToWindow() 237 { 238 _inherited::AttachedToWindow(); 239 if (keyPressFilter) 240 Window()->AddCommonFilter(keyPressFilter); 241 242 // transparent to reduce flicker 243 SetViewColor(B_TRANSPARENT_COLOR); 244 } 245 246 247 void 248 TransportButton::DetachedFromWindow() 249 { 250 if (keyPressFilter) { 251 Window()->RemoveCommonFilter(keyPressFilter); 252 delete keyPressFilter; 253 } 254 _inherited::DetachedFromWindow(); 255 } 256 257 258 TransportButton::~TransportButton() 259 { 260 delete startPressingMessage; 261 delete pressingMessage; 262 delete donePressingMessage; 263 delete bitmaps; 264 } 265 266 267 void 268 TransportButton::WindowActivated(bool state) 269 { 270 if (!state) 271 ShortcutKeyUp(); 272 273 _inherited::WindowActivated(state); 274 } 275 276 277 void 278 TransportButton::SetEnabled(bool on) 279 { 280 _inherited::SetEnabled(on); 281 if (!on) 282 ShortcutKeyUp(); 283 } 284 285 286 const unsigned char * 287 TransportButton::BitsForMask(uint32 mask) const 288 { 289 switch (mask) { 290 case 0: 291 return normalBits; 292 case kDisabledMask: 293 return disabledBits; 294 case kPressedMask: 295 return pressedBits; 296 default: 297 break; 298 } 299 TRESPASS(); 300 return 0; 301 } 302 303 304 BBitmap * 305 TransportButton::MakeBitmap(uint32 mask) 306 { 307 BBitmap *result = new BBitmap(Bounds(), B_CMAP8); 308 result->SetBits(BitsForMask(mask), (Bounds().Width() + 1) 309 * (Bounds().Height() + 1), 0, B_CMAP8); 310 311 ReplaceTransparentColor(result, Parent()->ViewColor()); 312 313 return result; 314 } 315 316 317 uint32 318 TransportButton::ModeMask() const 319 { 320 return (IsEnabled() ? 0 : kDisabledMask) 321 | (Value() ? kPressedMask : 0); 322 } 323 324 325 void 326 TransportButton::Draw(BRect) 327 { 328 DrawBitmapAsync(bitmaps->GetBitmap(ModeMask())); 329 } 330 331 332 void 333 TransportButton::StartPressing() 334 { 335 SetValue(1); 336 if (startPressingMessage) 337 Invoke(startPressingMessage); 338 339 if (pressingMessage) { 340 ASSERT(pressingMessage); 341 messageSender = PeriodicMessageSender::Launch(Messenger(), 342 pressingMessage, pressingPeriod); 343 } 344 } 345 346 347 void 348 TransportButton::MouseCancelPressing() 349 { 350 if (!mouseDown || keyDown) 351 return; 352 353 mouseDown = false; 354 355 if (pressingMessage) { 356 ASSERT(messageSender); 357 PeriodicMessageSender *sender = messageSender; 358 messageSender = 0; 359 sender->Quit(); 360 } 361 362 if (donePressingMessage) 363 Invoke(donePressingMessage); 364 SetValue(0); 365 } 366 367 368 void 369 TransportButton::DonePressing() 370 { 371 if (pressingMessage) { 372 ASSERT(messageSender); 373 PeriodicMessageSender *sender = messageSender; 374 messageSender = 0; 375 sender->Quit(); 376 } 377 378 Invoke(); 379 SetValue(0); 380 } 381 382 383 void 384 TransportButton::MouseStartPressing() 385 { 386 if (mouseDown) 387 return; 388 389 mouseDown = true; 390 if (!keyDown) 391 StartPressing(); 392 } 393 394 395 void 396 TransportButton::MouseDonePressing() 397 { 398 if (!mouseDown) 399 return; 400 401 mouseDown = false; 402 if (!keyDown) 403 DonePressing(); 404 } 405 406 407 void 408 TransportButton::ShortcutKeyDown() 409 { 410 if (!IsEnabled()) 411 return; 412 413 if (keyDown) 414 return; 415 416 keyDown = true; 417 if (!mouseDown) 418 StartPressing(); 419 } 420 421 422 void 423 TransportButton::ShortcutKeyUp() 424 { 425 if (!keyDown) 426 return; 427 428 keyDown = false; 429 if (!mouseDown) 430 DonePressing(); 431 } 432 433 434 void 435 TransportButton::MouseDown(BPoint) 436 { 437 if (!IsEnabled()) 438 return; 439 440 ASSERT(Window()->Flags() & B_ASYNCHRONOUS_CONTROLS); 441 SetTracking(true); 442 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 443 MouseStartPressing(); 444 } 445 446 void 447 TransportButton::MouseMoved(BPoint point, uint32 code, const BMessage *) 448 { 449 if (IsTracking() && Bounds().Contains(point) != Value()) { 450 if (!Value()) 451 MouseStartPressing(); 452 else 453 MouseCancelPressing(); 454 } 455 } 456 457 void 458 TransportButton::MouseUp(BPoint point) 459 { 460 if (IsTracking()) { 461 if (Bounds().Contains(point)) 462 MouseDonePressing(); 463 else 464 MouseCancelPressing(); 465 SetTracking(false); 466 } 467 } 468 469 void 470 TransportButton::SetStartPressingMessage(BMessage *message) 471 { 472 delete startPressingMessage; 473 startPressingMessage = message; 474 } 475 476 void 477 TransportButton::SetPressingMessage(BMessage *message) 478 { 479 delete pressingMessage; 480 pressingMessage = message; 481 } 482 483 void 484 TransportButton::SetDonePressingMessage(BMessage *message) 485 { 486 delete donePressingMessage; 487 donePressingMessage = message; 488 } 489 490 void 491 TransportButton::SetPressingPeriod(bigtime_t newTime) 492 { 493 pressingPeriod = newTime; 494 } 495 496 497 PlayPauseButton::PlayPauseButton(BRect frame, const char *name, 498 BMessage *invokeMessage, BMessage *blinkMessage, 499 uint32 key, uint32 modifiers, uint32 resizeFlags) 500 : TransportButton(frame, name, kPlayButtonBitmapBits, 501 kPressedPlayButtonBitmapBits, 502 kDisabledPlayButtonBitmapBits, invokeMessage, NULL, 503 NULL, NULL, 0, key, modifiers, resizeFlags), 504 fState(PlayPauseButton::kStopped), 505 fLastModeMask(0), 506 fRunner(NULL), 507 fBlinkMessage(blinkMessage) 508 { 509 } 510 511 void 512 PlayPauseButton::SetStopped() 513 { 514 if (fState == kStopped || fState == kAboutToPlay) 515 return; 516 517 fState = kStopped; 518 delete fRunner; 519 fRunner = NULL; 520 Invalidate(); 521 } 522 523 void 524 PlayPauseButton::SetPlaying() 525 { 526 if (fState == kAboutToPause) 527 return; 528 529 // in playing state blink the LED on and off 530 if (fState == kPlayingLedOn) 531 fState = kPlayingLedOff; 532 else 533 fState = kPlayingLedOn; 534 535 Invalidate(); 536 } 537 538 const bigtime_t kPlayingBlinkPeriod = 600000; 539 540 void 541 PlayPauseButton::SetPaused() 542 { 543 if (fState == kAboutToPlay) 544 return; 545 546 // in paused state blink the LED on and off 547 if (fState == kPausedLedOn) 548 fState = kPausedLedOff; 549 else 550 fState = kPausedLedOn; 551 552 Invalidate(); 553 } 554 555 uint32 556 PlayPauseButton::ModeMask() const 557 { 558 if (!IsEnabled()) 559 return kDisabledMask; 560 561 uint32 result = 0; 562 563 if (Value()) 564 result = kPressedMask; 565 566 if (fState == kPlayingLedOn || fState == kAboutToPlay) 567 result |= kPlayingMask; 568 else if (fState == kAboutToPause || fState == kPausedLedOn) 569 result |= kPausedMask; 570 571 return result; 572 } 573 574 const unsigned char * 575 PlayPauseButton::BitsForMask(uint32 mask) const 576 { 577 switch (mask) { 578 case kPlayingMask: 579 return kPlayingPlayButtonBitmapBits; 580 case kPlayingMask | kPressedMask: 581 return kPressedPlayingPlayButtonBitmapBits; 582 case kPausedMask: 583 return kPausedPlayButtonBitmapBits; 584 case kPausedMask | kPressedMask: 585 return kPressedPausedPlayButtonBitmapBits; 586 default: 587 return _inherited::BitsForMask(mask); 588 } 589 TRESPASS(); 590 return 0; 591 } 592 593 594 void 595 PlayPauseButton::StartPressing() 596 { 597 if (fState == kPlayingLedOn || fState == kPlayingLedOff) 598 fState = kAboutToPause; 599 else 600 fState = kAboutToPlay; 601 602 _inherited::StartPressing(); 603 } 604 605 void 606 PlayPauseButton::MouseCancelPressing() 607 { 608 if (fState == kAboutToPause) 609 fState = kPlayingLedOn; 610 else 611 fState = kStopped; 612 613 _inherited::MouseCancelPressing(); 614 } 615 616 void 617 PlayPauseButton::DonePressing() 618 { 619 if (fState == kAboutToPause) { 620 fState = kPausedLedOn; 621 } else if (fState == kAboutToPlay) { 622 fState = kPlayingLedOn; 623 if (!fRunner && fBlinkMessage) 624 fRunner = new BMessageRunner(Messenger(), fBlinkMessage, 625 kPlayingBlinkPeriod); 626 } 627 628 _inherited::DonePressing(); 629 } 630 631 632 RecordButton::RecordButton(BRect frame, const char *name, 633 BMessage *invokeMessage, BMessage *blinkMessage, 634 uint32 key, uint32 modifiers, uint32 resizeFlags) 635 : TransportButton(frame, name, kRecordButtonBitmapBits, 636 kPressedRecordButtonBitmapBits, 637 kDisabledRecordButtonBitmapBits, invokeMessage, NULL, NULL, 638 NULL, 0, key, modifiers, resizeFlags), 639 fState(RecordButton::kStopped), 640 fLastModeMask(0), 641 fRunner(NULL), 642 fBlinkMessage(blinkMessage) 643 { 644 } 645 646 void 647 RecordButton::SetStopped() 648 { 649 if (fState == kStopped || fState == kAboutToRecord) 650 return; 651 652 fState = kStopped; 653 delete fRunner; 654 fRunner = NULL; 655 Invalidate(); 656 } 657 658 const bigtime_t kRecordingBlinkPeriod = 600000; 659 660 void 661 RecordButton::SetRecording() 662 { 663 if (fState == kAboutToStop) 664 return; 665 666 if (fState == kRecordingLedOff) 667 fState = kRecordingLedOn; 668 else 669 fState = kRecordingLedOff; 670 671 Invalidate(); 672 } 673 674 uint32 675 RecordButton::ModeMask() const 676 { 677 if (!IsEnabled()) 678 return kDisabledMask; 679 680 uint32 result = 0; 681 682 if (Value()) 683 result = kPressedMask; 684 685 if (fState == kAboutToStop || fState == kRecordingLedOn) 686 result |= kRecordingMask; 687 688 return result; 689 } 690 691 const unsigned char * 692 RecordButton::BitsForMask(uint32 mask) const 693 { 694 switch (mask) { 695 case kRecordingMask: 696 return kRecordingRecordButtonBitmapBits; 697 case kRecordingMask | kPressedMask: 698 return kPressedRecordingRecordButtonBitmapBits; 699 default: 700 return _inherited::BitsForMask(mask); 701 } 702 TRESPASS(); 703 return 0; 704 } 705 706 707 void 708 RecordButton::StartPressing() 709 { 710 if (fState == kRecordingLedOn || fState == kRecordingLedOff) 711 fState = kAboutToStop; 712 else 713 fState = kAboutToRecord; 714 715 _inherited::StartPressing(); 716 } 717 718 void 719 RecordButton::MouseCancelPressing() 720 { 721 if (fState == kAboutToStop) 722 fState = kRecordingLedOn; 723 else 724 fState = kStopped; 725 726 _inherited::MouseCancelPressing(); 727 } 728 729 void 730 RecordButton::DonePressing() 731 { 732 if (fState == kAboutToStop) { 733 fState = kStopped; 734 delete fRunner; 735 fRunner = NULL; 736 } else if (fState == kAboutToRecord) { 737 fState = kRecordingLedOn; 738 if (!fRunner && fBlinkMessage) 739 fRunner = new BMessageRunner(Messenger(), fBlinkMessage, 740 kRecordingBlinkPeriod); 741 } 742 743 _inherited::DonePressing(); 744 } 745