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 19using std::map; 20 21class 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. 27public: 28 BitmapStash(TransportButton *); 29 ~BitmapStash(); 30 BBitmap *GetBitmap(uint32 signature); 31 32private: 33 TransportButton *owner; 34 map<uint32, BBitmap *> stash; 35}; 36 37 38BitmapStash::BitmapStash(TransportButton *owner) 39 : owner(owner) 40{ 41} 42 43 44BBitmap * 45BitmapStash::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 57BitmapStash::~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 66class PeriodicMessageSender { 67 // used to send a specified message repeatedly when holding down a button 68public: 69 static PeriodicMessageSender *Launch(BMessenger target, 70 const BMessage *message, bigtime_t period); 71 void Quit(); 72 73private: 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 91PeriodicMessageSender::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 101PeriodicMessageSender * 102PeriodicMessageSender::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 120void 121PeriodicMessageSender::Quit() 122{ 123 requestToQuit = true; 124} 125 126 127status_t 128PeriodicMessageSender::TrackBinder(void *castToThis) 129{ 130 ((PeriodicMessageSender *)castToThis)->Run(); 131 return 0; 132} 133 134 135void 136PeriodicMessageSender::Run() 137{ 138 for (;;) { 139 snooze(period); 140 if (requestToQuit) 141 break; 142 target.SendMessage(&message); 143 } 144 delete this; 145} 146 147 148class SkipButtonKeypressFilter : public BMessageFilter { 149public: 150 SkipButtonKeypressFilter(uint32 shortcutKey, uint32 shortcutModifier, 151 TransportButton *target); 152 153protected: 154 filter_result Filter(BMessage *message, BHandler **handler); 155 156private: 157 uint32 shortcutKey; 158 uint32 shortcutModifier; 159 TransportButton *target; 160}; 161 162 163SkipButtonKeypressFilter::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 173filter_result 174SkipButtonKeypressFilter::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 208TransportButton::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 235void 236TransportButton::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 247void 248TransportButton::DetachedFromWindow() 249{ 250 if (keyPressFilter) { 251 Window()->RemoveCommonFilter(keyPressFilter); 252 delete keyPressFilter; 253 } 254 _inherited::DetachedFromWindow(); 255} 256 257 258TransportButton::~TransportButton() 259{ 260 delete startPressingMessage; 261 delete pressingMessage; 262 delete donePressingMessage; 263 delete bitmaps; 264} 265 266 267void 268TransportButton::WindowActivated(bool state) 269{ 270 if (!state) 271 ShortcutKeyUp(); 272 273 _inherited::WindowActivated(state); 274} 275 276 277void 278TransportButton::SetEnabled(bool on) 279{ 280 _inherited::SetEnabled(on); 281 if (!on) 282 ShortcutKeyUp(); 283} 284 285 286const unsigned char * 287TransportButton::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 304BBitmap * 305TransportButton::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 317uint32 318TransportButton::ModeMask() const 319{ 320 return (IsEnabled() ? 0 : kDisabledMask) 321 | (Value() ? kPressedMask : 0); 322} 323 324 325void 326TransportButton::Draw(BRect) 327{ 328 DrawBitmapAsync(bitmaps->GetBitmap(ModeMask())); 329} 330 331 332void 333TransportButton::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 347void 348TransportButton::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 368void 369TransportButton::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 383void 384TransportButton::MouseStartPressing() 385{ 386 if (mouseDown) 387 return; 388 389 mouseDown = true; 390 if (!keyDown) 391 StartPressing(); 392} 393 394 395void 396TransportButton::MouseDonePressing() 397{ 398 if (!mouseDown) 399 return; 400 401 mouseDown = false; 402 if (!keyDown) 403 DonePressing(); 404} 405 406 407void 408TransportButton::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 422void 423TransportButton::ShortcutKeyUp() 424{ 425 if (!keyDown) 426 return; 427 428 keyDown = false; 429 if (!mouseDown) 430 DonePressing(); 431} 432 433 434void 435TransportButton::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 446void 447TransportButton::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 457void 458TransportButton::MouseUp(BPoint point) 459{ 460 if (IsTracking()) { 461 if (Bounds().Contains(point)) 462 MouseDonePressing(); 463 else 464 MouseCancelPressing(); 465 SetTracking(false); 466 } 467} 468 469void 470TransportButton::SetStartPressingMessage(BMessage *message) 471{ 472 delete startPressingMessage; 473 startPressingMessage = message; 474} 475 476void 477TransportButton::SetPressingMessage(BMessage *message) 478{ 479 delete pressingMessage; 480 pressingMessage = message; 481} 482 483void 484TransportButton::SetDonePressingMessage(BMessage *message) 485{ 486 delete donePressingMessage; 487 donePressingMessage = message; 488} 489 490void 491TransportButton::SetPressingPeriod(bigtime_t newTime) 492{ 493 pressingPeriod = newTime; 494} 495 496 497PlayPauseButton::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 511void 512PlayPauseButton::SetStopped() 513{ 514 if (fState == kStopped || fState == kAboutToPlay) 515 return; 516 517 fState = kStopped; 518 delete fRunner; 519 fRunner = NULL; 520 Invalidate(); 521} 522 523void 524PlayPauseButton::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 538const bigtime_t kPlayingBlinkPeriod = 600000; 539 540void 541PlayPauseButton::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 555uint32 556PlayPauseButton::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 574const unsigned char * 575PlayPauseButton::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 594void 595PlayPauseButton::StartPressing() 596{ 597 if (fState == kPlayingLedOn || fState == kPlayingLedOff) 598 fState = kAboutToPause; 599 else 600 fState = kAboutToPlay; 601 602 _inherited::StartPressing(); 603} 604 605void 606PlayPauseButton::MouseCancelPressing() 607{ 608 if (fState == kAboutToPause) 609 fState = kPlayingLedOn; 610 else 611 fState = kStopped; 612 613 _inherited::MouseCancelPressing(); 614} 615 616void 617PlayPauseButton::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 632RecordButton::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 646void 647RecordButton::SetStopped() 648{ 649 if (fState == kStopped || fState == kAboutToRecord) 650 return; 651 652 fState = kStopped; 653 delete fRunner; 654 fRunner = NULL; 655 Invalidate(); 656} 657 658const bigtime_t kRecordingBlinkPeriod = 600000; 659 660void 661RecordButton::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 674uint32 675RecordButton::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 691const unsigned char * 692RecordButton::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 707void 708RecordButton::StartPressing() 709{ 710 if (fState == kRecordingLedOn || fState == kRecordingLedOff) 711 fState = kAboutToStop; 712 else 713 fState = kAboutToRecord; 714 715 _inherited::StartPressing(); 716} 717 718void 719RecordButton::MouseCancelPressing() 720{ 721 if (fState == kAboutToStop) 722 fState = kRecordingLedOn; 723 else 724 fState = kStopped; 725 726 _inherited::MouseCancelPressing(); 727} 728 729void 730RecordButton::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