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